From 5cee0686e421b1f21484c23e413692616e9e2ffa Mon Sep 17 00:00:00 2001 From: TheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com> Date: Wed, 1 Apr 2026 18:25:29 +0900 Subject: feat(backtester): Phase 1 complete — realistic backtesting engine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Slippage modeling (configurable per-trade, buy higher/sell lower) - Trading fee deduction (maker/taker configurable) - Stop-loss and take-profit auto-execution per position - Short selling support (allow_short flag) - Walk-forward analysis engine (in-sample/out-of-sample, efficiency ratio) - Daily equity curve Sharpe/Sortino with risk-free rate adjustment - Recovery factor, consecutive win/loss streaks, fee-aware PnL - 312 tests passing --- services/backtester/src/backtester/metrics.py | 2 +- services/backtester/src/backtester/simulator.py | 8 ++------ services/backtester/src/backtester/walk_forward.py | 17 +++++++++++------ 3 files changed, 14 insertions(+), 13 deletions(-) (limited to 'services/backtester/src') diff --git a/services/backtester/src/backtester/metrics.py b/services/backtester/src/backtester/metrics.py index 5b43afd..239cb6f 100644 --- a/services/backtester/src/backtester/metrics.py +++ b/services/backtester/src/backtester/metrics.py @@ -207,7 +207,7 @@ def compute_detailed_metrics( # Sortino (downside deviation of excess returns) downside = [min(r - daily_rf, 0.0) for r in daily_returns] - downside_var = sum(d ** 2 for d in downside) / len(downside) + downside_var = sum(d**2 for d in downside) / len(downside) downside_std = math.sqrt(downside_var) sortino = (mean_excess / downside_std * math.sqrt(365)) if downside_std > 0 else 0.0 else: diff --git a/services/backtester/src/backtester/simulator.py b/services/backtester/src/backtester/simulator.py index 1e37740..64c88dd 100644 --- a/services/backtester/src/backtester/simulator.py +++ b/services/backtester/src/backtester/simulator.py @@ -89,9 +89,7 @@ class OrderSimulator: if triggered and exit_price is not None: # Close the position - close_side = ( - OrderSide.SELL if pos.side == OrderSide.BUY else OrderSide.BUY - ) + close_side = OrderSide.SELL if pos.side == OrderSide.BUY else OrderSide.BUY fee = self._calculate_fee(exit_price, pos.quantity) if pos.side == OrderSide.BUY: @@ -198,9 +196,7 @@ class OrderSimulator: ) return True - def _close_open_position( - self, symbol: str, side: OrderSide, quantity: Decimal - ) -> None: + def _close_open_position(self, symbol: str, side: OrderSide, quantity: Decimal) -> None: """Remove closed quantity from open positions (FIFO).""" remaining_qty = quantity new_positions: list[OpenPosition] = [] diff --git a/services/backtester/src/backtester/walk_forward.py b/services/backtester/src/backtester/walk_forward.py index fe6d020..c7b7fd8 100644 --- a/services/backtester/src/backtester/walk_forward.py +++ b/services/backtester/src/backtester/walk_forward.py @@ -1,4 +1,5 @@ """Walk-forward analysis for strategy parameter optimization.""" + from dataclasses import dataclass, field from decimal import Decimal from typing import Callable @@ -10,6 +11,7 @@ from backtester.engine import BacktestEngine, BacktestResult, StrategyProtocol @dataclass class WalkForwardWindow: """Result for a single in-sample/out-of-sample window.""" + window_index: int in_sample_result: BacktestResult out_of_sample_result: BacktestResult @@ -19,6 +21,7 @@ class WalkForwardWindow: @dataclass class WalkForwardResult: """Aggregated walk-forward analysis results.""" + strategy_name: str symbol: str num_windows: int @@ -130,12 +133,14 @@ class WalkForwardEngine: engine = BacktestEngine(strategy, self._initial_balance) oos_result = engine.run(out_of_sample) - windows.append(WalkForwardWindow( - window_index=i, - in_sample_result=best_is_result, - out_of_sample_result=oos_result, - best_params=best_params, - )) + windows.append( + WalkForwardWindow( + window_index=i, + in_sample_result=best_is_result, + out_of_sample_result=oos_result, + best_params=best_params, + ) + ) return WalkForwardResult( strategy_name=strategy_name, -- cgit v1.2.3