Skip to content

Commit 310efa7

Browse files
committed
Refactor CoinGecko API interactions to use synchronous requests
- Replaced asynchronous `aiohttp` calls with synchronous `requests` library for API interactions, simplifying the code structure and improving readability. - Adjusted rate limiting logic to accommodate the new synchronous approach, reducing the wait time between API calls from 60 seconds to 15 seconds. - Updated helper functions to track API call counts and log relevant information for better monitoring. - Removed unnecessary asynchronous constructs, streamlining the overall functionality of the CoinGecko API module. - Introduced a new file `drift_data_api.py` placeholder to facilitate future data fetching from the Drift data API, laying groundwork for further integration.
1 parent 586a1ef commit 310efa7

File tree

2 files changed

+84
-84
lines changed

2 files changed

+84
-84
lines changed

backend/utils/coingecko_api.py

Lines changed: 83 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,8 @@
66
import os
77
import json
88
import time
9-
import asyncio
10-
import aiohttp
9+
import requests
1110
import logging
12-
import math
1311
from typing import Dict, List, Optional, Union, Tuple
1412
from datetime import datetime
1513

@@ -24,15 +22,21 @@
2422
COINGECKO_API_BASE = "https://api.coingecko.com/api/v3"
2523
COINGECKO_DEMO_API_KEY = "CG-oWyNSQuvyZMKCDzL3yqGzyrh" # Demo key
2624
COINGECKO_REQ_PER_MINUTE = 30 # Conservative rate limit
27-
COINGECKO_RATE_LIMIT_DELAY = 60.0 / COINGECKO_REQ_PER_MINUTE
25+
COINGECKO_RATE_LIMIT_DELAY = 2 # Reduced from 60s to 15s
2826
MAX_COINS_PER_PAGE = 250 # Maximum allowed by CoinGecko API
2927

3028
# --- Global Rate Limiter Variables ---
31-
cg_rate_limit_lock = asyncio.Lock()
3229
cg_last_request_time = 0.0
30+
cg_api_calls = 0 # Counter for API calls
3331

3432
# --- Helper Functions ---
35-
async def enforce_rate_limit(last_request_time: float, rate_limit: float) -> None:
33+
def increment_api_calls():
34+
"""Increment the API call counter and log the current count."""
35+
global cg_api_calls
36+
cg_api_calls += 1
37+
logger.info(f"CoinGecko API calls made: {cg_api_calls}")
38+
39+
def enforce_rate_limit(last_request_time: float, rate_limit: float) -> None:
3640
"""
3741
Enforce rate limiting by waiting if necessary.
3842
@@ -44,9 +48,11 @@ async def enforce_rate_limit(last_request_time: float, rate_limit: float) -> Non
4448
time_since_last_request = current_time - last_request_time
4549

4650
if time_since_last_request < rate_limit:
47-
await asyncio.sleep(rate_limit - time_since_last_request)
51+
wait_time = rate_limit - time_since_last_request
52+
logger.info(f"Rate limiting: Waiting {wait_time:.2f}s")
53+
time.sleep(wait_time)
4854

49-
async def make_api_request(
55+
def make_api_request(
5056
url: str,
5157
method: str = "GET",
5258
headers: Optional[Dict] = None,
@@ -68,41 +74,38 @@ async def make_api_request(
6874
Returns:
6975
Dict containing the response data or error information
7076
"""
71-
await enforce_rate_limit(last_request_time, rate_limit)
77+
enforce_rate_limit(last_request_time, rate_limit)
78+
increment_api_calls() # Track API call
7279

