diff --git a/tests/integration/test_paper_book.py b/tests/integration/test_paper_book.py index 124df1e..3e2e158 100644 --- a/tests/integration/test_paper_book.py +++ b/tests/integration/test_paper_book.py @@ -47,33 +47,38 @@ def test_derive_and_persist_is_idempotent_per_run_date(): def test_realized_pnl_on_full_exit(): + # Compounding MTM model: the FULL held-book gain (not just a realized sliver) flows into the nav curve, + # so equity reflects it even after a full exit to cash. repo = PaperRepo("sqlite://") repo.migrate() - svc = PaperBookService(repo, capital=100_000.0) - # day 1: long BTC, full notional 100k at price 100 -> qty 1000 - _derive(svc, "2026-06-21", 1.0, {"BTC": 100.0}, "2026-06-21") - # day 2: fully exit (weight 0) at price 110 -> realized = 1000 * (110-100) = 10_000 - _derive(svc, "2026-06-22", 0.0, {"BTC": 110.0}, "2026-06-22") - view = svc.view(live=FakeLivePrice({})) # no open positions + at = dt.datetime(2026, 6, 22, tzinfo=dt.UTC) + # day 1: long BTC full 100k at 100 -> qty 1000 (sized off equity = capital) + persist_paper_book(repo, capital=100_000.0, run_date="2026-06-21", sleeve_weights={"s": 1.0}, + symbol_weights_by_sleeve={"s": {"BTC": 1.0}}, prices={"BTC": 100.0}, at=at) + # day 2: fully exit (sleeve weight 0) at 110 -> prior book marked: gain 1000*(110-100)=10_000 -> equity 110k, flat + persist_paper_book(repo, capital=100_000.0, run_date="2026-06-22", sleeve_weights={"s": 0.0}, + symbol_weights_by_sleeve={"s": {"BTC": 1.0}}, prices={"BTC": 110.0}, at=at) + view = PaperBookService(repo, capital=100_000.0).view(live=FakeLivePrice({})) # no open positions assert not view.positions - assert view.equity == 110_000.0 # capital + realized 10k, even with no open book + assert view.equity == pytest.approx(110_000.0) # full book gain captured by the compounding nav curve def test_realized_pnl_on_partial_reduction(): + # Compounding MTM model: holding the full book into the up-day captures the whole gain in the nav curve; + # the reduction still records a realized sliver (now display-only, not the equity driver). repo = PaperRepo("sqlite://") repo.migrate() - svc = PaperBookService(repo, capital=100_000.0) + at = dt.datetime(2026, 6, 22, tzinfo=dt.UTC) # day 1: long BTC full 100k at 100 -> qty 1000 - _derive(svc, "2026-06-21", 1.0, {"BTC": 100.0}, "2026-06-21") - # day 2: reduce to half weight at price 110. target notional 50k / 110 = ~454.5 qty. - # realized on the reduced (sold) qty = (1000 - 454.5...) * (110 - 100) - _derive(svc, "2026-06-22", 0.5, {"BTC": 110.0}, "2026-06-22") - closed = 1000.0 - (0.5 * 100_000.0 / 110.0) - expected_realized = closed * (110.0 - 100.0) - assert repo.realized_pnl() == pytest.approx(expected_realized) - view = svc.view(live=FakeLivePrice({"BTC": 110.0})) - # equity = capital + realized + unrealized(0 since last==entry on the surviving lot) - assert view.equity == pytest.approx(100_000.0 + expected_realized) + persist_paper_book(repo, capital=100_000.0, run_date="2026-06-21", sleeve_weights={"s": 1.0}, + symbol_weights_by_sleeve={"s": {"BTC": 1.0}}, prices={"BTC": 100.0}, at=at) + # day 2: reduce to half sleeve weight at 110 -> prior full book (qty 1000) marked: gain 1000*(110-100)=10_000 + persist_paper_book(repo, capital=100_000.0, run_date="2026-06-22", sleeve_weights={"s": 0.5}, + symbol_weights_by_sleeve={"s": {"BTC": 1.0}}, prices={"BTC": 110.0}, at=at) + assert repo.realized_pnl() > 0.0 # reduction still records a realized sliver (display-only) + view = PaperBookService(repo, capital=100_000.0).view(live=FakeLivePrice({"BTC": 110.0})) + # equity = compounding nav curve (110k, full book gain) + intraday (0: surviving lot entry=110, marked @110) + assert view.equity == pytest.approx(110_000.0) def test_persist_paper_book_writes_nav():