"""
Signals page — today's recommendation cards with rich visual indicators.
Each signal is displayed as a data-dense card with strategy badges,
confidence bars, and expandable thesis sections.
"""
from datetime import datetime
import pandas as pd
import plotly.graph_objects as go
import streamlit as st
from tradingagents.ui.theme import COLORS, get_plotly_template, page_header, signal_card
from tradingagents.ui.utils import load_recommendations
TIMEFRAME_LOOKBACK_DAYS = {
"7D": 7,
"1M": 30,
"3M": 90,
"6M": 180,
"1Y": 365,
}
@st.cache_data(ttl=3600)
def _load_price_history(ticker: str, period: str) -> pd.DataFrame:
try:
from tradingagents.dataflows.y_finance import download_history
except Exception:
return pd.DataFrame()
data = download_history(
ticker,
period=period,
interval="1d",
auto_adjust=True,
progress=False,
)
if data is None or data.empty:
return pd.DataFrame()
if isinstance(data.columns, pd.MultiIndex):
tickers = data.columns.get_level_values(1).unique()
target = ticker if ticker in tickers else tickers[0]
data = data.xs(target, level=1, axis=1).copy()
data = data.reset_index()
date_col = "Date" if "Date" in data.columns else data.columns[0]
close_col = "Close" if "Close" in data.columns else "Adj Close"
if close_col not in data.columns:
return pd.DataFrame()
history = data[[date_col, close_col]].rename(columns={date_col: "date", close_col: "close"})
history["date"] = pd.to_datetime(history["date"])
history = history.dropna(subset=["close"]).sort_values("date")
return history
def _slice_history_window(history: pd.DataFrame, timeframe: str) -> pd.DataFrame:
days = TIMEFRAME_LOOKBACK_DAYS.get(timeframe)
if history.empty or days is None:
return pd.DataFrame()
latest_date = history["date"].max()
cutoff = latest_date - pd.Timedelta(days=days)
window = history[history["date"] >= cutoff].copy()
if len(window) < 2:
return pd.DataFrame()
return window
def _format_move_pct(window: pd.DataFrame) -> str:
first_close = float(window["close"].iloc[0])
last_close = float(window["close"].iloc[-1])
if first_close == 0:
return "0.00%"
move = ((last_close - first_close) / first_close) * 100
return f"{move:+.2f}%"
def _get_daily_movement(ticker: str) -> str:
"""Get today's intraday price movement percentage."""
try:
from tradingagents.dataflows.y_finance import download_history
today_data = download_history(
ticker,
period="1d",
interval="1d",
auto_adjust=True,
progress=False,
)
if today_data is None or today_data.empty:
return "N/A"
if isinstance(today_data.columns, pd.MultiIndex):
tickers = today_data.columns.get_level_values(1).unique()
if ticker not in tickers:
ticker = tickers[0]
today_data = today_data.xs(ticker, level=1, axis=1)
close_col = "Close" if "Close" in today_data.columns else "Adj Close"
if close_col not in today_data.columns:
return "N/A"
today_close = float(today_data[close_col].iloc[-1])
today_open = float(today_data.get("Open", today_data[close_col]).iloc[-1])
if today_open != 0:
daily_move = ((today_close - today_open) / today_open) * 100
return f"{daily_move:+.2f}%"
except Exception:
pass
return "N/A"
def _build_dynamic_chart(
history: pd.DataFrame, timeframe: str, ticker: str = ""
) -> tuple[go.Figure, str, str, str]:
window = _slice_history_window(history, timeframe)
if window.empty:
return go.Figure(), "N/A", COLORS["text_muted"], "N/A"
first_close = float(window["close"].iloc[0])
last_close = float(window["close"].iloc[-1])
line_color = COLORS["green"] if last_close >= first_close else COLORS["red"]
move_text = _format_move_pct(window)
daily_move_text = _get_daily_movement(ticker) if ticker else "N/A"
template = dict(get_plotly_template())
fig = go.Figure()
fig.add_trace(
go.Scatter(
x=history["date"],
y=history["close"],
mode="lines",
line=dict(color="rgba(148,163,184,0.22)", width=1.1),
hovertemplate="%{x|%b %d, %Y}
$%{y:.2f}
%{{x|%b %d, %Y}}
$%{{y:.2f}}