Cycles/main.py

175 lines
5.5 KiB
Python
Raw Permalink 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)
# OPTIMIZATION: Disable progress for parallel execution to improve performance
show_progress = config.get('show_progress', True)
debug_mode = config.get('debug', 0) == 1
# Only show progress in debug (sequential) mode
if not debug_mode:
show_progress = False
logger.info("Progress tracking disabled for parallel execution (performance optimization)")
runner = BacktestRunner(
storage,
system_utils,
result_processor,
logging_instance=logger,
show_progress=show_progress
)
# 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_filename = 'btcusd_1-min_data_with_price_predictions.csv'
data_1min = runner.load_data(
data_filename,
config['start_date'],
config['stop_date']
)
# Run backtests
logger.info("Starting backtest execution...")
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")
# OPTIMIZATION: Save trade files in batch after parallel execution
if all_trades and not debug_mode:
logger.info("Saving trade files in batch...")
result_processor.save_all_trade_files(all_trades)
# 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()