Cycles/main.py

154 lines
4.8 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""
Backtest execution script for cryptocurrency trading strategies
Refactored for improved maintainability and error handling
"""
import logging
import datetime
import argparse
import sys
from pathlib import Path
2025-05-20 16:59:17 +08:00
# Import custom modules
from config_manager import ConfigManager
from backtest_runner import BacktestRunner
from result_processor import ResultProcessor
2025-05-20 16:59:17 +08:00
from cycles.utils.storage import Storage
from cycles.utils.system import SystemUtils
def setup_logging() -> logging.Logger:
"""Configure and return logging instance"""
logger = logging.getLogger(__name__)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[
logging.FileHandler("backtest.log"),
logging.StreamHandler()
]
)
return logger
def create_metadata_lines(config: dict, data_df, result_processor: ResultProcessor) -> list:
"""Create metadata lines for results file"""
start_date = config['start_date']
stop_date = config['stop_date']
initial_usd = config['initial_usd']
# Get price information
start_time, start_price = result_processor.get_price_info(data_df, start_date)
stop_time, stop_price = result_processor.get_price_info(data_df, stop_date)
metadata_lines = [
f"Start date\t{start_date}\tPrice\t{start_price or 'N/A'}",
f"Stop date\t{stop_date}\tPrice\t{stop_price or 'N/A'}",
f"Initial USD\t{initial_usd}"
]
return metadata_lines
def main():
"""Main execution function"""
logger = setup_logging()
try:
# Parse command line arguments
parser = argparse.ArgumentParser(description="Run backtest with config file.")
parser.add_argument("config", type=str, nargs="?", help="Path to config JSON file.")
args = parser.parse_args()
# Initialize configuration manager
config_manager = ConfigManager(logging_instance=logger)
# Load configuration
logger.info("Loading configuration...")
config = config_manager.load_config(args.config)
# Initialize components
logger.info("Initializing components...")
storage = Storage(
data_dir=config['data_dir'],
results_dir=config['results_dir'],
logging=logger
)
system_utils = SystemUtils(logging=logger)
result_processor = ResultProcessor(storage, logging_instance=logger)
runner = BacktestRunner(storage, system_utils, result_processor, logging_instance=logger)
# Validate inputs
logger.info("Validating inputs...")
runner.validate_inputs(
config['timeframes'],
config['stop_loss_pcts'],
config['initial_usd']
)
# Load data
logger.info("Loading market data...")
data_filename = 'btcusd_1-min_data.csv'
data_1min = runner.load_data(
data_filename,
config['start_date'],
config['stop_date']
)
# Run backtests
logger.info("Starting backtest execution...")
debug_mode = True # Can be moved to config
all_results, all_trades = runner.run_backtests(
data_1min,
config['timeframes'],
config['stop_loss_pcts'],
config['initial_usd'],
debug=debug_mode
)
# Process and save results
logger.info("Processing and saving results...")
timestamp = datetime.datetime.now().strftime("%Y_%m_%d_%H_%M")
# Create metadata
metadata_lines = create_metadata_lines(config, data_1min, result_processor)
# Save aggregated results
result_file = result_processor.save_backtest_results(
all_results,
metadata_lines,
timestamp
)
logger.info(f"Backtest completed successfully. Results saved to {result_file}")
logger.info(f"Processed {len(all_results)} result combinations")
logger.info(f"Generated {len(all_trades)} total trades")
except KeyboardInterrupt:
logger.warning("Backtest interrupted by user")
sys.exit(130) # Standard exit code for Ctrl+C
except FileNotFoundError as e:
logger.error(f"File not found: {e}")
sys.exit(1)
except ValueError as e:
logger.error(f"Invalid configuration or data: {e}")
sys.exit(1)
except RuntimeError as e:
logger.error(f"Runtime error during backtest: {e}")
sys.exit(1)
except Exception as e:
logger.error(f"Unexpected error: {e}", exc_info=True)
sys.exit(1)
if __name__ == "__main__":
main()