1
0
mirror of https://gitlab.com/simple-stock-bots/simple-stock-bot.git synced 2025-06-16 15:17:28 +00:00

API requests are now made under one function

This commit is contained in:
Anson 2021-08-13 15:45:30 -07:00
parent 6d2b9309d7
commit 55e4fe5d70
2 changed files with 154 additions and 157 deletions

View File

@ -1,6 +1,7 @@
"""Class with functions for running the bot with IEX Cloud. """Class with functions for running the bot with IEX Cloud.
""" """
import logging
import os import os
from datetime import datetime from datetime import datetime
from logging import warning from logging import warning
@ -47,6 +48,30 @@ class IEX_Symbol:
schedule.every().day.do(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:
url = "https://cloud.iexapis.com/stable" + endpoint
# set token param if it wasn't passed.
params["token"] = params.get("token", self.IEX_TOKEN)
resp = r.get(url, params=params, timeout=timeout)
# Make sure API returned a proper status code
try:
resp.raise_for_status()
except r.exceptions.HTTPError as e:
logging.error(e)
return {}
# Make sure API returned valid JSON
try:
resp_json = resp.json()
return resp_json
except r.exceptions.JSONDecodeError as e:
logging.error(e)
return {}
def clear_charts(self) -> None: def clear_charts(self) -> None:
""" """
Clears cache of chart data. Clears cache of chart data.
@ -71,14 +96,8 @@ class IEX_Symbol:
If `return_df` is set to `True` returns a dataframe, otherwise returns `None`. If `return_df` is set to `True` returns a dataframe, otherwise returns `None`.
""" """
reg_symbols = r.get( reg_symbols = self.get("/ref-data/symbols")
f"https://cloud.iexapis.com/stable/ref-data/symbols?token={self.IEX_TOKEN}", otc_symbols = self.get("/ref-data/otc/symbols")
timeout=5,
).json()
otc_symbols = r.get(
f"https://cloud.iexapis.com/stable/ref-data/otc/symbols?token={self.IEX_TOKEN}",
timeout=5,
).json()
reg = pd.DataFrame(data=reg_symbols) reg = pd.DataFrame(data=reg_symbols)
otc = pd.DataFrame(data=otc_symbols) otc = pd.DataFrame(data=otc_symbols)
@ -105,7 +124,7 @@ class IEX_Symbol:
""" """
resp = r.get( resp = r.get(
"https://pjmps0c34hp7.statuspage.io/api/v2/status.json", "https://pjmps0c34hp7.statuspage.io/api/v2/status.json",
timeout=5, timeout=15,
) )
if resp.status_code == 200: if resp.status_code == 200:
@ -174,14 +193,7 @@ class IEX_Symbol:
Formatted markdown Formatted markdown
""" """
IEXurl = f"https://cloud.iexapis.com/stable/stock/{symbol.id}/quote?token={self.IEX_TOKEN}" if IEXData := self.get(f"/stock/{symbol.id}/quote"):
response = r.get(
IEXurl,
timeout=5,
)
if response.status_code == 200:
IEXData = response.json()
if symbol.symbol.upper() in self.otc_list: if symbol.symbol.upper() in self.otc_list:
return f"OTC - {symbol.symbol.upper()}, {IEXData['companyName']} most recent price is: $**{IEXData['latestPrice']}**" return f"OTC - {symbol.symbol.upper()}, {IEXData['companyName']} most recent price is: $**{IEXData['latestPrice']}**"
@ -249,13 +261,11 @@ class IEX_Symbol:
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."
IEXurl = f"https://cloud.iexapis.com/stable/stock/{symbol}/dividends/next?token={self.IEX_TOKEN}" if resp := self.get(f"/stock/{symbol.id}/dividends/next"):
response = r.get( try:
IEXurl, IEXData = resp[0]
timeout=5, except IndexError as e:
) return f"${symbol.id.upper()} either doesn't exist or pays no dividend."
if response.status_code == 200 and response.json():
IEXData = response.json()[0]
keys = ( keys = (
"amount", "amount",
"currency", "currency",
@ -313,28 +323,19 @@ class IEX_Symbol:
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."
IEXurl = f"https://cloud.iexapis.com/stable/stock/{symbol}/news/last/15?token={self.IEX_TOKEN}" if data := self.get(f"/stock/{symbol.id}/news/last/15"):
response = r.get( line = []
IEXurl,
timeout=5,
)
if response.status_code == 200:
data = response.json()
if data:
line = []
for news in data: for news in data:
if news["lang"] == "en" and not news["hasPaywall"]: if news["lang"] == "en" and not news["hasPaywall"]:
line.append( line.append(
f"*{news['source']}*: [{news['headline']}]({news['url']})" f"*{news['source']}*: [{news['headline']}]({news['url']})"
) )
return f"News for **{symbol.id.upper()}**:\n" + "\n".join(line[:5])
else:
return f"No news found for: {symbol}\nEither today is boring or the symbol does not exist."
else: else:
return f"No news found for: {symbol}\nEither today is boring or the symbol does not exist." return f"No news found for: {symbol.id}\nEither today is boring or the symbol does not exist."
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 description for Stock """Gets description for Stock
@ -351,14 +352,7 @@ class IEX_Symbol:
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."
IEXurl = f"https://cloud.iexapis.com/stable/stock/{symbol}/company?token={self.IEX_TOKEN}" if data := self.get(f"/stock/{symbol.id}/company"):
response = r.get(
IEXurl,
timeout=5,
)
if response.status_code == 200:
data = response.json()
[data.pop(k) for k in list(data) if data[k] == ""] [data.pop(k) for k in list(data) if data[k] == ""]
if "description" in data: if "description" in data:
@ -381,14 +375,7 @@ class IEX_Symbol:
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."
IEXurl = f"https://cloud.iexapis.com/stable/stock/{symbol}/stats?token={self.IEX_TOKEN}" if data := self.get(f"/stock/{symbol.id}/stats"):
response = r.get(
IEXurl,
timeout=5,
)
if response.status_code == 200:
data = response.json()
[data.pop(k) for k in list(data) if data[k] == ""] [data.pop(k) for k in list(data) if data[k] == ""]
m = "" m = ""
@ -412,25 +399,20 @@ 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: def cap_reply(self, symbol: Stock) -> str:
"""Get the Market Cap of a stock""" """Get the Market Cap of a stock"""
response = r.get(
f"https://cloud.iexapis.com/stable/stock/{stock.id}/stats?token={self.IEX_TOKEN}", if data := self.get(f"/stable/stock/{symbol.id}/stats"):
timeout=5,
)
if response.status_code == 200:
try: try:
data = response.json()
cap = data["marketcap"] cap = data["marketcap"]
except KeyError: except KeyError:
return f"{stock.id} returned an error." return f"{symbol.id} returned an error."
message = f"The current market cap of {stock.name} is $**{cap:,.2f}**" message = f"The current market cap of {symbol.name} is $**{cap:,.2f}**"
else: else:
message = f"The Coin: {stock.name} was not found or returned and error." message = f"The Stock: {symbol.name} was not found or returned and error."
return message return message
@ -453,13 +435,8 @@ class IEX_Symbol:
if symbol.id.upper() not in list(self.symbol_list["symbol"]): if symbol.id.upper() not in list(self.symbol_list["symbol"]):
return pd.DataFrame() return pd.DataFrame()
IEXurl = f"https://cloud.iexapis.com/stable/stock/{symbol}/intraday-prices?token={self.IEX_TOKEN}" if data := self.get(f"/stock/{symbol.id}/intraday-prices"):
response = r.get( df = pd.DataFrame(data)
IEXurl,
timeout=5,
)
if response.status_code == 200:
df = pd.DataFrame(response.json())
df.dropna(inplace=True, subset=["date", "minute", "high", "low", "volume"]) df.dropna(inplace=True, subset=["date", "minute", "high", "low", "volume"])
df["DT"] = pd.to_datetime(df["date"] + "T" + df["minute"]) df["DT"] = pd.to_datetime(df["date"] + "T" + df["minute"])
df = df.set_index("DT") df = df.set_index("DT")
@ -494,13 +471,11 @@ class IEX_Symbol:
except KeyError: except KeyError:
pass pass
response = r.get( if data := self.get(
f"https://cloud.iexapis.com/stable/stock/{symbol}/chart/1mm?token={self.IEX_TOKEN}&chartInterval=3&includeToday=false", f"/stock/{symbol.id}/chart/1mm",
timeout=5, params={"chartInterval": 3, "includeToday": "false"},
) ):
df = pd.DataFrame(data)
if response.status_code == 200:
df = pd.DataFrame(response.json())
df.dropna(inplace=True, subset=["date", "minute", "high", "low", "volume"]) df.dropna(inplace=True, subset=["date", "minute", "high", "low", "volume"])
df["DT"] = pd.to_datetime(df["date"] + "T" + df["minute"]) df["DT"] = pd.to_datetime(df["date"] + "T" + df["minute"])
df = df.set_index("DT") df = df.set_index("DT")
@ -518,14 +493,10 @@ class IEX_Symbol:
list of $ID: NAME, CHANGE% list of $ID: NAME, CHANGE%
""" """
stocks = r.get( if data := self.get(f"/stock/market/list/mostactive"):
f"https://cloud.iexapis.com/stable/stock/market/list/mostactive?token={self.IEX_TOKEN}",
timeout=5,
)
if stocks.status_code == 200:
return [ return [
f"`${s['symbol']}`: {s['companyName']}, {100*s['changePercent']:.2f}%" f"`${s['symbol']}`: {s['companyName']}, {100*s['changePercent']:.2f}%"
for s in stocks.json() for s in data
] ]
else: else:
return ["Trending Stocks Currently Unavailable."] return ["Trending Stocks Currently Unavailable."]

View File

@ -1,6 +1,7 @@
"""Class with functions for running the bot with IEX Cloud. """Class with functions for running the bot with IEX Cloud.
""" """
import logging
from datetime import datetime from datetime import datetime
from typing import List, Optional, Tuple from typing import List, Optional, Tuple
@ -33,6 +34,25 @@ class cg_Crypto:
self.get_symbol_list() self.get_symbol_list()
schedule.every().day.do(self.get_symbol_list) schedule.every().day.do(self.get_symbol_list)
def get(self, endpoint, params: dict = {}, timeout=10) -> dict:
url = "https://api.coingecko.com/api/v3" + endpoint
resp = r.get(url, params=params, timeout=timeout)
# Make sure API returned a proper status code
try:
resp.raise_for_status()
except r.exceptions.HTTPError as e:
logging.error(e)
return {}
# Make sure API returned valid JSON
try:
resp_json = resp.json()
return resp_json
except r.exceptions.JSONDecodeError as e:
logging.error(e)
return {}
def symbol_id(self, symbol) -> str: def symbol_id(self, symbol) -> str:
try: try:
return self.symbol_list[self.symbol_list["symbol"] == symbol]["id"].values[ return self.symbol_list[self.symbol_list["symbol"] == symbol]["id"].values[
@ -45,10 +65,7 @@ class cg_Crypto:
self, return_df=False self, return_df=False
) -> Optional[Tuple[pd.DataFrame, datetime]]: ) -> Optional[Tuple[pd.DataFrame, datetime]]:
raw_symbols = r.get( raw_symbols = self.get("/coins/list")
"https://api.coingecko.com/api/v3/coins/list",
timeout=5,
).json()
symbols = pd.DataFrame(data=raw_symbols) symbols = pd.DataFrame(data=raw_symbols)
symbols["description"] = ( symbols["description"] = (
@ -69,15 +86,16 @@ class cg_Crypto:
str str
Human readable text on status of CoinGecko API Human readable text on status of CoinGecko API
""" """
status = r.get( status = self.get(
"https://api.coingecko.com/api/v3/ping", "/ping",
timeout=5, timeout=5,
) )
if status.status_code == 200: try:
return f"CoinGecko API responded that it was OK in {status.elapsed.total_seconds()} Seconds." status.raise_for_status()
else: return f"CoinGecko API responded that it was OK with a {status.status_code} in {status.elapsed.total_seconds()} Seconds."
return f"CoinGecko API returned an error in {status.elapsed.total_seconds()} Seconds." except:
return f"CoinGecko API returned an error code {status.status_code} in {status.elapsed.total_seconds()} Seconds."
def search_symbols(self, search: str) -> List[Tuple[str, str]]: def search_symbols(self, search: str) -> List[Tuple[str, str]]:
"""Performs a fuzzy search to find coin symbols closest to a search term. """Performs a fuzzy search to find coin symbols closest to a search term.
@ -133,14 +151,16 @@ class cg_Crypto:
markdown formatted string of the symbols price and movement. markdown formatted string of the symbols price and movement.
""" """
response = r.get( if resp := self.get(
f"https://api.coingecko.com/api/v3/simple/price?ids={coin.id}&vs_currencies={self.vs_currency}&include_24hr_change=true", "/simple/price",
timeout=5, params={
) "ids": coin.id,
if response.status_code == 200: "vs_currencies": self.vs_currency,
"include_24hr_change": "true",
},
):
try: try:
data = response.json()[coin.id] data = resp[coin.id]
price = data[self.vs_currency] price = data[self.vs_currency]
change = data[self.vs_currency + "_24h_change"] change = data[self.vs_currency + "_24h_change"]
@ -160,7 +180,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: {coin.name} was not found." message = f"The price for {coin.name} is not available. If you suspect this is an error run `/status`"
return message return message
@ -177,13 +197,13 @@ class cg_Crypto:
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.
""" """
response = r.get(
f"https://api.coingecko.com/api/v3/coins/{symbol.id}/ohlc?vs_currency=usd&days=1", if resp := self.get(
timeout=5, f"/coins/{symbol.id}/ohlc",
) params={"vs_currency": self.vs_currency, "days": 1},
if response.status_code == 200: ):
df = pd.DataFrame( df = pd.DataFrame(
response.json(), columns=["Date", "Open", "High", "Low", "Close"] resp, columns=["Date", "Open", "High", "Low", "Close"]
).dropna() ).dropna()
df["Date"] = pd.to_datetime(df["Date"], unit="ms") df["Date"] = pd.to_datetime(df["Date"], unit="ms")
df = df.set_index("Date") df = df.set_index("Date")
@ -205,14 +225,13 @@ class cg_Crypto:
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.
""" """
response = r.get(
f"https://api.coingecko.com/api/v3/coins/{symbol.id}/ohlc?vs_currency=usd&days=30",
timeout=5,
)
if response.status_code == 200: if resp := self.get(
f"/coins/{symbol.id}/ohlc",
params={"vs_currency": self.vs_currency, "days": 30},
):
df = pd.DataFrame( df = pd.DataFrame(
response.json(), columns=["Date", "Open", "High", "Low", "Close"] resp, columns=["Date", "Open", "High", "Low", "Close"]
).dropna() ).dropna()
df["Date"] = pd.to_datetime(df["Date"], unit="ms") df["Date"] = pd.to_datetime(df["Date"], unit="ms")
df = df.set_index("Date") df = df.set_index("Date")
@ -233,12 +252,12 @@ class cg_Crypto:
Preformatted markdown. Preformatted markdown.
""" """
response = r.get( if data := self.get(
f"https://api.coingecko.com/api/v3/coins/{symbol.id}?localization=false", f"/coins/{symbol.id}",
timeout=5, params={
) "localization": "false",
if response.status_code == 200: },
data = response.json() ):
return f""" return f"""
[{data['name']}]({data['links']['homepage'][0]}) Statistics: [{data['name']}]({data['links']['homepage'][0]}) Statistics:
@ -265,14 +284,18 @@ class cg_Crypto:
str str
Preformatted markdown. 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:
if resp := self.get(
f"/simple/price",
params={
"ids": coin.id,
"vs_currencies": self.vs_currency,
"include_market_cap": "true",
},
):
print(resp)
try: try:
data = response.json()[coin.id] data = resp[coin.id]
price = data[self.vs_currency] price = data[self.vs_currency]
cap = data[self.vs_currency + "_market_cap"] cap = data[self.vs_currency + "_market_cap"]
@ -302,12 +325,10 @@ class cg_Crypto:
Preformatted markdown. Preformatted markdown.
""" """
response = r.get( if data := self.get(
f"https://api.coingecko.com/api/v3/coins/{symbol.id}?localization=false", f"/coins/{symbol.id}",
timeout=5, params={"localization": "false"},
) ):
if response.status_code == 200:
data = response.json()
try: try:
return markdownify(data["description"]["en"]) return markdownify(data["description"]["en"])
except KeyError: except KeyError:
@ -324,28 +345,29 @@ class cg_Crypto:
list of $$ID: NAME, CHANGE% list of $$ID: NAME, CHANGE%
""" """
coins = r.get( coins = self.get("/search/trending")
"https://api.coingecko.com/api/v3/search/trending",
timeout=5,
)
try: try:
trending = [] trending = []
if coins.status_code == 200: for coin in coins["coins"]:
for coin in coins.json()["coins"]: c = coin["item"]
c = coin["item"]
sym = c["symbol"].upper() sym = c["symbol"].upper()
name = c["name"] name = c["name"]
change = r.get( change = self.get(
f"https://api.coingecko.com/api/v3/simple/price?ids={c['id']}&vs_currencies={self.vs_currency}&include_24hr_change=true" f"/simple/price",
).json()[c["id"]]["usd_24h_change"] params={
"ids": c["id"],
"vs_currencies": self.vs_currency,
"include_24hr_change": "true",
},
)[c["id"]]["usd_24h_change"]
msg = f"`$${sym}`: {name}, {change:.2f}%" msg = f"`$${sym}`: {name}, {change:.2f}%"
trending.append(msg) trending.append(msg)
except Exception as e: except Exception as e:
print(e) logging.warning(e)
trending = ["Trending Coins Currently Unavailable."] trending = ["Trending Coins Currently Unavailable."]
return trending return trending
@ -364,10 +386,14 @@ class cg_Crypto:
""" """
query = ",".join([c.id for c in coins]) query = ",".join([c.id for c in coins])
prices = r.get( prices = self.get(
f"https://api.coingecko.com/api/v3/simple/price?ids={query}&vs_currencies=usd&include_24hr_change=true", f"/simple/price",
timeout=5, params={
).json() "ids": query,
"vs_currencies": self.vs_currency,
"include_24hr_change": "true",
},
)
replies = [] replies = []
for coin in coins: for coin in coins: