summaryrefslogtreecommitdiff
path: root/services/backtester
diff options
context:
space:
mode:
authorTheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com>2026-04-01 16:24:30 +0900
committerTheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com>2026-04-01 16:24:30 +0900
commit100aa624ad3f8ad466a95f9da8af30f31f77cc9c (patch)
treeef81b9f37872ed462a1f84ea238a130f758782d2 /services/backtester
parent73eaf704584e5bf3c4499ccdd574af87304e1e5f (diff)
fix: resolve lint issues and final integration fixes
- Fix ambiguous variable name in binance_rest.py - Remove unused volumes variable in volume_profile_strategy.py - Fix import ordering in backtester main.py and test_metrics.py - Auto-format all files with ruff
Diffstat (limited to 'services/backtester')
-rw-r--r--services/backtester/src/backtester/config.py1
-rw-r--r--services/backtester/src/backtester/engine.py5
-rw-r--r--services/backtester/src/backtester/main.py25
-rw-r--r--services/backtester/src/backtester/metrics.py23
-rw-r--r--services/backtester/src/backtester/reporter.py1
-rw-r--r--services/backtester/src/backtester/simulator.py1
-rw-r--r--services/backtester/tests/test_engine.py4
-rw-r--r--services/backtester/tests/test_metrics.py17
-rw-r--r--services/backtester/tests/test_reporter.py1
-rw-r--r--services/backtester/tests/test_simulator.py4
10 files changed, 47 insertions, 35 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
diff --git a/services/backtester/tests/test_engine.py b/services/backtester/tests/test_engine.py
index 1a25e1c..6962477 100644
--- a/services/backtester/tests/test_engine.py
+++ b/services/backtester/tests/test_engine.py
@@ -1,13 +1,13 @@
"""Tests for the BacktestEngine."""
+
from datetime import datetime, timezone
from decimal import Decimal
from unittest.mock import MagicMock
-import pytest
from shared.models import Candle, Signal, OrderSide
-from backtester.engine import BacktestEngine, BacktestResult
+from backtester.engine import BacktestEngine
def make_candle(symbol: str, price: float, timeframe: str = "1h") -> Candle:
diff --git a/services/backtester/tests/test_metrics.py b/services/backtester/tests/test_metrics.py
index b222b8a..68bc0b5 100644
--- a/services/backtester/tests/test_metrics.py
+++ b/services/backtester/tests/test_metrics.py
@@ -1,4 +1,6 @@
"""Tests for detailed backtest metrics."""
+
+import math
from datetime import datetime, timedelta, timezone
from decimal import Decimal
@@ -21,9 +23,9 @@ def test_compute_metrics_basic():
"""Two round-trip trades: 1 win, 1 loss. Verify counts and win_rate."""
trades = [
_make_trade("BUY", "100", 0),
- _make_trade("SELL", "120", 10), # win: +20
+ _make_trade("SELL", "120", 10), # win: +20
_make_trade("BUY", "130", 20),
- _make_trade("SELL", "110", 30), # loss: -20
+ _make_trade("SELL", "110", 30), # loss: -20
]
metrics = compute_detailed_metrics(trades, Decimal("10000"), Decimal("10000"))
@@ -37,9 +39,9 @@ def test_compute_metrics_profit_factor():
"""Verify profit_factor = gross_profit / gross_loss."""
trades = [
_make_trade("BUY", "100", 0),
- _make_trade("SELL", "150", 10), # win: +50
+ _make_trade("SELL", "150", 10), # win: +50
_make_trade("BUY", "150", 20),
- _make_trade("SELL", "130", 30), # loss: -20
+ _make_trade("SELL", "130", 30), # loss: -20
]
metrics = compute_detailed_metrics(trades, Decimal("10000"), Decimal("10030"))
@@ -51,9 +53,9 @@ def test_compute_metrics_max_drawdown():
"""Max drawdown should be > 0 when there is a losing trade after a peak."""
trades = [
_make_trade("BUY", "100", 0),
- _make_trade("SELL", "150", 10), # win: equity goes up
+ _make_trade("SELL", "150", 10), # win: equity goes up
_make_trade("BUY", "150", 20),
- _make_trade("SELL", "120", 30), # loss: equity drops
+ _make_trade("SELL", "120", 30), # loss: equity drops
]
metrics = compute_detailed_metrics(trades, Decimal("10000"), Decimal("10020"))
@@ -91,6 +93,3 @@ def test_compute_metrics_empty_trades():
assert metrics.calmar_ratio == 0.0
assert metrics.max_drawdown == 0.0
assert metrics.monthly_returns == {}
-
-
-import math
diff --git a/services/backtester/tests/test_reporter.py b/services/backtester/tests/test_reporter.py
index aef3fc6..2ea49c0 100644
--- a/services/backtester/tests/test_reporter.py
+++ b/services/backtester/tests/test_reporter.py
@@ -1,4 +1,5 @@
"""Tests for the report formatter."""
+
import json
from datetime import timedelta
from decimal import Decimal
diff --git a/services/backtester/tests/test_simulator.py b/services/backtester/tests/test_simulator.py
index 9d8b23e..e8c80ec 100644
--- a/services/backtester/tests/test_simulator.py
+++ b/services/backtester/tests/test_simulator.py
@@ -1,9 +1,9 @@
"""Tests for the OrderSimulator."""
+
from decimal import Decimal
-import pytest
-from shared.models import Signal, OrderSide, OrderType
+from shared.models import Signal, OrderSide
from backtester.simulator import OrderSimulator