exec(unified): sync real KernelIntent translator §7 — verified side/size/cancel

intent_translator.py: the injected to_kernel_intent KernelExecPort needs, wiring the pure
engine to the live DITAv2 kernel. Side/size/cancel mappings each verified against vendored
source (bingx_venue:627, rust_backend:448/897); EXIT inversion mutation-verified RED. Tested
against REAL dita_v2 (importorskip). Lazy dita_v2 import keeps the rest of the package clean.
143 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Codex
2026-07-15 08:17:39 +02:00
parent ec95bd6663
commit 5abe0cc6e0
2 changed files with 223 additions and 0 deletions

View File

@@ -0,0 +1,104 @@
"""IntentSpec → dita_v2 KernelIntent — the ONE module allowed to import dita_v2 (spec §7).
``kernel_port.KernelExecPort`` takes an INJECTED ``to_kernel_intent`` so the port (and the
whole pure engine) never imports the vendored kernel and stays local-testable against a fake.
This module supplies the real translator. The dita_v2 import is LAZY (inside the factory) so
merely importing this module never drags in dita_v2 — you pay the dependency only when you
actually wire a live kernel.
THREE mappings, each VERIFIED against the vendored kernel source (not assumed — a wrong branch
here fires real wrong-direction / wrong-size orders):
* SIDE (bingx_venue.py:627-628 `_legacy_intent`): ``KernelIntent.side`` is the POSITION side
(TradeSide.LONG/SHORT); the venue BUY/SELL is derived DOWNSTREAM from (side, action). Our
engine speaks ORDER side (Side.BUY/SELL), so the translator inverts on the reducing leg:
ENTER BUY→LONG SELL→SHORT (opening: order side == position direction)
EXIT BUY→SHORT SELL→LONG (reduceOnly: order side is opposite the position)
Truth table is exhaustively + mutation-tested. Getting one cell wrong = catastrophic.
* SIZE / LEVERAGE (rust_backend.py:448, mock_venue.py:60/88/168/198): the submitted order
quantity is ``float(intent.target_size)`` VERBATIM — there is NO ``target_size × leverage``
in the quantity path (leverage rides separately for margin/accounting). So target_size =
spec.size and leverage = 1.0 is correct AND leverage-agnostic (§2.3). The "×leverage poison"
in [[ditav2_kernel_audit]] was in the capital/PnL path, not the order-quantity path.
* CANCEL side (rust_backend.py:897-909): a CANCEL resolves its target via the slot's
active_entry/exit order by slot_id — ``intent.side`` is NOT consulted. So CANCEL.side is
informational-only; we set it by the reducing-leg convention and it changes nothing.
Pure translation. The datetime source is INJECTED (no ambient clock — §NFR).
"""
from __future__ import annotations
from datetime import datetime
from typing import Any, Callable
from .contract import Side
from .kernel_port import IntentSpec
# One-way flatten: single slot, unit leverage (size is the final order quantity).
_SLOT_ID = 0
_UNIT_LEVERAGE = 1.0
def make_kernel_intent_translator(
clock: Callable[[], datetime],
*,
slot_id: int = _SLOT_ID,
leverage: float = _UNIT_LEVERAGE,
) -> Callable[[IntentSpec], Any]:
"""Build a ``to_kernel_intent(spec) -> KernelIntent`` bound to an injected datetime clock.
dita_v2 is imported HERE, lazily, so the module stays importable without it. Tries the
bare package name first, then the in-repo path, so it works whether ``clean_arch`` or the
repo root is the import anchor."""
try:
from dita_v2.contracts import KernelCommandType, KernelIntent, TradeSide
except ImportError: # pragma: no cover - path depends on how the kernel is vendored
from prod.clean_arch.dita_v2.contracts import ( # type: ignore
KernelCommandType, KernelIntent, TradeSide,
)
_ACTION = {
"ENTER": KernelCommandType.ENTER,
"EXIT": KernelCommandType.EXIT,
"CANCEL": KernelCommandType.CANCEL,
}
def _position_side(order_side: Side, action_kind: str):
"""Order side (BUY/SELL) + action → POSITION side (LONG/SHORT). Verified truth table.
ENTER is the only OPENING action: there the order side IS the position direction. Every
other action (EXIT, CANCEL) is on the reducing leg, where the order side is the OPPOSITE
of the position it closes. So: is_long = BUY on open, SELL on reduce."""
opening = action_kind == "ENTER"
is_buy = order_side is Side.BUY
is_long = is_buy if opening else not is_buy
return TradeSide.LONG if is_long else TradeSide.SHORT
def to_kernel_intent(spec: IntentSpec):
action = _ACTION.get(spec.action_kind)
if action is None:
raise ValueError(f"unknown action_kind {spec.action_kind!r} (want ENTER|EXIT|CANCEL)")
return KernelIntent(
timestamp=clock(),
intent_id=spec.request_id,
trade_id=spec.base_request_id,
slot_id=slot_id,
asset=spec.asset,
side=_position_side(spec.side, spec.action_kind),
action=action,
reference_price=float(spec.limit_price),
target_size=float(spec.size), # VERBATIM order quantity — never ×leverage
leverage=leverage,
reason=spec.reason,
order_type=spec.order_type, # "LIMIT" | "MARKET" (matches KernelIntent default)
limit_price=float(spec.limit_price), # 0 for MARKET (spec builds it that way)
metadata={
"tif": spec.tif, # "PostOnly" | "GTC" — venue TIF rides metadata
"reduce_only": spec.reduce_only,
"exec_unified": True, # provenance: this intent came from the unified layer
},
)
return to_kernel_intent

