"""Unit tests for the paper-book equity curve math (pure function).""" from datetime import date, datetime from types import SimpleNamespace from app.services.paper_trade_service import build_equity_curve def _trade(**kw): defaults = dict( ticker_id=1, direction="long", entry_price=100.0, shares=10.0, status="open", opened_at=datetime(2026, 1, 5, 15, 0), closed_at=None, close_price=None, ) defaults.update(kw) return SimpleNamespace(**defaults) BENCH = { date(2026, 1, 5): 500.0, date(2026, 1, 6): 505.0, date(2026, 1, 7): 510.0, } def test_open_long_marks_to_market_vs_benchmark(): trades = [_trade()] ticker_closes = {1: {date(2026, 1, 5): 100.0, date(2026, 1, 6): 104.0, date(2026, 1, 7): 106.0}} curve = build_equity_curve(trades, ticker_closes, BENCH) assert [p["date"] for p in curve] == ["2026-01-05", "2026-01-06", "2026-01-07"] # Day 3: book +6 * 10 shares; benchmark: 1000 basis * (510-500)/500 = +20 assert curve[-1]["book_pnl"] == 60.0 assert curve[-1]["benchmark_pnl"] == 20.0 def test_closed_trade_freezes_both_legs_at_close_date(): trades = [ _trade( status="closed", closed_at=datetime(2026, 1, 6, 21, 0), close_price=104.0, ) ] # Ticker keeps rising after the close — must NOT affect the curve. ticker_closes = {1: {date(2026, 1, 5): 100.0, date(2026, 1, 6): 104.0, date(2026, 1, 7): 999.0}} curve = build_equity_curve(trades, ticker_closes, BENCH) # Realized +4 * 10 from close date onward. assert curve[-1]["book_pnl"] == 40.0 # Benchmark leg also freezes at the close date: (505-500)/500 * 1000 = +10. assert curve[-1]["benchmark_pnl"] == 10.0 def test_short_direction_and_missing_ticker_prices(): trades = [ _trade(direction="short"), _trade(ticker_id=2), # no price history — contributes nothing ] ticker_closes = {1: {date(2026, 1, 5): 100.0, date(2026, 1, 6): 96.0, date(2026, 1, 7): 90.0}} curve = build_equity_curve(trades, ticker_closes, BENCH) # Short: entry 100 → 90 = +10/share * 10 shares. assert curve[-1]["book_pnl"] == 100.0 # Benchmark counterfactual is long-SPY for the priced trade only. assert curve[-1]["benchmark_pnl"] == 20.0 def test_empty_without_trades_or_benchmark(): assert build_equity_curve([], {}, BENCH) == [] assert build_equity_curve([_trade()], {}, {}) == []