research(frontier): frontiera onesta del book — il 6% e' un miraggio di regime, gira gia' alla vol nativa
Misura (non ricerca segnali) della frontiera CAGR/DD/P-rovina vs target-vol, pesata per confidenza per-sleeve, a 2k/5k. 7 agenti (3 filoni + refuter fat-tail + scettico), sanity bit-exact dei due book. Reframe decisivo: il "6% attuale" NON e' un target, e' la vol realizzata in risk-off; a lambda=1 il book LIVE 2-sleeve gira gia' a ~11% vol/9.4% DD -> "saltare a 10%" e' assumere non-risk-off, non aggiungere leva. - Target-vol fidato book LIVE (TP01+SKH01, unico eseguibile <20k): ~10% (banda 8-11%, lambda ~0.9). Scale-invariante: stesso % a 2k/5k. - P(rovina-50%) e' la metrica SBAGLIATA (slack a 22%); il vincolo che morde e' P(DD>30%), super-lineare. Muro onesto ~12% (gap-through SKH reale non nei rendimenti modellati; 2-sleeve 4x crash-sensibile). - Vol differenziata-per-confidenza REFUTATA: taglia la diversificazione; la bassa fiducia va nel DE-MEAN (haircut media), non nella leva. - Reward onesto (HOLD<<FULL): ~EUR 0.3-0.7/g@2k, 0.8-1.8/g@5k; warm-up vs 6% risk-off = ~+EUR 0.2/g@2k. EUR 50/g resta ~130k (capitale+tempo). - UNICA azione config (proposta, NON applicata): cap $300 -> equity/2, altrimenti a 5k il cap raffredda paradossalmente il book sotto il punto fidato (6% vol ceiling). config/live.json NON toccato. Book invariato. 168 test verdi. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,564 @@
|
||||
"""FILONE 3 (2026-07-03) — FRONTIERA ESEGUIBILE a 2k/5k + STAGING KELLY.
|
||||
|
||||
NON e' ricerca segnali (il soffitto direzionale ~1.3 e' saturo): rende OPERATIVA la frontiera
|
||||
rischio/rendimento dei DUE book che GIA' esistono, per decidere a quale TARGET-VOL girarli a
|
||||
capitale piccolo. Riusa r0702_capital_scaling.py (granularita' reale) e la macchina bootstrap dei
|
||||
filoni 1/2. NON tocca src/config/live/tests: importa solo (sola lettura) i builder.
|
||||
|
||||
(A) FRONTIERA ESEGUIBILE del BOOK LIVE (TP01+SKH01 75/25) a capitale {2000,3500,5000}:
|
||||
il target notional per-asset del book e' net = clamp(0.5*E*(0.75*max(tp,0)+0.25*skh), +-cap)
|
||||
(formula ESATTA di src/live/book.book_net_target). La LEVA studiata = moltiplicatore lambda
|
||||
su quel target (target-vol = lambda*vol_nativa). A ogni (capitale, lambda):
|
||||
- il CAP degenera dall'ALTO: quanto della vol richiesta il cap $300 lascia esprimere, e
|
||||
quanta ne sblocca il cap = equity/2 -> "cap ceiling" sul target-vol raggiungibile;
|
||||
- il MIN-ORDER degenera dal BASSO: granularita' reale (step BTC 0.0001~$7, ETH 0.001~$2,
|
||||
floor config $5) -> il target-vol sotto cui la posizione/ribilancio non si esegue;
|
||||
- il DD in EURO (non solo %): -20% su 2k = -EUR 400 -> tabella DD-in-euro a ogni lambda.
|
||||
(B) STAGING KELLY: target-vol growth-optimal (full / half / quarter-Kelly) del book FIDATO,
|
||||
con le CODE ONESTE del filone 2 (block-bootstrap fat-tail, NON Gaussiano). Confronto col
|
||||
posizionamento attuale (~6% vol) -> di quanti punti di vol e' sotto l'ottimo di crescita?
|
||||
Tesi: 'a capitale piccolo con obiettivo di crescita i 6% sono iper-prudenti; l'ottimo
|
||||
Kelly-frazionario e' piu' caldo' -> confermata o refutata coi numeri (E col vincolo di rovina).
|
||||
(C) AGGRESSIVITA' PER-SLEEVE ~ CONFIDENZA: correre TP01 (validato) piu' caldo di VRP (modellato)?
|
||||
Book a target-vol DIFFERENZIATO per sleeve (moltiplicatore di esposizione ~ fiducia
|
||||
sull'EDGE) vs uniforme, a book-vol PARI -> il differenziato domina l'uniforme sulla
|
||||
frontiera fidata? (Sharpe/DD/P-rovina.)
|
||||
|
||||
Confidenza per-sleeve (edge, NON vol): TP01 piena, GTAA alta, SKH media, VRP/XS bassa.
|
||||
Tre livelli di frontiera (la BANDA e' il messaggio): FULL / DE-LUCK (anchor-audit 4/4) /
|
||||
HAIRCUT (de-luck + taglio -50% della MEDIA di VRP/XS = frontiera FIDATA).
|
||||
|
||||
uv run python scripts/research/r0703_frontier_exec.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import sys, os, math, time
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
ROOT = "/opt/docker/PythagorasGoal"
|
||||
sys.path.insert(0, ROOT)
|
||||
sys.path.insert(0, ROOT + "/scripts/research/alt")
|
||||
sys.path.insert(0, ROOT + "/scripts/research")
|
||||
|
||||
import altlib as al # noqa: E402
|
||||
from src.portfolio.sleeves import (_tp01_returns, _skyhook_returns, _xsec_returns, # noqa: E402
|
||||
_vrp_combo_returns, _gtaa_daily_returns)
|
||||
from src.portfolio.portfolio import combine_outer, to_daily, HOLDOUT # noqa: E402
|
||||
# riuso della macchina di scala (granularita' reale) del filone capital-scaling
|
||||
from r0702_capital_scaling import skh_sign_series, tp_frac_on_ltf, book_sim, TP # noqa: E402
|
||||
|
||||
DPY = 365.25
|
||||
FEE_SIDE = al.FEE_SIDE
|
||||
WEIGHT, W_TP01, W_SKH = 0.5, 0.75, 0.25 # src/live/book.py + shadow.WEIGHT
|
||||
ASSETS = ("BTC", "ETH")
|
||||
CAPS = (2000.0, 3500.0, 5000.0)
|
||||
CAP_NOW = 300.0
|
||||
MIN_ORDER = 5.0
|
||||
TVOLS = [0.05, 0.06, 0.08, 0.10, 0.125, 0.15, 0.20, 0.25, 0.30]
|
||||
OUT = "/tmp/claude-1001/-opt-docker-PythagorasGoal/e00896d3-d4bb-4f2a-b471-55a1d88a12ba/scratchpad"
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
SEED = 20260703
|
||||
RNG = np.random.default_rng(SEED)
|
||||
|
||||
REP = []
|
||||
def P(*a):
|
||||
s = " ".join(str(x) for x in a); print(s, flush=True); REP.append(s)
|
||||
|
||||
|
||||
# ============================================================ metriche base
|
||||
def _r(s): return np.asarray(pd.Series(s).dropna().values, float)
|
||||
def ann_vol(s): r = _r(s); return float(r.std() * math.sqrt(DPY)) if len(r) else 0.0
|
||||
def ann_mean(s): r = _r(s); return float(r.mean() * DPY) if len(r) else 0.0
|
||||
def sharpe(s):
|
||||
r = _r(s); return float(r.mean() / r.std() * math.sqrt(DPY)) if len(r) > 1 and r.std() > 0 else 0.0
|
||||
def cagr(s):
|
||||
r = _r(s); eq = np.cumprod(1 + np.clip(r, -1, None)); y = len(r) / DPY
|
||||
return float(eq[-1] ** (1 / y) - 1) if y > 0 and eq[-1] > 0 else -1.0
|
||||
def maxdd(s):
|
||||
r = _r(s); eq = np.cumprod(1 + np.clip(r, -1, None)); pk = np.maximum.accumulate(eq)
|
||||
return float(np.max((pk - eq) / pk)) if len(r) else 0.0
|
||||
def worst_week(s):
|
||||
ss = pd.Series(s).dropna()
|
||||
if ss.index.tz is None:
|
||||
try: ss.index = ss.index.tz_localize("UTC")
|
||||
except Exception: pass
|
||||
return float(((1 + ss).resample("168h").prod() - 1).min())
|
||||
|
||||
|
||||
# ============================================================ sleeves + book variants (per la parte RISCHIO)
|
||||
def build_sleeves():
|
||||
d = {}
|
||||
for nm, fn in [("TP01", _tp01_returns), ("XS01", _xsec_returns), ("VRP01", _vrp_combo_returns),
|
||||
("SKH01", _skyhook_returns), ("GTAA01", _gtaa_daily_returns)]:
|
||||
d[nm] = to_daily(fn())
|
||||
return d
|
||||
|
||||
def demean(s, h):
|
||||
return s if h == 0.0 else s - h * float(s.mean())
|
||||
|
||||
W5 = {"TP01": 0.33, "XS01": 0.15, "VRP01": 0.12, "SKH01": 0.20, "GTAA01": 0.20}
|
||||
W2 = {"TP01": 0.75, "SKH01": 0.25}
|
||||
# haircut sulla MEDIA (edge), std/coda INVARIATE (filoni 1/2)
|
||||
LV5 = {
|
||||
"FULL": dict(TP01=0.0, XS01=0.0, VRP01=0.0, SKH01=0.0, GTAA01=0.0),
|
||||
"DELUCK": dict(TP01=0.15, XS01=0.0, VRP01=0.0, SKH01=0.40, GTAA01=0.0),
|
||||
"HAIRCUT": dict(TP01=0.15, XS01=0.5, VRP01=0.5, SKH01=0.40, GTAA01=0.0),
|
||||
}
|
||||
LV2 = {
|
||||
"FULL": dict(TP01=0.0, SKH01=0.0),
|
||||
"DELUCK": dict(TP01=0.15, SKH01=0.40),
|
||||
"HAIRCUT": dict(TP01=0.15, SKH01=0.50),
|
||||
}
|
||||
# confidenza sull'EDGE -> moltiplicatore di ESPOSIZIONE per la parte (C)
|
||||
CONF = dict(TP01=1.00, GTAA01=0.90, SKH01=0.70, VRP01=0.45, XS01=0.45)
|
||||
|
||||
|
||||
def make_book(sl, weights, hair):
|
||||
cols = {nm: demean(sl[nm], hair.get(nm, 0.0)) for nm in weights}
|
||||
return combine_outer(cols, weights)
|
||||
|
||||
|
||||
# ============================================================ block-bootstrap (fat-tail, filoni 1/2)
|
||||
def make_paths(r, n_paths, horizon, block, rng):
|
||||
r = _r(r); N = len(r); nb = int(math.ceil(horizon / block))
|
||||
starts = rng.integers(0, N - block + 1, size=(n_paths, nb))
|
||||
off = np.arange(block)
|
||||
idx = (starts[:, :, None] + off[None, None, :]).reshape(n_paths, nb * block)[:, :horizon]
|
||||
return r[idx].astype(np.float64)
|
||||
|
||||
def path_stats(scaled):
|
||||
"""(n,H) rendimenti scalati -> maxDD per path, terminal wealth (floor 0), log-terminal."""
|
||||
sc = np.clip(scaled, -1.0, None)
|
||||
eq = np.cumprod(1.0 + sc, axis=1)
|
||||
pk = np.maximum.accumulate(eq, axis=1)
|
||||
dd = (pk - eq) / pk
|
||||
term = eq[:, -1]
|
||||
return dd.max(axis=1), term
|
||||
|
||||
NPATH = 4000
|
||||
HOR = int(round(5 * 365))
|
||||
BLOCK = 15
|
||||
RUIN = 0.50 # rovina = maxDD >= 50% (equity <= 50% del capitale iniziale)
|
||||
DDLIM = 0.30
|
||||
|
||||
|
||||
# ============================================================ (SANITY)
|
||||
def sanity(sl):
|
||||
P("=" * 110)
|
||||
P("SANITY — ricostruzione book (tolleranza per deriva dati)")
|
||||
P("=" * 110)
|
||||
b5 = make_book(sl, W5, LV5["FULL"]); b2 = make_book(sl, W2, LV2["FULL"])
|
||||
for nm, b, tgt in [("5-SLEEVE", b5, "[target 2.24 / 2.46 / 6.2%]"),
|
||||
("2-SLEEVE LIVE", b2, "[target ~1.78 / 1.17 / 9%]")]:
|
||||
ho = b[b.index >= HOLDOUT]
|
||||
P(f" {nm:14s} n={len(b):5d} FULL Sh={sharpe(b):.3f} DD={maxdd(b)*100:5.2f}% vol={ann_vol(b)*100:5.2f}% "
|
||||
f"CAGR={cagr(b)*100:5.2f}% | HOLD Sh={sharpe(ho):.3f} DD={maxdd(ho)*100:5.2f}% {tgt}")
|
||||
P(" per-sleeve (FULL):")
|
||||
for nm in ("TP01", "XS01", "VRP01", "SKH01", "GTAA01"):
|
||||
s = sl[nm]
|
||||
P(f" {nm:8s} Sh={sharpe(s):+.2f} vol={ann_vol(s)*100:5.2f}% CAGR={cagr(s)*100:+6.2f}% "
|
||||
f"DD={maxdd(s)*100:5.2f}% worstDay={float(pd.Series(s).min())*100:+.2f}% n={len(s)}")
|
||||
return b5, b2
|
||||
|
||||
|
||||
# ============================================================ FRONTIER TABLE consolidata (deliverable)
|
||||
def frontier_table(sl):
|
||||
"""Tabella-frontiera del deliverable: target-vol/lambda x {Sharpe, CAGR, maxDD, worst-week,
|
||||
P(rovina50%|5y), P(DD>30%|5y)} per BOOK 5-sleeve E 2-sleeve, ai 3 livelli di confidenza
|
||||
(FULL / DE-LUCK / HAIRCUT). Sharpe e' INVARIANTE per leva (native); CAGR/DD scalano; le prob.
|
||||
di rovina/DD>30 vengono dal block-bootstrap fat-tail 5y (le stesse dei filoni 1/2)."""
|
||||
P("\n" + "=" * 110)
|
||||
P("FRONTIER TABLE (deliverable) — target-vol x metriche, 2 book x 3 livelli confidenza")
|
||||
P(" Sharpe invariante-per-leva (native) ; CAGR/maxDD/wWeek = leva applicata ; Pruin/Pdd30 = 5y block-bootstrap fat-tail")
|
||||
P("=" * 110)
|
||||
specs = [("5-SLEEVE", W5, LV5), ("2-SLEEVE(live)", W2, LV2)]
|
||||
for bname, weights, levels in specs:
|
||||
for lv in ("FULL", "DELUCK", "HAIRCUT"):
|
||||
b = make_book(sl, weights, levels[lv]); v0 = ann_vol(b)
|
||||
ho = b[b.index >= HOLDOUT]
|
||||
bp = make_paths(b, NPATH, HOR, BLOCK, np.random.default_rng(SEED + 51 + hash(bname + lv) % 9973))
|
||||
P(f"\n --- {bname} / {lv} (native vol {v0*100:.2f}%, Sh_full {sharpe(b):.3f}, Sh_hold {sharpe(ho):.3f}) ---")
|
||||
P(f" {'tvol':>5} {'lam':>5} {'Sharpe':>7} {'CAGR':>7} {'maxDD':>7} {'wWeek':>7} {'Pruin50':>8} {'Pdd30':>7}")
|
||||
for tv in TVOLS:
|
||||
lam = tv / v0
|
||||
rr = pd.Series(_r(b) * lam, index=pd.Series(b).dropna().index)
|
||||
mdd, _ = path_stats(bp * lam)
|
||||
P(f" {tv*100:4.0f}% {lam:5.2f} {sharpe(rr):7.3f} {cagr(rr)*100:6.1f}% "
|
||||
f"{maxdd(rr)*100:6.2f}% {worst_week(rr)*100:6.2f}% {float((mdd>=RUIN).mean())*100:7.2f}% "
|
||||
f"{float((mdd>=DDLIM).mean())*100:6.2f}%")
|
||||
|
||||
|
||||
# ============================================================ (A) FRONTIERA ESEGUIBILE (book live)
|
||||
def daily_frames():
|
||||
"""Frame giornaliero per asset con tp_frac (>=0), skh_sign (+-1) e ret. skh_sign daily = ultimo
|
||||
valore del segno 230m del giorno (il book live tiene il SEGNO SKH, non una frazione)."""
|
||||
frames = {}; skh_cache = {}
|
||||
for a in ASSETS:
|
||||
d1 = al.get(a, "1d")
|
||||
ts = d1["timestamp"].values.astype("int64")
|
||||
close = d1["close"].values.astype(float)
|
||||
tp = np.nan_to_num(np.asarray(TP.target_series(d1), float))
|
||||
ret = np.zeros(len(close)); ret[1:] = close[1:] / close[:-1] - 1.0
|
||||
idx = pd.to_datetime(ts, unit="ms", utc=True)
|
||||
ltf, sgn = skh_sign_series(a); skh_cache[a] = (ltf, sgn)
|
||||
sk = pd.Series(sgn, index=pd.to_datetime(ltf["timestamp"].values.astype("int64"), unit="ms", utc=True))
|
||||
sk_d = sk.resample("1D").last().reindex(idx, method="ffill").fillna(0.0).values
|
||||
frames[a] = pd.DataFrame({"ret": ret, "tp": np.maximum(tp, 0.0), "skh": sk_d}, index=idx)
|
||||
common = frames["BTC"].index.intersection(frames["ETH"].index)
|
||||
for a in ASSETS:
|
||||
frames[a] = frames[a].loc[common]
|
||||
return frames, skh_cache
|
||||
|
||||
def capped_book_returns(frames, C, cap, lam):
|
||||
"""Rendimenti giornalieri del BOOK LIVE ricostruito col MECCANISMO reale (segno SKH, cap
|
||||
per-asset), scalato di lam. posfrac_a = clip(lam*0.5*(0.75*tp+0.25*skh), +-cap/C)."""
|
||||
capfrac = cap / C
|
||||
tot = np.zeros(len(frames["BTC"])); fee = np.zeros_like(tot); prev = {a: 0.0 for a in ASSETS}
|
||||
posf = {}
|
||||
for a in ASSETS:
|
||||
f = frames[a]
|
||||
raw = lam * WEIGHT * (W_TP01 * f["tp"].values + W_SKH * f["skh"].values)
|
||||
posf[a] = np.clip(raw, -capfrac, capfrac)
|
||||
for a in ASSETS:
|
||||
pf = posf[a]; r = frames[a]["ret"].values
|
||||
held = np.zeros_like(pf); held[1:] = pf[:-1]
|
||||
tot += held * r
|
||||
d = np.abs(np.diff(pf, prepend=0.0))
|
||||
fee += FEE_SIDE * d
|
||||
net = tot - fee
|
||||
return pd.Series(net, index=frames["BTC"].index), posf
|
||||
|
||||
def gross_cap_diag(frames, C, cap, lam):
|
||||
"""Diagnostica esposizione: gross-lev medio richiesto vs realizzato, % barre cap-bound,
|
||||
frazione di target che il cap lascia passare (invested)."""
|
||||
capfrac = cap / C
|
||||
raw_g = np.zeros(len(frames["BTC"])); cap_g = np.zeros_like(raw_g); bind = np.zeros_like(raw_g)
|
||||
for a in ASSETS:
|
||||
f = frames[a]
|
||||
raw = lam * WEIGHT * (W_TP01 * f["tp"].values + W_SKH * f["skh"].values)
|
||||
cp = np.clip(raw, -capfrac, capfrac)
|
||||
raw_g += np.abs(raw); cap_g += np.abs(cp)
|
||||
bind += (np.abs(raw) > capfrac + 1e-12).astype(float)
|
||||
active = raw_g > 1e-9
|
||||
invested = float(cap_g[active].sum() / raw_g[active].sum()) if active.any() else 1.0
|
||||
return dict(gross_req=float(np.mean(raw_g)), gross_real=float(np.mean(cap_g)),
|
||||
cap_bind=float(np.mean(bind > 0)), invested=invested)
|
||||
|
||||
def sectionA(frames, skh_cache):
|
||||
P("\n" + "=" * 110)
|
||||
P("(A) FRONTIERA ESEGUIBILE — BOOK LIVE TP01+SKH01 75/25 (meccanismo reale: segno SKH, cap per-asset)")
|
||||
P("=" * 110)
|
||||
# vol nativa del book-meccanismo (lam=1, cap infinito) come riferimento della leva
|
||||
r_unc, _ = capped_book_returns(frames, 1e9, 1e18, 1.0)
|
||||
v_mech = ann_vol(r_unc)
|
||||
P(f" book-meccanismo (segno SKH, cap infinito, lam=1): vol nativa {v_mech*100:.2f}% "
|
||||
f"Sh {sharpe(r_unc):.2f} DD {maxdd(r_unc)*100:.2f}%")
|
||||
P(f" (NB: proxy segno-SKH; il RITORNO fidato del book e' la serie sleeve-based del blocco RISCHIO,")
|
||||
P(f" che cattura gli exit intrabar di SKH -> qui la vol serve solo a MISURARE cap/granularita'.)")
|
||||
|
||||
# ---- A1: CAP CEILING & VOL RAGGIUNGIBILE (sweep target-vol richiesto x capitale x cap) ----
|
||||
P("\n A1) CAP CEILING — vol RAGGIUNGIBILE sotto il cap (richiesto = lam*vol_nativa):")
|
||||
P(" per ogni target-vol RICHIESTO: vol RAGGIUNTA col cap $300 (oggi) e col cap = equity/2,")
|
||||
P(" %barre cap-bound, gross-lev realizzato. Il cap degenera quando 'raggiunta' smette di salire.")
|
||||
a1 = []
|
||||
for C in CAPS:
|
||||
P(f"\n --- capitale ${C:,.0f} (cap oggi $300 = {300/C*100:.0f}% eq ; cap prop = equity/2 = ${C/2:,.0f}) ---")
|
||||
P(f" {'tv_req':>7} {'lam':>5} | {'vRaggH300':>9} {'bind%300':>8} {'inv%300':>8} | "
|
||||
f"{'vRagg_e2':>9} {'bind%e2':>8} {'inv%e2':>8} | {'grossReq':>8}")
|
||||
for tv in TVOLS:
|
||||
lam = tv / v_mech
|
||||
r300, _ = capped_book_returns(frames, C, CAP_NOW, lam)
|
||||
re2, _ = capped_book_returns(frames, C, C / 2.0, lam)
|
||||
d300 = gross_cap_diag(frames, C, CAP_NOW, lam)
|
||||
de2 = gross_cap_diag(frames, C, C / 2.0, lam)
|
||||
a1.append(dict(C=C, tv_req=tv, lam=round(lam, 3),
|
||||
v300=ann_vol(r300), bind300=d300["cap_bind"], inv300=d300["invested"],
|
||||
ve2=ann_vol(re2), binde2=de2["cap_bind"], inve2=de2["invested"],
|
||||
dd300=maxdd(r300), dde2=maxdd(re2), gross=d300["gross_req"]))
|
||||
P(f" {tv*100:6.1f}% {lam:5.2f} | {ann_vol(r300)*100:8.2f}% {d300['cap_bind']*100:7.1f}% "
|
||||
f"{d300['invested']*100:7.1f}% | {ann_vol(re2)*100:8.2f}% {de2['cap_bind']*100:7.1f}% "
|
||||
f"{de2['invested']*100:7.1f}% | {d300['gross_req']:8.2f}")
|
||||
# cap ceiling: il target-vol RAGGIUNTO massimo sotto ciascun cap (la vol non sale piu')
|
||||
P("\n CAP CEILING (max vol raggiungibile prima che il cap la sazi):")
|
||||
for C in CAPS:
|
||||
rows = [x for x in a1 if x["C"] == C]
|
||||
v300max = max(x["v300"] for x in rows); ve2max = max(x["ve2"] for x in rows)
|
||||
P(f" ${C:,.0f}: cap $300 -> tetto vol ~{v300max*100:.1f}% | cap equity/2 -> tetto vol ~{ve2max*100:.1f}%")
|
||||
|
||||
# ---- A2: MIN-ORDER FLOOR (granularita' reale sul 230m, book_sim di r0702) ----
|
||||
P("\n A2) MIN-ORDER FLOOR — granularita' reale (step BTC 0.0001~$7 / ETH 0.001~$2 ; floor config $5):")
|
||||
P(" a target-vol basso + capitale piccolo la posizione/ribilancio scende sotto $5 e NON si esegue.")
|
||||
P(f" {'capitale':>8} {'tv_req':>7} {'lam':>5} {'asset':>5} {'ord/anno':>9} {'medOrd$':>8} {'submin%':>8} {'investito%':>11}")
|
||||
for C in CAPS:
|
||||
for tv in (0.05, 0.10, 0.20):
|
||||
lam = tv / v_mech
|
||||
for a in ASSETS:
|
||||
ltf, sgn = skh_cache[a]
|
||||
tpf = tp_frac_on_ltf(a, ltf)
|
||||
ts = ltf["timestamp"].values.astype("int64")
|
||||
# book_sim di r0702 con lam sul segnale (cap = equity/2 proposto)
|
||||
r = book_sim(lam * tpf, sgn, ts, C, C / 2.0, MIN_ORDER)
|
||||
P(f" {C:>8.0f} {tv*100:6.1f}% {lam:5.2f} {a:>5} {r['orders_py']:>9.0f} "
|
||||
f"{r['med_order']:>8.0f} {r['sub_min']*100:>7.1f}% {r['invested']*100:>10.1f}%")
|
||||
P(" Lettura: il muro dal basso e' fisiologico e piccolo (i micro-ribilanci saltati = banda")
|
||||
P(" d'isteresi gratuita); la POSIZIONE piena resta >> $5 gia' a target-vol 5% -> il min-order")
|
||||
P(" NON e' il vincolo binding a 2-5k. Il vincolo binding e' il CAP (A1).")
|
||||
return a1, v_mech
|
||||
|
||||
|
||||
def sectionA_dd_euro(sl):
|
||||
"""DD-in-EURO: per ogni capitale x target-vol, maxDD% del book FIDATO (haircut) -> EURO.
|
||||
Sia 2-sleeve (live) sia 5-sleeve (research/paper)."""
|
||||
P("\n A3) DD-IN-EURO (book FIDATO = HAIRCUT) — maxDD full & bootstrap-p95, worst-week, in EURO:")
|
||||
out = {}
|
||||
for tag, weights, lv in [("2-SLEEVE (live)", W2, LV2["HAIRCUT"]), ("5-SLEEVE (research)", W5, LV5["HAIRCUT"])]:
|
||||
b = make_book(sl, weights, lv); v0 = ann_vol(b)
|
||||
bp = make_paths(b, NPATH, HOR, BLOCK, np.random.default_rng(SEED + len(tag)))
|
||||
P(f"\n --- {tag} (vol nativa {v0*100:.2f}%, Sh {sharpe(b):.2f}) ---")
|
||||
P(f" {'tv':>5} {'lam':>5} {'maxDD%':>7} {'p95DD%':>7} {'wWeek%':>7} | "
|
||||
f"{'DD€@2k':>8} {'DD€@3.5k':>9} {'DD€@5k':>8} | {'p95€@2k':>8} {'p95€@5k':>8}")
|
||||
for tv in TVOLS:
|
||||
lam = tv / v0
|
||||
rr = _r(b) * lam
|
||||
dd = maxdd(rr); ww = worst_week(pd.Series(rr, index=b.index))
|
||||
mdd, _ = path_stats(bp * lam); p95 = float(np.percentile(mdd, 95))
|
||||
out[(tag, tv)] = dict(dd=dd, p95=p95, ww=ww)
|
||||
P(f" {tv*100:4.0f}% {lam:5.2f} {dd*100:6.2f}% {p95*100:6.2f}% {ww*100:6.2f}% | "
|
||||
f"{2000*dd:8.0f} {3500*dd:9.0f} {5000*dd:8.0f} | {2000*p95:8.0f} {5000*p95:8.0f}")
|
||||
P(" (EURO = maxDD% * capitale ; p95€ = 95° pctl del maxDD su 5y bootstrap fat-tail * capitale.)")
|
||||
return out
|
||||
|
||||
|
||||
# ============================================================ (B) STAGING KELLY
|
||||
def kelly_growth(book, label, v_curr_note):
|
||||
P("\n" + "-" * 110)
|
||||
P(f" KELLY — {label} (vol nativa {ann_vol(book)*100:.2f}%, Sh {sharpe(book):.3f}, "
|
||||
f"mean_ann {ann_mean(book)*100:.2f}%)")
|
||||
v0 = ann_vol(book); m0 = ann_mean(book)
|
||||
# Gaussiano-log: g(lam)=lam*m0 - 0.5*(lam*v0)^2 -> lam*=m0/v0^2 -> tvol* = m0/v0 = Sharpe
|
||||
lam_g = m0 / (v0 ** 2)
|
||||
tv_g = lam_g * v0
|
||||
P(f" KELLY GAUSSIANO (log-approx): full-Kelly target-vol = Sharpe = {tv_g*100:.1f}% "
|
||||
f"(lam*={lam_g:.2f}) -> half {tv_g*50:.1f}% quarter {tv_g*25:.1f}%")
|
||||
# Fat-tail: massimizza E[log W terminale] su 5y block-bootstrap
|
||||
bp = make_paths(book, NPATH, HOR, BLOCK, np.random.default_rng(SEED + 11))
|
||||
lam_grid = np.arange(0.25, 8.01, 0.25)
|
||||
elog = []; pruin = []; medterm = []
|
||||
for lam in lam_grid:
|
||||
_, term = path_stats(bp * lam)
|
||||
term = np.maximum(term, 1e-9)
|
||||
elog.append(float(np.mean(np.log(term))))
|
||||
mdd, _ = path_stats(bp * lam)
|
||||
pruin.append(float((mdd >= RUIN).mean()))
|
||||
medterm.append(float(np.median(term)))
|
||||
elog = np.array(elog); lam_star = float(lam_grid[int(np.argmax(elog))])
|
||||
tv_fat = lam_star * v0
|
||||
P(f" KELLY FAT-TAIL (argmax E[log W] su 5y block-bootstrap): full-Kelly target-vol "
|
||||
f"~{tv_fat*100:.1f}% (lam*={lam_star:.2f})")
|
||||
P(f" -> half-Kelly ~{tv_fat*50:.1f}% quarter-Kelly ~{tv_fat*25:.1f}% (le code abbassano l'ottimo vs Gauss)")
|
||||
# curva crescita/rovina attorno all'ottimo
|
||||
P(f" {'tvol':>6} {'lam':>5} {'E[logW]5y':>10} {'medW5y':>8} {'Pruin50%':>9} {'CAGR_emp':>9} {'maxDD%':>7}")
|
||||
show = [0.06, 0.08, 0.10, 0.125, 0.15, 0.20, 0.25, 0.30, round(tv_fat, 3)]
|
||||
for tv in sorted(set(show)):
|
||||
lam = tv / v0
|
||||
_, term = path_stats(bp * lam); term = np.maximum(term, 1e-9)
|
||||
mdd, _ = path_stats(bp * lam)
|
||||
rr = _r(book) * lam
|
||||
P(f" {tv*100:5.1f}% {lam:5.2f} {float(np.mean(np.log(term))):10.4f} "
|
||||
f"{float(np.median(term)):8.3f} {float((mdd>=RUIN).mean())*100:8.2f}% "
|
||||
f"{cagr(pd.Series(rr))*100:8.2f}% {maxdd(pd.Series(rr))*100:6.2f}%")
|
||||
# dove P(rovina) supera 1% e 5%
|
||||
def wall(thr):
|
||||
w = None
|
||||
for lam, pr in zip(lam_grid, pruin):
|
||||
if pr >= thr: w = lam * v0; break
|
||||
return w
|
||||
w1, w5 = wall(0.01), wall(0.05)
|
||||
P(f" MURO rovina: P(rovina50%)>=1% a tvol~{None if w1 is None else round(w1*100,1)}% ; "
|
||||
f">=5% a tvol~{None if w5 is None else round(w5*100,1)}%")
|
||||
P(f" posizione ATTUALE ~{v_curr_note}% vol -> distanza dall'ottimo fat-tail full-Kelly: "
|
||||
f"{(tv_fat- v_curr_note/100)*100:+.1f} pt ; da quarter-Kelly: {(tv_fat*0.25 - v_curr_note/100)*100:+.1f} pt")
|
||||
return dict(v0=v0, tv_gauss=tv_g, tv_fat=tv_fat, lam_star=lam_star,
|
||||
wall1=w1, wall5=w5)
|
||||
|
||||
|
||||
def sectionB(sl):
|
||||
P("\n" + "=" * 110)
|
||||
P("(B) STAGING KELLY — target-vol growth-optimal del book FIDATO (code oneste, NON Gaussiano)")
|
||||
P("=" * 110)
|
||||
res = {}
|
||||
# book FIDATO = HAIRCUT (edge de-luckato + VRP/XS media tagliata)
|
||||
b5h = make_book(sl, W5, LV5["HAIRCUT"])
|
||||
b2h = make_book(sl, W2, LV2["HAIRCUT"])
|
||||
b5f = make_book(sl, W5, LV5["FULL"])
|
||||
b2f = make_book(sl, W2, LV2["FULL"])
|
||||
v5 = ann_vol(b5f) * 100; v2 = ann_vol(b2f) * 100
|
||||
P(f" vol nativa attuale (lam=1, as-run): 5-sleeve {v5:.2f}% | 2-sleeve live {v2:.2f}%")
|
||||
P(f" ('i 6% attuali' del brief = ~vol nativa del book 5-sleeve diversificato; il 2-sleeve live e' piu' caldo)")
|
||||
res["5s_FULL"] = kelly_growth(b5f, "5-SLEEVE FULL (canonico)", v5)
|
||||
res["5s_HC"] = kelly_growth(b5h, "5-SLEEVE HAIRCUT (fidato)", v5)
|
||||
res["2s_FULL"] = kelly_growth(b2f, "2-SLEEVE FULL (canonico, live)", v2)
|
||||
res["2s_HC"] = kelly_growth(b2h, "2-SLEEVE HAIRCUT (fidato, live)", v2)
|
||||
P("\n TESI 'a capitale piccolo i 6% sono iper-prudenti; l'ottimo Kelly-frazionario e' piu' caldo':")
|
||||
hc = res["5s_HC"]
|
||||
P(f" - Kelly-frazionario FIDATO (5s haircut): quarter-Kelly ~{hc['tv_fat']*25:.0f}% vol, "
|
||||
f"half ~{hc['tv_fat']*50:.0f}% -> il 6% attuale e' sotto perfino il QUARTER-Kelly fat-tail.")
|
||||
P(f" - MA il vincolo di ROVINA a capitale piccolo morde molto prima: P(rovina50%)>=1% gia' a "
|
||||
f"tvol~{None if hc['wall1'] is None else round(hc['wall1']*100)}% -> l'ottimo di CRESCITA (Kelly) e' OLTRE la")
|
||||
P(f" soglia di rovina tollerabile -> la TESI e' CONFERMATA in senso Kelly (6% << ottimo di crescita)")
|
||||
P(f" ma la RACCOMANDAZIONE non e' Kelly ne' il muro di rovina: e' un warm-up MODESTO (sez. D),")
|
||||
P(f" ancorato alla vol NATIVA del book (~8-11%) = ancora deep sub-Kelly, tail-safe sotto iniezione.")
|
||||
return res
|
||||
|
||||
|
||||
# ============================================================ (C) AGGRESSIVITA' PER-SLEEVE
|
||||
def scale_series(sl, mult):
|
||||
return {nm: sl[nm] * mult.get(nm, 1.0) for nm in sl}
|
||||
|
||||
def book_at_vol(book, target_v):
|
||||
"""serie book scalata al target-vol dato."""
|
||||
v0 = ann_vol(book)
|
||||
lam = target_v / v0 if v0 > 0 else 0.0
|
||||
return pd.Series(_r(book) * lam, index=pd.Series(book).dropna().index), lam
|
||||
|
||||
def sectionC(sl):
|
||||
P("\n" + "=" * 110)
|
||||
P("(C) AGGRESSIVITA' PER-SLEEVE ~ CONFIDENZA vs UNIFORME (a book-vol PARI, sulla frontiera fidata)")
|
||||
P("=" * 110)
|
||||
P(f" moltiplicatori di esposizione ~ fiducia sull'EDGE: {CONF}")
|
||||
P(" UNIFORME = tutti gli sleeve alla stessa leva; DIFF = ogni sleeve scalato per la sua confidenza,")
|
||||
P(" poi leva globale per PAREGGIARE la book-vol. Domanda: il DIFF domina l'UNIFORME (Sh su, DD/rovina giu')?")
|
||||
out = {}
|
||||
for tag, weights in [("5-SLEEVE", W5), ("2-SLEEVE", W2)]:
|
||||
conf = {nm: CONF[nm] for nm in weights}
|
||||
b_uni = combine_outer({nm: sl[nm] for nm in weights}, weights)
|
||||
b_dif = combine_outer({nm: sl[nm] * conf[nm] for nm in weights}, weights)
|
||||
P(f"\n --- {tag}: uniforme vol {ann_vol(b_uni)*100:.2f}% Sh {sharpe(b_uni):.3f} | "
|
||||
f"diff-conf vol {ann_vol(b_dif)*100:.2f}% Sh {sharpe(b_dif):.3f} "
|
||||
f"corr(uni,dif)={np.corrcoef(_align(b_uni,b_dif))[0,1]:.3f} ---")
|
||||
P(f" {'book-vol':>9} {'variante':>10} {'Sh_full':>8} {'Sh_hold':>8} {'CAGR':>7} {'maxDD':>7} {'Pruin50':>8} {'Pdd30':>7}")
|
||||
for tv in (0.08, 0.10, 0.125):
|
||||
for name, bk in [("uniforme", b_uni), ("diff-conf", b_dif)]:
|
||||
bs, lam = book_at_vol(bk, tv)
|
||||
bs.index = pd.Series(bk).dropna().index
|
||||
ho = bs[bs.index >= HOLDOUT]
|
||||
bp = make_paths(bk, NPATH, HOR, BLOCK, np.random.default_rng(SEED + 21 + int(tv*1000)))
|
||||
mdd, _ = path_stats(bp * lam)
|
||||
out[(tag, tv, name)] = dict(shf=sharpe(bs), shh=sharpe(ho), dd=maxdd(bs),
|
||||
pr=float((mdd>=RUIN).mean()), pd30=float((mdd>=DDLIM).mean()))
|
||||
P(f" {tv*100:8.1f}% {name:>10} {sharpe(bs):8.3f} {sharpe(ho):8.3f} {cagr(bs)*100:6.1f}% "
|
||||
f"{maxdd(bs)*100:6.2f}% {float((mdd>=RUIN).mean())*100:7.2f}% {float((mdd>=DDLIM).mean())*100:6.2f}%")
|
||||
# verdetto
|
||||
dom = all(out[(tag, tv, "diff-conf")]["shf"] >= out[(tag, tv, "uniforme")]["shf"] - 1e-9 and
|
||||
out[(tag, tv, "diff-conf")]["dd"] <= out[(tag, tv, "uniforme")]["dd"] + 1e-9
|
||||
for tv in (0.08, 0.10, 0.125))
|
||||
P(f" -> DIFF domina UNIFORME (Sh>= E DD<= a ogni book-vol)? {'SI' if dom else 'NO'}")
|
||||
P("\n VERDETTO (C): il differenziato-per-confidenza NON domina l'uniforme -> anzi PEGGIORA la")
|
||||
P(" frontiera. Tagliare l'esposizione degli sleeve a bassa fiducia (VRP/XS/SKH) RIMUOVE la loro")
|
||||
P(" DIVERSIFICAZIONE (corr uni/diff 0.98-0.99): Sharpe scende (5s 2.24->2.11, 2s 1.78->1.69), DD")
|
||||
P(" sale a book-vol pari. La bassa fiducia sull'EDGE va espressa DE-MEANando VRP/XS in aspettativa")
|
||||
P(" (la banda fidata dei filoni 1/2), NON tagliandone la vol: correre TP01 piu' caldo di VRP e'")
|
||||
P(" intuitivo ma sub-ottimo, perche' il valore di VRP/XS e' la varianza-riduzione, non il ritorno.")
|
||||
return out
|
||||
|
||||
def _align(a, b):
|
||||
J = pd.concat({"a": pd.Series(a), "b": pd.Series(b)}, axis=1, join="inner").dropna()
|
||||
return J["a"].values, J["b"].values
|
||||
|
||||
|
||||
# ============================================================ (D) SINTESI + RACCOMANDAZIONE
|
||||
def inject_tail(paths, x_native, seed_off):
|
||||
"""Sostituisce UN giorno casuale per path con x_native (rendimento NATIVO, pre-leva): stress
|
||||
'il campione 2019-26 puo' NON contenere il crash peggiore' (anti-allucinazione #1)."""
|
||||
P2 = paths.copy()
|
||||
col = np.random.default_rng(SEED + seed_off).integers(0, P2.shape[1], size=P2.shape[0])
|
||||
P2[np.arange(P2.shape[0]), col] = x_native
|
||||
return P2
|
||||
|
||||
def sectionD(sl, a1, v_mech, kelly, ddeuro):
|
||||
P("\n" + "=" * 110)
|
||||
P("(D) SINTESI OPERATIVA — RACCOMANDAZIONE target-vol a 2k e 5k + azione config")
|
||||
P("=" * 110)
|
||||
P(" Riconciliazione delle 3 forze (la BANDA e' il messaggio):")
|
||||
P(" [crescita] Kelly fat-tail full ~60-90% vol, quarter ~16-22% -> spinge CALDO")
|
||||
P(" [rovina] muro P(rovina50%|5y)>=1% (book fidato) ~25-29% vol -> tetto duro")
|
||||
P(" [coda] iniezione di un crash 1.5x il peggior giorno storico (il campione puo' non averlo)")
|
||||
P(" [ethos] il valore del book e' il TAGLIO del DD, non l'alpha; a capitale piccolo la rovina e' definitiva")
|
||||
P(" => raccomandazione = warm-up MODESTO ancorato alla vol NATIVA del book, verificato sotto iniezione,")
|
||||
P(" NON il muro di rovina (17-25% sarebbe reckless) ne' Kelly (assurdo). Criterio operativo:")
|
||||
P(" p95(maxDD|5y, coda iniettata) <= 25% E P(rovina50%|5y, coda iniettata) <= 2%.")
|
||||
rec_out = {}
|
||||
for tag, key, weights, lv, v_nat in [("2-SLEEVE (live)", "2s_HC", W2, LV2["HAIRCUT"], None),
|
||||
("5-SLEEVE (research)", "5s_HC", W5, LV5["HAIRCUT"], None)]:
|
||||
k = kelly[key]
|
||||
b = make_book(sl, weights, lv)
|
||||
bp = make_paths(b, NPATH, HOR, BLOCK, np.random.default_rng(SEED + 31))
|
||||
v0 = ann_vol(b)
|
||||
crash = 1.5 * float(pd.Series(b).min()) # 1.5x il peggior giorno storico del book (nativo)
|
||||
bpi = inject_tail(bp, crash, 41)
|
||||
P(f"\n {tag}: vol nativa {v0*100:.2f}% ; crash iniettato = {crash*100:.2f}%/giorno (1.5x worst-day storico {float(pd.Series(b).min())*100:.2f}%)")
|
||||
P(f" {'tvol':>5} {'lam':>5} {'maxDD%':>7} {'p95DD':>7} {'p95DD+cr':>9} {'Prov+cr':>8} {'CAGR':>6} | "
|
||||
f"{'DD€2k':>6} {'DD€5k':>6} {'p95€2k+cr':>10} {'p95€5k+cr':>10}")
|
||||
rec = None
|
||||
for tv in (0.06, 0.08, 0.10, 0.12, 0.15, 0.20):
|
||||
lam = tv / v0
|
||||
rr = pd.Series(_r(b) * lam, index=pd.Series(b).dropna().index)
|
||||
dd = maxdd(rr)
|
||||
mdd, _ = path_stats(bp * lam); p95 = float(np.percentile(mdd, 95))
|
||||
mddi, _ = path_stats(bpi * lam); p95i = float(np.percentile(mddi, 95)); provi = float((mddi >= RUIN).mean())
|
||||
if p95i <= 0.25 and provi <= 0.02:
|
||||
rec = tv
|
||||
P(f" {tv*100:4.0f}% {lam:5.2f} {dd*100:6.2f}% {p95*100:6.2f}% {p95i*100:8.2f}% "
|
||||
f"{provi*100:7.2f}% {cagr(rr)*100:5.1f}% | {2000*dd:6.0f} {5000*dd:6.0f} "
|
||||
f"{2000*p95i:9.0f} {5000*p95i:9.0f}")
|
||||
rec_out[tag] = rec
|
||||
if rec is not None:
|
||||
lam = rec / v0
|
||||
rr = pd.Series(_r(b) * lam, index=pd.Series(b).dropna().index)
|
||||
P(f" -> WARM-UP raccomandato ~{rec*100:.0f}% vol (sotto coda-iniettata): DD atteso {maxdd(rr)*100:.1f}% "
|
||||
f"= EUR {2000*maxdd(rr):.0f}@2k / EUR {5000*maxdd(rr):.0f}@5k ; CAGR_full {cagr(rr)*100:.1f}% "
|
||||
f"-> ~EUR {2000*cagr(rr)/365.25:.2f}/g@2k, ~EUR {5000*cagr(rr)/365.25:.2f}/g@5k")
|
||||
P(f" riferimenti: Kelly-frazionario quarter ~{k['tv_fat']*25:.0f}% ; muro rovina1% ~{None if k['wall1'] is None else round(k['wall1']*100)}% ; vol nativa {v0*100:.1f}%")
|
||||
P("\n CAP CEILING (sez.A1): col cap $300 la vol RAGGIUNGIBILE crolla al crescere del capitale")
|
||||
P(" (il cap fisso diventa una frazione minore dell'equity -> throttling PARADOSSALE):")
|
||||
for C in CAPS:
|
||||
rows = [x for x in a1 if x["C"] == C]
|
||||
v300 = max(x["v300"] for x in rows); ve2 = max(x["ve2"] for x in rows)
|
||||
P(f" ${C:,.0f}: cap $300 -> tetto vol ~{v300*100:.1f}% | cap equity/2 -> tetto vol ~{ve2*100:.1f}%")
|
||||
P("\n RACCOMANDAZIONE FINALE (book LIVE 2-sleeve; vol capital-indipendente, cambia solo DD-in-EURO e cap):")
|
||||
r2 = rec_out.get("2-SLEEVE (live)")
|
||||
P(f" - target-vol ~10-11% (= la vol NATIVA del book a lam~1). E' un warm-up di ~+4-5 pt dai '6% attuali'")
|
||||
P(f" del regime diversificato, MA e' ancora meta' del quarter-Kelly fidato (~22%) e ben dentro il muro")
|
||||
P(f" di rovina (~25%): tail-safe anche con un crash 1.5x-worse iniettato (P(rovina)~0). Criterio-coda -> ~{None if r2 is None else round(r2*100)}%.")
|
||||
P(f" - a 2k: DD atteso ~9-12% = EUR ~200-290 ; a 5k: stesso % = EUR ~500-720. La % di rischio NON cambia")
|
||||
P(f" col capitale; cambia l'EURO in gioco e cosa e' ESEGUIBILE.")
|
||||
P(f" - NON alzare oltre: 15-20% (verso Kelly/muro) triplica il DD-in-euro senza margine di coda a capitale piccolo.")
|
||||
P("\n AZIONE CONFIG (SOLO PROPOSTA, config/live.json NON toccato):")
|
||||
P(" max_notional_per_asset_usd: PORTARLO a equity/2 -> $1000 (2k) / $1750 (3.5k) / $2500 (5k).")
|
||||
P(" Motivo: col cap fermo a $300 il tetto vol raggiungibile scende a ~13%@2k / ~8%@3.5k / ~6%@5k -> a 3.5-5k")
|
||||
P(" il book verrebbe FORZATO sotto il target ~10-11% (throttling), cioe' il capitale in piu' RAFFREDDA il book.")
|
||||
P(" Il cap=equity/2 ripristina il rapporto attuale (300/600) e riporta il tetto a ~27% (headroom ampio).")
|
||||
P(" min_order_usd $5: LASCIARE (granularita' reale non binding a 2-5k, sez.A2). Tranching K=2: NON cablare")
|
||||
P(" (blocco feed-intraday). Nessun altro cambio.")
|
||||
|
||||
|
||||
def main():
|
||||
t0 = time.time()
|
||||
P("costruzione sleeve...")
|
||||
sl = build_sleeves()
|
||||
b5, b2 = sanity(sl)
|
||||
frontier_table(sl)
|
||||
P("\ncostruzione frame giornalieri book live (segno SKH 230m)...")
|
||||
frames, skh_cache = daily_frames()
|
||||
a1, v_mech = sectionA(frames, skh_cache)
|
||||
ddeuro = sectionA_dd_euro(sl)
|
||||
kelly = sectionB(sl)
|
||||
_ = sectionC(sl)
|
||||
sectionD(sl, a1, v_mech, kelly, ddeuro)
|
||||
with open(os.path.join(OUT, "r0703_frontier_exec_report.txt"), "w") as f:
|
||||
f.write("\n".join(REP))
|
||||
P(f"\n[report -> {OUT}/r0703_frontier_exec_report.txt] done in {time.time()-t0:.0f}s")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user