The trading bot implements a robust multi-provider price fetching system with comprehensive error handling, rate limiting, and fallback mechanisms.
- Finnhub Primary - Main data source (with backoff protection)
- Yahoo Finance - First fallback
- Polygon - Second fallback
- Alpaca - Third fallback
- Cached Prices - Memory cache (any age)
- Database Prices - Last known prices from database
Issue Fixed: Previously parsed JSON on any 200 status without checking content-type Solution:
- Check
Content-Typeheader before JSON parsing - Catch
aiohttp.ContentTypeErrorandValueError - Log non-JSON response text for debugging
if 'application/json' in content_type:
try:
data = await resp.json()
# Process data
except (aiohttp.ContentTypeError, ValueError) as json_err:
print(f"❌ Provider: JSON parsing error: {json_err}")
text_response = await resp.text()
print(f"❌ Provider: Response text: {text_response[:200]}")
return NoneIssue Fixed: Backoff timing used request start time instead of completion time Solution:
- Use
time.time()at actual completion for backoff calculations - Separate tracking for Finnhub failures vs general provider failures
- More precise rate limiting
# Before (inaccurate)
backoff_until = current_time + 60 # Used start time
# After (accurate)
backoff_completion_time = time.time() # Use completion time
backoff_until = backoff_completion_time + 60- Specific handling for 429 status codes
- Raises
ClientResponseErrorto trigger backoff logic - Distinguishes between rate limits and other HTTP errors
- Respects backoff periods to avoid additional API calls
- Proper JSON parsing with error handling
- Enhanced caching with fallback to symbol if name unavailable
- TTL: 24 hours (configurable via
PRICE_CACHE_TTL) - Structure:
{symbol: (price, timestamp)} - Persistence: Automatically saved to database
- Fallback: Uses any cached price if all providers fail
- TTL: 24 hours (configurable via
COMPANY_CACHE_TTL) - Structure:
{symbol: (name, timestamp)} - Fallback: Returns symbol if name unavailable
# Cache settings
PRICE_CACHE_TTL=86400 # Price cache TTL (24 hours)
COMPANY_CACHE_TTL=86400 # Company name cache TTL (24 hours)
MIN_REQUEST_INTERVAL=2 # Minimum seconds between API requests
# API Keys
FINNHUB_API_KEY=your_key # Primary Finnhub key
FINNHUB_API_KEY_SECOND=key2 # Secondary Finnhub key (unused currently)
Polygon_API_KEY=your_key # Polygon.io key
ALPACA_API_KEY=your_key # Alpaca API key
ALPACA_SECRET_KEY=your_secret # Alpaca secret key
ALPACA_ENDPOINT=endpoint_url # Alpaca endpoint (paper/live)- Endpoint:
https://finnhub.io/api/v1/quote?symbol={symbol}&token={api_key} - Rate Limits: Handles 429 responses with backoff
- Data Field:
c(current price)
- Endpoint:
https://query1.finance.yahoo.com/v7/finance/quote?symbols={symbol} - Rate Limits: No authentication required
- Data Field:
quoteResponse.result[0].regularMarketPrice
- Endpoint:
https://api.polygon.io/v2/aggs/ticker/{symbol}/prev?apikey={api_key} - Rate Limits: API key based
- Data Field:
results[0].c(close price)
- Endpoint:
{endpoint}/stocks/{symbol}/quotes/latest - Authentication: API key + secret in headers
- Data Field: Mid-price calculated from bid/ask (
(bp + ap) / 2)
Request Price for AAPL
├── Check Cache (< 24h) → Return if found
├── Check Rate Limiting → Skip to fallbacks if limited
├── Try Finnhub → 429 Rate Limited
│ ├── Set backoff_until = now + 60s
│ └── Continue to next provider
├── Try Yahoo Finance → Success!
│ ├── Cache price with timestamp
│ ├── Save to database
│ └── Return price
└── If all fail:
├── Try stale cache (any age)
├── Try database lookup
└── Return None if nothing available
CREATE TABLE last_price (
symbol TEXT PRIMARY KEY,
price REAL,
last_updated TEXT -- SQLite CURRENT_TIMESTAMP
);All price fetching attempts are logged with emojis for easy monitoring:
- ✅ Success
- ❌ Error/Failure
⚠️ Warning (rate limits, non-JSON responses)- 🔄 Attempt/Retry
- 📦 Cache usage
- 🗄️ Database fallback
- ⏰ Backoff period
- Request Throttling: Minimum 2-second interval between requests
- Connection Reuse: Uses
aiohttp.ClientSessionfor efficient connection pooling - Timeout Protection: 10-second timeout on all HTTP requests
- Memory Efficiency: Bounded cache with TTL cleanup
- Database Optimization: Batch cache persistence with transaction commits
- Exponential Backoff: Currently uses fixed 60-second backoff
- Circuit Breaker: Could implement circuit breaker pattern for failed providers
- Metrics Collection: Add performance metrics and success rates per provider
- Dynamic Provider Selection: Prioritize providers based on recent success rates