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()