research(rendita): curva capitale->book eseguibile, muro della rendita, asimmetria funded
Ondata "rendita passiva" (2° filone del 2026-07-25). Book live, pesi, config e cron INVARIATI: questa sessione non tocca nulla del path di esecuzione. 1) DIFETTO DI ESEGUIBILITA' su GTAA01 (5° sleeve del book attivo). gtaa.py modella 2bps proporzionali e lo sleeve e' documentato "switch mensile/basso turnover": entrambe false. Il vol-target ribilancia OGNI GIORNO su 6 gambe e IB ha un pavimento FISSO per ordine (min(max($0.35, $0.0035/az), 1% valore)) = ~$530/anno indipendenti dal capitale. A $600: CAGR -3.5% / Sharpe -0.55 (vs +5.5%/0.77 modellato); negativo fino a ~$3k. Con banda $50 + cadenza settimanale (plateau, non argmax) torna a Sh 0.52 / CAGR 3.0%. -> Sharpe onesto di GTAA01 ~0.55-0.64, non 0.77. REGOLA NUOVA: il costo di un venue va modellato nella sua FORMA (fisso vs proporzionale), non solo nel livello. Controprova su Deribit (proporzionale): TP01 realistico = modellato da ~$500 in su. 2) LA RENDITA NON E' capitale x CAGR. Il muro del 24/07 (EUR 122k) ignorava il rischio di sequenza. Prelievo perpetuo (P(cap a 20a >= cap iniziale) >= 90%) sul path live de-luckato: 5.98% -> $497k; banda onesta $232k-$497k (il de-luck x0.6 sopra il path live rischia di contare due volte la degradazione SKH01). Invariante di lente: la rendita perpetua vale ~45-55% del CAGR -> ogni muro calcolato come target/CAGR sbaglia di ~2x. 3) DIVERSIFICARE NON CREA REDDITO a pari nozionale (5.98% -> 6.35%); libera BUDGET DI RISCHIO: a iso-rischio (vol 15%) 7.51%/$395k -> 9.25%/$321k. L'ipotesi "piu' capitale -> piu' sleeve -> CAGR super-lineare" e' REFUTATA nella forma forte (curva Sharpe piatta da $600 a $200k). 4) ASIMMETRIA CAPITALE-PROPRIO vs FUNDED. Su conto funded il capitale e' $100k -> gli sleeve STAT-MODE per taglia (XS01) diventano eseguibili, e le regole prop passano sul DRAWDOWN, non sul CAGR. Aggiungendo XS01: P(pass) HYRO 44.4->54.7%, funded P(vivo 1a) 18.9->57.6%. 5) LA VIA: i 600 euro come BIGLIETTO. Scala di conti funded, 36 mesi. Problema mai studiato: N conti sullo STESSO book bustano INSIEME -> serve sleeve diversi su conti diversi. Vince il MISTO, non gli estremi; il book live attuale e' la politica PEGGIORE. Verdetto in banda: P(arrivare a 50 EUR/g in 3 anni) 2-21%, P(bruciare i 600) 33-78% -> scommessa a coda destra, non una rendita. 6) ADDENDUM "10k su IB": allocazione fra venue. 94% su IB piu' che dimezza il reddito (EUR 0.93/g vs 2.16/g). La leva che convertirebbe lo Sharpe in reddito e' chiusa dai costi (Reg-T 2x + margine 5.5%) -> vince tutto-Deribit a 1.33x. GTAA01 sui 10k rende EUR 0.50/g sopra la liquidita' ferma, pagati con un maxDD del 10%. Bug catturato in sessione: base di prelievo funded aggiornata giornalmente come HWM mobile -> payout $230/anno invece di ~$7.000; regole vere = max-loss STATICO dal saldo iniziale. Test di regressione cablato. Test: 214 pass (200 preesistenti + 14 nuovi in tests/test_capcurve.py). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,329 @@
|
||||
"""r0725_prop_ladder — I 600 EURO COME BIGLIETTO: la scala di conti funded (2026-07-25).
|
||||
|
||||
Domanda del goal: "puoi usare i 600 euro come vuoi, puoi ipotizzare di creare qualcosa".
|
||||
`r0725_capcurve.py` ha refutato i 600 euro come CAPITALE (rendita 50 EUR/g -> muro $300-500k,
|
||||
0% dei path in 20 anni senza depositi). Qui i 600 euro sono usati come **biglietto**: si compra
|
||||
un'evaluation, il conto funded moltiplica il NOZIONALE senza possedere capitale, i payout
|
||||
comprano altri biglietti. E' l'unica leva che non richiede depositi.
|
||||
|
||||
Il 24/07 si era fermato a UN conto ($100k -> ~EUR 14.5/g atteso, cap $200k per firm). Ma
|
||||
**50 EUR/g richiede piu' conti in parallelo**, e qui c'e' il problema che nessuno ha ancora
|
||||
studiato: **N conti che girano lo STESSO book bustano INSIEME** (correlazione 1.0). La
|
||||
diversificazione fra conti e' illusoria salvo assegnare **sleeve DIVERSI a conti diversi**.
|
||||
|
||||
Da cui la domanda di ricerca vera, che e' un problema di costruzione di portafoglio sotto
|
||||
barriera di rovina PER-CONTO:
|
||||
- CONCENTRARE: ogni conto gira il book diversificato (Sharpe alto per conto -> P(vivo) alta
|
||||
per conto, ma i conti sono perfettamente correlati -> o vivono tutti o muoiono tutti);
|
||||
- SPARPAGLIARE: ogni conto gira UNO sleeve (Sharpe basso per conto -> P(vivo) piu' bassa,
|
||||
ma le morti sono INDIPENDENTI -> il reddito aggregato e' molto piu' stabile).
|
||||
Non e' ovvio quale vinca: la barriera di rovina e' convessa, il reddito e' lineare.
|
||||
|
||||
Modello (tutto dichiarato):
|
||||
* bootstrap a blocchi CONGIUNTO dei 3 sleeve (stessi indici di blocco per tutti -> la
|
||||
correlazione fra sleeve e' quella REALE, non imposta);
|
||||
* finestra comune 2024+ (XS01 nasce li'); de-luck x0.6 sul drift;
|
||||
* eval -> funded con le regole vere (max-loss STATICO, daily-loss), payout mensile 80%,
|
||||
prelievo immediato (mai lasciare credito alla firm: regola del 24/07);
|
||||
* bust = biglietto perso; la cassa ricompra se se lo puo' permettere;
|
||||
* cap per firm; **rischio controparte**: ogni anno una firm puo' sparire (il settore ha perso
|
||||
il 13% delle firm nel 2024) -> i conti aperti su quella firm valgono zero;
|
||||
* fisco 33% sui payout.
|
||||
|
||||
ONESTA' — il risultato va letto come una BANDA, non come un punto. Si gira con due lenti:
|
||||
* CLOSE-ONLY = **TETTO OTTIMISTA**: le regole prop scattano sull'equity intraday, non sulla
|
||||
chiusura (C-bis del 24/07: i wick tagliano fino a 37pp di P(pass) e 48pp di P(vivo)).
|
||||
* INTRADAY = **PAVIMENTO PESSIMISTA**: il gap e' lognormale calibrato sui percentili del recon
|
||||
MTM 1h, ma estratto **INDIPENDENTE dal rendimento del giorno**. Nella realta' i wick profondi
|
||||
stanno sulle giornate brutte (sono ACCOPPIATI): estrarli indipendenti aggiunge finti tuffi
|
||||
anche nelle giornate buone -> breach spuri -> stima troppo severa. Il modo giusto e' il
|
||||
bootstrap delle TUPLE (ritorno, gap) come in `r0724_goal50_intraday_mc.py`, che pero' ha il
|
||||
recon solo per TP01+SKH01 (XS01 e' 1d nativo su Hyperliquid, intraday non ricostruibile).
|
||||
-> FOLLOW-UP dichiarato, non simulato qui.
|
||||
La verita' sta FRA le due lenti. Il confronto fra POLITICHE, invece, e' robusto: e' la stessa
|
||||
lente applicata a tutte. La finestra 2024+ e' anche quella di scoperta di XS01 -> le config che
|
||||
lo contengono sono favorite; "CONC-2SL" (solo TP01/SKH01) e' il controllo pulito.
|
||||
|
||||
Uso: `uv run python scripts/research/r0725_prop_ladder.py`
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
sys.path.insert(0, str(ROOT / "scripts" / "research"))
|
||||
|
||||
from src.portfolio.portfolio import metrics # noqa: E402
|
||||
from src.portfolio.sleeves import _xsec_returns # noqa: E402
|
||||
|
||||
import r0724_skh_live_weight as skl # noqa: E402
|
||||
from r0725_capcurve import EURUSD, TAX_RATE, tp01_realistic # noqa: E402
|
||||
|
||||
TARGET_EUR_DAY = 50.0
|
||||
DELUCK = 0.6
|
||||
SPLIT = 0.80
|
||||
FIRM_DEATH_PER_YEAR = 0.10 # il settore ha perso il 13% delle firm nel 2024 (ricerca 24/07)
|
||||
START_EUR = 600.0
|
||||
HORIZON_MONTHS = 36
|
||||
MAX_CONCURRENT = 6
|
||||
|
||||
# (nome, [(nozionale, prezzo biglietto)], eval(target,dd,daily), funded(max_loss,daily), cap)
|
||||
FIRMS = [
|
||||
dict(name="HYRO", tickets=[(25_000, 249.0), (50_000, 379.0), (100_000, 579.0)],
|
||||
ev=(0.10, 0.06, 0.04), fu=(0.06, 0.04), cap=200_000),
|
||||
dict(name="FTMO", tickets=[(50_000, 345.0), (100_000, 540.0), (200_000, 1_080.0)],
|
||||
ev=(0.10, 0.10, 0.05), fu=(0.10, 0.05), cap=400_000),
|
||||
]
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ sleeve
|
||||
def _d(s: pd.Series) -> pd.Series:
|
||||
s = s.dropna().sort_index()
|
||||
if s.index.tz is None:
|
||||
s.index = s.index.tz_localize("UTC")
|
||||
return s
|
||||
|
||||
|
||||
def sleeve_panel() -> tuple[np.ndarray, list[str]]:
|
||||
"""Matrice (giorni x sleeve) sulla finestra comune, de-luckata sul drift."""
|
||||
tp = _d(tp01_realistic(100_000 * 0.75))
|
||||
skh = _d(skl.skh_book(0, "hourly"))
|
||||
xs = _d(_xsec_returns()).resample("1D").apply(lambda x: (1 + x).prod() - 1)
|
||||
J = pd.concat({"TP01": tp, "SKH01": skh, "XS01": xs}, axis=1, join="inner").fillna(0.0)
|
||||
M = J.to_numpy(float)
|
||||
M = M - (1.0 - DELUCK) * M.mean(axis=0, keepdims=True) # de-luck: taglia il drift
|
||||
return M, list(J.columns)
|
||||
|
||||
|
||||
# --------------------------------------------------------------- politiche
|
||||
def policies(names: list[str]) -> dict:
|
||||
"""Ogni politica = lista di vettori-peso sugli sleeve, uno per 'slot' di conto.
|
||||
Gli slot si riempiono in ordine man mano che la cassa permette biglietti."""
|
||||
i = {n: k for k, n in enumerate(names)}
|
||||
n = len(names)
|
||||
|
||||
def w(**kw):
|
||||
v = np.zeros(n)
|
||||
for k, x in kw.items():
|
||||
v[i[k]] = x
|
||||
return v
|
||||
|
||||
book_div = w(TP01=0.55, SKH01=0.20, XS01=0.25)
|
||||
book_2sl = w(TP01=0.75, SKH01=0.25)
|
||||
return {
|
||||
"CONC-2SL ogni conto = book live (TP01/SKH01)": [book_2sl] * MAX_CONCURRENT,
|
||||
"CONC-DIV ogni conto = book diversificato": [book_div] * MAX_CONCURRENT,
|
||||
"SPARSO 1 sleeve per conto (a rotazione)": [w(TP01=1.0), w(XS01=1.0), w(SKH01=1.0),
|
||||
w(TP01=1.0), w(XS01=1.0), w(SKH01=1.0)],
|
||||
"MISTO meta' diversificati, meta' single": [book_div, w(XS01=1.0), book_div,
|
||||
w(TP01=1.0), book_div, w(XS01=1.0)],
|
||||
}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------- sim
|
||||
# --- geometria dei WICK (dal recon MTM 1h del book, r0724_goal50_intraday_mc.py):
|
||||
# gap = (minimo intraday - chiusura), in punti di equity: p50 -0.17pp, p10 -1.03pp, p1 -2.7pp.
|
||||
# Le regole prop scattano sull'equity INTRADAY, non sulla chiusura -> ignorarli gonfia P(pass)
|
||||
# e P(vivo) (C-bis 24/07: fino a -37pp e -48pp). Qui il gap e' lognormale calibrato su quei
|
||||
# percentili e SCALATO sulla vol della configurazione (un book meno volatile ha wick minori).
|
||||
WICK_MU, WICK_SIGMA = np.log(0.0017), 1.30
|
||||
WICK_REF_VOL = 0.114 # vol annua del book TP01/SKH01 su cui la geometria e' misurata
|
||||
|
||||
|
||||
def wick_percentiles() -> dict:
|
||||
"""Percentili impliciti della calibrazione — stampati per essere verificabili."""
|
||||
q = {p: float(np.exp(WICK_MU + WICK_SIGMA * np.sqrt(2) * _erfinv(2 * p - 1)))
|
||||
for p in (0.50, 0.90, 0.99)}
|
||||
return q
|
||||
|
||||
|
||||
def _erfinv(x: float) -> float:
|
||||
from math import sqrt
|
||||
from statistics import NormalDist
|
||||
return NormalDist().inv_cdf((x + 1) / 2) / sqrt(2)
|
||||
|
||||
|
||||
def simulate(M: np.ndarray, slots: list[np.ndarray], n_paths: int, lev: float,
|
||||
seed: int, block: int = 20, intraday: bool = False) -> dict:
|
||||
"""Una politica, n_paths traiettorie di HORIZON_MONTHS mesi.
|
||||
|
||||
Stato per conto: fase (0=eval, 1=funded), equity normalizzata alla base, firm, nozionale.
|
||||
Il bootstrap dei ritorni degli sleeve e' CONGIUNTO (stesso blocco per tutti gli sleeve e
|
||||
per tutti i conti dello stesso path) -> due conti che girano lo stesso book sono
|
||||
perfettamente correlati, due conti su sleeve diversi hanno la correlazione REALE.
|
||||
"""
|
||||
rng = np.random.default_rng(seed)
|
||||
n_days = HORIZON_MONTHS * 30
|
||||
n_sl = M.shape[1]
|
||||
n_blocks = int(np.ceil(n_days / block))
|
||||
starts = rng.integers(0, len(M) - block, size=(n_paths, n_blocks))
|
||||
idx = (starts[:, :, None] + np.arange(block)[None, None, :]).reshape(n_paths, -1)[:, :n_days]
|
||||
R = M[idx] # (paths, days, sleeve)
|
||||
|
||||
n_slot = len(slots)
|
||||
W = np.stack(slots) # (slot, sleeve)
|
||||
# ritorni per (path, day, slot) = R . W^T, con leva
|
||||
Rs = np.einsum("pds,ks->pdk", R, W) * lev
|
||||
# gap intraday per (path, day, slot), scalato sulla vol della config: un book meno volatile
|
||||
# ha wick proporzionalmente piu' piccoli. Il breach si valuta sul MINIMO (chiusura + gap).
|
||||
if intraday:
|
||||
vol_k = Rs.reshape(-1, n_slot).std(axis=0) * np.sqrt(365.0)
|
||||
scale = np.clip(vol_k / WICK_REF_VOL, 0.0, 3.0)
|
||||
G = -np.exp(rng.normal(WICK_MU, WICK_SIGMA, size=Rs.shape)) * scale[None, None, :]
|
||||
else:
|
||||
G = None
|
||||
|
||||
cash = np.full(n_paths, START_EUR * EURUSD)
|
||||
phase = np.full((n_paths, n_slot), -1, np.int8) # -1 vuoto, 0 eval, 1 funded
|
||||
eq = np.ones((n_paths, n_slot))
|
||||
firm = np.full((n_paths, n_slot), -1, np.int8)
|
||||
notional = np.zeros((n_paths, n_slot))
|
||||
days_in = np.zeros((n_paths, n_slot), int)
|
||||
firm_alive = np.ones((n_paths, len(FIRMS)), bool)
|
||||
cum_payout = np.zeros(n_paths) # netto fisco, USD
|
||||
payout_last_year = np.zeros(n_paths)
|
||||
spent = np.zeros(n_paths)
|
||||
|
||||
p_death_day = 1.0 - (1.0 - FIRM_DEATH_PER_YEAR) ** (1 / 365.0)
|
||||
|
||||
for t in range(n_days):
|
||||
# --- morte di una firm (rischio controparte)
|
||||
died = rng.random((n_paths, len(FIRMS))) < p_death_day
|
||||
newly_dead = died & firm_alive
|
||||
firm_alive &= ~died
|
||||
if newly_dead.any():
|
||||
for fi in range(len(FIRMS)):
|
||||
kill = newly_dead[:, fi]
|
||||
if kill.any():
|
||||
hit = (firm == fi) & kill[:, None]
|
||||
phase[hit] = -1
|
||||
notional[hit] = 0.0
|
||||
|
||||
# --- acquisto biglietti negli slot liberi
|
||||
for k in range(n_slot):
|
||||
free = (phase[:, k] == -1)
|
||||
if not free.any():
|
||||
continue
|
||||
for fi, F in enumerate(FIRMS):
|
||||
used = np.where((firm == fi), notional, 0.0).sum(axis=1)
|
||||
for (size, price) in sorted(F["tickets"], key=lambda x: -x[0]):
|
||||
buy = (free & firm_alive[:, fi] & (cash >= price)
|
||||
& (used + size <= F["cap"]))
|
||||
if not buy.any():
|
||||
continue
|
||||
cash[buy] -= price
|
||||
spent[buy] += price
|
||||
phase[buy, k] = 0
|
||||
eq[buy, k] = 1.0
|
||||
firm[buy, k] = fi
|
||||
notional[buy, k] = size
|
||||
days_in[buy, k] = 0
|
||||
free = free & ~buy
|
||||
if not free.any():
|
||||
break
|
||||
if not free.any():
|
||||
break
|
||||
|
||||
# --- dinamica dei conti
|
||||
r = Rs[:, t, :] # (paths, slot)
|
||||
act = phase >= 0
|
||||
eq_prev = eq
|
||||
eq = np.where(act, eq * (1.0 + r), eq)
|
||||
# equity peggiore TOCCATA nella giornata: e' su questa che scattano le regole prop
|
||||
if G is None:
|
||||
eq_low, r_low = eq, r
|
||||
else:
|
||||
eq_low = np.where(act, eq_prev * (1.0 + r + G[:, t, :]), eq)
|
||||
r_low = r + G[:, t, :]
|
||||
days_in += act
|
||||
|
||||
for fi, F in enumerate(FIRMS):
|
||||
sel = act & (firm == fi)
|
||||
if not sel.any():
|
||||
continue
|
||||
ev_t, ev_dd, ev_dl = F["ev"]
|
||||
fu_ml, fu_dl = F["fu"]
|
||||
in_ev = sel & (phase == 0)
|
||||
in_fu = sel & (phase == 1)
|
||||
# breach — valutato sull'equity INTRADAY (eq_low), il target sulla CHIUSURA
|
||||
bust_ev = in_ev & ((eq_low < 1.0 - ev_dd) | (r_low < -ev_dl))
|
||||
bust_fu = in_fu & ((eq_low < 1.0 - fu_ml) | (r_low < -fu_dl))
|
||||
bust = bust_ev | bust_fu
|
||||
phase[bust] = -1
|
||||
notional[bust] = 0.0
|
||||
# pass eval
|
||||
passed = in_ev & (eq >= 1.0 + ev_t) & ~bust
|
||||
phase[passed] = 1
|
||||
eq[passed] = 1.0
|
||||
|
||||
# --- payout mensile (prelievo immediato, base ripristinata)
|
||||
if (t + 1) % 30 == 0:
|
||||
fu = phase == 1
|
||||
gain = np.where(fu & (eq > 1.0), (eq - 1.0) * notional, 0.0)
|
||||
gross = gain.sum(axis=1) * SPLIT
|
||||
net = gross * (1.0 - TAX_RATE)
|
||||
cum_payout += net
|
||||
payout_last_year += net
|
||||
cash += net
|
||||
eq = np.where(fu & (eq > 1.0), 1.0, eq)
|
||||
if (t + 1) % 365 == 0 and t + 1 < n_days:
|
||||
payout_last_year[:] = 0.0
|
||||
|
||||
eur_day = cum_payout / EURUSD / (n_days / 365.0) / 365.0
|
||||
live_notional = np.where(phase == 1, notional, 0.0).sum(axis=1)
|
||||
return dict(eur_day=eur_day,
|
||||
p_target=float((eur_day >= TARGET_EUR_DAY).mean()),
|
||||
p_ge10=float((eur_day >= 10.0).mean()),
|
||||
p_zero=float((cum_payout <= 0).mean()),
|
||||
med=float(np.median(eur_day)),
|
||||
p90=float(np.percentile(eur_day, 90)),
|
||||
p10=float(np.percentile(eur_day, 10)),
|
||||
med_notional=float(np.median(live_notional)),
|
||||
med_spent=float(np.median(spent)))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
M, names = sleeve_panel()
|
||||
print("=" * 104)
|
||||
print(" I 600 EURO COME BIGLIETTO — scala di conti funded, e il problema della correlazione")
|
||||
print("=" * 104)
|
||||
print(f"\n finestra comune: {len(M)} giorni | sleeve: {names} | de-luck x{DELUCK}")
|
||||
print(" correlazioni fra sleeve (le morti dei conti sono correlate quanto lo sono queste):")
|
||||
C = np.corrcoef(M.T)
|
||||
for i, n in enumerate(names):
|
||||
print(" " + f"{n:>7} " + " ".join(f"{C[i, j]:>6.2f}" for j in range(len(names))))
|
||||
for i, n in enumerate(names):
|
||||
m = metrics(pd.Series(M[:, i], index=pd.date_range("2024-01-01", periods=len(M), tz="UTC")))
|
||||
print(f" {n:>7}: Sharpe {m['sharpe']:>5.2f} CAGR {m['cagr']:>6.1%} maxDD {m['maxdd']:>5.1%}")
|
||||
|
||||
print(f"\n orizzonte {HORIZON_MONTHS} mesi | cassa iniziale EUR {START_EUR:.0f} | "
|
||||
f"max {MAX_CONCURRENT} conti | rischio-morte firm {FIRM_DEATH_PER_YEAR:.0%}/anno")
|
||||
print(" NB: livelli ASSOLUTI = TETTO (close-only). Il confronto fra POLITICHE e' la misura onesta.\n")
|
||||
|
||||
q = wick_percentiles()
|
||||
print(f" wick calibrati: p50 -{q[0.50]*100:.2f}pp p90 -{q[0.90]*100:.2f}pp "
|
||||
f"p99 -{q[0.99]*100:.2f}pp (target dal recon 24/07: -0.17 / -1.03 / -2.70)")
|
||||
|
||||
pols = policies(names)
|
||||
for intraday in (False, True):
|
||||
lens = "INTRADAY (wick: la lente onesta)" if intraday else "CLOSE-ONLY (tetto ottimista)"
|
||||
for lev in (0.75, 1.0):
|
||||
print("-" * 104)
|
||||
print(f" {lens} — LEVA {lev:.2f}x")
|
||||
print("-" * 104)
|
||||
print(f" {'politica':>46} {'EUR/g med':>10} {'p10':>7} {'p90':>7} "
|
||||
f"{'P(>=10/g)':>10} {'P(>=50/g)':>10} {'P(zero)':>8}")
|
||||
for pname, slots in pols.items():
|
||||
r = simulate(M, slots, n_paths=3000, lev=lev, seed=20260725, intraday=intraday)
|
||||
print(f" {pname:>46} {r['med']:>10.2f} {r['p10']:>7.2f} {r['p90']:>7.2f} "
|
||||
f"{r['p_ge10']:>10.1%} {r['p_target']:>10.1%} {r['p_zero']:>8.1%}")
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user