fix: refresh latest regime trading session
This commit is contained in:
+1
-1
@@ -28,7 +28,7 @@ FINNHUB_API_KEY=
|
|||||||
ALPHA_VANTAGE_API_KEY=
|
ALPHA_VANTAGE_API_KEY=
|
||||||
|
|
||||||
# Regime Monitor — FRED (VIX + HY credit spreads). Free key: https://fred.stlouisfed.org/docs/api/api_key.html
|
# 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=
|
FRED_API_KEY=
|
||||||
|
|
||||||
# Scheduled Jobs
|
# Scheduled Jobs
|
||||||
|
|||||||
@@ -772,7 +772,7 @@ async def update_regime_monitor(db: AsyncSession, rebuild_sessions: int = REBUIL
|
|||||||
written, latest_result = await _upsert_snapshot(
|
written, latest_result = await _upsert_snapshot(
|
||||||
db,
|
db,
|
||||||
computed,
|
computed,
|
||||||
rewrite_existing_v2=rebuilding or snapshot_date == date.today(),
|
rewrite_existing_v2=rebuilding or snapshot_date == latest_date,
|
||||||
)
|
)
|
||||||
snapshots_written += int(written)
|
snapshots_written += int(written)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|||||||
@@ -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
|
history before that freeze date is retrospective/exploratory; readings after it
|
||||||
form the forward record.
|
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
|
## Warning study
|
||||||
|
|
||||||
The study calls the outcome a **10% correction**, not a regime break. The first
|
The study calls the outcome a **10% correction**, not a regime break. The first
|
||||||
|
|||||||
@@ -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.</>}
|
footnote={<>Breadth divergence, SMH/SPY rollover, and point-in-time fundamental observations. Unknown or stale fundamentals reduce coverage; they never default to 50.</>}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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-80" />}><RegimeQuadrant /></Suspense>
|
||||||
<Suspense fallback={<SkeletonCard className="h-72" />}><ScoreHistoryChart /></Suspense>
|
<Suspense fallback={<SkeletonCard className="h-72" />}><ScoreHistoryChart /></Suspense>
|
||||||
|
|||||||
@@ -169,6 +169,59 @@ async def test_prior_v2_snapshot_is_immutable_without_explicit_rebuild(db_sessio
|
|||||||
assert row.total_score == 10.0
|
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():
|
def test_compute_index_uses_one_max_price_vote_and_has_no_combined_score():
|
||||||
end = date(2026, 6, 26)
|
end = date(2026, 6, 26)
|
||||||
rising = [100.0 + index * 0.2 for index in range(700)]
|
rising = [100.0 + index * 0.2 for index in range(700)]
|
||||||
|
|||||||
Reference in New Issue
Block a user