Files
fxhnt/tests/unit/test_slippage.py
jgrusewski bd73e02774 feat(exec): pure decomposed slippage model
execution_slippage_bps / timing_drift_bps / slippage_vs_assumed_bps —
pure float math (spec §3) for the Bybit testnet real-paper leg. BUY/SELL
sign, bps math, None-guards on non-positive mid/book_mark.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 14:04:03 +02:00

51 lines
2.0 KiB
Python

"""Pure decomposed-slippage model (spec §3). All math over floats — no I/O — so it is unit-tested directly.
The adapter supplies `fill_price`, `mid_at_order`, `book_mark`; the bps math is exercised here in isolation:
sign on both sides, a known fill/mid → exact bps, the decomposition sums to the fill-vs-mark total, the
assumed-tier delta, and the `mid<=0`/`book_mark<=0` None-guards (never a divide-by-zero or a garbage bp).
"""
from __future__ import annotations
from fxhnt.application.slippage import (
execution_slippage_bps,
slippage_vs_assumed_bps,
timing_drift_bps,
)
def test_buy_pays_above_mid_is_positive():
# bought at 101 vs mid 100 → +100 bps cost
assert execution_slippage_bps(101.0, 100.0, "BUY") == 100.0
def test_sell_below_mid_is_positive_cost():
# sold at 99 vs mid 100 → +100 bps cost
assert execution_slippage_bps(99.0, 100.0, "SELL") == 100.0
def test_timing_drift_sign():
# BUY, mid moved 100→101 from the book mark → +100 bps adverse drift
assert timing_drift_bps(101.0, 100.0, "BUY") == 100.0
def test_decomposition_sums_to_total():
fill, mid, mark = 102.0, 101.0, 100.0
exec_bps = execution_slippage_bps(fill, mid, "BUY")
timing_bps = timing_drift_bps(mid, mark, "BUY")
total = exec_bps + timing_bps
# The decomposition (spec §3) DEFINES total_fill_vs_mark_bps = execution + timing. The two terms use
# different denominators (mid for execution, mark for timing), so the sum reconstructs the single-
# denominator fill-vs-mark move ((102-100)/100*1e4 = 200) only up to that ~1bp compounding — it is NOT
# exactly 200 (the plan's `< 1e-6` assertion was off by this compounding term).
fill_vs_mark = (fill - mark) / mark * 1e4
assert abs(total - fill_vs_mark) < 1.0
def test_assumed_delta():
assert slippage_vs_assumed_bps(40.0, 35.0) == 5.0 # 5bp worse than the 35bp tier
def test_guards_none_on_bad_inputs():
assert execution_slippage_bps(101.0, 0.0, "BUY") is None
assert timing_drift_bps(101.0, -1.0, "SELL") is None