Coverage for src / monte_neo / metrics / profit_factor.py: 60%

25 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-28 16:27 +0200

1"""Profit Factor metric. 

2 

3Calculates the ratio of gross profits to gross losses. 

4""" 

5 

6from __future__ import annotations 

7 

8import numpy as np 

9 

10from monte_neo.utils.logger import get_logger 

11 

12logger = get_logger(__name__) 

13 

14 

15class ProfitFactorMetric: 

16 """Profit Factor calculator.""" 

17 

18 def calculate(self, pnls: list[float] | np.ndarray) -> float: 

19 """Calculate profit factor. 

20 

21 Profit Factor = Gross Profit / Gross Loss 

22 

23 Values > 1 indicate profitability. 

24 Values > 2 are considered excellent. 

25 

26 Args: 

27 pnls: List of P&L values. 

28 

29 Returns: 

30 Profit factor value. 

31 """ 

32 if not len(pnls): 

33 return 0.0 

34 

35 pnls = np.array(pnls) 

36 

37 gross_profit = np.sum(pnls[pnls > 0]) 

38 gross_loss = abs(np.sum(pnls[pnls < 0])) 

39 

40 if gross_loss == 0: 

41 return float("inf") if gross_profit > 0 else 0.0 

42 

43 return float(gross_profit / gross_loss) 

44 

45 def calculate_rolling( 

46 self, 

47 pnls: list[float] | np.ndarray, 

48 window: int = 20, 

49 ) -> np.ndarray: 

50 """Calculate rolling profit factor. 

51 

52 Args: 

53 pnls: List of P&L values. 

54 window: Rolling window size. 

55 

56 Returns: 

57 Array of rolling profit factors. 

58 """ 

59 if len(pnls) < window: 

60 return np.array([self.calculate(pnls)]) 

61 

62 pnls = np.array(pnls) 

63 rolling_pf = [] 

64 

65 for i in range(window, len(pnls) + 1): 

66 window_pnls = pnls[i - window : i] 

67 rolling_pf.append(self.calculate(window_pnls)) 

68 

69 return np.array(rolling_pf) 

70 

71 def is_acceptable( 

72 self, 

73 pnls: list[float] | np.ndarray, 

74 threshold: float = 1.5, 

75 ) -> bool: 

76 """Check if profit factor meets threshold. 

77 

78 Args: 

79 pnls: List of P&L values. 

80 threshold: Minimum acceptable profit factor. 

81 

82 Returns: 

83 True if profit factor >= threshold. 

84 """ 

85 return self.calculate(pnls) >= threshold