7380
try:
74-
async with aiohttp.ClientSession() as session:
75-
async with session.request(
76-
method=method,
77-
url=url,
78-
headers=headers,
79-
params=params,
80-
timeout=aiohttp.ClientTimeout(total=30)
81-
) as response:
82-
if response.status == 429: # Rate limit exceeded
83-
retry_after = int(response.headers.get("Retry-After", "60"))
84-
logger.warning(f"Rate limit exceeded. Waiting {retry_after} seconds.")
85-
await asyncio.sleep(retry_after)
86-
return await make_api_request(url, method, headers, params, rate_limit)
87-
88-
response.raise_for_status()
89-
return await response.json()
90-
except aiohttp.ClientError as e:
81+
response = requests.request(
82+
method=method,
83+
url=url,
84+
headers=headers,
85+
params=params,
86+
timeout=30
87+
)
88+
89+
if response.status_code == 429: # Rate limit exceeded
90+
retry_after = int(response.headers.get("Retry-After", "10"))
91+
logger.warning(f"Rate limit exceeded. Waiting {retry_after} seconds.")
92+
time.sleep(retry_after)
93+
return make_api_request(url, method, headers, params, rate_limit)
94+
95+
response.raise_for_status()
96+
return response.json()
97+
except requests.RequestException as e:
9198
logger.error(f"API request failed: {str(e)}")
9299
return {"error": str(e)}
93-
except asyncio.TimeoutError:
94-
logger.error(f"API request timed out: {url}")
95-
return {"error": "Request timed out"}
96100
except Exception as e:
97101
logger.error(f"Unexpected error during API request: {str(e)}")
98102
return {"error": str(e)}
99103

