From 9008865d75ed45690d85beea80b8ebf4938f825e Mon Sep 17 00:00:00 2001 From: Dennis Thiessen Date: Wed, 17 Jun 2026 11:23:16 +0200 Subject: [PATCH] =?UTF-8?q?run=20fundamentals=20weekly,=20not=20daily=20?= =?UTF-8?q?=E2=80=94=20it's=20quarterly-ish=20data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pulled the fundamental collector out of the daily pipeline (where it re-fetched near-identical numbers every day and burned free-tier API quota) and made it an independent weekly job. P/E/market-cap drift with price but the score buckets them coarsely; revenue growth and earnings surprise only change at quarterly earnings. Added "weekly" to the frequency map; fundamental_fetch_frequency now defaults to weekly (configurable). Co-Authored-By: Claude Opus 4.8 --- app/config.py | 2 +- app/scheduler.py | 9 +++++++-- app/services/admin_service.py | 1 - tests/unit/test_scheduler.py | 5 ++++- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/config.py b/app/config.py index 005cb0f..12259b0 100644 --- a/app/config.py +++ b/app/config.py @@ -50,7 +50,7 @@ class Settings(BaseSettings): sentiment_fresh_hours: int = 72 sentiment_max_per_run: int = 25 sentiment_top_composite: int = 30 - fundamental_fetch_frequency: str = "daily" + fundamental_fetch_frequency: str = "weekly" # quarterly-ish data; weekly conserves API quota rr_scan_frequency: str = "daily" alerts_frequency: str = "hourly" fundamental_rate_limit_retries: int = 3 diff --git a/app/scheduler.py b/app/scheduler.py index ee13fcd..c3a4418 100644 --- a/app/scheduler.py +++ b/app/scheduler.py @@ -1002,7 +1002,6 @@ async def sync_ticker_universe() -> None: # updates its own runtime status while the pipeline runs. _PIPELINE_STEPS = [ ("data_collector", "collect_ohlcv"), - ("fundamental_collector", "collect_fundamentals"), ("sentiment_collector", "collect_sentiment"), ("rr_scanner", "scan_rr"), ("outcome_evaluator", "evaluate_outcomes"), @@ -1049,6 +1048,7 @@ async def run_daily_pipeline() -> None: _FREQUENCY_MAP: dict[str, dict[str, int]] = { "hourly": {"hours": 1}, "daily": {"hours": 24}, + "weekly": {"weeks": 1}, } @@ -1076,7 +1076,6 @@ def configure_scheduler() -> None: # interval job). They stay manually triggerable from Admin → Jobs. _members = [ (collect_ohlcv, "data_collector", "Data Collector (OHLCV)"), - (collect_fundamentals, "fundamental_collector", "Fundamental Collector"), (collect_sentiment, "sentiment_collector", "Sentiment Collector"), (scan_rr, "rr_scanner", "R:R Scanner"), (evaluate_outcomes, "outcome_evaluator", "Outcome Evaluator"), @@ -1099,6 +1098,12 @@ def configure_scheduler() -> None: sync_ticker_universe, "interval", hours=24, id="ticker_universe_sync", name="Ticker Universe Sync", replace_existing=True, ) + # Fundamentals — quarterly-ish data; weekly by default (conserves API quota) + fund_interval = _parse_frequency(settings.fundamental_fetch_frequency) + scheduler.add_job( + collect_fundamentals, "interval", **fund_interval, + id="fundamental_collector", name="Fundamental Collector", replace_existing=True, + ) alerts_interval = _parse_frequency(settings.alerts_frequency) scheduler.add_job( dispatch_alerts_job, "interval", **alerts_interval, diff --git a/app/services/admin_service.py b/app/services/admin_service.py index 63545d2..b71b71a 100644 --- a/app/services/admin_service.py +++ b/app/services/admin_service.py @@ -504,7 +504,6 @@ JOB_LABELS = { # Jobs driven by the daily_pipeline (in order) rather than their own timer. PIPELINE_MEMBERS = { "data_collector", - "fundamental_collector", "sentiment_collector", "rr_scanner", "outcome_evaluator", diff --git a/tests/unit/test_scheduler.py b/tests/unit/test_scheduler.py index d82bb44..ab4a1f7 100644 --- a/tests/unit/test_scheduler.py +++ b/tests/unit/test_scheduler.py @@ -23,8 +23,11 @@ class TestParseFrequency: assert _parse_frequency("Hourly") == {"hours": 1} assert _parse_frequency("DAILY") == {"hours": 24} + def test_weekly_maps_to_one_week(self): + assert _parse_frequency("weekly") == {"weeks": 1} + def test_unknown_defaults_to_daily(self): - assert _parse_frequency("weekly") == {"hours": 24} + assert _parse_frequency("monthly") == {"hours": 24} assert _parse_frequency("") == {"hours": 24}