"""Tests for PortfolioTracker.""" from decimal import Decimal from shared.models import Order, OrderSide, OrderStatus, OrderType from portfolio_manager.portfolio import PortfolioTracker def make_order(side: OrderSide, price: str, quantity: str) -> Order: """Helper to create a filled Order.""" return Order( signal_id="test-signal", symbol="BTC/USDT", side=side, type=OrderType.MARKET, price=Decimal(price), quantity=Decimal(quantity), status=OrderStatus.FILLED, ) def test_portfolio_add_buy_order() -> None: tracker = PortfolioTracker() order = make_order(OrderSide.BUY, "50000", "0.1") tracker.apply_order(order) position = tracker.get_position("BTC/USDT") assert position is not None assert position.quantity == Decimal("0.1") assert position.avg_entry_price == Decimal("50000") def test_portfolio_add_multiple_buys() -> None: tracker = PortfolioTracker() tracker.apply_order(make_order(OrderSide.BUY, "50000", "0.1")) tracker.apply_order(make_order(OrderSide.BUY, "52000", "0.1")) position = tracker.get_position("BTC/USDT") assert position is not None assert position.quantity == Decimal("0.2") assert position.avg_entry_price == Decimal("51000") def test_portfolio_sell_reduces_position() -> None: tracker = PortfolioTracker() tracker.apply_order(make_order(OrderSide.BUY, "50000", "0.2")) tracker.apply_order(make_order(OrderSide.SELL, "55000", "0.1")) position = tracker.get_position("BTC/USDT") assert position is not None assert position.quantity == Decimal("0.1") assert position.avg_entry_price == Decimal("50000") def test_portfolio_no_position_returns_none() -> None: tracker = PortfolioTracker() position = tracker.get_position("ETH/USDT") assert position is None def test_realized_pnl_on_sell() -> None: """Selling should track realized PnL.""" tracker = PortfolioTracker() # Buy at 50000 tracker.apply_order( Order( signal_id="s1", symbol="BTCUSDT", side=OrderSide.BUY, type=OrderType.MARKET, price=Decimal("50000"), quantity=Decimal("0.1"), status=OrderStatus.FILLED, ) ) assert tracker.realized_pnl == Decimal("0") # Sell at 55000 — profit of 500 tracker.apply_order( Order( signal_id="s2", symbol="BTCUSDT", side=OrderSide.SELL, type=OrderType.MARKET, price=Decimal("55000"), quantity=Decimal("0.1"), status=OrderStatus.FILLED, ) ) assert tracker.realized_pnl == Decimal("500") def test_realized_pnl_on_loss() -> None: """Selling at a loss should track negative realized PnL.""" tracker = PortfolioTracker() tracker.apply_order( Order( signal_id="s1", symbol="BTCUSDT", side=OrderSide.BUY, type=OrderType.MARKET, price=Decimal("50000"), quantity=Decimal("0.1"), status=OrderStatus.FILLED, ) ) tracker.apply_order( Order( signal_id="s2", symbol="BTCUSDT", side=OrderSide.SELL, type=OrderType.MARKET, price=Decimal("45000"), quantity=Decimal("0.1"), status=OrderStatus.FILLED, ) ) assert tracker.realized_pnl == Decimal("-500") def test_realized_pnl_accumulates() -> None: """Multiple sells accumulate realized PnL.""" tracker = PortfolioTracker() # Buy 0.2 at 50000 tracker.apply_order( Order( signal_id="s1", symbol="BTCUSDT", side=OrderSide.BUY, type=OrderType.MARKET, price=Decimal("50000"), quantity=Decimal("0.2"), status=OrderStatus.FILLED, ) ) # Sell 0.1 at 55000 -> +500 tracker.apply_order( Order( signal_id="s2", symbol="BTCUSDT", side=OrderSide.SELL, type=OrderType.MARKET, price=Decimal("55000"), quantity=Decimal("0.1"), status=OrderStatus.FILLED, ) ) # Sell 0.1 at 60000 -> +1000 tracker.apply_order( Order( signal_id="s3", symbol="BTCUSDT", side=OrderSide.SELL, type=OrderType.MARKET, price=Decimal("60000"), quantity=Decimal("0.1"), status=OrderStatus.FILLED, ) ) assert tracker.realized_pnl == Decimal("1500")