Cycles/cycles/utils/config_manager.py

129 lines
4.4 KiB
Python
Raw Normal View History

2025-05-23 20:37:14 +08:00
"""
Configuration Manager
This module provides centralized configuration handling for the backtesting system.
It handles loading, validation, and provides a clean interface for accessing
configuration data.
"""
import json
import datetime
import logging
from typing import Dict, List, Optional, Any
from pathlib import Path
class ConfigManager:
"""
Manages configuration loading, validation, and access.
Provides a centralized way to handle configuration files with validation
and convenient access methods.
"""
def __init__(self, config_path: Optional[str] = None):
"""
Initialize configuration manager.
Args:
config_path: Path to configuration file. If None, uses default.
"""
self.config_path = config_path or "configs/config_default.json"
self.config = self._load_config()
self._validate_config()
def _load_config(self) -> Dict[str, Any]:
"""Load configuration from file."""
try:
with open(self.config_path, 'r') as f:
config = json.load(f)
logging.info(f"Loaded configuration from: {self.config_path}")
return config
except FileNotFoundError:
available_configs = list(Path("configs").glob("*.json"))
raise FileNotFoundError(
f"Config file '{self.config_path}' not found. "
f"Available configs: {[str(c) for c in available_configs]}"
)
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON in config file '{self.config_path}': {e}")
def _validate_config(self) -> None:
"""Validate configuration structure and values."""
required_fields = ['start_date', 'initial_usd', 'timeframes', 'strategies']
for field in required_fields:
if field not in self.config:
raise ValueError(f"Missing required field '{field}' in configuration")
# Validate strategies
if not self.config['strategies']:
raise ValueError("At least one strategy must be specified")
for strategy in self.config['strategies']:
if 'name' not in strategy:
raise ValueError("Strategy must have a 'name' field")
# Validate timeframes
if not self.config['timeframes']:
raise ValueError("At least one timeframe must be specified")
logging.info("Configuration validation successful")
@property
def start_date(self) -> str:
"""Get start date."""
return self.config['start_date']
@property
def stop_date(self) -> str:
"""Get stop date, defaulting to current date if None."""
stop_date = self.config.get('stop_date')
if stop_date is None:
return datetime.datetime.now().strftime("%Y-%m-%d")
return stop_date
@property
def initial_usd(self) -> float:
"""Get initial USD amount."""
return self.config['initial_usd']
@property
def timeframes(self) -> List[str]:
"""Get list of timeframes to test."""
return self.config['timeframes']
@property
def strategies_config(self) -> List[Dict[str, Any]]:
"""Get strategies configuration."""
return self.config['strategies']
@property
def combination_rules(self) -> Dict[str, Any]:
"""Get combination rules for strategy manager."""
return self.config.get('combination_rules', {
"entry": "any",
"exit": "any",
"min_confidence": 0.5
})
def get_strategy_manager_config(self) -> Dict[str, Any]:
"""Get configuration for strategy manager."""
return {
"strategies": self.strategies_config,
"combination_rules": self.combination_rules
}
def get_timeframe_task_config(self, timeframe: str) -> Dict[str, Any]:
"""Get configuration for a specific timeframe task."""
return {
"initial_usd": self.initial_usd,
"strategies": self.strategies_config,
"combination_rules": self.combination_rules
}
def __repr__(self) -> str:
"""String representation of configuration."""
return (f"ConfigManager(config_path='{self.config_path}', "
f"strategies={len(self.strategies_config)}, "
f"timeframes={len(self.timeframes)})")