"""
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"),
},
)