feat(multipair): G10 multi-pair pipeline + USE_MULTIPAIR knob (Option C)

- prepare_hourly.py: parameterize PAIR env var; OUT_DEFAULT per-pair; load_m1_from_zips(pair=)
- prepare_multipair.py: inner-join 5-pair hourly parquets on datetime → wide parquet
  cols: datetime, {pair}_ret, {pair}_rv × n_pairs; eurusd_rv = target
- fetch_multipair.py: download GBPUSD/USDJPY/USDCHF/AUDUSD M1 2008-2023 from histdata
- train.py: USE_MULTIPAIR knob (JEPA_USE_MULTIPAIR=1); build() reads multipair parquet
  with n_channels = n_pairs × 2; target = eurusd_rv
- Taskfile: data:fetch:multipair, data:prepare:pair, data:prepare:multipair, data:test updated
- 7 new tests in test_multipair.py; 34/35 pass (1 SKIP until multipair parquet built)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-26 14:00:57 +02:00
co-authored by Claude Sonnet 4.6
parent b2bc01ba9e
commit bd8962f997
6 changed files with 281 additions and 11 deletions
+18 -7
View File
@@ -35,6 +35,7 @@ PHASE1_LR = float(_os.environ.get("JEPA_PHASE1_LR", 1e-3))
PHASE1_JOINT = bool(int(_os.environ.get("JEPA_PHASE1_JOINT", 1)))
PHASE1_JOINT_EPOCHS= int(_os.environ.get("JEPA_PHASE1_JOINT_EPOCHS", 30))
PHASE1_ENCODER_LR = float(_os.environ.get("JEPA_PHASE1_ENCODER_LR", 3e-6))
USE_MULTIPAIR = bool(int(_os.environ.get("JEPA_USE_MULTIPAIR", 0)))
SEED = int(_os.environ.get("JEPA_SEED", 0))
# ---------------------------
@@ -149,19 +150,29 @@ def build():
falls back to eurusd_daily.parquet otherwise.
"""
import os
hourly_path = "data/processed/eurusd_hourly.parquet"
daily_path = "data/processed/eurusd_daily.parquet"
if USE_HOURLY and os.path.exists(hourly_path):
multipair_path = "data/processed/eurusd_multipair.parquet"
hourly_path = "data/processed/eurusd_hourly.parquet"
daily_path = "data/processed/eurusd_daily.parquet"
if USE_MULTIPAIR and os.path.exists(multipair_path):
df = pd.read_parquet(multipair_path).reset_index(drop=True)
df["date"] = pd.to_datetime(df["datetime"])
# All {pair}_ret + {pair}_rv columns as features; eurusd_rv as target
feat_cols = [c for c in df.columns if c.endswith("_ret") or c.endswith("_rv")]
FEAT_COLS = feat_cols
target_col = "eurusd_rv"
elif USE_HOURLY and os.path.exists(hourly_path):
df = pd.read_parquet(hourly_path).reset_index(drop=True)
df["date"] = pd.to_datetime(df["datetime"])
# 2-channel default (HPO: adding hl_range+ret_intrabar hurt — correlated with base feats)
FEAT_COLS = ["ret", "realized_vol"]
target_col = "realized_vol"
else:
df = pd.read_parquet(daily_path).reset_index(drop=True)
df["date"] = pd.to_datetime(df["date"])
# 2-channel default (HPO: adding hl_range+ret_intrabar hurt — correlated with base feats)
# To experiment: change to ["ret", "realized_vol", "hl_range", "ret_intrabar"]
FEAT_COLS = ["ret", "realized_vol"]
FEAT_COLS = ["ret", "realized_vol"]
target_col = "realized_vol"
feats = df[FEAT_COLS].to_numpy(np.float32)
target = df["realized_vol"].to_numpy(np.float32)
target = df[target_col].to_numpy(np.float32)
tr_idx = df.index[df["date"].dt.year <= 2021].tolist()
te_idx = df.index[df["date"].dt.year >= 2022].tolist()
mu = feats[:tr_idx[-1]+1].mean(0)