Files
fxhnt/tests/integration/test_paper_compounding.py
jgrusewski 490e6cd575 fix(paper): compounding mark-to-market equity (size book off running equity)
Equity = prior equity + the PRIOR held book marked to today's close
(Σ positions_before.qty·(close−entry)), and the throttled book is sized off
that running equity so gains compound. Previously positions were re-derived
each day at that day's close, so unrealized≡0 and equity only reflected
realized slivers, discarding all held-position appreciation.

Also fix positions_before to anchor on nav rows (one per rebalanced run_date)
instead of position rows, so a FLAT book (full exit / kill-switch, which
persists zero position rows) is honored as the prior: the day after a kill
correctly sees an EMPTY book rather than the last non-empty book before it.

Updated test_backfill_rerun_is_idempotent_* to assert compounding equity
reproduction instead of the old realized-monotone model.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 10:05:21 +02:00

38 lines
2.1 KiB
Python

import datetime as dt
from fxhnt.adapters.persistence.paper_repo import PaperRepo
from fxhnt.application.paper_book import persist_paper_book
def _p(repo, d, px, lev=1.0):
persist_paper_book(repo, capital=100000.0, run_date=d, sleeve_weights={"s": 1.0},
symbol_weights_by_sleeve={"s": {"X": 1.0}}, prices={"X": px},
at=dt.datetime(2026, 1, 1, tzinfo=dt.UTC), leverage=lev)
def test_equity_compounds_off_prior_equity():
r = PaperRepo("sqlite://"); r.migrate()
_p(r, "2026-01-01", 100.0) # day0: no prior -> equity = capital
assert r.nav_history()[-1].equity == 100000.0
_p(r, "2026-01-02", 110.0) # prior qty 1000 *(110-100)=+10000 -> 110000
assert abs(r.nav_history()[-1].equity - 110000.0) < 1e-6
pos = r.positions_at("2026-01-02") # sized off 110000 -> qty 110000/110=1000
assert abs(pos[0].qty - 110000.0 / 110.0) < 1e-6
_p(r, "2026-01-03", 121.0) # +10% again, compounded -> 121000
assert abs(r.nav_history()[-1].equity - 121000.0) < 1e-6
def test_killed_flat_book_keeps_equity_flat():
r = PaperRepo("sqlite://"); r.migrate()
_p(r, "2026-01-01", 100.0)
_p(r, "2026-01-02", 110.0) # equity 110000
_p(r, "2026-01-03", 130.0, lev=0.0) # kill: prior(qty1000)*(130-110)=+20000 -> 130000, FLAT book
assert abs(r.nav_history()[-1].equity - 130000.0) < 1e-6
assert r.positions_at("2026-01-03") == []
_p(r, "2026-01-04", 200.0) # prior empty -> gain 0 -> equity flat 130000
assert abs(r.nav_history()[-1].equity - 130000.0) < 1e-6
def test_rerun_is_idempotent():
r = PaperRepo("sqlite://"); r.migrate()
seq = [("2026-01-01", 100.0), ("2026-01-02", 110.0), ("2026-01-03", 121.0)]
for d, px in seq: _p(r, d, px)
first = [(n.run_date, n.equity) for n in r.nav_history()]
for d, px in seq: _p(r, d, px) # re-run ascending
assert [(n.run_date, n.equity) for n in r.nav_history()] == first