test(paper): update full-exit/partial-reduction equity tests to compounding model
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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():
|
||||
|
||||
Reference in New Issue
Block a user