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

102 lines
3.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""STA05 — EWMA-cross ensemble vote.
IDEA: Vote across many EMA crossovers (fast/slow pairs drawn from {5..200}).
position = net_vote / n_pairs (continuous, in [-1,+1]).
Apply vol-targeting on top. Diversified trend signal.
Grids tested (<=4 configs, <=6 total backtests):
Config A: wide pairs (5 fast × 4 slow), log-spaced fast {5,10,20,40},
slow {40,80,120,200} — only fast < slow. Position = sum(sign) / n.
Vol-target 20% cap 2x. TFs: 1d, 12h (2 cells × 2 assets = 4 runs, total 4)
Config B: same pairs but LONG-ONLY (clip to [0,1]) — long-flat like TP01.
TFs: 1d only (2 more runs = 6 total)
Both configs evaluated in the same pass by running study_weights twice on 1d/12h
for A (4 runs) and once on 1d for B (2 runs). Total = 6.
"""
import sys
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt")
import altlib as al
import numpy as np
# ---------------------------------------------------------------------------
# EMA PAIR POOL
# ---------------------------------------------------------------------------
FAST_SPANS = [5, 10, 20, 40]
SLOW_SPANS = [40, 80, 120, 200]
# all valid (fast, slow) pairs where fast < slow
PAIRS = [(f, s) for f in FAST_SPANS for s in SLOW_SPANS if f < s]
# e.g. (5,40),(5,80),...,(40,80),(40,120),(40,200) = 13 pairs
def _ewma_vote(df, long_only: bool = False) -> np.ndarray:
"""Ensemble vote across EMA crossover pairs.
For each pair (fast, slow): signal = sign(ema_fast - ema_slow).
Position = mean(signals) across pairs, clipped to [-1,1] (or [0,1] if long_only).
Apply vol-targeting.
"""
c = df["close"].values.astype(float)
n = len(c)
votes = np.zeros(n)
for fast_span, slow_span in PAIRS:
ema_fast = al.ema(c, fast_span)
ema_slow = al.ema(c, slow_span)
# sign: +1 if fast > slow (uptrend), -1 if below
sig = np.sign(ema_fast - ema_slow)
votes += sig
# net vote normalized to [-1, 1]
direction = votes / len(PAIRS)
if long_only:
direction = np.clip(direction, 0.0, 1.0)
# vol-target: scale to 20% annualized vol, cap 2x
pos = al.vol_target(direction, df, target_vol=0.20, vol_win_days=30, leverage_cap=2.0)
return pos
# Config A: long-short ensemble
def target_ls(df):
return _ewma_vote(df, long_only=False)
# Config B: long-only ensemble (long-flat)
def target_lo(df):
return _ewma_vote(df, long_only=True)
# ---------------------------------------------------------------------------
# RUN — 4 runs for Config A (1d+12h), 2 for Config B (1d) = 6 total
# ---------------------------------------------------------------------------
print(f"EMA pairs: {PAIRS} ({len(PAIRS)} total)")
print("Running Config A (long-short) on 1d + 12h ...")
rep_a = al.study_weights("STA05-A-LS", target_ls, tfs=("1d", "12h"))
print(al.fmt(rep_a))
print("JSON:", al.as_json(rep_a))
print("\nRunning Config B (long-only) on 1d ...")
rep_b = al.study_weights("STA05-B-LO", target_lo, tfs=("1d",))
print(al.fmt(rep_b))
print("JSON:", al.as_json(rep_b))
# ---------------------------------------------------------------------------
# PICK BEST CONFIG
# ---------------------------------------------------------------------------
best_a = rep_a["verdict"].get("best_holdout_sharpe", -9)
best_b = rep_b["verdict"].get("best_holdout_sharpe", -9)
if best_a >= best_b:
rep_best = rep_a
print("\n>>> BEST: Config A (long-short)")
else:
rep_best = rep_b
print("\n>>> BEST: Config B (long-only)")
print("\n=== FINAL BEST ===")
print(al.fmt(rep_best))
print("JSON:", al.as_json(rep_best))