Skip to content

Commit 40610f4

Browse files
committed
2 parents 9907385 + 3754b12 commit 40610f4

File tree

1 file changed

+165
-0
lines changed

1 file changed

+165
-0
lines changed
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
"""
2+
CoinGecko Data Fetcher
3+
Fetches cryptocurrency price and market data from the CoinGecko API.
4+
Returns JSON output for Rust integration.
5+
"""
6+
7+
import sys
8+
import json
9+
import os
10+
import requests
11+
from typing import Dict, Any, List
12+
13+
# 1. CONFIGURATION
14+
API_KEY = os.environ.get('COINGECKO_API_KEY')
15+
if API_KEY:
16+
BASE_URL = "https://pro-api.coingecko.com/api/v3"
17+
else:
18+
BASE_URL = "https://api.coingecko.com/api/v3"
19+
20+
def _make_request(endpoint: str, params: Dict[str, Any] = None) -> Any:
21+
"""A private helper function to handle all API requests and errors."""
22+
full_url = f"{BASE_URL}/{endpoint}"
23+
24+
if API_KEY:
25+
if params is None:
26+
params = {}
27+
params['x_cg_pro_api_key'] = API_KEY
28+
29+
try:
30+
response = requests.get(full_url, params=params, timeout=15)
31+
response.raise_for_status()
32+
return response.json()
33+
except requests.exceptions.HTTPError as e:
34+
return {"error": f"HTTP Error: {e.response.status_code} - {e.response.text}"}
35+
except requests.exceptions.RequestException as e:
36+
return {"error": f"Network or request error: {str(e)}"}
37+
except json.JSONDecodeError:
38+
return {"error": "Failed to decode API response."}
39+
40+
# 2. CORE FUNCTIONS
41+
42+
def get_ping() -> Dict[str, Any]:
43+
"""1. Checks if the CoinGecko API is responsive."""
44+
return _make_request("ping")
45+
46+
def get_prices(coin_ids: List[str]) -> Dict[str, Any]:
47+
"""2. Fetches the current price for one or more cryptocurrencies."""
48+
if not coin_ids:
49+
return {"error": "No coin IDs provided."}
50+
params = {'ids': ",".join(coin_ids), 'vs_currencies': 'usd'}
51+
data = _make_request("simple/price", params=params)
52+
53+
if data and "error" not in data:
54+
results = [{"id": coin_id, "price_usd": prices.get('usd', 0)} for coin_id, prices in data.items()]
55+
return {"data": results}
56+
return data
57+
58+
def get_market_info(coin_id: str) -> Dict[str, Any]:
59+
"""3. Fetches detailed market information for a single cryptocurrency."""
60+
params = {'vs_currency': 'usd', 'ids': coin_id}
61+
data = _make_request("coins/markets", params=params)
62+
63+
if data and "error" not in data and isinstance(data, list) and len(data) > 0:
64+
return data[0] # The raw API response is already well-formatted
65+
elif isinstance(data, list) and len(data) == 0:
66+
return {"error": f"No market data found for ID: {coin_id}"}
67+
return data
68+
69+
def get_historical_data(coin_id: str, date: str) -> Dict[str, Any]:
70+
"""4. Fetches historical data for a coin on a specific date."""
71+
params = {'date': date} # API expects DD-MM-YYYY format
72+
data = _make_request(f"coins/{coin_id}/history", params=params)
73+
74+
if data and "error" not in data:
75+
# Standardize the response
76+
return {
77+
"id": data.get('id'),
78+
"symbol": data.get('symbol'),
79+
"date": date,
80+
"price_usd": data.get('market_data', {}).get('current_price', {}).get('usd'),
81+
"market_cap_usd": data.get('market_data', {}).get('market_cap', {}).get('usd'),
82+
"volume_usd": data.get('market_data', {}).get('total_volume', {}).get('usd')
83+
}
84+
return data
85+
86+
def get_market_chart(coin_id: str, days: int) -> Dict[str, Any]:
87+
"""5. Fetches market chart data (prices, market caps, volumes) for a coin."""
88+
params = {'vs_currency': 'usd', 'days': days}
89+
data = _make_request(f"coins/{coin_id}/market_chart", params=params)
90+
return data # The raw response is already well-structured for charting
91+
92+
def get_trending_coins() -> Dict[str, Any]:
93+
"""6. Fetches the top-7 trending coins on CoinGecko."""
94+
data = _make_request("search/trending")
95+
if data and "error" not in data:
96+
# The API returns a list of items inside a 'coins' key
97+
return {"trending": data.get("coins", [])}
98+
return data
99+
100+
def search_coins(query: str) -> Dict[str, Any]:
101+
"""7. Searches for coins by name or symbol."""
102+
params = {'query': query}
103+
data = _make_request("search", params=params)
104+
if data and "error" not in data:
105+
return {"results": data.get("coins", [])}
106+
return data
107+
108+
def get_global_market_data() -> Dict[str, Any]:
109+
"""8. Fetches global crypto market data (e.g., BTC dominance)."""
110+
response = _make_request("global")
111+
if response and "error" not in response:
112+
return response.get("data", {}) # Data is nested under a 'data' key
113+
return response
114+
115+
# 3. CLI INTERFACE
116+
117+
def main():
118+
"""Main Command-Line Interface entry point."""
119+
if len(sys.argv) < 2:
120+
print(json.dumps({
121+
"error": "Usage: python coingecko_data.py <command> [args]",
122+
"commands": [
123+
"ping",
124+
"price <id1> [id2...]",
125+
"info <id>",
126+
"history <id> <dd-mm-yyyy>",
127+
"chart <id> <days>",
128+
"trending",
129+
"search <query>",
130+
"global"
131+
]
132+
}, indent=2))
133+
sys.exit(1)
134+
135+
command = sys.argv[1].lower()
136+
result = {}
137+
138+
try:
139+
if command == "ping":
140+
result = get_ping()
141+
elif command == "price":
142+
result = get_prices(sys.argv[2:])
143+
elif command == "info":
144+
result = get_market_info(sys.argv[2])
145+
elif command == "history":
146+
result = get_historical_data(sys.argv[2], sys.argv[3])
147+
elif command == "chart":
148+
result = get_market_chart(sys.argv[2], int(sys.argv[3]))
149+
elif command == "trending":
150+
result = get_trending_coins()
151+
elif command == "search":
152+
result = search_coins(" ".join(sys.argv[2:]))
153+
elif command == "global":
154+
result = get_global_market_data()
155+
else:
156+
result = {"error": f"Unknown command: {command}"}
157+
except IndexError:
158+
result = {"error": "Missing arguments for command."}
159+
except Exception as e:
160+
result = {"error": f"An unexpected error occurred: {e}"}
161+
162+
print(json.dumps(result, indent=2))
163+
164+
if __name__ == "__main__":
165+
main()

0 commit comments

Comments
 (0)