Files

441 lines
13 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python3
"""
📊 Trading Bot Dashboard - Streamlit
Real-time monitoring für V1.8 Trading Bot
"""
import streamlit as st
import sqlite3
import pandas as pd
from datetime import datetime, timedelta
import plotly.express as px
import plotly.graph_objects as go
from pathlib import Path
# ==========================================
# PAGE CONFIG
# ==========================================
st.set_page_config(
page_title="Trading Bot Dashboard",
page_icon="📊",
layout="wide"
)
# ==========================================
# DATABASE CONNECTION
# ==========================================
DB_PATH = Path(__file__).parent / "trading_bot.db"
@st.cache_resource
def get_connection():
if not DB_PATH.exists():
st.error(f"Database not found: {DB_PATH}")
st.stop()
return sqlite3.connect(str(DB_PATH), check_same_thread=False)
conn = get_connection()
# ==========================================
# HEADER
# ==========================================
st.title("📊 Trading Bot Dashboard V1.8")
st.markdown("---")
2025-12-16 22:02:15 +01:00
# Controls
col1, col2, col3 = st.columns([1, 1, 2])
with col1:
if st.button("🔄 Refresh Data"):
st.cache_data.clear()
st.rerun()
with col2:
auto_refresh = st.toggle("Auto-refresh (30s)")
2025-12-16 22:02:15 +01:00
with col3:
trade_filter = st.selectbox(
"📊 Filter Trades:",
["All Trades", "Live Trades Only", "Historical Only"],
index=1 # Default to "Live Trades Only"
)
if auto_refresh:
if "last_refresh" not in st.session_state:
st.session_state.last_refresh = datetime.now()
elapsed = (datetime.now() - st.session_state.last_refresh).total_seconds()
if elapsed >= 30:
st.session_state.last_refresh = datetime.now()
st.rerun()
else:
st.markdown(f"*Auto-refresh in {30 - int(elapsed)}s...*")
# ==========================================
# LOAD DATA
# ==========================================
@st.cache_data(ttl=30)
def load_all_trades():
query = """
SELECT
ticket, position_id, symbol, strategy_name, type, volume,
entry_price, sl_price, tp_price, entry_time, exit_time,
session, regime, quality, confidence, timeframe_alignment,
risk_amount, risk_pct, net_profit, profit_pct, rr_ratio,
status, exit_reason
FROM trades
ORDER BY entry_time DESC
"""
try:
return pd.read_sql_query(query, conn)
except Exception as e:
st.error(f"Error loading trades: {e}")
return pd.DataFrame()
@st.cache_data(ttl=30)
def load_bot_status():
try:
return pd.read_sql_query("SELECT * FROM bot_status ORDER BY timestamp DESC LIMIT 1", conn)
except Exception as e:
st.error(f"Error loading bot status: {e}")
return pd.DataFrame()
# Load data
2025-12-16 22:02:15 +01:00
df_trades_raw = load_all_trades()
df_status = load_bot_status()
2025-12-16 22:02:15 +01:00
# ==========================================
# APPLY TRADE FILTER
# ==========================================
if trade_filter == "Live Trades Only":
df_trades = df_trades_raw[df_trades_raw['status'] != 'historical'].copy()
st.info(f"📊 Showing **Live Trades Only** (excluding {(df_trades_raw['status'] == 'historical').sum()} historical imports)")
elif trade_filter == "Historical Only":
df_trades = df_trades_raw[df_trades_raw['status'] == 'historical'].copy()
st.info(f"📚 Showing **Historical Trades Only** ({len(df_trades)} trades)")
else: # All Trades
df_trades = df_trades_raw.copy()
live_count = (df_trades['status'] != 'historical').sum()
hist_count = (df_trades['status'] == 'historical').sum()
st.info(f"📊 Showing **All Trades** ({live_count} live + {hist_count} historical)")
# ==========================================
# TOP METRICS
# ==========================================
st.subheader("📈 Key Metrics")
col1, col2, col3, col4, col5 = st.columns(5)
total_trades = len(df_trades)
closed_trades = len(df_trades[df_trades['status'] == 'closed'])
open_trades = len(df_trades[df_trades['status'] == 'open'])
if closed_trades > 0:
winning_trades = len(df_trades[(df_trades['status'] == 'closed') & (df_trades['net_profit'] > 0)])
win_rate = (winning_trades / closed_trades) * 100
total_profit = df_trades[df_trades['status'] == 'closed']['net_profit'].sum()
else:
win_rate = 0
total_profit = 0
with col1:
st.metric("Total Trades", total_trades)
with col2:
st.metric("Open Positions", open_trades)
with col3:
st.metric("Win Rate", f"{win_rate:.1f}%")
with col4:
profit_color = "normal" if total_profit >= 0 else "inverse"
st.metric("Total Profit", f"${total_profit:.2f}", delta=None)
with col5:
if closed_trades > 0:
avg_profit = total_profit / closed_trades
st.metric("Avg Profit/Trade", f"${avg_profit:.2f}")
else:
st.metric("Avg Profit/Trade", "N/A")
st.markdown("---")
# ==========================================
# SESSION FILTER CHECK
# ==========================================
st.subheader("🎯 Session Filter Status")
# Calculate session distribution
session_counts = df_trades['session'].value_counts()
col1, col2 = st.columns([1, 2])
with col1:
st.markdown("#### Session Distribution")
for session in ['ny', 'london', 'asian', 'overlap']:
count = session_counts.get(session, 0)
pct = (count / total_trades * 100) if total_trades > 0 else 0
if session == 'ny':
if count == total_trades:
st.success(f"✅ NY: {count} ({pct:.1f}%) - PERFECT!")
else:
st.warning(f"⚠️ NY: {count} ({pct:.1f}%)")
else:
if count > 0:
st.error(f"❌ {session.upper()}: {count} ({pct:.1f}%) - VIOLATION!")
else:
st.success(f"✅ {session.upper()}: {count} (0%) - Blocked")
with col2:
st.markdown("#### Verdict")
ny_count = session_counts.get('ny', 0)
violation_count = total_trades - ny_count
if total_trades == 0:
st.info("️ No trades yet - waiting for data...")
elif violation_count == 0:
st.success("✅ **SESSION FILTER WORKING PERFECTLY!**")
st.markdown("All trades are in NY session (13:00-21:00 UTC)")
else:
st.error(f"❌ **SESSION FILTER NOT WORKING!**")
st.markdown(f"**{violation_count} trades** ({violation_count/total_trades*100:.1f}%) outside NY session!")
st.markdown("**Action Required:** See FIX_DUPLICATE_SCHEDULER.md")
st.markdown("---")
# ==========================================
# HOURLY DISTRIBUTION
# ==========================================
st.subheader("⏰ Trades by Hour (UTC)")
if not df_trades.empty:
2025-12-16 22:02:15 +01:00
# Extract hour from entry_time (handle both ISO8601 and standard format)
df_trades['hour_utc'] = pd.to_datetime(df_trades['entry_time'], errors='coerce').dt.hour
# Count trades by hour
hourly_dist = df_trades.groupby('hour_utc').size().reset_index(name='count')
# Create visualization
fig = go.Figure()
# Add bars
colors = ['red' if (h < 13 or h >= 21) else 'green' for h in hourly_dist['hour_utc']]
fig.add_trace(go.Bar(
x=hourly_dist['hour_utc'],
y=hourly_dist['count'],
marker_color=colors,
text=hourly_dist['count'],
textposition='outside'
))
# Add NY session marker
fig.add_vrect(x0=13, x1=21, fillcolor="green", opacity=0.1, layer="below", line_width=0)
fig.add_annotation(x=17, y=hourly_dist['count'].max(), text="NY Session (13-21 UTC)", showarrow=False)
fig.update_layout(
xaxis_title="Hour (UTC)",
yaxis_title="Number of Trades",
height=400,
showlegend=False,
xaxis=dict(dtick=1, range=[-0.5, 23.5])
)
st.plotly_chart(fig, use_container_width=True)
# Check for violations
violations = df_trades[(df_trades['hour_utc'] < 13) | (df_trades['hour_utc'] >= 21)]
if not violations.empty:
st.error(f"⚠️ **{len(violations)} trades outside NY hours detected!**")
with st.expander("Show violation details"):
st.dataframe(violations[['ticket', 'session', 'entry_time', 'hour_utc', 'type', 'net_profit']])
else:
st.info("No trades to display")
st.markdown("---")
# ==========================================
# SESSION PERFORMANCE
# ==========================================
st.subheader("📊 Performance by Session")
if closed_trades > 0:
session_perf = df_trades[df_trades['status'] == 'closed'].groupby('session').agg({
'ticket': 'count',
'net_profit': ['sum', 'mean']
}).round(2)
session_perf.columns = ['Trades', 'Total Profit', 'Avg Profit']
# Calculate win rate per session
win_rates = []
for session in session_perf.index:
session_trades = df_trades[(df_trades['status'] == 'closed') & (df_trades['session'] == session)]
wins = len(session_trades[session_trades['net_profit'] > 0])
wr = (wins / len(session_trades) * 100) if len(session_trades) > 0 else 0
win_rates.append(wr)
session_perf['Win Rate %'] = win_rates
# Color code
def color_sessions(row):
if row.name == 'ny':
return ['background-color: #90EE90'] * len(row) # Light green
else:
return ['background-color: #FFB6C6'] * len(row) # Light red
st.dataframe(session_perf.style.apply(color_sessions, axis=1), use_container_width=True)
else:
st.info("No closed trades yet")
st.markdown("---")
# ==========================================
# RECENT TRADES
# ==========================================
st.subheader("📋 Recent Trades (Last 20)")
if not df_trades.empty:
recent = df_trades.head(20).copy()
# Color code session
def highlight_session(row):
if row['session'] == 'ny':
return ['background-color: #90EE90'] * len(row)
else:
return ['background-color: #FFB6C6'] * len(row)
# Select columns
display_cols = ['ticket', 'type', 'session', 'entry_time', 'confidence', 'quality', 'regime', 'status', 'net_profit']
recent_display = recent[display_cols]
st.dataframe(
recent_display.style.apply(highlight_session, axis=1),
use_container_width=True,
height=400
)
else:
st.info("No trades yet")
st.markdown("---")
# ==========================================
# PROFIT OVER TIME
# ==========================================
st.subheader("💰 Cumulative Profit Over Time")
if closed_trades > 0:
profit_timeline = df_trades[df_trades['status'] == 'closed'].copy()
profit_timeline['exit_time'] = pd.to_datetime(profit_timeline['exit_time'], errors='coerce')
profit_timeline = profit_timeline.sort_values('exit_time')
profit_timeline['cumulative_profit'] = profit_timeline['net_profit'].cumsum()
fig = px.line(
profit_timeline,
x='exit_time',
y='cumulative_profit',
title='Cumulative Profit',
labels={'exit_time': 'Date', 'cumulative_profit': 'Profit ($)'}
)
fig.update_traces(line_color='green' if profit_timeline['cumulative_profit'].iloc[-1] > 0 else 'red')
fig.add_hline(y=0, line_dash="dash", line_color="gray")
st.plotly_chart(fig, use_container_width=True)
else:
st.info("No closed trades yet")
st.markdown("---")
# ==========================================
# CONFIDENCE & QUALITY ANALYSIS
# ==========================================
st.subheader("🎯 Signal Quality Analysis")
col1, col2 = st.columns(2)
with col1:
st.markdown("#### Confidence Distribution")
if not df_trades.empty:
fig = px.histogram(
df_trades,
x='confidence',
nbins=20,
title='Trade Confidence Distribution'
)
st.plotly_chart(fig, use_container_width=True)
else:
st.info("No data")
with col2:
st.markdown("#### Quality Breakdown")
if not df_trades.empty:
quality_counts = df_trades['quality'].value_counts()
fig = px.pie(
values=quality_counts.values,
names=quality_counts.index,
title='Signal Quality Distribution'
)
st.plotly_chart(fig, use_container_width=True)
else:
st.info("No data")
st.markdown("---")
# ==========================================
# BOT STATUS
# ==========================================
st.subheader("🤖 Bot Status")
if not df_status.empty:
status = df_status.iloc[0]
col1, col2, col3 = st.columns(3)
with col1:
st.markdown("**Version:**")
st.code(status.get('version', 'N/A'))
with col2:
st.markdown("**Status:**")
bot_status = status.get('status', 'unknown')
if bot_status == 'running':
st.success("🟢 Running")
elif bot_status == 'stopped':
st.error("🔴 Stopped")
else:
st.warning("⚠️ Unknown")
with col3:
st.markdown("**Last Update:**")
st.code(status.get('timestamp', 'N/A'))
if 'config' in status and status['config']:
with st.expander("Show Configuration"):
st.json(status['config'])
else:
st.warning("No bot status available")
st.markdown("---")
# ==========================================
# FOOTER
# ==========================================
st.markdown("---")
st.caption(f"Dashboard last updated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
st.caption("📊 Trading Bot V1.8 - Aggressive Mode (NY Only)")