104 lines
3.8 KiB
Python
104 lines
3.8 KiB
Python
import logging
|
|
import requests
|
|
from datetime import datetime, timedelta
|
|
import time
|
|
import json
|
|
|
|
class USMarketsClient:
|
|
def __init__(self, api_key, rate_limit_per_minute=300):
|
|
self.api_key = api_key
|
|
self.base_url = "https://financialmodelingprep.com/api"
|
|
self.rate_limit = rate_limit_per_minute
|
|
self.last_request_time = 0
|
|
self.session = requests.Session()
|
|
|
|
def _make_request(self, endpoint):
|
|
"""Make API request with rate limiting and retry logic"""
|
|
current_time = time.time()
|
|
time_passed = current_time - self.last_request_time
|
|
if time_passed < (60 / self.rate_limit):
|
|
time.sleep((60 / self.rate_limit) - time_passed)
|
|
|
|
max_retries = 3
|
|
retry_delay = 5 # seconds
|
|
|
|
for attempt in range(max_retries):
|
|
try:
|
|
# Ensure we're not appending apikey to an existing query string
|
|
separator = '&' if '?' in endpoint else '?'
|
|
url = f"{self.base_url}{endpoint}{separator}apikey={self.api_key}"
|
|
|
|
response = self.session.get(url, timeout=30)
|
|
self.last_request_time = time.time()
|
|
|
|
if response.status_code == 200:
|
|
data = response.json()
|
|
if self._validate_response(data):
|
|
return data
|
|
else:
|
|
logging.error(f"Invalid data received for endpoint {endpoint}")
|
|
return None
|
|
elif response.status_code == 429: # Rate limit exceeded
|
|
retry_after = int(response.headers.get('Retry-After', retry_delay))
|
|
time.sleep(retry_after)
|
|
continue
|
|
else:
|
|
logging.error(f"API request failed with status code {response.status_code}")
|
|
return None
|
|
|
|
except requests.exceptions.RequestException as e:
|
|
if attempt < max_retries - 1:
|
|
time.sleep(retry_delay)
|
|
continue
|
|
logging.error(f"API request error: {e}")
|
|
return None
|
|
|
|
return None
|
|
|
|
def _validate_response(self, data):
|
|
"""Validate API response data"""
|
|
if not data:
|
|
return False
|
|
if isinstance(data, list) and len(data) == 0:
|
|
return False
|
|
if isinstance(data, dict) and 'error' in data:
|
|
return False
|
|
return True
|
|
|
|
def get_market_indices(self):
|
|
"""Get market indices data"""
|
|
return self._make_request("/v3/quotes/index")
|
|
|
|
def get_sector_performance(self):
|
|
"""Get sector performance data"""
|
|
return self._make_request("/v3/sectors-performance")
|
|
|
|
def get_sector_historical(self, from_date, to_date):
|
|
"""Get historical sector performance data"""
|
|
endpoint = f"/v3/historical-sectors-performance?from={from_date}&to={to_date}"
|
|
return self._make_request(endpoint)
|
|
|
|
def get_sector_pe_ratios(self, date, exchange='NYSE'):
|
|
"""Get sector PE ratios"""
|
|
endpoint = f"/v4/sector_price_earning_ratio?date={date}&exchange={exchange}"
|
|
return self._make_request(endpoint)
|
|
|
|
def get_industry_pe_ratios(self, date, exchange='NYSE'):
|
|
"""Get industry PE ratios"""
|
|
endpoint = f"/v4/industry_price_earning_ratio?date={date}&exchange={exchange}"
|
|
return self._make_request(endpoint)
|
|
|
|
def get_market_movers(self, mover_type='gainers'):
|
|
"""Get market movers data"""
|
|
endpoint = f"/v3/stock_market/{mover_type}"
|
|
return self._make_request(endpoint)
|
|
|
|
def get_market_actives(self):
|
|
"""Get most active stocks"""
|
|
return self._make_request("/v3/stock_market/actives")
|
|
|
|
def __del__(self):
|
|
"""Cleanup session on deletion"""
|
|
if hasattr(self, 'session'):
|
|
self.session.close()
|