""" Portfolio page — position tracker with P/L visualization. Shows portfolio summary KPIs and individual position rows with color-coded P/L indicators. """ from datetime import datetime import pandas as pd import streamlit as st from tradingagents.ui.theme import COLORS, kpi_card, page_header, pnl_color from tradingagents.ui.utils import load_open_positions def render(): st.markdown(page_header("Portfolio", "Open positions & P/L tracker"), unsafe_allow_html=True) # ---- Add position form ---- with st.expander("Add Position"): col1, col2, col3, col4 = st.columns(4) with col1: ticker = st.text_input("Ticker", placeholder="AAPL") with col2: entry_price = st.number_input("Entry Price", min_value=0.0, format="%.2f") with col3: shares = st.number_input("Shares", min_value=0, step=1) with col4: st.write("") if st.button("Add Position"): if ticker and entry_price > 0 and shares > 0: from tradingagents.dataflows.discovery.performance.position_tracker import ( PositionTracker, ) tracker = PositionTracker() pos = tracker.create_position( { "ticker": ticker.upper(), "entry_price": entry_price, "shares": shares, "recommendation_date": datetime.now().isoformat(), "pipeline": "manual", "scanner": "manual", "strategy_match": "manual", "confidence": 5, } ) tracker.save_position(pos) st.success(f"Added {ticker.upper()}") st.rerun() positions = load_open_positions() if not positions: st.markdown( f"""
No open positions
Enter positions manually above or run the discovery pipeline.
""", unsafe_allow_html=True, ) return # ---- Portfolio summary ---- total_invested = sum(p["entry_price"] * p.get("shares", 0) for p in positions) total_current = sum(p["metrics"]["current_price"] * p.get("shares", 0) for p in positions) total_pnl = total_current - total_invested total_pnl_pct = (total_pnl / total_invested * 100) if total_invested > 0 else 0 cols = st.columns(4) summary_kpis = [ ("Invested", f"${total_invested:,.0f}", "", "blue"), ("Current Value", f"${total_current:,.0f}", "", "blue"), ( "P/L", f"${total_pnl:+,.0f}", f"{total_pnl_pct:+.1f}%", "green" if total_pnl >= 0 else "red", ), ("Positions", str(len(positions)), "", "amber"), ] for col, (label, value, delta, color) in zip(cols, summary_kpis): with col: st.markdown(kpi_card(label, value, delta, color), unsafe_allow_html=True) st.markdown("
", unsafe_allow_html=True) # ---- Position cards ---- st.markdown( '
Open Positions // live
', unsafe_allow_html=True, ) for p in positions: ticker = p["ticker"] entry = p["entry_price"] current = p["metrics"]["current_price"] shares = p.get("shares", 0) pnl_dollar = (current - entry) * shares pnl_pct = p["metrics"]["current_return"] days = p["metrics"]["days_held"] color = pnl_color(pnl_pct) st.markdown( f"""
{ticker} {shares} shares · {days}d
{pnl_pct:+.1f}% ${pnl_dollar:+,.0f}
Entry ${entry:.2f} Current ${current:.2f}
""", unsafe_allow_html=True, ) # ---- Data table fallback ---- with st.expander("Detailed Table"): data = [] for p in positions: pnl = (p["metrics"]["current_price"] - p["entry_price"]) * p.get("shares", 0) data.append( { "Ticker": p["ticker"], "Entry": p["entry_price"], "Current": p["metrics"]["current_price"], "Shares": p.get("shares", 0), "P/L": pnl, "P/L %": p["metrics"]["current_return"], "Days": p["metrics"]["days_held"], } ) st.dataframe( pd.DataFrame(data), width="stretch", hide_index=True, column_config={ "Entry": st.column_config.NumberColumn(format="$%.2f"), "Current": st.column_config.NumberColumn(format="$%.2f"), "Shares": st.column_config.NumberColumn(format="%d"), "P/L": st.column_config.NumberColumn(format="$%+.2f"), "P/L %": st.column_config.NumberColumn(format="%+.1f%%"), "Days": st.column_config.NumberColumn(format="%d"), }, )