import asyncio import sys from datetime import UTC, datetime, timedelta import click from rich.console import Console from rich.table import Table @click.group() def portfolio(): """Portfolio management commands.""" pass @portfolio.command() def show(): """Show the current portfolio holdings and balances.""" try: from sqlalchemy import select from shared.config import Settings from shared.db import Database from shared.sa_models import PositionRow except ImportError as e: click.echo(f"Error: Could not import required modules: {e}", err=True) sys.exit(1) async def _show(): settings = Settings() db = Database(settings.database_url.get_secret_value()) await db.connect() try: async with db.get_session() as session: result = await session.execute(select(PositionRow)) rows = result.scalars().all() if not rows: click.echo("No open positions.") return console = Console() table = Table(title="Current Positions", show_header=True, header_style="bold cyan") table.add_column("Symbol", style="bold") table.add_column("Quantity", justify="right") table.add_column("Avg Entry", justify="right") table.add_column("Current Price", justify="right") table.add_column("Unrealized PnL", justify="right") table.add_column("Updated At") for row in rows: pnl = row.quantity * (row.current_price - row.avg_entry_price) pnl_style = "green" if pnl >= 0 else "red" table.add_row( row.symbol, f"{row.quantity:.6f}", f"{row.avg_entry_price:.2f}", f"{row.current_price:.2f}", f"[{pnl_style}]{pnl:.2f}[/{pnl_style}]", row.updated_at.strftime("%Y-%m-%d %H:%M:%S") if row.updated_at else "-", ) console.print(table) finally: await db.close() asyncio.run(_show()) @portfolio.command() @click.option("--days", default=30, show_default=True, help="Number of days of history") def history(days): """Show PnL history for the portfolio.""" try: from sqlalchemy import select from shared.config import Settings from shared.db import Database from shared.sa_models import PortfolioSnapshotRow except ImportError as e: click.echo(f"Error: Could not import required modules: {e}", err=True) sys.exit(1) async def _history(): settings = Settings() db = Database(settings.database_url.get_secret_value()) await db.connect() try: since = datetime.now(UTC) - timedelta(days=days) stmt = ( select(PortfolioSnapshotRow) .where(PortfolioSnapshotRow.snapshot_at >= since) .order_by(PortfolioSnapshotRow.snapshot_at.asc()) ) async with db.get_session() as session: result = await session.execute(stmt) rows = result.scalars().all() if not rows: click.echo(f"No portfolio snapshots found in the last {days} days.") return console = Console() table = Table( title=f"Portfolio History (last {days} days)", show_header=True, header_style="bold cyan", ) table.add_column("Date", style="bold") table.add_column("Total Value", justify="right") table.add_column("Realized PnL", justify="right") table.add_column("Unrealized PnL", justify="right") for row in rows: r_style = "green" if row.realized_pnl >= 0 else "red" u_style = "green" if row.unrealized_pnl >= 0 else "red" table.add_row( row.snapshot_at.strftime("%Y-%m-%d %H:%M"), f"{row.total_value:.2f}", f"[{r_style}]{row.realized_pnl:.2f}[/{r_style}]", f"[{u_style}]{row.unrealized_pnl:.2f}[/{u_style}]", ) console.print(table) finally: await db.close() asyncio.run(_history())