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