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
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-28 16:27 +0200
1"""Utility configuration module."""
3from __future__ import annotations
5import os
6from dataclasses import dataclass, field
7from pathlib import Path
9import yaml
10from dotenv import load_dotenv
13@dataclass
14class Config:
15 """Application configuration."""
17 # Data settings
18 data_dir: Path = field(default_factory=lambda: Path("./data"))
19 binance_api_key: str = ""
20 binance_api_secret: str = ""
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
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
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"
44def load_config(config_path: str | Path | None = None) -> Config:
45 """Load configuration from file and environment.
47 Args:
48 config_path: Optional path to YAML config file.
50 Returns:
51 Config object.
52 """
53 # Load environment variables
54 load_dotenv()
56 config = Config()
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")
64 workers = os.getenv("MONTE_NEO_WORKERS", "auto")
65 config.n_workers = None if workers == "auto" else int(workers)
67 # Override from YAML file
68 if config_path:
69 config = _merge_yaml_config(config, Path(config_path))
71 return config
74def _merge_yaml_config(config: Config, path: Path) -> Config:
75 """Merge YAML config into Config object."""
76 if not path.exists():
77 return config
79 with open(path) as f:
80 yaml_config = yaml.safe_load(f) or {}
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"]
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"]
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"]
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"]
118 return config
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 }
146 with open(path, "w") as f:
147 yaml.dump(data, f, default_flow_style=False)