Files
siloqy/prod/docs/VIOLET_SUB_SPEC__L3_EXCHANGE_LEVERAGE.md
Codex f1ee1368d2 VIOLET: master dev spec & plan + parallel L3 exchange-leverage sub-spec
VIOLET_DEV_SPEC_AND_PLAN.md: authoritative consolidated plan (mission, doctrine,
V0->V6 ladder w/ status, code map, full sizing composition, next steps, vision,
TODOs, operational notes). Supersedes scattered plan files.
VIOLET_SUB_SPEC__L3_EXCHANGE_LEVERAGE.md: self-contained parallel-developable unit
(V3.5) for an independent agent — wrap prod/bingx/leverage.py conviction->exchange
mapping, V-TYPES + bit-identity gate, full file paths/tests/gates/acceptance. Zero
overlap with V3.4 (DecisionEngine<->Sizing integration, lead-owned).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 23:14:51 +02:00

8.7 KiB
Raw Blame History

VIOLET Sub-Spec — L3 Exchange-Leverage Wrapper (parallel-developable unit)

Status: READY TO BUILD, independently. Self-contained brief for an autonomous agent running on this host (/mnt/dolphinng5_predict, no git remote). Python /home/dolphin/siloqy_env/bin/python3. Branch exp/pink-ditav2-sprint0-20260530. This is V3.5 of VIOLET_DEV_SPEC_AND_PLAN.md. Develop in parallel with V3.4 (DecisionEngine↔Sizing integration, owned by the lead) — zero file overlap.

1. Objective

Wrap BLUE's conviction→exchange-leverage mapping into a V-TYPES-bounded VIOLET L3 component, bit-identical to the real mapping in prod/bingx/leverage.py. This is the "tradeability" side of the dual-leverage: the bet-sizer's internal conviction leverage [0.5, 9.0] sizes the QUANTITY; the exchange leverage [1, 3] (BingX integer, conservatively capped) is derived from it at the venue boundary. VIOLET needs a typed, observable wrapper for this so V4 (execution) can set venue leverage faithfully.

