| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788 |
- 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)
|