mirror of
https://gitlab.com/simple-stock-bots/simple-stock-bot.git
synced 2025-06-15 23:06:40 +00:00
Merge branch 'canary' into 'master'
Canary Closes #68, #70, and #69 See merge request simple-stock-bots/simple-telegram-stock-bot!24
This commit is contained in:
commit
24220cd0cc
108
IEX_Symbol.py
108
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."]
|
||||
|
@ -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
|
||||
|
14
T_info.py
14
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
|
||||
|
169
bot.py
169
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"<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>"
|
||||
# )
|
||||
|
||||
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"<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."
|
||||
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))
|
||||
|
||||
|
114
cg_Crypto.py
114
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(
|
||||
|
Binary file not shown.
@ -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": "<div>\n<style scoped>\n .dataframe tbody tr th:only-of-type {\n vertical-align: middle;\n }\n\n .dataframe tbody tr th {\n vertical-align: top;\n }\n\n .dataframe thead th {\n text-align: right;\n }\n</style>\n<table border=\"1\" class=\"dataframe\">\n <thead>\n <tr style=\"text-align: right;\">\n <th></th>\n <th>id</th>\n <th>symbol</th>\n <th>name</th>\n <th>description</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <th>0</th>\n <td>A</td>\n <td>A</td>\n <td>Agilent Technologies Inc.</td>\n <td>$A: Agilent Technologies Inc.</td>\n </tr>\n <tr>\n <th>1</th>\n <td>AA</td>\n <td>AA</td>\n <td>Alcoa Corp</td>\n <td>$AA: Alcoa Corp</td>\n </tr>\n <tr>\n <th>2</th>\n <td>AAA</td>\n <td>AAA</td>\n <td>Listed Funds Trust - AAF First Priority CLO Bo...</td>\n <td>$AAA: Listed Funds Trust - AAF First Priority ...</td>\n </tr>\n <tr>\n <th>3</th>\n <td>AAAU</td>\n <td>AAAU</td>\n <td>Goldman Sachs Physical Gold ETF Shares - Goldm...</td>\n <td>$AAAU: Goldman Sachs Physical Gold ETF Shares ...</td>\n </tr>\n <tr>\n <th>4</th>\n <td>AAC</td>\n <td>AAC</td>\n <td>Ares Acquisition Corporation - Class A</td>\n <td>$AAC: Ares Acquisition Corporation - Class A</td>\n </tr>\n <tr>\n <th>...</th>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n </tr>\n <tr>\n <th>6565</th>\n <td>zyro</td>\n <td>zyro</td>\n <td>Zyro</td>\n <td>$$zyro: Zyro</td>\n </tr>\n <tr>\n <th>6566</th>\n <td>zytara-dollar</td>\n <td>zusd</td>\n <td>Zytara Dollar</td>\n <td>$$zusd: Zytara Dollar</td>\n </tr>\n <tr>\n <th>6567</th>\n <td>zyx</td>\n <td>zyx</td>\n <td>ZYX</td>\n <td>$$zyx: ZYX</td>\n </tr>\n <tr>\n <th>6568</th>\n <td>zzz-finance</td>\n <td>zzz</td>\n <td>zzz.finance</td>\n <td>$$zzz: zzz.finance</td>\n </tr>\n <tr>\n <th>6569</th>\n <td>zzz-finance-v2</td>\n <td>zzzv2</td>\n <td>zzz.finance v2</td>\n <td>$$zzzv2: zzz.finance v2</td>\n </tr>\n </tbody>\n</table>\n<p>16946 rows × 4 columns</p>\n</div>"
|
||||
},
|
||||
"metadata": {},
|
||||
"execution_count": 51
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"df = pd.concat([stocks(), coins()])\n",
|
||||
"df"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 79,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
" def search_symbols(search: str):\n",
|
||||
" \"\"\"Performs a fuzzy search to find stock symbols closest to a search term.\n",
|
||||
"\n",
|
||||
" Parameters\n",
|
||||
" ----------\n",
|
||||
" search : str\n",
|
||||
" String used to search, could be a company name or something close to the companies stock ticker.\n",
|
||||
"\n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" List[tuple[str, str]]\n",
|
||||
" A list tuples of every stock sorted in order of how well they match. Each tuple contains: (Symbol, Issue Name).\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" try:\n",
|
||||
" if search_index[search]: return search_index[search]\n",
|
||||
" except KeyError:\n",
|
||||
" pass\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
" search = search.lower()\n",
|
||||
"\n",
|
||||
" df[\"Match\"] = df.apply(\n",
|
||||
" lambda x: fuzz.ratio(search, f\"{x['symbol']}\".lower()),\n",
|
||||
" axis=1,\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" df.sort_values(by=\"Match\", ascending=False, inplace=True)\n",
|
||||
" if df[\"Match\"].head().sum() < 300:\n",
|
||||
" df[\"Match\"] = df.apply(\n",
|
||||
" lambda x: fuzz.partial_ratio(search, x[\"name\"].lower()),\n",
|
||||
" axis=1,\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" df.sort_values(by=\"Match\", ascending=False, inplace=True)\n",
|
||||
"\n",
|
||||
" symbols = df.head(20)\n",
|
||||
" symbol_list = list(zip(list(symbols[\"symbol\"]), list(symbols[\"description\"])))\n",
|
||||
" \n",
|
||||
" return symbol_list"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 91,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"search_list = df['id'].to_list() + df['description'].to_list()\n",
|
||||
"search_index = {}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 92,
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "stream",
|
||||
"name": "stderr",
|
||||
"text": [
|
||||
" 5%|▍ | 1545/33892 [06:51<2:23:40, 3.75it/s]\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"output_type": "error",
|
||||
"ename": "KeyboardInterrupt",
|
||||
"evalue": "",
|
||||
"traceback": [
|
||||
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
|
||||
"\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)",
|
||||
"\u001b[0;32m<ipython-input-92-559f57361529>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mi\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mtqdm\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msearch_list\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0msearch_index\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0msearch_symbols\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
|
||||
"\u001b[0;32m<ipython-input-79-2cfb15c9428a>\u001b[0m in \u001b[0;36msearch_symbols\u001b[0;34m(search)\u001b[0m\n\u001b[1;32m 22\u001b[0m \u001b[0msearch\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0msearch\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlower\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 23\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 24\u001b[0;31m df[\"Match\"] = df.apply(\n\u001b[0m\u001b[1;32m 25\u001b[0m \u001b[0;32mlambda\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mfuzz\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mratio\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msearch\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34mf\"{x['symbol']}\"\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlower\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 26\u001b[0m \u001b[0maxis\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
|
||||
"\u001b[0;32m~/.local/lib/python3.9/site-packages/pandas/core/frame.py\u001b[0m in \u001b[0;36mapply\u001b[0;34m(self, func, axis, raw, result_type, args, **kwds)\u001b[0m\n\u001b[1;32m 7763\u001b[0m \u001b[0mkwds\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mkwds\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 7764\u001b[0m )\n\u001b[0;32m-> 7765\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mop\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_result\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 7766\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 7767\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mapplymap\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfunc\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mna_action\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mOptional\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mDataFrame\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
|
||||
"\u001b[0;32m~/.local/lib/python3.9/site-packages/pandas/core/apply.py\u001b[0m in \u001b[0;36mget_result\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 183\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mapply_raw\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 184\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 185\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mapply_standard\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 186\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 187\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mapply_empty_result\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
|
||||
"\u001b[0;32m~/.local/lib/python3.9/site-packages/pandas/core/apply.py\u001b[0m in \u001b[0;36mapply_standard\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 274\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 275\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mapply_standard\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 276\u001b[0;31m \u001b[0mresults\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mres_index\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mapply_series_generator\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 277\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 278\u001b[0m \u001b[0;31m# wrap results\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
|
||||
"\u001b[0;32m~/.local/lib/python3.9/site-packages/pandas/core/apply.py\u001b[0m in \u001b[0;36mapply_series_generator\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 286\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 287\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0moption_context\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"mode.chained_assignment\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 288\u001b[0;31m \u001b[0;32mfor\u001b[0m \u001b[0mi\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mv\u001b[0m \u001b[0;32min\u001b[0m \u001b[0menumerate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mseries_gen\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 289\u001b[0m \u001b[0;31m# ignore SettingWithCopy here in case the user mutates\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 290\u001b[0m \u001b[0mresults\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mf\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mv\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
|
||||
"\u001b[0;32m~/.local/lib/python3.9/site-packages/pandas/core/apply.py\u001b[0m in \u001b[0;36mseries_generator\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 408\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0marr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mzip\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mvalues\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 409\u001b[0m \u001b[0;31m# GH#35462 re-pin mgr in case setitem changed it\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 410\u001b[0;31m \u001b[0mser\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_mgr\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmgr\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 411\u001b[0m \u001b[0mblk\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mvalues\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0marr\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 412\u001b[0m \u001b[0mser\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mname\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
|
||||
"\u001b[0;32m~/.local/lib/python3.9/site-packages/pandas/core/generic.py\u001b[0m in \u001b[0;36m__setattr__\u001b[0;34m(self, name, value)\u001b[0m\n\u001b[1;32m 5473\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5474\u001b[0m \u001b[0mobject\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__getattribute__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 5475\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mobject\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__setattr__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 5476\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mAttributeError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5477\u001b[0m \u001b[0;32mpass\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
|
||||
"\u001b[0;31mKeyboardInterrupt\u001b[0m: "
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"\n",
|
||||
"for i in tqdm(search_list):\n",
|
||||
" search_index[i] = search_symbols(i)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 89,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pickle\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"with open('search_index.pickle', 'wb') as handle:\n",
|
||||
" pickle.dump(search_index, handle, protocol=pickle.HIGHEST_PROTOCOL)\n",
|
||||
"\n",
|
||||
"# with open('filename.pickle', 'rb') as handle:\n",
|
||||
"# b = pickle.load(handle)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
]
|
||||
}
|
111
symbol_router.py
111
symbol_router.py
@ -1,18 +1,17 @@
|
||||
"""Function that routes symbols to the correct API provider.
|
||||
"""
|
||||
|
||||
import re
|
||||
import pandas as pd
|
||||
import random
|
||||
import datetime
|
||||
import random
|
||||
import re
|
||||
from logging import critical, debug, error, info, warning
|
||||
|
||||
import pandas as pd
|
||||
from fuzzywuzzy import fuzz
|
||||
|
||||
from typing import List, Tuple
|
||||
|
||||
from IEX_Symbol import IEX_Symbol
|
||||
from cg_Crypto import cg_Crypto
|
||||
|
||||
from Symbol import Symbol, Stock, Coin
|
||||
from IEX_Symbol import IEX_Symbol
|
||||
from Symbol import Coin, Stock, Symbol
|
||||
|
||||
|
||||
class Router:
|
||||
@ -24,10 +23,9 @@ 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?
|
||||
|
||||
Parameters
|
||||
----------
|
||||
@ -36,7 +34,7 @@ class Router:
|
||||
|
||||
Returns
|
||||
-------
|
||||
List[str]
|
||||
list[str]
|
||||
List of stock symbols as strings without dollar sign.
|
||||
"""
|
||||
symbols = []
|
||||
@ -45,15 +43,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 +63,7 @@ class Router:
|
||||
Human readable text on status of the bot and relevant APIs
|
||||
"""
|
||||
|
||||
return f"""
|
||||
stats = f"""
|
||||
Bot Status:
|
||||
{bot_resp}
|
||||
|
||||
@ -76,7 +74,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,8 +88,9 @@ class Router:
|
||||
|
||||
Returns
|
||||
-------
|
||||
List[tuple[str, str]]
|
||||
A list tuples of every stock sorted in order of how well they match. Each tuple contains: (Symbol, Issue Name).
|
||||
list[tuple[str, str]]
|
||||
A list tuples of every stock sorted in order of how well they match.
|
||||
Each tuple contains: (Symbol, Issue Name).
|
||||
"""
|
||||
|
||||
df = pd.concat([self.stock.symbol_list, self.crypto.symbol_list])
|
||||
@ -113,7 +116,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 +127,7 @@ class Router:
|
||||
|
||||
Returns
|
||||
-------
|
||||
List[tuple[str, str]]
|
||||
list[tuple[str, str]]
|
||||
Each tuple contains: (Symbol, Issue Name).
|
||||
"""
|
||||
|
||||
@ -141,7 +144,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 +161,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
|
||||
@ -179,7 +182,8 @@ class Router:
|
||||
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.
|
||||
Each symbol passed in is a key with its value being a human readable
|
||||
formatted string of the symbols div dates.
|
||||
"""
|
||||
replies = []
|
||||
for symbol in symbols:
|
||||
@ -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
|
||||
@ -203,7 +207,8 @@ class Router:
|
||||
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.
|
||||
Each symbol passed in is a key with its value being a human
|
||||
readable markdown formatted string of the symbols news.
|
||||
"""
|
||||
replies = []
|
||||
|
||||
@ -220,18 +225,19 @@ 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
|
||||
-------
|
||||
Dict[str, str]
|
||||
Each symbol passed in is a key with its value being a human readable formatted string of the symbols information.
|
||||
Each symbol passed in is a key with its value being a human readable formatted
|
||||
string of the symbols information.
|
||||
"""
|
||||
replies = []
|
||||
|
||||
@ -256,7 +262,8 @@ class Router:
|
||||
Returns
|
||||
-------
|
||||
pd.DataFrame
|
||||
Returns a timeseries dataframe with high, low, and volume data if its available. Otherwise returns empty pd.DataFrame.
|
||||
Returns a timeseries dataframe with high, low, and volume data if its available.
|
||||
Otherwise returns empty pd.DataFrame.
|
||||
"""
|
||||
|
||||
if isinstance(symbol, Stock):
|
||||
@ -279,7 +286,8 @@ class Router:
|
||||
Returns
|
||||
-------
|
||||
pd.DataFrame
|
||||
Returns a timeseries dataframe with high, low, and volume data if its available. Otherwise returns empty pd.DataFrame.
|
||||
Returns a timeseries dataframe with high, low, and volume data if its available.
|
||||
Otherwise returns empty pd.DataFrame.
|
||||
"""
|
||||
if isinstance(symbol, Stock):
|
||||
return self.stock.chart_reply(symbol)
|
||||
@ -289,18 +297,19 @@ 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
|
||||
-------
|
||||
Dict[str, str]
|
||||
Each symbol passed in is a key with its value being a human readable formatted string of the symbols statistics.
|
||||
Each symbol passed in is a key with its value being a human readable
|
||||
formatted string of the symbols statistics.
|
||||
"""
|
||||
replies = []
|
||||
|
||||
@ -314,6 +323,32 @@ class Router:
|
||||
|
||||
return replies
|
||||
|
||||
def cap_reply(self, symbols: list[Symbol]) -> list[str]:
|
||||
"""Gets market cap for each symbol in the list
|
||||
|
||||
Parameters
|
||||
----------
|
||||
symbols : list[str]
|
||||
List of stock symbols
|
||||
|
||||
Returns
|
||||
-------
|
||||
Dict[str, str]
|
||||
Each symbol passed in is a key with its value being a human readable
|
||||
formatted string of the symbols market cap.
|
||||
"""
|
||||
replies = []
|
||||
|
||||
for symbol in symbols:
|
||||
if isinstance(symbol, Stock):
|
||||
replies.append(self.stock.cap_reply(symbol))
|
||||
elif isinstance(symbol, Coin):
|
||||
replies.append(self.crypto.cap_reply(symbol))
|
||||
else:
|
||||
print(f"{symbol} is not a Stock or Coin")
|
||||
|
||||
return replies
|
||||
|
||||
def trending(self) -> str:
|
||||
"""Checks APIs for trending symbols.
|
||||
|
||||
@ -326,7 +361,7 @@ class Router:
|
||||
stocks = self.stock.trending()
|
||||
coins = self.crypto.trending()
|
||||
|
||||
reply = "`Trending Stocks:\n"
|
||||
reply = "Trending Stocks:\n"
|
||||
reply += "-" * len("Trending Stocks:") + "\n"
|
||||
for stock in stocks:
|
||||
reply += stock + "\n"
|
||||
@ -336,7 +371,7 @@ class Router:
|
||||
for coin in coins:
|
||||
reply += coin + "\n"
|
||||
|
||||
return reply + "`"
|
||||
return reply
|
||||
|
||||
def random_pick(self) -> str:
|
||||
|
||||
@ -350,7 +385,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