mirror of
https://gitlab.com/simple-stock-bots/simple-telegram-stock-bot.git
synced 2025-06-16 06:56:46 +00:00
start on #18
This commit is contained in:
parent
baf45e09c8
commit
acc0ebdd28
@ -9,7 +9,7 @@ import requests as r
|
||||
import schedule
|
||||
from fuzzywuzzy import fuzz
|
||||
import os
|
||||
|
||||
from logging import warning
|
||||
from Symbol import Stock
|
||||
|
||||
|
||||
@ -36,7 +36,7 @@ class IEX_Symbol:
|
||||
self.IEX_TOKEN = os.environ["IEX"]
|
||||
except KeyError:
|
||||
self.IEX_TOKEN = ""
|
||||
print(
|
||||
warning(
|
||||
"Starting without an IEX Token will not allow you to get market data!"
|
||||
)
|
||||
|
||||
|
72
bot.py
72
bot.py
@ -7,6 +7,8 @@ import html
|
||||
import json
|
||||
import traceback
|
||||
|
||||
from logging import debug, info, warning, error, critical
|
||||
|
||||
import mplfinance as mpf
|
||||
from uuid import uuid4
|
||||
|
||||
@ -36,7 +38,7 @@ try:
|
||||
STRIPE_TOKEN = os.environ["STRIPE"]
|
||||
except KeyError:
|
||||
STRIPE_TOKEN = ""
|
||||
print("Starting without a STRIPE Token will not allow you to accept Donations!")
|
||||
warning("Starting without a STRIPE Token will not allow you to accept Donations!")
|
||||
|
||||
s = Router()
|
||||
t = T_info()
|
||||
@ -47,11 +49,12 @@ logging.basicConfig(
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
print("Bot Online")
|
||||
info("Bot script started.")
|
||||
|
||||
|
||||
def start(update: Update, context: CallbackContext):
|
||||
"""Send a message when the command /start is issued."""
|
||||
info(f"Start command ran by {update.message.chat.username}")
|
||||
update.message.reply_text(
|
||||
text=t.help_text,
|
||||
parse_mode=telegram.ParseMode.MARKDOWN,
|
||||
@ -61,6 +64,7 @@ def start(update: Update, context: CallbackContext):
|
||||
|
||||
def help(update: Update, context: CallbackContext):
|
||||
"""Send link to docs when the command /help is issued."""
|
||||
info(f"Help command ran by {update.message.chat.username}")
|
||||
update.message.reply_text(
|
||||
text=t.help_text,
|
||||
parse_mode=telegram.ParseMode.MARKDOWN,
|
||||
@ -70,6 +74,7 @@ def help(update: Update, context: CallbackContext):
|
||||
|
||||
def license(update: Update, context: CallbackContext):
|
||||
"""Return bots license agreement"""
|
||||
info(f"License command ran by {update.message.chat.username}")
|
||||
update.message.reply_text(
|
||||
text=t.license,
|
||||
parse_mode=telegram.ParseMode.MARKDOWN,
|
||||
@ -78,6 +83,7 @@ def license(update: Update, context: CallbackContext):
|
||||
|
||||
|
||||
def status(update: Update, context: CallbackContext):
|
||||
warning(f"Status command ran by {update.message.chat.username}")
|
||||
bot_resp = datetime.datetime.now(update.message.date.tzinfo) - update.message.date
|
||||
|
||||
update.message.reply_text(
|
||||
@ -90,6 +96,7 @@ def status(update: Update, context: CallbackContext):
|
||||
|
||||
|
||||
def donate(update: Update, context: CallbackContext):
|
||||
info(f"Donate command ran by {update.message.chat.username}")
|
||||
chat_id = update.message.chat_id
|
||||
|
||||
if update.message.text.strip() == "/donate":
|
||||
@ -107,7 +114,7 @@ def donate(update: Update, context: CallbackContext):
|
||||
except ValueError:
|
||||
update.message.reply_text(f"{amount} is not a valid donation amount or number.")
|
||||
return
|
||||
print(price)
|
||||
info(f"Donation amount: {price}")
|
||||
|
||||
context.bot.send_invoice(
|
||||
chat_id=chat_id,
|
||||
@ -126,6 +133,7 @@ def donate(update: Update, context: CallbackContext):
|
||||
|
||||
|
||||
def precheckout_callback(update: Update, context: CallbackContext):
|
||||
info(f"precheckout_callback queried")
|
||||
query = update.pre_checkout_query
|
||||
|
||||
query.answer(ok=True)
|
||||
@ -138,6 +146,7 @@ def precheckout_callback(update: Update, context: CallbackContext):
|
||||
|
||||
|
||||
def successful_payment_callback(update: Update, context: CallbackContext):
|
||||
info(f"Successful payment!")
|
||||
update.message.reply_text(
|
||||
"Thank you for your donation! It goes a long way to keeping the bot free!"
|
||||
)
|
||||
@ -148,6 +157,7 @@ def symbol_detect(update: Update, context: CallbackContext):
|
||||
Runs on any message that doesn't have a command and searches for symbols,
|
||||
then returns the prices of any symbols found.
|
||||
"""
|
||||
|
||||
message = update.message.text
|
||||
chat_id = update.message.chat_id
|
||||
symbols = s.find_symbols(message)
|
||||
@ -155,7 +165,9 @@ def symbol_detect(update: Update, context: CallbackContext):
|
||||
if symbols:
|
||||
# Let user know bot is working
|
||||
context.bot.send_chat_action(chat_id=chat_id, action=telegram.ChatAction.TYPING)
|
||||
print(symbols)
|
||||
info(f"User called for symbols: {update.message.chat.username}")
|
||||
info(f"Symbols found: {symbols}")
|
||||
|
||||
for reply in s.price_reply(symbols):
|
||||
update.message.reply_text(
|
||||
text=reply,
|
||||
@ -168,6 +180,7 @@ def dividend(update: Update, context: CallbackContext):
|
||||
"""
|
||||
waits for /dividend or /div command and then finds dividend info on that symbol.
|
||||
"""
|
||||
info(f"Dividend command ran by {update.message.chat.username}")
|
||||
message = update.message.text
|
||||
chat_id = update.message.chat_id
|
||||
|
||||
@ -193,6 +206,7 @@ def news(update: Update, context: CallbackContext):
|
||||
"""
|
||||
waits for /news command and then finds news info on that symbol.
|
||||
"""
|
||||
info(f"News command ran by {update.message.chat.username}")
|
||||
message = update.message.text
|
||||
chat_id = update.message.chat_id
|
||||
|
||||
@ -215,10 +229,11 @@ def news(update: Update, context: CallbackContext):
|
||||
)
|
||||
|
||||
|
||||
def info(update: Update, context: CallbackContext):
|
||||
def information(update: Update, context: CallbackContext):
|
||||
"""
|
||||
waits for /info command and then finds info on that symbol.
|
||||
"""
|
||||
info(f"Information command ran by {update.message.chat.username}")
|
||||
message = update.message.text
|
||||
chat_id = update.message.chat_id
|
||||
|
||||
@ -242,6 +257,7 @@ def info(update: Update, context: CallbackContext):
|
||||
|
||||
|
||||
def search(update: Update, context: CallbackContext):
|
||||
info(f"Search command ran by {update.message.chat.username}")
|
||||
message = update.message.text.replace("/search ", "")
|
||||
chat_id = update.message.chat_id
|
||||
|
||||
@ -265,6 +281,7 @@ def search(update: Update, context: CallbackContext):
|
||||
|
||||
|
||||
def intra(update: Update, context: CallbackContext):
|
||||
info(f"Intra command ran by {update.message.chat.username}")
|
||||
# TODO: Document usage of this command. https://iexcloud.io/docs/api/#historical-prices
|
||||
|
||||
message = update.message.text
|
||||
@ -320,6 +337,7 @@ def intra(update: Update, context: CallbackContext):
|
||||
|
||||
|
||||
def chart(update: Update, context: CallbackContext):
|
||||
info(f"Chart command ran by {update.message.chat.username}")
|
||||
# TODO: Document usage of this command. https://iexcloud.io/docs/api/#historical-prices
|
||||
|
||||
message = update.message.text
|
||||
@ -350,7 +368,7 @@ def chart(update: Update, context: CallbackContext):
|
||||
context.bot.send_chat_action(
|
||||
chat_id=chat_id, action=telegram.ChatAction.UPLOAD_PHOTO
|
||||
)
|
||||
print(symbol)
|
||||
|
||||
buf = io.BytesIO()
|
||||
mpf.plot(
|
||||
df,
|
||||
@ -375,6 +393,7 @@ def stat(update: Update, context: CallbackContext):
|
||||
"""
|
||||
https://iexcloud.io/docs/api/#key-stats
|
||||
"""
|
||||
info(f"Stat command ran by {update.message.chat.username}")
|
||||
message = update.message.text
|
||||
chat_id = update.message.chat_id
|
||||
|
||||
@ -401,6 +420,7 @@ def cap(update: Update, context: CallbackContext):
|
||||
"""
|
||||
Market Cap Information
|
||||
"""
|
||||
info(f"Cap command ran by {update.message.chat.username}")
|
||||
message = update.message.text
|
||||
chat_id = update.message.chat_id
|
||||
|
||||
@ -427,6 +447,7 @@ def trending(update: Update, context: CallbackContext):
|
||||
"""
|
||||
Trending Symbols
|
||||
"""
|
||||
info(f"Trending command ran by {update.message.chat.username}")
|
||||
|
||||
chat_id = update.message.chat_id
|
||||
|
||||
@ -444,14 +465,14 @@ def inline_query(update: Update, context: CallbackContext):
|
||||
Handles inline query.
|
||||
Does a fuzzy search on input and returns stocks that are close.
|
||||
"""
|
||||
|
||||
info(f"Inline command ran by {update.message.chat.username}")
|
||||
info(f"Query: {update.inline_query.query}")
|
||||
matches = s.inline_search(update.inline_query.query)[:5]
|
||||
|
||||
symbols = " ".join([match[1].split(":")[0] for match in matches])
|
||||
prices = s.batch_price_reply(s.find_symbols(symbols))
|
||||
|
||||
results = []
|
||||
print(update.inline_query.query)
|
||||
for match, price in zip(matches, prices):
|
||||
try:
|
||||
results.append(
|
||||
@ -464,16 +485,20 @@ def inline_query(update: Update, context: CallbackContext):
|
||||
)
|
||||
)
|
||||
except TypeError:
|
||||
logging.warning(str(match))
|
||||
warning(f"{match} caused error in inline query.")
|
||||
pass
|
||||
print(match[0], "\n\n\n")
|
||||
|
||||
if len(results) == 5:
|
||||
update.inline_query.answer(results)
|
||||
info("Inline Command was successful")
|
||||
return
|
||||
update.inline_query.answer(results)
|
||||
|
||||
|
||||
def rand_pick(update: Update, context: CallbackContext):
|
||||
info(
|
||||
f"Someone is gambling! Random_pick command ran by {update.message.chat.username}"
|
||||
)
|
||||
|
||||
update.message.reply_text(
|
||||
text=s.random_pick(),
|
||||
@ -484,22 +509,25 @@ def rand_pick(update: Update, context: CallbackContext):
|
||||
|
||||
def error(update: Update, context: CallbackContext):
|
||||
"""Log Errors caused by Updates."""
|
||||
logger.warning('Update "%s" caused error "%s"', update, error)
|
||||
warning('Update "%s" caused error "%s"', update, error)
|
||||
|
||||
tb_list = traceback.format_exception(
|
||||
None, context.error, context.error.__traceback__
|
||||
)
|
||||
tb_string = "".join(tb_list)
|
||||
print(tb_string)
|
||||
# if update:
|
||||
# message = (
|
||||
# f"An exception was raised while handling an update\n"
|
||||
# f"<pre>update = {html.escape(json.dumps(update.to_dict(), indent=2, ensure_ascii=False))}"
|
||||
# "</pre>\n\n"
|
||||
# f"<pre>context.chat_data = {html.escape(str(context.chat_data))}</pre>\n\n"
|
||||
# f"<pre>context.user_data = {html.escape(str(context.user_data))}</pre>\n\n"
|
||||
# f"<pre>{html.escape(tb_string)}</pre>"
|
||||
# )
|
||||
|
||||
if update:
|
||||
message = (
|
||||
f"An exception was raised while handling an update\n"
|
||||
f"<pre>update = {html.escape(json.dumps(update.to_dict(), indent=2, ensure_ascii=False))}"
|
||||
"</pre>\n\n"
|
||||
f"<pre>context.chat_data = {html.escape(str(context.chat_data))}</pre>\n\n"
|
||||
f"<pre>context.user_data = {html.escape(str(context.user_data))}</pre>\n\n"
|
||||
f"<pre>{html.escape(tb_string)}</pre>"
|
||||
)
|
||||
warning(message)
|
||||
else:
|
||||
warning(tb_string)
|
||||
update.message.reply_text(
|
||||
text="An error has occured. Please inform @MisterBiggs if the error persists."
|
||||
)
|
||||
@ -524,7 +552,7 @@ def main():
|
||||
dp.add_handler(CommandHandler("dividend", dividend))
|
||||
dp.add_handler(CommandHandler("div", dividend))
|
||||
dp.add_handler(CommandHandler("news", news))
|
||||
dp.add_handler(CommandHandler("info", info))
|
||||
dp.add_handler(CommandHandler("info", information))
|
||||
dp.add_handler(CommandHandler("stat", stat))
|
||||
dp.add_handler(CommandHandler("stats", stat))
|
||||
dp.add_handler(CommandHandler("cap", cap))
|
||||
|
@ -7,7 +7,7 @@ import random
|
||||
import datetime
|
||||
from fuzzywuzzy import fuzz
|
||||
|
||||
from typing import List, Tuple
|
||||
from logging import debug, info, warning, error, critical
|
||||
|
||||
from IEX_Symbol import IEX_Symbol
|
||||
from cg_Crypto import cg_Crypto
|
||||
@ -24,7 +24,7 @@ class Router:
|
||||
self.stock = IEX_Symbol()
|
||||
self.crypto = cg_Crypto()
|
||||
|
||||
def find_symbols(self, text: str) -> List[Symbol]:
|
||||
def find_symbols(self, text: str) -> list[Symbol]:
|
||||
"""Finds stock tickers starting with a dollar sign, and cryptocurrencies with two dollar signs
|
||||
in a blob of text and returns them in a list.
|
||||
Only returns each match once. Example: Whats the price of $tsla?
|
||||
@ -36,7 +36,7 @@ class Router:
|
||||
|
||||
Returns
|
||||
-------
|
||||
List[str]
|
||||
list[str]
|
||||
List of stock symbols as strings without dollar sign.
|
||||
"""
|
||||
symbols = []
|
||||
@ -45,15 +45,15 @@ class Router:
|
||||
if stock.upper() in self.stock.symbol_list["symbol"].values:
|
||||
symbols.append(Stock(stock))
|
||||
else:
|
||||
print(f"{stock} is not in list of stocks")
|
||||
info(f"{stock} is not in list of stocks")
|
||||
|
||||
coins = set(re.findall(self.CRYPTO_REGEX, text))
|
||||
for coin in coins:
|
||||
if coin.lower() in self.crypto.symbol_list["symbol"].values:
|
||||
symbols.append(Coin(coin.lower()))
|
||||
else:
|
||||
print(f"{coin} is not in list of coins")
|
||||
print(symbols)
|
||||
info(f"{coin} is not in list of coins")
|
||||
info(symbols)
|
||||
return symbols
|
||||
|
||||
def status(self, bot_resp) -> str:
|
||||
@ -65,7 +65,7 @@ class Router:
|
||||
Human readable text on status of the bot and relevant APIs
|
||||
"""
|
||||
|
||||
return f"""
|
||||
stats = f"""
|
||||
Bot Status:
|
||||
{bot_resp}
|
||||
|
||||
@ -76,7 +76,11 @@ class Router:
|
||||
{self.crypto.status()}
|
||||
"""
|
||||
|
||||
def search_symbols(self, search: str) -> List[Tuple[str, str]]:
|
||||
warning(stats)
|
||||
|
||||
return stats
|
||||
|
||||
def search_symbols(self, search: str) -> list[tuple[str, str]]:
|
||||
"""Performs a fuzzy search to find stock symbols closest to a search term.
|
||||
|
||||
Parameters
|
||||
@ -86,7 +90,7 @@ class Router:
|
||||
|
||||
Returns
|
||||
-------
|
||||
List[tuple[str, str]]
|
||||
list[tuple[str, str]]
|
||||
A list tuples of every stock sorted in order of how well they match. Each tuple contains: (Symbol, Issue Name).
|
||||
"""
|
||||
|
||||
@ -113,7 +117,7 @@ class Router:
|
||||
self.searched_symbols[search] = symbol_list
|
||||
return symbol_list
|
||||
|
||||
def inline_search(self, search: str) -> List[Tuple[str, str]]:
|
||||
def inline_search(self, search: str) -> list[tuple[str, str]]:
|
||||
"""Searches based on the shortest symbol that contains the same string as the search.
|
||||
Should be very fast compared to a fuzzy search.
|
||||
|
||||
@ -124,7 +128,7 @@ class Router:
|
||||
|
||||
Returns
|
||||
-------
|
||||
List[tuple[str, str]]
|
||||
list[tuple[str, str]]
|
||||
Each tuple contains: (Symbol, Issue Name).
|
||||
"""
|
||||
|
||||
@ -141,7 +145,7 @@ class Router:
|
||||
self.searched_symbols[search] = symbol_list
|
||||
return symbol_list
|
||||
|
||||
def price_reply(self, symbols: list[Symbol]) -> List[str]:
|
||||
def price_reply(self, symbols: list[Symbol]) -> list[str]:
|
||||
"""Returns current market price or after hours if its available for a given stock symbol.
|
||||
|
||||
Parameters
|
||||
@ -158,17 +162,17 @@ class Router:
|
||||
replies = []
|
||||
|
||||
for symbol in symbols:
|
||||
print(symbol)
|
||||
info(symbol)
|
||||
if isinstance(symbol, Stock):
|
||||
replies.append(self.stock.price_reply(symbol))
|
||||
elif isinstance(symbol, Coin):
|
||||
replies.append(self.crypto.price_reply(symbol))
|
||||
else:
|
||||
print(f"{symbol} is not a Stock or Coin")
|
||||
info(f"{symbol} is not a Stock or Coin")
|
||||
|
||||
return replies
|
||||
|
||||
def dividend_reply(self, symbols: list) -> List[str]:
|
||||
def dividend_reply(self, symbols: list) -> list[str]:
|
||||
"""Returns the most recent, or next dividend date for a stock symbol.
|
||||
|
||||
Parameters
|
||||
@ -192,7 +196,7 @@ class Router:
|
||||
|
||||
return replies
|
||||
|
||||
def news_reply(self, symbols: list) -> List[str]:
|
||||
def news_reply(self, symbols: list) -> list[str]:
|
||||
"""Gets recent english news on stock symbols.
|
||||
|
||||
Parameters
|
||||
@ -220,12 +224,12 @@ class Router:
|
||||
|
||||
return replies
|
||||
|
||||
def info_reply(self, symbols: list) -> List[str]:
|
||||
def info_reply(self, symbols: list) -> list[str]:
|
||||
"""Gets information on stock symbols.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
symbols : List[str]
|
||||
symbols : list[str]
|
||||
List of stock symbols.
|
||||
|
||||
Returns
|
||||
@ -289,12 +293,12 @@ class Router:
|
||||
print(f"{symbol} is not a Stock or Coin")
|
||||
return pd.DataFrame()
|
||||
|
||||
def stat_reply(self, symbols: List[Symbol]) -> List[str]:
|
||||
def stat_reply(self, symbols: list[Symbol]) -> list[str]:
|
||||
"""Gets key statistics for each symbol in the list
|
||||
|
||||
Parameters
|
||||
----------
|
||||
symbols : List[str]
|
||||
symbols : list[str]
|
||||
List of stock symbols
|
||||
|
||||
Returns
|
||||
@ -314,12 +318,12 @@ class Router:
|
||||
|
||||
return replies
|
||||
|
||||
def cap_reply(self, symbols: List[Symbol]) -> List[str]:
|
||||
def cap_reply(self, symbols: list[Symbol]) -> list[str]:
|
||||
"""Gets market cap for each symbol in the list
|
||||
|
||||
Parameters
|
||||
----------
|
||||
symbols : List[str]
|
||||
symbols : list[str]
|
||||
List of stock symbols
|
||||
|
||||
Returns
|
||||
@ -375,7 +379,7 @@ class Router:
|
||||
|
||||
return f"{choice}\nBuy and hold until: {hold}"
|
||||
|
||||
def batch_price_reply(self, symbols: list[Symbol]) -> List[str]:
|
||||
def batch_price_reply(self, symbols: list[Symbol]) -> list[str]:
|
||||
"""Returns current market price or after hours if its available for a given stock symbol.
|
||||
|
||||
Parameters
|
||||
|
Loading…
x
Reference in New Issue
Block a user