research(wave-0822): lo scettico conferma il gradino di leva ma corregge due contorni — e la lente close-only e' cieca solo sulle regole a UN giorno
This commit is contained in:
@@ -638,8 +638,10 @@ def stats(daily: pd.Series) -> dict:
|
||||
cagr = float(eq.iloc[-1] ** (1 / yrs) - 1) if yrs > 0.5 and eq.iloc[-1] > 0 else float("nan")
|
||||
ho = r[r.index >= HOLDOUT]
|
||||
sho = float(ho.mean() / ho.std() * math.sqrt(365.25)) if len(ho) > 30 and ho.std() > 0 else float("nan")
|
||||
yrs_ = max(len(r) / 365.25, 1e-9)
|
||||
return dict(sharpe=sh, holdout=sho, maxdd=dd, cagr=cagr, n=len(r),
|
||||
vol=float(r.std() * math.sqrt(365.25)))
|
||||
vol=float(r.std() * math.sqrt(365.25)),
|
||||
se_ann=float(r.std() * math.sqrt(365.25) / math.sqrt(yrs_)))
|
||||
|
||||
|
||||
def delevering_null(cand: pd.Series, base: pd.Series) -> dict:
|
||||
@@ -737,7 +739,7 @@ def main() -> None:
|
||||
# ---------------- STEP 4: griglia ----------------
|
||||
print("\n[4] GRIGLIA — dichiarata PRIMA, contata AL RIALZO")
|
||||
FAM = {"CAL": ["static_long", "static_short", "zrev", "zmom"],
|
||||
"FvP": ["carry", "static_long", "zrev", "zmom"]}
|
||||
"FvP": ["carry", "static_long", "static_short", "zrev", "zmom"]}
|
||||
WINS = [720, 2160, 4320] # 30 / 90 / 180 giorni in ore
|
||||
ROLLS = [7, 21]
|
||||
THRS = [0.0, 1.0]
|
||||
@@ -794,7 +796,7 @@ def main() -> None:
|
||||
print("\n LE CELLE CANONICHE (quelle che un lettore chiede per nome), roll=7g:")
|
||||
for fam, sig, lab in [
|
||||
("FvP", "carry", "vendi il datato quando e' in CONTANGO / compra perp (il carry classico)"),
|
||||
("FvP", "static_short", "SEMPRE corto il datato / lungo perp"),
|
||||
("FvP", "static_short", "SEMPRE corto il datato / lungo perp = LA RACCOLTA DEL CONTANGO"),
|
||||
("FvP", "static_long", "SEMPRE lungo il datato / corto perp (il contrario)"),
|
||||
("CAL", "static_long", "calendar: lungo back / corto front"),
|
||||
("CAL", "static_short", "calendar: corto back / lungo front")]:
|
||||
@@ -802,8 +804,8 @@ def main() -> None:
|
||||
if len(sub):
|
||||
r = G.loc[sub.index[0]]
|
||||
print(f" {fam}/{sig:12s} Sh {r.sharpe:+6.2f} hold {r.holdout:+6.2f} "
|
||||
f"CAGR {r.cagr*100:+6.2f}% DD {r.maxdd*100:5.2f}% vol {r.vol*100:5.2f}%"
|
||||
f" <- {lab}")
|
||||
f"CAGR {r.cagr*100:+6.2f}% (+-{r.se_ann*100:.2f} SE) DD {r.maxdd*100:5.2f}% "
|
||||
f"vol {r.vol*100:5.2f}% <- {lab}")
|
||||
|
||||
print("\n migliori 8 celle per Sharpe FULL (NON e' la selezione — solo panorama):")
|
||||
for i in G.sharpe.nlargest(8).index:
|
||||
@@ -826,7 +828,11 @@ def main() -> None:
|
||||
print(f" cella scelta AL BUIO: {b.family} {b.sig} W={int(b.win)}h roll={int(b.roll)}g thr={b.thr}")
|
||||
print(f" Sharpe in-sample {ins[best_i]:+.2f} -> FULL {bs['sharpe']:+.2f} "
|
||||
f"HOLD-OUT {bs['holdout']:+.2f} maxDD {bs['maxdd']*100:.2f}% "
|
||||
f"CAGR {bs['cagr']*100:+.2f}% vol {bs['vol']*100:.2f}%")
|
||||
f"CAGR {bs['cagr']*100:+.2f}% (+-{bs['se_ann']*100:.2f} SE annuo) "
|
||||
f"vol {bs['vol']*100:.2f}%")
|
||||
print(f" NB con {bs['n']/365.25:.1f} anni e vol {bs['vol']*100:.1f}% l'errore standard del "
|
||||
f"rendimento annuo e' {bs['se_ann']*100:.2f}pp: sotto ~{2*bs['se_ann']*100:.1f}pp/anno "
|
||||
f"NIENTE e' distinguibile da zero, ne' in un verso ne' nell'altro.")
|
||||
best_full = G.sharpe.idxmax()
|
||||
print(f" (per confronto, la cella col miglior FULL sarebbe stata "
|
||||
f"{G.loc[best_full,'family']} {G.loc[best_full,'sig']} "
|
||||
|
||||
@@ -271,6 +271,15 @@ def main() -> None:
|
||||
("V1", EXV1, FUN_V1, BK1, CT1)):
|
||||
lg = legs(EXX, FUN)
|
||||
base = lg["BASE"]
|
||||
if tag == "V2":
|
||||
# controlli di §1(c): L calcolata sulla gamba COMBINATA (cio' che fa il ramo
|
||||
# NAIVE del bersaglio), applicata nei DUE modi. Se coincidono, il "fantasma"
|
||||
# non e' un problema di QUANDO si applica la size.
|
||||
for w in (30, 90):
|
||||
Lc = V.leverage_series(base, **dict(FROZEN, w=w))
|
||||
lg[f"NAIVEcomb w{w}"] = base * Lc.reindex(base.index).fillna(1.0)
|
||||
lg[f"COMBingr w{w}"] = V.leg_daily(
|
||||
EXX, {a: V.sizes_from_L(EXX[a], Lc) for a in ASSETS})
|
||||
for nm, s in lg.items():
|
||||
b, c, m_s = iso_pair(TP, s, base)
|
||||
BKd.setdefault(nm, {})[o] = b
|
||||
@@ -386,32 +395,29 @@ def main() -> None:
|
||||
print(f" {a}: max|dif| = {np.max(np.abs(j['a']-j['b'])):.2e} "
|
||||
f"Sh ingresso {sh(s_in):.4f} vs Sh moltiplicazione {sh(s_mul):.4f}")
|
||||
|
||||
print("\n (c) ALLORA COS'E' IL '+0,04 FANTASMA'? Il controllo NAIVE del bersaglio calcola L")
|
||||
print(" sulla gamba 50/50 COMBINATA; la versione 'causale' la calcola PER ASSET.")
|
||||
print(" Terza variante di controllo: L combinata, applicata come size all'INGRESSO.")
|
||||
lg_base = {o: None for o in offs}
|
||||
d_comb, d_perass = [], []
|
||||
for o in offs[: min(6, len(offs))]:
|
||||
E2 = {a: V.extract(a, o) for a in ASSETS}
|
||||
base = V.leg_daily(E2, {a: V.size_flat(E2[a]) for a in ASSETS})
|
||||
Lc = V.leverage_series(base, **FROZEN)
|
||||
naive = base * Lc.reindex(base.index).fillna(1.0)
|
||||
comb_in = V.leg_daily(E2, {a: V.sizes_from_L(E2[a], Lc) for a in ASSETS})
|
||||
per_ass = V.leg_daily(
|
||||
E2, {a: V.sizes_from_L(E2[a], V.leverage_series(E2[a]["base_daily"], **FROZEN))
|
||||
for a in ASSETS})
|
||||
j = pd.concat({"a": naive, "b": comb_in}, axis=1, join="inner").dropna()
|
||||
d_comb.append(float(np.max(np.abs(j["a"] - j["b"]))))
|
||||
b1, c1, _ = iso_pair(TP, naive, base)
|
||||
b2, c2, _ = iso_pair(TP, per_ass, base)
|
||||
d_perass.append(sh(hold(b1)) - sh(hold(c1)) - (sh(hold(b2)) - sh(hold(c2))))
|
||||
del E2
|
||||
print(f" L combinata: 'moltiplica la serie' vs 'size all'ingresso' -> "
|
||||
f"max|dif| mediano {np.median(d_comb):.2e} (identiche)")
|
||||
print(f" differenza ISO dShHOLD [L combinata] - [L per asset], mediana appaiata su "
|
||||
f"{len(d_perass)} ancore: {np.median(d_perass):+.3f}")
|
||||
print(" -> il divario NAIVE/CAUSALE non e' QUANDO si applica la size, e' SU QUALE")
|
||||
print(" SERIE si calcola L (gamba combinata vs per asset).")
|
||||
print("\n (c) ALLORA COS'E' IL '+0,04 FANTASMA'? Il ramo NAIVE del bersaglio calcola L")
|
||||
print(" sulla gamba 50/50 COMBINATA e MOLTIPLICA la serie; il ramo 'causale' calcola L")
|
||||
print(" PER ASSET e la applica all'INGRESSO. Sono DUE differenze in una. Le separo:")
|
||||
for w in (30, 90):
|
||||
d = []
|
||||
for o in offs:
|
||||
j = pd.concat({"a": BK2[f"NAIVEcomb w{w}"][o], "b": BK2[f"COMBingr w{w}"][o]},
|
||||
axis=1, join="inner").dropna()
|
||||
d.append(float(np.max(np.abs(j["a"] - j["b"]))))
|
||||
print(f" w{w}: [L combinata, MOLTIPLICA] vs [L combinata, size all'INGRESSO] -> "
|
||||
f"max|dif| mediano {np.median(d):.2e} = IDENTICHE")
|
||||
print(f"\n {'coppia':<38}{'dShFULL':>10}{'dShHOLD':>10} (delta GREZZI vs baseline nudo,")
|
||||
print(f" {'':<38}{'':>10}{'':>10} la convenzione del §4 del bersaglio)")
|
||||
for w in (30, 90):
|
||||
for nm in (f"NAIVEcomb w{w}", f"COMBingr w{w}",
|
||||
(lead if w == 90 else f"VTL tv20 w{w}")):
|
||||
gF = A.anchor_luck_delta(lambda o, n=nm: BK2[n][o], lambda o: BK2["BASE"][o], offs,
|
||||
metric=sh)
|
||||
gH = A.anchor_luck_delta(lambda o, n=nm: hold(BK2[n][o]),
|
||||
lambda o: hold(BK2["BASE"][o]), offs, metric=sh)
|
||||
print(f" {nm:<38}{gF['median_paired']:>+10.3f}{gH['median_paired']:>+10.3f}")
|
||||
print(" -> il 'fantasma' e' interamente [L combinata] - [L per asset]: una scelta di")
|
||||
print(" SERIE DI RIFERIMENTO, non un look-ahead. Il ramo NAIVE non e' non-causale.")
|
||||
|
||||
# ------------------------------------------------------------------ §2 TEST PRE-REGISTRATO
|
||||
print("\n" + "-" * L)
|
||||
@@ -560,7 +566,34 @@ def main() -> None:
|
||||
|
||||
# ------------------------------------------------------------------ §5 sintesi
|
||||
print("\n" + "-" * L)
|
||||
print(" 5. SINTESI DEI NUMERI CHE CONTANO")
|
||||
print(" 5. GATE (per il contratto di consegna)")
|
||||
print("-" * L)
|
||||
can = offs[0]
|
||||
legs_can = {}
|
||||
for nm in ("BASE", lead, "BIN L>=med (0/1)"):
|
||||
legs_can[nm] = (V.leg_daily(EXV2, {a: V.size_flat(EXV2[a]) for a in ASSETS})
|
||||
if nm == "BASE"
|
||||
else V.leg_daily(EXV2, {a: FUN_V2[nm](EXV2[a]) for a in ASSETS}))
|
||||
nlos = int((EXV2["BTC"]["net"] < 0).sum() + (EXV2["ETH"]["net"] < 0).sum())
|
||||
ntot = int(len(EXV2["BTC"]["net"]) + len(EXV2["ETH"]["net"]))
|
||||
for nm, s_ in legs_can.items():
|
||||
m = A.marginal_vs_tp01(s_)
|
||||
imp = A.implausible_sharpe(s_, n_trades=ntot, n_losing_trades=nlos)
|
||||
print(f" {nm:<22} marginal {str(m.get('marginal_verdict')):<9} corr {m.get('corr_full')}"
|
||||
f" implausible={imp['implausible']} (perdite/attive "
|
||||
f"{imp.get('loss_frac', 0)*100:.1f}%)")
|
||||
trials = [float(np.median([sh(BK2[n][o]) for o in offs])) for n in BK2]
|
||||
dsr_c, sr0 = A.deflated_sharpe(float(np.median([sh(BK2[lead][o]) for o in offs])), trials,
|
||||
BK2[lead][can])
|
||||
dsr_b, _ = A.deflated_sharpe(med_F, trials, BK2["BASE"][can])
|
||||
print(f" deflated-Sharpe: candidato {dsr_c:.3f} / BASELINE {dsr_b:.3f} su {len(trials)} "
|
||||
f"trial (sd {np.std(trials, ddof=1):.4f}) -> VACUO, lo passa anche il baseline")
|
||||
print(" null de-levering: DEGENERE per costruzione (sh(k*base) == sh(base)); il suo")
|
||||
print(" sostituto e' il confronto ISO-PESO usato in tutte le tabelle sopra.")
|
||||
print(" causalita': §1(a) 50/50 size ricalcolate da dati troncati, max|dif| 0.00e+00.")
|
||||
|
||||
print("\n" + "-" * L)
|
||||
print(" 6. SINTESI DEI NUMERI CHE CONTANO")
|
||||
print("-" * L)
|
||||
print(f" fortuna d'ancora del LIBRO BASE: ShFULL canonico "
|
||||
f"{sh(BK2['BASE'][0]) if 0 in BK2['BASE'] else float('nan'):.3f} vs mediana "
|
||||
@@ -569,7 +602,10 @@ def main() -> None:
|
||||
f"{dLEADH['median_paired']:+.3f} HOLD")
|
||||
print(f" riprodotto dal FILTRO BINARIO: {RES['BIN L>=1 (0/1)']['dF']:+.3f} FULL / "
|
||||
f"{RES['BIN L>=1 (0/1)']['dH']:+.3f} HOLD")
|
||||
print(f" controllo di segno (INVERSO): {RES['BIN L<1 (INVERSO)']['dF']:+.3f} FULL")
|
||||
print(f" riprodotto dal BINARIO alla MEDIANA:{RES['BIN L>=med (0/1)']['dF']:+.3f} FULL / "
|
||||
f"{RES['BIN L>=med (0/1)']['dH']:+.3f} HOLD ({RES['BIN L>=med (0/1)']['nF']}/{len(offs)} ancore)")
|
||||
print(f" controllo di segno (INVERSO): {RES['BIN L<1 (INVERSO)']['dF']:+.3f} FULL "
|
||||
f"({RES['BIN L<1 (INVERSO)']['nF']}/{len(offs)}) -> il filtro e' reale, non un artefatto")
|
||||
print(f"\n fatto in {time.time()-t0:.0f}s")
|
||||
print("=" * L)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user