feat: realistic budget, EUR trade size in Discord, confidence analytics
- config: starting equity 10k→100 EUR, max_position_pct 20%→10%, min_order 25→5 EUR
- discord: show invested EUR amount (💶 Einsatz) in trade_open embed
- dashboard: add Analytics tab with win-rate by confidence bucket
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7a0eccbcef
commit
be1adfb94c
@ -3,15 +3,15 @@ interval_minutes: 15
|
||||
timeframes: [15m, 1h, 4h]
|
||||
ohlcv_limit: 200
|
||||
|
||||
starting_equity_eur: 10000 # Demo-Startkapital (nur für lokale Buchführung)
|
||||
starting_equity_eur: 100 # Demo-Startkapital (nur für lokale Buchführung)
|
||||
|
||||
risk:
|
||||
max_position_pct: 0.20
|
||||
max_position_pct: 0.10
|
||||
max_open_positions: 2
|
||||
stop_loss_atr_mult: 2.0
|
||||
take_profit_atr_mult: 3.0
|
||||
daily_loss_limit_pct: 0.05
|
||||
min_order_eur: 25
|
||||
min_order_eur: 5
|
||||
|
||||
ai:
|
||||
mode: ensemble # ensemble (beide Voter müssen sich einig sein) | single (nur voter_a)
|
||||
|
||||
@ -23,8 +23,8 @@ def load_df(stmt) -> pd.DataFrame:
|
||||
return pd.DataFrame([r.model_dump() for r in rows])
|
||||
|
||||
|
||||
tab_overview, tab_trades, tab_decisions, tab_ai = st.tabs(
|
||||
["Overview", "Trades", "Decisions", "AI-Vergleich"]
|
||||
tab_overview, tab_trades, tab_decisions, tab_ai, tab_analytics = st.tabs(
|
||||
["Overview", "Trades", "Decisions", "AI-Vergleich", "Analytics"]
|
||||
)
|
||||
|
||||
with tab_overview:
|
||||
@ -110,3 +110,54 @@ with tab_ai:
|
||||
st.caption(
|
||||
"Hinweis: Nur Ticks bei denen beide BUY/SELL mit ausreichender Confidence stimmen führen zu Trades."
|
||||
)
|
||||
|
||||
with tab_analytics:
|
||||
st.subheader("Confidence-Analytics")
|
||||
df_d = load_df(select(Decision))
|
||||
df_t = load_df(select(Trade))
|
||||
if df_d.empty or df_t.empty:
|
||||
st.info("Noch zu wenig Daten — mindestens ein abgeschlossener Trade nötig.")
|
||||
else:
|
||||
# Decisions mit abgeschlossenen Trades joinen
|
||||
closed = df_t[df_t["status"] == "closed"][["decision_id", "pnl_eur"]].dropna()
|
||||
merged = df_d.merge(closed, left_on="id", right_on="decision_id", how="inner")
|
||||
|
||||
if merged.empty:
|
||||
st.info("Noch keine abgeschlossenen Trades mit verknüpften Decisions.")
|
||||
else:
|
||||
merged["won"] = merged["pnl_eur"] > 0
|
||||
merged["avg_confidence"] = (merged["voter_a_confidence"] + merged["voter_b_confidence"]) / 2
|
||||
|
||||
# Confidence-Buckets
|
||||
bins = [0.0, 0.65, 0.75, 0.85, 1.01]
|
||||
labels = ["0.60–0.65", "0.65–0.75", "0.75–0.85", "0.85–1.0"]
|
||||
merged["conf_bucket"] = pd.cut(merged["avg_confidence"], bins=bins, labels=labels)
|
||||
bucket_stats = (
|
||||
merged.groupby("conf_bucket", observed=True)
|
||||
.agg(trades=("won", "count"), win_rate=("won", "mean"), avg_pnl=("pnl_eur", "mean"))
|
||||
.reset_index()
|
||||
)
|
||||
bucket_stats["win_rate"] = (bucket_stats["win_rate"] * 100).round(1)
|
||||
bucket_stats["avg_pnl"] = bucket_stats["avg_pnl"].round(2)
|
||||
|
||||
st.markdown("#### Win-Rate nach Confidence-Bucket (Ø beider Voter)")
|
||||
st.dataframe(bucket_stats, use_container_width=True)
|
||||
|
||||
fig_conf = px.bar(
|
||||
bucket_stats,
|
||||
x="conf_bucket",
|
||||
y="win_rate",
|
||||
text="win_rate",
|
||||
labels={"conf_bucket": "Ø Confidence", "win_rate": "Win-Rate (%)"},
|
||||
color="win_rate",
|
||||
color_continuous_scale=["#E74C3C", "#F1C40F", "#2ECC71"],
|
||||
)
|
||||
fig_conf.update_traces(texttemplate="%{text:.1f}%", textposition="outside")
|
||||
st.plotly_chart(fig_conf, use_container_width=True)
|
||||
|
||||
# Gesamtstatistik
|
||||
st.markdown("#### Gesamt")
|
||||
col1, col2, col3 = st.columns(3)
|
||||
col1.metric("Trades mit Decision", len(merged))
|
||||
col2.metric("Ø Win-Rate", f"{merged['won'].mean() * 100:.1f}%")
|
||||
col3.metric("Ø Confidence bei Wins", f"{merged[merged['won']]['avg_confidence'].mean():.2f}" if merged["won"].any() else "—")
|
||||
|
||||
@ -91,10 +91,11 @@ def notify_decision(settings: Settings, symbol: str, ensemble, label_a: str, lab
|
||||
)
|
||||
|
||||
|
||||
def notify_trade_opened(settings: Settings, trade) -> None:
|
||||
def notify_trade_opened(settings: Settings, trade, qty_eur: float = 0.0) -> None:
|
||||
if not _should(settings, "trade_open"):
|
||||
return
|
||||
side_emoji = "📈" if trade.side == "buy" else "📉"
|
||||
invested = qty_eur or trade.qty * trade.entry_price
|
||||
_post(
|
||||
settings,
|
||||
{
|
||||
@ -102,8 +103,9 @@ def notify_trade_opened(settings: Settings, trade) -> None:
|
||||
"color": COLOR_GREEN if trade.side == "buy" else COLOR_RED,
|
||||
"fields": [
|
||||
{"name": "Side", "value": trade.side.upper(), "inline": True},
|
||||
{"name": "💶 Einsatz", "value": f"**{invested:.2f} EUR**", "inline": True},
|
||||
{"name": "Qty", "value": f"{trade.qty:.6f}", "inline": True},
|
||||
{"name": "Entry", "value": f"{trade.entry_price:.2f} EUR", "inline": True},
|
||||
{"name": "Entry", "value": f"{trade.entry_price:.2f} USD", "inline": True},
|
||||
{
|
||||
"name": "Stop-Loss",
|
||||
"value": f"{trade.stop_loss:.2f}" if trade.stop_loss else "—",
|
||||
|
||||
@ -69,5 +69,5 @@ def execute_trade(
|
||||
sl=sl,
|
||||
tp=tp,
|
||||
)
|
||||
discord.notify_trade_opened(settings, trade)
|
||||
discord.notify_trade_opened(settings, trade, qty_eur=qty_eur)
|
||||
return trade
|
||||
|
||||
Loading…
Reference in New Issue
Block a user