diff --git a/tradingagents/ui/pages/todays_picks.py b/tradingagents/ui/pages/todays_picks.py
index 2ce89772..9f018b27 100644
--- a/tradingagents/ui/pages/todays_picks.py
+++ b/tradingagents/ui/pages/todays_picks.py
@@ -79,92 +79,105 @@ def _format_move_pct(window: pd.DataFrame) -> str:
return f"{move:+.2f}%"
-def _build_mini_chart(window: pd.DataFrame, timeframe: str) -> go.Figure:
- template = get_plotly_template()
+def _build_dynamic_chart(history: pd.DataFrame, timeframe: str) -> tuple[go.Figure, str, str]:
+ window = _slice_history_window(history, timeframe)
+ if window.empty:
+ return go.Figure(), "N/A", COLORS["text_muted"]
+
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)
+
+ 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}",
+ name="History",
+ )
+ )
fig.add_trace(
go.Scatter(
x=window["date"],
y=window["close"],
mode="lines",
- line=dict(color=line_color, width=1.8),
+ line=dict(color=line_color, width=2.8),
fill="tozeroy",
- fillcolor="rgba(34,197,94,0.08)" if line_color == COLORS["green"] else "rgba(239,68,68,0.08)",
+ fillcolor=(
+ "rgba(34,197,94,0.18)"
+ if line_color == COLORS["green"]
+ else "rgba(239,68,68,0.18)"
+ ),
hovertemplate=f"{timeframe}
%{{x|%b %d, %Y}}
$%{{y:.2f}}",
- name=timeframe,
+ name=f"{timeframe} Focus",
)
)
- fig.update_layout(
- **template,
- height=140,
- showlegend=False,
- margin=dict(l=0, r=0, t=0, b=0),
+
+ # Override template keys before expansion to avoid duplicate keyword args.
+ template["height"] = 210
+ template["showlegend"] = False
+ template["margin"] = dict(l=0, r=0, t=10, b=0)
+ fig.update_layout(**template)
+
+ # Tighten Y-axis to selected timeframe range for better signal visibility.
+ y_min = float(window["close"].min())
+ y_max = float(window["close"].max())
+ if y_min == y_max:
+ pad = max(0.5, y_min * 0.01)
+ else:
+ pad = max((y_max - y_min) * 0.08, y_max * 0.01)
+
+ fig.update_xaxes(
+ showticklabels=False,
+ showgrid=False,
+ range=[window["date"].min(), history["date"].max()],
+ rangeslider=dict(visible=False),
)
- fig.update_xaxes(showticklabels=False, showgrid=False)
- fig.update_yaxes(showgrid=True, gridcolor="rgba(42,53,72,0.28)", tickprefix="$", nticks=4)
- return fig
+ fig.update_yaxes(
+ showgrid=True,
+ gridcolor="rgba(42,53,72,0.28)",
+ tickprefix="$",
+ nticks=5,
+ range=[y_min - pad, y_max + pad],
+ )
+ return fig, move_text, line_color
-def _render_multi_timeframe_charts(ticker: str, selected_timeframes: list[str]) -> None:
- if not selected_timeframes:
- return
-
+def _render_single_dynamic_chart(ticker: str, timeframe: str) -> None:
base_history = _load_price_history(ticker, "1y")
if base_history.empty:
+ st.caption("No price history available for this ticker.")
return
- ordered_timeframes = [tf for tf in TIMEFRAME_LOOKBACK_DAYS.keys() if tf in selected_timeframes]
- if not ordered_timeframes:
+ window = _slice_history_window(base_history, timeframe)
+ if window.empty:
+ st.caption(f"Not enough data to render {timeframe} window.")
return
+ fig, move_text, move_color = _build_dynamic_chart(base_history, timeframe)
st.markdown(
f"""
-
-
+
- Multi-Timeframe Price Map
+ Dynamic Price View • {timeframe}
+ {move_text}
""",
unsafe_allow_html=True,
)
-
- for i in range(0, len(ordered_timeframes), 2):
- cols = st.columns(2)
- for j, col in enumerate(cols):
- idx = i + j
- if idx >= len(ordered_timeframes):
- continue
- timeframe = ordered_timeframes[idx]
- window = _slice_history_window(base_history, timeframe)
- if window.empty:
- continue
- first_close = float(window["close"].iloc[0])
- last_close = float(window["close"].iloc[-1])
- move_text = _format_move_pct(window)
- move_color = COLORS["green"] if last_close >= first_close else COLORS["red"]
- fig = _build_mini_chart(window, timeframe)
-
- with col:
- st.markdown(
- f"""
-
- {timeframe}
- {move_text}
-
- """,
- unsafe_allow_html=True,
- )
- st.plotly_chart(fig, width="stretch")
+ st.plotly_chart(fig, width="stretch", config={"displayModeBar": False})
def render():
@@ -199,14 +212,6 @@ def render():
min_score = st.slider("Min Score", 0, 100, 0)
with ctrl_cols[3]:
show_charts = st.checkbox("Price Charts", value=False)
- if show_charts:
- selected_timeframes = st.multiselect(
- "Timeframes",
- list(TIMEFRAME_LOOKBACK_DAYS.keys()),
- default=["1M", "3M", "6M", "1Y"],
- )
- else:
- selected_timeframes = []
# Apply filters
filtered = [
@@ -259,7 +264,26 @@ def render():
)
if show_charts:
- _render_multi_timeframe_charts(ticker, selected_timeframes)
+ st.markdown(
+ f"""
+
+ Chart Timeframe
+
+ """,
+ unsafe_allow_html=True,
+ )
+ chart_timeframe = st.radio(
+ f"Timeframe for {ticker}",
+ list(TIMEFRAME_LOOKBACK_DAYS.keys()),
+ index=2,
+ horizontal=True,
+ label_visibility="collapsed",
+ key=f"chart_tf_{ticker}_{idx}",
+ )
+ _render_single_dynamic_chart(ticker, chart_timeframe)
# Action buttons
btn_cols = st.columns(2)