2. Non-negotiable constraints

  • WRAP, DON'T REIMPLEMENT. Call the real prod/bingx/leverage.py functions; do not re-derive the linear map / rounding. Bit-identity is the gate.
  • ZERO edits to shared files (prod/bingx/leverage.py, prod/nautilus_event_trader.py, prod/clean_arch/dita_v2/*, nautilus_dolphin/**, blue_parity.py). Per-commit: git diff --cached --name-only must not contain them.
  • V-TYPES (prod/clean_arch/violet/domain.py): refined types at boundaries, @typed (beartype) on public methods, StrictModel value objects. Only poison guards (finite + in-domain); NO arbitrary magnitude caps BLUE/leverage.py lacks (a prior reviewer flagged exactly this liberty in the sizing layer — do not repeat it).
  • Exchange-agnostic naming preserved: this is the L3 boundary; keep the internal (conviction) vs exchange distinction explicit in types and field names.

3. The wrap target (authoritative — prod/bingx/leverage.py, 83 lines, no callers)

Constants: CONVICTION_MIN=0.5, CONVICTION_MAX=9.0, EXCHANGE_LEV_MIN=1, EXCHANGE_LEV_MAX=3, LEVERAGE_MAPPING_RULE="round_half_even_linear_0.5_to_9.0_to_1_to_exchange_cap".

Functions to wrap (exact signatures):

def normalize_bingx_leverage_value(leverage, *, exchange_min=EXCHANGE_LEV_MIN,
                                   exchange_max=EXCHANGE_LEV_MAX) -> int
    # ROUND_HALF_EVEN(leverage) clamped to [exchange_min, exchange_max] (BingX int-only)
def map_internal_conviction_to_exchange_leverage_target(internal, *, exchange_min=..,
                                   exchange_max=..) -> float
    # clamp internal to [0.5,9.0]; linear: exch_min + (internal-0.5)/(9.0-0.5) * (exch_max-exch_min)
def map_internal_conviction_to_exchange_leverage(internal, *, exchange_min=.., exchange_max=..) -> int
    # = normalize_bingx_leverage_value(map_..._target(internal), ...)  -> final integer exchange leverage

Behaviours that MUST round-trip bit-identically: the [0.5,9.0] clamp of out-of-range conviction; the linear interpolation; ROUND_HALF_EVEN (banker's rounding — x.5 cases round to even, e.g. 1.5→2, 2.5→2); the integer clamp to [exchange_min, exchange_max]; non-default exchange_min/max args.

4. Deliverable — files to CREATE

4.1 prod/clean_arch/violet/exchange_leverage.py

  • Refined types (V-TYPES, in this file or import from domain): reuse ConvictionLeverage from alpha_wrappers.py (Annotated float gt/ge 0). New: ExchangeLeverage = Annotated[int, Field(ge=1)] (BingX integer; NO upper-cap liberty — the function clamps to exchange_max itself).
  • ExchangeLeverageDecision(StrictModel): internal_conviction: ConvictionLeverage, target_exchange_leverage: float (the pre-round target, allow_inf_nan=False), exchange_leverage: ExchangeLeverage (final int), exchange_min: int, exchange_max: int. Frozen + extra=forbid.
  • VioletExchangeLeverage class:
    • _import_leverage(): import prod.bingx.leverage (it's an in-repo module; plain from prod.bingx import leverage should work since cwd is the root — verify; no nautilus_dolphin root-injection needed).
    • __init__(self, *, exchange_min=1, exchange_max=3): store caps (defaults = gold/BingX).
    • @typed map_target(self, internal_conviction: float) -> float: wraps map_internal_conviction_to_exchange_leverage_target.
    • @typed normalize(self, leverage: float) -> int: wraps normalize_bingx_leverage_value.
    • @typed to_exchange(self, internal_conviction: float) -> ExchangeLeverageDecision: wraps map_internal_conviction_to_exchange_leverage, returns the full decision (target + final + caps) for traceability.
  • Module docstring: the dual-leverage doctrine (conviction sizes quantity; exchange leverage derived at venue boundary), cite FRACTIONAL_LEVERAGE_TO_BINGX_FIX.md and VIOLET_V3_FINDINGS.md §2.

4.2 prod/clean_arch/violet/test_violet_exchange_leverage.py

Mirror the test patterns in test_violet_sizing.py / test_violet_modulation.py (hypothesis + drift-guards + @pytest.mark.gate). Required tests:

Unit:

  • defaults: exchange_min=1, exchange_max=3; constants match leverage.py (drift-guard: import leverage.py and assert CONVICTION_MIN/MAX/EXCHANGE_LEV_MIN/MAX equal the wrapper's).
  • conviction 0.5 → target 1.0; conviction 9.0 → target 3.0 (endpoints).
  • ROUND_HALF_EVEN boundary cases: craft convictions whose target lands on x.5 and assert the final int matches banker's rounding (e.g. target 1.5 → 2, 2.5 → 2). Compute the exact conviction that yields target=1.5/2.5 from the linear map and verify.
  • out-of-range conviction (<0.5, >9.0, and the sizing extremes up to 9) clamps like BLUE.
  • non-default exchange_max (e.g. 5, 9) flows through.
  • ExchangeLeverageDecision frozen (pydantic raises on mutate).

Property (hypothesis):

  • @given conviction ∈ floats[-5, 64] (incl. out-of-range), exchange_max ∈ ints[1,9]: wrapper output == leverage.py output exactly (this is unit-level bit-identity); final exchange_leverage ∈ [exchange_min, exchange_max] and is an int.

Gate (@pytest.mark.gate):

  • test_gate_exchange_leverage_bit_identity: N≥1e6 Monte-Carlo over the joint space (conviction ∈ uniform[-1, 64] to hammer clamping + the full sizing range; exchange_min ∈ {1}, exchange_max ∈ {1,2,3,5,9}). Assert VIOLET to_exchange(...).exchange_leverage and .target_exchange_leverage are float/int-for-float == to the real leverage.py functions across every sample. np.count_nonzero(blue != violet) == 0. Write a gate report to prod/VIOLET_dev/reports/violet_v3_exchange_leverage_<ts>.json (mirror _write_gate_report in test_violet_sizing.py).

5. Validation gate (BINDING)

  1. MC bit-identity (§4.2 gate) at N≥1e6, exact ==, joint conviction×exchange-cap space incl. out-of-domain conviction (clamp coverage) and x.5 rounding boundaries.
  2. Full non-gate suite green; shared-files-clean; the import of prod.bingx.leverage resolves on-host.

6. Acceptance criteria

  • exchange_leverage.py + test_violet_exchange_leverage.py created (no other files touched).
  • MC bit-identity gate: 0/1e6 mismatches.
  • Unit + property tests green; ROUND_HALF_EVEN boundary explicitly tested.
  • git diff --cached --name-only ∌ any shared file.
  • Commit message documents: wrap target, bit-identity result, V-TYPES boundary.

7. Watch-outs (learned from the sizing review)

  • No arbitrary magnitude caps in the V-TYPES aliases (no le=64-style ceilings) — only what leverage.py itself enforces. The function clamps; the type must not double-guard with a value BLUE/leverage.py would accept.
  • ROUND_HALF_EVEN ≠ round-half-up. 2.5 → 2, not 3. Test the even-rounding explicitly.
  • Bit-identity here is "trivial" by design (you call the real function) — that's the point: the gate proves the V-TYPES boundary + arg passing perturb nothing. Do NOT use approx.
  • target (float, pre-round) and exchange_leverage (int, post-round) are BOTH part of the contract — journal/return both.

8. Integration (lead will wire; agent need not)

The lead integrates VioletExchangeLeverage at the L3/exec boundary in V4 (venue leverage-set), consuming VioletSizer's conviction output. The agent's deliverable is the standalone, gated component + tests. Hand back: the two files + the gate report path.

9. References

prod/bingx/leverage.py (target) · prod/docs/FRACTIONAL_LEVERAGE_TO_BINGX_FIX.md (dual-leverage origin) · VIOLET_DEV_SPEC_AND_PLAN.md (V3.5) · VIOLET_V3_FINDINGS.md §2 (dual-leverage) · pattern refs: prod/clean_arch/violet/modulation.py, test_violet_sizing.py (gate + _write_gate_report style), domain.py (V-TYPES).