import importlib.util import sys from pathlib import Path import pytest from okx_codex_trader.models import Candle def load_module(): path = Path(__file__).resolve().parents[1] / "scripts" / "search_btc_eth_bbmr_risk_variants.py" spec = importlib.util.spec_from_file_location("search_btc_eth_bbmr_risk_variants", path) assert spec is not None module = importlib.util.module_from_spec(spec) assert spec.loader is not None sys.modules[spec.name] = module spec.loader.exec_module(module) return module def test_add_cost_columns_records_net_metrics(): module = load_module() row = { "total_return": 0.12, "annualized_return": 0.04, "max_drawdown": 0.20, "calmar": 0.20, } module.add_cost_columns(row, "maker_taker", 0.0021) assert row["cost_model"] == "maker_taker" assert row["roundtrip_cost_on_margin"] == pytest.approx(0.0021) assert row["net_total_return"] == pytest.approx(0.12) assert row["net_annualized_return"] == pytest.approx(0.04) assert row["net_max_drawdown"] == pytest.approx(0.20) assert row["net_calmar"] == pytest.approx(0.20) def test_risk_qualified_keeps_recent_horizons_per_cost_model(): module = load_module() pandas = pytest.importorskip("pandas") rows = [] for cost_model, ret_1y in (("maker_taker", 0.11), ("taker_taker", 0.07)): for horizon, total_return in (("full", 0.30), ("3y", 0.20), ("1y", ret_1y), ("6m", 0.08), ("3m", 0.06)): rows.append( { "cost_model": cost_model, "symbol": "BTC", "label": "bbmr-test", "horizon": horizon, "total_return": total_return, "annualized_return": 0.05, "max_drawdown": 0.20, "calmar": 0.25, "trades_per_month": 12.0, } ) qualified = module.risk_qualified(pandas.DataFrame(rows)).sort_values("cost_model").reset_index(drop=True) assert qualified["cost_model"].tolist() == ["maker_taker", "taker_taker"] assert qualified.loc[0, "ret_1y"] == pytest.approx(0.11) assert qualified.loc[1, "ret_1y"] == pytest.approx(0.07) def test_exit_price_for_risk_hit_uses_open_when_long_stop_is_gapped(): module = load_module() position = {"side": "long", "entry_price": 100.0, "stop_price": 99.0} candle = Candle(symbol="BTC-USDT-SWAP", ts=1, open=98.0, high=100.0, low=97.5, close=99.5, volume=1.0) assert module.exit_price_for_risk_hit(position, candle, None) == pytest.approx(98.0) def test_exit_price_for_risk_hit_uses_open_when_short_stop_is_gapped(): module = load_module() position = {"side": "short", "entry_price": 100.0, "stop_price": 101.0} candle = Candle(symbol="ETH-USDT-SWAP", ts=1, open=102.0, high=103.0, low=99.0, close=100.0, volume=1.0) assert module.exit_price_for_risk_hit(position, candle, None) == pytest.approx(102.0) def test_exit_price_for_risk_hit_prefers_open_take_profit_before_intrabar_stop(): module = load_module() position = {"side": "long", "entry_price": 100.0, "stop_price": 99.0} candle = Candle(symbol="BTC-USDT-SWAP", ts=1, open=102.0, high=103.0, low=98.0, close=100.0, volume=1.0) assert module.exit_price_for_risk_hit(position, candle, 101.0) == pytest.approx(102.0)