Files
siloqy/prod/clean_arch/violet/test_violet_exchange_leverage.py

186 lines
6.4 KiB
Python
Raw Normal View History

from __future__ import annotations
import json
from datetime import datetime, timezone
from pathlib import Path
import numpy as np
import pytest
from hypothesis import given, settings, strategies as st
from pydantic import ValidationError
from prod.clean_arch.violet.exchange_leverage import (
CONVICTION_MAX,
CONVICTION_MIN,
EXCHANGE_LEV_MAX,
EXCHANGE_LEV_MIN,
ExchangeLeverageDecision,
VioletExchangeLeverage,
)
from prod.bingx import leverage as bingx_leverage
REPORTS_DIR = Path("/mnt/dolphinng5_predict/prod/VIOLET_dev/reports")
def _write_gate_report(name: str, **fields):
REPORTS_DIR.mkdir(parents=True, exist_ok=True)
ts = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
payload = {
"generated_utc": datetime.now(timezone.utc).isoformat(),
"layer": f"violet_v3_{name}",
**fields,
}
path = REPORTS_DIR / f"violet_v3_{name}_{ts}.json"
path.write_text(json.dumps(payload, indent=2, default=str))
return path
def _wrapper(exchange_min: int = EXCHANGE_LEV_MIN, exchange_max: int = EXCHANGE_LEV_MAX) -> VioletExchangeLeverage:
return VioletExchangeLeverage(exchange_min=exchange_min, exchange_max=exchange_max)
def test_defaults_and_constants_match_blue_module():
w = _wrapper()
assert w.exchange_min == 1
assert w.exchange_max == 3
assert CONVICTION_MIN == bingx_leverage.CONVICTION_MIN
assert CONVICTION_MAX == bingx_leverage.CONVICTION_MAX
assert EXCHANGE_LEV_MIN == bingx_leverage.EXCHANGE_LEV_MIN
assert EXCHANGE_LEV_MAX == bingx_leverage.EXCHANGE_LEV_MAX
def test_endpoints_map_cleanly():
w = _wrapper()
assert w.map_target(0.5) == 1.0
assert w.map_target(9.0) == 3.0
assert w.normalize(1.0) == 1
assert w.normalize(3.0) == 3
def test_round_half_even_boundary_cases():
w = _wrapper()
low = 0.5 + ((1.5 - 1.0) / (3.0 - 1.0)) * (9.0 - 0.5)
high = 0.5 + ((2.5 - 1.0) / (3.0 - 1.0)) * (9.0 - 0.5)
assert w.map_target(low) == pytest.approx(1.5)
assert w.map_target(high) == pytest.approx(2.5)
assert w.normalize(w.map_target(low)) == 2
assert w.normalize(w.map_target(high)) == 2
assert w.to_exchange(low).exchange_leverage == 2
assert w.to_exchange(high).exchange_leverage == 2
@pytest.mark.parametrize("conviction", [-10.0, -1.0, 0.0, 0.49, 9.1, 64.0])
@pytest.mark.parametrize("exchange_max", [1, 2, 3, 5, 9])
def test_out_of_range_clamps_like_blue(conviction, exchange_max):
w = _wrapper(exchange_max=exchange_max)
assert w.map_target(conviction) == bingx_leverage.map_internal_conviction_to_exchange_leverage_target(
conviction,
exchange_min=1,
exchange_max=exchange_max,
)
assert w.normalize(conviction) == bingx_leverage.normalize_bingx_leverage_value(
conviction,
exchange_min=1,
exchange_max=exchange_max,
)
assert w.to_exchange(conviction).exchange_leverage == bingx_leverage.map_internal_conviction_to_exchange_leverage(
conviction,
exchange_min=1,
exchange_max=exchange_max,
)
@pytest.mark.parametrize("exchange_max", [5, 9])
def test_non_default_exchange_max_flows_through(exchange_max):
w = _wrapper(exchange_max=exchange_max)
conviction = 6.25
decision = w.to_exchange(conviction)
assert decision.exchange_min == 1
assert decision.exchange_max == exchange_max
assert decision.target_exchange_leverage == bingx_leverage.map_internal_conviction_to_exchange_leverage_target(
conviction, exchange_min=1, exchange_max=exchange_max
)
assert decision.exchange_leverage == bingx_leverage.map_internal_conviction_to_exchange_leverage(
conviction, exchange_min=1, exchange_max=exchange_max
)
def test_exchange_leverage_decision_is_frozen():
d = ExchangeLeverageDecision(
internal_conviction=1.5,
target_exchange_leverage=1.25,
exchange_leverage=1,
exchange_min=1,
exchange_max=3,
)
with pytest.raises(ValidationError):
d.exchange_leverage = 2
@given(
conviction=st.floats(min_value=-5.0, max_value=64.0, allow_nan=False, allow_infinity=False),
exchange_max=st.sampled_from([1, 2, 3, 5, 9]),
)
@settings(max_examples=200, deadline=None)
def test_property_bit_identity(conviction, exchange_max):
w = _wrapper(exchange_max=exchange_max)
target = w.map_target(conviction)
blue_target = bingx_leverage.map_internal_conviction_to_exchange_leverage_target(
conviction,
exchange_min=1,
exchange_max=exchange_max,
)
assert target == blue_target
final = w.to_exchange(conviction)
blue_final = bingx_leverage.map_internal_conviction_to_exchange_leverage(
conviction,
exchange_min=1,
exchange_max=exchange_max,
)
assert final.target_exchange_leverage == blue_target
assert final.exchange_leverage == blue_final
assert isinstance(final.exchange_leverage, int)
assert 1 <= final.exchange_leverage <= exchange_max
@pytest.mark.gate
def test_gate_exchange_leverage_bit_identity():
rng = np.random.default_rng(0)
n = 1_000_000
conviction = rng.uniform(-1.0, 64.0, n)
exchange_max = rng.choice(np.array([1, 2, 3, 5, 9], dtype=np.int64), n)
violet = np.empty(n, dtype=np.int64)
blue = np.empty(n, dtype=np.int64)
violet_target = np.empty(n, dtype=np.float64)
blue_target = np.empty(n, dtype=np.float64)
w_cache: dict[int, VioletExchangeLeverage] = {}
for i in range(n):
ex_max = int(exchange_max[i])
w = w_cache.get(ex_max)
if w is None:
w = w_cache[ex_max] = _wrapper(exchange_max=ex_max)
conv = float(conviction[i])
violet_target[i] = w.map_target(conv)
violet[i] = w.to_exchange(conv).exchange_leverage
blue_target[i] = bingx_leverage.map_internal_conviction_to_exchange_leverage_target(
conv, exchange_min=1, exchange_max=ex_max
)
blue[i] = bingx_leverage.map_internal_conviction_to_exchange_leverage(
conv, exchange_min=1, exchange_max=ex_max
)
target_mismatches = int(np.count_nonzero(violet_target != blue_target))
final_mismatches = int(np.count_nonzero(violet != blue))
total_mismatches = target_mismatches + final_mismatches
_write_gate_report(
"exchange_leverage",
N=n,
target_mismatches=target_mismatches,
final_mismatches=final_mismatches,
mismatches=total_mismatches,
exchange_max_values=[1, 2, 3, 5, 9],
)
assert total_mismatches == 0