feat: replace regime monitor with v2 methodology

This commit is contained in:
2026-07-15 09:02:56 +02:00
parent fd21067a40
commit 1d5b1489be
17 changed files with 1599 additions and 1535 deletions
+18 -12
View File
@@ -1,7 +1,7 @@
"""Market-level endpoints (benchmark regime + AI/Tech regime-change monitor)."""
from fastapi import APIRouter, Depends, Query
from pydantic import BaseModel
from pydantic import BaseModel, Field, field_validator
from sqlalchemy.ext.asyncio import AsyncSession
from app.dependencies import get_db, require_access, require_admin
@@ -40,12 +40,18 @@ async def backtest_report(
class RegimeConfigUpdate(BaseModel):
weights: dict[str, float] | None = None
alert_threshold: float | None = None
tickers: dict | None = None
leader_weight: float | None = None
rs_lookback: int | None = None
fundamental_staleness_days: int | None = None
breadth_basket: list[str] | None = Field(default=None, min_length=20, max_length=100)
fundamental_staleness_days: int | None = Field(default=None, ge=30, le=180)
@field_validator("breadth_basket")
@classmethod
def normalise_basket(cls, value: list[str] | None) -> list[str] | None:
if value is None:
return None
cleaned = [symbol.strip().upper().replace(".", "-") for symbol in value if symbol.strip()]
if len(cleaned) != len(set(cleaned)):
raise ValueError("breadth basket symbols must be unique")
return cleaned
class RegimeFundamentalsUpdate(BaseModel):
@@ -59,7 +65,7 @@ async def regime_monitor(
_user: User = Depends(require_access),
db: AsyncSession = Depends(get_db),
) -> APIEnvelope:
"""Latest AI/Tech regime-change index (0-100) + per-signal breakdown + trend."""
"""Latest v2 State and Warning risk-thermometer readings."""
data = await regime_monitor_service.get_regime_monitor(db)
return APIEnvelope(status="success", data=data)
@@ -69,7 +75,7 @@ async def regime_config(
_admin: User = Depends(require_admin),
db: AsyncSession = Depends(get_db),
) -> APIEnvelope:
"""Editable weights / thresholds / ticker lists for the regime monitor."""
"""Editable fixed breadth basket and fundamental freshness window."""
data = await regime_monitor_service.get_regime_config(db)
return APIEnvelope(status="success", data=data)
@@ -80,7 +86,7 @@ async def update_regime_config(
_admin: User = Depends(require_admin),
db: AsyncSession = Depends(get_db),
) -> APIEnvelope:
"""Merge the supplied fields into the stored regime-monitor config."""
"""Update the deliberately small v2 operator configuration."""
updates = body.model_dump(exclude_none=True)
data = await regime_monitor_service.update_regime_config(db, updates)
return APIEnvelope(status="success", data=data)
@@ -133,10 +139,10 @@ async def regime_event_study(
@router.get("/regime/history", response_model=APIEnvelope)
async def regime_history(
days: int = Query(default=400, ge=7, le=2000),
days: int = Query(default=800, ge=7, le=2000),
_user: User = Depends(require_access),
db: AsyncSession = Depends(get_db),
) -> APIEnvelope:
"""Daily history of the index / early-warning / combined scores (for the chart)."""
"""Point-in-time v2 State/Warning history. Legacy rows are excluded."""
data = await regime_monitor_service.get_regime_history(db, days=days)
return APIEnvelope(status="success", data=data)