1// HAR family (registry keys har.*). Daily series RV_t (t = 1..T), trading-day lags, causal construction:2// HAR-RV (Corsi 2009): y_{t+h} = β0 + βd RV_t + βw RV^{(5)}_t + βm RV^{(22)}_t + ε, RV^{(k)}_t = (1/k) Σ_{j=0}^{k−1} RV_{t−j}3// log-HAR: same with log RV on both sides (forecast = exp(ŷ) × smearing optional, not applied by default)4// HAR-X: + user covariates X_t (already aligned at t)5// HARQ (Bollerslev–Patton–Quaedvlieg 2016): βd → (βd + βq √RQ_t): term βq · (√RQ_t − mean √RQ) · RV_t added (demeaning optional)6// Target for horizon h: y_{t+h} = (1/h) Σ_{j=1}^{h} RV_{t+j} (average over the next h days, "overlapping" when h > 1)7// or the single-day RV_{t+h} (`target = single`). Overlapping targets induce MA(h−1) errors → HAC with ≥ h−1 lags.8#pragma once9#include <vector>10#include <span>11#include <string>12#include <Eigen/Dense>13#include "raid/core/types.hpp"14#include "raid/core/estimate.hpp"15#include "raid/core/plan.hpp"16#include "raid/estimators/ols.hpp"17#include "raid/backend/backend.hpp"1819namespace raid::est {2021enum class HarTarget { average, single };2223struct HarSpec {24 std::vector<usize> lags{1, 5, 22}; // daily, weekly, monthly windows25 int horizon{1};26 HarTarget target{HarTarget::average};27 bool log{false}; // log-HAR28 bool harq{false}; // add √RQ_t · RV_t29 bool harq_demean_sqrt_rq{true}; // use (√RQ_t − √(mean RQ)) · RV_t exactly as highfrequency::HARmodel(type="HARQ"); the mean is over the sample passed to the fit (estimation window), never the future30 stats::CovOptions cov{stats::CovType::hac_bartlett, -1, false, {}, true}; // hac_lags −1 → max(h−1, ⌊4(n/100)^{2/9}⌋)31 std::string model_key() const { return std::string(log ? "har.logrv" : "har.rv") + (harq ? ".harq" : "") + ".h" + std::to_string(horizon); }32};3334/// Builds the aligned design (y, X) from a daily RV series (and optional RQ and exogenous columns X_t, aligned at t).35struct HarDesign { Eigen::MatrixXd X; Eigen::VectorXd y; std::vector<std::string> names; usize first_t{0}; usize n{0}; f64 sqrt_rq_mean{0}; };36HarDesign har_design(std::span<const f64> rv, const HarSpec& spec, std::span<const f64> rq = {}, const Eigen::MatrixXd& exog = Eigen::MatrixXd(), const std::vector<std::string>& exog_names = {});3738/// Fit one HAR on one series (CPU QR + robust covariance). Estimate.extra contains fitted/residual summaries.39Estimate har_fit(std::span<const f64> rv, const HarSpec& spec, std::span<const f64> rq = {}, const Eigen::MatrixXd& exog = Eigen::MatrixXd(), const std::vector<std::string>& exog_names = {});4041/// Forecast ŷ for the last available t (origin = last observation) from a fitted HAR; for log models returns exp(ŷ)42/// (naive back-transform; the smearing factor is reported in extra["smearing"] and not applied).43f64 har_forecast(const Estimate& fit, std::span<const f64> rv, const HarSpec& spec, std::span<const f64> rq = {}, const Eigen::VectorXd& exog_last = Eigen::VectorXd());4445/// Rolling (or expanding when window = 0) re-estimation with one-origin-ahead forecasts, strictly causal:46/// the model fitted on observations ≤ t predicts y_{t+h}. Returns forecasts aligned with the target index.47struct HarRolling { std::vector<usize> origin; std::vector<f64> forecast, actual; Eigen::MatrixXd coefficients; usize n_fits{0}; f64 seconds{0}; };48HarRolling har_rolling(std::span<const f64> rv, const HarSpec& spec, usize window, usize step = 1, std::span<const f64> rq = {});4950/// Batched HAR over a panel of daily RV series (CSR). Sufficient statistics are accumulated by the backend (Metal when51/// available: regression_stats) and solved in FP64 on the CPU; inference is classical (HAC requires residuals: use52/// har_fit per series when robust SE are needed). Returns one Estimate per series.53struct HarBatchResult { std::vector<Estimate> fits; backend::KernelStats stats; };54HarBatchResult har_batch(std::span<const f64> rv_concat, std::span<const i64> offsets, const HarSpec& spec, const ExecutionPlan& plan = {});5556/// Forecast losses for volatility: MSE and QLIKE (Patton 2011): QLIKE = y/ŷ − log(y/ŷ) − 1.57struct ForecastLoss { f64 mse, qlike; usize n; };58ForecastLoss forecast_loss(std::span<const f64> actual, std::span<const f64> forecast);5960} // namespace raid::est61