Files
Adriano Dal Pastro 5ac4e16af8 research(alt): sweep 104 strategie alternative su Deribit (153 agenti) + marginal scorer
Ondata di ricerca onesta a largo spettro su BTC/ETH+DVOL certificati: 104 ipotesi
distinte (11 famiglie), un agente-finder per ipotesi, verifica avversariale a 3
scettici sui promettenti, sintesi (153 agenti totali). Esito: NIENTE di nuovo regge
-> conferma del soffitto strutturale ~1.3 BTC/ETH-direzionale; lo stack
TP01+XS01+VRP01 resta imbattuto.

- altlib.py: harness condiviso vettoriale leak-free (eval_weights/study_weights,
  fee-sweep, both-asset + hold-out 2025+). Riproduce i numeri canonici di TP01.
- MARGINAL SCORER (study_marginal/marginal_vs_tp01): Sharpe INCREMENTALE vs baseline
  TP01 (corr, blend uplift OOS, alpha residua) + jackknife OOS (clean-year +
  drop-best-month). earns_slot = abs!=FAIL & ADDS & robust_oos. Smaschera gli overlay
  su TSMOM con PASS assoluti fasulli (CMB04, VOL11, ...) e il falso positivo KAMA
  (ADDS ma muore al jackknife).
- runs/*.py (104) script riproducibili per ipotesi; wf_altstrat.js workflow.
- Verdetto: 0 candidati deployabili; 2 LEAD fragili (VOL08, STA05_LS) da forward-monitor.
- test_marginal_scorer.py blocca baseline + invarianti. Suite: 32 verde.

Diario: docs/diary/2026-06-20-alt-strategies-100agent-sweep.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 19:50:39 +00:00

146 lines
5.6 KiB
Python

"""RSK01 — Vol-target B&H + DD breaker.
Hypothesis: Long-only vol-targeted (no trend signal) with a circuit breaker:
- Normally always long, scaled by vol-targeting (target 20%, cap 2x)
- Goes FLAT when the strategy equity drawdown from peak exceeds `dd_thresh`
- Re-enters when the MARKET (asset price) recovers by `recovery_frac` from its
trough level at the time the breaker fired
(NOTE: recovery on MARKET price, not strategy equity — otherwise the flat
position freezes equity and the breaker never clears, a death spiral)
- Does the breaker beat pure vol-targeted buy&hold?
Grid: 3 configs on 1d (= 6 asset-backtests, within the 6-backtest limit).
"""
import sys
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt")
import altlib as al
import numpy as np
def rsk01_target(df, dd_thresh: float = 0.15, recovery_frac: float = 0.50) -> np.ndarray:
"""
Causal vol-targeted long-only position with equity-DD circuit breaker.
Breaker fires when strategy equity drawdown > dd_thresh.
Recovery: re-enter when asset price has risen by recovery_frac * (asset price drop
from the time breaker fired). This is observable from MARKET price, avoids death-spiral.
At each bar i:
1. Base vol-targeted position (direction=+1) computed causally
2. Simulated strategy equity updated by previous bar's held position
3. If equity-DD > dd_thresh → BREAKER ON, record price_trough = close[i]
4. BREAKER recovers when close[i] >= price_trough * (1 + recovery_frac * rel_drop)
where rel_drop = (price_at_breaker_on - price_trough_at_bar_i) / price_at_breaker_on
More simply: re-enter when close[i] >= price_trough * (1 + recovery_frac * dd_thresh)
"""
c = df["close"].values.astype(float)
r = al.simple_returns(c)
# Base vol-targeted position (always long direction=+1)
direction = np.ones(len(c))
base_pos = al.vol_target(direction, df, target_vol=0.20, vol_win_days=30, leverage_cap=2.0)
n = len(c)
final_pos = np.zeros(n)
# Strategy equity tracking (causal: equity at i reflects positions through i-1)
eq = 1.0
peak = 1.0
breaker_on = False
price_trough = np.nan # asset price when breaker fired
recovery_target_price = np.nan # asset price target for re-entry
for i in range(n):
# Update strategy equity from previous bar's position
if i > 0:
prev_pos = final_pos[i - 1]
eq *= (1.0 + prev_pos * r[i])
# Update running equity peak
if eq > peak:
peak = eq
dd = (peak - eq) / peak if peak > 0 else 0.0
price_now = c[i]
if not breaker_on:
if dd > dd_thresh:
breaker_on = True
# Record asset price trough at breakout trigger
price_trough = price_now
# Recovery target: price rises by recovery_frac * dd_thresh above trough
# (dd_thresh is a proxy for the % drop in the asset that caused the DD)
recovery_target_price = price_trough * (1.0 + recovery_frac * dd_thresh)
else:
# Re-enter when asset recovers to recovery_target_price
if price_now >= recovery_target_price:
breaker_on = False
price_trough = np.nan
recovery_target_price = np.nan
# Also reset the equity peak to current level to avoid immediate re-trigger
peak = eq
final_pos[i] = 0.0 if breaker_on else base_pos[i]
return final_pos
def make_target(dd_thresh: float, recovery_frac: float):
"""Factory to create a target function with fixed params."""
def _target(df):
return rsk01_target(df, dd_thresh=dd_thresh, recovery_frac=recovery_frac)
_target.__name__ = f"RSK01_dd{int(dd_thresh*100)}_rec{int(recovery_frac*100)}"
return _target
# Grid: 3 configs on 1d (= 6 asset-backtests, within the 6-backtest limit)
CONFIGS_SCREEN = [
(0.10, 0.50), # tight breaker, recover 50% of dd_thresh in price terms
(0.15, 0.50), # moderate breaker
(0.20, 0.50), # loose breaker
]
print("=== RSK01: Vol-target B&H + DD circuit breaker ===")
print("Recovery measured on MARKET PRICE (not frozen strategy equity)")
print("Screening 3 configs on 1d (6 asset-backtests)...")
print()
best_rep = None
best_score = -999
best_cfg = None
for dd_thresh, rec_frac in CONFIGS_SCREEN:
name = f"RSK01_dd{int(dd_thresh*100)}_rec{int(rec_frac*100)}"
target_fn = make_target(dd_thresh, rec_frac)
rep = al.study_weights(name, target_fn, tfs=("1d",))
score = rep["verdict"].get("best_holdout_sharpe", -9)
btc = rep["cells"][0]["per_asset"]["BTC"]
eth = rep["cells"][0]["per_asset"]["ETH"]
print(f" {name}:")
print(f" BTC: full Sh={btc['full']['sharpe']:.2f} DD={btc['full']['maxdd']:.1%} "
f"TIM={btc['tim']:.1%} hold Sh={btc['holdout']['sharpe']:.2f}")
print(f" ETH: full Sh={eth['full']['sharpe']:.2f} DD={eth['full']['maxdd']:.1%} "
f"TIM={eth['tim']:.1%} hold Sh={eth['holdout']['sharpe']:.2f}")
print(f" grade={rep['verdict']['grade']} minFull={rep['verdict'].get('best_full_sharpe'):.2f} "
f"minHold={score:.2f}")
print()
if score > best_score:
best_score = score
best_rep = rep
best_cfg = (dd_thresh, rec_frac)
print(f"Best config: dd_thresh={best_cfg[0]}, recovery_frac={best_cfg[1]}")
print()
# Final clean report on best config
dd_thresh, rec_frac = best_cfg
name = f"RSK01_dd{int(dd_thresh*100)}_rec{int(rec_frac*100)}"
target_fn = make_target(dd_thresh, rec_frac)
final_rep = al.study_weights(name, target_fn, tfs=("1d",))
print(al.fmt(final_rep))
print("JSON:", al.as_json(final_rep))