def stonk_upcoming_earnings(update: Update, context: CallbackContext): # TODO: Optimize this to load all symbols for event lookup in one go instead of per symbol. stonks = context.chat_data.get(conf.INTERNALS['stock'], {}) columns = ['Company', 'Sym.', 'Date', '-days'] data = [] if len(stonks) > 0: now = datetime.now() for k, s in sorted(stonks.items()): ue = s.upcoming_earning() date = 'N/A' days_left = 'N/A' if ue: date = ue.strftime('%Y-%m-%d') days_left = (ue - now).days data.append([s.name, s.symbol, date, days_left]) df = pd.DataFrame(data, columns=columns) reply = df.to_string(index=False, formatters={'Company': '{:.10}'.format}) reply = f'📅 📅 📅\n\n{reply}' else: reply = '🧻🤲 Watch list is empty.' reply_message(update, reply, parse_mode=ParseMode.HTML, pre=True)
def price(update: Update, context: CallbackContext, reply: bool = True, symbols: Union[bool, List[Union[None, str]]] = False): if not symbols: symbols = parse_symbols(update, context.args) if len(symbols) == 0: return False for symbol in symbols: try: s = Stonk(symbol) except InvalidSymbol: reply_symbol_error(update, symbol) continue p_text = s.details_price_textual() if reply: reply_message(update, p_text, parse_mode=ParseMode.HTML, pre=True) else: send_message(context, update.effective_message.chat_id, p_text, parse_mode=ParseMode.HTML, pre=True)
def show_chats(update: Update, context: CallbackContext) -> None: """Shows which chats the bot is in""" users = '' groups = '' channels = '' for key, user in context.bot_data.setdefault(conf.INTERNALS['users'], {}).items(): users += f'{user.chat.username} ({user.chat.id}),' users = users[:-1] if users != '' else 'N/A' for key, group in context.bot_data.setdefault(conf.INTERNALS['groups'], {}).items(): groups += f'{group.chat.title} ({group.chat.id}) by {group.cause_user.username} ({group.cause_user.id}),' groups = groups[:-1] if groups != '' else 'N/A' for key, channel in context.bot_data.setdefault(conf.INTERNALS['channels'], {}).items(): channels += f'{channel.chat.title} ({channel.chat.id}) by {channel.cause_user.username} (' \ f'{channel.cause_user.id}),' channels = channels[:-1] if channels != '' else 'N/A' text = (f'Users: {users}.\n\n' f'Groups: {groups}.\n\n' f'Channels: {channels}.') reply_message(update, text, parse_mode=ParseMode.HTML, pre=True)
def list_price(update: Update, context: CallbackContext) -> NoReturn: stonks = context.chat_data.get(conf.INTERNALS['stock'], {}) columns = [ 'Sym.', '⬆️ H', '⬇️️ L', '🛬 C', f"±{conf.LOCAL['currency']}", '±%' ] data = [] if len(stonks) > 0: for k, s in sorted(stonks.items()): dp = s.price_daily() data.append( [s.symbol, dp.high, dp.low, dp.close, dp.diff, dp.percent]) df = pd.DataFrame(data, columns=columns) reply = df.to_string(index=False, formatters={ columns[1]: formatter_conditional_no_dec, columns[2]: formatter_conditional_no_dec, columns[3]: formatter_conditional_no_dec, columns[4]: formatter_conditional_no_dec, columns[5]: formatter_conditional_no_dec }) reply = f'🚀 🚀 🚀 📉 📉 📉\n\n{reply}' else: reply = '🧻🤲 Watch list is empty.' reply_message(update, reply, parse_mode=ParseMode.HTML, pre=True)
def stonk_del(update: Update, context: CallbackContext) -> Union[None, bool]: symbols = parse_symbols(update, context.args) if len(symbols) == 0: return False for symbol in symbols: symbol: str = symbol.upper() stonks = context.chat_data.get(conf.INTERNALS['stock'], {}) if symbol in stonks: stonks.pop(symbol, None) context.chat_data[conf.INTERNALS['stock']] = stonks msg_daily = get_daily_dict(context.chat_data) rise = msg_daily.get( conf.JOBS['check_rise_fall_day']['dict']['rise'], factory_defaultdict()) fall = msg_daily.get( conf.JOBS['check_rise_fall_day']['dict']['fall'], factory_defaultdict()) if rise and len(rise) > 0: msg_daily[conf.JOBS['check_rise_fall_day']['dict'] ['rise']].pop(symbol, None) if fall and len(fall) > 0: msg_daily[conf.JOBS['check_rise_fall_day']['dict'] ['fall']].pop(symbol, None) reply = f'✅ Symbol <b>{symbol}</b> was removed.' else: reply = f'⚠️ Symbol <b>{symbol}</b> is not in watchlist.' reply_message(update, reply, parse_mode=ParseMode.HTML)
def stonk_add(update: Update, context: CallbackContext) -> Union[None, bool]: symbols = parse_symbols(update, context.args) if len(symbols) == 0: return False for symbol in symbols: s = False try: s = Stonk(symbol) except InvalidSymbol: reply_symbol_error(update, symbol) return False stonks = context.chat_data.get(conf.INTERNALS['stock'], {}) if s.symbol not in stonks: stonks[s.symbol] = s context.chat_data[conf.INTERNALS['stock']] = stonks reply = f"✅ {s.name} ({s.symbol}; ISIN: {s.isin}) added to watchlist." else: stonk_list(update, context) reply_random_gif(update, 'boring') reply = f"⚠️ {s.name} ({s.symbol}; ISIN: {s.isin}) is already in the watchlist." reply_message(update, reply)
def orders(update: Update, context: CallbackContext): count = parse_daily_perf_count(update, context.args) d = Discovery() text = d.orders(count) text = f'📖 📖 📖\n\n{text}\n\n<a href="https://finance.yahoo.com/most-active">Source</a>' reply_message(update, text, parse_mode=ParseMode.HTML, pre=True)
def losers(update: Update, context: CallbackContext): count = parse_daily_perf_count(update, context.args) d = Discovery() text = d.losers(count) text = f"""📉 📉 📉 ({conf.LOCAL['currency']})\n\n{text}\n\n<a href="https://finance.yahoo.com/losers">Source</a>""" reply_message(update, text, parse_mode=ParseMode.HTML, pre=True)
def discovery_websites(update: Update, context: CallbackContext): d = Discovery() message_html = '🔍 Useful discovery sources:\n' for item in d.websites: message_html += f"* <a href=\"{item['url']}\">{item['description']}</>\n" reply_message(update, message_html, parse_mode=ParseMode.HTML)
def hot_penny(update: Update, context: CallbackContext): count = parse_daily_perf_count(update, context.args) d = Discovery() text = d.hot_pennystocks(count) text = f"""🔥 👼 💰 ({conf.LOCAL['currency']})\n\n{text}\n\n<a href="https://www.pennystockflow.com/">Source</a>""" reply_message(update, text, parse_mode=ParseMode.HTML, pre=True)
def high_short(update: Update, context: CallbackContext): count = parse_daily_perf_count(update, context.args) d = Discovery() text = d.high_short(count) text = f'🩳 🩳 🩳\n\n{text}\n\n<a href="https://www.highshortinterest.com/">Source</a>' reply_message(update, text, parse_mode=ParseMode.HTML, pre=True)
def low_float(update: Update, context: CallbackContext): count = parse_daily_perf_count(update, context.args) d = Discovery() text = d.low_float(count) text = f'🤲 🤲 🤲\n\n{text}\n\n<a href="https://www.lowfloat.com/">Source</a>' reply_message(update, text, parse_mode=ParseMode.HTML, pre=True)
def underval_growth(update: Update, context: CallbackContext): count = parse_daily_perf_count(update, context.args) d = Discovery() text = d.undervalued_growth(count) text = f"""👼 🕺 🕺 ({conf.LOCAL['currency']})\n\n{text}\n\n<a href="https://finance.yahoo.com/screener/predefined/undervalued_growth_stocks">Source</a>""" reply_message(update, text, parse_mode=ParseMode.HTML, pre=True)
def r_samoyed_coin(update: Update, context: CallbackContext): args = parse_reddit(update, context.args) if not args: return False s = RedditAnalysis(context, update) result = s.samoyedcoin(args['sort'], args['count']) reply_message(update, result, parse_mode=ParseMode.HTML)
def mauerstrassenwetten(update: Update, context: CallbackContext): args = parse_reddit(update, context.args) if not args: return False s = RedditAnalysis(context, update) result = s.mauerstrassenwetten(args['sort'], args['count']) reply_message(update, result, parse_mode=ParseMode.HTML)
def stock_messages(update: Update, context: CallbackContext): symbols = parse_symbols(update, context.args) if len(symbols) == 0: return False st = Stocktwits() text = st.messages_ticker(symbols) result = f'🐣 Stocktwits Messages 💬\n\n{text}' reply_message(update, result, parse_mode=ParseMode.HTML)
def bullbear(update: Update, context: CallbackContext): symbols = parse_symbols(update, context.args) if len(symbols) == 0: return False st = Stocktwits() text = st.bullbear(symbols) result = f'🐣 Stocktwits Analysis 🔍\n\n{text}' reply_message(update, result, parse_mode=ParseMode.HTML, pre=True)
def help_admin(update: Update, context: CallbackContext) -> NoReturn: reply = """Hi admin, I am the STONKS BOT! You are allowed to use the following commands: * /stonk_clear | /sc -> Clears the watchlist. * /all_stonk_clear | /asc -> Clears the watchlist of every user. * /exec_job_check_rise_fall | /ejcrf -> Executes check_rise_fall immediately. * /all_stonk_clear | /asc -> Clears all stonk lists of all chats. * /bot_list_all_data | /blad -> Lists internal data storage. * /show_chats -> Lists all chats. * /chat_data_reset | /acdr -> Clear all chat data in `bot_data`. """ reply_message(update, reply)
def stonk_list(update: Update, context: CallbackContext) -> NoReturn: stonks = context.chat_data.get(conf.INTERNALS['stock'], {}) reply = '' if len(stonks) > 0: for k in sorted(stonks.keys()): reply += f'💎 {stonks[k].name} ({k})\n' reply = reply[0:-1] else: reply = '🧻🤲 Watch list is empty.' reply_message(update, reply)
def help(update: Update, context: CallbackContext) -> NoReturn: reply = """Hi ape, I am the STONKS BOT! Try to use the following commands: * /help | /h -> This help. Fundamental: * /chart [<SYMBOLs/ISINs>] | /c -> Plot the last trading day of a stock. * /price | /p [<SYMBOLs/ISINs>] -> Get details about the stonk price * /details | /d [<SYMBOLs/ISINs>] -> Shortcut for /chart & /price. * /stonk_add [<SYMBOLs/ISINs>] | /sa -> Add a stock to the watchlist. * /stonk_del [<SYMBOLs/ISINs>] | /sd -> Delete a stock from the watchlist. * /stonk_list | /sl -> Show the watchlist. * /stonk_clear | /sc -> Clears the watchlist (not allowed in group chats). * /list_price | /lp -> List watchlist prices. Discovery: * /discovery | /di -> Useful infos to find hot stocks. * /sector_performance | /sp -> Show sector daily performance. * /upcoming_earnings | /ue -> Show upcoming earning dates. * /stonk_upcoming_earnings | /sue -> Show upcoming earning dates for watched stonks. * /gainers (<count>) | /g -> Show daily gainers. * /losers (<count>) | /l -> Show daily losers. * /orders (<count>) | /o -> Show daily high volume stonks. * /high_short (<count>) | /hs -> Show stonks with high short interest. * /low_float (<count>) | /lf -> Show stonks with low float. * /hot_penny (<count>) | /hp -> Show hot penny stonks. * /underval_large (<count>) | /ul -> Show undervalued large cap stonks. * /underval_growth (<count>) | /ug -> Show undervalued growth stonks. Sentiment: * /wallstreetbets (<sort={hot, rising, new} count>) | /wsb -> Show relevant r/wallstreetbets posts. * /mauerstrassenwetten (<sort={hot, rising, new} count>) | /msw -> Zeige relevante r/mauerstrassenwetten Einträge. * /investing (<sort={hot, rising, new} count>) | /ri -> Show relevant r/investing posts. * /rstocks (<sort={hot, rising, new} count>) | /rs -> Show relevant r/stocks posts. * /gamestop (<sort={hot, rising, new} count>) | /gme -> Show relevant r/gamestop posts. * /spielstopp (<sort={hot, rising, new} count>) | /rss -> Show relevant r/spielstopp posts. * /stockmarket (<sort={hot, rising, new} count>) | /rsm -> Show relevant r/stockmarket posts. * /daytrading (<sort={hot, rising, new} count>) | /rdt -> Show relevant r/daytrading posts. * /pennystocks (<sort={hot, rising, new} count>) | /rps -> Show relevant r/pennystocks posts. * /cryptomarkets (<sort={hot, rising, new} count>) | /rcm -> Show relevant r/cryptomarkets posts. * /satoshistreetbets (<sort={hot, rising, new} count>) | /ssb -> Show relevant r/satoshistreetbets posts. * /rsamoyedcoin (<sort={hot, rising, new} count>) | /rsc -> Show relevant r/samoyedcoin posts. * /popular_symbols (<sort={hot, rising, new} count>) | /ps -> Show popular symbols from Reddit. * /bullbear [<SYMBOLs>] | /bb -> Bull / Bear analysis for chosen symbols. * /stock_messages [<SYMBOLs>] | /sm -> Get the latest TwitStock messages for chosen symbols. * /trending_symbols | /ts -> Get the latest trending symbols from TwitStock. """ reply_message(update, reply)
def bot_list_all_data(update: Update, context: CallbackContext): user_data = context.dispatcher.user_data chat_data = context.dispatcher.chat_data bot_data = context.dispatcher.bot_data chat_ids = list( set( list(user_data.keys()) + list(chat_data.keys()) + list(bot_data.get(conf.INTERNALS['groups'], {}).keys()))) bot = context.bot result = 'User Info:\n' for c_id in chat_ids: user_info = bot.get_chat(c_id).to_dict() user_info.pop('photo', None) user_info = html.escape(formatter_to_json(user_info)) result += f'{user_info}\n' result += f'\nBot Data:\n{formatter_to_json(bot_data)}\n\n' result += f'\nChat Data:\n{formatter_to_json(chat_data)}\n\n' result += f'\nUser Data:\n{formatter_to_json(user_data)}' reply_message(update, result, parse_mode=ParseMode.HTML, pre=True)
def popular_symbols(self, days: int = 1, limit: int = 30, convert_currency: bool = True) -> str: subs = [ 'pennystocks', 'Daytrading', 'StockMarket', 'stocks', 'investing', 'wallstreetbets', 'mauerstrassenwetten' ] columns = [ 'Company', 'Symbol', 'Mentions', 'Price', '% 1mo.', 'Earnings Date', 'Earnings Days Left' ] data = [] timestamp_after = int( (datetime.today() - timedelta(days=days)).timestamp()) psaw_api = PushshiftAPI() praw_api = self._get_reddit_client() tickers = [] for sub in subs: comments = psaw_api.search_submissions(after=timestamp_after, subreddit=sub, limit=limit, filter=['id']) for c in comments: try: t = praw_api.submission(id=c.id) if not t.removed_by_category and (t.selftext or t.title): tickers += self._find_tickers(t) except Exception as e: # TODO: If HTTP 5xx save datetime to bot_data and do not execute for x minutes. msg = '💔 The remote data source is having issues or has blocked me. Try again MUCH later.' if self.update: reply_message(self.update, msg) reply_random_gif(self.update, 'bot broken') raise e if len(tickers) > 0: now = datetime.now() tickers_stats = dict(Counter(tickers)) tickers_dedup = list(tickers_stats.keys()) # TODO: Refactor to own method. blacklist = [ 'YOLO', 'LMAO', 'LOL', 'MOFO', 'HAHA', 'DFV', 'WSB', 'MSW', 'CEO', 'CTO', 'CFO', 'CIO', 'USER' ] tickers_clean = [t for t in tickers_dedup if t not in blacklist] history = yf.download(' '.join(tickers_clean), period='1mo', prepost=True, actions=False, progress=False, group_by='ticker') tickers_not_existing = list(yf.shared._ERRORS.keys()) tickers_existing = [ t for t in tickers_clean if t not in tickers_not_existing ] tickers = yf.Tickers(' '.join(tickers_existing)) for symbol in tickers_existing: try: t = getattr(tickers.tickers, symbol) t_info = t.info except: continue earnings_date = None earnings_days_left = None if t.calendar is not None: if len(t.calendar.T) > 0: earnings_date = t.calendar.iloc[:, 0]['Earnings Date'] earnings_days_left = (earnings_date - now).days price = history[symbol]['Close'][-1] perf_1mo = (price / history[symbol]['Open'][0]) - 1 # TODO: Refactor with stonk.py (extract method or something like this). name = t_info.get( 'longName', t_info.get('shortName', t_info.get('name', 'ERROR_IN_NAME_RETRIEVAL'))) data.append([ name, symbol, tickers_stats[symbol], price, perf_1mo, earnings_date, earnings_days_left ]) df = pd.DataFrame(data, columns=columns) if convert_currency: columns_to_convert = [columns[3]] df = self.currency.convert_to_currency_df( self.currency_api, df, columns_to_convert) df = df.sort_values(by=columns[2], ascending=False) result = df[columns[:-1]].to_string( header=['Company', 'Sym', '#', 'Price', '% 1mo', 'Earn.📅'], index=False, formatters={ columns[0]: '{:.9}'.format, columns[3]: formatter_round_currency_scalar, columns[4]: formatter_conditional_no_dec, columns[5]: formatter_date }) else: result = 'No symbols could be found. Try again later.' return result
def stonk_clear(update: Update, context: CallbackContext) -> NoReturn: context.chat_data[conf.INTERNALS['stock']] = {} # clear_daily_dict(context.chat_data) reply = f'🖤 Watch list purged.' reply_message(update, reply)
def trending_symbols(update: Update, context: CallbackContext): st = Stocktwits() text = st.trending() result = f'🐣 Stocktwits Trending 🚀\n\n{text}' reply_message(update, result, parse_mode=ParseMode.HTML, pre=True)
def upcoming_earnings(update: Update, context: CallbackContext): d = Discovery() text = d.upcoming_earnings() text = f'🗓️ 🗓️ 🗓️\n\n{text}' reply_message(update, text, parse_mode=ParseMode.HTML, pre=True)
def popular_symbols(update: Update, context: CallbackContext): s = RedditAnalysis(context, update) text = s.popular_symbols() result = f"💁 💁 💁 ({conf.LOCAL['currency']})\n\n{text}" reply_message(update, result, parse_mode=ParseMode.HTML, pre=True)