TradingAgents/tradingagents/ui/pages/home.py

230 lines
8.7 KiB
Python

"""
Overview page — trading terminal home screen.
Shows KPI cards, strategy scatter plot, and recent signal summary.
"""
from datetime import datetime
import pandas as pd
import plotly.express as px
import streamlit as st
from tradingagents.ui.theme import COLORS, get_plotly_template, kpi_card, page_header
from tradingagents.ui.utils import (
load_open_positions,
load_recommendations,
load_statistics,
load_strategy_metrics,
)
def render() -> None:
"""Render the overview page."""
st.markdown(
page_header("Overview", f"Market session {datetime.now().strftime('%A, %B %d %Y')}"),
unsafe_allow_html=True,
)
stats = load_statistics()
positions = load_open_positions()
strategy_metrics = load_strategy_metrics()
overall = stats.get("overall_7d", {}) if stats else {}
win_rate_7d = overall.get("win_rate", 0)
avg_return_7d = overall.get("avg_return", 0)
total_recs = stats.get("total_recommendations", 0) if stats else 0
open_count = len(positions) if positions else 0
best_strat_name = "N/A"
best_strat_wr = 0.0
for item in strategy_metrics or []:
wr = item.get("Win Rate", 0) or 0
if wr > best_strat_wr:
best_strat_wr = wr
best_strat_name = item.get("Strategy", "unknown")
# ---- KPI Row ----
cols = st.columns(5)
kpis = [
(
"Win Rate 7d",
f"{win_rate_7d:.0f}%",
(
f"+{win_rate_7d - 50:.0f}pp vs 50%"
if win_rate_7d >= 50
else f"{win_rate_7d - 50:.0f}pp vs 50%"
),
"green" if win_rate_7d >= 50 else "red",
),
("Avg Return 7d", f"{avg_return_7d:+.2f}%", "", "green" if avg_return_7d > 0 else "red"),
("Open Positions", str(open_count), "", "blue"),
("Total Signals", str(total_recs), "", "amber"),
(
"Top Strategy",
best_strat_name.upper(),
f"{best_strat_wr:.0f}% WR" if best_strat_wr else "",
"green" if best_strat_wr >= 60 else "amber",
),
]
for col, (label, value, delta, color) in zip(cols, kpis):
with col:
st.markdown(kpi_card(label, value, delta, color), unsafe_allow_html=True)
st.markdown("<div style='height:1.5rem;'></div>", unsafe_allow_html=True)
# ---- Two-column: strategy chart + today's signals ----
left_col, right_col = st.columns([3, 2])
with left_col:
st.markdown(
'<div class="section-title">Strategy Performance <span class="accent">// scatter</span></div>',
unsafe_allow_html=True,
)
if strategy_metrics:
df = pd.DataFrame(strategy_metrics)
template = get_plotly_template()
fig = px.scatter(
df,
x="Win Rate",
y="Avg Return",
size="Count",
color="Strategy",
hover_name="Strategy",
hover_data={
"Win Rate": ":.1f",
"Avg Return": ":.2f",
"Count": True,
"Strategy": False,
},
labels={"Win Rate": "Win Rate (%)", "Avg Return": "Avg Return (%)"},
size_max=40,
)
fig.add_hline(y=0, line_dash="dot", line_color=COLORS["text_muted"], opacity=0.4)
fig.add_vline(x=50, line_dash="dot", line_color=COLORS["text_muted"], opacity=0.4)
fig.add_annotation(
x=75,
y=5,
text="WINNERS",
showarrow=False,
font=dict(size=10, color=COLORS["green"], family="JetBrains Mono"),
opacity=0.3,
)
fig.add_annotation(
x=25,
y=-5,
text="LOSERS",
showarrow=False,
font=dict(size=10, color=COLORS["red"], family="JetBrains Mono"),
opacity=0.3,
)
fig.update_layout(
**template,
height=380,
showlegend=True,
legend=dict(
bgcolor="rgba(0,0,0,0)",
font=dict(size=10),
orientation="h",
yanchor="bottom",
y=-0.25,
),
)
st.plotly_chart(fig, width="stretch")
else:
st.info("Run the discovery pipeline to generate strategy data.")
with right_col:
st.markdown(
'<div class="section-title">Today\'s Signals <span class="accent">// latest</span></div>',
unsafe_allow_html=True,
)
today = datetime.now().strftime("%Y-%m-%d")
recs = load_recommendations(today)
if recs:
for rec in recs[:6]:
ticker = rec.get("ticker", "???")
score = rec.get("final_score", 0)
conf = rec.get("confidence", 0)
strat = (rec.get("strategy_match") or "momentum").upper()
entry = rec.get("entry_price")
entry_str = f"${entry:.2f}" if entry else "N/A"
score_color = (
COLORS["green"]
if score >= 35
else (COLORS["amber"] if score >= 20 else COLORS["text_muted"])
)
conf_bar_w = conf * 10
conf_color = (
COLORS["green"]
if conf >= 8
else (COLORS["amber"] if conf >= 6 else COLORS["red"])
)
st.markdown(
f"""
<div style="background:{COLORS['bg_card']};border:1px solid {COLORS['border']};
border-radius:8px;padding:0.65rem 0.85rem;margin-bottom:0.5rem;">
<div style="display:flex;justify-content:space-between;align-items:center;">
<div style="display:flex;align-items:center;gap:0.6rem;">
<span style="font-family:'JetBrains Mono',monospace;
font-weight:700;font-size:0.95rem;
color:{COLORS['text_primary']};">{ticker}</span>
<span style="font-family:'DM Sans',sans-serif;font-size:0.6rem;
font-weight:600;text-transform:uppercase;
padding:0.15rem 0.4rem;border-radius:3px;
background:rgba(59,130,246,0.15);
color:{COLORS['blue']};letter-spacing:0.04em;">{strat}</span>
</div>
<span style="font-family:'JetBrains Mono',monospace;
font-size:0.8rem;color:{score_color};font-weight:600;">{score}</span>
</div>
<div style="display:flex;justify-content:space-between;
align-items:center;margin-top:0.35rem;">
<span style="font-family:'JetBrains Mono',monospace;
font-size:0.72rem;color:{COLORS['text_muted']};">{entry_str}</span>
<div style="width:50px;height:3px;background:{COLORS['border']};
border-radius:2px;overflow:hidden;">
<div style="height:100%;width:{conf_bar_w}%;
background:{conf_color};border-radius:2px;"></div>
</div>
</div>
</div>
""",
unsafe_allow_html=True,
)
if len(recs) > 6:
st.caption(
f"+{len(recs) - 6} more signals. Switch to Signals page for the full list."
)
else:
st.info("No signals generated today.")
# ---- Strategy table ----
if strategy_metrics:
st.markdown("<div style='height:1rem;'></div>", unsafe_allow_html=True)
st.markdown(
'<div class="section-title">Strategy Breakdown <span class="accent">// table</span></div>',
unsafe_allow_html=True,
)
df_table = pd.DataFrame(strategy_metrics).sort_values("Win Rate", ascending=False)
st.dataframe(
df_table,
width="stretch",
hide_index=True,
column_config={
"Win Rate": st.column_config.NumberColumn(format="%.1f%%"),
"Avg Return": st.column_config.NumberColumn(format="%+.2f%%"),
"Count": st.column_config.NumberColumn(format="%d"),
},
)