1"""Ridge / elastic-net and LightGBM baselines on hand-crafted origin features (identical information set).2Targets: (i) cumulative H-minute return (location), (ii) log realised variance over H (scale). Predictive3distribution: Student-t with location from the mean model (ridge/LGBM), scale from exp(log-variance model) and nu4from the kurtosis of standardised training residuals (clipped to [2.5, 50])."""5from __future__ import annotations67import numpy as np89from ..data.windows import SymbolGrid, N_DAY10from .naive import _hist_returns1112FEATURE_NAMES = ["log_rv30", "log_rv390", "log_rv5d", "ret30", "ret390", "ret_day", "overnight", "relvol30",13 "range30", "minute_frac", "time_to_close", "weekday", "early_close", "log_sigma", "skew390", "kurt390",14 "sign_last", "ret5", "rv_ratio_30_390"]151617def origin_features(g: SymbolGrid, s: int, m: int, sigma: float) -> np.ndarray | None:18 r, sl = _hist_returns(g, s, m, 6)19 if len(r) < 60:20 return None21 r30, r390, r5 = r[-30:], r[-390:], r[-5:]22 lc_today = g.flat_lc[s * N_DAY: s * N_DAY + m + 1]23 ok = ~np.isnan(lc_today)24 ret_day = float(lc_today[ok][-1] - lc_today[ok][0]) if ok.sum() >= 2 else 0.025 gap = g.gap[s]; gap = 0.0 if np.isnan(gap) else float(gap)26 base = g.flat_base[s * N_DAY + max(0, m - 29): s * N_DAY + m + 1]; mk = g.flat_mask[s * N_DAY + max(0, m - 29): s * N_DAY + m + 1]27 seas = g.flat_seas[s * N_DAY + max(0, m - 29): s * N_DAY + m + 1]28 rel = base[mk, 4] - seas[mk]; rel = rel[~np.isnan(rel)]29 relvol30 = float(rel.mean()) if len(rel) else 0.030 rng30 = float((base[mk, 1] - base[mk, 2]).mean()) if mk.any() else 0.031 sd390 = r390.std() + 1e-1232 f = [np.log(np.mean(r30 ** 2) + 1e-12), np.log(np.mean(r390 ** 2) + 1e-12), np.log(np.mean(r ** 2) + 1e-12),33 r30.sum() / sigma, r390.sum() / sigma, ret_day / sigma, gap / sigma, relvol30, rng30 / sigma, m / 389.0,34 (g.n_slots[s] - 1 - m) / 389.0, g.weekday[s] / 4.0, float(g.early[s]), np.log(sigma),35 float(np.mean(((r390 - r390.mean()) / sd390) ** 3)), float(np.mean(((r390 - r390.mean()) / sd390) ** 4)),36 float(np.sign(r[-1])), r5.sum() / sigma, np.log((np.mean(r30 ** 2) + 1e-12) / (np.mean(r390 ** 2) + 1e-12))]37 return np.array(f, dtype=np.float64)383940class _TwoPart:41 """Common fit/predict for location + log-variance models with Student-t assembly."""4243 def __init__(self, name: str):44 self.name = name45 self.loc: dict[int, object] = {}; self.lv: dict[int, object] = {}; self.nu: dict[int, float] = {}46 self.scaler = None4748 def _new(self):49 raise NotImplementedError5051 def fit(self, X: np.ndarray, cum: np.ndarray, rv: np.ndarray, sigma: np.ndarray, H: int, Xv=None, cumv=None, rvv=None, sigv=None) -> None:52 y_loc = cum / sigma53 y_lv = np.log(rv / sigma ** 2 + 1e-9)54 self.loc[H] = self._new().fit(X, y_loc)55 self.lv[H] = self._new().fit(X, y_lv)56 var = np.exp(self.lv[H].predict(X)) * sigma ** 257 z = (cum - self.loc[H].predict(X) * sigma) / np.sqrt(var)58 k = float(np.mean(z ** 4) / np.mean(z ** 2) ** 2)59 self.nu[H] = float(np.clip(4 + 6 / max(k - 3, 0.12), 2.5, 50))6061 def predict_batch(self, X: np.ndarray, sigma: np.ndarray, H: int) -> dict:62 mu = self.loc[H].predict(X) * sigma63 var = np.exp(self.lv[H].predict(X)) * sigma ** 264 nu = self.nu[H]65 return {"mu": mu, "scale": np.sqrt(var * (nu - 2) / nu), "nu": np.full(len(X), nu), "var": var}666768class RidgeBaseline(_TwoPart):69 def __init__(self, alpha: float = 1.0):70 super().__init__("ridge"); self.alpha = alpha7172 def _new(self):73 from sklearn.linear_model import Ridge74 from sklearn.pipeline import make_pipeline75 from sklearn.preprocessing import StandardScaler76 return make_pipeline(StandardScaler(), Ridge(alpha=self.alpha))777879class ElasticNetBaseline(_TwoPart):80 def __init__(self, alpha: float = 1e-3, l1_ratio: float = 0.5):81 super().__init__("elastic_net"); self.alpha, self.l1 = alpha, l1_ratio8283 def _new(self):84 from sklearn.linear_model import ElasticNet85 from sklearn.pipeline import make_pipeline86 from sklearn.preprocessing import StandardScaler87 return make_pipeline(StandardScaler(), ElasticNet(alpha=self.alpha, l1_ratio=self.l1, max_iter=5000))888990class LightGBMBaseline(_TwoPart):91 def __init__(self, n_estimators: int = 400, learning_rate: float = 0.03, num_leaves: int = 31, seed: int = 0):92 super().__init__("lightgbm"); self.kw = dict(n_estimators=n_estimators, learning_rate=learning_rate,93 num_leaves=num_leaves, subsample=0.8, subsample_freq=1,94 colsample_bytree=0.8, random_state=seed, verbose=-1, n_jobs=8)9596 def _new(self):97 import lightgbm as lgb98 return lgb.LGBMRegressor(**self.kw)99