File size: 6,915 Bytes
5cbffcd | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | import MetaTrader5 as mt5
import json
import os
from datetime import datetime
import pandas as pd
class BacktestBridge:
"""Bridge to run MQL5 Expert Advisors and extract backtest results"""
def __init__(self):
self.mt5_path = None
def init_mt5(self):
"""Initialize MT5 connection"""
if not mt5.initialize():
print(f"MT5 initialization failed: {mt5.last_error()}")
return False
# Get MT5 terminal path
terminal_info = mt5.terminal_info()
self.mt5_path = terminal_info.path
print(f"MT5 Path: {self.mt5_path}")
return True
def compile_ea(self, mq5_file_path):
"""
Compile MQL5 Expert Advisor
Note: This requires MetaEditor CLI or manual compilation
"""
# Check if file exists
if not os.path.exists(mq5_file_path):
return {'success': False, 'error': 'MQ5 file not found'}
# For now, assume EA is already compiled
# You need to manually compile in MetaEditor or use MetaEditor CLI
ex5_path = mq5_file_path.replace('.mq5', '.ex5')
if not os.path.exists(ex5_path):
return {
'success': False,
'error': 'EX5 file not found. Please compile in MetaEditor first.'
}
return {'success': True, 'ex5_path': ex5_path}
def get_history_deals(self, from_date, to_date):
"""Get historical deals from MT5"""
deals = mt5.history_deals_get(from_date, to_date)
if deals is None:
return []
deals_list = []
for deal in deals:
deals_list.append({
'ticket': deal.ticket,
'order': deal.order,
'time': deal.time,
'type': 'BUY' if deal.type == 0 else 'SELL',
'entry': 'IN' if deal.entry == 0 else 'OUT',
'symbol': deal.symbol,
'volume': deal.volume,
'price': deal.price,
'profit': deal.profit,
'commission': deal.commission,
'swap': deal.swap,
'comment': deal.comment
})
return deals_list
def get_history_orders(self, from_date, to_date):
"""Get historical orders from MT5"""
orders = mt5.history_orders_get(from_date, to_date)
if orders is None:
return []
orders_list = []
for order in orders:
orders_list.append({
'ticket': order.ticket,
'time_setup': order.time_setup,
'time_done': order.time_done,
'type': order.type,
'state': order.state,
'symbol': order.symbol,
'volume_initial': order.volume_initial,
'volume_current': order.volume_current,
'price_open': order.price_open,
'price_current': order.price_current,
'sl': order.sl,
'tp': order.tp,
'comment': order.comment
})
return orders_list
def analyze_backtest_results(self, from_date, to_date):
"""Analyze backtest results and calculate statistics"""
deals = self.get_history_deals(from_date, to_date)
if not deals:
return {'error': 'No deals found in the specified period'}
# Convert to DataFrame for easier analysis
df = pd.DataFrame(deals)
# Calculate statistics
total_trades = len(df[df['entry'] == 'OUT'])
winning_trades = len(df[(df['entry'] == 'OUT') & (df['profit'] > 0)])
losing_trades = len(df[(df['entry'] == 'OUT') & (df['profit'] < 0)])
total_profit = df[df['entry'] == 'OUT']['profit'].sum()
total_commission = df[df['entry'] == 'OUT']['commission'].sum()
total_swap = df[df['entry'] == 'OUT']['swap'].sum()
net_profit = total_profit + total_commission + total_swap
win_rate = (winning_trades / total_trades * 100) if total_trades > 0 else 0
# Calculate max drawdown
df_out = df[df['entry'] == 'OUT'].copy()
df_out['cumulative_profit'] = df_out['profit'].cumsum()
df_out['peak'] = df_out['cumulative_profit'].cummax()
df_out['drawdown'] = df_out['peak'] - df_out['cumulative_profit']
max_drawdown = df_out['drawdown'].max()
# Profit factor
gross_profit = df[(df['entry'] == 'OUT') & (df['profit'] > 0)]['profit'].sum()
gross_loss = abs(df[(df['entry'] == 'OUT') & (df['profit'] < 0)]['profit'].sum())
profit_factor = (gross_profit / gross_loss) if gross_loss > 0 else 0
return {
'total_trades': total_trades,
'winning_trades': winning_trades,
'losing_trades': losing_trades,
'win_rate': round(win_rate, 2),
'total_profit': round(total_profit, 2),
'total_commission': round(total_commission, 2),
'total_swap': round(total_swap, 2),
'net_profit': round(net_profit, 2),
'max_drawdown': round(max_drawdown, 2),
'profit_factor': round(profit_factor, 2),
'gross_profit': round(gross_profit, 2),
'gross_loss': round(gross_loss, 2),
'deals': deals
}
def export_backtest_report(self, from_date, to_date, output_file='backtest_report.json'):
"""Export backtest results to JSON file"""
results = self.analyze_backtest_results(from_date, to_date)
with open(output_file, 'w') as f:
json.dump(results, f, indent=2, default=str)
print(f"Backtest report exported to {output_file}")
return results
# Example usage
if __name__ == "__main__":
bridge = BacktestBridge()
if bridge.init_mt5():
# Define backtest period
from_date = datetime(2024, 1, 1)
to_date = datetime.now()
# Analyze results
results = bridge.analyze_backtest_results(from_date, to_date)
print("\n=== BACKTEST RESULTS ===")
print(f"Total Trades: {results.get('total_trades', 0)}")
print(f"Win Rate: {results.get('win_rate', 0)}%")
print(f"Net Profit: ${results.get('net_profit', 0)}")
print(f"Max Drawdown: ${results.get('max_drawdown', 0)}")
print(f"Profit Factor: {results.get('profit_factor', 0)}")
# Export to JSON
bridge.export_backtest_report(from_date, to_date)
mt5.shutdown() |