summaryrefslogtreecommitdiff
path: root/cli/src/trading_cli/commands/portfolio.py
diff options
context:
space:
mode:
Diffstat (limited to 'cli/src/trading_cli/commands/portfolio.py')
-rw-r--r--cli/src/trading_cli/commands/portfolio.py110
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())