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>
8.7 KiB
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.pyfunctions; 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-onlymust not contain them. - V-TYPES (
prod/clean_arch/violet/domain.py): refined types at boundaries,@typed(beartype) on public methods,StrictModelvalue 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
ConvictionLeveragefromalpha_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.VioletExchangeLeverageclass:_import_leverage(): importprod.bingx.leverage(it's an in-repo module; plainfrom prod.bingx import leverageshould 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: wrapsmap_internal_conviction_to_exchange_leverage_target.@typed normalize(self, leverage: float) -> int: wrapsnormalize_bingx_leverage_value.@typed to_exchange(self, internal_conviction: float) -> ExchangeLeverageDecision: wrapsmap_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.mdandVIOLET_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 assertCONVICTION_MIN/MAX/EXCHANGE_LEV_MIN/MAXequal 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. ExchangeLeverageDecisionfrozen (pydantic raises on mutate).
Property (hypothesis):
@givenconviction ∈ 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 VIOLETto_exchange(...).exchange_leverageand.target_exchange_leverageare float/int-for-float==to the real leverage.py functions across every sample.np.count_nonzero(blue != violet) == 0. Write a gate report toprod/VIOLET_dev/reports/violet_v3_exchange_leverage_<ts>.json(mirror_write_gate_reportintest_violet_sizing.py).
5. Validation gate (BINDING)
- 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. - Full non-gate suite green; shared-files-clean; the import of
prod.bingx.leverageresolves on-host.
6. Acceptance criteria
exchange_leverage.py+test_violet_exchange_leverage.pycreated (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) andexchange_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).