research(wave-0822): versione finale di r0822_term_structure (sezione 7 + tre fix dell'agente)
This commit is contained in:
@@ -96,6 +96,10 @@ from r0822_sol_leg import k_iso_dd # noqa: E402 (null de-lev
|
||||
from src.portfolio.portfolio import ( # noqa: E402
|
||||
HOLDOUT, combine_outer, weights_tilt_null,
|
||||
)
|
||||
from src.backtest.harness import backtest_signals # noqa: E402
|
||||
from src.strategies.skyhook import ( # noqa: E402
|
||||
SKH01_V1, SKH01_V2_DD, skyhook_entries,
|
||||
)
|
||||
|
||||
ASSETS = ("BTC", "ETH")
|
||||
OFFSETS_FULL = tuple(range(0, 690, 30)) # 23 a priori: griglia dell'audit 02/07
|
||||
@@ -149,12 +153,18 @@ def ins(s: pd.Series) -> pd.Series:
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- estrazione trade
|
||||
def extract(asset: str, off: int) -> dict:
|
||||
def extract(asset: str, off: int, params=None) -> dict:
|
||||
"""Trade + contesto per (asset, offset). Replica il loop di `backtest_signals` con
|
||||
leverage=1: stessa detection, stesso non-overlap, stessi fill al livello. Cio' che
|
||||
aggiunge e' la SIZE per-trade, che l'harness non espone (ha un solo `position_size`
|
||||
scalare). La §0 verifica bit-exact che con size=1 si ritrovi la serie ufficiale."""
|
||||
_, _, ltf, ent = R.run_asset(asset, off)
|
||||
if params is None or params is SKH01_V2_DD:
|
||||
_, _, ltf, ent = R.run_asset(asset, off) # path canonico (sanity bit-exact §0)
|
||||
else: # stessa pipeline, altra config Skyhook
|
||||
df5 = R.get5m(asset)
|
||||
ltf = R.resample_off(df5, R.LTF_MIN, off)
|
||||
htf = R.resample_off(df5, R.HTF_MIN, off)
|
||||
ent = skyhook_entries(ltf, htf, params)
|
||||
c = ltf["close"].values.astype(float)
|
||||
h = ltf["high"].values.astype(float)
|
||||
lo = ltf["low"].values.astype(float)
|
||||
@@ -349,10 +359,63 @@ HEAD = (f" {'variante':<28}{'ShFULL':>8}{'ShHOLD':>9}{'ShIS':>8}"
|
||||
f"{'vol':>9}{'maxDD':>9}{'DDisovol':>10}{'CAGRiso':>9}{'size med':>10}")
|
||||
|
||||
|
||||
def transfer_test(offs) -> None:
|
||||
"""TEST DI TRASFERIMENTO — la regola di size CONGELATA (tv=0.20, w=90, cap 3, fissata
|
||||
all'INGRESSO) applicata a SKH01_**V1**, l'altra config Skyhook: stesso motore, stessi asset,
|
||||
ma geometria d'uscita DIVERSA (stop/target in ATR 2.5/6.0 invece che a percentuale fissa
|
||||
4%/2%, ptn_n=55 invece di 45). E' il gate giusto per questo filone: un forward di 6 mesi non
|
||||
puo' misurare un dSharpe di 0.07 (SE ~1.4), un trasferimento si'. Se il meccanismo e'
|
||||
'pareggiare il contributo di rischio dei trade' deve funzionare anche li'; se e' cucito sulla
|
||||
sequenza di trade di V2-DD, no. NB: V1 e' l'unica config alternativa gia' nel repo e non
|
||||
scelta da me — non e' una griglia nuova."""
|
||||
print("=" * 112)
|
||||
print(" TEST DI TRASFERIMENTO — regola di size CONGELATA da V2-DD, applicata a SKH01_V1")
|
||||
print("=" * 112)
|
||||
TP = A.tp01_baseline_daily()
|
||||
sp = dict(tv=0.20, w=90, cap=3.0, active_only=False)
|
||||
base_b, var_b, ctrl_b = {}, {}, {}
|
||||
for o in offs:
|
||||
ex = {a: extract(a, o, SKH01_V1) for a in ASSETS}
|
||||
lb = leg_daily(ex, {a: size_flat(ex[a]) for a in ASSETS})
|
||||
lv = leg_daily(ex, {a: sizes_from_L(ex[a], leverage_series(ex[a]["base_daily"], **sp))
|
||||
for a in ASSETS})
|
||||
m_s = vol(lv) / vol(lb) if vol(lb) > 0 else 1.0
|
||||
base_b[o], var_b[o] = book(TP, lb), book(TP, lv)
|
||||
ctrl_b[o] = book(TP, lb * m_s)
|
||||
dF = A.anchor_luck_delta(lambda o: var_b[o], lambda o: ctrl_b[o], offs, metric=sh)
|
||||
dH = A.anchor_luck_delta(lambda o: hold(var_b[o]), lambda o: hold(ctrl_b[o]), offs, metric=sh)
|
||||
rF = A.anchor_luck_delta(lambda o: var_b[o], lambda o: base_b[o], offs, metric=sh)
|
||||
print(f" trade V1 medi/ancora: " + ", ".join(
|
||||
f"{a} {int(np.mean([len(extract(a, offs[0], SKH01_V1)['i_ent'])]))}" for a in ASSETS))
|
||||
print(f" libro V1 BASE, mediana della banda: ShFULL "
|
||||
f"{np.median([sh(base_b[o]) for o in offs]):.3f} / ShHOLD "
|
||||
f"{np.median([sh(hold(base_b[o])) for o in offs]):.3f}")
|
||||
print(f" dShFULL grezzo {rF['median_paired']:+.3f} ({rF['n_positive']}/{len(offs)} ancore)")
|
||||
print(f" dShFULL ISO-PESO {dF['median_paired']:+.3f} ({dF['n_positive']}/{len(offs)} ancore)"
|
||||
f" banda [{dF['lo']:+.3f}, {dF['hi']:+.3f}]")
|
||||
print(f" dShHOLD ISO-PESO {dH['median_paired']:+.3f} ({dH['n_positive']}/{len(offs)} ancore)")
|
||||
yrs = sorted({int(y) for y in base_b[offs[0]].index.year})
|
||||
cells = []
|
||||
for y in yrs:
|
||||
v = [sh(var_b[o][var_b[o].index.year == y]) - sh(ctrl_b[o][ctrl_b[o].index.year == y])
|
||||
for o in offs if (var_b[o].index.year == y).sum() > 20]
|
||||
cells.append(float(np.median(v)) if v else float("nan"))
|
||||
print(" per anno (ISO): " + "".join(f"{y:>8}" for y in yrs))
|
||||
print(" " + "".join(f"{c:>+8.2f}" for c in cells) +
|
||||
f" positivi {int(np.nansum(np.asarray(cells) > 0))}/{len(yrs)}")
|
||||
print("\n LETTURA: se il segno regge qui, il meccanismo non e' cucito su V2-DD. Se non regge,")
|
||||
print(" la regola e' una proprieta' della sequenza di trade su cui e' stata trovata.")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--every", type=int, default=1)
|
||||
ap.add_argument("--transfer", action="store_true",
|
||||
help="esegue SOLO il test di trasferimento su SKH01_V1")
|
||||
args = ap.parse_args()
|
||||
if args.transfer:
|
||||
transfer_test(OFFSETS_FULL[:: max(1, args.every)])
|
||||
return
|
||||
offs = OFFSETS_FULL[:: max(1, args.every)]
|
||||
t0 = time.time()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user