research(wave-0702bis): ondata video-claims — Elliott 3/3 e Albimarini 2/2 scartati, mappa capital scaling 2-5k

Sei agenti, nessun sopravvissuto:
- ELL-A range-cycle: rumore (0/24 Bonferroni; nessuna cella weekly regge
  a tutte le 7 ancore). Lezione pandas: resample("7D", origin) IGNORA
  origin -> usare "168h" per le bande d'ancora weekly.
- ELL-B Fibonacci: l'edge apparente e' la POSIZIONE dei livelli, non i
  numeri (null location-matched: pctl 0.39-0.68); confluenza FAIL 4/4.
- ELL-C canale: Donchian travestito (non batte il Donchian equivalente,
  DSR 0.685, IS 1.40 -> HOLD -0.87; target 1.618 = caso; anchor-luck 4h).
- ALB-A diagonale: il condor stessa-scadenza la batte a ogni f; senza
  gate IV-rank tutte le strutture perdono (3a conferma: l'alpha del VRP
  e' il gate); fee-negativa su Deribit a qualsiasi size; 2o caso
  "0-perdite = Sharpe implausibile" dopo CC01.
- ALB-B claims: 82%/PF 5.16/"420%" consistente con zero skill (P=20-45%,
  78.6% delle finestre 6-mesi lo produce); replay con code reali =
  rovina 1998/2002/2020; la diagonale passa il 12-40% della perdita naked.
- Capital scaling 600->2-5k: unico vincolo binding = cap $300/asset
  (a 5k book al 49% del target) -> AL DEPOSITO alzare a equity/2;
  min_order $5 lasciare; XS01 ~20k confermata; aspettativa onesta
  de-luckata 2k ~EUR 0.6-0.8/g, 5k ~EUR 1.4-2/g.

Nessun nuovo sleeve, book live invariato. 168 test verdi.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-07-02 23:31:38 +00:00
parent a74cc69583
commit 76120b59c2
8 changed files with 3453 additions and 0 deletions
+562
View File
@@ -0,0 +1,562 @@
"""r0702_ell_fibconfluence.py — FIBONACCI CONFLUENCE: il prezzo reagisce ai livelli Fib
piu' che a livelli QUALSIASI? (2026-07-02, filone ELL-B)
CLAIM (video didattico, Casario): proiettando su swing di prezzo griglie di ritracciamento
(0.382/0.5/0.618) ed estensione (1.272/1.414/1.618/2.618) di Fibonacci, i livelli — e in
particolare le CONFLUENZE dove due griglie convergono — sarebbero zone di reazione del prezzo.
Claim testabile: la reazione ai livelli Fib batte quella a livelli con rapporti CASUALI
proiettati sugli STESSI swing? IL NULL E' TUTTO: senza placebo, "il prezzo ha reagito al 61.8%"
e' solo apofenia (qualunque livello dentro il range viene "toccato e rispettato" a volte).
METODO (tutto a priori, dichiarato PRIMA di guardare i numeri):
* Swing MECCANICI e causali: zigzag a soglia k*ATR14 (k in {2,4}), NON-REPAINTING — un pivot
e' confermato solo quando close si e' mosso k*ATR dall'estremo; i livelli dello swing
(A->B) sono utilizzabili solo da confirm_idx+1 (timestamp di conferma esplicito).
* Griglia attiva per finestra [confirm(B)+1, confirm(pivot successivo)]: ritracciamenti
L = pB - r*M e estensioni L = pA + e*M (M = pB-pA). Finestre disgiunte per costruzione.
* TOCCO = low<=L<=high con la barra precedente che NON conteneva L (fresh touch); direzione
attesa dal lato di approccio (close[i-1]>L -> supporto -> atteso rimbalzo SU; <L -> giu').
Decisione a close[i] (high/low noti a fine barra), reazione misurata da close[i] a
close[i+H], H in {5,20} barre — ESEGUIBILE (niente entry sugli estremi). La variante
"fill al livello" (da L invece che da close) e' riportata SOLO come lens ottimista.
* Statistica primaria: media della reazione segnata NORMALIZZATA per ATR (react/(ATR/close))
sui tocchi IN-SAMPLE (pre-2025); raw bps riportati. Il drift da buy-the-dip e' identico
per Fib e placebo -> il percentile vs placebo lo neutralizza.
* NULL (a): 100 set di rapporti PLACEBO fissi — 3 ritracciamenti uniformi in [0.2,0.9] +
4 estensioni uniformi in [1.05,3.0], esclusa banda max(0.02, 2%rel) attorno ai Fib veri —
proiettati sugli STESSI swing. NULL (b): 100 repliche con rapporti casuali RI-SORTEGGIATI
per ogni swing (stessa densita', nessuna coerenza cross-swing). NULL (c) — AGGIUNTO dopo
il run 1 come indurimento dichiarato: 100 set LOCATION-MATCHED (ogni rapporto = Fib vero
+/- jitter appena fuori la banda esclusa, entro ~6-8%) — il null uniforme (a) non e'
density-matched (la reazione media dipende da DOVE sta il rapporto, non solo da quale
numero e'), quindi (c) e' il test affilato di "0.618 e' speciale vs 0.58/0.66".
Verdetto = percentile del Fib vero nelle tre distribuzioni placebo.
* CONFLUENZA: ritracciamenti dello swing corrente x estensioni dello swing s-2 (stesso
verso, il precedente ha verso opposto e proietta sempre FUORI dal range corrente — bug
geometrico del run 1, corretto e dichiarato) entro eps=0.25*ATR(w0); reazione in zona
confluente vs livelli singoli delle stesse griglie, stesso placebo.
* MULTIPLE TESTING: 16 celle (2 asset x 2 TF x 2 k x 2 H). Soglia single-cell Bonferroni
~0.997; verdetto famiglia (a priori): esiste (k,H) con percentile POOLED IS >= 0.99 su
ENTRAMBI i null E tutte le 4 celle asset x TF a pctl >= 0.90. Solo allora si strategizza
(fade ai livelli, fee 0.10% RT) con al.study_family_honest + al.study_marginal.
* Hold-out 2025-26 MAI usato per selezionare (qui non si seleziona nulla: rapporti fissati
dal claim; k/H riportati per cella, il verdetto e' sul pooled IS).
CAUSALITA': pivot ricalcolati su prefisso troncato == pivot full con confirm<cut (check [0]);
niente DatetimeIndex.view("int64") (mai usato; date via pandas tz-aware).
Uso: uv run python scripts/research/r0702_ell_fibconfluence.py
"""
from __future__ import annotations
import sys
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt")
import altlib as al # noqa: E402
import numpy as np # noqa: E402
import pandas as pd # noqa: E402
# ----------------------------- costanti (a priori) --------------------------
SEED = 20260702
ASSETS = ("BTC", "ETH")
TFS = ("1h", "1d")
K_GRID = (2.0, 4.0) # soglia zigzag = k * ATR14
ATR_WIN = 14
RET_TRUE = (0.382, 0.5, 0.618)
EXT_TRUE = (1.272, 1.414, 1.618, 2.618)
HORIZONS = (5, 20) # barre di reazione
N_PA = 100 # placebo (a): set di rapporti fissi
N_PB = 100 # placebo (b): rapporti ri-sorteggiati per swing
N_PC = 100 # placebo (c): set fissi LOCATION-MATCHED (jitter attorno ai Fib)
CONF_EPS_ATR = 0.25 # tolleranza confluenza = 0.25 * ATR14(w0)
MIN_CELL_TOUCH = 50 # tocchi IS minimi perche' una cella conti nel verdetto
MIN_CONF_TOUCH = 30
RET_RANGE = (0.20, 0.90)
EXT_RANGE = (1.05, 3.00)
NL = len(RET_TRUE) + len(EXT_TRUE) # 7 livelli per griglia
N_SETS = 1 + N_PA + N_PB + N_PC # riga 0 = Fib vero
# ===========================================================================
# ZIGZAG causale non-repainting + segmenti
# ===========================================================================
def zigzag_pivots(df: pd.DataFrame, k_atr: float, atr_win: int = ATR_WIN) -> list:
"""Pivot alternati H/L: (pivot_idx, price, type +1=high/-1=low, confirm_idx).
Un pivot e' confermato quando close si muove k*ATR14[i] oltre l'estremo corrente.
Causale: usa solo dati <= i; il pivot NON viene mai spostato dopo la conferma."""
h = df["high"].values.astype(float)
l = df["low"].values.astype(float)
c = df["close"].values.astype(float)
a = al.atr(df, atr_win)
n = len(c)
piv: list = []
start = atr_win
if n <= start + 2:
return piv
ehi, ehi_i = h[start], start
elo, elo_i = l[start], start
dir_ = 0
i = start + 1
while i < n and dir_ == 0: # bootstrap: direzione ignota
if h[i] > ehi:
ehi, ehi_i = h[i], i
if l[i] < elo:
elo, elo_i = l[i], i
if c[i] <= ehi - k_atr * a[i]:
piv.append((ehi_i, float(ehi), +1, i))
dir_ = -1
seg = l[ehi_i:i + 1]
elo_i = ehi_i + int(np.argmin(seg)); elo = float(seg.min())
elif c[i] >= elo + k_atr * a[i]:
piv.append((elo_i, float(elo), -1, i))
dir_ = +1
seg = h[elo_i:i + 1]
ehi_i = elo_i + int(np.argmax(seg)); ehi = float(seg.max())
i += 1
while i < n:
if dir_ == -1: # in discesa: cerco swing low
if l[i] < elo:
elo, elo_i = l[i], i
if c[i] >= elo + k_atr * a[i]:
piv.append((elo_i, float(elo), -1, i))
dir_ = +1
seg = h[elo_i:i + 1]
ehi_i = elo_i + int(np.argmax(seg)); ehi = float(seg.max())
else: # in salita: cerco swing high
if h[i] > ehi:
ehi, ehi_i = h[i], i
if c[i] <= ehi - k_atr * a[i]:
piv.append((ehi_i, float(ehi), +1, i))
dir_ = -1
seg = l[ehi_i:i + 1]
elo_i = ehi_i + int(np.argmin(seg)); elo = float(seg.min())
i += 1
return piv
def build_segments(piv: list, n: int) -> list:
"""Segmenti: swing A->B (pivot s-1 -> s), griglia attiva in [confirm(B)+1, confirm(next)].
prev2 = (qA, qB) dello swing s-2 (STESSO verso del corrente, per la confluenza: lo swing
s-1 ha verso opposto e le sue estensioni proiettano sempre fuori dal range corrente)."""
segs = []
for s in range(1, len(piv)):
A, B = piv[s - 1], piv[s]
w0 = B[3] + 1
w1 = piv[s + 1][3] if s + 1 < len(piv) else n - 1
prev2 = (piv[s - 3][1], piv[s - 2][1]) if s >= 3 else None
segs.append(dict(w0=w0, w1=w1, pA=A[1], pB=B[1], prev2=prev2))
return segs
# ===========================================================================
# RATIO SET (vero + placebo)
# ===========================================================================
def _draw_valid(rng, lo: float, hi: float, size, forbid) -> np.ndarray:
"""Uniformi in [lo,hi] con banda esclusa max(0.02, 2% relativo) attorno ai Fib veri."""
out = rng.uniform(lo, hi, size=size)
for _ in range(200):
bad = np.zeros(out.shape, bool)
for f in forbid:
bad |= np.abs(out - f) < max(0.02, 0.02 * f)
if not bad.any():
break
out[bad] = rng.uniform(lo, hi, size=int(bad.sum()))
return out
def make_placebo_a(rng):
"""(N_PA,3) ritracciamenti + (N_PA,4) estensioni: set FISSI cross-swing."""
r = _draw_valid(rng, *RET_RANGE, (N_PA, len(RET_TRUE)), RET_TRUE)
e = _draw_valid(rng, *EXT_RANGE, (N_PA, len(EXT_TRUE)), EXT_TRUE)
return r, e
def make_placebo_b(rng, n_seg: int):
"""(n_seg,N_PB,3) + (n_seg,N_PB,4): rapporti RI-SORTEGGIATI per ogni swing."""
r = _draw_valid(rng, *RET_RANGE, (n_seg, N_PB, len(RET_TRUE)), RET_TRUE)
e = _draw_valid(rng, *EXT_RANGE, (n_seg, N_PB, len(EXT_TRUE)), EXT_TRUE)
return r, e
def make_placebo_c(rng):
"""Set fissi LOCATION-MATCHED: ogni rapporto = Fib vero +/- jitter appena FUORI la banda
esclusa (max(0.02,2%rel)) ed entro ~6-8% -> vicini di casa dei Fib. Se i Fib sono davvero
speciali (il mercato ancora ESATTAMENTE a 0.618), devono battere anche questi."""
def jit(true_vals, lo, hi):
f = np.array(true_vals, float)[None, :]
band = np.maximum(0.02, 0.02 * f)
delta = band + rng.uniform(0.0, 0.06 * np.maximum(1.0, f), size=(N_PC, f.shape[1]))
sign = rng.choice([-1.0, 1.0], size=(N_PC, f.shape[1]))
return np.clip(f + sign * delta, lo, hi)
return jit(RET_TRUE, *RET_RANGE), jit(EXT_TRUE, *EXT_RANGE)
# ===========================================================================
# TOUCH SCAN vettoriale + accumulo
# ===========================================================================
def _touch_scan(h, l, c, w0: int, w1: int, L: np.ndarray, ids: np.ndarray):
"""Fresh touch dei livelli L nella finestra [w0,w1]: ritorna (bar globali, direzione
attesa dal lato di approccio, livello, id-set). Direzione: sign(close[i-1] - L)."""
lo = l[w0:w1 + 1]; hi = h[w0:w1 + 1]
contains = (lo[:, None] <= L[None, :]) & (hi[:, None] >= L[None, :])
fresh = contains.copy()
fresh[1:] &= ~contains[:-1]
fresh[0] &= ~((l[w0 - 1] <= L) & (h[w0 - 1] >= L))
tt, kk = np.nonzero(fresh)
if not len(tt):
z = np.zeros(0)
return z.astype(int), z, z, z.astype(int)
gi = w0 + tt
Lk = L[kk]
dirs = np.sign(c[gi - 1] - Lk)
m = dirs != 0
return gi[m], dirs[m], Lk[m], ids[kk[m]]
def _new_store():
return dict(
acc={(H, s): dict(sn=np.zeros(N_SETS), sr=np.zeros(N_SETS),
sl=np.zeros(N_SETS), c=np.zeros(N_SETS))
for H in HORIZONS for s in ("IS", "FULL")},
brk={s: dict(b=np.zeros(N_SETS), t=np.zeros(N_SETS)) for s in ("IS", "FULL")},
)
def _acc_touches(store, gi, dirs, Lk, sid, c, a14, is_pre, n):
if not len(gi):
return
relatr = a14[gi] / c[gi]
ok = np.isfinite(relatr) & (relatr > 0)
gi, dirs, Lk, sid, relatr = gi[ok], dirs[ok], Lk[ok], sid[ok], relatr[ok]
if not len(gi):
return
broke = ((dirs > 0) & (c[gi] < Lk)) | ((dirs < 0) & (c[gi] > Lk))
for scope, sm in (("FULL", np.ones(len(gi), bool)), ("IS", is_pre[gi])):
b = store["brk"][scope]
np.add.at(b["t"], sid[sm], 1.0)
np.add.at(b["b"], sid[sm & broke], 1.0)
for H in HORIZONS:
m = sm & ((gi + H) < n)
if not m.any():
continue
g2, d2 = gi[m], dirs[m]
react = d2 * (c[g2 + H] / c[g2] - 1.0)
a = store["acc"][(H, scope)]
np.add.at(a["sn"], sid[m], react / relatr[m])
np.add.at(a["sr"], sid[m], react)
np.add.at(a["sl"], sid[m], d2 * (c[g2 + H] / Lk[m] - 1.0))
np.add.at(a["c"], sid[m], 1.0)
def scan_config(df: pd.DataFrame, segs: list, PA_r, PA_e, PB_r, PB_e, PC_r, PC_e) -> dict:
"""Scansione completa di un config (asset,tf,k): test principale (griglia dello swing
corrente: ret+ext) e confluenza (ret corrente x ext dello swing s-2, stesso verso)."""
h = df["high"].values.astype(float)
l = df["low"].values.astype(float)
c = df["close"].values.astype(float)
a14 = al.atr(df, ATR_WIN)
n = len(c)
is_pre = (pd.to_datetime(df["datetime"], utc=True) < al.HOLDOUT).values
R_true_r = np.array(RET_TRUE)[None, :]
R_true_e = np.array(EXT_TRUE)[None, :]
main, conf, sing = _new_store(), _new_store(), _new_store()
czc = np.zeros(N_SETS) # numero di zone confluenti per set
set_rep = np.repeat(np.arange(N_SETS), NL)
for si, seg in enumerate(segs):
w0, w1 = seg["w0"], seg["w1"]
if w0 < 1 or w1 < w0 or w0 >= n:
continue
w1 = min(w1, n - 1)
pA, pB = seg["pA"], seg["pB"]
M = pB - pA
if abs(M) < 1e-12:
continue
Rr = np.vstack([R_true_r, PA_r, PB_r[si], PC_r]) # (N_SETS,3)
Re = np.vstack([R_true_e, PA_e, PB_e[si], PC_e]) # (N_SETS,4)
L = np.concatenate([pB - Rr * M, pA + Re * M], axis=1).ravel()
_acc_touches(main, *_touch_scan(h, l, c, w0, w1, L, set_rep), c, a14, is_pre, n)
# ------- confluenza: ret(cur) x ext(swing s-2, stesso verso) entro eps -------
if seg["prev2"] is None:
continue
qA, qB = seg["prev2"]
Mp = qB - qA
if abs(Mp) < 1e-12:
continue
eps = CONF_EPS_ATR * a14[w0]
if not np.isfinite(eps) or eps <= 0:
continue
Lr = pB - Rr * M # (N_SETS,3)
Le = qA + Re * Mp # (N_SETS,4)
dmat = np.abs(Lr[:, :, None] - Le[:, None, :]) # (N_SETS,3,4)
cs, cr, ce = np.nonzero(dmat <= eps)
if len(cs):
Lc = 0.5 * (Lr[cs, cr] + Le[cs, ce])
np.add.at(czc, cs, 1.0)
_acc_touches(conf, *_touch_scan(h, l, c, w0, w1, Lc, cs), c, a14, is_pre, n)
used_r = np.zeros((N_SETS, Lr.shape[1]), bool); used_r[cs, cr] = True
used_e = np.zeros((N_SETS, Le.shape[1]), bool); used_e[cs, ce] = True
sr_, rr_ = np.nonzero(~used_r)
se_, ee_ = np.nonzero(~used_e)
Ls = np.concatenate([Lr[sr_, rr_], Le[se_, ee_]])
ids_s = np.concatenate([sr_, se_])
_acc_touches(sing, *_touch_scan(h, l, c, w0, w1, Ls, ids_s), c, a14, is_pre, n)
return dict(main=main, conf=conf, sing=sing, czc=czc, n_seg=len(segs))
# ===========================================================================
# STATISTICHE dai contatori
# ===========================================================================
def _means(store, H: int, scope: str, field: str = "sn"):
a = store["acc"][(H, scope)]
with np.errstate(invalid="ignore", divide="ignore"):
return np.where(a["c"] > 0, a[field] / a["c"], np.nan)
def _pctl(true_val: float, plc: np.ndarray):
v = plc[np.isfinite(plc)]
if len(v) < 20 or not np.isfinite(true_val):
return None
return float(np.mean(v <= true_val))
IDX_A = slice(1, 1 + N_PA)
IDX_B = slice(1 + N_PA, 1 + N_PA + N_PB)
IDX_C = slice(1 + N_PA + N_PB, N_SETS)
def _cell_stats(store, H: int):
out = {}
for scope in ("IS", "FULL"):
mn = _means(store, H, scope)
mr = _means(store, H, scope, "sr")
ml = _means(store, H, scope, "sl")
cnt = store["acc"][(H, scope)]["c"]
out[scope] = dict(
n=int(cnt[0]),
raw_bps=float(mr[0] * 1e4) if np.isfinite(mr[0]) else None,
lvl_bps=float(ml[0] * 1e4) if np.isfinite(ml[0]) else None,
norm=float(mn[0]) if np.isfinite(mn[0]) else None,
plcA_med=float(np.nanmedian(mn[IDX_A])),
plcC_med=float(np.nanmedian(mn[IDX_C])),
pctlA=_pctl(mn[0], mn[IDX_A]),
pctlB=_pctl(mn[0], mn[IDX_B]),
pctlC=_pctl(mn[0], mn[IDX_C]),
)
b = store["brk"]["IS"]
with np.errstate(invalid="ignore", divide="ignore"):
rate = np.where(b["t"] > 0, b["b"] / b["t"], np.nan)
out["brk_rate"] = float(rate[0]) if np.isfinite(rate[0]) else None
# "il livello tiene" = break-rate BASSO -> pctl_hold alto se true < placebo
v = rate[IDX_A][np.isfinite(rate[IDX_A])]
out["brk_pctl_hold"] = (float(np.mean(v >= rate[0]))
if len(v) >= 20 and np.isfinite(rate[0]) else None)
return out
def _pool_stores(stores: list):
"""Somma i contatori di piu' config (stesso significato dei set -> pooling coerente)."""
pooled = _new_store()
for st in stores:
for key, a in st["acc"].items():
for f in ("sn", "sr", "sl", "c"):
pooled["acc"][key][f] += a[f]
for s, b in st["brk"].items():
pooled["brk"][s]["b"] += b["b"]
pooled["brk"][s]["t"] += b["t"]
return pooled
# ===========================================================================
# STRATEGIA (solo se il claim batte il placebo) — fade ai livelli Fib
# ===========================================================================
def fib_fade_positions(df: pd.DataFrame, k_atr: float, H: int) -> np.ndarray:
"""Posizione long/short: al fresh-touch di un livello Fib vero prendi la direzione
del rimbalzo atteso e tienila H barre (tocchi sovrapposti sommati, clip a +/-1).
Decisione a close[i] -> eval_weights la applica dalla barra i+1 (nessun leak)."""
piv = zigzag_pivots(df, k_atr)
n = len(df)
segs = build_segments(piv, n)
h = df["high"].values.astype(float)
l = df["low"].values.astype(float)
c = df["close"].values.astype(float)
sig = np.zeros(n)
ids0 = np.zeros(NL, dtype=int)
for seg in segs:
w0, w1 = seg["w0"], min(seg["w1"], n - 1)
if w0 < 1 or w1 < w0:
continue
pA, pB = seg["pA"], seg["pB"]
M = pB - pA
if abs(M) < 1e-12:
continue
L = np.concatenate([pB - np.array(RET_TRUE) * M, pA + np.array(EXT_TRUE) * M])
gi, dirs, _, _ = _touch_scan(h, l, c, w0, w1, L, ids0)
np.add.at(sig, gi, dirs)
return np.clip(pd.Series(sig).rolling(H, min_periods=1).sum().values, -1.0, 1.0)
def make_fib_target(tf: str = "1d", k_atr: float = 2.0, H: int = 20):
def target(df):
return fib_fade_positions(df, k_atr, H)
return target
# ===========================================================================
# MAIN
# ===========================================================================
def main():
print("=" * 104)
print("r0702 ELL-B FIBONACCI CONFLUENCE — reazione ai livelli Fib vs rapporti placebo "
"(il null e' tutto)")
print(f"zigzag k*ATR14 k={K_GRID}, ratios ret={RET_TRUE} ext={EXT_TRUE}, H={HORIZONS} barre,"
f" placebo A={N_PA} set fissi / B={N_PB} per-swing / C={N_PC} location-matched,"
f" seed={SEED}")
print("statistica = media reazione segnata/ATR sui tocchi IS (pre-2025); "
"pctl alto = Fib meglio del placebo")
print("=" * 104)
# ---------- [0] CAUSALITY: pivot su prefisso troncato + target strategy ----------
print("\n[0] CAUSALITY CHECK zigzag (pivot confermati < cut identici su prefisso 90%)")
for a in ASSETS:
for tf in TFS:
df = al.get(a, tf)
cut = int(len(df) * 0.9)
pf = [p for p in zigzag_pivots(df, K_GRID[0]) if p[3] < cut]
pp = [p for p in zigzag_pivots(df.iloc[:cut].reset_index(drop=True), K_GRID[0])
if p[3] < cut]
ok = pf == pp
print(f" {a} {tf}: pivots full<cut={len(pf)} prefix={len(pp)} "
f"identici={'OK' if ok else 'FAIL'}")
cz = al.causality_ok(make_fib_target("1d", K_GRID[0], HORIZONS[1]), tf="1d")
print(f" target fade 1d (al.causality_ok): ok={cz['ok']} "
f"max_tail_diff={cz['max_tail_diff']}")
# ---------- [1] SCANSIONE tutti i config ----------
rng_a = np.random.default_rng(SEED)
PA_r, PA_e = make_placebo_a(rng_a)
PC_r, PC_e = make_placebo_c(np.random.default_rng(SEED + 7))
results = {}
print("\n[1] SWING E TOCCHI per config")
for ia, a in enumerate(ASSETS):
for itf, tf in enumerate(TFS):
df = al.get(a, tf)
n = len(df)
for ik, k in enumerate(K_GRID):
piv = zigzag_pivots(df, k)
segs = build_segments(piv, n)
rng_b = np.random.default_rng([SEED, ia, itf, ik])
PB_r, PB_e = make_placebo_b(rng_b, len(segs))
res = scan_config(df, segs, PA_r, PA_e, PB_r, PB_e, PC_r, PC_e)
results[(a, tf, k)] = res
nIS = int(res["main"]["acc"][(HORIZONS[0], "IS")]["c"][0])
nF = int(res["main"]["acc"][(HORIZONS[0], "FULL")]["c"][0])
print(f" {a} {tf} k={k:.0f}: bars={n} pivots={len(piv)} swings={len(segs)} "
f"tocchi_true IS={nIS} FULL={nF} zone_confluenti_true={int(res['czc'][0])}")
# ---------- [2] TABELLA PRINCIPALE per cella ----------
print("\n[2] REAZIONE AL LIVELLO vs PLACEBO — per cella (statistica: media react/ATR, IS)")
print(f" pctlA = vs {N_PA} set fissi uniformi; pctlB = vs {N_PB} per-swing; "
f"pctlC = vs {N_PC} location-matched (jitter attorno ai Fib);"
f" Bonferroni 16 celle -> single-cell sig ~>=0.997")
hdr = (f" {'asset':<5s} {'tf':<3s} {'k':>2s} {'H':>3s} {'nIS':>6s} {'nFULL':>6s} "
f"{'rawIS(bps)':>10s} {'lvlIS(bps)':>10s} {'normIS':>8s} {'plcAmed':>8s} "
f"{'plcCmed':>8s} {'pctlA_IS':>8s} {'pctlB_IS':>8s} {'pctlC_IS':>8s} "
f"{'pctlA_F':>8s} {'brkIS':>6s} {'brkPctl':>7s}")
print(hdr)
cells = {}
for (a, tf, k), res in results.items():
for H in HORIZONS:
st = _cell_stats(res["main"], H)
cells[(a, tf, k, H)] = st
i_, f_ = st["IS"], st["FULL"]
print(f" {a:<5s} {tf:<3s} {k:>2.0f} {H:>3d} {i_['n']:>6d} {f_['n']:>6d} "
f"{i_['raw_bps']:>10.1f} {i_['lvl_bps']:>10.1f} {i_['norm']:>8.3f} "
f"{i_['plcA_med']:>8.3f} {i_['plcC_med']:>8.3f} "
f"{i_['pctlA']:>8.2f} {i_['pctlB']:>8.2f} {i_['pctlC']:>8.2f} "
f"{f_['pctlA']:>8.2f} {st['brk_rate']:>6.2f} "
f"{st['brk_pctl_hold'] if st['brk_pctl_hold'] is not None else float('nan'):>7.2f}")
# ---------- [3] POOLED per (k,H) su asset x TF ----------
print("\n[3] POOLED (somma tocchi su 2 asset x 2 TF) — il test famiglia")
pooled_pass = {}
for k in K_GRID:
pooled = _pool_stores([results[(a, tf, k)]["main"] for a in ASSETS for tf in TFS])
for H in HORIZONS:
st = _cell_stats(pooled, H)
i_ = st["IS"]
cell_ps = [cells[(a, tf, k, H)]["IS"]["pctlA"] for a in ASSETS for tf in TFS
if cells[(a, tf, k, H)]["IS"]["n"] >= MIN_CELL_TOUCH]
min_cell = min(cell_ps) if cell_ps else None
ok = (i_["pctlA"] is not None and i_["pctlA"] >= 0.99
and i_["pctlB"] is not None and i_["pctlB"] >= 0.99
and i_["pctlC"] is not None and i_["pctlC"] >= 0.95
and min_cell is not None and min_cell >= 0.90)
pooled_pass[(k, H)] = ok
print(f" k={k:.0f} H={H:>2d}: nIS={i_['n']:>6d} normIS={i_['norm']:+.3f} "
f"(plcA med {i_['plcA_med']:+.3f} / plcC med {i_['plcC_med']:+.3f}) "
f"pctlA_IS={i_['pctlA']:.2f} pctlB_IS={i_['pctlB']:.2f} "
f"pctlC_IS={i_['pctlC']:.2f} pctlA_FULL={st['FULL']['pctlA']:.2f} "
f"min_cell_pctlA={min_cell if min_cell is not None else 'n/a'} "
f"PASS(A,B>=0.99 & C>=0.95 & cells>=0.90)={ok}")
# ---------- [4] CONFLUENZA vs livello singolo ----------
print("\n[4] CONFLUENZA (ret swing corrente x ext swing s-2 stesso verso, eps=0.25*ATR)")
print(" diff = norm(conf) - norm(single); pctl del diff vero vs placebo A")
conf_ok_cells = {}
for (a, tf, k), res in results.items():
for H in HORIZONS:
mc = _means(res["conf"], H, "IS")
ms = _means(res["sing"], H, "IS")
nc = int(res["conf"]["acc"][(H, "IS")]["c"][0])
ns = int(res["sing"]["acc"][(H, "IS")]["c"][0])
diff = mc - ms
p = _pctl(diff[0], diff[IDX_A])
czc_true = int(res["czc"][0])
czc_plc = float(np.mean(res["czc"][IDX_A]))
conf_ok_cells[(a, tf, k, H)] = (p is not None and p >= 0.95
and nc >= MIN_CONF_TOUCH)
d0 = f"{diff[0]:+.3f}" if np.isfinite(diff[0]) else " n/a"
print(f" {a} {tf} k={k:.0f} H={H:>2d}: zone true={czc_true} (plcA med~{czc_plc:.0f}) "
f"tocchiIS conf={nc} sing={ns} "
f"norm conf={mc[0] if np.isfinite(mc[0]) else float('nan'):+.3f} "
f"sing={ms[0] if np.isfinite(ms[0]) else float('nan'):+.3f} diff={d0} "
f"pctl_diff={p if p is not None else 'n/a'}")
conf_real = {}
for k in K_GRID:
for H in HORIZONS:
conf_real[(k, H)] = all(conf_ok_cells[(a, tf, k, H)] for a in ASSETS for tf in TFS)
print(" verdetto confluenza per (k,H) [tutte le 4 celle pctl>=0.95 e n>=30]: "
+ " ".join(f"k={k:.0f},H={H}:{'PASS' if v else 'FAIL'}"
for (k, H), v in conf_real.items()))
# ---------- [5] VERDETTO + eventuale strategizzazione ----------
fib_real = any(pooled_pass.values())
any_conf = any(conf_real.values())
print("\n[5] VERDETTO")
print(f" fib_real (pooled IS: A,B>=0.99, C>=0.95, tutte le celle >=0.90, "
f"per qualche k,H): {fib_real} -> {pooled_pass}")
print(f" confluenza > singolo livello (robusto 4/4 celle): {any_conf}")
if fib_real:
print("\n[6] STRATEGIA (il claim regge il placebo) — fade ai livelli, "
"study_family_honest + marginal")
grid = [dict(k_atr=k, H=H) for k in K_GRID for H in HORIZONS]
fam = al.study_family_honest("FIB-FADE", make_fib_target, grid, tfs=TFS)
ch = fam.get("chosen")
print(f" chosen(IS)={ch} n_cells={fam.get('n_cells')} "
f"DSR={fam.get('deflated_sharpe')} (null-max {fam.get('expected_null_max')}) "
f"dsr_pass={fam.get('dsr_pass')}")
print(f" earns_slot_marginal={fam.get('earns_slot_marginal')} "
f"EARNS_SLOT_HONEST={fam.get('earns_slot_honest')}")
if fam.get("marginal"):
print(al.fmt_marginal(fam["marginal"]))
else:
print("\n[6] STRATEGIA: SALTATA — la reazione ai livelli Fib NON batte i rapporti "
"casuali in modo robusto (regola a priori). Nessun edge da strategizzare.")
print("\nFine. Selezione: nessuna (rapporti dal claim, verdetto su pooled IS); "
"hold-out mai usato per scegliere.")
if __name__ == "__main__":
main()