#!/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("---") # 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)") 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 df_trades_raw = load_all_trades() df_status = load_bot_status() # ========================================== # 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: # 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)")