Coverage for src / monte_neo / metrics / utils.py: 100%
23 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-28 16:27 +0200
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-28 16:27 +0200
1"""Utility functions for metrics calculation."""
3from __future__ import annotations
5import numpy as np
8def max_consecutive(pnls: list[float] | np.ndarray, wins: bool) -> int:
9 """Calculate max consecutive wins or losses.
11 Args:
12 pnls: List of P&L values.
13 wins: If True, count wins; else count losses.
15 Returns:
16 Maximum consecutive count.
17 """
18 max_count = 0
19 current = 0
21 for pnl in pnls:
22 is_win = pnl > 0
23 if is_win == wins:
24 current += 1
25 max_count = max(max_count, current)
26 else:
27 current = 0
29 return max_count
32def calculate_recovery_factor(total_return: float, max_dd: float) -> float:
33 """Calculate recovery factor.
35 Args:
36 total_return: Total return percentage.
37 max_dd: Maximum drawdown (0.0 to 1.0).
39 Returns:
40 Recovery factor (total_return / max_dd).
41 """
42 if max_dd == 0:
43 return 0.0
44 return float(total_return / max_dd)
47def calculate_calmar_ratio(
48 avg_return: float, max_dd: float, periods_per_year: int = 252
49) -> float:
50 """Calculate Calmar ratio.
52 Args:
53 avg_return: Average return per period.
54 max_dd: Maximum drawdown.
55 periods_per_year: Trading periods per year.
57 Returns:
58 Calmar ratio (annual_return / max_drawdown).
59 """
60 if max_dd == 0:
61 return 0.0
63 annual_return = avg_return * periods_per_year
64 return float(annual_return / max_dd)
67def get_empty_metrics() -> dict[str, float]:
68 """Return empty metrics when no trades."""
69 return {
70 "profit_factor": 0.0,
71 "total_return": 0.0,
72 "avg_return": 0.0,
73 "sharpe_ratio": 0.0,
74 "sortino_ratio": 0.0,
75 "max_drawdown": 0.0,
76 "avg_drawdown": 0.0,
77 "recovery_factor": 0.0,
78 "calmar_ratio": 0.0,
79 "winrate": 0.0,
80 "expectancy": 0.0,
81 "avg_win": 0.0,
82 "avg_loss": 0.0,
83 "win_loss_ratio": 0.0,
84 "trade_count": 0.0,
85 "consecutive_wins": 0.0,
86 "consecutive_losses": 0.0,
87 }