feat: raise market_api rate to 20/min, add global Polygon cap at 45/min, add rate-limit API + watchlist warning

This commit is contained in:
Celes Renata
2026-04-16 07:26:10 +00:00
parent 0b3ab4ed90
commit 0ee7f26633
5 changed files with 148 additions and 9 deletions
+29 -3
View File
@@ -45,7 +45,7 @@ def _ensure_dict(val: Any) -> Optional[dict]:
# Default polling cadences by source class (seconds).
# Individual sources can override via config.polling_interval_seconds.
DEFAULT_CADENCES: dict[str, int] = {
"market_api": 900,
"market_api": 300,
"news_api": 300,
"filings_api": 3600,
"web_scrape": 1800,
@@ -55,7 +55,7 @@ DEFAULT_CADENCES: dict[str, int] = {
# Default rate limits per source type (requests per minute)
DEFAULT_RATE_LIMITS: dict[str, int] = {
"market_api": 5,
"market_api": 20,
"news_api": 20,
"filings_api": 10,
"web_scrape": 10,
@@ -63,6 +63,12 @@ DEFAULT_RATE_LIMITS: dict[str, int] = {
"macro_news": 10,
}
# Global rate limit across all Polygon-backed source types (requests per minute).
# market_api + news_api share a single Polygon API key, so we cap the combined
# throughput to stay safely under the plan limit.
POLYGON_SOURCE_TYPES: set[str] = {"market_api", "news_api"}
POLYGON_GLOBAL_RATE_LIMIT: int = 45
# How long to wait before retrying a failed source (seconds)
DEFAULT_BACKOFF_BASE: int = 60
MAX_BACKOFF: int = 3600
@@ -173,15 +179,35 @@ async def check_rate_limit(
) -> bool:
"""Check whether the source type is within its rate limit window.
Enforces two limits:
1. Per-source-type limit (e.g. market_api: 20/min)
2. Global Polygon limit across all Polygon-backed types (45/min combined)
Returns True if the request is allowed, False if rate-limited.
"""
limit = max_per_minute or DEFAULT_RATE_LIMITS.get(source_type, 30)
window = now.strftime("%Y%m%d%H%M")
# Per-source-type check
key = rate_limit_key(source_type, window)
count = await rds.incr(key)
if count == 1:
await rds.expire(key, 120)
return count <= limit
if count > limit:
return False
# Global Polygon check for source types that share the Polygon API key
if source_type in POLYGON_SOURCE_TYPES:
global_key = rate_limit_key("_polygon_global", window)
global_count = await rds.incr(global_key)
if global_count == 1:
await rds.expire(global_key, 120)
if global_count > POLYGON_GLOBAL_RATE_LIMIT:
# Roll back the per-type counter since we won't actually make the call
await rds.decr(key)
return False
return True
async def fetch_active_sources(pool: asyncpg.Pool) -> list[asyncpg.Record]: