1"""HAR-RV at the intraday scale (Corsi 2009 adapted): log RV over the next H minutes regressed on log trailing RV over2the last 30 minutes, 390 minutes (one day) and 5 days (1,950 minutes), plus time-of-day seasonal log variance.3Fitted by OLS on training origins; predictive distribution Student-t with zero mean, scale from exp(fitted) and a4degrees-of-freedom estimated from standardised training residuals (method of moments, clipped to [2.5, 50])."""5from __future__ import annotations67import numpy as np89from ..data.windows import SymbolGrid, N_DAY10from .naive import _hist_returns111213def har_features(g: SymbolGrid, s: int, m: int, seas_prof: np.ndarray | None = None) -> np.ndarray:14 r, sl = _hist_returns(g, s, m, 6)15 if len(r) < 60:16 return None17 idx_today = np.flatnonzero(np.arange(len(r)) >= len(r) - m) if m > 0 else np.zeros(0, dtype=int)18 rv30 = np.mean(r[-30:] ** 2)19 rv390 = np.mean(r[-390:] ** 2)20 rv5d = np.mean(r ** 2)21 f = [np.log(rv30 + 1e-12), np.log(rv390 + 1e-12), np.log(rv5d + 1e-12)]22 return np.array(f)232425class HARRV:26 name = "har_rv"2728 def __init__(self):29 self.beta: dict[int, np.ndarray] = {}30 self.nu: dict[int, float] = {}31 self.sd: dict[int, float] = {}3233 @staticmethod34 def _design(X: np.ndarray, m: np.ndarray, H: int) -> np.ndarray:35 tod = (m + 1 + H / 2) / 389.036 return np.column_stack([np.ones(len(X)), X, tod, tod ** 2, np.log(H) * np.ones(len(X))])3738 def fit(self, X: np.ndarray, m: np.ndarray, rv: np.ndarray, cum: np.ndarray, H: int) -> None:39 A = self._design(X, m, H)40 y = np.log(rv + 1e-12)41 beta, *_ = np.linalg.lstsq(A, y, rcond=None)42 self.beta[H] = beta43 pred_var = np.exp(A @ beta)44 z = cum / np.sqrt(pred_var)45 k = float(np.mean(z ** 4) / np.mean(z ** 2) ** 2) # kurtosis -> nu = 4 + 6/(k-3)46 nu = 4 + 6 / max(k - 3, 0.12)47 self.nu[H] = float(np.clip(nu, 2.5, 50))48 self.sd[H] = float(np.std(y - A @ beta))4950 def predict_batch(self, X: np.ndarray, m: np.ndarray, H: int) -> dict:51 A = self._design(X, m, H)52 var = np.exp(A @ self.beta[H] + 0.5 * self.sd[H] ** 2) # log-normal mean correction53 nu = self.nu[H]54 return {"mu": np.zeros(len(X)), "scale": np.sqrt(var * (nu - 2) / nu), "nu": np.full(len(X), nu), "var": var}55