research(wave-0822): GROWTH-POLICY — il libro gira al 7% di Kelly, e ogni gate del progetto e' cieco alla scala

This commit is contained in:
Adriano Dal Pastro
2026-08-22 17:10:03 +00:00
parent 3b206c8e6d
commit 14c1a75a7e
8 changed files with 275 additions and 89 deletions
+38 -13
View File
@@ -282,6 +282,17 @@ def leverage_series(base: pd.Series, tv: float | None, w: int, cap: float,
return L.fillna(1.0)
def activity_L(base: pd.Series, w: int, cap: float) -> pd.Series:
"""L_t da sola ATTIVITA': quante barre ATTIVE (trade chiusi) negli ultimi w giorni, riferita
alla mediana espandente causale. Serve a separare i DUE canali dentro `rv` calcolata su tutte
le barre di uno sleeve all'88% di zeri: la MAGNITUDINE degli esiti e la FREQUENZA dei trade.
Se il guadagno vive qui, la variante non e' 'vol-target' ma un filtro di frequenza."""
act = (base != 0.0).astype(float)
cnt = act.rolling(w, min_periods=max(5, w // 3)).sum().shift(1).replace(0.0, np.nan)
ref = cnt.expanding(min_periods=VT_WARM).median()
return (ref / cnt).clip(lower=1.0 / cap, upper=cap).fillna(1.0)
def sizes_from_L(ex: dict, L: pd.Series) -> np.ndarray:
"""La size di ogni trade = L al giorno d'INGRESSO. E' la versione CAUSALE del vol-target su
uno sleeve a equity a gradino: L al giorno di CHIUSURA non era nota all'ingresso."""
@@ -389,11 +400,12 @@ def main() -> None:
for w in (30, 90, 180):
for cp in (2.0, 3.0):
BVT[f"BOOKVT w{w} cap{cp:.0f}"] = dict(tv=0.10, w=w, cap=cp)
ACT = {f"ACT w{w}": dict(w=w, cap=3.0) for w in (90, 180)}
NAIVE = {f"NAIVE-VT w{w}": dict(tv=0.20, w=w, cap=3.0, active_only=False) for w in (30, 90)}
ALL = list(SZ) + list(VTL) + list(BVT) + list(NAIVE)
ALL = list(SZ) + list(VTL) + list(ACT) + list(BVT) + list(NAIVE)
print(f"\n GRIGLIA DICHIARATA: {len(SZ)} size per-trade + {len(VTL)} vol-target di gamba + "
f"{len(BVT)} vol-target di libro + {len(NAIVE)} controlli non-causali = {len(ALL)} celle "
f"(+ baseline).")
f"{len(ACT)} filtro-attivita' + {len(BVT)} vol-target di libro + {len(NAIVE)} controlli "
f"non-causali = {len(ALL)} celle (+ baseline).")
# ------------------------------------------------------------------ serie
TP = A.tp01_baseline_daily()
@@ -420,6 +432,16 @@ def main() -> None:
MTP[nm][o] = 1.0
if o == offs[0]:
SIZEMED[nm] = float(np.median(np.concatenate(list(sz.values()))))
for nm, sp in ACT.items():
SLE[nm], BK[nm], MTP[nm] = {}, {}, {}
for o in offs:
sz = {a: sizes_from_L(EX[o][a], activity_L(EX[o][a]["base_daily"], **sp))
for a in ASSETS}
SLE[nm][o] = leg_daily(EX[o], sz)
BK[nm][o] = book(TP, SLE[nm][o])
MTP[nm][o] = 1.0
if o == offs[0]:
SIZEMED[nm] = float(np.median(np.concatenate(list(sz.values()))))
for nm, sp in BVT.items():
SLE[nm], BK[nm], MTP[nm] = {}, {}, {}
for o in offs:
@@ -467,7 +489,7 @@ def main() -> None:
f"{maxdd(s)*100:>8.1f}%{maxdd(iv)*100:>9.1f}%{cagr(iv)*100:>8.1f}%{m}")
prow("BASELINE (size fissa)", base_s, 1.00)
for nm in list(SZ) + list(VTL) + list(NAIVE):
for nm in list(SZ) + list(VTL) + list(ACT) + list(NAIVE):
prow(nm, SLE[nm][can], SIZEMED.get(nm))
# ------------------------------------------------------------------ §3 libro, banda appaiata
@@ -592,7 +614,7 @@ def main() -> None:
print("\n" + "-" * 112)
print(" 7. GATE — marginal_vs_tp01 / implausible_sharpe / weights_tilt_null")
print("-" * 112)
best_sleeve = max([n for n in list(SZ) + list(VTL)], key=lambda x: is_med[x])
best_sleeve = max([n for n in list(SZ) + list(VTL) + list(ACT)], key=lambda x: is_med[x])
for nm in ("BASE", best_sleeve):
m = A.marginal_vs_tp01(SLE[nm][can])
b25 = m.get("blends", {}).get("w25", {})
@@ -606,7 +628,7 @@ def main() -> None:
f"attive {imp.get('active_frac', 0)*100:.1f}% perdite/attive "
f"{imp.get('loss_frac', 0)*100:.1f}% Calmar {imp.get('calmar', 0):.1f}")
cols = {"TP01": TP, "SKH01": SLE["BASE"][can]}
for nm in [best, best_sleeve] + list(BVT)[:2]:
for nm in dict.fromkeys([best, best_sleeve] + list(BVT)[:2]):
m_s = vol(SLE[nm][can]) / vol(SLE["BASE"][can])
wp = {"TP01": W_TP * MTP[nm][can], "SKH01": W_SKH * m_s}
g = weights_tilt_null(cols, {"TP01": W_TP, "SKH01": W_SKH}, wp,
@@ -630,7 +652,7 @@ def main() -> None:
no = ns = 0
for a in ASSETS:
df1h = TO.get1h(a)
tgt = netted_target(a, EX[can][a], nm, SZ, VTL, BVT, SLE, TP, can, df1h)
tgt = netted_target(a, EX[can][a], nm, SZ, VTL, ACT, BVT, SLE, TP, can, df1h)
ev = A.eval_weights_smallcap(df1h, tgt, capital=CAPITAL, min_order=MIN_ORDER)
tm += 0.5 * ev["modeled"]["sharpe"]
tr += 0.5 * ev["realistic"]["sharpe"]
@@ -647,8 +669,8 @@ def main() -> None:
print(" un contributo positivo si scompone per ANNO prima di crederci) + deriva della size")
print("-" * 112)
yrs = sorted({int(y) for y in BK["BASE"][can].index.year})
top = [best, best_iso] + [max(BVT, key=lambda x: RES[x]["iF"])]
top = list(dict.fromkeys(top))
top = list(dict.fromkeys([best, best_iso, max(ACT, key=lambda x: RES[x]["iF"]),
max(BVT, key=lambda x: RES[x]["iF"])]))
print(f" {'variante':<28}" + "".join(f"{y:>8}" for y in yrs))
for nm in top:
cells = []
@@ -668,11 +690,12 @@ def main() -> None:
if nm in SZ:
szs = np.concatenate([SZ[nm](EX[can][a]) for a in ASSETS])
dys = pd.DatetimeIndex(np.concatenate([EX[can][a]["day_ent"].values for a in ASSETS]))
elif nm in VTL:
elif nm in VTL or nm in ACT:
mk = ((lambda b: leverage_series(b, **VTL[nm])) if nm in VTL
else (lambda b: activity_L(b, **ACT[nm])))
szs, dys = [], []
for a in ASSETS:
szs.append(sizes_from_L(EX[can][a],
leverage_series(EX[can][a]["base_daily"], **VTL[nm])))
szs.append(sizes_from_L(EX[can][a], mk(EX[can][a]["base_daily"])))
dys.append(EX[can][a]["day_ent"].values)
szs = np.concatenate(szs); dys = pd.DatetimeIndex(np.concatenate(dys))
else:
@@ -697,7 +720,7 @@ def main() -> None:
print("=" * 112)
def netted_target(asset: str, ex: dict, nm, SZ, VTL, BVT, SLE, TP, can, df1h) -> np.ndarray:
def netted_target(asset: str, ex: dict, nm, SZ, VTL, ACT, BVT, SLE, TP, can, df1h) -> np.ndarray:
"""Peso NETTO per asset sul grid 1h: 0.75*target TP01 + 0.25*posizione SKH (dir*size), poi
l'eventuale leva di libro sulla gamba TP01. E' cio' che il cron manda a Deribit come UNA
posizione netta sullo stesso strumento."""
@@ -711,6 +734,8 @@ def netted_target(asset: str, ex: dict, nm, SZ, VTL, BVT, SLE, TP, can, df1h) ->
sizes = SZ[nm](ex)
elif nm in VTL:
sizes = sizes_from_L(ex, leverage_series(ex["base_daily"], **VTL[nm]))
elif nm in ACT:
sizes = sizes_from_L(ex, activity_L(ex["base_daily"], **ACT[nm]))
else: # BOOKVT
L = leverage_series(book(TP, SLE["BASE"][can]), BVT[nm]["tv"], BVT[nm]["w"], BVT[nm]["cap"])
sizes = sizes_from_L(ex, L)