This document details all supported market data providers, their configuration, rate limits, and usage patterns.
Trading Buddy integrates with 10+ market data providers, organized by data domain:
| Domain | Primary | Secondary | Tertiary | Fallback |
|---|---|---|---|---|
| Real-Time Prices | Polygon WS | Tiingo IEX | Finnhub | Yahoo Finance |
| Fundamentals | YCharts | FMP | Alpha Vantage | Yahoo Finance |
| News | Benzinga | Tiingo News | NewsAPI | - |
| Sentiment | Tiingo NLP | StockTwits | - | - |
| Corporate Actions | Polygon | FMP | - | - |
| Macro/Yields | YCharts | FRED | - | - |
Purpose: Order execution (paper and live trading)
Free Tier: Yes (paper trading)
Sign Up: alpaca.markets
Environment Variables:
APCA_API_KEY_ID=your_key_id
APCA_API_SECRET_KEY=your_secret_key
APCA_API_BASE_URL=https://paper-api.alpaca.markets # Paper trading
# APCA_API_BASE_URL=https://api.alpaca.markets # Live tradingCapabilities:
- Paper trading (unlimited)
- Live trading (commission-free)
- Market data (limited free tier)
- Account management
- Order management
- Position tracking
Purpose: Real-time prices, market status, corporate actions
Free Tier: Limited (delayed data, market status)
Sign Up: polygon.io
Environment Variables:
POLYGON_API_KEY=your_polygon_keyRate Limits:
| Tier | Requests/Min | WebSocket |
|---|---|---|
| Free | 5 | No |
| Starter | 300 | Yes |
| Developer | 1000 | Yes |
Endpoints Used:
GET /v1/marketstatus/now # Market status
GET /v2/aggs/ticker/{symbol} # Price bars
GET /v3/reference/tickers # Ticker details
WS wss://socket.polygon.io # Real-time streaming
Usage Example:
# In provider_router.py
async def _fetch_polygon_price(self, symbol: str):
url = f"https://api.polygon.io/v2/last/trade/{symbol}"
params = {"apiKey": os.environ.get('POLYGON_API_KEY')}
async with session.get(url, params=params) as response:
return await response.json()Purpose: Real-time quotes, company profiles, basic fundamentals
Free Tier: Yes (60 calls/minute)
Sign Up: finnhub.io
Environment Variables:
FINNHUB_API_KEY=your_finnhub_keyRate Limits:
| Tier | Calls/Min |
|---|---|
| Free | 60 |
| Paid | 300-600 |
Endpoints Used:
GET /api/v1/quote?symbol={symbol} # Real-time quote
GET /api/v1/stock/profile2?symbol={symbol} # Company profile
GET /api/v1/company-news?symbol={symbol} # Company news
GET /api/v1/stock/metric?symbol={symbol} # Basic fundamentals
Purpose: Historical prices, technical indicators, fundamentals
Free Tier: Yes (25 calls/day)
Sign Up: alphavantage.co
Environment Variables:
ALPHA_VANTAGE_API_KEY=your_av_keyRate Limits:
| Tier | Calls/Min | Calls/Day |
|---|---|---|
| Free | 5 | 25 |
| Premium | 75 | Unlimited |
Endpoints Used:
GET /query?function=GLOBAL_QUOTE&symbol={symbol}
GET /query?function=TIME_SERIES_DAILY&symbol={symbol}
GET /query?function=OVERVIEW&symbol={symbol}
Purpose: IEX real-time data, news, fundamentals
Free Tier: Yes (limited)
Sign Up: tiingo.com
Environment Variables:
TIINGO_API_TOKEN=your_tiingo_tokenCapabilities:
- IEX real-time prices
- Historical end-of-day
- News with sentiment
- Fundamentals
Endpoints Used:
GET /iex/?tickers={symbol} # IEX prices
GET /tiingo/news?tickers={symbol} # News
WS wss://api.tiingo.com/iex # WebSocket
Purpose: News aggregation, headlines
Free Tier: Yes (100 calls/day)
Sign Up: newsapi.org
Environment Variables:
NEWSAPI_KEY=your_newsapi_keyRate Limits:
| Tier | Calls/Day |
|---|---|
| Developer (Free) | 100 |
| Business | 1000 |
Purpose: Advanced financial analytics, ratios, industry comparisons
Free Tier: No (professional subscription required)
Contact: ycharts.com
Environment Variables:
YCHARTS_API_KEY=your_ycharts_keyCapabilities:
- Cleaned fundamental data
- Financial ratios
- Industry comparisons
- Macro indicators
- Historical timeseries
Single API key for multiple services:
Environment Variables:
RAPIDAPI_KEY=your_rapidapi_keyPurpose: Price quotes, technical indicators
Host: twelve-data1.p.rapidapi.com
GET /price?symbol={symbol}
GET /time_series?symbol={symbol}
Purpose: Fundamentals, financial statements
Host: financialmodelingprep.p.rapidapi.com
GET /api/v3/quote/{symbol}
GET /api/v3/profile/{symbol}
Purpose: Social sentiment, retail trader activity
Host: stocktwits-sentiment.p.rapidapi.com
GET /sentiment?symbol={symbol}
Purpose: Low-latency news for trading
Free Tier: No
Contact: benzinga.com
Environment Variables:
BENZINGA_API_KEY=your_benzinga_keydata_providers:
prices:
primary: polygon_ws
secondary: tiingo_iex
tertiary: finnhub_rest
fallback: yahoo_finance
validation:
max_price_discrepancy_pct: 0.3
freshness_ms:
quotes: 2000
bars_1m: 60000
fundamentals:
primary: ycharts
secondary: fmp_rapidapi
tertiary: alpha_vantage
fallback: yahoo_finance
validation:
staleness_days: 90
cross_check_key_ratios: true
credentials:
polygon_key: "${POLYGON_API_KEY}"
finnhub_key: "${FINNHUB_API_KEY}"
tiingo_token: "${TIINGO_API_TOKEN}"
alpha_vantage_key: "${ALPHA_VANTAGE_API_KEY}"
ycharts_key: "${YCHARTS_API_KEY}"
rapidapi_key: "${RAPIDAPI_KEY}"
newsapi_key: "${NEWSAPI_KEY}"
benzinga_key: "${BENZINGA_API_KEY}"
rate_limits:
polygon_rest:
rpm: 300
burst: 10
finnhub_rest:
rpm: 60
burst: 5
alpha_vantage:
rpm: 5
daily_limit: 25The Provider Router implements per-provider rate limiting:
class RateLimiter:
"""Leaky bucket rate limiter"""
def __init__(self, rpm: int, burst: int = 5):
self.rpm = rpm # Requests per minute
self.burst = burst # Max burst requests
self.tokens = burst
self.last_update = time.time()
async def acquire(self) -> bool:
"""Returns True if request is allowed"""
# Refill tokens based on time elapsed
now = time.time()
tokens_to_add = (now - self.last_update) * (self.rpm / 60.0)
self.tokens = min(self.burst, self.tokens + tokens_to_add)
self.last_update = now
if self.tokens >= 1:
self.tokens -= 1
return True
return FalseAutomatic failover when providers fail:
class CircuitBreaker:
"""Disable failing providers automatically"""
def __init__(self, failure_threshold: int = 3, reset_timeout: int = 60):
self.failure_threshold = failure_threshold
self.reset_timeout = reset_timeout
self.failures = 0
self.state = "CLOSED" # CLOSED, OPEN, HALF_OPEN
self.last_failure = None
def record_failure(self):
self.failures += 1
self.last_failure = time.time()
if self.failures >= self.failure_threshold:
self.state = "OPEN"
def is_available(self) -> bool:
if self.state == "CLOSED":
return True
if self.state == "OPEN":
# Check if reset timeout has passed
if time.time() - self.last_failure > self.reset_timeout:
self.state = "HALF_OPEN"
return True
return False
return True # HALF_OPEN allows one requestMulti-source validation ensures data accuracy:
class DataValidator:
"""Cross-validate data from multiple sources"""
def validate_price(self, prices: Dict[str, float]) -> ValidationResult:
"""Ensure prices from different sources agree"""
values = list(prices.values())
if len(values) < 2:
return ValidationResult(passed=True, confidence=0.5)
# Calculate deviation
mean = statistics.mean(values)
max_deviation = max(abs(v - mean) / mean for v in values)
if max_deviation > 0.005: # 0.5% threshold
return ValidationResult(
passed=False,
confidence=1 - max_deviation,
discrepancies=[f"Price deviation: {max_deviation:.2%}"]
)
return ValidationResult(passed=True, confidence=1.0)# data_providers.yaml
credentials:
new_provider_key: "${NEW_PROVIDER_API_KEY}"
rate_limits:
new_provider:
rpm: 100
burst: 10# In provider_router.py or separate file
class NewProviderClient:
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "https://api.newprovider.com"
async def get_quote(self, symbol: str) -> Dict:
async with aiohttp.ClientSession() as session:
url = f"{self.base_url}/quote/{symbol}"
headers = {"Authorization": f"Bearer {self.api_key}"}
async with session.get(url, headers=headers) as response:
return await response.json()# In provider_router.py
self.providers["new_provider"] = NewProviderClient(
os.environ.get('NEW_PROVIDER_API_KEY')
)# Required
SECRET_KEY=your_secret_key
APCA_API_KEY_ID=your_alpaca_key
APCA_API_SECRET_KEY=your_alpaca_secret
# Market Data (at least one)
POLYGON_API_KEY=your_polygon_key
FINNHUB_API_KEY=your_finnhub_key
ALPHA_VANTAGE_API_KEY=your_av_key
# Optional but Recommended
TIINGO_API_TOKEN=your_tiingo_token
NEWSAPI_KEY=your_newsapi_key
RAPIDAPI_KEY=your_rapidapi_key
# Professional (Optional)
YCHARTS_API_KEY=your_ycharts_key
BENZINGA_API_KEY=your_benzinga_key- Verify API key is set in environment
- Check key hasn't expired
- Ensure key has correct permissions
- Check rate limit configuration
- Reduce request frequency
- Upgrade to higher tier
- This is normal - different providers have slightly different data
- System will use consensus value
- Check logs for details
- Circuit breaker will activate after 3 failures
- System will failover to secondary provider
- Provider will be retried after 60 seconds