algorembrant commited on
Commit
13c3b17
·
verified ·
1 Parent(s): fd06463

Upload 7 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ XAUUSD_M15_Data.csv filter=lfs diff=lfs merge=lfs -text
RL_ONNX_EA.mq5 ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //+------------------------------------------------------------------+
2
+ //| RL_ONNX_EA.mq5 |
3
+ //| Copyright 2026, Algorembrant |
4
+ //| Rembrant Oyangoren Albeos |
5
+ //+------------------------------------------------------------------+
6
+ #property copyright "Algorembrant, Rembrant Oyangoren Albeos"
7
+ #property link "https://github.com/Algorembrant"
8
+ #property version "1.00"
9
+
10
+ #include <Trade\Trade.mqh>
11
+ CTrade trade;
12
+
13
+ // Inputs compatible with the RL training setup
14
+ input string ONNX_Filename = "RL_Agent_XAUUSD.onnx";
15
+ input double RiskPercent = 2.0;
16
+
17
+ long model_handle = INVALID_HANDLE;
18
+ double max_lot_size = 20.0;
19
+
20
+ int OnInit() {
21
+ Print("Initializing RL XAUUSDc ONNX EA...");
22
+
23
+ // Load the ONNX model trained in Google Colab
24
+ model_handle = OnnxCreate(ONNX_Filename, ONNX_DEFAULT);
25
+ if(model_handle == INVALID_HANDLE) {
26
+ Print("Error loading ONNX model ", ONNX_Filename, " : ", GetLastError());
27
+ return INIT_FAILED;
28
+ }
29
+
30
+ trade.SetExpertMagicNumber(2026101); // 2026 methodologies
31
+
32
+ return INIT_SUCCEEDED;
33
+ }
34
+
35
+ void OnDeinit(const int reason) {
36
+ if(model_handle != INVALID_HANDLE)
37
+ OnnxRelease(model_handle);
38
+ Print("EA Deinitialized.");
39
+ }
40
+
41
+ void OnTick() {
42
+ // Strictly execute on close-price only (Wait for new bar generation)
43
+ static datetime last_time = 0;
44
+ datetime current_time = iTime(_Symbol, _Period, 0);
45
+
46
+ // If the bar hasn't closed / new bar hasn't opened, do nothing
47
+ if(current_time == last_time) return;
48
+ last_time = current_time;
49
+
50
+ // --- 1. Fetch data & Indicators
51
+ // The ONNX model requires the exact 100+ vectorized attributes as built by pandas_ta in python.
52
+ // In this production script, we construct the input float array shape based on observation_space.
53
+ // Ensure length matches `features.shape[1]` exactly.
54
+ float features[];
55
+ int num_features = 100; // MUST MATCH exactly the CSV features
56
+ ArrayResize(features, num_features);
57
+
58
+ for(int i=0; i<ArraySize(features); i++) {
59
+ // [In a complete system, map MT5 runtime M3 moving averages, FDI, oscillators to these indices here]
60
+ features[i] = 0.0f;
61
+ }
62
+
63
+ // --- 2. Predict with ONNX Model
64
+ long action_output[] = {3}; // Default 3 = Do Nothing
65
+
66
+ // onnx model inference
67
+ if(!OnnxRun(model_handle, ONNX_NO_CONVERSION, features, action_output)) {
68
+ Print("ONNX Run Error: ", GetLastError());
69
+ return;
70
+ }
71
+
72
+ long action = action_output[0];
73
+
74
+ // --- 3. Execute Actions & Handle Fragment/Sizing
75
+ double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
76
+ double spread = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) * point;
77
+ double closePrice = iClose(_Symbol, _Period, 1);
78
+ double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
79
+ double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
80
+
81
+ // System rule: After opened, wait until tookprofit or stopout. No averaging.
82
+ if(PositionsTotal() > 0) return;
83
+
84
+ // StopLoss distance constraints: SL distance never less than spread * 10
85
+ double sl_dist = MathMax(closePrice * 0.005, spread * 10.0);
86
+
87
+ // Calculate Sizing (2% Risk)
88
+ double balance = AccountInfoDouble(ACCOUNT_BALANCE);
89
+ double risk_amount = balance * (RiskPercent / 100.0);
90
+
91
+ // MT5 Standard contract size computation for Gold (usually $100 per lot per $1)
92
+ double sl_dollar_risk_per_lot = sl_dist * 100.0;
93
+ double lots = 0.01;
94
+
95
+ if(sl_dollar_risk_per_lot > 0)
96
+ lots = risk_amount / sl_dollar_risk_per_lot;
97
+
98
+ // Fragmentize execution cap
99
+ lots = MathRound(lots * 100.0) / 100.0;
100
+ if(lots < 0.01) lots = 0.01;
101
+
102
+ // Fragmenting Logic: Open multiple positions if lot size exceeds cap
103
+ int fragments = 1;
104
+ double current_fragment_lot = lots;
105
+
106
+ if(lots > max_lot_size) {
107
+ fragments = (int)MathCeil(lots / max_lot_size);
108
+ current_fragment_lot = max_lot_size;
109
+ Print("Notice: Position fragmentization triggered. Total Lot = ", lots, " -> Fragmented into ", fragments, " orders.");
110
+ }
111
+
112
+ if(action == 0) { // BUY
113
+ double stoploss = ask - sl_dist;
114
+ double takeprofit = ask + (sl_dist * 1.5); // TP >= 1R
115
+
116
+ for(int f=0; f<fragments; f++) {
117
+ if(f == fragments - 1 && lots > max_lot_size) {
118
+ // Remaining fraction stringency
119
+ current_fragment_lot = lots - (max_lot_size * (fragments - 1));
120
+ }
121
+ trade.Buy(current_fragment_lot, _Symbol, ask, stoploss, takeprofit, "RL_BUY");
122
+ }
123
+ }
124
+ else if(action == 1) { // SELL
125
+ double stoploss = bid + sl_dist;
126
+ double takeprofit = bid - (sl_dist * 1.5); // TP >= 1R
127
+
128
+ for(int f=0; f<fragments; f++) {
129
+ if(f == fragments - 1 && lots > max_lot_size) {
130
+ current_fragment_lot = lots - (max_lot_size * (fragments - 1));
131
+ }
132
+ trade.Sell(current_fragment_lot, _Symbol, bid, stoploss, takeprofit, "RL_SELL");
133
+ }
134
+ }
135
+ }
RL_XAUUSD_Colab_System.ipynb ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "# Phase 2: RL Trading Agent for MT5 (XAUUSDc)\n",
8
+ "This notebook trains a reinforcement learning model on the extracted MT5 data, simulating live-market constraints and exporting an ONNX model for the Expert Advisor."
9
+ ]
10
+ },
11
+ {
12
+ "cell_type": "code",
13
+ "execution_count": null,
14
+ "metadata": {},
15
+ "outputs": [],
16
+ "source": [
17
+ "!pip install -q stable-baselines3[extra] pandas_ta xgboost onnx onnxruntime plotly gym"
18
+ ]
19
+ },
20
+ {
21
+ "cell_type": "code",
22
+ "execution_count": null,
23
+ "metadata": {},
24
+ "outputs": [],
25
+ "source": [
26
+ "import os\n",
27
+ "import math\n",
28
+ "import numpy as np\n",
29
+ "import pandas as pd\n",
30
+ "import torch\n",
31
+ "import torch.nn as nn\n",
32
+ "import onnx\n",
33
+ "import onnxruntime as ort\n",
34
+ "import plotly.graph_objects as go\n",
35
+ "import gym\n",
36
+ "from gym import spaces\n",
37
+ "from stable_baselines3 import PPO\n",
38
+ "from google.colab import files\n",
39
+ "\n",
40
+ "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n",
41
+ "print(f\"Using device: {device}\")"
42
+ ]
43
+ },
44
+ {
45
+ "cell_type": "code",
46
+ "execution_count": null,
47
+ "metadata": {},
48
+ "outputs": [],
49
+ "source": [
50
+ "# Load Dataset (Upload XAUUSD_M3_Data.csv to Colab first)\n",
51
+ "if not os.path.exists('XAUUSD_M3_Data.csv'):\n",
52
+ " print(\"Please upload XAUUSD_M3_Data.csv to the Colab environment.\")\n",
53
+ "else:\n",
54
+ " df = pd.read_csv('XAUUSD_M3_Data.csv', index_col='time', parse_dates=True)\n",
55
+ " print(f\"Loaded {len(df)} rows.\")"
56
+ ]
57
+ },
58
+ {
59
+ "cell_type": "code",
60
+ "execution_count": null,
61
+ "metadata": {},
62
+ "outputs": [],
63
+ "source": [
64
+ "# Vectorized Custom Gym Environment for XAUUSDc\n",
65
+ "class XAUUSDM3Env(gym.Env):\n",
66
+ " def __init__(self, df, initial_balance=2000.0, risk_per_trade=0.02, max_lot_size=20.0):\n",
67
+ " super(XAUUSDM3Env, self).__init__()\n",
68
+ " self.df = df\n",
69
+ " self.prices = df['close'].values\n",
70
+ " self.spreads = df['spread'].values if 'spread' in df.columns else np.full(len(df), 20.0)\n",
71
+ " \n",
72
+ " # Features for observation (dropping strings/dates)\n",
73
+ " self.features = df.select_dtypes(include=[np.number]).fillna(0).values\n",
74
+ " \n",
75
+ " self.initial_balance = initial_balance\n",
76
+ " self.risk_per_trade = risk_per_trade\n",
77
+ " self.max_lot_size = max_lot_size\n",
78
+ " \n",
79
+ " # Actions: 0=Buy, 1=Sell, 2=Hold, 3=Do Nothing\n",
80
+ " self.action_space = spaces.Discrete(4)\n",
81
+ " \n",
82
+ " self.observation_space = spaces.Box(\n",
83
+ " low=-np.inf, high=np.inf, shape=(self.features.shape[1],), dtype=np.float32\n",
84
+ " )\n",
85
+ " \n",
86
+ " self.reset()\n",
87
+ "\n",
88
+ " def reset(self):\n",
89
+ " self.current_step = 0\n",
90
+ " self.balance = self.initial_balance\n",
91
+ " self.equity = self.initial_balance\n",
92
+ " self.current_position = 0 # 1=Long, -1=Short, 0=Flat\n",
93
+ " self.entry_price = 0.0\n",
94
+ " self.stop_loss = 0.0\n",
95
+ " self.take_profit = 0.0\n",
96
+ " self.lot_size = 0.0\n",
97
+ " self.history = []\n",
98
+ " return self.features[self.current_step]\n",
99
+ "\n",
100
+ " def _calculate_lot_size(self, sl_distance):\n",
101
+ " # 2% Risk\n",
102
+ " risk_amount = self.balance * self.risk_per_trade\n",
103
+ " # XAUUSDc lot size standard: $100 per $1 move for 1 lot usually. \n",
104
+ " sl_dollar_risk_per_lot = sl_distance * 100.0 \n",
105
+ " if sl_dollar_risk_per_lot <= 0:\n",
106
+ " return 0.01\n",
107
+ " \n",
108
+ " lots = risk_amount / sl_dollar_risk_per_lot\n",
109
+ " return max(0.01, round(lots, 2))\n",
110
+ "\n",
111
+ " def step(self, action):\n",
112
+ " done = False\n",
113
+ " reward = 0.0\n",
114
+ " \n",
115
+ " current_price = self.prices[self.current_step]\n",
116
+ " spread = self.spreads[self.current_step] / 100.0 # Standard conversion for points\n",
117
+ " \n",
118
+ " # Calculate equity running\n",
119
+ " if self.current_position == 1:\n",
120
+ " self.equity = self.balance + (current_price - self.entry_price) * 100.0 * self.lot_size\n",
121
+ " elif self.current_position == -1:\n",
122
+ " self.equity = self.balance + (self.entry_price - current_price) * 100.0 * self.lot_size\n",
123
+ "\n",
124
+ " # Execute at close-price\n",
125
+ " if action == 0 and self.current_position == 0:\n",
126
+ " # BUY\n",
127
+ " sl_dist = max(current_price * 0.005, spread * 10.0)\n",
128
+ " self.stop_loss = current_price - sl_dist\n",
129
+ " self.take_profit = current_price + (sl_dist * 2.0) # > 1R\n",
130
+ " self.entry_price = current_price + spread\n",
131
+ " self.lot_size = self._calculate_lot_size(sl_dist)\n",
132
+ " self.current_position = 1\n",
133
+ " \n",
134
+ " elif action == 1 and self.current_position == 0:\n",
135
+ " # SELL\n",
136
+ " sl_dist = max(current_price * 0.005, spread * 10.0)\n",
137
+ " self.stop_loss = current_price + sl_dist + spread\n",
138
+ " self.take_profit = current_price - (sl_dist * 2.0) \n",
139
+ " self.entry_price = current_price\n",
140
+ " self.lot_size = self._calculate_lot_size(sl_dist)\n",
141
+ " self.current_position = -1\n",
142
+ "\n",
143
+ " # Check SL / TP for exit\n",
144
+ " if self.current_position == 1:\n",
145
+ " if current_price <= self.stop_loss or current_price >= self.take_profit:\n",
146
+ " profit = (current_price - self.entry_price) * 100.0 * self.lot_size\n",
147
+ " self.balance += profit\n",
148
+ " self.equity = self.balance\n",
149
+ " self.current_position = 0\n",
150
+ " reward = profit\n",
151
+ " self.history.append({'type': 'long', 'profit': profit, 'lot': self.lot_size})\n",
152
+ " \n",
153
+ " elif self.current_position == -1:\n",
154
+ " if current_price >= self.stop_loss or current_price <= self.take_profit:\n",
155
+ " profit = (self.entry_price - current_price) * 100.0 * self.lot_size\n",
156
+ " self.balance += profit\n",
157
+ " self.equity = self.balance\n",
158
+ " self.current_position = 0\n",
159
+ " reward = profit\n",
160
+ " self.history.append({'type': 'short', 'profit': profit, 'lot': self.lot_size})\n",
161
+ "\n",
162
+ " self.current_step += 1\n",
163
+ " if self.current_step >= len(self.prices) - 1 or self.equity <= 0:\n",
164
+ " done = True\n",
165
+ " \n",
166
+ " next_state = self.features[self.current_step] if not done else np.zeros(self.features.shape[1])\n",
167
+ " return next_state, reward, done, {}\n"
168
+ ]
169
+ },
170
+ {
171
+ "cell_type": "code",
172
+ "execution_count": null,
173
+ "metadata": {},
174
+ "outputs": [],
175
+ "source": [
176
+ "# Train Model\n",
177
+ "if 'df' in locals():\n",
178
+ " train_size = int(len(df) * 0.7)\n",
179
+ " train_df = df.iloc[:train_size].copy()\n",
180
+ " test_df = df.iloc[train_size:].copy()\n",
181
+ " \n",
182
+ " env = XAUUSDM3Env(train_df)\n",
183
+ " model = PPO(\"MlpPolicy\", env, verbose=1, device=device)\n",
184
+ " \n",
185
+ " print(\"Starting RL Training...\")\n",
186
+ " model.learn(total_timesteps=50000)\n",
187
+ " print(\"Training Finished.\")\n"
188
+ ]
189
+ },
190
+ {
191
+ "cell_type": "code",
192
+ "execution_count": null,
193
+ "metadata": {},
194
+ "outputs": [],
195
+ "source": [
196
+ "# Plotting white-themed performance metrics\n",
197
+ "if 'env' in locals() and len(env.history) > 0:\n",
198
+ " profits = [x['profit'] for x in env.history]\n",
199
+ " cumulative = np.cumsum(profits)\n",
200
+ " \n",
201
+ " fig = go.Figure()\n",
202
+ " fig.add_trace(go.Scatter(y=cumulative, mode='lines', name='Cumulative Profit', line=dict(color='blue')))\n",
203
+ " fig.update_layout(\n",
204
+ " title=\"RL Agent Performance (Cumulative Profit)\",\n",
205
+ " xaxis_title=\"Trades\",\n",
206
+ " yaxis_title=\"USD Returns\",\n",
207
+ " template=\"plotly_white\"\n",
208
+ " )\n",
209
+ " fig.show()\n"
210
+ ]
211
+ },
212
+ {
213
+ "cell_type": "code",
214
+ "execution_count": null,
215
+ "metadata": {},
216
+ "outputs": [],
217
+ "source": [
218
+ "# Export to ONNX for MT5 Expert Advisor\n",
219
+ "if 'model' in locals():\n",
220
+ " class OnnxablePolicy(nn.Module):\n",
221
+ " def __init__(self, policy):\n",
222
+ " super().__init__()\n",
223
+ " self.policy = policy\n",
224
+ " \n",
225
+ " def forward(self, observation):\n",
226
+ " return self.policy(observation, deterministic=True)[0]\n",
227
+ " \n",
228
+ " onnx_policy = OnnxablePolicy(model.policy)\n",
229
+ " dummy_input = torch.randn(1, env.observation_space.shape[0]).to(device)\n",
230
+ " onnx_policy.to(device)\n",
231
+ " \n",
232
+ " onnx_path = \"RL_Agent_XAUUSD.onnx\"\n",
233
+ " torch.onnx.export(\n",
234
+ " onnx_policy,\n",
235
+ " dummy_input,\n",
236
+ " onnx_path,\n",
237
+ " opset_version=11,\n",
238
+ " input_names=[\"input\"],\n",
239
+ " output_names=[\"output\"]\n",
240
+ " )\n",
241
+ " print(f\"ONNX Model successfully exported to {onnx_path}. Next, download it and deploy to your MT5 EA.\")\n",
242
+ " try: files.download(onnx_path)\n",
243
+ " except: pass\n"
244
+ ]
245
+ }
246
+ ],
247
+ "metadata": {
248
+ "colab": {
249
+ "provenance": []
250
+ },
251
+ "kernelspec": {
252
+ "display_name": "Python 3",
253
+ "name": "python3"
254
+ },
255
+ "language_info": {
256
+ "name": "python"
257
+ }
258
+ },
259
+ "nbformat": 4,
260
+ "nbformat_minor": 0
261
+ }
XAUUSD_M15_Data.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:12e0adfe6dca2e13a67039ec8e8ff5317fef64494b5c7692d02925dcfa20ec49
3
+ size 34739911
XAUUSD_M3_Data.csv ADDED
The diff for this file is too large to render. See raw diff
 
