import math import importlib.util import sys from pathlib import Path import pytest from okx_codex_trader.models import Candle from okx_codex_trader.rsi2_report import RSI2Config def load_validation_module(): path = Path(__file__).resolve().parents[1] / "scripts" / "validate_external_backtest.py" spec = importlib.util.spec_from_file_location("validate_external_backtest", 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 build_oscillating_candles(count: int) -> list[Candle]: candles = [] previous_close = 100.0 for index in range(count): close = 100.0 + index * 0.04 + math.sin(index / 3.0) * 4.0 open_price = previous_close high = max(open_price, close) + 0.5 low = min(open_price, close) - 0.5 candles.append( Candle( symbol="BTC-USDT-SWAP", ts=index * 60_000, open=open_price, high=high, low=low, close=close, volume=1_000.0, ) ) previous_close = close return candles def test_rsi2_matches_backtesting_py_on_same_signals_and_execution_model(): module = load_validation_module() result = module.validate_rsi2_with_backtesting( candles=build_oscillating_candles(500), symbol="BTC-USDT-SWAP", bar="1m", leverage=3, config=RSI2Config( trend_sma=20, rsi_length=2, rsi_long_threshold=20.0, rsi_short_threshold=80.0, exit_rsi=50.0, ), ) assert result.internal_trades == result.external_trades assert result.internal_trades > 0 assert result.final_equity_diff == pytest.approx(0.0, abs=0.02) assert result.return_diff == pytest.approx(0.0, abs=0.000002) assert result.max_drawdown_diff == pytest.approx(0.0, abs=0.000002)