2025-11-26 21:14:01 +01:00
|
|
|
|
#!/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
|
2026-05-12 12:25:11 +02:00
|
|
|
|
from pathlib import Path
|
2025-11-26 21:14:01 +01:00
|
|
|
|
|
|
|
|
|
|
# ==========================================
|
|
|
|
|
|
# PAGE CONFIG
|
|
|
|
|
|
# ==========================================
|
|
|
|
|
|
|
|
|
|
|
|
st.set_page_config(
|
|
|
|
|
|
page_title="Trading Bot Dashboard",
|
|
|
|
|
|
page_icon="📊",
|
|
|
|
|
|
layout="wide"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================
|
|
|
|
|
|
# DATABASE CONNECTION
|
|
|
|
|
|
# ==========================================
|
|
|
|
|
|
|
2026-05-12 12:25:11 +02:00
|
|
|
|
DB_PATH = Path(__file__).parent / "trading_bot.db"
|
|
|
|
|
|
|
2025-11-26 21:14:01 +01:00
|
|
|
|
@st.cache_resource
|
|
|
|
|
|
def get_connection():
|
2026-05-12 12:25:11 +02:00
|
|
|
|
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)
|
2025-11-26 21:14:01 +01:00
|
|
|
|
|
|
|
|
|
|
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])
|
2025-11-26 21:14:01 +01:00
|
|
|
|
with col1:
|
|
|
|
|
|
if st.button("🔄 Refresh Data"):
|
|
|
|
|
|
st.cache_data.clear()
|
2026-05-12 12:25:11 +02:00
|
|
|
|
st.rerun()
|
2025-11-26 21:14:01 +01:00
|
|
|
|
|
|
|
|
|
|
with col2:
|
2026-05-12 12:25:11 +02:00
|
|
|
|
auto_refresh = st.toggle("Auto-refresh (30s)")
|
2025-11-26 21:14:01 +01:00
|
|
|
|
|
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"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-11-26 21:14:01 +01:00
|
|
|
|
if auto_refresh:
|
2026-05-12 12:25:11 +02:00
|
|
|
|
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...*")
|
2025-11-26 21:14:01 +01:00
|
|
|
|
|
|
|
|
|
|
# ==========================================
|
|
|
|
|
|
# LOAD DATA
|
|
|
|
|
|
# ==========================================
|
|
|
|
|
|
|
|
|
|
|
|
@st.cache_data(ttl=30)
|
|
|
|
|
|
def load_all_trades():
|
|
|
|
|
|
query = """
|
|
|
|
|
|
SELECT
|
2026-05-12 12:25:11 +02:00
|
|
|
|
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
|
2025-11-26 21:14:01 +01:00
|
|
|
|
FROM trades
|
|
|
|
|
|
ORDER BY entry_time DESC
|
|
|
|
|
|
"""
|
2026-05-12 12:25:11 +02:00
|
|
|
|
try:
|
|
|
|
|
|
return pd.read_sql_query(query, conn)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
st.error(f"Error loading trades: {e}")
|
|
|
|
|
|
return pd.DataFrame()
|
2025-11-26 21:14:01 +01:00
|
|
|
|
|
|
|
|
|
|
@st.cache_data(ttl=30)
|
|
|
|
|
|
def load_bot_status():
|
2026-05-12 12:25:11 +02:00
|
|
|
|
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()
|
2025-11-26 21:14:01 +01:00
|
|
|
|
|
|
|
|
|
|
# Load data
|
2025-12-16 22:02:15 +01:00
|
|
|
|
df_trades_raw = load_all_trades()
|
2025-11-26 21:14:01 +01:00
|
|
|
|
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)")
|
|
|
|
|
|
|
2025-11-26 21:14:01 +01:00
|
|
|
|
# ==========================================
|
|
|
|
|
|
# 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)
|
2026-05-12 12:25:11 +02:00
|
|
|
|
df_trades['hour_utc'] = pd.to_datetime(df_trades['entry_time'], errors='coerce').dt.hour
|
2025-11-26 21:14:01 +01:00
|
|
|
|
|
|
|
|
|
|
# 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()
|
2026-05-12 12:25:11 +02:00
|
|
|
|
profit_timeline['exit_time'] = pd.to_datetime(profit_timeline['exit_time'], errors='coerce')
|
2025-11-26 21:14:01 +01:00
|
|
|
|
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)")
|