summaryrefslogtreecommitdiff
path: root/services/backtester/tests
diff options
context:
space:
mode:
Diffstat (limited to 'services/backtester/tests')
-rw-r--r--services/backtester/tests/test_metrics.py68
1 files changed, 68 insertions, 0 deletions
diff --git a/services/backtester/tests/test_metrics.py b/services/backtester/tests/test_metrics.py
index 68bc0b5..34314b3 100644
--- a/services/backtester/tests/test_metrics.py
+++ b/services/backtester/tests/test_metrics.py
@@ -93,3 +93,71 @@ def test_compute_metrics_empty_trades():
assert metrics.calmar_ratio == 0.0
assert metrics.max_drawdown == 0.0
assert metrics.monthly_returns == {}
+
+
+def test_recovery_factor():
+ """Recovery factor should be positive when there is a drawdown."""
+ trades = [
+ _make_trade("BUY", "100", 0),
+ _make_trade("SELL", "150", 10), # win
+ _make_trade("BUY", "150", 20),
+ _make_trade("SELL", "120", 30), # loss: creates drawdown
+ ]
+ metrics = compute_detailed_metrics(trades, Decimal("10000"), Decimal("10020"))
+ assert metrics.recovery_factor > 0
+
+
+def test_consecutive_losses():
+ """Consecutive loss tracking should count streaks correctly."""
+ trades = [
+ _make_trade("BUY", "100", 0),
+ _make_trade("SELL", "110", 10), # win
+ _make_trade("BUY", "110", 20),
+ _make_trade("SELL", "105", 30), # loss
+ _make_trade("BUY", "105", 40),
+ _make_trade("SELL", "100", 50), # loss
+ ]
+ metrics = compute_detailed_metrics(trades, Decimal("10000"), Decimal("10005"))
+ assert metrics.max_consecutive_losses >= 1
+ assert metrics.max_consecutive_wins >= 1
+
+
+def test_risk_free_rate_affects_sharpe():
+ """Higher risk-free rate should lower Sharpe ratio."""
+ base = datetime(2025, 1, 1, tzinfo=timezone.utc)
+ trades = [
+ TradeRecord(time=base, symbol="BTCUSDT", side="BUY", price=Decimal("100"), quantity=Decimal("1")),
+ TradeRecord(time=base + timedelta(days=1), symbol="BTCUSDT", side="SELL", price=Decimal("110"), quantity=Decimal("1")),
+ TradeRecord(time=base + timedelta(days=2), symbol="BTCUSDT", side="BUY", price=Decimal("105"), quantity=Decimal("1")),
+ TradeRecord(time=base + timedelta(days=3), symbol="BTCUSDT", side="SELL", price=Decimal("115"), quantity=Decimal("1")),
+ TradeRecord(time=base + timedelta(days=4), symbol="BTCUSDT", side="BUY", price=Decimal("110"), quantity=Decimal("1")),
+ TradeRecord(time=base + timedelta(days=5), symbol="BTCUSDT", side="SELL", price=Decimal("108"), quantity=Decimal("1")),
+ ]
+ m1 = compute_detailed_metrics(trades, Decimal("10000"), Decimal("10018"), risk_free_rate=0.0)
+ m2 = compute_detailed_metrics(trades, Decimal("10000"), Decimal("10018"), risk_free_rate=0.10)
+ assert m2.sharpe_ratio <= m1.sharpe_ratio
+
+
+def test_daily_returns_populated():
+ """Daily returns list should be populated when there are trades."""
+ trades = [
+ _make_trade("BUY", "100", 0),
+ _make_trade("SELL", "110", 60),
+ _make_trade("BUY", "105", 120),
+ _make_trade("SELL", "115", 180),
+ ]
+ metrics = compute_detailed_metrics(trades, Decimal("10000"), Decimal("10020"))
+ assert len(metrics.daily_returns) > 0
+
+
+def test_fee_subtracted_from_pnl():
+ """Fees should be subtracted from trade PnL."""
+ base = datetime(2025, 1, 1, tzinfo=timezone.utc)
+ trades_with_fees = [
+ TradeRecord(time=base, symbol="BTC", side="BUY", price=Decimal("100"), quantity=Decimal("1"), fee=Decimal("1")),
+ TradeRecord(time=base + timedelta(minutes=10), symbol="BTC", side="SELL", price=Decimal("110"), quantity=Decimal("1"), fee=Decimal("1")),
+ ]
+ # PnL should be 10 - 1 - 1 = 8
+ metrics = compute_detailed_metrics(trades_with_fees, Decimal("10000"), Decimal("10008"))
+ assert metrics.winning_trades == 1
+ assert metrics.trade_pairs[0]["pnl"] == pytest.approx(8.0)