Upload 7 files
Browse files- .gitattributes +1 -0
- RL_ONNX_EA.mq5 +135 -0
- RL_XAUUSD_Colab_System.ipynb +261 -0
- XAUUSD_M15_Data.csv +3 -0
- XAUUSD_M3_Data.csv +0 -0
- data_fetcher.py +78 -0
- fetch_mt5_data.py +80 -0
- generate_notebook.py +264 -0
.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!")
|