View File

@@ -0,0 +1,119 @@
"""IntentSpec → real dita_v2 KernelIntent translation (spec §7). Runs against the ACTUAL
vendored kernel contracts (not fakes) so the field mapping is verified where it matters.
The side truth table is the load-bearing test: a single wrong cell = a real wrong-direction
order. Mutation: drop the `not is_buy` inversion in _position_side -> the EXIT rows go RED.
dita_v2 lives under prod/clean_arch; add it to the path if the repo root isn't the anchor.
If it truly can't be imported (a box without the kernel), skip loudly rather than fake it.
Run: /home/dolphin/siloqy_env/bin/python3 -m pytest prod/exec_unified/test_intent_translator.py -q
"""
from __future__ import annotations
import os
import sys
from datetime import datetime
from decimal import Decimal
import pytest
# Anchor dita_v2: try the repo's clean_arch dir (works both at /mnt and /root/dev copies).
for _cand in ("/mnt/dolphinng5_predict/prod/clean_arch",
os.path.join(os.path.dirname(__file__), "..", "clean_arch")):
if os.path.isdir(_cand) and _cand not in sys.path:
sys.path.insert(0, _cand)
dita = pytest.importorskip("dita_v2.contracts", reason="vendored dita_v2 not importable on this box")
TradeSide = dita.TradeSide
KernelCommandType = dita.KernelCommandType
from prod.exec_unified.contract import Side
from prod.exec_unified.intent_translator import make_kernel_intent_translator
from prod.exec_unified.kernel_port import IntentSpec
FIXED_TS = datetime(2026, 7, 15, 12, 0, 0)
def _xlate():
return make_kernel_intent_translator(lambda: FIXED_TS)
def _spec(**kw):
base = dict(request_id="req-0", base_request_id="pos-1", asset="BTCUSDT", side=Side.BUY,
action_kind="ENTER", order_type="LIMIT", limit_price=Decimal("100.5"),
size=Decimal("0.001"), tif="PostOnly", reduce_only=False, reason="test")
base.update(kw)
return IntentSpec(**base)
# ── THE side truth table (verified vs bingx_venue._legacy_intent) ─────────────
@pytest.mark.parametrize("action,order_side,expect_pos", [
("ENTER", Side.BUY, TradeSide.LONG), # open long = buy
("ENTER", Side.SELL, TradeSide.SHORT), # open short = sell
("EXIT", Side.SELL, TradeSide.LONG), # close long = sell (INVERTED)
("EXIT", Side.BUY, TradeSide.SHORT), # close short = buy (INVERTED)
])
def test_position_side_truth_table(action, order_side, expect_pos):
reduce = action != "ENTER"
ki = _xlate()(_spec(action_kind=action, side=order_side, reduce_only=reduce,
order_type="MARKET" if reduce else "LIMIT",
limit_price=Decimal("0") if reduce else Decimal("100.5")))
assert ki.side is expect_pos
# Mutation: `is_long = is_buy` (drop the inversion) -> the two EXIT rows fail.
# ── size / leverage: quantity is verbatim, never levered ─────────────────────
def test_target_size_is_verbatim_and_leverage_is_unit():
ki = _xlate()(_spec(size=Decimal("0.0731")))
assert ki.target_size == 0.0731 # the exact order quantity — no ×leverage
assert ki.leverage == 1.0 # unit: exec_unified is leverage-agnostic (§2.3)
def test_custom_leverage_never_scales_size():
# even with a non-unit leverage the quantity is untouched (leverage is accounting-only).
ki = make_kernel_intent_translator(lambda: FIXED_TS, leverage=5.0)(_spec(size=Decimal("2")))
assert ki.target_size == 2.0
assert ki.leverage == 5.0
# ── action + order_type + price passthrough ──────────────────────────────────
def test_action_mapping():
assert _xlate()(_spec(action_kind="ENTER")).action is KernelCommandType.ENTER
assert _xlate()(_spec(action_kind="EXIT", reduce_only=True)).action is KernelCommandType.EXIT
assert _xlate()(_spec(action_kind="CANCEL")).action is KernelCommandType.CANCEL
def test_unknown_action_raises():
with pytest.raises(ValueError):
_xlate()(_spec(action_kind="FROBNICATE"))
def test_maker_limit_carries_price_and_tif_metadata():
ki = _xlate()(_spec(order_type="LIMIT", limit_price=Decimal("64547.5"), tif="PostOnly"))
assert ki.order_type == "LIMIT"
assert ki.limit_price == 64547.5 and ki.reference_price == 64547.5
assert ki.metadata["tif"] == "PostOnly"
assert ki.metadata["reduce_only"] is False
assert ki.metadata["exec_unified"] is True
def test_market_carries_zero_price():
ki = _xlate()(_spec(action_kind="EXIT", side=Side.SELL, reduce_only=True,
order_type="MARKET", limit_price=Decimal("0")))
assert ki.order_type == "MARKET"
assert ki.limit_price == 0.0
# ── identity + clock ─────────────────────────────────────────────────────────
def test_ids_and_slot_and_injected_clock():
ki = _xlate()(_spec(request_id="req-3", base_request_id="pos-9"))
assert ki.intent_id == "req-3" # per-attempt id
assert ki.trade_id == "pos-9" # position identity
assert ki.slot_id == 0 # single-slot flatten
assert ki.timestamp == FIXED_TS # injected, deterministic — no ambient now()
assert ki.asset == "BTCUSDT" # undashed at kernel layer (dialect dashes at venue)