diff options
Diffstat (limited to 'services/backtester/src')
| -rw-r--r-- | services/backtester/src/backtester/config.py | 1 | ||||
| -rw-r--r-- | services/backtester/src/backtester/engine.py | 5 | ||||
| -rw-r--r-- | services/backtester/src/backtester/main.py | 25 | ||||
| -rw-r--r-- | services/backtester/src/backtester/metrics.py | 23 | ||||
| -rw-r--r-- | services/backtester/src/backtester/reporter.py | 1 | ||||
| -rw-r--r-- | services/backtester/src/backtester/simulator.py | 1 |
6 files changed, 34 insertions, 22 deletions
diff --git a/services/backtester/src/backtester/config.py b/services/backtester/src/backtester/config.py index bfbc196..5a912f3 100644 --- a/services/backtester/src/backtester/config.py +++ b/services/backtester/src/backtester/config.py @@ -1,4 +1,5 @@ """Configuration for the backtester service.""" + from pydantic_settings import BaseSettings diff --git a/services/backtester/src/backtester/engine.py b/services/backtester/src/backtester/engine.py index 386309b..0441011 100644 --- a/services/backtester/src/backtester/engine.py +++ b/services/backtester/src/backtester/engine.py @@ -1,4 +1,5 @@ """Backtesting engine that runs strategies against historical candle data.""" + from __future__ import annotations from dataclasses import dataclass, field @@ -98,9 +99,7 @@ class BacktestEngine: ) for t in simulator.trades ] - detailed = compute_detailed_metrics( - trade_records, self._initial_balance, final_balance - ) + detailed = compute_detailed_metrics(trade_records, self._initial_balance, final_balance) return BacktestResult( strategy_name=self._strategy.name, diff --git a/services/backtester/src/backtester/main.py b/services/backtester/src/backtester/main.py index ab69ee1..c9b3890 100644 --- a/services/backtester/src/backtester/main.py +++ b/services/backtester/src/backtester/main.py @@ -1,22 +1,21 @@ """Main entry point for the backtester service.""" -import sys + import os +import sys from decimal import Decimal +from shared.db import Database # noqa: E402 +from shared.models import Candle # noqa: E402 + +from backtester.config import BacktestConfig # noqa: E402 +from backtester.engine import BacktestEngine # noqa: E402 +from backtester.reporter import format_report # noqa: E402 + # Allow importing strategies from the strategy-engine service -_STRATEGY_ENGINE_PATH = os.path.join( - os.path.dirname(__file__), "../../../../strategy-engine" -) +_STRATEGY_ENGINE_PATH = os.path.join(os.path.dirname(__file__), "../../../../strategy-engine") if _STRATEGY_ENGINE_PATH not in sys.path: sys.path.insert(0, _STRATEGY_ENGINE_PATH) -from shared.db import Database -from shared.models import Candle - -from backtester.config import BacktestConfig -from backtester.engine import BacktestEngine -from backtester.reporter import format_report - async def run_backtest() -> str: """Load strategy, fetch candles, run backtest, and return a formatted report.""" @@ -35,9 +34,7 @@ async def run_backtest() -> str: strategy = strategy_cls() strategy.configure({}) except Exception as exc: - raise RuntimeError( - f"Failed to load strategy '{config.strategy_name}': {exc}" - ) from exc + raise RuntimeError(f"Failed to load strategy '{config.strategy_name}': {exc}") from exc db = Database(config.database_url) await db.connect() diff --git a/services/backtester/src/backtester/metrics.py b/services/backtester/src/backtester/metrics.py index 15be0e6..caf8477 100644 --- a/services/backtester/src/backtester/metrics.py +++ b/services/backtester/src/backtester/metrics.py @@ -1,4 +1,5 @@ """Detailed backtest metrics: Sharpe, Sortino, drawdown, and more.""" + from __future__ import annotations import math @@ -87,7 +88,9 @@ def compute_detailed_metrics( ) pairs = _pair_trades(trades) - total_return = float(final_balance - initial_balance) / float(initial_balance) if initial_balance else 0.0 + total_return = ( + float(final_balance - initial_balance) / float(initial_balance) if initial_balance else 0.0 + ) if not pairs: return DetailedMetrics( @@ -114,7 +117,9 @@ def compute_detailed_metrics( gross_profit = sum(p["pnl"] for p in wins) gross_loss = abs(sum(p["pnl"] for p in losses)) - profit_factor = gross_profit / gross_loss if gross_loss > 0 else float("inf") if gross_profit > 0 else 0.0 + profit_factor = ( + gross_profit / gross_loss if gross_loss > 0 else float("inf") if gross_profit > 0 else 0.0 + ) avg_win = gross_profit / winning_trades if winning_trades else 0.0 avg_loss = gross_loss / losing_trades if losing_trades else 0.0 @@ -123,7 +128,11 @@ def compute_detailed_metrics( # Holding periods holding_periods = [p["holding_period"] for p in pairs] - avg_holding = sum(holding_periods, timedelta(0)) / len(holding_periods) if holding_periods else timedelta(0) + avg_holding = ( + sum(holding_periods, timedelta(0)) / len(holding_periods) + if holding_periods + else timedelta(0) + ) # Build equity curve from pairs init_bal = float(initial_balance) @@ -153,7 +162,11 @@ def compute_detailed_metrics( max_dd = dd # Duration: use pair exit times if i <= len(pairs) and dd_start_idx < len(pairs): - start_time = pairs[dd_start_idx]["exit_time"] if dd_start_idx < len(pairs) else pairs[0]["entry_time"] + start_time = ( + pairs[dd_start_idx]["exit_time"] + if dd_start_idx < len(pairs) + else pairs[0]["entry_time"] + ) end_time = pairs[i - 1]["exit_time"] max_dd_duration = end_time - start_time if end_time > start_time else timedelta(0) @@ -171,7 +184,7 @@ def compute_detailed_metrics( if len(returns) > 1: mean_r = sum(returns) / len(returns) downside = [min(r, 0.0) for r in returns] - downside_var = sum(d ** 2 for d in downside) / (len(downside) - 1) + downside_var = sum(d**2 for d in downside) / (len(downside) - 1) downside_std = math.sqrt(downside_var) sortino = (mean_r / downside_std * math.sqrt(365)) if downside_std > 0 else 0.0 else: diff --git a/services/backtester/src/backtester/reporter.py b/services/backtester/src/backtester/reporter.py index e9e9936..cc5d67b 100644 --- a/services/backtester/src/backtester/reporter.py +++ b/services/backtester/src/backtester/reporter.py @@ -1,4 +1,5 @@ """Report formatting for backtest results.""" + from __future__ import annotations import csv diff --git a/services/backtester/src/backtester/simulator.py b/services/backtester/src/backtester/simulator.py index b897c5a..33eeb76 100644 --- a/services/backtester/src/backtester/simulator.py +++ b/services/backtester/src/backtester/simulator.py @@ -1,4 +1,5 @@ """Simulated order executor for backtesting.""" + from dataclasses import dataclass, field from datetime import datetime, timezone from decimal import Decimal |
