Coverage for src / monte_neo / monte_carlo / cscv.py: 100%

19 statements  

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

1"""Combinatorial Symmetric Cross-Validation (CSCV) module. 

2 

3Used to detect backtest overfitting and calculate Probability of Overfitting (PBO). 

4""" 

5 

6from __future__ import annotations 

7 

8from typing import TYPE_CHECKING, Any 

9 

10import pandas as pd 

11 

12from monte_neo.utils.logger import get_logger 

13 

14if TYPE_CHECKING: 

15 from monte_neo.indicators.base import BaseIndicator 

16 from monte_neo.metrics.calculator import MetricsCalculator 

17 

18logger = get_logger(__name__) 

19 

20class CSCVAnalyzer: 

21 """Analyzer for Combinatorial Symmetric Cross-Validation.""" 

22 

23 def analyze( 

24 self, 

25 indicator: BaseIndicator, 

26 data: pd.DataFrame, 

27 metrics_calc: MetricsCalculator, 

28 n_segments: int = 10, 

29 ) -> dict[str, Any]: 

30 """Run simplified CSCV analysis. 

31  

32 Args: 

33 indicator: Indicator to test. 

34 data: OHLCV data. 

35 metrics_calc: Metrics calculator. 

36 n_segments: Number of segments to split data into. 

37  

38 Returns: 

39 Dictionary with PBO and robustness metrics. 

40 """ 

41 if len(data) < n_segments: 

42 return {"error": "Insufficient data for CSCV"} 

43 

44 segment_size = len(data) // n_segments 

45 segments = [data.iloc[i*segment_size : (i+1)*segment_size] for i in range(n_segments)] 

46 

47 results = [] 

48 

49 for i in range(n_segments): 

50 test_data = segments[i] 

51 # signals = indicator.generate_signals(test_data) 

52 # metrics = metrics_calc.calculate_all(test_data, signals) 

53 # results.append(metrics.get("sharpe_ratio", 0)) 

54 

55 # Use indicator's own signal generation which might be accelerated 

56 signals = indicator.generate_signals(test_data) 

57 metrics = metrics_calc.calculate_all(test_data, signals) 

58 results.append(metrics.get("sharpe_ratio", 0)) 

59 

60 pbo = sum(1 for r in results if r < 0) / len(results) if results else 1.0 

61 

62 return { 

63 "pbo": pbo, 

64 "segment_scores": results, 

65 "is_robust": pbo < 0.2 

66 }