Files
AlphaMaster/analyze_index_overfit2.py
T
2026-07-05 23:19:21 +08:00

152 lines
5.7 KiB
Python

"""Analyze index group overfitting: check time-period breakdown of the factor"""
import sys, json, math
import numpy as np
import torch
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
from config import Config
from data_pipeline.data_manager import MT5DataManager
from data_pipeline.fetcher import MT5DataFetcher
from model_core.vocab import FORMULA_VOCAB, VOCAB_VERSION
from model_core.vm import StackVM
from model_core.features import MT5FeatureEngineer
from strategy_manager.signal import compute_target_positions_stateless
_H1_PER_YEAR = 6240
# Load index strategy
data = json.load(open("strategies/best_index.json"))
formula = data["formula"]
print(f"Formula: {formula}")
print(f"Decode: ", end="")
names = FORMULA_VOCAB.token_names
print(" -> ".join(names[t] if 0 <= t < len(names) else f"?{t}" for t in formula))
print()
# Load index group data
Config.SYMBOLS = ["US30.cash", "US100.cash", "US500.cash", "US2000.cash", "JP225.cash"]
with MT5DataFetcher(offline=True) as fetcher:
mgr = MT5DataManager(fetcher)
mgr.load()
raw_dict = mgr.raw_dict
syms = mgr.symbols
T = raw_dict["open"].shape[1]
feat = MT5FeatureEngineer.compute_features(raw_dict)
target_ret = mgr.target_ret
print(f"Symbols: {syms}")
print(f"T={T} bars ({T/_H1_PER_YEAR:.2f} years)")
# Get bar times - just use bar index for simplicity
bar_times = None
# Run factor
vm = StackVM()
factor = vm.execute(formula, feat)
factor_np = factor.detach().numpy()
# Compute positions and PnL
pos = compute_target_positions_stateless(factor)
pos_np = pos.detach().numpy()
target_np = target_ret.detach().numpy()
cost_rate = 0.0003
prev = np.zeros_like(pos_np)
prev[:, 1:] = pos_np[:, :-1]
turnover = np.abs(pos_np - prev)
pnl = pos_np * target_np - turnover * cost_rate
port_pnl = pnl.mean(axis=0)
port_cum = np.cumsum(port_pnl)
# Split into 8 equal segments
n_seg = 8
seg_len = T // n_seg
print(f"\n=== {n_seg}-SEGMENT BREAKDOWN ===")
print(f"{'Seg':>4s} {'Bars':>6s} {'Period':>22s} {'Return':>9s} {'Sharpe':>8s} {'MDD':>8s} {'WinRate':>8s} {'AvgPos':>7s}")
for seg in range(n_seg):
s = seg * seg_len
e = min((seg + 1) * seg_len, T)
seg_pnl = port_pnl[s:e]
seg_cum = np.cumsum(seg_pnl)
t_start = f"bar{s}"
t_end = f"bar{e}"
seg_ret = float(seg_cum[-1])
m, sd = seg_pnl.mean(), seg_pnl.std()
seg_sharpe = m / (sd + 1e-10) * math.sqrt(_H1_PER_YEAR)
seg_mdd = float((np.maximum.accumulate(seg_cum) - seg_cum).max())
# Win rate per bar
win_rate = float((seg_pnl > 0).mean())
# Avg position
seg_pos = pos_np[:, s:e]
avg_pos = float(np.abs(seg_pos).mean())
print(f"{seg+1:>4d} {e-s:>6d} {t_start+' ~ '+t_end:>22s} {seg_ret:>+9.4f} {seg_sharpe:>+8.3f} {seg_mdd:>8.4f} {win_rate*100:>7.1f}% {avg_pos:>7.3f}")
# Per-year analysis
print(f"\n=== PER-YEAR BREAKDOWN ===")
n_years = T // _H1_PER_YEAR
print(f"{'Year':>6s} {'Bars':>6s} {'Return':>9s} {'Sharpe':>8s} {'MDD':>8s} {'WinRate':>8s}")
for yr in range(n_years + 1):
s = yr * _H1_PER_YEAR
e = min((yr + 1) * _H1_PER_YEAR, T)
if s >= T:
break
seg_pnl = port_pnl[s:e]
seg_cum = np.cumsum(seg_pnl)
seg_ret = float(seg_cum[-1])
m, sd = seg_pnl.mean(), seg_pnl.std()
seg_sharpe = m / (sd + 1e-10) * math.sqrt(_H1_PER_YEAR)
seg_mdd = float((np.maximum.accumulate(seg_cum) - seg_cum).max())
win_rate = float((seg_pnl > 0).mean())
t_start = f"bar{s}"
t_end = f"bar{e}"
print(f" Y{yr+1:>2d} {e-s:>6d} {seg_ret:>+9.4f} {seg_sharpe:>+8.3f} {seg_mdd:>8.4f} {win_rate*100:>7.1f}% ({t_start} ~ {t_end})")
# Per-symbol per-half analysis
print(f"\n=== PER-SYMBOL PER-HALF ANALYSIS ===")
split = T // 2
print(f"{'Symbol':<14s} {'H1_Ret':>9s} {'H2_Ret':>9s} {'H1_Sharpe':>10s} {'H2_Sharpe':>10s} {'H1_MDD':>8s} {'H2_MDD':>8s}")
for i, sym in enumerate(syms):
p1 = pnl[i, :split]
p2 = pnl[i, split:]
c1 = np.cumsum(p1)
c2 = np.cumsum(p2)
r1 = float(c1[-1])
r2 = float(c2[-1])
s1 = p1.mean() / (p1.std() + 1e-10) * math.sqrt(_H1_PER_YEAR)
s2 = p2.mean() / (p2.std() + 1e-10) * math.sqrt(_H1_PER_YEAR)
mdd1 = float((np.maximum.accumulate(c1) - c1).max())
mdd2 = float((np.maximum.accumulate(c2) - c2).max())
print(f"{sym:<14s} {r1:>+9.4f} {r2:>+9.4f} {s1:>+10.3f} {s2:>+10.3f} {mdd1:>8.4f} {mdd2:>8.4f}")
# Factor value distribution over time
print(f"\n=== FACTOR VALUE STATISTICS OVER TIME ===")
for seg in range(n_seg):
s = seg * seg_len
e = min((seg + 1) * seg_len, T)
seg_factor = factor_np[:, s:e]
seg_pos = pos_np[:, s:e]
print(f"Seg {seg+1}: factor mean={seg_factor.mean():+.4f} std={seg_factor.std():.4f} "
f"pos_mean={seg_pos.mean():+.4f} pos_abs={np.abs(seg_pos).mean():.4f} "
f"long%={float((seg_pos>0.01).mean())*100:.1f}% short%={float((seg_pos<-0.01).mean())*100:.1f}% flat%={float((np.abs(seg_pos)<0.01).mean())*100:.1f}%")
# Check if factor is basically "always long"
print(f"\n=== POSITION DISTRIBUTION (overall) ===")
for i, sym in enumerate(syms):
p = pos_np[i]
long_pct = float((p > 0.01).mean()) * 100
short_pct = float((p < -0.01).mean()) * 100
flat_pct = float((np.abs(p) < 0.01).mean()) * 100
print(f" {sym:<14s}: long={long_pct:.1f}% short={short_pct:.1f}% flat={flat_pct:.1f}% avg_abs_pos={np.abs(p).mean():.3f}")