run fundamentals weekly, not daily — it's quarterly-ish data
Deploy / lint (push) Successful in 5s
Deploy / test (push) Successful in 36s
Deploy / deploy (push) Successful in 24s

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 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 11:23:16 +02:00
parent e982487abd
commit 9008865d75
4 changed files with 12 additions and 5 deletions
+1 -1
View File
@@ -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
+7 -2
View File
@@ -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,
-1
View File
@@ -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",
+4 -1
View File
@@ -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}