Files
sentiment-engine/prod/exec_unified/test_friction.py
Codex ec95bd6663 exec(unified): sync local build — kernel_port + dialect §11 + friction §12 + size threading
Lands the /root/dev local increments (share was ENOSPC; now writable) into the canonical repo:
- kernel_port.py: real ExecPort over the DITAv2 kernel (duck-typed; injected to_kernel_intent).
- dialect.py §11: BingX boundary — clientOrderId(H4)/dash/quantize/payload (PostOnly=timeInForce).
- friction.py §12: effective-bps + naive-baseline savings; side-lane journal that can't raise
  (b46ebd2); BingX commission-sign flip. DDL ships with code (register in applier verify-set).
- drive_loop/executor/working: Decimal size threaded through plan/working types (exit size cap).
All pure stdlib+Decimal, mutation-litmus RED on the two load-bearing asserts. 132 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 08:05:26 +02:00

158 lines
7.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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")