fix: refresh latest regime trading session
Deploy / lint (push) Successful in 10s
Deploy / test (push) Successful in 1m14s
Deploy / deploy (push) Successful in 39s

This commit is contained in:
2026-07-15 09:13:42 +02:00
parent 1d5b1489be
commit 81c6f5fe2f
5 changed files with 66 additions and 2 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ FINNHUB_API_KEY=
ALPHA_VANTAGE_API_KEY=
# Regime Monitor — FRED (VIX + HY credit spreads). Free key: https://fred.stlouisfed.org/docs/api/api_key.html
# Optional: without it the VIX (P5) and credit-spread (F2) signals show as n/a.
# Optional: without it the volatility (V1) and credit (C1) pillars show as n/a.
FRED_API_KEY=
# Scheduled Jobs
+1 -1
View File
@@ -772,7 +772,7 @@ async def update_regime_monitor(db: AsyncSession, rebuild_sessions: int = REBUIL
written, latest_result = await _upsert_snapshot(
db,
computed,
rewrite_existing_v2=rebuilding or snapshot_date == date.today(),
rewrite_existing_v2=rebuilding or snapshot_date == latest_date,
)
snapshots_written += int(written)
await db.commit()
+5
View File
@@ -48,6 +48,11 @@ Each snapshot stores the fixed basket symbols, hash, and freeze date. Reconstruc
history before that freeze date is retrospective/exploratory; readings after it
form the forward record.
The automatic 400-session rebuild is intentionally one-shot: it runs only when
no v2 snapshot exists. If an initial seed used partial data or the wrong basket,
the operational reseed procedure is to remove the v2 snapshot rows and run the
Regime Monitor job again. There is no routine force-rebuild flag.
## Warning study
The study calls the outcome a **10% correction**, not a regime break. The first
+6
View File
@@ -328,6 +328,12 @@ export default function RegimePage() {
footnote={<>Breadth divergence, SMH/SPY rollover, and point-in-time fundamental observations. Unknown or stale fundamentals reduce coverage; they never default to 50.</>}
/>
</div>
<p className="text-xs text-gray-600">
Data quality · oldest market input:{' '}
{data.data_quality?.oldest_market_input_age_days == null
? 'unavailable'
: `${data.data_quality.oldest_market_input_age_days}d`}
</p>
<Suspense fallback={<SkeletonCard className="h-80" />}><RegimeQuadrant /></Suspense>
<Suspense fallback={<SkeletonCard className="h-72" />}><ScoreHistoryChart /></Suspense>
+53
View File
@@ -169,6 +169,59 @@ async def test_prior_v2_snapshot_is_immutable_without_explicit_rebuild(db_sessio
assert row.total_score == 10.0
@pytest.mark.asyncio
async def test_routine_can_refresh_latest_trading_session_after_civil_day_rolls(
monkeypatch,
):
latest_date = date(2020, 1, 3)
config = copy.deepcopy(DEFAULT_CONFIG)
prices = {
"SMH": [(latest_date, 100.0)],
"QQQ": [(latest_date, 100.0)],
"SPY": [(latest_date, 100.0)],
}
rewrites: list[bool] = []
async def fake_config(_db):
return config
async def fake_overrides(_db):
return {"locked": True, "fetched_at": None, "effective_date": None}
async def fake_prices(_config, _start, _end):
return prices
async def fake_fred(_series_id, _start, _end):
return None
async def fake_breadth(_db, _symbols, window, min_tickers):
return {}, {}
async def fake_latest(_db):
return object(), {"methodology": "v2"}
async def fake_upsert(_db, result, *, rewrite_existing_v2):
rewrites.append(rewrite_existing_v2)
return True, result
class FakeDB:
async def commit(self):
return None
monkeypatch.setattr(rms, "get_regime_config", fake_config)
monkeypatch.setattr(rms, "get_fundamental_overrides", fake_overrides)
monkeypatch.setattr(rms, "_fetch_prices", fake_prices)
monkeypatch.setattr(rms, "_fetch_fred_series", fake_fred)
monkeypatch.setattr(rms.breadth_service, "compute_breadth_details", fake_breadth)
monkeypatch.setattr(rms, "_latest_v2_row", fake_latest)
monkeypatch.setattr(rms, "_upsert_snapshot", fake_upsert)
result = await rms.update_regime_monitor(FakeDB())
assert result["date"] == latest_date.isoformat()
assert rewrites == [True]
def test_compute_index_uses_one_max_price_vote_and_has_no_combined_score():
end = date(2026, 6, 26)
rising = [100.0 + index * 0.2 for index in range(700)]