98 lines
4.0 KiB
Python
98 lines
4.0 KiB
Python
|
|
"""V3.2: EsoF size-modulation — exact replication of BLUE's SC haircut (profuse)."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import math
|
||
|
|
import sys
|
||
|
|
|
||
|
|
sys.path.insert(0, "/mnt/dolphinng5_predict")
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
from hypothesis import given, settings, strategies as st
|
||
|
|
|
||
|
|
from prod.clean_arch.violet.alpha_wrappers import SizeDecision, VioletBetSizer
|
||
|
|
from prod.clean_arch.violet.modulation import VioletSizeModulation
|
||
|
|
|
||
|
|
MOD = VioletSizeModulation()
|
||
|
|
G = MOD._gate
|
||
|
|
|
||
|
|
|
||
|
|
def _base(vel_div: float = -0.20) -> SizeDecision:
|
||
|
|
return VioletBetSizer(base_fraction=0.20, min_leverage=0.5,
|
||
|
|
max_leverage=9.0).calculate(capital=69_000.0, vel_div=vel_div)
|
||
|
|
|
||
|
|
|
||
|
|
# ── band mapping reproduces BLUE exactly (drift-guarded against live constants) ─
|
||
|
|
|
||
|
|
def test_band_values_match_blue_constants():
|
||
|
|
assert MOD.mult_for(0.0) == pytest.approx(G.ESOF_NEUTRAL_CORE_MULT) # neutral core
|
||
|
|
assert MOD.mult_for(-0.5) == pytest.approx(G.ESOF_UNFAVORABLE_CORE_MULT) # deepest cut
|
||
|
|
assert MOD.mult_for(None) == pytest.approx(
|
||
|
|
min(1.0, max(0.0, G.ESOF_STALE_FALLBACK_MULT))) # stale fallback
|
||
|
|
assert MOD.mult_for(0.5) == pytest.approx(1.0) # full size
|
||
|
|
assert MOD.mult_for(-0.15) == pytest.approx(1.0) # mild-neg full
|
||
|
|
|
||
|
|
|
||
|
|
def test_mult_equals_wrapped_blue_fn_clamped():
|
||
|
|
# mult_for == clamp(esof_size_mult_from_score, 0, 1) for a grid of scores.
|
||
|
|
for sc in [0.3, 0.05, 0.0, -0.03, -0.07, -0.2, -0.25, -0.3, -1.0, None]:
|
||
|
|
expect = max(0.0, min(1.0, float(G.esof_size_mult_from_score(sc))))
|
||
|
|
assert MOD.mult_for(sc) == pytest.approx(expect)
|
||
|
|
|
||
|
|
|
||
|
|
# ── haircut-only [0,1] clamp (BLUE :3316) ──────────────────────────────────────
|
||
|
|
|
||
|
|
@given(score=st.one_of(st.none(),
|
||
|
|
st.floats(min_value=-5.0, max_value=5.0, allow_nan=False,
|
||
|
|
allow_infinity=False)))
|
||
|
|
@settings(max_examples=120, deadline=None)
|
||
|
|
def test_mult_always_in_unit_interval(score):
|
||
|
|
assert 0.0 <= MOD.mult_for(score) <= 1.0
|
||
|
|
|
||
|
|
|
||
|
|
def test_nonfinite_score_is_safe():
|
||
|
|
for bad in (float("nan"), float("inf"), float("-inf")):
|
||
|
|
assert 0.0 <= MOD.mult_for(bad) <= 1.0
|
||
|
|
|
||
|
|
|
||
|
|
# ── apply: near-1 = no-op; haircut scales with exact rounding (BLUE :3318-3338) ─
|
||
|
|
|
||
|
|
def test_near_one_mult_returns_base_unchanged():
|
||
|
|
base = _base()
|
||
|
|
mod, mult = MOD.apply(base, 0.5) # full-size band -> mult 1.0
|
||
|
|
assert mult >= 0.999
|
||
|
|
assert mod is base or mod.conviction_leverage == base.conviction_leverage
|
||
|
|
|
||
|
|
|
||
|
|
def test_haircut_scales_with_blue_rounding():
|
||
|
|
base = _base() # conviction 9.0
|
||
|
|
mod, mult = MOD.apply(base, 0.0) # neutral core -> mult 0.8
|
||
|
|
assert mult == pytest.approx(0.8)
|
||
|
|
assert mod.conviction_leverage == round(base.conviction_leverage * mult, 6)
|
||
|
|
assert mod.notional_fraction == round(base.notional_fraction * mult, 12)
|
||
|
|
# haircut ⇒ strictly smaller size
|
||
|
|
assert mod.conviction_leverage < base.conviction_leverage
|
||
|
|
assert mod.notional_fraction < base.notional_fraction
|
||
|
|
|
||
|
|
|
||
|
|
def test_apply_preserves_fraction_bucket_signal():
|
||
|
|
base = _base()
|
||
|
|
mod, _ = MOD.apply(base, -0.5) # deep haircut
|
||
|
|
assert mod.fraction == base.fraction # only size/leverage scale, not fraction
|
||
|
|
assert mod.bucket_idx == base.bucket_idx
|
||
|
|
assert mod.signal_bucket == base.signal_bucket
|
||
|
|
assert isinstance(mod, SizeDecision)
|
||
|
|
|
||
|
|
|
||
|
|
# ── property: modulated size never exceeds base (haircut-only) and stays finite ─
|
||
|
|
|
||
|
|
@given(vel_div=st.floats(min_value=-0.5, max_value=-0.021, allow_nan=False),
|
||
|
|
score=st.one_of(st.none(), st.floats(min_value=-1.0, max_value=1.0, allow_nan=False)))
|
||
|
|
@settings(max_examples=80, deadline=None)
|
||
|
|
def test_modulated_never_exceeds_base(vel_div, score):
|
||
|
|
base = _base(vel_div)
|
||
|
|
mod, mult = MOD.apply(base, score)
|
||
|
|
assert mod.conviction_leverage <= base.conviction_leverage + 1e-9
|
||
|
|
assert mod.notional_fraction <= base.notional_fraction + 1e-9
|
||
|
|
assert math.isfinite(mod.conviction_leverage) and mod.conviction_leverage >= 0.0
|