diff options
Diffstat (limited to 'services/backtester/tests/test_walk_forward.py')
| -rw-r--r-- | services/backtester/tests/test_walk_forward.py | 103 |
1 files changed, 103 insertions, 0 deletions
diff --git a/services/backtester/tests/test_walk_forward.py b/services/backtester/tests/test_walk_forward.py new file mode 100644 index 0000000..e672dac --- /dev/null +++ b/services/backtester/tests/test_walk_forward.py @@ -0,0 +1,103 @@ +"""Tests for walk-forward analysis.""" +import sys +from pathlib import Path +from decimal import Decimal +from datetime import datetime, timedelta, timezone + +import pytest + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) +sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "strategy-engine")) + +from shared.models import Candle +from backtester.walk_forward import WalkForwardEngine, WalkForwardResult +from strategies.rsi_strategy import RsiStrategy + + +def _generate_candles(n=100, base_price=100.0): + candles = [] + for i in range(n): + # Simple oscillating price + price = base_price + (i % 20) - 10 + candles.append(Candle( + symbol="BTCUSDT", timeframe="1h", + open_time=datetime(2025, 1, 1, tzinfo=timezone.utc) + timedelta(hours=i), + open=Decimal(str(price)), + high=Decimal(str(price + 5)), + low=Decimal(str(price - 5)), + close=Decimal(str(price)), + volume=Decimal("100"), + )) + return candles + + +def test_walk_forward_basic(): + param_grid = [ + {"period": 5, "oversold": 30, "overbought": 70, "quantity": "0.1"}, + {"period": 10, "oversold": 25, "overbought": 75, "quantity": "0.1"}, + ] + engine = WalkForwardEngine( + strategy_factory=RsiStrategy, + param_grid=param_grid, + initial_balance=Decimal("10000"), + num_windows=3, + ) + candles = _generate_candles(150) + result = engine.run(candles) + + assert isinstance(result, WalkForwardResult) + assert result.strategy_name == "rsi" + assert result.num_windows > 0 + + +def test_walk_forward_efficiency_ratio(): + param_grid = [ + {"period": 5, "oversold": 30, "overbought": 70, "quantity": "0.1"}, + ] + engine = WalkForwardEngine( + strategy_factory=RsiStrategy, + param_grid=param_grid, + num_windows=2, + ) + candles = _generate_candles(100) + result = engine.run(candles) + + # Efficiency ratio should be a finite number + assert isinstance(result.efficiency_ratio, float) + + +def test_walk_forward_empty_candles(): + engine = WalkForwardEngine( + strategy_factory=RsiStrategy, + param_grid=[{"period": 5}], + ) + result = engine.run([]) + assert result.num_windows == 0 + + +def test_walk_forward_too_few_candles(): + engine = WalkForwardEngine( + strategy_factory=RsiStrategy, + param_grid=[{"period": 5}], + num_windows=10, + ) + candles = _generate_candles(20) + result = engine.run(candles) + assert result.num_windows == 0 # window_size < 10 + + +def test_walk_forward_selects_best_params(): + param_grid = [ + {"period": 3, "oversold": 40, "overbought": 60, "quantity": "0.1"}, # very aggressive + {"period": 14, "oversold": 30, "overbought": 70, "quantity": "0.1"}, # standard + ] + engine = WalkForwardEngine( + strategy_factory=RsiStrategy, + param_grid=param_grid, + num_windows=2, + ) + candles = _generate_candles(200) + result = engine.run(candles) + + for window in result.windows: + assert window.best_params in param_grid |
