feat(features): add hl_range + ret_intrabar OHLCV features (4-channel input)

- prepare_hourly.py: keep O/H/L columns from M1 zips; compute per-hour
  hl_range=log(H/L) and ret_intrabar=log(close/open); backward-compat
  (falls back to 4-col output only when O/H/L present in input)
- train.py build(): auto-detect extra features from parquet columns
  (FEAT_COLS = [ret, realized_vol] + [hl_range, ret_intrabar] if present)
- 5 new tests (9 total in test_prepare_hourly); 24/24 pass

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-26 13:11:59 +02:00
co-authored by Claude Sonnet 4.6
parent e635a641a4
commit caccd1aa7b
3 changed files with 122 additions and 14 deletions
+8 -2
View File
@@ -154,7 +154,11 @@ def build():
else:
df = pd.read_parquet(daily_path).reset_index(drop=True)
df["date"] = pd.to_datetime(df["date"])
feats = df[["ret", "realized_vol"]].to_numpy(np.float32)
# Use OHLCV-derived features when available; fall back to 2-channel
base_feats = ["ret", "realized_vol"]
extra_feats = [c for c in ["hl_range", "ret_intrabar"] if c in df.columns]
FEAT_COLS = base_feats + extra_feats
feats = df[FEAT_COLS].to_numpy(np.float32)
target = df["realized_vol"].to_numpy(np.float32)
tr_idx = df.index[df["date"].dt.year <= 2021].tolist()
te_idx = df.index[df["date"].dt.year >= 2022].tolist()
@@ -268,7 +272,9 @@ def main():
df2 = pd.read_parquet(daily_path2).reset_index(drop=True)
df2["date"] = pd.to_datetime(df2["date"])
tr_mask = df2["date"].dt.year <= 2021
feats2 = df2[["ret", "realized_vol"]].to_numpy(np.float32)
base2 = ["ret", "realized_vol"]
extra2 = [c for c in ["hl_range", "ret_intrabar"] if c in df2.columns]
feats2 = df2[base2 + extra2].to_numpy(np.float32)
mu2 = feats2[tr_mask].mean(0); sd2 = feats2[tr_mask].std(0) + 1e-8
fn2 = (feats2 - mu2) / sd2
def _export_windows(year_mask):