1
0
mirror of https://gitlab.com/simple-stock-bots/simple-stock-bot.git synced 2026-06-03 21:00:26 +00:00

Fix bugs in MarketData and CoinGecko handlers

- Change logging.error to logging.debug for response headers in MarketData.get()
- Fix MarketData.status() to check its own API instead of CoinGecko uptime monitor
- Fix f-string bug in cg_Crypto.cap_reply (market cap was not interpolated)
- Add exponential backoff with max 3 retries for CoinGecko 429 errors
- Fix formatting issues in cg_Crypto (quote consistency, spacing)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Anson Biggs
2026-02-21 16:52:42 -05:00
parent 018613e896
commit b8f2f6998a
2 changed files with 31 additions and 28 deletions
+9 -15
View File
@@ -74,7 +74,7 @@ class MarketData:
resp = r.get(url, params=params, timeout=timeout, headers=headers)
logging.error(resp.headers.items())
logging.debug(resp.headers.items())
# Make sure API returned a proper status code
try:
@@ -133,27 +133,21 @@ class MarketData:
self.charts = {}
def status(self) -> str:
# TODO: At the moment this API is poorly documented, this function likely needs to be revisited later.
"""Check MarketData.app API status by making a test request."""
try:
# Test the API with a simple request
status = r.get(
"https://stats.uptimerobot.com/api/getMonitorList/6Kv3zIow0A",
"https://api.marketdata.app/v1/stocks/quotes/AAPL/",
timeout=5,
)
status.raise_for_status()
return f"MarketData.app API is responding OK with status {status.status_code} in {status.elapsed.total_seconds():.2f} seconds."
except r.HTTPError:
return f"API returned an HTTP error code {status.status_code} in {status.elapsed.total_seconds()} seconds."
return f"MarketData.app API returned an HTTP error code {status.status_code} in {status.elapsed.total_seconds():.2f} seconds."
except r.Timeout:
return "API timed out before it was able to give status. This is likely due to a surge in usage or a complete outage."
statusJSON = status.json()
if statusJSON["status"] == "ok":
return (
f"CoinGecko API responded that it was OK with a {status.status_code} in {status.elapsed.total_seconds()} seconds."
)
else:
return f"MarketData.app is currently reporting the following status: {statusJSON['status']}"
return "MarketData.app API timed out before it could respond. This is likely due to a surge in usage or a complete outage."
except Exception as e:
return f"MarketData.app API check failed: {str(e)}"
def price_reply(self, symbol: Stock) -> str:
"""Returns price movement of Stock for the last market day, or after hours.
+22 -13
View File
@@ -31,15 +31,24 @@ class cg_Crypto:
# This results in a rate limit of 15 requests per minute for each bot.
# Given this, the rate limit effectively becomes 1 request every 4 seconds for each bot.
@rate_limited(0.25)
def get(self, endpoint, params: dict = {}, timeout=10) -> dict:
def get(self, endpoint, params: dict = {}, timeout=10, retry_count=0, max_retries=3) -> 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
if resp.status_code == 429:
log.warning(f"CoinGecko returned 429 - Too Many Requests for endpoint: {endpoint}. Sleeping and trying again.")
time.sleep(10)
return self.get(endpoint=endpoint, params=params, timeout=timeout)
if retry_count >= max_retries:
log.error(f"CoinGecko 429 retry limit ({max_retries}) exceeded for endpoint: {endpoint}")
return {}
backoff_time = (2**retry_count) * 10 # Exponential backoff: 10s, 20s, 40s
log.warning(
f"CoinGecko returned 429 - Too Many Requests for endpoint: {endpoint}. Retry {retry_count + 1}/{max_retries} after {backoff_time}s."
)
time.sleep(backoff_time)
return self.get(
endpoint=endpoint, params=params, timeout=timeout, retry_count=retry_count + 1, max_retries=max_retries
)
try:
resp.raise_for_status()
@@ -214,14 +223,14 @@ class cg_Crypto:
},
):
return f"""
[{data['name']}]({data['links']['homepage'][0]}) Statistics:
Market Cap: ${data['market_data']['market_cap'][self.vs_currency]:,}
Market Cap Ranking: {data.get('market_cap_rank',"Not Available")}
[{data["name"]}]({data["links"]["homepage"][0]}) Statistics:
Market Cap: ${data["market_data"]["market_cap"][self.vs_currency]:,}
Market Cap Ranking: {data.get("market_cap_rank", "Not Available")}
CoinGecko Scores:
Overall: {data.get('coingecko_score','Not Available')}
Development: {data.get('developer_score','Not Available')}
Community: {data.get('community_score','Not Available')}
Public Interest: {data.get('public_interest_score','Not Available')}
Overall: {data.get("coingecko_score", "Not Available")}
Development: {data.get("developer_score", "Not Available")}
Community: {data.get("community_score", "Not Available")}
Public Interest: {data.get("public_interest_score", "Not Available")}
"""
else:
return f"{symbol.symbol} returned an error."
@@ -261,7 +270,7 @@ class cg_Crypto:
message = (
f"The current price of {coin.name} is $**{price:,}** and"
+ " its market cap is $**{cap:,.2f}** {self.vs_currency.upper()}"
+ f" its market cap is $**{cap:,.2f}** {self.vs_currency.upper()}"
)
else:
@@ -374,7 +383,7 @@ class cg_Crypto:
p["usd_24h_change"] = 0
replies.append(
f"{coin.name}: ${p.get('usd',0):,} and has moved {p.get('usd_24h_change',0.0):.2f}% in the past 24 hours."
f"{coin.name}: ${p.get('usd', 0):,} and has moved {p.get('usd_24h_change', 0.0):.2f}% in the past 24 hours."
)
return replies