data_fetcher.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import pytz
3
+ import numpy as np
4
+ import pandas as pd
5
+ import ta
6
+ import MetaTrader5 as mt5
7
+ from datetime import datetime
8
+
9
+ def fetch_data():
10
+ if not mt5.initialize():
11
+ print(f"MT5 initialize() failed, error code: {mt5.last_error()}")
12
+ return None
13
+
14
+ symbol = "XAUUSDc"
15
+
16
+ # Direct M3 fetch (Terminal was likely stalled before)
17
+ timeframe = mt5.TIMEFRAME_M3
18
+
19
+ # Use 2026 dates (updated methodologies)
20
+ # Fetching from March 1st to current (approx 19th)
21
+ utc_from = datetime(2026, 3, 1)
22
+ utc_to = datetime(2026, 3, 19)
23
+
24
+ print(f"Fetching data for {symbol} on M3 timeframe...")
25
+ rates = mt5.copy_rates_range(symbol, timeframe, utc_from, utc_to)
26
+
27
+ if rates is None or len(rates) == 0:
28
+ print(f"Failed to fetch rates for {symbol}, error code: {mt5.last_error()}")
29
+ mt5.shutdown()
30
+ return None
31
+
32
+ df = pd.DataFrame(rates)
33
+ df['time'] = pd.to_datetime(df['time'], unit='s')
34
+ df.set_index('time', inplace=True)
35
+
36
+ # Ensure standard OHLCV names
37
+ df = df[['open', 'high', 'low', 'close', 'tick_volume', 'spread', 'real_volume']]
38
+
39
+ print(f"Fetched and resampled {len(df)} rows. Calculating 100+ vectorized indicators...")
40
+
41
+ # Generate 90+ indicators automatically using the 'ta' library
42
+ df = ta.add_all_ta_features(df, open="open", high="high", low="low", close="close", volume="tick_volume", fillna=True)
43
+
44
+ # Add EXTRA indicators to exceed 100 count (approx 110-120 total)
45
+ # 1. Rolling statistics (10-step windows)
46
+ for col in ['open', 'high', 'low', 'close']:
47
+ df[f'{col}_roll_mean_10'] = df[col].rolling(10).mean()
48
+ df[f'{col}_roll_std_10'] = df[col].rolling(10).std()
49
+
50
+ # 2. Lags (1-5)
51
+ for i in range(1, 6):
52
+ df[f'close_lag_{i}'] = df['close'].shift(i)
53
+
54
+ # 3. RSI Variants
55
+ df['rsi_fast'] = ta.momentum.rsi(df['close'], window=7)
56
+ df['rsi_slow'] = ta.momentum.rsi(df['close'], window=21)
57
+
58
+ # 4. Volatility Helpers
59
+ df['atr_custom'] = ta.volatility.average_true_range(df['high'], df['low'], df['close'], window=14)
60
+
61
+ # Drop rows with NaN from new indicators
62
+ df.dropna(inplace=True)
63
+
64
+ # Drop columns that have all NaNs
65
+ df.dropna(axis=1, how='all', inplace=True)
66
+
67
+ # Drop rows with any NaN values resulting from indicator lookbacks
68
+ df.dropna(inplace=True)
69
+
70
+ output_filename = "XAUUSD_M3_Data.csv"
71
+ print(f"Saving {len(df)} clean rows to {output_filename} (Columns: {len(df.columns)})...")
72
+ df.to_csv(output_filename)
73
+
74
+ print("Data extraction and indicator generation complete!")
75
+ mt5.shutdown()
76
+
77
+ if __name__ == "__main__":
78
+ fetch_data()
fetch_mt5_data.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import MetaTrader5 as mt5
2
+ import pandas as pd
3
+ from datetime import datetime
4
+ import json
5
+
6
+ # ==================== MT5 INITIALIZATION ====================
7
+ if not mt5.initialize():
8
+ print("❌ MT5 initialize failed. Check terminal is open + logged in.")
9
+ quit()
10
+
11
+ symbol = "XAUUSDc"
12
+ timeframe = mt5.TIMEFRAME_M3
13
+
14
+ # ==================== DATE RANGE (auto-safe) ====================
15
+ date_from = datetime(2025, 1, 1)
16
+ date_to = datetime.now() # ← changed from fixed 2026-03-20
17
+
18
+ print(f"🔄 Fetching {symbol} M3 from {date_from.date()} to {date_to.date()} (UTC)...")
19
+
20
+ rates = mt5.copy_rates_range(symbol, timeframe, date_from, date_to)
21
+
22
+ # ==================== CRITICAL SAFETY CHECKS ====================
23
+ if rates is None:
24
+ print("❌ MT5 returned None. Possible reasons:")
25
+ print(" • Symbol does NOT exist on your broker")
26
+ print(" • History not downloaded (open chart + scroll back 1 year)")
27
+ print(" • Date range has no bars")
28
+ mt5.shutdown()
29
+ quit()
30
+
31
+ if len(rates) == 0:
32
+ print("❌ No bars returned (0 rows).")
33
+ print(" Try changing symbol to 'XAUUSD' (without 'c') or download history in MT5.")
34
+ mt5.shutdown()
35
+ quit()
36
+
37
+ print(f"✅ Fetched {len(rates)} bars successfully!")
38
+
39
+ # ==================== CREATE DATAFRAME ====================
40
+ df = pd.DataFrame(rates)
41
+
42
+ # Debug columns (this will now never crash)
43
+ print(f"Columns returned by MT5: {list(df.columns)}")
44
+
45
+ # Convert time (MT5 always returns unix seconds in 'time' column)
46
+ df['time'] = pd.to_datetime(df['time'], unit='s')
47
+
48
+ # Keep only needed columns + spread
49
+ df = df[['time', 'open', 'high', 'low', 'close', 'tick_volume', 'spread', 'real_volume']]
50
+
51
+ # ==================== SAVE CSV ====================
52
+ csv_path = "xauusd_3m_2025_2026.csv"
53
+ df.to_csv(csv_path, index=False)
54
+ print(f"💾 Saved {len(df)} bars to → {csv_path}")
55
+
56
+ # ==================== SAVE SYMBOL PARAMS (for Colab) ====================
57
+ info = mt5.symbol_info(symbol)
58
+ if info is None:
59
+ print("⚠️ symbol_info failed. Using fallback values.")
60
+ params = {"tick_size": 0.01, "tick_value": 1.0, "volume_min": 0.01, "volume_max": 200.0,
61
+ "volume_step": 0.01, "point": 0.01, "trade_calc_mode": 0}
62
+ else:
63
+ params = {
64
+ "tick_size": info.trade_tick_size,
65
+ "tick_value": info.trade_tick_value,
66
+ "volume_min": info.volume_min,
67
+ "volume_max": info.volume_max,
68
+ "volume_step": info.volume_step,
69
+ "point": info.point,
70
+ "trade_calc_mode": info.trade_calc_mode
71
+ }
72
+
73
+ with open("symbol_params.json", "w") as f:
74
+ json.dump(params, f, indent=2)
75
+ print("💾 Saved symbol_params.json")
76
+
77
+ mt5.shutdown()
78
+ print("\n🎉 DONE! Now upload BOTH files to Google Colab:")
79
+ print(" 1. xauusd_3m_2025_2026.csv")
80
+ print(" 2. symbol_params.json")
generate_notebook.py ADDED
@@ -0,0 +1,264 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+
3
+ notebook = {
4
+ "cells": [
5
+ {
6
+ "cell_type": "markdown",
7
+ "metadata": {},
8
+ "source": [
9
+ "# Phase 2: RL Trading Agent for MT5 (XAUUSDc)\n",
10
+ "This notebook trains a reinforcement learning model on the extracted MT5 data, simulating live-market constraints and exporting an ONNX model for the Expert Advisor."
11
+ ]
12
+ },
13
+ {
14
+ "cell_type": "code",
15
+ "execution_count": None,
16
+ "metadata": {},
17
+ "outputs": [],
18
+ "source": [
19
+ "!pip install -q stable-baselines3[extra] pandas_ta xgboost onnx onnxruntime plotly gym"
20
+ ]
21
+ },
22
+ {
23
+ "cell_type": "code",
24
+ "execution_count": None,
25
+ "metadata": {},
26
+ "outputs": [],
27
+ "source": [
28
+ "import os\n",
29
+ "import math\n",
30
+ "import numpy as np\n",
31
+ "import pandas as pd\n",
32
+ "import torch\n",
33
+ "import torch.nn as nn\n",
34
+ "import onnx\n",
35
+ "import onnxruntime as ort\n",
36
+ "import plotly.graph_objects as go\n",
37
+ "import gym\n",
38
+ "from gym import spaces\n",
39
+ "from stable_baselines3 import PPO\n",
40
+ "from google.colab import files\n",
41
+ "\n",
42
+ "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n",
43
+ "print(f\"Using device: {device}\")"
44
+ ]
45
+ },
46
+ {
47
+ "cell_type": "code",
48
+ "execution_count": None,
49
+ "metadata": {},
50
+ "outputs": [],
51
+ "source": [
52
+ "# Load Dataset (Upload XAUUSD_M3_Data.csv to Colab first)\n",
53
+ "if not os.path.exists('XAUUSD_M3_Data.csv'):\n",
54
+ " print(\"Please upload XAUUSD_M3_Data.csv to the Colab environment.\")\n",
55
+ "else:\n",
56
+ " df = pd.read_csv('XAUUSD_M3_Data.csv', index_col='time', parse_dates=True)\n",
57
+ " print(f\"Loaded {len(df)} rows.\")"
58
+ ]
59
+ },
60
+ {
61
+ "cell_type": "code",
62
+ "execution_count": None,
63
+ "metadata": {},
64
+ "outputs": [],
65
+ "source": [
66
+ "# Vectorized Custom Gym Environment for XAUUSDc\n",
67
+ "class XAUUSDM3Env(gym.Env):\n",
68
+ " def __init__(self, df, initial_balance=2000.0, risk_per_trade=0.02, max_lot_size=20.0):\n",
69
+ " super(XAUUSDM3Env, self).__init__()\n",
70
+ " self.df = df\n",
71
+ " self.prices = df['close'].values\n",
72
+ " self.spreads = df['spread'].values if 'spread' in df.columns else np.full(len(df), 20.0)\n",
73
+ " \n",
74
+ " # Features for observation (dropping strings/dates)\n",
75
+ " self.features = df.select_dtypes(include=[np.number]).fillna(0).values\n",
76
+ " \n",
77
+ " self.initial_balance = initial_balance\n",
78
+ " self.risk_per_trade = risk_per_trade\n",
79
+ " self.max_lot_size = max_lot_size\n",
80
+ " \n",
81
+ " # Actions: 0=Buy, 1=Sell, 2=Hold, 3=Do Nothing\n",
82
+ " self.action_space = spaces.Discrete(4)\n",
83
+ " \n",
84
+ " self.observation_space = spaces.Box(\n",
85
+ " low=-np.inf, high=np.inf, shape=(self.features.shape[1],), dtype=np.float32\n",
86
+ " )\n",
87
+ " \n",
88
+ " self.reset()\n",
89
+ "\n",
90
+ " def reset(self):\n",
91
+ " self.current_step = 0\n",
92
+ " self.balance = self.initial_balance\n",
93
+ " self.equity = self.initial_balance\n",
94
+ " self.current_position = 0 # 1=Long, -1=Short, 0=Flat\n",
95
+ " self.entry_price = 0.0\n",
96
+ " self.stop_loss = 0.0\n",
97
+ " self.take_profit = 0.0\n",
98
+ " self.lot_size = 0.0\n",
99
+ " self.history = []\n",
100
+ " return self.features[self.current_step]\n",
101
+ "\n",
102
+ " def _calculate_lot_size(self, sl_distance):\n",
103
+ " # 2% Risk\n",
104
+ " risk_amount = self.balance * self.risk_per_trade\n",
105
+ " # XAUUSDc lot size standard: $100 per $1 move for 1 lot usually. \n",
106
+ " sl_dollar_risk_per_lot = sl_distance * 100.0 \n",
107
+ " if sl_dollar_risk_per_lot <= 0:\n",
108
+ " return 0.01\n",
109
+ " \n",
110
+ " lots = risk_amount / sl_dollar_risk_per_lot\n",
111
+ " return max(0.01, round(lots, 2))\n",
112
+ "\n",
113
+ " def step(self, action):\n",
114
+ " done = False\n",
115
+ " reward = 0.0\n",
116
+ " \n",
117
+ " current_price = self.prices[self.current_step]\n",
118
+ " spread = self.spreads[self.current_step] / 100.0 # Standard conversion for points\n",
119
+ " \n",
120
+ " # Calculate equity running\n",
121
+ " if self.current_position == 1:\n",
122
+ " self.equity = self.balance + (current_price - self.entry_price) * 100.0 * self.lot_size\n",
123
+ " elif self.current_position == -1:\n",
124
+ " self.equity = self.balance + (self.entry_price - current_price) * 100.0 * self.lot_size\n",
125
+ "\n",
126
+ " # Execute at close-price\n",
127
+ " if action == 0 and self.current_position == 0:\n",
128
+ " # BUY\n",
129
+ " sl_dist = max(current_price * 0.005, spread * 10.0)\n",
130
+ " self.stop_loss = current_price - sl_dist\n",
131
+ " self.take_profit = current_price + (sl_dist * 2.0) # > 1R\n",
132
+ " self.entry_price = current_price + spread\n",
133
+ " self.lot_size = self._calculate_lot_size(sl_dist)\n",
134
+ " self.current_position = 1\n",
135
+ " \n",
136
+ " elif action == 1 and self.current_position == 0:\n",
137
+ " # SELL\n",
138
+ " sl_dist = max(current_price * 0.005, spread * 10.0)\n",
139
+ " self.stop_loss = current_price + sl_dist + spread\n",
140
+ " self.take_profit = current_price - (sl_dist * 2.0) \n",
141
+ " self.entry_price = current_price\n",
142
+ " self.lot_size = self._calculate_lot_size(sl_dist)\n",
143
+ " self.current_position = -1\n",
144
+ "\n",
145
+ " # Check SL / TP for exit\n",
146
+ " if self.current_position == 1:\n",
147
+ " if current_price <= self.stop_loss or current_price >= self.take_profit:\n",
148
+ " profit = (current_price - self.entry_price) * 100.0 * self.lot_size\n",
149
+ " self.balance += profit\n",
150
+ " self.equity = self.balance\n",
151
+ " self.current_position = 0\n",
152
+ " reward = profit\n",
153
+ " self.history.append({'type': 'long', 'profit': profit, 'lot': self.lot_size})\n",
154
+ " \n",
155
+ " elif self.current_position == -1:\n",
156
+ " if current_price >= self.stop_loss or current_price <= self.take_profit:\n",
157
+ " profit = (self.entry_price - current_price) * 100.0 * self.lot_size\n",
158
+ " self.balance += profit\n",
159
+ " self.equity = self.balance\n",
160
+ " self.current_position = 0\n",
161
+ " reward = profit\n",
162
+ " self.history.append({'type': 'short', 'profit': profit, 'lot': self.lot_size})\n",
163
+ "\n",
164
+ " self.current_step += 1\n",
165
+ " if self.current_step >= len(self.prices) - 1 or self.equity <= 0:\n",
166
+ " done = True\n",
167
+ " \n",
168
+ " next_state = self.features[self.current_step] if not done else np.zeros(self.features.shape[1])\n",
169
+ " return next_state, reward, done, {}\n"
170
+ ]
171
+ },
172
+ {
173
+ "cell_type": "code",
174
+ "execution_count": None,
175
+ "metadata": {},
176
+ "outputs": [],
177
+ "source": [
178
+ "# Train Model\n",
179
+ "if 'df' in locals():\n",
180
+ " train_size = int(len(df) * 0.7)\n",
181
+ " train_df = df.iloc[:train_size].copy()\n",
182
+ " test_df = df.iloc[train_size:].copy()\n",
183
+ " \n",
184
+ " env = XAUUSDM3Env(train_df)\n",
185
+ " model = PPO(\"MlpPolicy\", env, verbose=1, device=device)\n",
186
+ " \n",
187
+ " print(\"Starting RL Training...\")\n",
188
+ " model.learn(total_timesteps=50000)\n",
189
+ " print(\"Training Finished.\")\n"
190
+ ]
191
+ },
192
+ {
193
+ "cell_type": "code",
194
+ "execution_count": None,
195
+ "metadata": {},
196
+ "outputs": [],
197
+ "source": [
198
+ "# Plotting white-themed performance metrics\n",
199
+ "if 'env' in locals() and len(env.history) > 0:\n",
200
+ " profits = [x['profit'] for x in env.history]\n",
201
+ " cumulative = np.cumsum(profits)\n",
202
+ " \n",
203
+ " fig = go.Figure()\n",
204
+ " fig.add_trace(go.Scatter(y=cumulative, mode='lines', name='Cumulative Profit', line=dict(color='blue')))\n",
205
+ " fig.update_layout(\n",
206
+ " title=\"RL Agent Performance (Cumulative Profit)\",\n",
207
+ " xaxis_title=\"Trades\",\n",
208
+ " yaxis_title=\"USD Returns\",\n",
209
+ " template=\"plotly_white\"\n",
210
+ " )\n",
211
+ " fig.show()\n"
212
+ ]
213
+ },
214
+ {
215
+ "cell_type": "code",
216
+ "execution_count": None,
217
+ "metadata": {},
218
+ "outputs": [],
219
+ "source": [
220
+ "# Export to ONNX for MT5 Expert Advisor\n",
221
+ "if 'model' in locals():\n",
222
+ " class OnnxablePolicy(nn.Module):\n",
223
+ " def __init__(self, policy):\n",
224
+ " super().__init__()\n",
225
+ " self.policy = policy\n",
226
+ " \n",
227
+ " def forward(self, observation):\n",
228
+ " return self.policy(observation, deterministic=True)[0]\n",
229
+ " \n",
230
+ " onnx_policy = OnnxablePolicy(model.policy)\n",
231
+ " dummy_input = torch.randn(1, env.observation_space.shape[0]).to(device)\n",
232
+ " onnx_policy.to(device)\n",
233
+ " \n",
234
+ " onnx_path = \"RL_Agent_XAUUSD.onnx\"\n",
235
+ " torch.onnx.export(\n",
236
+ " onnx_policy,\n",
237
+ " dummy_input,\n",
238
+ " onnx_path,\n",
239
+ " opset_version=11,\n",
240
+ " input_names=[\"input\"],\n",
241
+ " output_names=[\"output\"]\n",
242
+ " )\n",
243
+ " print(f\"ONNX Model successfully exported to {onnx_path}. Next, download it and deploy to your MT5 EA.\")\n",
244
+ " try: files.download(onnx_path)\n",
245
+ " except: pass\n"
246
+ ]
247
+ }
248
+ ],
249
+ "metadata": {
250
+ "colab": {"provenance": []},
251
+ "kernelspec": {
252
+ "display_name": "Python 3",
253
+ "name": "python3"
254
+ },
255
+ "language_info": {"name": "python"}
256
+ },
257
+ "nbformat": 4,
258
+ "nbformat_minor": 0
259
+ }
260
+
261
+ with open("RL_XAUUSD_Colab_System.ipynb", "w") as f:
262
+ json.dump(notebook, f, indent=1)
263
+
264
+ print("Generated RL_XAUUSD_Colab_System.ipynb securely!")