feat: Streamlit dashboard with sidebar, detail, portfolio, and main app
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
70
dashboard/portfolio_view.py
Normal file
70
dashboard/portfolio_view.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import streamlit as st
|
||||
import plotly.graph_objects as go
|
||||
import pandas as pd
|
||||
|
||||
def render_portfolio(portfolio_manager, current_prices: dict, db):
|
||||
st.header("Portfolio Simulator")
|
||||
pv = portfolio_manager.get_portfolio_value(current_prices)
|
||||
|
||||
cols = st.columns(5)
|
||||
cols[0].metric("Initial Capital", f"${portfolio_manager.initial_capital:.2f}")
|
||||
cols[1].metric("Current Value", f"${pv['total_value']:.2f}")
|
||||
pnl_delta = f"{pv['pnl_pct']:+.1f}%"
|
||||
cols[2].metric("Total P&L", f"${pv['total_pnl']:+.2f}", pnl_delta)
|
||||
cols[3].metric("Win Rate", f"{pv['win_rate']:.0f}%")
|
||||
cols[4].metric("Available Cash", f"${pv['cash']:.2f}")
|
||||
|
||||
st.divider()
|
||||
col_left, col_right = st.columns([3, 2])
|
||||
|
||||
with col_left:
|
||||
st.subheader("Current Holdings")
|
||||
if portfolio_manager.positions:
|
||||
rows = []
|
||||
for sym, pos in portfolio_manager.positions.items():
|
||||
price = current_prices.get(sym, pos["entry_price"])
|
||||
value = pos["quantity"] * price
|
||||
pnl = value - pos["invested_usd"]
|
||||
pnl_pct = (pnl / pos["invested_usd"] * 100) if pos["invested_usd"] > 0 else 0
|
||||
rows.append({
|
||||
"Coin": sym.replace("USDT", ""),
|
||||
"Invested": f"${pos['invested_usd']:.2f}",
|
||||
"Qty": f"{pos['quantity']:.6f}",
|
||||
"Entry": f"${pos['entry_price']:.4f}",
|
||||
"Current": f"${price:.4f}",
|
||||
"P&L": f"${pnl:+.2f} ({pnl_pct:+.1f}%)",
|
||||
})
|
||||
st.dataframe(pd.DataFrame(rows), use_container_width=True, hide_index=True)
|
||||
else:
|
||||
st.info("No open positions")
|
||||
|
||||
with col_right:
|
||||
st.subheader("Allocation")
|
||||
if portfolio_manager.positions:
|
||||
labels = [s.replace("USDT", "") for s in portfolio_manager.positions]
|
||||
values = [p["quantity"] * current_prices.get(s, p["entry_price"])
|
||||
for s, p in portfolio_manager.positions.items()]
|
||||
labels.append("Cash")
|
||||
values.append(pv["cash"])
|
||||
fig = go.Figure(data=[go.Pie(labels=labels, values=values, hole=0.4)])
|
||||
fig.update_layout(template="plotly_dark", height=300, margin=dict(t=20, b=20))
|
||||
st.plotly_chart(fig, use_container_width=True)
|
||||
else:
|
||||
st.info("100% Cash")
|
||||
|
||||
st.divider()
|
||||
st.subheader("Trade History")
|
||||
if portfolio_manager.trades:
|
||||
trade_rows = []
|
||||
for t in reversed(portfolio_manager.trades[-20:]):
|
||||
trade_rows.append({
|
||||
"Time": t["timestamp"][:16],
|
||||
"Coin": t["coin"].replace("USDT", ""),
|
||||
"Side": t["side"],
|
||||
"Price": f"${t['price']:.4f}",
|
||||
"Amount": f"${t['amount_usd']:.2f}",
|
||||
"Reason": t["reason"],
|
||||
})
|
||||
st.dataframe(pd.DataFrame(trade_rows), use_container_width=True, hide_index=True)
|
||||
else:
|
||||
st.info("No trades yet")
|
||||
Reference in New Issue
Block a user