From e978a44bffd300aeceb5da360814fe87171f9dc5 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 10 May 2026 09:00:42 +0000 Subject: [PATCH] feat(gui): Strategia pannello P/L con slider sizing + fix max_loss MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pannello "P/L atteso — Conservativa vs Aggressiva": * Sostituiti slider Capitale/Spot con slider parametrici Cap/trade (EUR) + posizioni concorrenti. Il capitale richiesto viene calcolato in automatico via Kelly-binding aggregato: capital = cap_pertrade_usd × concorrenza / max(kelly, 1e-3). * Profili Conservativa/Aggressiva ora ereditano dai yaml SOLO le leve qualitative (width_pct, credit_ratio, kelly_fraction, feature attive); le leve di sizing (cap, concorrenza) sono comandate dagli slider per confronti omogenei. * Tre metriche header: capitale richiesto, cap aggregato notional, cap per trade USD. Fix in `_compute_pl`: * Max loss per contratto era `width` (errato per credit spread). Corretto a `width − credit` allineato a core/sizing_engine.py. Effetto: n_kelly aumenta proporzionalmente al credit incassato → P/L stimato più realistico per spread con credit_to_width_ratio alto (es. 0.30+ in profilo Aggressiva). Co-Authored-By: Claude Opus 4.7 (1M context) --- src/cerbero_bite/gui/pages/7_📚_Strategia.py | 73 ++++++++++++++++---- 1 file changed, 60 insertions(+), 13 deletions(-) diff --git a/src/cerbero_bite/gui/pages/7_📚_Strategia.py b/src/cerbero_bite/gui/pages/7_📚_Strategia.py index 713fbcd..ade2c89 100644 --- a/src/cerbero_bite/gui/pages/7_📚_Strategia.py +++ b/src/cerbero_bite/gui/pages/7_📚_Strategia.py @@ -476,7 +476,11 @@ def _compute_pl( cap_pertrade_usd = caps["cap_pertrade_eur"] * eur_to_usd risk_target = min(caps["kelly"] * capital, cap_pertrade_usd) - n_kelly = int(risk_target // width) if width > 0 else 0 + # Max loss per contratto = width − credit (NON width). Su un put + # spread incassi `credit` upfront, quindi la perdita massima è la + # larghezza meno il credito (vedi core/sizing_engine.py). + max_loss_per_contract = max(width - credit, 1e-6) + n_kelly = int(risk_target // max_loss_per_contract) n_per_trade = max(0, min(n_kelly, int(caps["max_n"]))) prob_time_stop = 0.07 @@ -670,27 +674,33 @@ def _render_pl_panel( """Pannello P/L: confronto Conservativa vs Aggressiva sugli stessi slider.""" st.subheader("💰 P/L atteso — Conservativa vs Aggressiva") st.caption( - "Stessi slider, due profili di sizing. **Conservativa** = la " - "golden config attuale (`strategy.yaml`). **Aggressiva** = " - "`strategy.aggressiva.yaml` con cap_per_trade 4×, max contratti " - "4×, 2 posizioni concorrenti. Le regole §2-§9 sono identiche; " - "cambiano SOLO le leve di sizing — quello che il P/L " - "conservativo lascia sul tavolo." + "Slider parametrici: scegli **cap per trade** e **posizioni " + "concorrenti**, il capitale richiesto viene calcolato in " + "automatico (Kelly-binding × concurrency / kelly_fraction). " + "Conservativa e Aggressiva ereditano dai rispettivi yaml SOLO " + "le leve qualitative (width_pct, credit_ratio, kelly_fraction, " + "feature attive); le leve di sizing (cap, concorrenza) le " + "controlli qui sotto." ) - col_a, col_b, col_c, col_d = st.columns(4) - capital = col_a.slider( - "Capitale (USD)", 720, 50_000, value=10_000, step=100 + col_a, col_b, col_c, col_d, col_e = st.columns(5) + cap_per_trade_eur = col_a.slider( + "Cap/trade (EUR)", 50, 2000, value=200, step=10, + help="Massima perdita per singolo trade. Bound al rischio.", ) - spot = col_b.slider("Spot ETH (USD)", 1500, 6000, value=3000, step=100) - win_rate = col_c.slider( + concurrency_override = col_b.slider( + "Pos. concorrenti", 1, 10, value=3, step=1, + help="Quanti trade simultanei. Cap aggregato = cap/trade × N.", + ) + spot = col_c.slider("Spot ETH (USD)", 1500, 6000, value=3000, step=100) + win_rate = col_d.slider( "Win rate atteso", 0.50, 0.90, value=0.75, step=0.01, help=( "Senza filtri quant ≈ 0.65–0.70. CON filtri (dealer gamma>0, " "no macro, IV−RV>0, liquidation_*_risk≠high) sale a 0.75–0.80." ), ) - trades_per_year = col_d.slider( + trades_per_year = col_e.slider( "Trade / anno (post-filtri)", 20, 200, value=110, step=5, help=( "Crypto è 24/7: l'entry cycle gira ogni giorno alle 14:00 UTC " @@ -702,6 +712,43 @@ def _render_pl_panel( cons_caps = _profile_caps(strategy_conservativa or strategy_main) aggr_caps = _profile_caps(strategy_aggressiva) + # Override sizing dai slider (sostituisce le leve cap/trade, + # cap_aggregate, max_concurrent dei yaml). + eur_to_usd = 1.075 + cap_pertrade_usd = cap_per_trade_eur * eur_to_usd + cap_aggregate_override = float(cap_per_trade_eur * concurrency_override) + cons_caps = { + **cons_caps, + "cap_pertrade_eur": float(cap_per_trade_eur), + "cap_aggregate_eur": cap_aggregate_override, + "max_concurrent": float(concurrency_override), + } + aggr_caps = { + **aggr_caps, + "cap_pertrade_eur": float(cap_per_trade_eur), + "cap_aggregate_eur": cap_aggregate_override, + "max_concurrent": float(concurrency_override), + } + # Capitale richiesto: Kelly-binding aggregato. + # Per ogni trade slot, kelly × capital ≥ cap_pertrade_usd → capital + # ≥ cap_pertrade_usd / kelly. Per N concorrenti, scala linearmente + # come limite conservativo del notional cumulato. + kelly_cons = cons_caps.get("kelly", 0.13) + kelly_aggr = aggr_caps.get("kelly", 0.13) + capital_cons = int( + cap_pertrade_usd * concurrency_override / max(kelly_cons, 1e-3) + ) + capital_aggr = int( + cap_pertrade_usd * concurrency_override / max(kelly_aggr, 1e-3) + ) + capital = max(capital_cons, capital_aggr) + cap_col1, cap_col2, cap_col3 = st.columns(3) + cap_col1.metric("📊 Capitale richiesto", f"${capital:,}") + cap_col2.metric( + "💸 Cap aggregato (notional)", + f"${int(cap_pertrade_usd * concurrency_override):,}", + ) + cap_col3.metric("🎯 Cap per trade (USD)", f"${int(cap_pertrade_usd):,}") cons_feats = _detect_features(strategy_conservativa or strategy_main) aggr_feats = _detect_features(strategy_aggressiva)