Files
zhutoutoutousan 98a87a69ca Update
2026-02-13 08:03:25 +01:00

360 lines
14 KiB
Python

"""
Parameter Optimization for ONNX Strategy
This script optimizes strategy parameters (prediction_threshold, min_confidence,
stop_loss_pips, take_profit_pips, lot_size) using grid search or random search.
"""
import os
import sys
from datetime import datetime, timedelta
import MetaTrader5 as mt5
import pandas as pd
import numpy as np
from itertools import product
import json
# Add paths
current_dir = os.path.dirname(os.path.abspath(__file__))
backtest_dir = os.path.join(os.path.dirname(current_dir), 'backtesting', 'MT5')
sys.path.insert(0, backtest_dir)
from backtest_engine import BacktestEngine
from onnx_backtest_strategy import ONNXBacktestStrategy
from performance_analyzer import PerformanceAnalyzer
class ONNXParameterOptimizer:
"""Optimize ONNX strategy parameters."""
def __init__(self, symbol: str, timeframe: int, model_path: str, scaler_path: str,
start_date: datetime, end_date: datetime, initial_balance: float = 10000.0):
"""
Initialize optimizer.
Args:
symbol: Trading symbol
timeframe: MT5 timeframe
model_path: Path to ONNX model
scaler_path: Path to scaler
start_date: Backtest start date
end_date: Backtest end date
initial_balance: Starting balance
"""
self.symbol = symbol
self.timeframe = timeframe
self.model_path = model_path
self.scaler_path = scaler_path
self.start_date = start_date
self.end_date = end_date
self.initial_balance = initial_balance
def grid_search(self, param_grid: dict, metric: str = 'sharpe_ratio') -> pd.DataFrame:
"""
Perform grid search optimization.
Args:
param_grid: Dictionary of parameter ranges
Example: {
'prediction_threshold': [0.0001, 0.0002, 0.0005],
'min_confidence': [0.2, 0.3, 0.4],
'stop_loss_pips': [30, 50, 70],
'take_profit_pips': [60, 100, 150],
'lot_size': [0.1, 0.2]
}
metric: Metric to optimize ('sharpe_ratio', 'total_return', 'max_drawdown', 'profit_factor')
Returns:
DataFrame with results sorted by metric
"""
print("="*60)
print("Grid Search Parameter Optimization")
print("="*60)
# Generate all parameter combinations
param_names = list(param_grid.keys())
param_values = list(param_grid.values())
combinations = list(product(*param_values))
total_combinations = len(combinations)
print(f"\nTotal parameter combinations: {total_combinations}")
print(f"Optimizing for: {metric}\n")
results = []
for i, combo in enumerate(combinations, 1):
params = dict(zip(param_names, combo))
print(f"[{i}/{total_combinations}] Testing: {params}")
try:
# Create strategy with these parameters
strategy = ONNXBacktestStrategy(
symbol=self.symbol,
timeframe=self.timeframe,
model_path=self.model_path,
scaler_path=self.scaler_path,
initial_balance=self.initial_balance,
**params
)
# Run backtest
engine = BacktestEngine(strategy, self.start_date, self.end_date)
backtest_results = engine.run()
# Calculate metrics
analyzer = PerformanceAnalyzer(backtest_results)
metrics = analyzer.metrics
# Store results
result = params.copy()
# Map metric names to match what we're looking for
result['total_return'] = metrics.get('total_return_pct', 0) / 100.0
result['max_drawdown'] = metrics.get('max_drawdown_pct', 0) / 100.0
result['sharpe_ratio'] = metrics.get('sharpe_ratio', 0.0) if 'sharpe_ratio' in metrics else 0.0
result['profit_factor'] = metrics.get('profit_factor', 0.0)
result['win_rate'] = metrics.get('win_rate_pct', 0) / 100.0
result['total_trades'] = metrics.get('total_trades', 0)
result['final_balance'] = metrics.get('final_balance', self.initial_balance)
results.append(result)
metric_value = result.get(metric, 0)
print(f" -> {metric}: {metric_value:.4f} | Trades: {result['total_trades']}")
except Exception as e:
print(f" X Error: {e}")
continue
# Convert to DataFrame
df_results = pd.DataFrame(results)
if len(df_results) == 0:
raise ValueError("No successful backtests!")
# Sort by metric (descending for most metrics, ascending for max_drawdown)
if metric == 'max_drawdown':
df_results = df_results.sort_values(metric, ascending=True)
else:
df_results = df_results.sort_values(metric, ascending=False)
return df_results
def random_search(self, param_ranges: dict, n_iter: int = 50,
metric: str = 'sharpe_ratio') -> pd.DataFrame:
"""
Perform random search optimization.
Args:
param_ranges: Dictionary of parameter ranges
Example: {
'prediction_threshold': (0.0001, 0.001),
'min_confidence': (0.1, 0.5),
'stop_loss_pips': (20, 100),
'take_profit_pips': (40, 200),
'lot_size': (0.1, 0.5)
}
n_iter: Number of random combinations to test
metric: Metric to optimize
Returns:
DataFrame with results sorted by metric
"""
print("="*60)
print("Random Search Parameter Optimization")
print("="*60)
print(f"\nTesting {n_iter} random parameter combinations")
print(f"Optimizing for: {metric}\n")
results = []
np.random.seed(42) # For reproducibility
for i in range(1, n_iter + 1):
# Generate random parameters
params = {}
for param_name, (min_val, max_val) in param_ranges.items():
if isinstance(min_val, int) and isinstance(max_val, int):
params[param_name] = np.random.randint(min_val, max_val + 1)
else:
params[param_name] = np.random.uniform(min_val, max_val)
print(f"[{i}/{n_iter}] Testing: {params}")
try:
# Create strategy
strategy = ONNXBacktestStrategy(
symbol=self.symbol,
timeframe=self.timeframe,
model_path=self.model_path,
scaler_path=self.scaler_path,
initial_balance=self.initial_balance,
**params
)
# Run backtest
engine = BacktestEngine(strategy, self.start_date, self.end_date)
backtest_results = engine.run()
# Calculate metrics
analyzer = PerformanceAnalyzer(backtest_results)
metrics = analyzer.metrics
# Store results
result = params.copy()
# Map metric names to match what we're looking for
result['total_return'] = metrics.get('total_return_pct', 0) / 100.0
result['max_drawdown'] = metrics.get('max_drawdown_pct', 0) / 100.0
result['sharpe_ratio'] = metrics.get('sharpe_ratio', 0.0) if 'sharpe_ratio' in metrics else 0.0
result['profit_factor'] = metrics.get('profit_factor', 0.0)
result['win_rate'] = metrics.get('win_rate_pct', 0) / 100.0
result['total_trades'] = metrics.get('total_trades', 0)
result['final_balance'] = metrics.get('final_balance', self.initial_balance)
results.append(result)
metric_value = result.get(metric, 0)
print(f" -> {metric}: {metric_value:.4f} | Trades: {result['total_trades']}")
except Exception as e:
print(f" X Error: {e}")
continue
# Convert to DataFrame
df_results = pd.DataFrame(results)
if len(df_results) == 0:
raise ValueError("No successful backtests!")
# Sort by metric
if metric == 'max_drawdown':
df_results = df_results.sort_values(metric, ascending=True)
else:
df_results = df_results.sort_values(metric, ascending=False)
return df_results
def save_results(self, df_results: pd.DataFrame, output_file: str = 'optimization_results.csv'):
"""Save optimization results to CSV."""
df_results.to_csv(output_file, index=False)
print(f"\nResults saved to: {output_file}")
# Also save top 10 as JSON
top_10 = df_results.head(10).to_dict('records')
json_file = output_file.replace('.csv', '_top10.json')
with open(json_file, 'w') as f:
json.dump(top_10, f, indent=2, default=str)
print(f"Top 10 results saved to: {json_file}")
def main():
"""Main optimization function."""
# Configuration
symbol = 'XAUUSD'
timeframe = mt5.TIMEFRAME_H1
model_path = 'models/XAUUSD_H1_model.onnx'
scaler_path = 'models/XAUUSD_H1_scaler.pkl'
initial_balance = 10000.0
# Backtest date range (use last 6 months for optimization)
end_date = datetime.now()
start_date = end_date - timedelta(days=180)
# Check if model exists
if not os.path.exists(model_path):
print(f"ERROR: Model not found: {model_path}")
print("Please train the model first using train_onnx_model.py")
return
# Initialize MT5
if not mt5.initialize():
print("ERROR: Failed to initialize MT5")
return
try:
# Create optimizer
optimizer = ONNXParameterOptimizer(
symbol=symbol,
timeframe=timeframe,
model_path=model_path,
scaler_path=scaler_path,
start_date=start_date,
end_date=end_date,
initial_balance=initial_balance
)
# Choose optimization method (use command line args or defaults)
import sys
choice = "2" # Default to random search
n_iter = 30 # Default iterations
if len(sys.argv) > 1:
choice = sys.argv[1]
if len(sys.argv) > 2:
n_iter = int(sys.argv[2])
print("\nOptimization Configuration:")
print(f"Method: {'Grid Search' if choice == '1' else 'Random Search'}")
if choice != "1":
print(f"Iterations: {n_iter}")
print()
if choice == "1":
# Grid search parameters
param_grid = {
'prediction_threshold': [0.0001, 0.0002, 0.0005, 0.001],
'min_confidence': [0.1, 0.2, 0.3, 0.4],
'stop_loss_pips': [30, 50, 70, 100],
'take_profit_pips': [60, 100, 150, 200],
'lot_size': [0.1, 0.2]
}
results = optimizer.grid_search(param_grid, metric='sharpe_ratio')
else:
# Random search parameters
param_ranges = {
'prediction_threshold': (0.00005, 0.002), # Lower threshold to get more trades
'min_confidence': (0.05, 0.5), # Lower confidence requirement
'stop_loss_pips': (20, 150),
'take_profit_pips': (40, 300),
'lot_size': (0.05, 0.3)
}
results = optimizer.random_search(param_ranges, n_iter=n_iter, metric='sharpe_ratio')
# Display top results
print("\n" + "="*60)
print("Top 10 Results")
print("="*60)
print(results.head(10).to_string(index=False))
# Save results
optimizer.save_results(results, 'onnx_optimization_results.csv')
# Display best parameters
best = results.iloc[0]
print("\n" + "="*60)
print("Best Parameters")
print("="*60)
print(f"Prediction Threshold: {best['prediction_threshold']:.6f}")
print(f"Min Confidence: {best['min_confidence']:.2f}")
print(f"Stop Loss (pips): {best['stop_loss_pips']}")
print(f"Take Profit (pips): {best['take_profit_pips']}")
print(f"Lot Size: {best['lot_size']:.2f}")
print(f"\nPerformance Metrics:")
print(f" Sharpe Ratio: {best.get('sharpe_ratio', 'N/A'):.4f}")
print(f" Total Return: {best.get('total_return', 'N/A'):.2%}")
print(f" Max Drawdown: {best.get('max_drawdown', 'N/A'):.2%}")
print(f" Profit Factor: {best.get('profit_factor', 'N/A'):.2f}")
except KeyboardInterrupt:
print("\n\nOptimization interrupted by user")
except Exception as e:
print(f"\nERROR: {e}")
import traceback
traceback.print_exc()
finally:
mt5.shutdown()
if __name__ == '__main__':
main()