175 lines
5.5 KiB
Python
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()
|
|
|