From 2db75c1d023d0ed0768a629f434e36f32db26db3 Mon Sep 17 00:00:00 2001 From: didier Date: Sun, 31 Jan 2021 19:59:22 +0000 Subject: [PATCH] Add Insider Activity over time command from Business Insider --- due_diligence/business_insider_api.py | 113 ++++++++++++++++++++++++++ due_diligence/dd_menu.py | 6 +- 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/due_diligence/business_insider_api.py b/due_diligence/business_insider_api.py index db6fb424a63..abca8d91f0c 100644 --- a/due_diligence/business_insider_api.py +++ b/due_diligence/business_insider_api.py @@ -201,3 +201,116 @@ def estimates(l_args, s_ticker): except: print("") return + + +# ----------------------------------------------- INSIDER_ACTIVITY ----------------------------------------------- +def insider_activity(l_args, df_stock, s_ticker, s_start, s_interval): + parser = argparse.ArgumentParser(prog='insider_activity', + description="""Insider activity over time [Source: Business Insider API]""") + parser.add_argument('-n', "--num", action="store", dest="n_num", type=check_positive, default=10, help='Number of latest insider activity') + + try: + (ns_parser, l_unknown_args) = parser.parse_known_args(l_args) + + if l_unknown_args: + print(f"The following args couldn't be interpreted: {l_unknown_args}\n") + return + + url_market_business_insider = f"https://markets.businessinsider.com/stocks/{s_ticker.lower()}-stock" + text_soup_market_business_insider = BeautifulSoup(requests.get(url_market_business_insider).text, "lxml") + + d_insider = dict() + l_insider_vals = list() + for idx, insider_val in enumerate(text_soup_market_business_insider.findAll('td', {'class':"table__td text-center"})): + #print(insider_val.text.strip()) + + l_insider_vals.append(insider_val.text.strip()) + + # Add value to dictionary + if (idx+1)%6 == 0: + # Check if we are still parsing insider trading activity + if '/' not in l_insider_vals[0]: + break + d_insider[(idx+1)//6] = l_insider_vals + l_insider_vals = list() + + df_insider = pd.DataFrame.from_dict(d_insider, orient='index', + columns = ['Date', 'Shares Traded', 'Shares Held', 'Price', 'Type', 'Option']) + + df_insider['Date'] = pd.to_datetime(df_insider['Date']) + df_insider = df_insider.set_index('Date') + df_insider = df_insider.sort_index(ascending=True) + + if s_start: + df_insider = df_insider[s_start:] + + pfig, ax = plt.subplots() + + if s_interval == "1440min": + plt.plot(df_stock.index, df_stock['5. adjusted close'].values, lw=3) + else: # Intraday + plt.plot(df_stock.index, df_stock['4. close'].values, lw=3) + + plt.title(f"{s_ticker.upper()} (Time Series) and Price Target") + + plt.xlabel('Time') + plt.ylabel('Share Price ($)') + + df_insider['Trade'] = df_insider.apply(lambda row: (1, -1)[row.Type == 'Sell'] * float(row['Shares Traded'].replace(',','')), axis=1) + plt.xlim(df_insider.index[0], df_stock.index[-1]) + min_price, max_price = ax.get_ylim() + + price_range = max_price - min_price + shares_range = df_insider[df_insider['Type'] == 'Buy'].groupby(by=['Date']).sum()['Trade'].max() - df_insider[df_insider['Type'] == 'Sell'].groupby(by=['Date']).sum()['Trade'].min() + n_proportion = price_range / shares_range + + for ind in df_insider[df_insider['Type'] == 'Sell'].groupby(by=['Date']).sum().index: + if ind in df_stock.index: + ind_dt = ind + else: + ind_dt = get_next_stock_market_days(ind, 1)[0] + + n_stock_price = 0 + if s_interval == "1440min": + n_stock_price = df_stock['5. adjusted close'][ind_dt] + else: + n_stock_price = df_stock['4. close'][ind_dt] + + plt.vlines(x=ind_dt, + ymin= n_stock_price + n_proportion * float(df_insider[df_insider['Type'] == 'Sell'].groupby(by=['Date']).sum()['Trade'][ind]), + ymax= n_stock_price, + colors='red', ls='-', lw=5) + + for ind in df_insider[df_insider['Type'] == 'Buy'].groupby(by=['Date']).sum().index: + if ind in df_stock.index: + ind_dt = ind + else: + ind_dt = get_next_stock_market_days(ind, 1)[0] + + n_stock_price = 0 + if s_interval == "1440min": + n_stock_price = df_stock['5. adjusted close'][ind_dt] + else: + n_stock_price = df_stock['4. close'][ind_dt] + + plt.vlines(x=ind_dt, + ymin=n_stock_price, + ymax=n_stock_price + n_proportion * float(df_insider[df_insider['Type'] == 'Buy'].groupby(by=['Date']).sum()['Trade'][ind]), + colors='green', ls='-', lw=5) + + plt.grid(b=True, which='major', color='#666666', linestyle='-') + plt.minorticks_on() + plt.grid(b=True, which='minor', color='#999999', linestyle='-', alpha=0.2) + plt.show() + + l_names = list() + for s_name in text_soup_market_business_insider.findAll('a', {'onclick':"silentTrackPI()"}): + l_names.append(s_name.text.strip()) + df_insider['Insider'] = l_names + + print(df_insider.sort_index(ascending=False).head(n=ns_parser.n_num).to_string()) + print("") + + except: + print("") + return diff --git a/due_diligence/dd_menu.py b/due_diligence/dd_menu.py index f223abe23e9..d908b674ac4 100644 --- a/due_diligence/dd_menu.py +++ b/due_diligence/dd_menu.py @@ -40,6 +40,7 @@ def print_due_diligence(s_ticker, s_start, s_interval): print(" rating rating of the company from strong sell to strong buy [FMP]") print(" pt price targets over time [Business Insider]") print(" est quarter and year analysts earnings estimates [Business Insider]") + print(" ins insider activity over time [Business Insider]") print(" insider insider trading of the company [Finviz]") print(" sec SEC filings [Market Watch]") print(" short short interest [Quandl]") @@ -57,7 +58,7 @@ def dd_menu(df_stock, s_ticker, s_start, s_interval): 'red', # Reddit 'short', # Quandl 'rating', # FMP - 'pt', 'est', # BI + 'pt', 'est', 'ins', # BI 'insider', 'news', 'analyst', # Finviz 'warnings', 'sec']) # MW @@ -108,6 +109,9 @@ def dd_menu(df_stock, s_ticker, s_start, s_interval): elif ns_known_args.cmd == 'est': bi_api.estimates(l_args, s_ticker) + elif ns_known_args.cmd == 'ins': + bi_api.insider_activity(l_args, df_stock, s_ticker, s_start, s_interval) + # FINANCIAL MODELING PREP API elif ns_known_args.cmd == 'rating': fmp_api.rating(l_args, s_ticker)