"""Shared forward-tracking read-model DTOs. The engine (`forward_engine.run_track`) produces + persists
these to the DB; `forward_ingest` reads them back to compute the gate; the dashboard reads them too. One
ForwardSummary per strategy is ALWAYS available; the per-day ForwardNavRow series is only rich for trackers
that record a days-list (others build it forward, one point per ingest)."""
from __future__ import annotations

from dataclasses import dataclass


@dataclass(frozen=True, slots=True)
class ForwardNavRow:
    strategy_id: str
    date: str          # ISO yyyy-mm-dd
    ret: float         # that day's fractional return (0.0 if unknown for snapshot-only trackers)
    nav: float         # cumulative NAV (1.0 base)


@dataclass(frozen=True, slots=True)
class ForwardSummary:
    strategy_id: str
    as_of: str         # last date represented
    days: int          # forward days elapsed
    nav: float
    total_return: float
    sharpe: float
    maxdd: float       # <= 0

    @property
    def total_return_pct(self) -> float:
        return 100.0 * self.total_return


@dataclass(frozen=True, slots=True)
class BacktestSummary:
    """One construction's historical-backtest verdict, persisted to the cockpit for the dashboard.
    Mirrors ForwardSummary but carries the gauntlet outcome (passed/dsr/is/oos/pvalue) instead of the
    forward gate. `as_of` is the last rebalance date in the backtest run."""
    strategy_id: str       # eqfactor_long | eqfactor_ls | eqfactor_tilt
    as_of: str             # last rebalance date (ISO yyyy-mm-dd), "" if none
    cagr: float
    ann_vol: float
    sharpe: float
    max_drawdown: float    # <= 0
    passed: bool
    dsr: float
    is_sharpe: float
    oos_sharpe: float
    pvalue: float


CAT_PASS = "PASS"
CAT_DIVERGENCE = "DIVERGENCE"
CAT_IMMATURE = "IMMATURE"
CAT_EXECUTION_GAP = "EXECUTION_GAP"


@dataclass(frozen=True, slots=True)
class GateVerdict:
    status: str        # "WAIT" | "GO" | "NO_GO" | "PASS"
    reason: str
    # machine-readable funding-signal category (B3). Default IMMATURE = neutral (never de-funds) so any
    # verdict constructed without an explicit category is fail-safe.
    category: str = CAT_IMMATURE
