mirror of
https://gitlab.com/simple-stock-bots/simple-stock-bot.git
synced 2025-06-16 15:17:28 +00:00
Merge branch 'Upgrading-Dependencies' into 'master'
Upgrading dependencies See merge request simple-stock-bots/simple-telegram-stock-bot!17
This commit is contained in:
commit
7e7fbbceb2
45
bot.py
45
bot.py
@ -1,4 +1,4 @@
|
|||||||
# Works with Python 3.7
|
# Works with Python 3.8
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
@ -15,7 +15,6 @@ from telegram.ext import (
|
|||||||
from functions import Symbol
|
from functions import Symbol
|
||||||
|
|
||||||
TELEGRAM_TOKEN = os.environ["TELEGRAM"]
|
TELEGRAM_TOKEN = os.environ["TELEGRAM"]
|
||||||
|
|
||||||
IEX_TOKEN = os.environ["IEX"]
|
IEX_TOKEN = os.environ["IEX"]
|
||||||
|
|
||||||
s = Symbol(IEX_TOKEN)
|
s = Symbol(IEX_TOKEN)
|
||||||
@ -28,20 +27,18 @@ logger = logging.getLogger(__name__)
|
|||||||
print("Bot Online")
|
print("Bot Online")
|
||||||
|
|
||||||
|
|
||||||
# Define a few command handlers. These usually take the two arguments bot and
|
def start(update, context):
|
||||||
# update. Error handlers also receive the raised TelegramError object in error.
|
|
||||||
def start(bot, update):
|
|
||||||
"""Send a message when the command /start is issued."""
|
"""Send a message when the command /start is issued."""
|
||||||
update.message.reply_text("I am started and ready to go!")
|
update.message.reply_text("I am started and ready to go!")
|
||||||
|
|
||||||
|
|
||||||
def help(bot, update):
|
def help(update, context):
|
||||||
"""Send link to docs when the command /help is issued."""
|
"""Send link to docs when the command /help is issued."""
|
||||||
message = "[Please see the documentaion for Bot information](https://simple-stock-bots.gitlab.io/site/telegram/)"
|
message = "[Please see the documentaion for Bot information](https://simple-stock-bots.gitlab.io/site/telegram/)"
|
||||||
update.message.reply_text(text=message, parse_mode=telegram.ParseMode.MARKDOWN)
|
update.message.reply_text(text=message, parse_mode=telegram.ParseMode.MARKDOWN)
|
||||||
|
|
||||||
|
|
||||||
def symbol_detect(bot, update):
|
def symbol_detect(update, context):
|
||||||
"""
|
"""
|
||||||
Runs on any message that doesn't have a command and searches for symbols, then returns the prices of any symbols found.
|
Runs on any message that doesn't have a command and searches for symbols, then returns the prices of any symbols found.
|
||||||
"""
|
"""
|
||||||
@ -51,7 +48,7 @@ def symbol_detect(bot, update):
|
|||||||
|
|
||||||
if symbols:
|
if symbols:
|
||||||
# Let user know bot is working
|
# Let user know bot is working
|
||||||
bot.send_chat_action(chat_id=chat_id, action=telegram.ChatAction.TYPING)
|
context.bot.send_chat_action(chat_id=chat_id, action=telegram.ChatAction.TYPING)
|
||||||
|
|
||||||
for reply in s.price_reply(symbols).items():
|
for reply in s.price_reply(symbols).items():
|
||||||
|
|
||||||
@ -60,7 +57,7 @@ def symbol_detect(bot, update):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def dividend(bot, update):
|
def dividend(update, context):
|
||||||
"""
|
"""
|
||||||
waits for /dividend or /div command and then finds dividend info on that symbol.
|
waits for /dividend or /div command and then finds dividend info on that symbol.
|
||||||
"""
|
"""
|
||||||
@ -69,16 +66,16 @@ def dividend(bot, update):
|
|||||||
symbols = s.find_symbols(message)
|
symbols = s.find_symbols(message)
|
||||||
|
|
||||||
if symbols:
|
if symbols:
|
||||||
bot.send_chat_action(chat_id=chat_id, action=telegram.ChatAction.TYPING)
|
context.bot.send_chat_action(chat_id=chat_id, action=telegram.ChatAction.TYPING)
|
||||||
|
|
||||||
for reply in s.symbol_name(symbols).items():
|
for reply in s.dividend_reply(symbols).items():
|
||||||
|
|
||||||
update.message.reply_text(
|
update.message.reply_text(
|
||||||
text=reply[1], parse_mode=telegram.ParseMode.MARKDOWN
|
text=reply[1], parse_mode=telegram.ParseMode.MARKDOWN
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def news(bot, update):
|
def news(update, context):
|
||||||
"""
|
"""
|
||||||
waits for /news command and then finds news info on that symbol.
|
waits for /news command and then finds news info on that symbol.
|
||||||
"""
|
"""
|
||||||
@ -87,16 +84,15 @@ def news(bot, update):
|
|||||||
symbols = s.find_symbols(message)
|
symbols = s.find_symbols(message)
|
||||||
|
|
||||||
if symbols:
|
if symbols:
|
||||||
bot.send_chat_action(chat_id=chat_id, action=telegram.ChatAction.TYPING)
|
context.bot.send_chat_action(chat_id=chat_id, action=telegram.ChatAction.TYPING)
|
||||||
|
|
||||||
for reply in s.news_reply(symbols).items():
|
for reply in s.news_reply(symbols).items():
|
||||||
|
|
||||||
update.message.reply_text(
|
update.message.reply_text(
|
||||||
text=reply[1], parse_mode=telegram.ParseMode.MARKDOWN
|
text=reply[1], parse_mode=telegram.ParseMode.MARKDOWN
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def info(bot, update):
|
def info(update, context):
|
||||||
"""
|
"""
|
||||||
waits for /info command and then finds info on that symbol.
|
waits for /info command and then finds info on that symbol.
|
||||||
"""
|
"""
|
||||||
@ -105,26 +101,26 @@ def info(bot, update):
|
|||||||
symbols = s.find_symbols(message)
|
symbols = s.find_symbols(message)
|
||||||
|
|
||||||
if symbols:
|
if symbols:
|
||||||
bot.send_chat_action(chat_id=chat_id, action=telegram.ChatAction.TYPING)
|
context.bot.send_chat_action(chat_id=chat_id, action=telegram.ChatAction.TYPING)
|
||||||
|
|
||||||
for reply in s.info_reply(symbols).items():
|
for reply in s.info_reply(symbols).items():
|
||||||
|
|
||||||
update.message.reply_text(
|
update.message.reply_text(
|
||||||
text=reply[1], parse_mode=telegram.ParseMode.MARKDOWN
|
text=reply[1], parse_mode=telegram.ParseMode.MARKDOWN
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def inline_query(bot, update):
|
def inline_query(update, context):
|
||||||
"""
|
"""
|
||||||
Handles inline query.
|
Handles inline query.
|
||||||
Does a fuzzy search on input and returns stocks that are close.
|
Does a fuzzy search on input and returns stocks that are close.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
matches = s.search_symbols(update.inline_query.query)
|
matches = s.search_symbols(update.inline_query.query)
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
for match in matches:
|
for match in matches:
|
||||||
try:
|
try:
|
||||||
price = s.price_reply([match[0]])[match[0]]
|
price = s.price_reply([match[0]])[match[0]]
|
||||||
print(price)
|
|
||||||
results.append(
|
results.append(
|
||||||
InlineQueryResultArticle(
|
InlineQueryResultArticle(
|
||||||
match[0],
|
match[0],
|
||||||
@ -135,22 +131,23 @@ def inline_query(bot, update):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
|
logging.warning(str(match))
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if len(results) == 5:
|
if len(results) == 5:
|
||||||
bot.answerInlineQuery(update.inline_query.id, results)
|
update.inline_query.answer(results)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def error(bot, update, error):
|
def error(update, context):
|
||||||
"""Log Errors caused by Updates."""
|
"""Log Errors caused by Updates."""
|
||||||
logger.warning('Update "%s" caused error "%s"', update, error)
|
logger.warning('Update "%s" caused error "%s"', update, error)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Start the bot."""
|
"""Start the context.bot."""
|
||||||
# Create the EventHandler and pass it your bot's token.
|
# Create the EventHandler and pass it your bot's token.
|
||||||
updater = Updater(TELEGRAM_TOKEN)
|
updater = Updater(TELEGRAM_TOKEN, use_context=True)
|
||||||
|
|
||||||
# Get the dispatcher to register handlers
|
# Get the dispatcher to register handlers
|
||||||
dp = updater.dispatcher
|
dp = updater.dispatcher
|
||||||
|
87
functions.py
87
functions.py
@ -6,58 +6,97 @@ from datetime import datetime, timedelta
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
import requests as r
|
import requests as r
|
||||||
from fuzzywuzzy import fuzz
|
from fuzzywuzzy import fuzz
|
||||||
|
import schedule
|
||||||
|
|
||||||
|
|
||||||
class Symbol:
|
class Symbol:
|
||||||
|
"""
|
||||||
|
Functions for finding stock market information about symbols.
|
||||||
|
"""
|
||||||
|
|
||||||
SYMBOL_REGEX = "[$]([a-zA-Z]{1,4})"
|
SYMBOL_REGEX = "[$]([a-zA-Z]{1,4})"
|
||||||
LIST_URL = "http://oatsreportable.finra.org/OATSReportableSecurities-SOD.txt"
|
|
||||||
|
searched_symbols = {}
|
||||||
|
|
||||||
def __init__(self, IEX_TOKEN: str):
|
def __init__(self, IEX_TOKEN: str):
|
||||||
self.IEX_TOKEN = IEX_TOKEN
|
self.IEX_TOKEN = IEX_TOKEN
|
||||||
self.symbol_list, self.symbol_ts = self.get_symbol_list()
|
self.get_symbol_list()
|
||||||
|
schedule.every().day.do(self.get_symbol_list)
|
||||||
|
|
||||||
def get_symbol_list(self):
|
def get_symbol_list(self, return_df=False):
|
||||||
raw_symbols = r.get(self.LIST_URL).text
|
"""
|
||||||
symbols = pd.DataFrame(
|
Fetches a list of stock market symbols from FINRA
|
||||||
[line.split("|") for line in raw_symbols.split("\n")][:-1]
|
|
||||||
)
|
Returns:
|
||||||
symbols.columns = symbols.iloc[0]
|
pd.DataFrame -- [DataFrame with columns: Symbol | Issue_Name | Primary_Listing_Mkt
|
||||||
symbols = symbols.drop(symbols.index[0])
|
datetime -- The time when the list of symbols was fetched. The Symbol list is updated every open and close of every trading day.
|
||||||
symbols = symbols.drop(symbols.index[-1])
|
"""
|
||||||
symbols["Description"] = symbols["Symbol"] + ": " + symbols["Issue_Name"]
|
raw_symbols = r.get(
|
||||||
return symbols, datetime.now()
|
f"https://cloud.iexapis.com/stable/ref-data/symbols?token={self.IEX_TOKEN}"
|
||||||
|
).json()
|
||||||
|
symbols = pd.DataFrame(data=raw_symbols)
|
||||||
|
|
||||||
|
symbols["description"] = symbols["symbol"] + ": " + symbols["name"]
|
||||||
|
self.symbol_list = symbols
|
||||||
|
if return_df:
|
||||||
|
return symbols, datetime.now()
|
||||||
|
|
||||||
def search_symbols(self, search: str):
|
def search_symbols(self, search: str):
|
||||||
if self.symbol_ts - datetime.now() > timedelta(hours=12):
|
"""
|
||||||
self.symbol_list, self.symbol_ts = self.get_symbol_list()
|
Performs a fuzzy search to find stock symbols closest to a search term.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
search {str} -- String used to search, could be a company name or something close to the companies stock ticker.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of Tuples -- A list tuples of every stock sorted in order of how well they match. Each tuple contains: (Symbol, Issue Name).
|
||||||
|
"""
|
||||||
|
schedule.run_pending()
|
||||||
|
search = search.lower()
|
||||||
|
try: # https://stackoverflow.com/a/3845776/8774114
|
||||||
|
return self.searched_symbols[search]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
symbols = self.symbol_list
|
symbols = self.symbol_list
|
||||||
symbols["Match"] = symbols.apply(
|
symbols["Match"] = symbols.apply(
|
||||||
lambda x: fuzz.ratio(search.lower(), f"{x['Symbol']}".lower()), axis=1,
|
lambda x: fuzz.ratio(search, f"{x['symbol']}".lower()), axis=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
symbols.sort_values(by="Match", ascending=False, inplace=True)
|
symbols.sort_values(by="Match", ascending=False, inplace=True)
|
||||||
if symbols["Match"].head().sum() < 300:
|
if symbols["Match"].head().sum() < 300:
|
||||||
symbols["Match"] = symbols.apply(
|
symbols["Match"] = symbols.apply(
|
||||||
lambda x: fuzz.partial_ratio(
|
lambda x: fuzz.partial_ratio(search, x["name"].lower()), axis=1,
|
||||||
search.lower(), f"{x['Symbol']} {x['Issue_Name']}".lower()
|
|
||||||
),
|
|
||||||
axis=1,
|
|
||||||
)
|
)
|
||||||
symbols.sort_values(by="Match", ascending=False, inplace=True)
|
|
||||||
|
|
||||||
return list(zip(list(symbols["Symbol"]), list(symbols["Description"])))
|
symbols.sort_values(by="Match", ascending=False, inplace=True)
|
||||||
|
symbols = symbols.head(10)
|
||||||
|
symbol_list = list(zip(list(symbols["symbol"]), list(symbols["description"])))
|
||||||
|
self.searched_symbols[search] = symbol_list
|
||||||
|
return symbol_list
|
||||||
|
|
||||||
def find_symbols(self, text: str):
|
def find_symbols(self, text: str):
|
||||||
"""
|
"""
|
||||||
Takes a blob of text and returns a list of symbols without any repeats.
|
Finds stock tickers starting with a dollar sign in a blob of text and returns them in a list. Only returns each match once. Example: Whats the price of $tsla? -> ['tsla']
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
text {str} -- Blob of text that might contain tickers with the format: $TICKER
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list -- List of every found match without the dollar sign.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return list(set(re.findall(self.SYMBOL_REGEX, text)))
|
return list(set(re.findall(self.SYMBOL_REGEX, text)))
|
||||||
|
|
||||||
def price_reply(self, symbols: list):
|
def price_reply(self, symbols: list):
|
||||||
"""
|
"""
|
||||||
Takes a list of symbols and returns a dictionary of strings with information about the symbol.
|
Takes a list of symbols and replies with Markdown formatted text about the symbols price change for the day.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
symbols {list} -- List of stock market symbols.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict -- Dictionary with keys of symbols and values of markdown formatted text example: {'tsla': 'The current stock price of Tesla Motors is $**420$$, the stock price is currently **up 42%**}
|
||||||
"""
|
"""
|
||||||
dataMessages = {}
|
dataMessages = {}
|
||||||
for symbol in symbols:
|
for symbol in symbols:
|
||||||
@ -82,7 +121,7 @@ class Symbol:
|
|||||||
|
|
||||||
return dataMessages
|
return dataMessages
|
||||||
|
|
||||||
def symbol_name(self, symbols: list):
|
def dividend_reply(self, symbols: list):
|
||||||
divMessages = {}
|
divMessages = {}
|
||||||
|
|
||||||
for symbol in symbols:
|
for symbol in symbols:
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
python-telegram-bot==11.1.0
|
python-telegram-bot==12.5.1
|
||||||
requests==2.21.0
|
requests==2.23.0
|
||||||
pandas==0.25.3
|
pandas==1.0.3
|
||||||
fuzzywuzzy==0.18.0
|
fuzzywuzzy==0.18.0
|
||||||
python-Levenshtein==0.12.0
|
python-Levenshtein==0.12.0
|
||||||
|
schedule==0.6.0
|
Loading…
x
Reference in New Issue
Block a user