"""Friction telemetry tests (§12). Each asserts a business invariant, not execution. Two are load-bearing and mutation-annotated: the BingX commission SIGN and the side-lane NEVER-RAISES law (b46ebd2). Run: /home/dolphin/siloqy_env/bin/python3 -m pytest prod/exec_unified/test_friction.py -q """ from __future__ import annotations from decimal import Decimal import pytest from prod.exec_unified.contract import Side, UrgencyClass from prod.exec_unified.friction import ( MAKER_FEE_BPS, TAKER_FEE_BPS, FrictionJournal, FrictionRecord, adverse_slippage_bps, effective_friction_bps, fee_bps, fee_cost_from_commission, naive_baseline_bps, savings_vs_naive_bps, ) # ── the commission SIGN (the omp headstone) ────────────────────────────────── def test_negative_commission_is_a_cost_not_a_rebate(): # BingX: negative commission = DEBIT. omp read this as a rebate and was wrong. # Mutation: `return commission` -> this goes RED. assert fee_cost_from_commission(Decimal("-0.013")) == Decimal("0.013") # cost, positive def test_positive_commission_is_a_rebate_negative_cost(): assert fee_cost_from_commission(Decimal("0.005")) == Decimal("-0.005") # earned, negative cost # ── adverse slippage: the side flip is the point ───────────────────────────── def test_buy_above_guideline_is_adverse_sell_is_improvement(): # same raw +0.1% delta: a cost to the buyer, a gift to the seller. buy = adverse_slippage_bps(Decimal("100"), Decimal("100.1"), Side.BUY) sell = adverse_slippage_bps(Decimal("100"), Decimal("100.1"), Side.SELL) assert buy == Decimal("10") # +10 bps adverse assert sell == Decimal("-10") # −10 bps = price improvement # Mutation: drop the side_sign flip -> sell would read +10 and this fails. def test_buy_below_guideline_is_price_improvement(): assert adverse_slippage_bps(Decimal("100"), Decimal("99.9"), Side.BUY) == Decimal("-10") def test_slippage_rejects_nonpositive_reference(): with pytest.raises(ValueError): adverse_slippage_bps(Decimal("0"), Decimal("1"), Side.BUY) # ── fee bps ────────────────────────────────────────────────────────────────── def test_fee_bps_of_notional(): # 0.02 quote cost on 100 notional = 2 bps (the measured maker rate shape). assert fee_bps(Decimal("0.02"), Decimal("100")) == Decimal("2") def test_fee_bps_rejects_nonpositive_notional(): with pytest.raises(ValueError): fee_bps(Decimal("1"), Decimal("0")) # ── effective friction = slippage + fee ────────────────────────────────────── def test_effective_friction_sums_slippage_and_fee(): # BUY 1 unit, guideline 100, fill 100.1 (+10 bps), fee 0.020021 on 100.1 notional (~2 bps). eff = effective_friction_bps(Decimal("100"), Decimal("100.1"), Side.BUY, fee_cost=Decimal("0.020020"), size=Decimal("1")) # 10 bps slippage + ~2 bps fee ≈ 12 bps assert Decimal("11.9") < eff < Decimal("12.1") # ── naive baseline + savings: the before/after ─────────────────────────────── def test_naive_baseline_crosses_spread_plus_taker(): # BUY guideline 100, touch (ask) 100.05 -> 5 bps cross + 5.016 taker = ~10.016 bps. base = naive_baseline_bps(Decimal("100"), Decimal("100.05"), Side.BUY) assert base == Decimal("5") + TAKER_FEE_BPS def test_savings_positive_when_smart_beats_naive(): assert savings_vs_naive_bps(Decimal("4"), Decimal("10")) == Decimal("6") # saved 6 bps assert savings_vs_naive_bps(Decimal("12"), Decimal("10")) == Decimal("-2") # worse than naive def test_maker_rate_is_cheaper_than_taker(): assert MAKER_FEE_BPS < TAKER_FEE_BPS # the whole reason T1 exists # ── record derives its own bps ─────────────────────────────────────────────── def _rec(**kw): base = dict(request_id="r", asset="BTCUSDT", side=Side.BUY, urgency=UrgencyClass.ACQUIRE, maker=True, guideline_px=Decimal("100"), placed_px=Decimal("100"), fill_px=Decimal("100"), touch_px=Decimal("100.05"), size=Decimal("1"), fee_cost=Decimal("0.02")) base.update(kw) return FrictionRecord(**base) def test_record_maker_fill_at_guideline_beats_naive(): # maker filled exactly at guideline (0 slippage) paying 2 bps fee vs naive 5bps cross+5bps taker. r = _rec() assert r.effective_bps() == Decimal("2") # 0 slippage + 2 bps fee assert r.savings_bps() > Decimal("7") # naive ~10.016 − 2 = ~8 bps saved # ── THE side-lane law: telemetry NEVER raises on the exec path (b46ebd2) ────── def test_record_swallows_a_failing_sink(): def boom(_): raise RuntimeError("clickhouse down") j = FrictionJournal(sink=boom) j.record(_rec()) # must NOT raise — a dead sink loses a row, never an order. # Mutation: remove the try/except in record() -> this raises and goes RED. assert len(j.rows()) == 1 # retained locally even though the sink failed def test_record_forwards_to_sink_when_healthy(): seen = [] j = FrictionJournal(sink=seen.append) j.record(_rec(request_id="a")) assert len(seen) == 1 and seen[0].request_id == "a" # ── aggregates: the §15 rung delta ─────────────────────────────────────────── def test_friction_and_savings_by_urgency(): j = FrictionJournal() j.record(_rec(request_id="a", urgency=UrgencyClass.ACQUIRE, fill_px=Decimal("100"))) # 2 bps j.record(_rec(request_id="b", urgency=UrgencyClass.ACQUIRE, fill_px=Decimal("100.1"), fee_cost=Decimal("0.02"))) # ~12 bps j.record(_rec(request_id="c", urgency=UrgencyClass.PROTECT, fill_px=Decimal("100"))) # 2 bps fr = j.friction_by_urgency() assert UrgencyClass.ACQUIRE in fr and UrgencyClass.PROTECT in fr assert fr[UrgencyClass.PROTECT] == Decimal("2") assert Decimal("6") < fr[UrgencyClass.ACQUIRE] < Decimal("8") # mean of ~2 and ~12 sv = j.savings_by_urgency() assert sv[UrgencyClass.PROTECT] > 0 # maker@guideline beats naive def test_aggregate_skips_unbucketable_rows_without_dying(): # a row with a poison reference px must not kill the whole aggregate (side-lane). j = FrictionJournal() j.record(_rec(request_id="ok")) j.record(_rec(request_id="bad", guideline_px=Decimal("0"))) # effective_bps() will raise fr = j.friction_by_urgency() # must still return the good one assert fr[UrgencyClass.ACQUIRE] == Decimal("2")