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>
This commit is contained in:
157
prod/exec_unified/test_friction.py
Normal file
157
prod/exec_unified/test_friction.py
Normal file
@@ -0,0 +1,157 @@
|
||||
"""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")
|
||||
Reference in New Issue
Block a user