research(wave-0822): VRP-QUOTE-VERE scartato — e collect_chain raccoglie la famiglia di contratti che il conto non puo' marginare

This commit is contained in:
Adriano Dal Pastro
2026-08-22 17:19:07 +00:00
parent f4b14f305b
commit 75606d0f44
9 changed files with 1800 additions and 37 deletions
+39 -11
View File
@@ -205,11 +205,15 @@ def _panel_index(start: str) -> pd.DatetimeIndex:
_RG_CACHE: dict = {}
def book_RG(w: dict, start: str = START_WIN) -> tuple[np.ndarray, np.ndarray]:
def book_RG(w: dict, start: str = START_WIN,
drift_mult: tuple = ()) -> tuple[np.ndarray, np.ndarray]:
"""(R, gap) giornalieri di un libro a pesi arbitrari sui 5 sleeve.
Crypto TP01+SKH01: minimo ESATTO sul path orario condiviso (r0725_prop_coupled).
XS01/VRP01/GTAA01: il loro minimo si SOMMA (worst simultaneo — convenzione del 25/07)."""
key = (start,) + tuple(round(float(w.get(nm, 0.0)), 6) for nm in SLEEVES)
XS01/VRP01/GTAA01: il loro minimo si SOMMA (worst simultaneo — convenzione del 25/07).
`drift_mult` = (("XS01", 0.5), ...): taglia il DRIFT di quello sleeve (non la vol) — serve
per lo stress giudiziale su uno sleeve scoperto DENTRO la finestra di misura."""
dm = dict(drift_mult)
key = (start, drift_mult) + tuple(round(float(w.get(nm, 0.0)), 6) for nm in SLEEVES)
if key in _RG_CACHE:
return _RG_CACHE[key]
idx = _panel_index(start)
@@ -218,15 +222,25 @@ def book_RG(w: dict, start: str = START_WIN) -> tuple[np.ndarray, np.ndarray]:
M = np.zeros(len(idx))
if w_tp > 0 or w_skh > 0:
c = _crypto(w_tp, w_skh).reindex(idx)
R += c["R"].values
cr = c["R"].values
if w_tp > 0 and dm.get("TP01", 1.0) != 1.0 or w_skh > 0 and dm.get("SKH01", 1.0) != 1.0:
raise NotImplementedError("stress sul drift crypto: il minimo e' path-esatto, non separabile")
R += cr
M += c["m"].values
for nm, fr in (("XS01", pc.xsec_daily_tuples()), ("VRP01", vrp_daily_tuples()),
("GTAA01", gtaa_daily_tuples())):
wt = w.get(nm, 0.0)
if wt > 0:
f = fr.reindex(idx)
R += wt * f["R"].values
M += wt * f["m"].values
rv = np.nan_to_num(f["R"].values)
mv = np.nan_to_num(f["m"].values)
mult = dm.get(nm, 1.0)
if mult != 1.0: # taglia il drift, lascia la forma della coda
cut = (1.0 - mult) * rv.mean() # NB: media PRIMA del taglio
rv = rv - cut
mv = mv - cut
R += wt * rv
M += wt * mv
R = np.nan_to_num(R)
M = np.nan_to_num(M)
_RG_CACHE[key] = (R, np.minimum(M, R) - R)
@@ -299,21 +313,34 @@ def funded_sim(R: np.ndarray, G: np.ndarray, idx: np.ndarray, lev: float, firm:
if (t + 1) % 365 == 0:
alive_at[(t + 1) // 365] = float(alive.mean())
return dict(p_alive=float(alive.mean()), alive_at=alive_at,
e_payout=float(payout.mean()), payout=payout)
e_payout=float(payout.mean()), payout=payout, alive=alive)
def objective(w: dict, lev: float, firm: str, ev_idx: np.ndarray, fu_idx: np.ndarray,
lens: str = "coupled", factor: float = DELUCK,
notional: float = 100_000.0) -> dict:
R, G = book_RG(w)
notional: float = 100_000.0, drift_mult: tuple = ()) -> dict:
R, G = book_RG(w, drift_mult=drift_mult)
R = deluck(R, factor)
e = eval_sim(R, G, ev_idx, lev, firm, lens)
f = funded_sim(R, G, fu_idx, lev, firm, notional, lens)
ann = float(R.mean() * 365.0)
vol = float(R.std() * np.sqrt(365.0))
# stimatore per-percorso di J: eval e funded girano su indici di bootstrap INDIPENDENTI,
# quindi mean(passed_i * alive_i) stima p_pass*p_alive ed e' APPAIABILE fra configurazioni.
jvec = (e["passed"] & f["alive"]).astype(float)
return dict(J=e["p_pass"] * f["p_alive"], p_pass=e["p_pass"], p_alive=f["p_alive"],
e_payout=f["e_payout"], sharpe=(ann / vol if vol > 0 else 0.0),
vol=vol, drift=ann, t_pass=e["t_pass"])
vol=vol, drift=ann, t_pass=e["t_pass"], jvec=jvec)
def paired_delta(wA: dict, levA: float, wB: dict, levB: float, firm: str,
ev_idx: np.ndarray, fu_idx: np.ndarray) -> tuple[float, float]:
"""Differenza APPAIATA di J fra due configurazioni (stessi indici di bootstrap) + errore
standard della differenza. Il livello di J ha SE ~0.01; la DIFFERENZA molto meno."""
a = objective(wA, levA, firm, ev_idx, fu_idx)["jvec"]
b = objective(wB, levB, firm, ev_idx, fu_idx)["jvec"]
d = a - b
return float(d.mean()), float(d.std(ddof=1) / np.sqrt(len(d)))
# ================================================================== §2. IL CONTO ANALITICO
@@ -358,7 +385,8 @@ def grid_scan(grid: list[dict], levs: tuple, firm: str, ev_idx: np.ndarray, fu_i
for w in grid:
for lev in levs:
o = objective(w, lev, firm, ev_idx, fu_idx, lens, factor)
rows.append(dict(w=wkey(w), lev=lev, **{k: v for k, v in o.items()}))
rows.append(dict(w=wkey(w), lev=lev,
**{k: v for k, v in o.items() if k != "jvec"}))
return pd.DataFrame(rows)