feat(gui): Strategia pannello P/L con slider sizing + fix max_loss
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) <noreply@anthropic.com>
This commit is contained in:
@@ -476,7 +476,11 @@ def _compute_pl(
|
|||||||
|
|
||||||
cap_pertrade_usd = caps["cap_pertrade_eur"] * eur_to_usd
|
cap_pertrade_usd = caps["cap_pertrade_eur"] * eur_to_usd
|
||||||
risk_target = min(caps["kelly"] * capital, cap_pertrade_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"])))
|
n_per_trade = max(0, min(n_kelly, int(caps["max_n"])))
|
||||||
|
|
||||||
prob_time_stop = 0.07
|
prob_time_stop = 0.07
|
||||||
@@ -670,27 +674,33 @@ def _render_pl_panel(
|
|||||||
"""Pannello P/L: confronto Conservativa vs Aggressiva sugli stessi slider."""
|
"""Pannello P/L: confronto Conservativa vs Aggressiva sugli stessi slider."""
|
||||||
st.subheader("💰 P/L atteso — Conservativa vs Aggressiva")
|
st.subheader("💰 P/L atteso — Conservativa vs Aggressiva")
|
||||||
st.caption(
|
st.caption(
|
||||||
"Stessi slider, due profili di sizing. **Conservativa** = la "
|
"Slider parametrici: scegli **cap per trade** e **posizioni "
|
||||||
"golden config attuale (`strategy.yaml`). **Aggressiva** = "
|
"concorrenti**, il capitale richiesto viene calcolato in "
|
||||||
"`strategy.aggressiva.yaml` con cap_per_trade 4×, max contratti "
|
"automatico (Kelly-binding × concurrency / kelly_fraction). "
|
||||||
"4×, 2 posizioni concorrenti. Le regole §2-§9 sono identiche; "
|
"Conservativa e Aggressiva ereditano dai rispettivi yaml SOLO "
|
||||||
"cambiano SOLO le leve di sizing — quello che il P/L "
|
"le leve qualitative (width_pct, credit_ratio, kelly_fraction, "
|
||||||
"conservativo lascia sul tavolo."
|
"feature attive); le leve di sizing (cap, concorrenza) le "
|
||||||
|
"controlli qui sotto."
|
||||||
)
|
)
|
||||||
|
|
||||||
col_a, col_b, col_c, col_d = st.columns(4)
|
col_a, col_b, col_c, col_d, col_e = st.columns(5)
|
||||||
capital = col_a.slider(
|
cap_per_trade_eur = col_a.slider(
|
||||||
"Capitale (USD)", 720, 50_000, value=10_000, step=100
|
"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)
|
concurrency_override = col_b.slider(
|
||||||
win_rate = col_c.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,
|
"Win rate atteso", 0.50, 0.90, value=0.75, step=0.01,
|
||||||
help=(
|
help=(
|
||||||
"Senza filtri quant ≈ 0.65–0.70. CON filtri (dealer gamma>0, "
|
"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."
|
"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,
|
"Trade / anno (post-filtri)", 20, 200, value=110, step=5,
|
||||||
help=(
|
help=(
|
||||||
"Crypto è 24/7: l'entry cycle gira ogni giorno alle 14:00 UTC "
|
"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)
|
cons_caps = _profile_caps(strategy_conservativa or strategy_main)
|
||||||
aggr_caps = _profile_caps(strategy_aggressiva)
|
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)
|
cons_feats = _detect_features(strategy_conservativa or strategy_main)
|
||||||
aggr_feats = _detect_features(strategy_aggressiva)
|
aggr_feats = _detect_features(strategy_aggressiva)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user