diff --git a/IEX_Symbol.py b/IEX_Symbol.py index 7c00c01..2fdae3a 100644 --- a/IEX_Symbol.py +++ b/IEX_Symbol.py @@ -30,7 +30,7 @@ class IEX_Symbol: Parameters ---------- IEX_TOKEN : str - IEX Token + IEX API Token """ try: self.IEX_TOKEN = os.environ["IEX"] @@ -47,12 +47,28 @@ class IEX_Symbol: schedule.every().day.do(self.clear_charts) def clear_charts(self) -> None: - """Clears cache of chart data.""" + """ + Clears cache of chart data. + Charts are cached so that only 1 API call per 24 hours is needed since the + chart data is expensive and a large download. + """ self.charts = {} def get_symbol_list( self, return_df=False ) -> Optional[Tuple[pd.DataFrame, datetime]]: + """Gets list of all symbols supported by IEX + + Parameters + ---------- + return_df : bool, optional + return the dataframe of all stock symbols, by default False + + Returns + ------- + Optional[Tuple[pd.DataFrame, datetime]] + If `return_df` is set to `True` returns a dataframe, otherwise returns `None`. + """ reg_symbols = r.get( f"https://cloud.iexapis.com/stable/ref-data/symbols?token={self.IEX_TOKEN}", @@ -145,18 +161,16 @@ class IEX_Symbol: return symbol_list def price_reply(self, symbol: Stock) -> str: - """Returns current market price or after hours if its available for a given stock symbol. + """Returns price movement of Stock for the last market day, or after hours. Parameters ---------- - symbols : list - List of stock symbols. + symbol : Stock Returns ------- - Dict[str, str] - Each symbol passed in is a key with its value being a human readable - markdown formatted string of the symbols price and movement. + str + Formatted markdown """ IEXurl = f"https://cloud.iexapis.com/stable/stock/{symbol.id}/quote?token={self.IEX_TOKEN}" @@ -223,13 +237,12 @@ class IEX_Symbol: Parameters ---------- - symbols : list - List of stock symbols. + symbol : Stock Returns ------- - Dict[str, str] - Each symbol passed in is a key with its value being a human readable formatted string of the symbols div dates. + str + Formatted markdown """ if symbol.symbol.upper() in self.otc_list: return "OTC stocks do not currently support any commands." @@ -284,17 +297,16 @@ class IEX_Symbol: return f"${symbol.id.upper()} either doesn't exist or pays no dividend." def news_reply(self, symbol: Stock) -> str: - """Gets recent english news on stock symbols. + """Gets most recent, english, non-paywalled news Parameters ---------- - symbols : list - List of stock symbols. + symbol : Stock Returns ------- - Dict[str, str] - Each symbol passed in is a key with its value being a human readable markdown formatted string of the symbols news. + str + Formatted markdown """ if symbol.symbol.upper() in self.otc_list: return "OTC stocks do not currently support any commands." @@ -323,17 +335,16 @@ class IEX_Symbol: return f"News for **{symbol.id.upper()}**:\n" + "\n".join(line[:5]) def info_reply(self, symbol: Stock) -> str: - """Gets information on stock symbols. + """Gets description for Stock Parameters ---------- - symbols : List[str] - List of stock symbols. + symbol : Stock Returns ------- - Dict[str, str] - Each symbol passed in is a key with its value being a human readable formatted string of the symbols information. + str + Formatted text """ if symbol.symbol.upper() in self.otc_list: return "OTC stocks do not currently support any commands." @@ -354,17 +365,16 @@ class IEX_Symbol: return f"No information found for: {symbol}\nEither today is boring or the symbol does not exist." def stat_reply(self, symbol: Stock) -> str: - """Gets key statistics for each symbol in the list + """Key statistics on a Stock Parameters ---------- - symbols : List[str] - List of stock symbols + symbol : Stock Returns ------- - Dict[str, str] - Each symbol passed in is a key with its value being a human readable formatted string of the symbols statistics. + str + Formatted markdown """ if symbol.symbol.upper() in self.otc_list: return "OTC stocks do not currently support any commands." @@ -503,7 +513,7 @@ class IEX_Symbol: Returns ------- list[str] - list of $ID: NAME + list of $ID: NAME, CHANGE% """ stocks = r.get( diff --git a/Symbol.py b/Symbol.py index 3e204c6..8c702a1 100644 --- a/Symbol.py +++ b/Symbol.py @@ -25,6 +25,8 @@ class Symbol: class Stock(Symbol): + """Stock Market Object. Gets data from IEX Cloud""" + def __init__(self, symbol: str) -> None: self.symbol = symbol self.id = symbol @@ -36,6 +38,8 @@ coins = r.get("https://api.coingecko.com/api/v3/coins/list").json() class Coin(Symbol): + """Cryptocurrency Object. Gets data from CoinGecko.""" + @functools.cache def __init__(self, symbol: str) -> None: self.symbol = symbol diff --git a/T_info.py b/T_info.py index 66d07ef..f87446c 100644 --- a/T_info.py +++ b/T_info.py @@ -21,7 +21,7 @@ Keep up with the latest news for the bot in its Telegram Channel: https://t.me/s Full documentation on using and running your own stock bot can be found [on the bots website.](https://simple-stock-bots.gitlab.io/site) -The bot detects _"Symbols"_ using either one or two dollar signs before the symbol. One dollar sign is for a stock market ticker, while two is for a cryptocurrency coin. `/chart $$eth` would return a chart of the past month of data for Ethereum, while `/dividend $psec` returns dividend information for Prospect Capital stock. +The bot detects _"Symbols"_ using either one `$` or two `$$` dollar signs before the symbol. One dollar sign is for a stock market ticker, while two is for a cryptocurrency coin. `/chart $$eth` would return a chart of the past month of data for Ethereum, while `/dividend $psec` returns dividend information for Prospect Capital stock. Simply calling a symbol in any message that the bot can see will also return the price. So a message like: `I wonder if $$btc will go to the Moon now that $tsla accepts it as payment` would return the current price for both Bitcoin and Tesla. @@ -67,7 +67,7 @@ info - $[symbol] General information about the symbol. âšī¸ news - $[symbol] News about the symbol. đ° stat - $[symbol] Key statistics about the symbol. đĸ dividend - $[symbol] Dividend info đ -intra - $[symbol] Plot since the last market open. đ trending - Trending Stocks and Cryptos. đŦ +intra - $[symbol] Plot since the last market open. đ chart - $[chart] Plot of the past month. đ """ # Not used by the bot but for updaing commands with BotFather diff --git a/bot.py b/bot.py index 8f2f934..4fe6e9b 100644 --- a/bot.py +++ b/bot.py @@ -53,7 +53,7 @@ info("Bot script started.") def start(update: Update, context: CallbackContext): - """Send a message when the command /start is issued.""" + """Send help text when the command /start is issued.""" info(f"Start command ran by {update.message.chat.username}") update.message.reply_text( text=t.help_text, @@ -63,7 +63,7 @@ def start(update: Update, context: CallbackContext): def help(update: Update, context: CallbackContext): - """Send link to docs when the command /help is issued.""" + """Send help text when the command /help is issued.""" info(f"Help command ran by {update.message.chat.username}") update.message.reply_text( text=t.help_text, @@ -73,7 +73,7 @@ def help(update: Update, context: CallbackContext): def license(update: Update, context: CallbackContext): - """Return bots license agreement""" + """Send bots license when the /license command is issued.""" info(f"License command ran by {update.message.chat.username}") update.message.reply_text( text=t.license, @@ -83,6 +83,7 @@ def license(update: Update, context: CallbackContext): def status(update: Update, context: CallbackContext): + """Gather status of bot and dependant services and return important status updates.""" warning(f"Status command ran by {update.message.chat.username}") bot_resp = datetime.datetime.now(update.message.date.tzinfo) - update.message.date @@ -96,6 +97,7 @@ def status(update: Update, context: CallbackContext): def donate(update: Update, context: CallbackContext): + """Sets up donation.""" info(f"Donate command ran by {update.message.chat.username}") chat_id = update.message.chat_id @@ -133,6 +135,7 @@ def donate(update: Update, context: CallbackContext): def precheckout_callback(update: Update, context: CallbackContext): + """Approves donation""" info(f"precheckout_callback queried") query = update.pre_checkout_query @@ -146,6 +149,7 @@ def precheckout_callback(update: Update, context: CallbackContext): def successful_payment_callback(update: Update, context: CallbackContext): + """Thanks user for donation""" info(f"Successful payment!") update.message.reply_text( "Thank you for your donation! It goes a long way to keeping the bot free!" @@ -177,9 +181,7 @@ def symbol_detect(update: Update, context: CallbackContext): def dividend(update: Update, context: CallbackContext): - """ - waits for /dividend or /div command and then finds dividend info on that symbol. - """ + """/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 @@ -203,9 +205,7 @@ def dividend(update: Update, context: CallbackContext): def news(update: Update, context: CallbackContext): - """ - waits for /news command and then finds news info on that symbol. - """ + """/news command 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 @@ -230,9 +230,7 @@ def news(update: Update, context: CallbackContext): def information(update: Update, context: CallbackContext): - """ - waits for /info command and then finds info on that symbol. - """ + """/info command 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 @@ -257,6 +255,10 @@ def information(update: Update, context: CallbackContext): def search(update: Update, context: CallbackContext): + """ + Uses fuzzy search on full list of stocks and crypto names + and descriptions then returns the top matches in order. + """ info(f"Search command ran by {update.message.chat.username}") message = update.message.text.replace("/search ", "") chat_id = update.message.chat_id @@ -281,8 +283,8 @@ def search(update: Update, context: CallbackContext): def intra(update: Update, context: CallbackContext): + """returns a chart of intraday data for a symbol""" 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 chat_id = update.message.chat_id @@ -337,8 +339,8 @@ def intra(update: Update, context: CallbackContext): def chart(update: Update, context: CallbackContext): + """returns a chart of the past month of data for a symbol""" 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 chat_id = update.message.chat_id @@ -390,9 +392,7 @@ def chart(update: Update, context: CallbackContext): def stat(update: Update, context: CallbackContext): - """ - https://iexcloud.io/docs/api/#key-stats - """ + """returns key statistics on symbol""" info(f"Stat command ran by {update.message.chat.username}") message = update.message.text chat_id = update.message.chat_id @@ -417,9 +417,7 @@ def stat(update: Update, context: CallbackContext): def cap(update: Update, context: CallbackContext): - """ - Market Cap Information - """ + """returns market cap for symbol""" info(f"Cap command ran by {update.message.chat.username}") message = update.message.text chat_id = update.message.chat_id @@ -444,9 +442,7 @@ def cap(update: Update, context: CallbackContext): def trending(update: Update, context: CallbackContext): - """ - Trending Symbols - """ + """returns currently trending symbols and how much they've moved in the past trading day.""" info(f"Trending command ran by {update.message.chat.username}") chat_id = update.message.chat_id @@ -462,8 +458,8 @@ def trending(update: Update, context: CallbackContext): def inline_query(update: Update, context: CallbackContext): """ - Handles inline query. - Does a fuzzy search on input and returns stocks that are close. + Handles inline query. Searches by looking if query is contained + in the symbol and returns matches in alphabetical order. """ info(f"Inline command ran by {update.message.chat.username}") info(f"Query: {update.inline_query.query}") @@ -496,6 +492,7 @@ def inline_query(update: Update, context: CallbackContext): def rand_pick(update: Update, context: CallbackContext): + """For the gamblers. Returns a random symbol to buy and a sell date""" info( f"Someone is gambling! Random_pick command ran by {update.message.chat.username}" ) @@ -558,13 +555,17 @@ def main(): dp.add_handler(CommandHandler("cap", cap)) dp.add_handler(CommandHandler("trending", trending)) dp.add_handler(CommandHandler("search", search)) - dp.add_handler(CommandHandler("intraday", intra)) - dp.add_handler(CommandHandler("intra", intra, run_async=True)) - dp.add_handler(CommandHandler("chart", chart, run_async=True)) dp.add_handler(CommandHandler("random", rand_pick)) dp.add_handler(CommandHandler("donate", donate)) dp.add_handler(CommandHandler("status", status)) + # Charting can be slow so they run async. + dp.add_handler(CommandHandler("intra", intra, run_async=True)) + dp.add_handler(CommandHandler("intraday", intra, run_async=True)) + dp.add_handler(CommandHandler("day", intra, run_async=True)) + dp.add_handler(CommandHandler("chart", chart, run_async=True)) + dp.add_handler(CommandHandler("month", chart, run_async=True)) + # on noncommand i.e message - echo the message on Telegram dp.add_handler(MessageHandler(Filters.text, symbol_detect)) diff --git a/cg_Crypto.py b/cg_Crypto.py index 0d59104..c71962e 100644 --- a/cg_Crypto.py +++ b/cg_Crypto.py @@ -220,18 +220,18 @@ class cg_Crypto: return pd.DataFrame() def stat_reply(self, symbol: Coin) -> str: - """Gets key statistics for each symbol in the list + """Gathers key statistics on coin. Mostly just CoinGecko scores. Parameters ---------- - symbols : List[str] - List of coin symbols + symbol : Coin Returns ------- - Dict[str, str] - Each symbol passed in is a key with its value being a human readable formatted string of the symbols statistics. + str + Preformatted markdown. """ + response = r.get( f"https://api.coingecko.com/api/v3/coins/{symbol.id}?localization=false", timeout=5, @@ -253,17 +253,16 @@ class cg_Crypto: return f"{symbol.symbol} returned an error." def cap_reply(self, coin: Coin) -> str: - """Gets market Cap for each symbol in the list + """Gets market cap for Coin Parameters ---------- - symbols : List[str] - List of coin symbols + coin : Coin Returns ------- - Dict[str, str] - Each symbol passed in is a key with its value being a human readable formatted string of the symbols markey cap. + str + Preformatted markdown. """ response = r.get( f"https://api.coingecko.com/api/v3/simple/price?ids={coin.id}&vs_currencies={self.vs_currency}&include_market_cap=true", @@ -290,17 +289,16 @@ class cg_Crypto: return message def info_reply(self, symbol: Coin) -> str: - """Gets information on stock symbols. + """Gets coin description Parameters ---------- - symbols : List[str] - List of stock symbols. + symbol : Coin Returns ------- - Dict[str, str] - Each symbol passed in is a key with its value being a human readable formatted string of the symbols information. + str + Preformatted markdown. """ response = r.get( @@ -322,7 +320,7 @@ class cg_Crypto: Returns ------- list[str] - list of $$ID: NAME + list of $$ID: NAME, CHANGE% """ coins = r.get( @@ -352,6 +350,17 @@ class cg_Crypto: return trending def batch_price(self, coins: list[Coin]) -> list[str]: + """Gets price of a list of coins all in one API call + + Parameters + ---------- + coins : list[Coin] + + Returns + ------- + list[str] + returns preformatted list of strings detailing price movement of each coin passed in. + """ query = ",".join([c.id for c in coins]) prices = r.get( diff --git a/search_index.pickle b/search_index.pickle deleted file mode 100644 index 26e0883..0000000 Binary files a/search_index.pickle and /dev/null differ diff --git a/searchindexer.ipynb b/searchindexer.ipynb deleted file mode 100644 index 66d422e..0000000 --- a/searchindexer.ipynb +++ /dev/null @@ -1,276 +0,0 @@ -{ - "metadata": { - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.0-final" - }, - "orig_nbformat": 2, - "kernelspec": { - "name": "python3", - "display_name": "Python 3.9.0 64-bit", - "metadata": { - "interpreter": { - "hash": "36cf16204b8548560b1c020c4e8fb5b57f0e4c58016f52f2d4be01e192833930" - } - } - } - }, - "nbformat": 4, - "nbformat_minor": 2, - "cells": [ - { - "cell_type": "code", - "execution_count": 62, - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "Requirement already satisfied: tqdm in /home/anson/.local/lib/python3.8/site-packages (4.59.0)\n" - ] - } - ], - "source": [ - "!pip install tqdm" - ] - }, - { - "cell_type": "code", - "execution_count": 64, - "metadata": {}, - "outputs": [], - "source": [ - "import requests as r\n", - "import pandas as pd\n", - "from fuzzywuzzy import fuzz\n", - "from functools import cache\n", - "from tqdm import tqdm" - ] - }, - { - "cell_type": "code", - "execution_count": 49, - "metadata": {}, - "outputs": [], - "source": [ - " def stocks():\n", - "\n", - " raw_symbols = r.get(\n", - " f\"https://cloud.iexapis.com/stable/ref-data/symbols?token=WOOOPS\"\n", - " ).json()\n", - " symbols = pd.DataFrame(data=raw_symbols)\n", - "\n", - " symbols[\"description\"] = \"$\" + symbols[\"symbol\"] + \": \" + symbols[\"name\"]\n", - " symbols[\"id\"] = symbols[\"symbol\"]\n", - "\n", - " symbols = symbols[[\"id\", \"symbol\", \"name\", \"description\"]]\n", - "\n", - " return symbols\n", - "\n", - "\n", - "\n", - " def coins():\n", - "\n", - " raw_symbols = r.get(\"https://api.coingecko.com/api/v3/coins/list\").json()\n", - " symbols = pd.DataFrame(data=raw_symbols)\n", - "\n", - " symbols[\"description\"] = \"$$\" + symbols[\"symbol\"] + \": \" + symbols[\"name\"]\n", - " symbols = symbols[[\"id\", \"symbol\", \"name\", \"description\"]]\n", - "\n", - " return symbols" - ] - }, - { - "cell_type": "code", - "execution_count": 51, - "metadata": {}, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - " id symbol \\\n", - "0 A A \n", - "1 AA AA \n", - "2 AAA AAA \n", - "3 AAAU AAAU \n", - "4 AAC AAC \n", - "... ... ... \n", - "6565 zyro zyro \n", - "6566 zytara-dollar zusd \n", - "6567 zyx zyx \n", - "6568 zzz-finance zzz \n", - "6569 zzz-finance-v2 zzzv2 \n", - "\n", - " name \\\n", - "0 Agilent Technologies Inc. \n", - "1 Alcoa Corp \n", - "2 Listed Funds Trust - AAF First Priority CLO Bo... \n", - "3 Goldman Sachs Physical Gold ETF Shares - Goldm... \n", - "4 Ares Acquisition Corporation - Class A \n", - "... ... \n", - "6565 Zyro \n", - "6566 Zytara Dollar \n", - "6567 ZYX \n", - "6568 zzz.finance \n", - "6569 zzz.finance v2 \n", - "\n", - " description \n", - "0 $A: Agilent Technologies Inc. \n", - "1 $AA: Alcoa Corp \n", - "2 $AAA: Listed Funds Trust - AAF First Priority ... \n", - "3 $AAAU: Goldman Sachs Physical Gold ETF Shares ... \n", - "4 $AAC: Ares Acquisition Corporation - Class A \n", - "... ... \n", - "6565 $$zyro: Zyro \n", - "6566 $$zusd: Zytara Dollar \n", - "6567 $$zyx: ZYX \n", - "6568 $$zzz: zzz.finance \n", - "6569 $$zzzv2: zzz.finance v2 \n", - "\n", - "[16946 rows x 4 columns]" - ], - "text/html": "
\n | id | \nsymbol | \nname | \ndescription | \n
---|---|---|---|---|
0 | \nA | \nA | \nAgilent Technologies Inc. | \n$A: Agilent Technologies Inc. | \n
1 | \nAA | \nAA | \nAlcoa Corp | \n$AA: Alcoa Corp | \n
2 | \nAAA | \nAAA | \nListed Funds Trust - AAF First Priority CLO Bo... | \n$AAA: Listed Funds Trust - AAF First Priority ... | \n
3 | \nAAAU | \nAAAU | \nGoldman Sachs Physical Gold ETF Shares - Goldm... | \n$AAAU: Goldman Sachs Physical Gold ETF Shares ... | \n
4 | \nAAC | \nAAC | \nAres Acquisition Corporation - Class A | \n$AAC: Ares Acquisition Corporation - Class A | \n
... | \n... | \n... | \n... | \n... | \n
6565 | \nzyro | \nzyro | \nZyro | \n$$zyro: Zyro | \n
6566 | \nzytara-dollar | \nzusd | \nZytara Dollar | \n$$zusd: Zytara Dollar | \n
6567 | \nzyx | \nzyx | \nZYX | \n$$zyx: ZYX | \n
6568 | \nzzz-finance | \nzzz | \nzzz.finance | \n$$zzz: zzz.finance | \n
6569 | \nzzz-finance-v2 | \nzzzv2 | \nzzz.finance v2 | \n$$zzzv2: zzz.finance v2 | \n
16946 rows à 4 columns
\n