Coverage for src / monte_neo / utils / config.py: 80%

76 statements  

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

1"""Utility configuration module.""" 

2 

3from __future__ import annotations 

4 

5import os 

6from dataclasses import dataclass, field 

7from pathlib import Path 

8 

9import yaml 

10from dotenv import load_dotenv 

11 

12 

13@dataclass 

14class Config: 

15 """Application configuration.""" 

16 

17 # Data settings 

18 data_dir: Path = field(default_factory=lambda: Path("./data")) 

19 binance_api_key: str = "" 

20 binance_api_secret: str = "" 

21 

22 # Generation settings 

23 default_symbol: str = "BTCUSDT" 

24 default_timeframe: str = "1h" 

25 max_iterations: int = 1000000000 

26 mc_iterations: int = 1000 

27 initial_capital: float = 100000.0 

28 leverage: float = 1.0 

29 

30 # Default target metrics 

31 target_profit_factor: float = 2.0 

32 target_sharpe_ratio: float = 1.0 

33 target_max_drawdown: float = 0.20 

34 target_winrate: float = 0.45 

35 

36 # Performance settings 

37 n_workers: int | None = None 

38 log_level: str = "INFO" 

39 use_gpu: bool = True 

40 gpu_precision: str = "float32" 

41 metal_driver: str = "cpp" 

42 

43 

44def load_config(config_path: str | Path | None = None) -> Config: 

45 """Load configuration from file and environment. 

46 

47 Args: 

48 config_path: Optional path to YAML config file. 

49 

50 Returns: 

51 Config object. 

52 """ 

53 # Load environment variables 

54 load_dotenv() 

55 

56 config = Config() 

57 

58 # Override from environment 

59 config.data_dir = Path(os.getenv("MONTE_NEO_DATA_DIR", config.data_dir)) 

60 config.binance_api_key = os.getenv("BINANCE_API_KEY", "") 

61 config.binance_api_secret = os.getenv("BINANCE_API_SECRET", "") 

62 config.log_level = os.getenv("MONTE_NEO_LOG_LEVEL", "INFO") 

63 

64 workers = os.getenv("MONTE_NEO_WORKERS", "auto") 

65 config.n_workers = None if workers == "auto" else int(workers) 

66 

67 # Override from YAML file 

68 if config_path: 

69 config = _merge_yaml_config(config, Path(config_path)) 

70 

71 return config 

72 

73 

74def _merge_yaml_config(config: Config, path: Path) -> Config: 

75 """Merge YAML config into Config object.""" 

76 if not path.exists(): 

77 return config 

78 

79 with open(path) as f: 

80 yaml_config = yaml.safe_load(f) or {} 

81 

82 # Data settings 

83 if "data" in yaml_config: 

84 data = yaml_config["data"] 

85 if "dir" in data: 

86 config.data_dir = Path(data["dir"]) 

87 if "symbol" in data: 

88 config.default_symbol = data["symbol"] 

89 if "timeframe" in data: 

90 config.default_timeframe = data["timeframe"] 

91 

92 # Metrics settings 

93 if "metrics" in yaml_config: 

94 metrics = yaml_config["metrics"] 

95 if "profit_factor" in metrics: 

96 config.target_profit_factor = metrics["profit_factor"] 

97 if "sharpe_ratio" in metrics: 

98 config.target_sharpe_ratio = metrics["sharpe_ratio"] 

99 if "max_drawdown" in metrics: 

100 config.target_max_drawdown = metrics["max_drawdown"] 

101 

102 # Monte Carlo settings 

103 if "monte_carlo" in yaml_config: 

104 mc = yaml_config["monte_carlo"] 

105 if "iterations" in mc: 

106 config.mc_iterations = mc["iterations"] 

107 

108 # Hardware settings 

109 if "hardware" in yaml_config: 

110 hw = yaml_config["hardware"] 

111 if "use_gpu" in hw: 

112 config.use_gpu = hw["use_gpu"] 

113 if "gpu_precision" in hw: 

114 config.gpu_precision = hw["gpu_precision"] 

115 if "metal_driver" in hw: 

116 config.metal_driver = hw["metal_driver"] 

117 

118 return config 

119 

120 

121def save_config(config: Config, path: str | Path) -> None: 

122 """Save configuration to YAML file.""" 

123 data = { 

124 "data": { 

125 "dir": str(config.data_dir), 

126 "symbol": config.default_symbol, 

127 "timeframe": config.default_timeframe, 

128 }, 

129 "metrics": { 

130 "profit_factor": config.target_profit_factor, 

131 "sharpe_ratio": config.target_sharpe_ratio, 

132 "max_drawdown": config.target_max_drawdown, 

133 "winrate": config.target_winrate, 

134 }, 

135 "monte_carlo": { 

136 "iterations": config.mc_iterations, 

137 "max_iterations": config.max_iterations, 

138 }, 

139 "hardware": { 

140 "use_gpu": config.use_gpu, 

141 "gpu_precision": config.gpu_precision, 

142 "metal_driver": config.metal_driver, 

143 }, 

144 } 

145 

146 with open(path, "w") as f: 

147 yaml.dump(data, f, default_flow_style=False)