100-
async def fetch_coingecko(session: aiohttp.ClientSession, endpoint: str, params: Dict = None) -> Optional[Union[List, Dict]]:
104+
def fetch_coingecko(endpoint: str, params: Dict = None) -> Optional[Union[List, Dict]]:
101105
"""
102106
Generic CoinGecko fetcher that exactly matches the successful example.
103107
104108
Args:
105-
session: aiohttp client session
106109
endpoint: API endpoint to fetch
107110
params: Optional query parameters
108111
@@ -127,57 +130,56 @@ async def fetch_coingecko(session: aiohttp.ClientSession, endpoint: str, params:
127130

128131
for retry in range(max_retries):
129132
# Apply rate limiting
130-
async with cg_rate_limit_lock:
131-
wait_time = COINGECKO_RATE_LIMIT_DELAY - (time.time() - cg_last_request_time)
132-
if wait_time > 0:
133-
logger.debug(f"CG Rate limiting. Wait: {wait_time:.2f}s")
134-
await asyncio.sleep(wait_time)
135-
cg_last_request_time = time.time()
133+
wait_time = COINGECKO_RATE_LIMIT_DELAY - (time.time() - cg_last_request_time)
134+
if wait_time > 0:
135+
logger.debug(f"CG Rate limiting. Wait: {wait_time:.2f}s")
136+
time.sleep(wait_time)
137+
cg_last_request_time = time.time()
136138

137139
try:
138140
logger.info(f"Fetching CG URL: {url} with params: {params}")
139141
logger.info(f"Headers: {headers}")
140142

141143
# Make the request exactly as in the example
142-
async with session.get(url, headers=headers, params=params, timeout=20) as response:
143-
response_text = await response.text()
144-
145-
if response.status == 200:
146-
try:
147-
data = json.loads(response_text)
148-
logger.info(f"Successfully received data from CoinGecko API")
149-
return data
150-
except json.JSONDecodeError:
151-
logger.error(f"Failed to parse JSON response: {response_text[:100]}...")
152-
return None
153-
else:
154-
logger.error(f"CG request failed {url}: Status {response.status}, Response: {response_text[:100]}...")
155-
156-
if response.status == 429:
157-
# Rate limit hit
158-
logger.warning(f"CG rate limit hit for {url}. Retrying after delay.")
159-
retry_delay *= 2 # Exponential backoff
160-
await asyncio.sleep(retry_delay)
161-
continue
162-
elif retry < max_retries - 1:
163-
retry_delay *= 1.5
164-
await asyncio.sleep(retry_delay)
165-
continue
166-
144+
response = requests.get(url, headers=headers, params=params, timeout=20)
145+
response_text = response.text
146+
147+
if response.status_code == 200:
148+
try:
149+
data = json.loads(response_text)
150+
logger.info(f"Successfully received data from CoinGecko API")
151+
return data
152+
except json.JSONDecodeError:
153+
logger.error(f"Failed to parse JSON response: {response_text[:100]}...")
167154
return None
155+
else:
156+
logger.error(f"CG request failed {url}: Status {response.status_code}, Response: {response_text[:100]}...")
157+
158+
if response.status_code == 429:
159+
# Rate limit hit
160+
logger.warning(f"CG rate limit hit for {url}. Retrying after delay.")
161+
retry_delay *= 2 # Exponential backoff
162+
time.sleep(retry_delay)
163+
continue
164+
elif retry < max_retries - 1:
165+
retry_delay *= 1.5
166+
time.sleep(retry_delay)
167+
continue
168+
169+
return None
168170

169-
except asyncio.TimeoutError:
171+
except requests.Timeout:
170172
logger.warning(f"CG request timeout for {url}")
171173
if retry < max_retries - 1:
172174
retry_delay *= 1.5
173-
await asyncio.sleep(retry_delay)
175+
time.sleep(retry_delay)
174176
continue
175177
return None
176178
except Exception as e:
177179
logger.error(f"Error fetching CG {url}: {e}")
178180
if retry < max_retries - 1:
179181
retry_delay *= 1.5
180-
await asyncio.sleep(retry_delay)
182+
time.sleep(retry_delay)
181183
continue
182184
return None
183185

@@ -213,12 +215,11 @@ def calculate_pagination(total_tokens: int) -> List[Tuple[int, int]]:
213215

214216
return pagination
215217

216-
async def fetch_all_coingecko_market_data(session: aiohttp.ClientSession, number_of_tokens: int = 250) -> dict:
218+
def fetch_all_coingecko_market_data(number_of_tokens: int = 250) -> dict:
217219
"""
218220
Fetches data from CG /coins/markets using the exact parameters that worked.
219221
220222
Args:
221-
session: aiohttp client session
222223
number_of_tokens: Number of top tokens by market cap to fetch (default: 250)
223224
224225
Returns:
@@ -251,7 +252,7 @@ async def fetch_all_coingecko_market_data(session: aiohttp.ClientSession, number
251252

252253
# Make API request
253254
endpoint = '/coins/markets'
254-
data = await fetch_coingecko(session, endpoint, params)
255+
data = fetch_coingecko(endpoint, params)
255256

256257
if not data or not isinstance(data, list):
257258
logger.warning(f"No valid data received for page {page_num}. Skipping.")
@@ -303,7 +304,7 @@ async def fetch_all_coingecko_market_data(session: aiohttp.ClientSession, number
303304

304305
# Respect rate limiting between page requests
305306
if page_num < total_pages:
306-
await asyncio.sleep(COINGECKO_RATE_LIMIT_DELAY)
307+
time.sleep(COINGECKO_RATE_LIMIT_DELAY)
307308

308309
if total_tokens_received < number_of_tokens:
309310
logger.warning(f"Requested {number_of_tokens} tokens but only received {total_tokens_received}. This appears to be all available tokens.")
@@ -314,12 +315,11 @@ async def fetch_all_coingecko_market_data(session: aiohttp.ClientSession, number
314315
logger.error(f"Error fetching CoinGecko market data: {e}", exc_info=True)
315316
return all_markets_data
316317

317-
async def fetch_coin_volume(session: aiohttp.ClientSession, coin_id: str) -> Optional[float]:
318+
def fetch_coin_volume(coin_id: str) -> Optional[float]:
318319
"""
319320
Helper to fetch 30d volume data for a single coin.
320321
321322
Args:
322-
session: aiohttp client session
323323
coin_id: CoinGecko coin ID
324324
325325
Returns:
@@ -335,7 +335,7 @@ async def fetch_coin_volume(session: aiohttp.ClientSession, coin_id: str) -> Opt
335335
}
336336

337337
endpoint = f"/coins/{coin_id}/market_chart"
338-
data = await fetch_coingecko(session, endpoint, params)
338+
data = fetch_coingecko(endpoint, params)
339339

340340
if not data or 'total_volumes' not in data or not data['total_volumes']:
341341
logger.warning(f"No volume data received for {coin_id}")
@@ -356,14 +356,13 @@ async def fetch_coin_volume(session: aiohttp.ClientSession, coin_id: str) -> Opt
356356

357357
except Exception as e:
358358
logger.error(f"Error fetching volume data for {coin_id}: {e}")
359-
raise # Re-raise to be caught by gather
359+
return None
360360

361-
async def fetch_all_coingecko_volumes(session: aiohttp.ClientSession, coin_ids: list) -> dict:
361+
def fetch_all_coingecko_volumes(coin_ids: list) -> dict:
362362
"""
363363
Fetches 30d volume data from CG /coins/{id}/market_chart.
364364
365365
Args:
366-
session: aiohttp client session
367366
coin_ids: List of CoinGecko coin IDs
368367
369368
Returns:
@@ -375,13 +374,13 @@ async def fetch_all_coingecko_volumes(session: aiohttp.ClientSession, coin_ids:
375374
# Process coins one by one to avoid rate limiting issues
376375
for coin_id in coin_ids:
377376
try:
378-
total_volume = await fetch_coin_volume(session, coin_id)
377+
total_volume = fetch_coin_volume(coin_id)
379378
if total_volume is not None:
380379
volume_data[coin_id] = total_volume
381380
logger.info(f"Processed 30d volume for {coin_id}: ${total_volume:,.2f}")
382381

383382
# Respect rate limiting between requests
384-
await asyncio.sleep(COINGECKO_RATE_LIMIT_DELAY)
383+
time.sleep(COINGECKO_RATE_LIMIT_DELAY)
385384

386385
except Exception as e:
387386
logger.warning(f"Error processing volume for {coin_id}: {str(e)}")
@@ -390,7 +389,7 @@ async def fetch_all_coingecko_volumes(session: aiohttp.ClientSession, coin_ids:
390389
logger.info(f"Successfully fetched 30d volume data for {len(volume_data)}/{len(coin_ids)} coins")
391390
return volume_data
392391

393-
async def fetch_coingecko_data(symbol: str) -> Dict:
392+
def fetch_coingecko_data(symbol: str) -> Dict:
394393
"""
395394
Fetch market data from CoinGecko API for a given symbol.
396395
@@ -404,7 +403,7 @@ async def fetch_coingecko_data(symbol: str) -> Dict:
404403

405404
# First, get the coin ID
406405
search_url = f"{COINGECKO_API_BASE}/search"
407-
search_response = await make_api_request(
406+
search_response = make_api_request(
408407
url=search_url,
409408
params={"query": symbol},
410409
rate_limit=COINGECKO_RATE_LIMIT_DELAY,
@@ -431,7 +430,7 @@ async def fetch_coingecko_data(symbol: str) -> Dict:
431430

432431
# Fetch detailed market data
433432
market_url = f"{COINGECKO_API_BASE}/coins/{coin_id}"
434-
market_response = await make_api_request(
433+
market_response = make_api_request(
435434
url=market_url,
436435
params={
437436
"localization": "false",
@@ -468,23 +467,23 @@ async def fetch_coingecko_data(symbol: str) -> Dict:
468467
"coingecko_ath_change_percentage": market_data.get("ath_change_percentage", {}).get("usd")
469468
}
470469

471-
async def fetch_coingecko_historical_volume(coin_id: str, days: int = 30) -> float:
470+
def fetch_coingecko_historical_volume(coin_id: str, number_of_days: int = 30) -> float:
472471
"""
473472
Fetch historical volume data from CoinGecko API.
474473
475474
Args:
476475
coin_id: The CoinGecko coin ID
477-
days: Number of days of historical data to fetch
476+
number_of_days: Number of days of historical data to fetch (default: 30)
478477
479478
Returns:
480479
Total volume over the specified period or 0 if error
481480
"""
482481
global cg_last_request_time
483482

484483
url = f"{COINGECKO_API_BASE}/coins/{coin_id}/market_chart"
485-
response = await make_api_request(
484+
response = make_api_request(
486485
url=url,
487-
params={"vs_currency": "usd", "days": str(days), "interval": "daily"},
486+
params={"vs_currency": "usd", "days": str(number_of_days), "interval": "daily"},
488487
rate_limit=COINGECKO_RATE_LIMIT_DELAY,
489488
last_request_time=cg_last_request_time
490489
)

backend/utils/drift_data_api.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# The purpose of this file is to fetch data from the drift data api

0 commit comments

Comments
 (0)