SPB Git

Repository Public Research preview

evaluate.py 6.5 KB 122 lines python

1"""Alignment of forecasts with realized targets and computation of losses (one long table per instrument).23Target for horizon h at origin t (day index i): Y_{t,h} = sum_{k=1..h} y_{i+k}, where y is a daily variance proxy4(percent^2) from wp46.scoring.proxies. The target is valid only if all h proxy days are valid. For the5'rv5ss_scaled' proxy, the scaling constant c_t = mean(r^2)/mean(rv5ss) is computed over the estimation window ending6at the origin (valid days only; no look-ahead). Forecast rows with status != ok/stale_fit get loss = NaN and keep their7status so that failure accounting is possible downstream."""8from __future__ import annotations910import numpy as np11import pandas as pd1213from .losses import LOSSES14from .proxies import proxy_series1516HORIZONS = (1, 5, 22)171819def cumulative_targets(panel: pd.DataFrame, proxy: str, horizons=HORIZONS, window: int = 1000) -> pd.DataFrame:20    y = proxy_series(panel, proxy)21    n = len(y)22    if proxy == "rv5ss_scaled":23        r2 = panel["r"].values ** 224        valid = np.isfinite(y)25        num = pd.Series(np.where(valid, r2, np.nan)).rolling(window, min_periods=200).mean().values26        den = pd.Series(y).rolling(window, min_periods=200).mean().values27        c = num / den                               # c_t known at the origin t28    out = {"origin": pd.to_datetime(panel["session"]).values}29    for h in horizons:30        tgt = np.full(n, np.nan)31        for i in range(n - h):32            seg = y[i + 1:i + 1 + h]33            if np.all(np.isfinite(seg)):34                tgt[i] = float(np.sum(seg)) * (c[i] if proxy == "rv5ss_scaled" else 1.0)35        out[f"y{h}"] = tgt36    return pd.DataFrame(out)373839def score_forecasts(F: pd.DataFrame, panel: pd.DataFrame, proxies=("rv5ss_on",), losses=("qlike",), horizons=HORIZONS,40                    window: int = 1000) -> pd.DataFrame:41    """Long table: origin, spec_id, model_id, dist, tier, h, proxy, loss, value, status, forecast, target."""42    rows = []43    F = F.copy()44    F["origin"] = pd.to_datetime(F["origin"])45    for proxy in proxies:46        T = cumulative_targets(panel, proxy, horizons, window)47        M = F.merge(T, on="origin", how="left")48        okf = M["status"].isin(["ok", "stale_fit"])49        for h in horizons:50            fc = M[f"h{h}"].values if f"h{h}" in M else np.full(len(M), np.nan)51            y = M[f"y{h}"].values52            for ln in losses:53                v = LOSSES[ln](np.where(np.isfinite(y), y, np.nan), np.where(okf & np.isfinite(fc), fc, np.nan))54                v = np.where(okf & np.isfinite(y) & np.isfinite(fc), v, np.nan)55                rows.append(pd.DataFrame({"origin": M["origin"].values, "spec_id": M["spec_id"].values, "model_id": M["model_id"].values,56                                          "dist": M["dist"].values, "tier": M["tier"].values, "h": h, "proxy": proxy, "loss": ln,57                                          "value": v, "status": M["status"].values, "forecast": fc, "target": y}))58    return pd.concat(rows, ignore_index=True)596061def common_sample_mask(L: pd.DataFrame, spec_ids: list[str]) -> pd.Series:62    """Origins (per h/proxy/loss) at which every spec in spec_ids has a finite loss."""63    sub = L[L["spec_id"].isin(spec_ids)]64    ok = sub.assign(f=np.isfinite(sub["value"])).groupby(["origin", "h", "proxy", "loss"])["f"].agg(["sum", "size"])65    full = ok[(ok["sum"] == len(spec_ids)) & (ok["size"] == len(spec_ids))].index66    key = pd.MultiIndex.from_frame(L[["origin", "h", "proxy", "loss"]])67    return pd.Series(key.isin(full), index=L.index)686970def score_forecasts_wide(F: pd.DataFrame, panel: pd.DataFrame, proxies=("rv5ss_on",), losses=("qlike",), horizons=HORIZONS,71                         window: int = 1000, primary_proxy: str = "rv5ss_on", primary_loss: str = "qlike") -> dict:72    """Memory-efficient scoring. Returns dict with73       'forecast': {h: DataFrame origin x spec_id (cumulative variance forecasts, NaN when status not ok/stale_fit)},74       'status':   DataFrame origin x spec_id (status strings),75       'target':   {(proxy, h): Series origin -> target},76       'loss':     {(proxy, loss, h): DataFrame origin x spec_id};77    secondary losses are computed for the primary proxy only and secondary proxies for the primary loss only."""78    F = F.copy()79    F["origin"] = pd.to_datetime(F["origin"])80    okf = F["status"].isin(["ok", "stale_fit"])81    status = F.pivot_table(index="origin", columns="spec_id", values="status", aggfunc="first")82    fc = {}83    for h in horizons:84        col = f"h{h}"85        v = F[col].where(okf) if col in F else pd.Series(np.nan, index=F.index)86        fc[h] = F.assign(_v=v).pivot_table(index="origin", columns="spec_id", values="_v", aggfunc="first", dropna=False)87    for extra in ("mu", "dp1", "dp2", "sigma2_t"):88        if extra in F:89            fc[extra] = F.assign(_v=F[extra].where(okf)).pivot_table(index="origin", columns="spec_id", values="_v", aggfunc="first", dropna=False)90    targets, loss = {}, {}91    for proxy in proxies:92        T = cumulative_targets(panel, proxy, horizons, window).set_index("origin")93        for h in horizons:94            y = T[f"y{h}"].reindex(fc[h].index)95            targets[(proxy, h)] = y96            for ln in losses:97                if proxy != primary_proxy and ln != primary_loss:98                    continue99                Hm = fc[h].values100                Y = np.repeat(y.values[:, None], Hm.shape[1], axis=1)101                with np.errstate(all="ignore"):102                    Lm = LOSSES[ln](np.where(np.isfinite(Y), Y, np.nan), np.where(np.isfinite(Hm), Hm, np.nan))103                Lm = np.where(np.isfinite(Y) & np.isfinite(Hm), Lm, np.nan)104                loss[(proxy, ln, h)] = pd.DataFrame(Lm, index=fc[h].index, columns=fc[h].columns)105    return {"forecast": fc, "status": status, "target": targets, "loss": loss}106107108def pack_wide(res: dict, symbol: str) -> pd.DataFrame:109    """Long-but-compact table for storage: one row per (kind, proxy, loss, h, origin) with spec columns."""110    frames = []111    for h, df in res["forecast"].items():112        if isinstance(h, int):113            frames.append(df.assign(kind="forecast", proxy="", loss="", h=h))114        else:115            frames.append(df.assign(kind=h, proxy="", loss="", h=1))116    for (proxy, ln, h), df in res["loss"].items():117        frames.append(df.assign(kind="loss", proxy=proxy, loss=ln, h=h))118    tgt = pd.DataFrame({f"__target__{p}_{h}": s for (p, h), s in res["target"].items()})119    out = pd.concat(frames).reset_index().rename(columns={"index": "origin"})120    out["symbol"] = symbol121    return out, tgt.reset_index().rename(columns={"index": "origin"}), res["status"].reset_index()122
Files main
Menu

Theme