Files
siloqy/prod/tests/test_long_capability_layers.py
2026-05-08 21:16:53 +02:00

195 lines
7.0 KiB
Python

import math
import sys
from pathlib import Path
from types import SimpleNamespace
import pytest
ROOT = Path("/mnt/dolphinng5_predict")
sys.path.insert(0, str(ROOT / "nautilus_dolphin"))
sys.path.insert(1, str(ROOT))
if "nautilus_dolphin" in sys.modules:
pkg = sys.modules["nautilus_dolphin"]
pkg_file = str(getattr(pkg, "__file__", "") or "")
if not pkg_file.endswith("nautilus_dolphin/nautilus_dolphin/__init__.py"):
del sys.modules["nautilus_dolphin"]
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker, ACBConfig
from nautilus_dolphin.nautilus.alpha_bet_sizer import AlphaBetSizer
from nautilus_dolphin.nautilus.alpha_exit_manager import AlphaExitManager
from nautilus_dolphin.nautilus.alpha_signal_generator import AlphaSignalGenerator
from nautilus_dolphin.nautilus.esf_alpha_orchestrator import NDAlphaEngine
from nautilus_dolphin.nautilus.dolphin_actor import _trade_direction_from_config
def test_signal_generator_long_gate_and_dc_are_side_aware():
gen = AlphaSignalGenerator(use_direction_confirm=True)
rising_prices = [100.0, 100.1, 100.2, 100.3, 100.4, 100.5, 100.6, 101.0]
falling_prices = [101.0, 100.8, 100.6, 100.4, 100.2, 100.0, 99.8, 99.5]
long_sig = gen.generate(
vel_div=0.025,
vel_div_history=[0.012] * 10,
asset_price_history=rising_prices,
trade_direction=1,
)
assert long_sig.is_valid
assert long_sig.direction == 1
assert long_sig.dc_status == "CONFIRM"
contradicted = gen.generate(
vel_div=0.025,
vel_div_history=[0.012] * 10,
asset_price_history=falling_prices,
trade_direction=1,
)
assert not contradicted.is_valid
assert contradicted.dc_status == "SKIP_CONTRADICT"
def test_bet_sizer_trend_multiplier_is_direction_aware_for_long():
sizer = AlphaBetSizer(
base_fraction=0.20,
min_leverage=0.5,
max_leverage=8.0,
use_alpha_layers=True,
use_dynamic_leverage=True,
)
favorable_long = sizer.calculate_size(25000, 0.025, vel_div_trend=0.02, trade_direction=1)
adverse_long = sizer.calculate_size(25000, 0.025, vel_div_trend=-0.02, trade_direction=1)
favorable_short = sizer.calculate_size(25000, -0.035, vel_div_trend=-0.02, trade_direction=-1)
adverse_short = sizer.calculate_size(25000, -0.035, vel_div_trend=0.02, trade_direction=-1)
assert favorable_long["fraction"] > adverse_long["fraction"]
assert favorable_short["fraction"] > adverse_short["fraction"]
def test_ndalphaengine_enters_long_when_begin_day_direction_is_long():
engine = NDAlphaEngine(
initial_capital=1000.0,
use_asset_selection=False,
use_direction_confirm=False,
use_sp_fees=False,
use_sp_slippage=False,
use_ob_edge=False,
use_alpha_layers=False,
use_dynamic_leverage=False,
lookback=1,
)
engine.begin_day("2026-05-08", posture="APEX", direction=1)
res = engine.step_bar(0, vel_div=0.025, prices={"BTCUSDT": 100.0}, vol_regime_ok=True)
assert res["entry"] is not None
assert res["entry"]["direction"] == 1
assert engine.position is not None
assert engine.position.direction == 1
def test_ndalphaengine_short_default_preserved():
engine = NDAlphaEngine(
initial_capital=1000.0,
use_asset_selection=False,
use_direction_confirm=False,
use_sp_fees=False,
use_sp_slippage=False,
use_ob_edge=False,
use_alpha_layers=False,
use_dynamic_leverage=False,
lookback=1,
)
engine.begin_day("2026-05-08", posture="APEX")
res = engine.step_bar(0, vel_div=-0.035, prices={"BTCUSDT": 100.0}, vol_regime_ok=True)
assert res["entry"] is not None
assert res["entry"]["direction"] == -1
def test_acb_short_default_and_long_cache_are_side_separated():
acb = AdaptiveCircuitBreaker()
acb._w750_threshold = 0.001
bullish = {
"funding_btc": 0.0002,
"dvol_btc": 30.0,
"fng": 80.0,
"taker": 1.25,
"available": True,
}
short = acb._calculate_signals(bullish)
long = acb._calculate_signals(bullish, direction=1)
assert short["signals"] == pytest.approx(0.0)
assert long["signals"] == pytest.approx(4.0)
snap = dict(bullish, _acb_ready=True, _staleness_s={})
short_hz = acb.get_dynamic_boost_from_hz("2026-05-08", snap, w750_velocity=0.002, direction=-1)
long_hz = acb.get_dynamic_boost_from_hz("2026-05-08", snap, w750_velocity=0.002, direction=1)
assert short_hz["side"] == "SHORT"
assert long_hz["side"] == "LONG"
assert short_hz["boost"] == pytest.approx(1.0)
assert long_hz["boost"] == pytest.approx(1.0 + 0.5 * math.log1p(4.0))
assert acb.get_dynamic_boost_for_date("2026-05-08")["side"] == "SHORT"
assert acb.get_dynamic_boost_for_date("2026-05-08", direction=1)["side"] == "LONG"
def test_acb_short_threshold_regression_values_still_match_v6():
acb = AdaptiveCircuitBreaker()
factors = {
"funding_btc": -0.0002,
"dvol_btc": 85.0,
"fng": 20.0,
"taker": 0.75,
"available": True,
}
result = acb._calculate_signals(factors)
assert result["signals"] == pytest.approx(4.0)
assert result["severity"] == 7
def test_acb_ob_beta_modulation_is_side_aware():
acb = AdaptiveCircuitBreaker()
acb._w750_threshold = 0.001
calm_ob = SimpleNamespace(
get_macro=lambda: SimpleNamespace(regime_signal=-1, depth_velocity=0.1, cascade_count=0)
)
stress_ob = SimpleNamespace(
get_macro=lambda: SimpleNamespace(regime_signal=1, depth_velocity=-0.3, cascade_count=2)
)
long_calm = acb.get_dynamic_boost_from_hz(
"2026-05-08", {"_acb_ready": True, "_staleness_s": {}}, w750_velocity=0.002, ob_engine=calm_ob, direction=1
)
short_calm = acb.get_dynamic_boost_from_hz(
"2026-05-09", {"_acb_ready": True, "_staleness_s": {}}, w750_velocity=0.002, ob_engine=calm_ob, direction=-1
)
short_stress = acb.get_dynamic_boost_from_hz(
"2026-05-10", {"_acb_ready": True, "_staleness_s": {}}, w750_velocity=0.002, ob_engine=stress_ob, direction=-1
)
assert long_calm["beta"] == pytest.approx(1.0)
assert short_calm["beta"] == pytest.approx(0.68)
assert short_stress["beta"] == pytest.approx(1.0)
def test_exit_manager_optional_vd_exit_is_long_aware():
manager = AlphaExitManager(vd_enabled=True, vd_consec_bars=2)
manager.setup_position("long-1", entry_price=100.0, direction=1, entry_bar=0)
first = manager.evaluate("long-1", current_price=100.1, current_bar=1, vel_div=-0.02)
second = manager.evaluate("long-1", current_price=100.1, current_bar=2, vel_div=-0.02)
assert first["action"] == "HOLD"
assert second["action"] == "EXIT"
assert second["reason"] == "VD_INVALIDATION"
def test_prodgreen_direction_parser_is_explicit_and_case_insensitive():
assert _trade_direction_from_config("LONG_ONLY") == 1
assert _trade_direction_from_config("short_only") == -1
with pytest.raises(ValueError):
_trade_direction_from_config("bidirectional")