mirror of
https://gitlab.com/simple-stock-bots/simple-stock-bot.git
synced 2025-06-16 15:17:28 +00:00
get bot working with marketdata api
This commit is contained in:
parent
bc1ad95c75
commit
b72f0518c2
7
.vscode/launch.json
vendored
7
.vscode/launch.json
vendored
@ -1,15 +1,12 @@
|
|||||||
{
|
{
|
||||||
// Use IntelliSense to learn about possible attributes.
|
|
||||||
// Hover to view descriptions of existing attributes.
|
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "Telegram Bot",
|
"name": "Telegram Bot",
|
||||||
"type": "python",
|
"type": "python",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "bot.py",
|
"program": "bot.py",
|
||||||
"console": "integratedTerminal"
|
"console": "integratedTerminal",
|
||||||
|
"python": "python3.11"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
134
MarketData.py
134
MarketData.py
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from datetime import datetime
|
import datetime as dt
|
||||||
from logging import warning
|
from logging import warning
|
||||||
from typing import List, Optional, Tuple
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ class MarketData:
|
|||||||
MarketData.app API Token
|
MarketData.app API Token
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.MARKETDATA_TOKEN = os.environ["MarketData"]
|
self.MARKETDATA_TOKEN = os.environ["MARKETDATA"]
|
||||||
|
|
||||||
if self.MARKETDATA_TOKEN == "TOKEN":
|
if self.MARKETDATA_TOKEN == "TOKEN":
|
||||||
self.MARKETDATA_TOKEN = ""
|
self.MARKETDATA_TOKEN = ""
|
||||||
@ -46,12 +46,9 @@ class MarketData:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if self.MARKETDATA_TOKEN != "":
|
if self.MARKETDATA_TOKEN != "":
|
||||||
self.get_symbol_list()
|
|
||||||
|
|
||||||
schedule.every().day.do(self.get_symbol_list)
|
|
||||||
schedule.every().day.do(self.clear_charts)
|
schedule.every().day.do(self.clear_charts)
|
||||||
|
|
||||||
def get(self, endpoint, params: dict = {}, timeout=5) -> dict:
|
def get(self, endpoint, params: dict = {}, timeout=10) -> dict:
|
||||||
url = "https://api.marketdata.app/v1/" + endpoint
|
url = "https://api.marketdata.app/v1/" + endpoint
|
||||||
|
|
||||||
# set token param if it wasn't passed.
|
# set token param if it wasn't passed.
|
||||||
@ -91,43 +88,6 @@ class MarketData:
|
|||||||
"""
|
"""
|
||||||
self.charts = {}
|
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 = self.get("/ref-data/symbols")
|
|
||||||
otc_symbols = self.get("/ref-data/otc/symbols")
|
|
||||||
|
|
||||||
reg = pd.DataFrame(data=reg_symbols)
|
|
||||||
otc = pd.DataFrame(data=otc_symbols)
|
|
||||||
self.otc_list = set(otc["symbol"].to_list())
|
|
||||||
|
|
||||||
symbols = pd.concat([reg, otc])
|
|
||||||
|
|
||||||
# IEX uses backtick ` as apostrophe which breaks telegram markdown parsing
|
|
||||||
symbols["name"] = symbols["name"].str.replace("`", "'")
|
|
||||||
|
|
||||||
symbols["description"] = "$" + symbols["symbol"] + ": " + symbols["name"]
|
|
||||||
symbols["id"] = symbols["symbol"]
|
|
||||||
symbols["type_id"] = "$" + symbols["symbol"].str.lower()
|
|
||||||
|
|
||||||
symbols = symbols[["id", "symbol", "name", "description", "type_id"]]
|
|
||||||
self.symbol_list = symbols
|
|
||||||
if return_df:
|
|
||||||
return symbols, datetime.now()
|
|
||||||
|
|
||||||
def status(self) -> str:
|
def status(self) -> str:
|
||||||
return "status isnt implemented by marketdata.app"
|
return "status isnt implemented by marketdata.app"
|
||||||
|
|
||||||
@ -144,12 +104,64 @@ class MarketData:
|
|||||||
Formatted markdown
|
Formatted markdown
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if quoteResp := self.get(f"/stocks/quotes/{symbol}/"):
|
if quoteResp := self.get(f"stocks/quotes/{symbol}/"):
|
||||||
return f"The current price of {quoteResp['symbol']} is ${quoteResp['last']}"
|
return f"The current price of {quoteResp['symbol']} is ${quoteResp['last']}"
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return f"Getting a quote for {symbol} encountered an error."
|
return f"Getting a quote for {symbol} encountered an error."
|
||||||
|
|
||||||
|
def intra_reply(self, symbol: Stock) -> pd.DataFrame:
|
||||||
|
"""Returns price data for a symbol of the past month up until the previous trading days close.
|
||||||
|
Also caches multiple requests made in the same day.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
symbol : str
|
||||||
|
Stock symbol.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
pd.DataFrame
|
||||||
|
Returns a timeseries dataframe with high, low, and volume data if its available. Otherwise returns empty pd.DataFrame.
|
||||||
|
"""
|
||||||
|
schedule.run_pending()
|
||||||
|
|
||||||
|
try:
|
||||||
|
return self.charts[symbol.id.upper()]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
to_date = dt.datetime.today().strftime("%Y-%m-%d")
|
||||||
|
resolution = "5" # minutes
|
||||||
|
|
||||||
|
if data := self.get(
|
||||||
|
f"stocks/candles/{resolution}/{symbol}",
|
||||||
|
params={
|
||||||
|
"from": dt.datetime.now().strftime("%Y-%m-%d"),
|
||||||
|
"to": dt.datetime.now().isoformat(),
|
||||||
|
},
|
||||||
|
):
|
||||||
|
data.pop("s")
|
||||||
|
df = pd.DataFrame(data)
|
||||||
|
df["t"] = pd.to_datetime(df["t"], unit="s")
|
||||||
|
df.set_index("t", inplace=True)
|
||||||
|
|
||||||
|
df.rename(
|
||||||
|
columns={
|
||||||
|
"o": "Open",
|
||||||
|
"h": "High",
|
||||||
|
"l": "Low",
|
||||||
|
"c": "Close",
|
||||||
|
"v": "Volume",
|
||||||
|
},
|
||||||
|
inplace=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.charts[symbol.id.upper()] = df
|
||||||
|
return df
|
||||||
|
|
||||||
|
return pd.DataFrame()
|
||||||
|
|
||||||
def chart_reply(self, symbol: Stock) -> pd.DataFrame:
|
def chart_reply(self, symbol: Stock) -> pd.DataFrame:
|
||||||
"""Returns price data for a symbol of the past month up until the previous trading days close.
|
"""Returns price data for a symbol of the past month up until the previous trading days close.
|
||||||
Also caches multiple requests made in the same day.
|
Also caches multiple requests made in the same day.
|
||||||
@ -171,24 +183,34 @@ class MarketData:
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
to_date = datetime.today().strftime("%Y-%m-%d")
|
to_date = dt.datetime.today().strftime("%Y-%m-%d")
|
||||||
from_date = (datetime.today() - datetime.timedelta(days=30)).strftime(
|
from_date = (dt.datetime.today() - dt.timedelta(days=30)).strftime("%Y-%m-%d")
|
||||||
"%Y-%m-%d"
|
resultion = "daily"
|
||||||
)
|
|
||||||
|
|
||||||
if data := self.get(
|
if data := self.get(
|
||||||
f"/stocks/candles/h/{symbol}",
|
f"stocks/candles/{resultion}/{symbol}",
|
||||||
params={
|
params={
|
||||||
"chartInterval": 3,
|
|
||||||
"to": to_date,
|
|
||||||
"from": from_date,
|
"from": from_date,
|
||||||
|
"to": to_date,
|
||||||
},
|
},
|
||||||
):
|
):
|
||||||
df = pd.DataFrame(
|
data.pop("s")
|
||||||
data, columns=["Date", "Open", "High", "Low", "Close"]
|
|
||||||
).dropna()
|
df = pd.DataFrame(data)
|
||||||
df["DT"] = pd.to_datetime(df["date"] + "T" + df["minute"])
|
df["t"] = pd.to_datetime(df["t"], unit="s")
|
||||||
df = df.set_index("DT")
|
df.set_index("t", inplace=True)
|
||||||
|
|
||||||
|
df.rename(
|
||||||
|
columns={
|
||||||
|
"o": "Open",
|
||||||
|
"h": "High",
|
||||||
|
"l": "Low",
|
||||||
|
"c": "Close",
|
||||||
|
"v": "Volume",
|
||||||
|
},
|
||||||
|
inplace=True,
|
||||||
|
)
|
||||||
|
|
||||||
self.charts[symbol.id.upper()] = df
|
self.charts[symbol.id.upper()] = df
|
||||||
return df
|
return df
|
||||||
|
|
||||||
|
16
Symbol.py
16
Symbol.py
@ -27,17 +27,13 @@ class Symbol:
|
|||||||
|
|
||||||
|
|
||||||
class Stock(Symbol):
|
class Stock(Symbol):
|
||||||
"""Stock Market Object. Gets data from IEX Cloud"""
|
"""Stock Market Object. Gets data from MarketData"""
|
||||||
|
|
||||||
def __init__(self, symbol: pd.DataFrame) -> None:
|
def __init__(self, symbol: str) -> None:
|
||||||
if len(symbol) > 1:
|
self.symbol = symbol
|
||||||
logging.info(f"Crypto with shared id:\n\t{symbol.id}")
|
self.id = symbol
|
||||||
symbol = symbol.head(1)
|
self.name = "$" + symbol
|
||||||
|
self.tag = "$" + symbol.upper()
|
||||||
self.symbol = symbol.symbol.values[0]
|
|
||||||
self.id = symbol.id.values[0]
|
|
||||||
self.name = symbol.name.values[0]
|
|
||||||
self.tag = symbol.type_id.values[0].upper()
|
|
||||||
|
|
||||||
|
|
||||||
class Coin(Symbol):
|
class Coin(Symbol):
|
||||||
|
4
bot.py
4
bot.py
@ -238,7 +238,7 @@ def intra(update: Update, context: CallbackContext):
|
|||||||
df,
|
df,
|
||||||
type="renko",
|
type="renko",
|
||||||
title=f"\n{symbol.name}",
|
title=f"\n{symbol.name}",
|
||||||
volume="volume" in df.keys(),
|
volume="Volume" in df.keys(),
|
||||||
style="yahoo",
|
style="yahoo",
|
||||||
savefig=dict(fname=buf, dpi=400, bbox_inches="tight"),
|
savefig=dict(fname=buf, dpi=400, bbox_inches="tight"),
|
||||||
)
|
)
|
||||||
@ -292,7 +292,7 @@ def chart(update: Update, context: CallbackContext):
|
|||||||
df,
|
df,
|
||||||
type="candle",
|
type="candle",
|
||||||
title=f"\n{symbol.name}",
|
title=f"\n{symbol.name}",
|
||||||
volume="volume" in df.keys(),
|
volume="Volume" in df.keys(),
|
||||||
style="yahoo",
|
style="yahoo",
|
||||||
savefig=dict(fname=buf, dpi=400, bbox_inches="tight"),
|
savefig=dict(fname=buf, dpi=400, bbox_inches="tight"),
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
python-telegram-bot==13.5
|
python-telegram-bot==13.5
|
||||||
requests==2.25.1
|
requests==2.25.1
|
||||||
pandas==1.2.1
|
pandas==2.0.0
|
||||||
schedule==1.0.0
|
schedule==1.0.0
|
||||||
mplfinance==0.12.7a5
|
mplfinance==0.12.7a5
|
||||||
markdownify==0.6.5
|
markdownify==0.6.5
|
||||||
|
@ -64,14 +64,8 @@ class Router:
|
|||||||
symbols = []
|
symbols = []
|
||||||
stocks = set(re.findall(self.STOCK_REGEX, text))
|
stocks = set(re.findall(self.STOCK_REGEX, text))
|
||||||
for stock in stocks:
|
for stock in stocks:
|
||||||
sym = self.stock.symbol_list[
|
# Market data lacks tools to check if a symbol is valid.
|
||||||
self.stock.symbol_list["symbol"].str.fullmatch(stock, case=False)
|
symbols.append(Stock(stock))
|
||||||
]
|
|
||||||
if ~sym.empty:
|
|
||||||
print(sym)
|
|
||||||
symbols.append(Stock(sym))
|
|
||||||
else:
|
|
||||||
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:
|
||||||
@ -172,60 +166,6 @@ class Router:
|
|||||||
|
|
||||||
return replies
|
return replies
|
||||||
|
|
||||||
def dividend_reply(self, symbols: list) -> list[str]:
|
|
||||||
"""Returns the most recent, or next dividend date for a stock symbol.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
symbols : list
|
|
||||||
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 div dates.
|
|
||||||
"""
|
|
||||||
replies = []
|
|
||||||
for symbol in symbols:
|
|
||||||
if isinstance(symbol, Stock):
|
|
||||||
replies.append(self.stock.dividend_reply(symbol))
|
|
||||||
elif isinstance(symbol, Coin):
|
|
||||||
replies.append("Cryptocurrencies do no have Dividends.")
|
|
||||||
else:
|
|
||||||
debug(f"{symbol} is not a Stock or Coin")
|
|
||||||
|
|
||||||
return replies
|
|
||||||
|
|
||||||
def news_reply(self, symbols: list) -> list[str]:
|
|
||||||
"""Gets recent english news on stock symbols.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
symbols : list
|
|
||||||
List of stock symbols.
|
|
||||||
|
|
||||||
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.
|
|
||||||
"""
|
|
||||||
replies = []
|
|
||||||
|
|
||||||
for symbol in symbols:
|
|
||||||
if isinstance(symbol, Stock):
|
|
||||||
replies.append(self.stock.news_reply(symbol))
|
|
||||||
elif isinstance(symbol, Coin):
|
|
||||||
# replies.append(self.crypto.news_reply(symbol))
|
|
||||||
replies.append(
|
|
||||||
"News is not yet supported for cryptocurrencies. If you have any suggestions for news sources please contatct @MisterBiggs"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
debug(f"{symbol} is not a Stock or Coin")
|
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user