diff options
Diffstat (limited to 'cli/src/trading_cli/commands/portfolio.py')
| -rw-r--r-- | cli/src/trading_cli/commands/portfolio.py | 110 |
1 files changed, 108 insertions, 2 deletions
diff --git a/cli/src/trading_cli/commands/portfolio.py b/cli/src/trading_cli/commands/portfolio.py index 9389bac..ad9a6b4 100644 --- a/cli/src/trading_cli/commands/portfolio.py +++ b/cli/src/trading_cli/commands/portfolio.py @@ -1,4 +1,10 @@ +import asyncio +import sys +from datetime import datetime, timedelta, timezone + import click +from rich.console import Console +from rich.table import Table @click.group() @@ -10,11 +16,111 @@ def portfolio(): @portfolio.command() def show(): """Show the current portfolio holdings and balances.""" - click.echo("Fetching current portfolio...") + try: + from shared.db import Database + from shared.config import Settings + from shared.sa_models import PositionRow + from sqlalchemy import select + 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) + 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.""" - click.echo(f"Fetching PnL history for the last {days} days...") + try: + from shared.db import Database + from shared.config import Settings + from shared.sa_models import PortfolioSnapshotRow + from sqlalchemy import select + 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) + await db.connect() + try: + since = datetime.now(timezone.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()) |
