mirror of
https://gitlab.com/simple-stock-bots/simple-telegram-stock-bot.git
synced 2025-06-16 15:06:53 +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.
|
"""Class with functions for running the bot with IEX Cloud.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
from datetime import datetime
|
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 pandas as pd
|
||||||
import requests as r
|
import requests as r
|
||||||
import schedule
|
import schedule
|
||||||
from fuzzywuzzy import fuzz
|
from fuzzywuzzy import fuzz
|
||||||
import os
|
|
||||||
|
|
||||||
from Symbol import Stock
|
from Symbol import Stock
|
||||||
|
|
||||||
@ -30,13 +31,13 @@ class IEX_Symbol:
|
|||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
IEX_TOKEN : str
|
IEX_TOKEN : str
|
||||||
IEX Token
|
IEX API Token
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.IEX_TOKEN = os.environ["IEX"]
|
self.IEX_TOKEN = os.environ["IEX"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self.IEX_TOKEN = ""
|
self.IEX_TOKEN = ""
|
||||||
print(
|
warning(
|
||||||
"Starting without an IEX Token will not allow you to get market data!"
|
"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)
|
schedule.every().day.do(self.clear_charts)
|
||||||
|
|
||||||
def clear_charts(self) -> None:
|
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 = {}
|
self.charts = {}
|
||||||
|
|
||||||
def get_symbol_list(
|
def get_symbol_list(
|
||||||
self, return_df=False
|
self, return_df=False
|
||||||
) -> Optional[Tuple[pd.DataFrame, datetime]]:
|
) -> 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(
|
reg_symbols = r.get(
|
||||||
f"https://cloud.iexapis.com/stable/ref-data/symbols?token={self.IEX_TOKEN}",
|
f"https://cloud.iexapis.com/stable/ref-data/symbols?token={self.IEX_TOKEN}",
|
||||||
@ -145,18 +162,16 @@ class IEX_Symbol:
|
|||||||
return symbol_list
|
return symbol_list
|
||||||
|
|
||||||
def price_reply(self, symbol: Stock) -> str:
|
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
|
Parameters
|
||||||
----------
|
----------
|
||||||
symbols : list
|
symbol : Stock
|
||||||
List of stock symbols.
|
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
Dict[str, str]
|
str
|
||||||
Each symbol passed in is a key with its value being a human readable
|
Formatted markdown
|
||||||
markdown formatted string of the symbols price and movement.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
IEXurl = f"https://cloud.iexapis.com/stable/stock/{symbol.id}/quote?token={self.IEX_TOKEN}"
|
IEXurl = f"https://cloud.iexapis.com/stable/stock/{symbol.id}/quote?token={self.IEX_TOKEN}"
|
||||||
@ -223,13 +238,12 @@ class IEX_Symbol:
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
symbols : list
|
symbol : Stock
|
||||||
List of stock symbols.
|
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
Dict[str, str]
|
str
|
||||||
Each symbol passed in is a key with its value being a human readable formatted string of the symbols div dates.
|
Formatted markdown
|
||||||
"""
|
"""
|
||||||
if symbol.symbol.upper() in self.otc_list:
|
if symbol.symbol.upper() in self.otc_list:
|
||||||
return "OTC stocks do not currently support any commands."
|
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."
|
return f"${symbol.id.upper()} either doesn't exist or pays no dividend."
|
||||||
|
|
||||||
def news_reply(self, symbol: Stock) -> str:
|
def news_reply(self, symbol: Stock) -> str:
|
||||||
"""Gets recent english news on stock symbols.
|
"""Gets most recent, english, non-paywalled news
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
symbols : list
|
symbol : Stock
|
||||||
List of stock symbols.
|
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
Dict[str, str]
|
str
|
||||||
Each symbol passed in is a key with its value being a human readable markdown formatted string of the symbols news.
|
Formatted markdown
|
||||||
"""
|
"""
|
||||||
if symbol.symbol.upper() in self.otc_list:
|
if symbol.symbol.upper() in self.otc_list:
|
||||||
return "OTC stocks do not currently support any commands."
|
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])
|
return f"News for **{symbol.id.upper()}**:\n" + "\n".join(line[:5])
|
||||||
|
|
||||||
def info_reply(self, symbol: Stock) -> str:
|
def info_reply(self, symbol: Stock) -> str:
|
||||||
"""Gets information on stock symbols.
|
"""Gets description for Stock
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
symbols : List[str]
|
symbol : Stock
|
||||||
List of stock symbols.
|
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
Dict[str, str]
|
str
|
||||||
Each symbol passed in is a key with its value being a human readable formatted string of the symbols information.
|
Formatted text
|
||||||
"""
|
"""
|
||||||
if symbol.symbol.upper() in self.otc_list:
|
if symbol.symbol.upper() in self.otc_list:
|
||||||
return "OTC stocks do not currently support any commands."
|
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."
|
return f"No information found for: {symbol}\nEither today is boring or the symbol does not exist."
|
||||||
|
|
||||||
def stat_reply(self, symbol: Stock) -> str:
|
def stat_reply(self, symbol: Stock) -> str:
|
||||||
"""Gets key statistics for each symbol in the list
|
"""Key statistics on a Stock
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
symbols : List[str]
|
symbol : Stock
|
||||||
List of stock symbols
|
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
Dict[str, str]
|
str
|
||||||
Each symbol passed in is a key with its value being a human readable formatted string of the symbols statistics.
|
Formatted markdown
|
||||||
"""
|
"""
|
||||||
if symbol.symbol.upper() in self.otc_list:
|
if symbol.symbol.upper() in self.otc_list:
|
||||||
return "OTC stocks do not currently support any commands."
|
return "OTC stocks do not currently support any commands."
|
||||||
@ -400,6 +411,28 @@ class IEX_Symbol:
|
|||||||
else:
|
else:
|
||||||
return f"No information found for: {symbol}\nEither today is boring or the symbol does not exist."
|
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:
|
def intra_reply(self, symbol: Stock) -> pd.DataFrame:
|
||||||
"""Returns price data for a symbol since the last market open.
|
"""Returns price data for a symbol since the last market open.
|
||||||
|
|
||||||
@ -476,17 +509,22 @@ class IEX_Symbol:
|
|||||||
return pd.DataFrame()
|
return pd.DataFrame()
|
||||||
|
|
||||||
def trending(self) -> list[str]:
|
def trending(self) -> list[str]:
|
||||||
"""Gets current coins trending on coingecko
|
"""Gets current coins trending on IEX. Only returns when market is open.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
list[str]
|
list[str]
|
||||||
list of $$ID: NAME
|
list of $ID: NAME, CHANGE%
|
||||||
"""
|
"""
|
||||||
|
|
||||||
stocks = r.get(
|
stocks = r.get(
|
||||||
f"https://cloud.iexapis.com/stable/stock/market/list/mostactive?token={self.IEX_TOKEN}",
|
f"https://cloud.iexapis.com/stable/stock/market/list/mostactive?token={self.IEX_TOKEN}",
|
||||||
timeout=5,
|
timeout=5,
|
||||||
).json()
|
)
|
||||||
|
if stocks.status_code == 200:
|
||||||
return [f"${s['symbol']}: {s['companyName']}" for s in stocks]
|
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 functools
|
||||||
|
|
||||||
import requests as r
|
import requests as r
|
||||||
|
|
||||||
|
|
||||||
@ -25,6 +26,8 @@ class Symbol:
|
|||||||
|
|
||||||
|
|
||||||
class Stock(Symbol):
|
class Stock(Symbol):
|
||||||
|
"""Stock Market Object. Gets data from IEX Cloud"""
|
||||||
|
|
||||||
def __init__(self, symbol: str) -> None:
|
def __init__(self, symbol: str) -> None:
|
||||||
self.symbol = symbol
|
self.symbol = symbol
|
||||||
self.id = symbol
|
self.id = symbol
|
||||||
@ -36,6 +39,8 @@ coins = r.get("https://api.coingecko.com/api/v3/coins/list").json()
|
|||||||
|
|
||||||
|
|
||||||
class Coin(Symbol):
|
class Coin(Symbol):
|
||||||
|
"""Cryptocurrency Object. Gets data from CoinGecko."""
|
||||||
|
|
||||||
@functools.cache
|
@functools.cache
|
||||||
def __init__(self, symbol: str) -> None:
|
def __init__(self, symbol: str) -> None:
|
||||||
self.symbol = symbol
|
self.symbol = symbol
|
||||||
|
14
T_info.py
14
T_info.py
@ -2,6 +2,7 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import requests as r
|
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
|
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.
|
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. 📰
|
- `/news $[symbol]` News about the symbol. 📰
|
||||||
- `/info $[symbol]` General information about the symbol. ℹ️
|
- `/info $[symbol]` General information about the symbol. ℹ️
|
||||||
- `/stat $[symbol]` Key statistics about the symbol. 🔢
|
- `/stat $[symbol]` Key statistics about the symbol. 🔢
|
||||||
|
- `/cap $[symbol]` Market Capitalization of symbol. 💰
|
||||||
- `/trending` Trending Stocks and Cryptos. 💬
|
- `/trending` Trending Stocks and Cryptos. 💬
|
||||||
- `/help` Get some help using the bot. 🆘
|
- `/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)
|
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 = """
|
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.
|
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.
|
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. ℹ️
|
info - $[symbol] General information about the symbol. ℹ️
|
||||||
news - $[symbol] News about the symbol. 📰
|
news - $[symbol] News about the symbol. 📰
|
||||||
stat - $[symbol] Key statistics about the symbol. 🔢
|
stat - $[symbol] Key statistics about the symbol. 🔢
|
||||||
|
cap - $[symbol] Market Capitalization of symbol. 💰
|
||||||
dividend - $[symbol] Dividend info 📅
|
dividend - $[symbol] Dividend info 📅
|
||||||
intra - $[symbol] Plot since the last market open. 📈
|
|
||||||
trending - Trending Stocks and Cryptos. 💬
|
trending - Trending Stocks and Cryptos. 💬
|
||||||
|
intra - $[symbol] Plot since the last market open. 📈
|
||||||
chart - $[chart] Plot of the past month. 📊
|
chart - $[chart] Plot of the past month. 📊
|
||||||
""" # Not used by the bot but for updaing commands with BotFather
|
""" # 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
|
# Works with Python 3.8
|
||||||
import datetime
|
import datetime
|
||||||
|
import html
|
||||||
import io
|
import io
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import html
|
import random
|
||||||
import json
|
import string
|
||||||
import traceback
|
import traceback
|
||||||
|
from logging import critical, debug, error, info, warning
|
||||||
import mplfinance as mpf
|
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
|
import mplfinance as mpf
|
||||||
import telegram
|
import telegram
|
||||||
from telegram import (
|
from telegram import (
|
||||||
InlineQueryResultArticle,
|
InlineQueryResultArticle,
|
||||||
@ -18,13 +20,13 @@ from telegram import (
|
|||||||
Update,
|
Update,
|
||||||
)
|
)
|
||||||
from telegram.ext import (
|
from telegram.ext import (
|
||||||
|
CallbackContext,
|
||||||
CommandHandler,
|
CommandHandler,
|
||||||
Filters,
|
Filters,
|
||||||
InlineQueryHandler,
|
InlineQueryHandler,
|
||||||
MessageHandler,
|
MessageHandler,
|
||||||
PreCheckoutQueryHandler,
|
PreCheckoutQueryHandler,
|
||||||
Updater,
|
Updater,
|
||||||
CallbackContext,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from symbol_router import Router
|
from symbol_router import Router
|
||||||
@ -36,7 +38,7 @@ try:
|
|||||||
STRIPE_TOKEN = os.environ["STRIPE"]
|
STRIPE_TOKEN = os.environ["STRIPE"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
STRIPE_TOKEN = ""
|
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()
|
s = Router()
|
||||||
t = T_info()
|
t = T_info()
|
||||||
@ -47,11 +49,12 @@ logging.basicConfig(
|
|||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
print("Bot Online")
|
info("Bot script started.")
|
||||||
|
|
||||||
|
|
||||||
def start(update: Update, context: CallbackContext):
|
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(
|
update.message.reply_text(
|
||||||
text=t.help_text,
|
text=t.help_text,
|
||||||
parse_mode=telegram.ParseMode.MARKDOWN,
|
parse_mode=telegram.ParseMode.MARKDOWN,
|
||||||
@ -60,7 +63,8 @@ def start(update: Update, context: CallbackContext):
|
|||||||
|
|
||||||
|
|
||||||
def help(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(
|
update.message.reply_text(
|
||||||
text=t.help_text,
|
text=t.help_text,
|
||||||
parse_mode=telegram.ParseMode.MARKDOWN,
|
parse_mode=telegram.ParseMode.MARKDOWN,
|
||||||
@ -69,7 +73,8 @@ def help(update: Update, context: CallbackContext):
|
|||||||
|
|
||||||
|
|
||||||
def license(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(
|
update.message.reply_text(
|
||||||
text=t.license,
|
text=t.license,
|
||||||
parse_mode=telegram.ParseMode.MARKDOWN,
|
parse_mode=telegram.ParseMode.MARKDOWN,
|
||||||
@ -78,6 +83,8 @@ def license(update: Update, context: CallbackContext):
|
|||||||
|
|
||||||
|
|
||||||
def status(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
|
bot_resp = datetime.datetime.now(update.message.date.tzinfo) - update.message.date
|
||||||
|
|
||||||
update.message.reply_text(
|
update.message.reply_text(
|
||||||
@ -90,6 +97,8 @@ def status(update: Update, context: CallbackContext):
|
|||||||
|
|
||||||
|
|
||||||
def donate(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
|
chat_id = update.message.chat_id
|
||||||
|
|
||||||
if update.message.text.strip() == "/donate":
|
if update.message.text.strip() == "/donate":
|
||||||
@ -107,7 +116,7 @@ def donate(update: Update, context: CallbackContext):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
update.message.reply_text(f"{amount} is not a valid donation amount or number.")
|
update.message.reply_text(f"{amount} is not a valid donation amount or number.")
|
||||||
return
|
return
|
||||||
print(price)
|
info(f"Donation amount: {price}")
|
||||||
|
|
||||||
context.bot.send_invoice(
|
context.bot.send_invoice(
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
@ -119,13 +128,15 @@ def donate(update: Update, context: CallbackContext):
|
|||||||
prices=[LabeledPrice("Donation:", price)],
|
prices=[LabeledPrice("Donation:", price)],
|
||||||
start_parameter="",
|
start_parameter="",
|
||||||
# suggested_tip_amounts=[100, 500, 1000, 2000],
|
# 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_width=500,
|
||||||
photo_height=500,
|
photo_height=500,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def precheckout_callback(update: Update, context: CallbackContext):
|
def precheckout_callback(update: Update, context: CallbackContext):
|
||||||
|
"""Approves donation"""
|
||||||
|
info(f"precheckout_callback queried")
|
||||||
query = update.pre_checkout_query
|
query = update.pre_checkout_query
|
||||||
|
|
||||||
query.answer(ok=True)
|
query.answer(ok=True)
|
||||||
@ -138,6 +149,8 @@ def precheckout_callback(update: Update, context: CallbackContext):
|
|||||||
|
|
||||||
|
|
||||||
def successful_payment_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(
|
update.message.reply_text(
|
||||||
"Thank you for your donation! It goes a long way to keeping the bot free!"
|
"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,
|
Runs on any message that doesn't have a command and searches for symbols,
|
||||||
then returns the prices of any symbols found.
|
then returns the prices of any symbols found.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
message = update.message.text
|
message = update.message.text
|
||||||
chat_id = update.message.chat_id
|
chat_id = update.message.chat_id
|
||||||
symbols = s.find_symbols(message)
|
symbols = s.find_symbols(message)
|
||||||
@ -155,7 +169,9 @@ def symbol_detect(update: Update, context: CallbackContext):
|
|||||||
if symbols:
|
if symbols:
|
||||||
# Let user know bot is working
|
# Let user know bot is working
|
||||||
context.bot.send_chat_action(chat_id=chat_id, action=telegram.ChatAction.TYPING)
|
context.bot.send_chat_action(chat_id=chat_id, action=telegram.ChatAction.TYPING)
|
||||||
print(symbols)
|
info(f"User called for symbols: {update.message.chat.username}")
|
||||||
|
info(f"Symbols found: {symbols}")
|
||||||
|
|
||||||
for reply in s.price_reply(symbols):
|
for reply in s.price_reply(symbols):
|
||||||
update.message.reply_text(
|
update.message.reply_text(
|
||||||
text=reply,
|
text=reply,
|
||||||
@ -165,9 +181,8 @@ def symbol_detect(update: Update, context: CallbackContext):
|
|||||||
|
|
||||||
|
|
||||||
def dividend(update: Update, context: CallbackContext):
|
def dividend(update: Update, context: CallbackContext):
|
||||||
"""
|
"""/dividend or /div command and then finds dividend info on that symbol."""
|
||||||
waits for /dividend or /div command and then finds dividend info on that symbol.
|
info(f"Dividend command ran by {update.message.chat.username}")
|
||||||
"""
|
|
||||||
message = update.message.text
|
message = update.message.text
|
||||||
chat_id = update.message.chat_id
|
chat_id = update.message.chat_id
|
||||||
|
|
||||||
@ -190,9 +205,8 @@ def dividend(update: Update, context: CallbackContext):
|
|||||||
|
|
||||||
|
|
||||||
def news(update: Update, context: CallbackContext):
|
def news(update: Update, context: CallbackContext):
|
||||||
"""
|
"""/news command then finds news info on that symbol."""
|
||||||
waits for /news command and then finds news info on that symbol.
|
info(f"News command ran by {update.message.chat.username}")
|
||||||
"""
|
|
||||||
message = update.message.text
|
message = update.message.text
|
||||||
chat_id = update.message.chat_id
|
chat_id = update.message.chat_id
|
||||||
|
|
||||||
@ -215,10 +229,9 @@ def news(update: Update, context: CallbackContext):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def info(update: Update, context: CallbackContext):
|
def information(update: Update, context: CallbackContext):
|
||||||
"""
|
"""/info command then finds info on that symbol."""
|
||||||
waits for /info command and then finds info on that symbol.
|
info(f"Information command ran by {update.message.chat.username}")
|
||||||
"""
|
|
||||||
message = update.message.text
|
message = update.message.text
|
||||||
chat_id = update.message.chat_id
|
chat_id = update.message.chat_id
|
||||||
|
|
||||||
@ -242,6 +255,11 @@ def info(update: Update, context: CallbackContext):
|
|||||||
|
|
||||||
|
|
||||||
def search(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 ", "")
|
message = update.message.text.replace("/search ", "")
|
||||||
chat_id = update.message.chat_id
|
chat_id = update.message.chat_id
|
||||||
|
|
||||||
@ -265,7 +283,8 @@ def search(update: Update, context: CallbackContext):
|
|||||||
|
|
||||||
|
|
||||||
def intra(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
|
message = update.message.text
|
||||||
chat_id = update.message.chat_id
|
chat_id = update.message.chat_id
|
||||||
@ -320,7 +339,8 @@ def intra(update: Update, context: CallbackContext):
|
|||||||
|
|
||||||
|
|
||||||
def chart(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
|
message = update.message.text
|
||||||
chat_id = update.message.chat_id
|
chat_id = update.message.chat_id
|
||||||
@ -350,7 +370,7 @@ def chart(update: Update, context: CallbackContext):
|
|||||||
context.bot.send_chat_action(
|
context.bot.send_chat_action(
|
||||||
chat_id=chat_id, action=telegram.ChatAction.UPLOAD_PHOTO
|
chat_id=chat_id, action=telegram.ChatAction.UPLOAD_PHOTO
|
||||||
)
|
)
|
||||||
print(symbol)
|
|
||||||
buf = io.BytesIO()
|
buf = io.BytesIO()
|
||||||
mpf.plot(
|
mpf.plot(
|
||||||
df,
|
df,
|
||||||
@ -372,9 +392,8 @@ def chart(update: Update, context: CallbackContext):
|
|||||||
|
|
||||||
|
|
||||||
def stat(update: Update, context: CallbackContext):
|
def stat(update: Update, context: CallbackContext):
|
||||||
"""
|
"""returns key statistics on symbol"""
|
||||||
https://iexcloud.io/docs/api/#key-stats
|
info(f"Stat command ran by {update.message.chat.username}")
|
||||||
"""
|
|
||||||
message = update.message.text
|
message = update.message.text
|
||||||
chat_id = update.message.chat_id
|
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):
|
def trending(update: Update, context: CallbackContext):
|
||||||
"""
|
"""returns currently trending symbols and how much they've moved in the past trading day."""
|
||||||
Trending Symbols
|
info(f"Trending command ran by {update.message.chat.username}")
|
||||||
"""
|
|
||||||
|
|
||||||
chat_id = update.message.chat_id
|
chat_id = update.message.chat_id
|
||||||
|
|
||||||
@ -415,17 +458,17 @@ def trending(update: Update, context: CallbackContext):
|
|||||||
|
|
||||||
def inline_query(update: Update, context: CallbackContext):
|
def inline_query(update: Update, context: CallbackContext):
|
||||||
"""
|
"""
|
||||||
Handles inline query.
|
Handles inline query. Searches by looking if query is contained
|
||||||
Does a fuzzy search on input and returns stocks that are close.
|
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]
|
matches = s.inline_search(update.inline_query.query)[:5]
|
||||||
|
|
||||||
symbols = " ".join([match[1].split(":")[0] for match in matches])
|
symbols = " ".join([match[1].split(":")[0] for match in matches])
|
||||||
prices = s.batch_price_reply(s.find_symbols(symbols))
|
prices = s.batch_price_reply(s.find_symbols(symbols))
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
print(update.inline_query.query)
|
|
||||||
for match, price in zip(matches, prices):
|
for match, price in zip(matches, prices):
|
||||||
try:
|
try:
|
||||||
results.append(
|
results.append(
|
||||||
@ -438,16 +481,21 @@ def inline_query(update: Update, context: CallbackContext):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
logging.warning(str(match))
|
warning(f"{match} caused error in inline query.")
|
||||||
pass
|
pass
|
||||||
print(match[0], "\n\n\n")
|
|
||||||
if len(results) == 5:
|
if len(results) == 5:
|
||||||
update.inline_query.answer(results)
|
update.inline_query.answer(results)
|
||||||
|
info("Inline Command was successful")
|
||||||
return
|
return
|
||||||
update.inline_query.answer(results)
|
update.inline_query.answer(results)
|
||||||
|
|
||||||
|
|
||||||
def rand_pick(update: Update, context: CallbackContext):
|
def rand_pick(update: Update, context: CallbackContext):
|
||||||
|
"""For the gamblers. Returns a random symbol to buy and a sell date"""
|
||||||
|
info(
|
||||||
|
f"Someone is gambling! Random_pick command ran by {update.message.chat.username}"
|
||||||
|
)
|
||||||
|
|
||||||
update.message.reply_text(
|
update.message.reply_text(
|
||||||
text=s.random_pick(),
|
text=s.random_pick(),
|
||||||
@ -458,24 +506,32 @@ def rand_pick(update: Update, context: CallbackContext):
|
|||||||
|
|
||||||
def error(update: Update, context: CallbackContext):
|
def error(update: Update, context: CallbackContext):
|
||||||
"""Log Errors caused by Updates."""
|
"""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(
|
tb_list = traceback.format_exception(
|
||||||
None, context.error, context.error.__traceback__
|
None, context.error, context.error.__traceback__
|
||||||
)
|
)
|
||||||
tb_string = "".join(tb_list)
|
tb_string = "".join(tb_list)
|
||||||
print(tb_string)
|
|
||||||
# if update:
|
err_code = "".join([random.choice(string.ascii_lowercase) for i in range(5)])
|
||||||
# message = (
|
warning(f"Logging error: {err_code}")
|
||||||
# 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))}"
|
if update:
|
||||||
# "</pre>\n\n"
|
message = (
|
||||||
# f"<pre>context.chat_data = {html.escape(str(context.chat_data))}</pre>\n\n"
|
f"An exception was raised while handling an update\n"
|
||||||
# f"<pre>context.user_data = {html.escape(str(context.user_data))}</pre>\n\n"
|
f"<pre>update = {html.escape(json.dumps(update.to_dict(), indent=2, ensure_ascii=False))}"
|
||||||
# f"<pre>{html.escape(tb_string)}</pre>"
|
"</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(
|
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
|
# Finally, send the message
|
||||||
@ -498,18 +554,23 @@ def main():
|
|||||||
dp.add_handler(CommandHandler("dividend", dividend))
|
dp.add_handler(CommandHandler("dividend", dividend))
|
||||||
dp.add_handler(CommandHandler("div", dividend))
|
dp.add_handler(CommandHandler("div", dividend))
|
||||||
dp.add_handler(CommandHandler("news", news))
|
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("stat", stat))
|
||||||
dp.add_handler(CommandHandler("stats", stat))
|
dp.add_handler(CommandHandler("stats", stat))
|
||||||
|
dp.add_handler(CommandHandler("cap", cap))
|
||||||
dp.add_handler(CommandHandler("trending", trending))
|
dp.add_handler(CommandHandler("trending", trending))
|
||||||
dp.add_handler(CommandHandler("search", search))
|
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("random", rand_pick))
|
||||||
dp.add_handler(CommandHandler("donate", donate))
|
dp.add_handler(CommandHandler("donate", donate))
|
||||||
dp.add_handler(CommandHandler("status", status))
|
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
|
# on noncommand i.e message - echo the message on Telegram
|
||||||
dp.add_handler(MessageHandler(Filters.text, symbol_detect))
|
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 datetime import datetime
|
||||||
from typing import Optional, List, Tuple
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import requests as r
|
import requests as r
|
||||||
import schedule
|
import schedule
|
||||||
from fuzzywuzzy import fuzz
|
from fuzzywuzzy import fuzz
|
||||||
from markdownify import markdownify
|
from markdownify import markdownify
|
||||||
|
|
||||||
from Symbol import Coin
|
from Symbol import Coin
|
||||||
|
|
||||||
|
|
||||||
@ -117,7 +118,7 @@ class cg_Crypto:
|
|||||||
self.searched_symbols[search] = symbol_list
|
self.searched_symbols[search] = symbol_list
|
||||||
return 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.
|
"""Returns current market price or after hours if its available for a given coin symbol.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@ -133,22 +134,22 @@ class cg_Crypto:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
response = r.get(
|
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,
|
timeout=5,
|
||||||
)
|
)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
name = data["name"]
|
data = response.json()[coin.id]
|
||||||
price = data["market_data"]["current_price"][self.vs_currency]
|
|
||||||
change = data["market_data"]["price_change_percentage_24h"]
|
price = data[self.vs_currency]
|
||||||
|
change = data[self.vs_currency + "_24h_change"]
|
||||||
if change is None:
|
if change is None:
|
||||||
change = 0
|
change = 0
|
||||||
except KeyError:
|
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
|
# Determine wording of change text
|
||||||
if change > 0:
|
if change > 0:
|
||||||
@ -159,7 +160,7 @@ class cg_Crypto:
|
|||||||
message += ", the coin hasn't shown any movement today."
|
message += ", the coin hasn't shown any movement today."
|
||||||
|
|
||||||
else:
|
else:
|
||||||
message = f"The Coin: {symbol.name} was not found."
|
message = f"The Coin: {coin.name} was not found."
|
||||||
|
|
||||||
return message
|
return message
|
||||||
|
|
||||||
@ -220,18 +221,18 @@ class cg_Crypto:
|
|||||||
return pd.DataFrame()
|
return pd.DataFrame()
|
||||||
|
|
||||||
def stat_reply(self, symbol: Coin) -> str:
|
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
|
Parameters
|
||||||
----------
|
----------
|
||||||
symbols : List[str]
|
symbol : Coin
|
||||||
List of coin symbols
|
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
Dict[str, str]
|
str
|
||||||
Each symbol passed in is a key with its value being a human readable formatted string of the symbols statistics.
|
Preformatted markdown.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
response = r.get(
|
response = r.get(
|
||||||
f"https://api.coingecko.com/api/v3/coins/{symbol.id}?localization=false",
|
f"https://api.coingecko.com/api/v3/coins/{symbol.id}?localization=false",
|
||||||
timeout=5,
|
timeout=5,
|
||||||
@ -252,18 +253,53 @@ class cg_Crypto:
|
|||||||
else:
|
else:
|
||||||
return f"{symbol.symbol} returned an error."
|
return f"{symbol.symbol} returned an error."
|
||||||
|
|
||||||
def info_reply(self, symbol: Coin) -> str:
|
def cap_reply(self, coin: Coin) -> str:
|
||||||
"""Gets information on stock symbols.
|
"""Gets market cap for Coin
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
symbols : List[str]
|
coin : Coin
|
||||||
List of stock symbols.
|
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
Dict[str, str]
|
str
|
||||||
Each symbol passed in is a key with its value being a human readable formatted string of the symbols information.
|
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(
|
response = r.get(
|
||||||
@ -285,17 +321,47 @@ class cg_Crypto:
|
|||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
list[str]
|
list[str]
|
||||||
list of $$ID: NAME
|
list of $$ID: NAME, CHANGE%
|
||||||
"""
|
"""
|
||||||
|
|
||||||
coins = r.get(
|
coins = r.get(
|
||||||
"https://api.coingecko.com/api/v3/search/trending",
|
"https://api.coingecko.com/api/v3/search/trending",
|
||||||
timeout=5,
|
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]:
|
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])
|
query = ",".join([c.id for c in coins])
|
||||||
|
|
||||||
prices = r.get(
|
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.
|
"""Function that routes symbols to the correct API provider.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
|
||||||
import pandas as pd
|
|
||||||
import random
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
from logging import critical, debug, error, info, warning
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
from fuzzywuzzy import fuzz
|
from fuzzywuzzy import fuzz
|
||||||
|
|
||||||
from typing import List, Tuple
|
|
||||||
|
|
||||||
from IEX_Symbol import IEX_Symbol
|
|
||||||
from cg_Crypto import cg_Crypto
|
from cg_Crypto import cg_Crypto
|
||||||
|
from IEX_Symbol import IEX_Symbol
|
||||||
from Symbol import Symbol, Stock, Coin
|
from Symbol import Coin, Stock, Symbol
|
||||||
|
|
||||||
|
|
||||||
class Router:
|
class Router:
|
||||||
@ -24,10 +23,9 @@ class Router:
|
|||||||
self.stock = IEX_Symbol()
|
self.stock = IEX_Symbol()
|
||||||
self.crypto = cg_Crypto()
|
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
|
"""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.
|
in a blob of text and returns them in a list.
|
||||||
Only returns each match once. Example: Whats the price of $tsla?
|
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
@ -36,7 +34,7 @@ class Router:
|
|||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
List[str]
|
list[str]
|
||||||
List of stock symbols as strings without dollar sign.
|
List of stock symbols as strings without dollar sign.
|
||||||
"""
|
"""
|
||||||
symbols = []
|
symbols = []
|
||||||
@ -45,15 +43,15 @@ class Router:
|
|||||||
if stock.upper() in self.stock.symbol_list["symbol"].values:
|
if stock.upper() in self.stock.symbol_list["symbol"].values:
|
||||||
symbols.append(Stock(stock))
|
symbols.append(Stock(stock))
|
||||||
else:
|
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))
|
coins = set(re.findall(self.CRYPTO_REGEX, text))
|
||||||
for coin in coins:
|
for coin in coins:
|
||||||
if coin.lower() in self.crypto.symbol_list["symbol"].values:
|
if coin.lower() in self.crypto.symbol_list["symbol"].values:
|
||||||
symbols.append(Coin(coin.lower()))
|
symbols.append(Coin(coin.lower()))
|
||||||
else:
|
else:
|
||||||
print(f"{coin} is not in list of coins")
|
info(f"{coin} is not in list of coins")
|
||||||
print(symbols)
|
info(symbols)
|
||||||
return symbols
|
return symbols
|
||||||
|
|
||||||
def status(self, bot_resp) -> str:
|
def status(self, bot_resp) -> str:
|
||||||
@ -65,7 +63,7 @@ class Router:
|
|||||||
Human readable text on status of the bot and relevant APIs
|
Human readable text on status of the bot and relevant APIs
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return f"""
|
stats = f"""
|
||||||
Bot Status:
|
Bot Status:
|
||||||
{bot_resp}
|
{bot_resp}
|
||||||
|
|
||||||
@ -76,7 +74,11 @@ class Router:
|
|||||||
{self.crypto.status()}
|
{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.
|
"""Performs a fuzzy search to find stock symbols closest to a search term.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@ -86,8 +88,9 @@ class Router:
|
|||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
List[tuple[str, str]]
|
list[tuple[str, str]]
|
||||||
A list tuples of every stock sorted in order of how well they match. Each tuple contains: (Symbol, Issue Name).
|
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])
|
df = pd.concat([self.stock.symbol_list, self.crypto.symbol_list])
|
||||||
@ -113,7 +116,7 @@ class Router:
|
|||||||
self.searched_symbols[search] = symbol_list
|
self.searched_symbols[search] = symbol_list
|
||||||
return 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.
|
"""Searches based on the shortest symbol that contains the same string as the search.
|
||||||
Should be very fast compared to a fuzzy search.
|
Should be very fast compared to a fuzzy search.
|
||||||
|
|
||||||
@ -124,7 +127,7 @@ class Router:
|
|||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
List[tuple[str, str]]
|
list[tuple[str, str]]
|
||||||
Each tuple contains: (Symbol, Issue Name).
|
Each tuple contains: (Symbol, Issue Name).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -141,7 +144,7 @@ class Router:
|
|||||||
self.searched_symbols[search] = symbol_list
|
self.searched_symbols[search] = symbol_list
|
||||||
return 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.
|
"""Returns current market price or after hours if its available for a given stock symbol.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@ -158,17 +161,17 @@ class Router:
|
|||||||
replies = []
|
replies = []
|
||||||
|
|
||||||
for symbol in symbols:
|
for symbol in symbols:
|
||||||
print(symbol)
|
info(symbol)
|
||||||
if isinstance(symbol, Stock):
|
if isinstance(symbol, Stock):
|
||||||
replies.append(self.stock.price_reply(symbol))
|
replies.append(self.stock.price_reply(symbol))
|
||||||
elif isinstance(symbol, Coin):
|
elif isinstance(symbol, Coin):
|
||||||
replies.append(self.crypto.price_reply(symbol))
|
replies.append(self.crypto.price_reply(symbol))
|
||||||
else:
|
else:
|
||||||
print(f"{symbol} is not a Stock or Coin")
|
info(f"{symbol} is not a Stock or Coin")
|
||||||
|
|
||||||
return replies
|
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.
|
"""Returns the most recent, or next dividend date for a stock symbol.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@ -179,7 +182,8 @@ class Router:
|
|||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
Dict[str, str]
|
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 = []
|
replies = []
|
||||||
for symbol in symbols:
|
for symbol in symbols:
|
||||||
@ -192,7 +196,7 @@ class Router:
|
|||||||
|
|
||||||
return replies
|
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.
|
"""Gets recent english news on stock symbols.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@ -203,7 +207,8 @@ class Router:
|
|||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
Dict[str, str]
|
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 = []
|
replies = []
|
||||||
|
|
||||||
@ -220,18 +225,19 @@ class Router:
|
|||||||
|
|
||||||
return replies
|
return replies
|
||||||
|
|
||||||
def info_reply(self, symbols: list) -> List[str]:
|
def info_reply(self, symbols: list) -> list[str]:
|
||||||
"""Gets information on stock symbols.
|
"""Gets information on stock symbols.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
symbols : List[str]
|
symbols : list[str]
|
||||||
List of stock symbols.
|
List of stock symbols.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
Dict[str, str]
|
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 = []
|
replies = []
|
||||||
|
|
||||||
@ -256,7 +262,8 @@ class Router:
|
|||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
pd.DataFrame
|
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):
|
if isinstance(symbol, Stock):
|
||||||
@ -279,7 +286,8 @@ class Router:
|
|||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
pd.DataFrame
|
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):
|
if isinstance(symbol, Stock):
|
||||||
return self.stock.chart_reply(symbol)
|
return self.stock.chart_reply(symbol)
|
||||||
@ -289,18 +297,19 @@ class Router:
|
|||||||
print(f"{symbol} is not a Stock or Coin")
|
print(f"{symbol} is not a Stock or Coin")
|
||||||
return pd.DataFrame()
|
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
|
"""Gets key statistics for each symbol in the list
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
symbols : List[str]
|
symbols : list[str]
|
||||||
List of stock symbols
|
List of stock symbols
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
Dict[str, str]
|
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 = []
|
replies = []
|
||||||
|
|
||||||
@ -314,6 +323,32 @@ class Router:
|
|||||||
|
|
||||||
return replies
|
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:
|
def trending(self) -> str:
|
||||||
"""Checks APIs for trending symbols.
|
"""Checks APIs for trending symbols.
|
||||||
|
|
||||||
@ -326,7 +361,7 @@ class Router:
|
|||||||
stocks = self.stock.trending()
|
stocks = self.stock.trending()
|
||||||
coins = self.crypto.trending()
|
coins = self.crypto.trending()
|
||||||
|
|
||||||
reply = "`Trending Stocks:\n"
|
reply = "Trending Stocks:\n"
|
||||||
reply += "-" * len("Trending Stocks:") + "\n"
|
reply += "-" * len("Trending Stocks:") + "\n"
|
||||||
for stock in stocks:
|
for stock in stocks:
|
||||||
reply += stock + "\n"
|
reply += stock + "\n"
|
||||||
@ -336,7 +371,7 @@ class Router:
|
|||||||
for coin in coins:
|
for coin in coins:
|
||||||
reply += coin + "\n"
|
reply += coin + "\n"
|
||||||
|
|
||||||
return reply + "`"
|
return reply
|
||||||
|
|
||||||
def random_pick(self) -> str:
|
def random_pick(self) -> str:
|
||||||
|
|
||||||
@ -350,7 +385,7 @@ class Router:
|
|||||||
|
|
||||||
return f"{choice}\nBuy and hold until: {hold}"
|
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.
|
"""Returns current market price or after hours if its available for a given stock symbol.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
Loading…
x
Reference in New Issue
Block a user