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

1"""Utility functions for metrics calculation.""" 

2 

3from __future__ import annotations 

4 

5import numpy as np 

6 

7 

8def max_consecutive(pnls: list[float] | np.ndarray, wins: bool) -> int: 

9 """Calculate max consecutive wins or losses. 

10 

11 Args: 

12 pnls: List of P&L values. 

13 wins: If True, count wins; else count losses. 

14 

15 Returns: 

16 Maximum consecutive count. 

17 """ 

18 max_count = 0 

19 current = 0 

20 

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 

28 

29 return max_count 

30 

31 

32def calculate_recovery_factor(total_return: float, max_dd: float) -> float: 

33 """Calculate recovery factor. 

34 

35 Args: 

36 total_return: Total return percentage. 

37 max_dd: Maximum drawdown (0.0 to 1.0). 

38 

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) 

45 

46 

47def calculate_calmar_ratio( 

48 avg_return: float, max_dd: float, periods_per_year: int = 252 

49) -> float: 

50 """Calculate Calmar ratio. 

51 

52 Args: 

53 avg_return: Average return per period. 

54 max_dd: Maximum drawdown. 

55 periods_per_year: Trading periods per year. 

56 

57 Returns: 

58 Calmar ratio (annual_return / max_drawdown). 

59 """ 

60 if max_dd == 0: 

61 return 0.0 

62 

63 annual_return = avg_return * periods_per_year 

64 return float(annual_return / max_dd) 

65 

66 

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 }