Загрузка данных
from pathlib import Path
path = Path("/content/AIQuant/aiquant/models/ensemble_pipeline.py")
text = path.read_text()
start_marker = "best_r = None"
end_marker = " # ── Save best params ─────────────────────────────────────────────────────"
start = text.index(start_marker)
end = text.index(end_marker, start)
new_block = '''# ── Honest threshold selection ─────────────────────────────────────────────
# Split OOS chronologically: 70% validation / 30% final unseen test.
oos_idx = np.where(oos_mask)[0]
if len(oos_idx) < 1000:
print(f" {RED('✗')} Not enough OOS samples for validation/test split.")
return {}
split_pos = int(len(oos_idx) * 0.70)
val_idx = oos_idx[:split_pos]
test_idx = oos_idx[split_pos:]
print(
f" OOS split: {len(val_idx):,} validation bars / "
f"{len(test_idx):,} final-test bars"
)
def evaluate_threshold(lt, st, idx):
"""Evaluate thresholds only on the supplied contiguous index range."""
scores = ens_score[idx]
prices = c[idx]
sig = np.zeros(len(idx), dtype=np.int8)
sig[scores > lt] = 1
sig[scores < st] = -1
changes = np.where(np.diff(sig, prepend=0) != 0)[0]
if len(changes) < 20:
return None
cap = float(capital)
equity = np.full(len(idx), cap, dtype=np.float64)
wins = losses = 0
gw = gl = 0.0
for j in range(len(changes) - 1):
eb = changes[j]
xb = changes[j + 1]
d = int(sig[eb])
if d == 0:
equity[eb:xb] = cap
continue
ep = prices[eb]
xp = prices[xb]
sz = (cap * 0.25) / ep
pnl = d * (xp - ep) * sz * (1 - FEE) ** 2
cap = max(cap + pnl, 1.0)
equity[eb:xb] = cap
if pnl > 0:
wins += 1
gw += pnl
else:
losses += 1
gl += abs(pnl)
equity[changes[-1]:] = cap
total_ret = (cap - capital) / capital * 100
peak = np.maximum.accumulate(equity)
dd = (equity - peak) / peak * 100
max_dd = float(np.min(dd))
n_days = len(idx) / 1440.0
if n_days > 2:
daily_log_returns = np.array([
np.diff(
np.log(
equity[i * 1440:min((i + 1) * 1440, len(equity))]
+ 1e-10
)
).sum()
for i in range(int(np.ceil(n_days)))
if len(equity[i * 1440:min((i + 1) * 1440, len(equity))]) > 1
])
if len(daily_log_returns) > 1 and daily_log_returns.std() > 0:
sharpe = float(
daily_log_returns.mean()
/ daily_log_returns.std()
* np.sqrt(365)
)
else:
sharpe = 0.0
else:
sharpe = 0.0
trades = wins + losses
win_rate = wins / trades * 100 if trades > 0 else 0.0
calmar = total_ret / abs(max_dd) if abs(max_dd) > 0.01 else 0.0
profit_factor = gw / (gl + 1e-10)
return dict(
sharpe=round(sharpe, 3),
ret=round(total_ret, 2),
max_dd=round(max_dd, 2),
calmar=round(calmar, 3),
pf=round(profit_factor, 3),
trades=trades,
win_rate=round(win_rate, 1),
final=round(cap, 2),
equity=equity,
signals=sig,
long_thresh=lt,
short_thresh=st,
)
# Select thresholds ONLY on validation data.
best_r = None
for long_thresh in [0.05, 0.08, 0.10, 0.12, 0.15, 0.18, 0.20, 0.25]:
for short_thresh in [-0.05, -0.08, -0.10, -0.12, -0.15, -0.18, -0.20, -0.25]:
r = evaluate_threshold(long_thresh, short_thresh, val_idx)
if r is None:
continue
if best_r is None or r['sharpe'] > best_r['sharpe']:
best_r = r
col = GREEN if r['sharpe'] >= 2.0 else (
YELLOW if r['sharpe'] >= 1.0 else RED
)
print(
f" {col('↑')} Validation Sharpe={col(f"{r['sharpe']:+.3f}")} "
f"Ret={r['ret']:+.1f}% MaxDD={r['max_dd']:.1f}% "
f"Trades={r['trades']:,} WR={r['win_rate']:.0f}% "
f"Calmar={r['calmar']:.2f} "
f"[L>{long_thresh} S<{short_thresh}]"
)
if best_r is None:
print(f" {RED('✗')} No valid threshold found.")
return {}
best_long = best_r['long_thresh']
best_short = best_r['short_thresh']
print()
print(" ── FINAL TEST (unseen) ─────────────────────────────────────────────")
final_r = evaluate_threshold(best_long, best_short, test_idx)
if final_r is None:
print(f" {RED('✗')} Final test produced fewer than 20 trades.")
return {}
print(
f" Final Test: Ret={final_r['ret']:+.2f}% "
f"Sharpe={final_r['sharpe']:+.3f} "
f"MaxDD={final_r['max_dd']:.2f}% "
f"PF={final_r['pf']:.3f} "
f"Trades={final_r['trades']:,} "
f"WR={final_r['win_rate']:.1f}%"
)
# Use final-test result for reporting, while thresholds were chosen
# exclusively on validation data.
best_r = final_r
# Save raw OOS predictions for later analysis.
np.savez(
RESULTS_DIR / 'oos_preds.npz',
oos_idx=oos_idx,
ens_score=ens_score[oos_idx],
val_idx=val_idx,
test_idx=test_idx,
best_long=np.array(best_long),
best_short=np.array(best_short),
)
'''
text = text[:start] + new_block + text[end:]
path.write_text(text)
print("✅ Threshold block replaced")