diff --git a/IEX_Symbol.py b/IEX_Symbol.py index 0012efd..4135002 100644 --- a/IEX_Symbol.py +++ b/IEX_Symbol.py @@ -1,14 +1,15 @@ """Class with functions for running the bot with IEX Cloud. """ +import os from datetime import datetime -from typing import Optional, List, Tuple +from logging import warning +from typing import List, Optional, Tuple import pandas as pd import requests as r import schedule from fuzzywuzzy import fuzz -import os from Symbol import Stock @@ -30,13 +31,13 @@ class IEX_Symbol: Parameters ---------- IEX_TOKEN : str - IEX Token + IEX API Token """ try: 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!" ) @@ -47,12 +48,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 +162,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 +238,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 +298,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 +336,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 +366,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." @@ -400,6 +411,28 @@ class IEX_Symbol: else: return f"No information found for: {symbol}\nEither today is boring or the symbol does not exist." + def cap_reply(self, stock: Stock) -> str: + """Get the Market Cap of a stock""" + response = r.get( + f"https://cloud.iexapis.com/stable/stock/{stock.id}/stats?token={self.IEX_TOKEN}", + timeout=5, + ) + if response.status_code == 200: + + try: + data = response.json() + + cap = data["marketcap"] + except KeyError: + return f"{stock.id} returned an error." + + message = f"The current market cap of {stock.name} is $**{cap:,.2f}**" + + else: + message = f"The Coin: {stock.name} was not found or returned and error." + + return message + def intra_reply(self, symbol: Stock) -> pd.DataFrame: """Returns price data for a symbol since the last market open. @@ -476,17 +509,22 @@ class IEX_Symbol: return pd.DataFrame() def trending(self) -> list[str]: - """Gets current coins trending on coingecko + """Gets current coins trending on IEX. Only returns when market is open. Returns ------- list[str] - list of $$ID: NAME + list of $ID: NAME, CHANGE% """ stocks = r.get( f"https://cloud.iexapis.com/stable/stock/market/list/mostactive?token={self.IEX_TOKEN}", timeout=5, - ).json() - - return [f"${s['symbol']}: {s['companyName']}" for s in stocks] + ) + if stocks.status_code == 200: + return [ + f"`${s['symbol']}`: {s['companyName']}, {s['changePercent']:.2f}%" + for s in stocks.json() + ] + else: + return ["Trending Stocks Currently Unavailable."] diff --git a/Symbol.py b/Symbol.py index 3e204c6..db04b4a 100644 --- a/Symbol.py +++ b/Symbol.py @@ -1,4 +1,5 @@ import functools + import requests as r @@ -25,6 +26,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 +39,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..1ba184b 100644 --- a/T_info.py +++ b/T_info.py @@ -2,6 +2,7 @@ """ import re + import requests as r @@ -19,9 +20,9 @@ Thanks for using this bot, consider supporting it by [buying me a beer.](https:/ Keep up with the latest news for the bot in its Telegram Channel: https://t.me/simplestockbotnews -Full documentation on using and running your own stock bot can be found [on the bots website.](https://simple-stock-bots.gitlab.io/site) +Full documentation on using and running your own stock bot can be found on the bots [docs.](https://docs.simplestockbot.com) -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. @@ -33,6 +34,7 @@ Simply calling a symbol in any message that the bot can see will also return the - `/news $[symbol]` News about the symbol. đ° - `/info $[symbol]` General information about the symbol. âšī¸ - `/stat $[symbol]` Key statistics about the symbol. đĸ + - `/cap $[symbol]` Market Capitalization of symbol. đ° - `/trending` Trending Stocks and Cryptos. đŦ - `/help` Get some help using the bot. đ @@ -41,7 +43,7 @@ Simply calling a symbol in any message that the bot can see will also return the Market data is provided by [IEX Cloud](https://iexcloud.io) - If you believe the bot is not behaving properly run `/status`. + If you believe the bot is not behaving properly run `/status` or [get in touch](https://docs.simplestockbot.com/contact). """ donate_text = """ @@ -54,9 +56,8 @@ The easiest way to donate is to run the `/donate [amount in USD]` command with U Example: `/donate 2` would donate 2 USD. An alternative way to donate is through https://www.buymeacoffee.com/Anson which requires no account and accepts Paypal or Credit card. -If you have any questions get in touch: @MisterBiggs or [anson@ansonbiggs.com](http://mailto:anson@ansonbiggs.com/) +If you have any questions see the [website](https:docs.simplestockbot.com) -_Donations can only be made in a chat directly with @simplestockbot_ """ @@ -66,8 +67,9 @@ help - Get some help using the bot. đ info - $[symbol] General information about the symbol. âšī¸ news - $[symbol] News about the symbol. đ° stat - $[symbol] Key statistics about the symbol. đĸ +cap - $[symbol] Market Capitalization of 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 e14f42b..f18e638 100644 --- a/bot.py +++ b/bot.py @@ -1,15 +1,17 @@ # Works with Python 3.8 import datetime +import html import io +import json import logging import os -import html -import json +import random +import string import traceback - -import mplfinance as mpf +from logging import critical, debug, error, info, warning from uuid import uuid4 +import mplfinance as mpf import telegram from telegram import ( InlineQueryResultArticle, @@ -18,13 +20,13 @@ from telegram import ( Update, ) from telegram.ext import ( + CallbackContext, CommandHandler, Filters, InlineQueryHandler, MessageHandler, PreCheckoutQueryHandler, Updater, - CallbackContext, ) from symbol_router import Router @@ -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.""" + """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, parse_mode=telegram.ParseMode.MARKDOWN, @@ -60,7 +63,8 @@ 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, parse_mode=telegram.ParseMode.MARKDOWN, @@ -69,7 +73,8 @@ 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, parse_mode=telegram.ParseMode.MARKDOWN, @@ -78,6 +83,8 @@ 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 update.message.reply_text( @@ -90,6 +97,8 @@ 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 if update.message.text.strip() == "/donate": @@ -107,7 +116,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, @@ -119,13 +128,15 @@ def donate(update: Update, context: CallbackContext): prices=[LabeledPrice("Donation:", price)], start_parameter="", # suggested_tip_amounts=[100, 500, 1000, 2000], - photo_url="https://simple-stock-bots.gitlab.io/site/img/Telegram.png", + photo_url="https://simple-stock-bots.gitlab.io/docs/img/Telegram.png", photo_width=500, photo_height=500, ) def precheckout_callback(update: Update, context: CallbackContext): + """Approves donation""" + info(f"precheckout_callback queried") query = update.pre_checkout_query query.answer(ok=True) @@ -138,6 +149,8 @@ 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!" ) @@ -148,6 +161,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 +169,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, @@ -165,9 +181,8 @@ 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 @@ -190,9 +205,8 @@ 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 @@ -215,10 +229,9 @@ def news(update: Update, context: CallbackContext): ) -def info(update: Update, context: CallbackContext): - """ - waits for /info command and then finds info on that symbol. - """ +def information(update: Update, context: CallbackContext): + """/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 @@ -242,6 +255,11 @@ def info(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 @@ -265,7 +283,8 @@ def search(update: Update, context: CallbackContext): def intra(update: Update, context: CallbackContext): - # TODO: Document usage of this command. https://iexcloud.io/docs/api/#historical-prices + """returns a chart of intraday data for a symbol""" + info(f"Intra command ran by {update.message.chat.username}") message = update.message.text chat_id = update.message.chat_id @@ -320,7 +339,8 @@ def intra(update: Update, context: CallbackContext): def chart(update: Update, context: CallbackContext): - # TODO: Document usage of this command. https://iexcloud.io/docs/api/#historical-prices + """returns a chart of the past month of data for a symbol""" + info(f"Chart command ran by {update.message.chat.username}") message = update.message.text chat_id = update.message.chat_id @@ -350,7 +370,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, @@ -372,9 +392,8 @@ 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 @@ -397,10 +416,34 @@ def stat(update: Update, context: CallbackContext): ) +def cap(update: Update, context: CallbackContext): + """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 + + if message.strip().split("@")[0] == "/cap": + update.message.reply_text( + "This command returns the market cap for a symbol.\nExample: /cap $tsla" + ) + return + + symbols = s.find_symbols(message) + + if symbols: + context.bot.send_chat_action(chat_id=chat_id, action=telegram.ChatAction.TYPING) + + for reply in s.cap_reply(symbols): + update.message.reply_text( + text=reply, + parse_mode=telegram.ParseMode.MARKDOWN, + disable_notification=True, + ) + + 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 @@ -415,17 +458,17 @@ 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}") 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( @@ -438,16 +481,21 @@ 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): + """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}" + ) update.message.reply_text( text=s.random_pick(), @@ -458,24 +506,32 @@ 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"
update = {html.escape(json.dumps(update.to_dict(), indent=2, ensure_ascii=False))}" - # "\n\n" - # f"
context.chat_data = {html.escape(str(context.chat_data))}\n\n" - # f"
context.user_data = {html.escape(str(context.user_data))}\n\n" - # f"
{html.escape(tb_string)}" - # ) + + err_code = "".join([random.choice(string.ascii_lowercase) for i in range(5)]) + warning(f"Logging error: {err_code}") + + if update: + message = ( + f"An exception was raised while handling an update\n" + f"
update = {html.escape(json.dumps(update.to_dict(), indent=2, ensure_ascii=False))}" + "\n\n" + f"
context.chat_data = {html.escape(str(context.chat_data))}\n\n" + f"
context.user_data = {html.escape(str(context.user_data))}\n\n" + f"
{html.escape(tb_string)}" + ) + warning(message) + else: + warning(tb_string) + update.message.reply_text( - text="An error has occured. Please inform @MisterBiggs if the error persists." + text=f"An error has occured. Please inform @MisterBiggs if the error persists. Error Code: `{err_code}`", + parse_mode=telegram.ParseMode.MARKDOWN, ) # Finally, send the message @@ -498,18 +554,23 @@ 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)) 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 6413040..00f248c 100644 --- a/cg_Crypto.py +++ b/cg_Crypto.py @@ -2,13 +2,14 @@ """ from datetime import datetime -from typing import Optional, List, Tuple +from typing import List, Optional, Tuple import pandas as pd import requests as r import schedule from fuzzywuzzy import fuzz from markdownify import markdownify + from Symbol import Coin @@ -117,7 +118,7 @@ class cg_Crypto: self.searched_symbols[search] = symbol_list return symbol_list - def price_reply(self, symbol: Coin) -> str: + def price_reply(self, coin: Coin) -> str: """Returns current market price or after hours if its available for a given coin symbol. Parameters @@ -133,22 +134,22 @@ class cg_Crypto: """ response = r.get( - f"https://api.coingecko.com/api/v3/coins/{symbol.id}?localization=false", + f"https://api.coingecko.com/api/v3/simple/price?ids={coin.id}&vs_currencies={self.vs_currency}&include_24hr_change=true", timeout=5, ) if response.status_code == 200: - data = response.json() try: - name = data["name"] - price = data["market_data"]["current_price"][self.vs_currency] - change = data["market_data"]["price_change_percentage_24h"] + data = response.json()[coin.id] + + price = data[self.vs_currency] + change = data[self.vs_currency + "_24h_change"] if change is None: change = 0 except KeyError: - return f"{symbol} returned an error." + return f"{coin.id} returned an error." - message = f"The current price of {name} is $**{price:,}**" + message = f"The current price of {coin.name} is $**{price:,}**" # Determine wording of change text if change > 0: @@ -159,7 +160,7 @@ class cg_Crypto: message += ", the coin hasn't shown any movement today." else: - message = f"The Coin: {symbol.name} was not found." + message = f"The Coin: {coin.name} was not found." return message @@ -220,18 +221,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, @@ -252,18 +253,53 @@ class cg_Crypto: else: return f"{symbol.symbol} returned an error." - def info_reply(self, symbol: Coin) -> str: - """Gets information on stock symbols. + def cap_reply(self, coin: Coin) -> str: + """Gets market cap for Coin Parameters ---------- - symbols : List[str] - List of stock 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 information. + 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", + timeout=5, + ) + if response.status_code == 200: + + try: + data = response.json()[coin.id] + + price = data[self.vs_currency] + cap = data[self.vs_currency + "_market_cap"] + except KeyError: + return f"{coin.id} returned an error." + + if cap == 0: + return f"The market cap for {coin.name} is not available for unknown reasons." + + message = f"The current price of {coin.name} is $**{price:,}** and its market cap is $**{cap:,.2f}** {self.vs_currency.upper()}" + + else: + message = f"The Coin: {coin.name} was not found or returned and error." + + return message + + def info_reply(self, symbol: Coin) -> str: + """Gets coin description + + Parameters + ---------- + symbol : Coin + + Returns + ------- + str + Preformatted markdown. """ response = r.get( @@ -285,17 +321,47 @@ class cg_Crypto: Returns ------- list[str] - list of $$ID: NAME + list of $$ID: NAME, CHANGE% """ coins = r.get( "https://api.coingecko.com/api/v3/search/trending", timeout=5, - ).json()["coins"] + ) + try: + trending = [] + if coins.status_code == 200: + for coin in coins.json()["coins"]: + c = coin["item"] - return [f"$${c['item']['symbol'].upper()}: {c['item']['name']}" for c in coins] + sym = c["symbol"].upper() + name = c["name"] + change = r.get( + f"https://api.coingecko.com/api/v3/simple/price?ids={c['id']}&vs_currencies={self.vs_currency}&include_24hr_change=true" + ).json()[c["id"]]["usd_24h_change"] + + msg = f"`$${sym}`: {name}, {change:.2f}%" + + trending.append(msg) + + except Exception as e: + print(e) + trending = ["Trending Coins Currently Unavailable."] + + 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