Cycles/main.py

175 lines
5.5 KiB
Python

#!/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
# Import custom modules
from config_manager import ConfigManager
from backtest_runner import BacktestRunner
from result_processor import ResultProcessor
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()