Update
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# Models and Data
|
||||
models/
|
||||
*.onnx
|
||||
*.h5
|
||||
*.pb
|
||||
*.pkl
|
||||
*.csv
|
||||
*.npy
|
||||
|
||||
# TensorFlow
|
||||
*.ckpt
|
||||
checkpoints/
|
||||
logs/
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Jupyter
|
||||
.ipynb_checkpoints/
|
||||
*.ipynb
|
||||
+406
@@ -0,0 +1,406 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| ONNX_EA.mq5 |
|
||||
//| Copyright 2025, MetaQuotes Ltd. |
|
||||
//| https://www.mql5.com |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Copyright 2025, MetaQuotes Ltd."
|
||||
#property link "https://www.mql5.com"
|
||||
#property version "1.00"
|
||||
#property description "Expert Advisor using ONNX model for price prediction"
|
||||
#property description "Based on: https://www.mql5.com/en/docs/onnx/onnx_prepare"
|
||||
|
||||
#include <Trade\Trade.mqh>
|
||||
|
||||
//--- Input parameters
|
||||
input group "ONNX Model Settings"
|
||||
input string InpModelPath = "models\\XAUUSD_H1_model.onnx"; // ONNX Model Path
|
||||
input int InpLookback = 60; // Lookback Period (bars)
|
||||
input bool InpUsePrediction = true; // Use Model Prediction
|
||||
|
||||
input group "Trading Settings"
|
||||
input double InpLotSize = 0.01; // Lot Size
|
||||
input int InpMagicNumber = 123456; // Magic Number
|
||||
input int InpSlippage = 3; // Slippage (points)
|
||||
input int InpStopLoss = 50; // Stop Loss (pips)
|
||||
input int InpTakeProfit = 100; // Take Profit (pips)
|
||||
|
||||
input group "Prediction Settings"
|
||||
input double InpPredictionThreshold = 0.0001; // Min Prediction Change (0.01%)
|
||||
input bool InpUseConfidence = true; // Use Confidence Filter
|
||||
input double InpMinConfidence = 0.6; // Minimum Confidence
|
||||
|
||||
//--- Global variables
|
||||
CTrade trade;
|
||||
long onnx_handle = INVALID_HANDLE;
|
||||
datetime last_bar_time = 0;
|
||||
double last_prediction = 0.0;
|
||||
double last_confidence = 0.0;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert initialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit()
|
||||
{
|
||||
// Set trade parameters
|
||||
trade.SetExpertMagicNumber(InpMagicNumber);
|
||||
trade.SetDeviationInPoints(InpSlippage);
|
||||
trade.SetTypeFilling(ORDER_FILLING_FOK);
|
||||
|
||||
// Load ONNX model
|
||||
string model_path = InpModelPath;
|
||||
|
||||
// Convert relative path to full path
|
||||
if(StringFind(model_path, "\\") == 0 || StringFind(model_path, "/") == 0)
|
||||
{
|
||||
// Already absolute path
|
||||
}
|
||||
else
|
||||
{
|
||||
// Relative path - prepend terminal data folder
|
||||
model_path = TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL5\\Files\\" + model_path;
|
||||
}
|
||||
|
||||
// Replace forward slashes with backslashes for Windows
|
||||
StringReplace(model_path, "/", "\\");
|
||||
|
||||
Print("Loading ONNX model from: ", model_path);
|
||||
|
||||
onnx_handle = OnnxCreate(model_path, ONNX_DEFAULT);
|
||||
|
||||
if(onnx_handle == INVALID_HANDLE)
|
||||
{
|
||||
Print("ERROR: Failed to load ONNX model. Error: ", GetLastError());
|
||||
Print("Make sure the model file exists at: ", model_path);
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
// Get model info
|
||||
int input_count = OnnxGetInputCount(onnx_handle);
|
||||
int output_count = OnnxGetOutputCount(onnx_handle);
|
||||
|
||||
Print("ONNX Model loaded successfully");
|
||||
Print(" Inputs: ", input_count);
|
||||
Print(" Outputs: ", output_count);
|
||||
|
||||
if(input_count > 0)
|
||||
{
|
||||
string input_name = OnnxGetInputName(onnx_handle, 0);
|
||||
Print(" Input name: ", input_name);
|
||||
}
|
||||
|
||||
if(output_count > 0)
|
||||
{
|
||||
string output_name = OnnxGetOutputName(onnx_handle, 0);
|
||||
Print(" Output name: ", output_name);
|
||||
}
|
||||
|
||||
return(INIT_SUCCEEDED);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert deinitialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
// Release ONNX model
|
||||
if(onnx_handle != INVALID_HANDLE)
|
||||
{
|
||||
OnnxRelease(onnx_handle);
|
||||
Print("ONNX model released");
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert tick function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
// Check if new bar
|
||||
datetime current_bar_time = iTime(_Symbol, PERIOD_CURRENT, 0);
|
||||
if(current_bar_time == last_bar_time)
|
||||
{
|
||||
return; // Still the same bar
|
||||
}
|
||||
last_bar_time = current_bar_time;
|
||||
|
||||
// Check if we should use prediction
|
||||
if(!InpUsePrediction)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare input data
|
||||
float input_data[];
|
||||
if(!PrepareInputData(input_data))
|
||||
{
|
||||
Print("ERROR: Failed to prepare input data");
|
||||
return;
|
||||
}
|
||||
|
||||
// Run ONNX model
|
||||
float output_data[];
|
||||
if(!RunONNXModel(input_data, output_data))
|
||||
{
|
||||
Print("ERROR: Failed to run ONNX model");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get prediction
|
||||
if(ArraySize(output_data) == 0)
|
||||
{
|
||||
Print("ERROR: Empty output from ONNX model");
|
||||
return;
|
||||
}
|
||||
|
||||
double predicted_price = output_data[0];
|
||||
double current_price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
|
||||
// Calculate prediction change
|
||||
double price_change = predicted_price - current_price;
|
||||
double price_change_pct = (price_change / current_price) * 100.0;
|
||||
|
||||
// Calculate confidence (simple heuristic based on prediction magnitude)
|
||||
double confidence = MathAbs(price_change_pct) / 1.0; // Normalize
|
||||
if(confidence > 1.0) confidence = 1.0;
|
||||
|
||||
last_prediction = predicted_price;
|
||||
last_confidence = confidence;
|
||||
|
||||
// Log prediction
|
||||
Print("Prediction: Current=", current_price,
|
||||
" Predicted=", predicted_price,
|
||||
" Change=", price_change_pct, "%",
|
||||
" Confidence=", confidence);
|
||||
|
||||
// Check if we should trade
|
||||
if(!InpUseConfidence || confidence >= InpMinConfidence)
|
||||
{
|
||||
// Check if prediction is significant
|
||||
if(MathAbs(price_change_pct) >= InpPredictionThreshold)
|
||||
{
|
||||
// Check existing position
|
||||
if(PositionSelect(_Symbol))
|
||||
{
|
||||
// Manage existing position
|
||||
ManagePosition(predicted_price, price_change_pct);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Open new position based on prediction
|
||||
if(price_change_pct > InpPredictionThreshold)
|
||||
{
|
||||
OpenBuyPosition();
|
||||
}
|
||||
else if(price_change_pct < -InpPredictionThreshold)
|
||||
{
|
||||
OpenSellPosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Prepare input data for ONNX model |
|
||||
//+------------------------------------------------------------------+
|
||||
bool PrepareInputData(float &input_array[])
|
||||
{
|
||||
// We need to prepare data similar to training
|
||||
// This is a simplified version - you may need to adjust based on your model
|
||||
|
||||
int lookback = InpLookback;
|
||||
int features = 12; // Adjust based on your model (OHLC + volume + indicators)
|
||||
|
||||
ArrayResize(input_array, lookback * features);
|
||||
ArrayInitialize(input_array, 0.0);
|
||||
|
||||
// Get historical data
|
||||
double open[], high[], low[], close[], volume[];
|
||||
ArraySetAsSeries(open, true);
|
||||
ArraySetAsSeries(high, true);
|
||||
ArraySetAsSeries(low, true);
|
||||
ArraySetAsSeries(close, true);
|
||||
ArraySetAsSeries(volume, true);
|
||||
|
||||
if(CopyOpen(_Symbol, PERIOD_CURRENT, 0, lookback + 50, open) < lookback)
|
||||
return false;
|
||||
if(CopyHigh(_Symbol, PERIOD_CURRENT, 0, lookback + 50, high) < lookback)
|
||||
return false;
|
||||
if(CopyLow(_Symbol, PERIOD_CURRENT, 0, lookback + 50, low) < lookback)
|
||||
return false;
|
||||
if(CopyClose(_Symbol, PERIOD_CURRENT, 0, lookback + 50, close) < lookback)
|
||||
return false;
|
||||
if(CopyTickVolume(_Symbol, PERIOD_CURRENT, 0, lookback + 50, volume) < lookback)
|
||||
return false;
|
||||
|
||||
// Calculate indicators (simplified - you may need to match training exactly)
|
||||
double rsi[], ema20[], ema50[], atr[];
|
||||
ArraySetAsSeries(rsi, true);
|
||||
ArraySetAsSeries(ema20, true);
|
||||
ArraySetAsSeries(ema50, true);
|
||||
ArraySetAsSeries(atr, true);
|
||||
|
||||
// Calculate RSI
|
||||
int rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE);
|
||||
if(rsi_handle == INVALID_HANDLE) return false;
|
||||
if(CopyBuffer(rsi_handle, 0, 0, lookback + 50, rsi) < lookback)
|
||||
{
|
||||
IndicatorRelease(rsi_handle);
|
||||
return false;
|
||||
}
|
||||
IndicatorRelease(rsi_handle);
|
||||
|
||||
// Calculate EMAs
|
||||
int ema20_handle = iMA(_Symbol, PERIOD_CURRENT, 20, 0, MODE_EMA, PRICE_CLOSE);
|
||||
int ema50_handle = iMA(_Symbol, PERIOD_CURRENT, 50, 0, MODE_EMA, PRICE_CLOSE);
|
||||
if(ema20_handle == INVALID_HANDLE || ema50_handle == INVALID_HANDLE) return false;
|
||||
|
||||
if(CopyBuffer(ema20_handle, 0, 0, lookback + 50, ema20) < lookback ||
|
||||
CopyBuffer(ema50_handle, 0, 0, lookback + 50, ema50) < lookback)
|
||||
{
|
||||
IndicatorRelease(ema20_handle);
|
||||
IndicatorRelease(ema50_handle);
|
||||
return false;
|
||||
}
|
||||
IndicatorRelease(ema20_handle);
|
||||
IndicatorRelease(ema50_handle);
|
||||
|
||||
// Calculate ATR
|
||||
int atr_handle = iATR(_Symbol, PERIOD_CURRENT, 14);
|
||||
if(atr_handle == INVALID_HANDLE) return false;
|
||||
if(CopyBuffer(atr_handle, 0, 0, lookback + 50, atr) < lookback)
|
||||
{
|
||||
IndicatorRelease(atr_handle);
|
||||
return false;
|
||||
}
|
||||
IndicatorRelease(atr_handle);
|
||||
|
||||
// Normalize and prepare data (simplified normalization)
|
||||
// IMPORTANT: This uses simplified normalization. For best results, you should:
|
||||
// 1. Save the scaler during training (train_onnx_model.py does this automatically)
|
||||
// 2. Implement the same normalization logic in MQL5, OR
|
||||
// 3. Pre-normalize data in Python and pass to MQL5 via files/global variables
|
||||
// The current implementation may not match training exactly, which can affect accuracy
|
||||
int idx = 0;
|
||||
for(int i = 0; i < lookback; i++)
|
||||
{
|
||||
// Normalize features (simplified - use proper scaler in production)
|
||||
input_array[idx++] = (float)((open[i] - close[lookback-1]) / close[lookback-1]);
|
||||
input_array[idx++] = (float)((high[i] - close[lookback-1]) / close[lookback-1]);
|
||||
input_array[idx++] = (float)((low[i] - close[lookback-1]) / close[lookback-1]);
|
||||
input_array[idx++] = (float)((close[i] - close[lookback-1]) / close[lookback-1]);
|
||||
input_array[idx++] = (float)(volume[i] / 1000000.0); // Normalize volume
|
||||
input_array[idx++] = (float)(rsi[i] / 100.0); // Normalize RSI
|
||||
input_array[idx++] = (float)((ema20[i] - close[lookback-1]) / close[lookback-1]);
|
||||
input_array[idx++] = (float)((ema50[i] - close[lookback-1]) / close[lookback-1]);
|
||||
input_array[idx++] = (float)(atr[i] / close[lookback-1]);
|
||||
input_array[idx++] = (float)((close[i] - close[i+1]) / close[i+1]); // Price change
|
||||
input_array[idx++] = (float)(high[i] / low[i]); // High/low ratio
|
||||
input_array[idx++] = (float)(volume[i] / (volume[i] + volume[i+1] + volume[i+2]) / 3.0); // Volume ratio
|
||||
}
|
||||
|
||||
// Reshape for model: (1, lookback, features)
|
||||
// ONNX expects shape [1, lookback, features]
|
||||
float reshaped[];
|
||||
ArrayResize(reshaped, 1 * lookback * features);
|
||||
ArrayCopy(reshaped, input_array);
|
||||
|
||||
ArrayCopy(input_array, reshaped);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Run ONNX model |
|
||||
//+------------------------------------------------------------------+
|
||||
bool RunONNXModel(float &input_data[], float &output_data[])
|
||||
{
|
||||
if(onnx_handle == INVALID_HANDLE)
|
||||
return false;
|
||||
|
||||
// Set input shape
|
||||
long input_shape[] = {1, InpLookback, 12}; // Adjust based on your model
|
||||
if(!OnnxSetInputShape(onnx_handle, 0, input_shape))
|
||||
{
|
||||
Print("ERROR: Failed to set input shape. Error: ", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Run model
|
||||
if(!OnnxRun(onnx_handle, ONNX_NO_CONVERSION, input_data, output_data))
|
||||
{
|
||||
Print("ERROR: Failed to run ONNX model. Error: ", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Open buy position |
|
||||
//+------------------------------------------------------------------+
|
||||
void OpenBuyPosition()
|
||||
{
|
||||
double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
double sl = InpStopLoss > 0 ? price - InpStopLoss * _Point * 10 : 0;
|
||||
double tp = InpTakeProfit > 0 ? price + InpTakeProfit * _Point * 10 : 0;
|
||||
|
||||
if(trade.Buy(InpLotSize, _Symbol, price, sl, tp, "ONNX Buy Signal"))
|
||||
{
|
||||
Print("Buy order opened. Ticket: ", trade.ResultOrder());
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Failed to open buy order. Error: ", trade.ResultRetcodeDescription());
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Open sell position |
|
||||
//+------------------------------------------------------------------+
|
||||
void OpenSellPosition()
|
||||
{
|
||||
double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
double sl = InpStopLoss > 0 ? price + InpStopLoss * _Point * 10 : 0;
|
||||
double tp = InpTakeProfit > 0 ? price - InpTakeProfit * _Point * 10 : 0;
|
||||
|
||||
if(trade.Sell(InpLotSize, _Symbol, price, sl, tp, "ONNX Sell Signal"))
|
||||
{
|
||||
Print("Sell order opened. Ticket: ", trade.ResultOrder());
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Failed to open sell order. Error: ", trade.ResultRetcodeDescription());
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Manage existing position |
|
||||
//+------------------------------------------------------------------+
|
||||
void ManagePosition(double predicted_price, double price_change_pct)
|
||||
{
|
||||
if(!PositionSelect(_Symbol))
|
||||
return;
|
||||
|
||||
long position_type = PositionGetInteger(POSITION_TYPE);
|
||||
double position_open_price = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
double current_profit = PositionGetDouble(POSITION_PROFIT);
|
||||
|
||||
// Simple management: close if prediction reverses
|
||||
if(position_type == POSITION_TYPE_BUY && price_change_pct < -InpPredictionThreshold)
|
||||
{
|
||||
// Prediction turned bearish, close long
|
||||
if(trade.PositionClose(_Symbol))
|
||||
{
|
||||
Print("Closed long position due to bearish prediction");
|
||||
}
|
||||
}
|
||||
else if(position_type == POSITION_TYPE_SELL && price_change_pct > InpPredictionThreshold)
|
||||
{
|
||||
// Prediction turned bullish, close short
|
||||
if(trade.PositionClose(_Symbol))
|
||||
{
|
||||
Print("Closed short position due to bullish prediction");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
# Quick Start Guide - ONNX with MetaTrader 5
|
||||
|
||||
Get started with ONNX machine learning models in MT5 in 5 minutes!
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **MetaTrader 5** installed and running
|
||||
2. **Python 3.8+** installed
|
||||
3. **MT5 account** (demo or live) with access to historical data
|
||||
|
||||
## Step 1: Install Dependencies
|
||||
|
||||
```bash
|
||||
cd ai
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
This installs:
|
||||
- TensorFlow/Keras for model training
|
||||
- ONNX runtime for model inference
|
||||
- MetaTrader5 Python module
|
||||
- Other required libraries
|
||||
|
||||
## Step 2: Verify MT5 Connection
|
||||
|
||||
Make sure MT5 is running and you're logged in. The scripts will automatically connect to MT5.
|
||||
|
||||
## Step 3: Train Your First Model
|
||||
|
||||
Train a price prediction model for Gold (XAUUSD):
|
||||
|
||||
```bash
|
||||
python train_onnx_model.py --symbol XAUUSD --timeframe H1 --epochs 30
|
||||
```
|
||||
|
||||
This will:
|
||||
- Download 2 years of historical data
|
||||
- Train an LSTM neural network
|
||||
- Save the model as `models/XAUUSD_H1_model.onnx`
|
||||
|
||||
**Expected time**: 5-15 minutes depending on your hardware.
|
||||
|
||||
## Step 4: Test the Model
|
||||
|
||||
Make a prediction with your trained model:
|
||||
|
||||
```bash
|
||||
python predict_with_onnx.py --model models/XAUUSD_H1_model.onnx --symbol XAUUSD
|
||||
```
|
||||
|
||||
You should see output like:
|
||||
```
|
||||
Current XAUUSD price: 2650.12345
|
||||
Making 1 prediction(s)...
|
||||
|
||||
Predicted next price: 2652.54321
|
||||
Expected change: 2.41976 (0.09%)
|
||||
```
|
||||
|
||||
## Step 5: Use in MetaTrader 5
|
||||
|
||||
### Option A: Copy Model to MT5
|
||||
|
||||
1. Copy your ONNX model to MT5's Files folder:
|
||||
```
|
||||
<MT5 Data Folder>\MQL5\Files\models\XAUUSD_H1_model.onnx
|
||||
```
|
||||
|
||||
Default locations:
|
||||
- Windows: `C:\Users\<YourName>\AppData\Roaming\MetaQuotes\Terminal\<TerminalID>\MQL5\Files\`
|
||||
- Or find it: MT5 → File → Open Data Folder → MQL5 → Files
|
||||
|
||||
2. Open `ONNX_EA.mq5` in MetaEditor
|
||||
|
||||
3. Compile (F7)
|
||||
|
||||
4. Attach to chart:
|
||||
- Model path: `models\XAUUSD_H1_model.onnx`
|
||||
- Lookback: `60` (must match training)
|
||||
- Set your trading parameters
|
||||
|
||||
### Option B: Run from MetaEditor
|
||||
|
||||
If you have Python integration enabled in MetaEditor:
|
||||
|
||||
1. Open `train_onnx_model.py` in MetaEditor
|
||||
2. Press F7 (Compile) to run
|
||||
3. The model will be saved to the project folder
|
||||
|
||||
## Common Commands
|
||||
|
||||
### Train for Different Symbols
|
||||
|
||||
```bash
|
||||
# EUR/USD on 15-minute charts
|
||||
python train_onnx_model.py --symbol EURUSD --timeframe M15
|
||||
|
||||
# Bitcoin on 4-hour charts
|
||||
python train_onnx_model.py --symbol BTCUSD --timeframe H4
|
||||
```
|
||||
|
||||
### Custom Training Parameters
|
||||
|
||||
```bash
|
||||
python train_onnx_model.py \
|
||||
--symbol XAUUSD \
|
||||
--timeframe H1 \
|
||||
--lookback 100 \
|
||||
--epochs 100 \
|
||||
--batch-size 64
|
||||
```
|
||||
|
||||
### Multiple Predictions
|
||||
|
||||
```bash
|
||||
python predict_with_onnx.py \
|
||||
--model models/XAUUSD_H1_model.onnx \
|
||||
--symbol XAUUSD \
|
||||
--predictions 5
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "MT5 initialization failed"
|
||||
- ✅ Make sure MT5 is running
|
||||
- ✅ Log into your account in MT5
|
||||
- ✅ Check that the symbol exists (e.g., XAUUSD, not GOLD)
|
||||
|
||||
### "No data available"
|
||||
- ✅ Ensure you have historical data downloaded in MT5
|
||||
- ✅ Check the date range (script uses last 2 years)
|
||||
- ✅ Verify symbol name is correct
|
||||
|
||||
### "Failed to load ONNX model" in EA
|
||||
- ✅ Check the file path is correct
|
||||
- ✅ Ensure model is in MT5's Files folder
|
||||
- ✅ Verify the model file exists and is not corrupted
|
||||
|
||||
### Model predictions seem wrong
|
||||
- ✅ Ensure `InpLookback` in EA matches training `--lookback`
|
||||
- ✅ Check that you're using the same symbol/timeframe
|
||||
- ✅ Verify feature normalization matches training
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Experiment with different models**:
|
||||
- Try different lookback periods
|
||||
- Adjust network architecture
|
||||
- Add more features
|
||||
|
||||
2. **Optimize trading parameters**:
|
||||
- Test different prediction thresholds
|
||||
- Tune stop loss/take profit
|
||||
- Adjust confidence levels
|
||||
|
||||
3. **Backtest thoroughly**:
|
||||
- Use MT5 Strategy Tester
|
||||
- Test on different time periods
|
||||
- Analyze performance metrics
|
||||
|
||||
4. **Monitor and improve**:
|
||||
- Track prediction accuracy
|
||||
- Retrain models periodically
|
||||
- Adjust based on market conditions
|
||||
|
||||
## Example Workflow
|
||||
|
||||
```bash
|
||||
# 1. Train model
|
||||
python train_onnx_model.py --symbol XAUUSD --timeframe H1 --epochs 50
|
||||
|
||||
# 2. Test predictions
|
||||
python predict_with_onnx.py --model models/XAUUSD_H1_model.onnx --symbol XAUUSD
|
||||
|
||||
# 3. Copy model to MT5 Files folder
|
||||
# (Manual step)
|
||||
|
||||
# 4. Compile and attach ONNX_EA.mq5 to chart
|
||||
|
||||
# 5. Monitor and adjust parameters
|
||||
```
|
||||
|
||||
## Tips
|
||||
|
||||
- 🎯 Start with longer timeframes (H1, H4) for more stable predictions
|
||||
- 📊 Use multiple models for different market conditions
|
||||
- 🔄 Retrain models periodically (weekly/monthly)
|
||||
- ⚠️ Always test on demo account first
|
||||
- 📈 Monitor model performance and adjust parameters
|
||||
|
||||
## Need Help?
|
||||
|
||||
- Check the main [README.md](README.md) for detailed documentation
|
||||
- Review the MQL5 ONNX documentation: https://www.mql5.com/en/docs/onnx/onnx_prepare
|
||||
- Examine the code comments for implementation details
|
||||
|
||||
Happy trading! 🚀
|
||||
+310
@@ -0,0 +1,310 @@
|
||||
# ONNX Models with MetaTrader 5
|
||||
|
||||
A complete framework for training and using ONNX machine learning models in MetaTrader 5 for algorithmic trading.
|
||||
|
||||
Based on the [MQL5 ONNX documentation](https://www.mql5.com/en/docs/onnx/onnx_prepare).
|
||||
|
||||
## Overview
|
||||
|
||||
This framework allows you to:
|
||||
1. **Train neural network models** in Python using MetaTrader 5 historical data
|
||||
2. **Export models to ONNX format** for use in MQL5
|
||||
3. **Use ONNX models in Expert Advisors** for real-time trading predictions
|
||||
4. **Test predictions** using Python scripts
|
||||
|
||||
## Features
|
||||
|
||||
- 🧠 **LSTM Neural Networks** for price prediction
|
||||
- 📊 **Technical Indicators** as features (RSI, EMA, ATR, etc.)
|
||||
- 🔄 **ONNX Export** for MQL5 integration
|
||||
- 📈 **Real-time Prediction** in Expert Advisors
|
||||
- 🎯 **Flexible Configuration** for different symbols and timeframes
|
||||
|
||||
## Installation
|
||||
|
||||
### 1. Install Python Dependencies
|
||||
|
||||
```bash
|
||||
cd ai
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 2. Install MetaTrader 5
|
||||
|
||||
- Download and install [MetaTrader 5](https://www.metatrader5.com/en/download)
|
||||
- Create a demo or live account
|
||||
- Enable Python integration in MT5 settings:
|
||||
- Tools → Options → Expert Advisors
|
||||
- Check "Allow DLL imports"
|
||||
- Check "Integration with Python" (if available)
|
||||
|
||||
### 3. Configure MetaEditor (Optional)
|
||||
|
||||
If you want to run Python scripts from MetaEditor:
|
||||
- MetaEditor → Tools → Options → Compiler
|
||||
- Set Python executable path
|
||||
- Or click "Install" to download Python
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Step 1: Train an ONNX Model
|
||||
|
||||
Train a model for price prediction:
|
||||
|
||||
```bash
|
||||
python train_onnx_model.py --symbol XAUUSD --timeframe H1 --lookback 60 --epochs 50
|
||||
```
|
||||
|
||||
This will:
|
||||
- Fetch 2 years of historical data from MT5
|
||||
- Prepare features (OHLCV + technical indicators)
|
||||
- Train an LSTM neural network
|
||||
- Export the model to `models/XAUUSD_H1_model.onnx`
|
||||
|
||||
### Step 2: Test the Model
|
||||
|
||||
Make predictions using the trained model:
|
||||
|
||||
```bash
|
||||
python predict_with_onnx.py --model models/XAUUSD_H1_model.onnx --symbol XAUUSD --timeframe H1
|
||||
```
|
||||
|
||||
### Step 3: Use in Expert Advisor
|
||||
|
||||
1. Copy the ONNX model to MT5's Files folder:
|
||||
```
|
||||
<MT5 Data Folder>\MQL5\Files\models\XAUUSD_H1_model.onnx
|
||||
```
|
||||
|
||||
2. Compile `ONNX_EA.mq5` in MetaEditor
|
||||
|
||||
3. Attach the EA to a chart with:
|
||||
- Model path: `models\XAUUSD_H1_model.onnx`
|
||||
- Your trading parameters
|
||||
|
||||
## Detailed Usage
|
||||
|
||||
### Training Models
|
||||
|
||||
#### Basic Training
|
||||
|
||||
```bash
|
||||
python train_onnx_model.py \
|
||||
--symbol XAUUSD \
|
||||
--timeframe H1 \
|
||||
--lookback 60 \
|
||||
--epochs 50 \
|
||||
--batch-size 32
|
||||
```
|
||||
|
||||
#### Advanced Options
|
||||
|
||||
```bash
|
||||
python train_onnx_model.py \
|
||||
--symbol EURUSD \
|
||||
--timeframe M15 \
|
||||
--lookback 100 \
|
||||
--epochs 100 \
|
||||
--batch-size 64 \
|
||||
--output custom_models
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `--symbol`: Trading symbol (XAUUSD, EURUSD, BTCUSD, etc.)
|
||||
- `--timeframe`: M1, M5, M15, M30, H1, H4, D1
|
||||
- `--lookback`: Number of bars to use for prediction (default: 60)
|
||||
- `--epochs`: Training epochs (default: 50)
|
||||
- `--batch-size`: Batch size (default: 32)
|
||||
- `--output`: Output directory (default: models)
|
||||
|
||||
### Making Predictions
|
||||
|
||||
#### Single Prediction
|
||||
|
||||
```bash
|
||||
python predict_with_onnx.py \
|
||||
--model models/XAUUSD_H1_model.onnx \
|
||||
--symbol XAUUSD \
|
||||
--timeframe H1
|
||||
```
|
||||
|
||||
#### Multiple Predictions
|
||||
|
||||
```bash
|
||||
python predict_with_onnx.py \
|
||||
--model models/XAUUSD_H1_model.onnx \
|
||||
--symbol XAUUSD \
|
||||
--timeframe H1 \
|
||||
--predictions 5
|
||||
```
|
||||
|
||||
### Expert Advisor Configuration
|
||||
|
||||
The `ONNX_EA.mq5` Expert Advisor includes:
|
||||
|
||||
**ONNX Model Settings:**
|
||||
- `InpModelPath`: Path to ONNX model file
|
||||
- `InpLookback`: Lookback period (must match training)
|
||||
- `InpUsePrediction`: Enable/disable model predictions
|
||||
|
||||
**Trading Settings:**
|
||||
- `InpLotSize`: Position size
|
||||
- `InpMagicNumber`: Magic number for trades
|
||||
- `InpStopLoss`: Stop loss in pips
|
||||
- `InpTakeProfit`: Take profit in pips
|
||||
|
||||
**Prediction Settings:**
|
||||
- `InpPredictionThreshold`: Minimum prediction change to trade (0.01% = 0.0001)
|
||||
- `InpUseConfidence`: Enable confidence filtering
|
||||
- `InpMinConfidence`: Minimum confidence level (0.0-1.0)
|
||||
|
||||
## Model Architecture
|
||||
|
||||
The default model uses:
|
||||
- **Input**: 60 bars × 12 features
|
||||
- **Architecture**:
|
||||
- LSTM(128) → Dropout(0.2)
|
||||
- LSTM(64) → Dropout(0.2)
|
||||
- LSTM(32) → Dropout(0.2)
|
||||
- Dense(16, ReLU)
|
||||
- Dense(1) - Price prediction
|
||||
- **Features**:
|
||||
- OHLC prices
|
||||
- Tick volume
|
||||
- RSI (14)
|
||||
- EMA(20), EMA(50)
|
||||
- ATR(14)
|
||||
- Price changes
|
||||
- High/Low ratio
|
||||
- Volume ratios
|
||||
|
||||
## Customization
|
||||
|
||||
### Modify Features
|
||||
|
||||
Edit `train_onnx_model.py` to add/remove features:
|
||||
|
||||
```python
|
||||
def prepare_features(self, df: pd.DataFrame) -> pd.DataFrame:
|
||||
feature_df = df[['open', 'high', 'low', 'close', 'tick_volume']].copy()
|
||||
|
||||
# Add your custom indicators
|
||||
feature_df['custom_indicator'] = your_calculation(df)
|
||||
|
||||
return feature_df
|
||||
```
|
||||
|
||||
### Change Model Architecture
|
||||
|
||||
Modify `build_model()` in `train_onnx_model.py`:
|
||||
|
||||
```python
|
||||
def build_model(self, input_shape: tuple) -> keras.Model:
|
||||
model = keras.Sequential([
|
||||
layers.LSTM(256, return_sequences=True, input_shape=input_shape),
|
||||
# Add your layers here
|
||||
layers.Dense(1)
|
||||
])
|
||||
return model
|
||||
```
|
||||
|
||||
### Adjust Expert Advisor Logic
|
||||
|
||||
Edit `ONNX_EA.mq5` to customize trading logic:
|
||||
- Entry conditions
|
||||
- Exit conditions
|
||||
- Position management
|
||||
- Risk management
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
ai/
|
||||
├── requirements.txt # Python dependencies
|
||||
├── train_onnx_model.py # Model training script
|
||||
├── predict_with_onnx.py # Prediction testing script
|
||||
├── ONNX_EA.mq5 # MQL5 Expert Advisor
|
||||
├── README.md # This file
|
||||
└── models/ # Trained ONNX models (created after training)
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### MT5 Connection Issues
|
||||
|
||||
**Error**: "MT5 initialization failed"
|
||||
- Ensure MetaTrader 5 is installed and running
|
||||
- Log into a demo or live account
|
||||
- Check that the symbol exists in MT5
|
||||
|
||||
### Model Loading Issues
|
||||
|
||||
**Error**: "Failed to load ONNX model"
|
||||
- Verify the model file path is correct
|
||||
- Ensure the model file is in MT5's Files folder
|
||||
- Check that the model was exported correctly
|
||||
|
||||
### Prediction Issues
|
||||
|
||||
**Error**: "Failed to prepare input data"
|
||||
- Ensure enough historical data is available
|
||||
- Check that lookback period matches training
|
||||
- Verify indicators can be calculated
|
||||
|
||||
### Shape Mismatch Errors
|
||||
|
||||
If you get shape mismatch errors:
|
||||
1. Check that `InpLookback` in EA matches training `--lookback`
|
||||
2. Verify feature count matches (default: 12 features)
|
||||
3. Ensure input normalization matches training
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Data Quality**: Use high-quality historical data
|
||||
2. **Feature Engineering**: Experiment with different indicators
|
||||
3. **Model Validation**: Always validate on out-of-sample data
|
||||
4. **Risk Management**: Use stop loss and position sizing
|
||||
5. **Backtesting**: Test thoroughly before live trading
|
||||
6. **Monitoring**: Monitor model performance regularly
|
||||
|
||||
## Example Workflow
|
||||
|
||||
1. **Train Model**:
|
||||
```bash
|
||||
python train_onnx_model.py --symbol XAUUSD --timeframe H1
|
||||
```
|
||||
|
||||
2. **Test Predictions**:
|
||||
```bash
|
||||
python predict_with_onnx.py --model models/XAUUSD_H1_model.onnx --symbol XAUUSD
|
||||
```
|
||||
|
||||
3. **Backtest in MT5**:
|
||||
- Use Strategy Tester with `ONNX_EA.mq5`
|
||||
- Test on historical data
|
||||
- Analyze results
|
||||
|
||||
4. **Optimize Parameters**:
|
||||
- Adjust prediction threshold
|
||||
- Tune confidence levels
|
||||
- Optimize stop loss/take profit
|
||||
|
||||
5. **Deploy**:
|
||||
- Start with small position sizes
|
||||
- Monitor performance
|
||||
- Adjust as needed
|
||||
|
||||
## References
|
||||
|
||||
- [MQL5 ONNX Documentation](https://www.mql5.com/en/docs/onnx/onnx_prepare)
|
||||
- [ONNX Model Zoo](https://github.com/onnx/models)
|
||||
- [MetaTrader 5 Python Module](https://pypi.org/project/MetaTrader5/)
|
||||
- [TensorFlow to ONNX](https://github.com/onnx/tensorflow-onnx)
|
||||
|
||||
## Disclaimer
|
||||
|
||||
Trading involves substantial risk of loss. This framework is provided for educational purposes only. Always test thoroughly on a demo account before using with real money. Past performance does not guarantee future results.
|
||||
|
||||
## License
|
||||
|
||||
This framework is provided for educational and research purposes.
|
||||
@@ -0,0 +1,159 @@
|
||||
"""
|
||||
Complete Example Workflow for ONNX + MT5
|
||||
|
||||
This script demonstrates the complete workflow:
|
||||
1. Train an ONNX model
|
||||
2. Test predictions
|
||||
3. Show how to use in MT5
|
||||
|
||||
Run this to see the full process in action.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
import MetaTrader5 as mt5
|
||||
|
||||
# Import our modules
|
||||
from train_onnx_model import ONNXModelTrainer
|
||||
from predict_with_onnx import ONNXPredictor
|
||||
|
||||
|
||||
def main():
|
||||
"""Complete workflow example."""
|
||||
print("="*60)
|
||||
print("ONNX + MetaTrader 5 - Complete Workflow Example")
|
||||
print("="*60)
|
||||
|
||||
# Configuration
|
||||
symbol = 'XAUUSD'
|
||||
timeframe_str = 'H1'
|
||||
lookback = 60
|
||||
epochs = 20 # Reduced for quick demo
|
||||
|
||||
# Convert timeframe
|
||||
timeframe_map = {
|
||||
'M1': mt5.TIMEFRAME_M1,
|
||||
'M5': mt5.TIMEFRAME_M5,
|
||||
'M15': mt5.TIMEFRAME_M15,
|
||||
'M30': mt5.TIMEFRAME_M30,
|
||||
'H1': mt5.TIMEFRAME_H1,
|
||||
'H4': mt5.TIMEFRAME_H4,
|
||||
'D1': mt5.TIMEFRAME_D1
|
||||
}
|
||||
timeframe = timeframe_map[timeframe_str]
|
||||
|
||||
# Create output directory
|
||||
models_dir = 'models'
|
||||
os.makedirs(models_dir, exist_ok=True)
|
||||
|
||||
# Step 1: Train Model
|
||||
print("\n" + "="*60)
|
||||
print("STEP 1: Training ONNX Model")
|
||||
print("="*60)
|
||||
|
||||
trainer = ONNXModelTrainer(
|
||||
symbol=symbol,
|
||||
timeframe=timeframe,
|
||||
lookback=lookback
|
||||
)
|
||||
|
||||
try:
|
||||
print(f"\nTraining model for {symbol} on {timeframe_str} timeframe...")
|
||||
print(f"Lookback: {lookback} bars")
|
||||
print(f"Epochs: {epochs}")
|
||||
print("\nThis may take several minutes...\n")
|
||||
|
||||
trainer.train(epochs=epochs, batch_size=32, verbose=1)
|
||||
|
||||
# Export model
|
||||
model_name = f"{symbol}_{timeframe_str}_model.onnx"
|
||||
model_path = os.path.join(models_dir, model_name)
|
||||
|
||||
print(f"\nExporting model to ONNX format...")
|
||||
trainer.export_to_onnx(model_path)
|
||||
|
||||
print(f"\n✓ Model saved to: {model_path}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n✗ Training failed: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
trainer.cleanup()
|
||||
return
|
||||
|
||||
finally:
|
||||
trainer.cleanup()
|
||||
|
||||
# Step 2: Test Predictions
|
||||
print("\n" + "="*60)
|
||||
print("STEP 2: Testing Predictions")
|
||||
print("="*60)
|
||||
|
||||
predictor = ONNXPredictor(model_path)
|
||||
|
||||
try:
|
||||
# Get current price
|
||||
symbol_info = mt5.symbol_info(symbol)
|
||||
if symbol_info:
|
||||
current_price = symbol_info.bid
|
||||
print(f"\nCurrent {symbol} price: {current_price:.5f}")
|
||||
else:
|
||||
print(f"\nWarning: Could not get current price for {symbol}")
|
||||
current_price = 0
|
||||
|
||||
# Make predictions
|
||||
print(f"\nMaking predictions...")
|
||||
predictions = predictor.predict_batch(symbol, timeframe, n_predictions=3)
|
||||
|
||||
print("\nPredictions:")
|
||||
for i, pred in enumerate(predictions, 1):
|
||||
if current_price > 0:
|
||||
change = pred - current_price
|
||||
change_pct = (change / current_price) * 100
|
||||
print(f" {i}. {pred:.5f} (change: {change:+.5f}, {change_pct:+.2f}%)")
|
||||
else:
|
||||
print(f" {i}. {pred:.5f}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n✗ Prediction failed: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
predictor.cleanup()
|
||||
|
||||
# Step 3: Instructions for MT5
|
||||
print("\n" + "="*60)
|
||||
print("STEP 3: Using in MetaTrader 5")
|
||||
print("="*60)
|
||||
|
||||
print(f"\nTo use this model in MetaTrader 5:")
|
||||
print(f"\n1. Copy the model file to MT5's Files folder:")
|
||||
print(f" {model_path}")
|
||||
print(f" → <MT5 Data Folder>\\MQL5\\Files\\models\\{os.path.basename(model_path)}")
|
||||
print(f"\n2. Open ONNX_EA.mq5 in MetaEditor")
|
||||
print(f"\n3. Set EA parameters:")
|
||||
print(f" - Model Path: models\\{os.path.basename(model_path)}")
|
||||
print(f" - Lookback: {lookback}")
|
||||
print(f" - Your trading parameters")
|
||||
print(f"\n4. Compile and attach to chart")
|
||||
print(f"\n5. Monitor performance")
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("Workflow completed!")
|
||||
print("="*60 + "\n")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Check MT5 connection first
|
||||
if not mt5.initialize():
|
||||
print("ERROR: Failed to initialize MT5")
|
||||
print("Make sure MetaTrader 5 is running and you're logged in.")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
print("\n\nInterrupted by user")
|
||||
finally:
|
||||
mt5.shutdown()
|
||||
@@ -0,0 +1,261 @@
|
||||
"""
|
||||
ONNX Model Prediction Script
|
||||
|
||||
This script loads a trained ONNX model and makes predictions using MT5 data.
|
||||
Can be run from MetaEditor or directly in Python.
|
||||
|
||||
Usage:
|
||||
python predict_with_onnx.py --model models/XAUUSD_H1_model.onnx --symbol XAUUSD
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import MetaTrader5 as mt5
|
||||
import onnxruntime as ort
|
||||
from datetime import datetime
|
||||
from sklearn.preprocessing import MinMaxScaler
|
||||
import pickle
|
||||
import os
|
||||
|
||||
|
||||
class ONNXPredictor:
|
||||
"""
|
||||
Predictor class for using ONNX models with MT5 data.
|
||||
"""
|
||||
|
||||
def __init__(self, model_path: str, scaler_path: str = None):
|
||||
"""
|
||||
Initialize the predictor.
|
||||
|
||||
Args:
|
||||
model_path: Path to ONNX model file
|
||||
scaler_path: Path to saved scaler (optional, will create if not provided)
|
||||
"""
|
||||
self.model_path = model_path
|
||||
self.scaler_path = scaler_path
|
||||
|
||||
# Load ONNX model
|
||||
if not os.path.exists(model_path):
|
||||
raise FileNotFoundError(f"ONNX model not found: {model_path}")
|
||||
|
||||
self.session = ort.InferenceSession(model_path)
|
||||
|
||||
# Get input/output info
|
||||
self.input_name = self.session.get_inputs()[0].name
|
||||
self.output_name = self.session.get_outputs()[0].name
|
||||
self.input_shape = self.session.get_inputs()[0].shape
|
||||
|
||||
print(f"Loaded ONNX model: {model_path}")
|
||||
print(f"Input shape: {self.input_shape}")
|
||||
print(f"Input name: {self.input_name}")
|
||||
print(f"Output name: {self.output_name}")
|
||||
|
||||
# Load or create scaler
|
||||
if scaler_path and os.path.exists(scaler_path):
|
||||
with open(scaler_path, 'rb') as f:
|
||||
self.scaler = pickle.load(f)
|
||||
print(f"Loaded scaler from: {scaler_path}")
|
||||
else:
|
||||
self.scaler = MinMaxScaler()
|
||||
print("Using default scaler (will need to fit)")
|
||||
|
||||
# Initialize MT5
|
||||
if not mt5.initialize():
|
||||
raise RuntimeError(f"MT5 initialization failed: {mt5.last_error()}")
|
||||
|
||||
def prepare_features(self, df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
Prepare features from raw OHLCV data (same as training).
|
||||
|
||||
Args:
|
||||
df: Raw OHLCV data
|
||||
|
||||
Returns:
|
||||
DataFrame with features
|
||||
"""
|
||||
features = ['open', 'high', 'low', 'close', 'tick_volume']
|
||||
feature_df = df[features].copy()
|
||||
|
||||
# Add technical indicators
|
||||
feature_df['rsi'] = self._calculate_rsi(df['close'], period=14)
|
||||
feature_df['ema_20'] = df['close'].ewm(span=20).mean()
|
||||
feature_df['ema_50'] = df['close'].ewm(span=50).mean()
|
||||
feature_df['atr'] = self._calculate_atr(df, period=14)
|
||||
feature_df['price_change'] = df['close'].pct_change()
|
||||
feature_df['high_low_ratio'] = df['high'] / df['low']
|
||||
feature_df['volume_ma'] = df['tick_volume'].rolling(window=20).mean()
|
||||
feature_df['volume_ratio'] = df['tick_volume'] / feature_df['volume_ma']
|
||||
|
||||
feature_df = feature_df.dropna()
|
||||
return feature_df
|
||||
|
||||
def _calculate_rsi(self, prices: pd.Series, period: int = 14) -> pd.Series:
|
||||
"""Calculate RSI indicator."""
|
||||
delta = prices.diff()
|
||||
gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
|
||||
loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
|
||||
rs = gain / loss
|
||||
rsi = 100 - (100 / (1 + rs))
|
||||
return rsi
|
||||
|
||||
def _calculate_atr(self, df: pd.DataFrame, period: int = 14) -> pd.Series:
|
||||
"""Calculate ATR indicator."""
|
||||
high_low = df['high'] - df['low']
|
||||
high_close = np.abs(df['high'] - df['close'].shift())
|
||||
low_close = np.abs(df['low'] - df['close'].shift())
|
||||
tr = pd.concat([high_low, high_close, low_close], axis=1).max(axis=1)
|
||||
atr = tr.rolling(window=period).mean()
|
||||
return atr
|
||||
|
||||
def get_latest_data(self, symbol: str, timeframe: int, lookback: int) -> np.ndarray:
|
||||
"""
|
||||
Get latest data from MT5 and prepare for prediction.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol
|
||||
timeframe: MT5 timeframe
|
||||
lookback: Number of bars needed
|
||||
|
||||
Returns:
|
||||
Prepared feature array ready for model input
|
||||
"""
|
||||
# Fetch data
|
||||
rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, lookback + 50)
|
||||
|
||||
if rates is None or len(rates) < lookback:
|
||||
raise ValueError(f"Insufficient data for {symbol}")
|
||||
|
||||
df = pd.DataFrame(rates)
|
||||
df['time'] = pd.to_datetime(df['time'], unit='s')
|
||||
|
||||
# Prepare features
|
||||
feature_df = self.prepare_features(df)
|
||||
|
||||
# Get last lookback bars
|
||||
feature_data = feature_df.values[-lookback:]
|
||||
|
||||
# Scale features
|
||||
if hasattr(self.scaler, 'scale_'):
|
||||
feature_data_scaled = self.scaler.transform(feature_data)
|
||||
else:
|
||||
# Fit scaler if not already fitted
|
||||
print("Warning: Scaler not fitted, fitting on current data...")
|
||||
feature_data_scaled = self.scaler.fit_transform(feature_data)
|
||||
|
||||
# Reshape for model input: (1, lookback, features)
|
||||
feature_data_scaled = feature_data_scaled.reshape(1, lookback, -1)
|
||||
|
||||
return feature_data_scaled.astype(np.float32)
|
||||
|
||||
def predict(self, symbol: str, timeframe: int, lookback: int = None) -> float:
|
||||
"""
|
||||
Make a prediction for the next price.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol
|
||||
timeframe: MT5 timeframe
|
||||
lookback: Number of bars to use (default: from model input shape)
|
||||
|
||||
Returns:
|
||||
Predicted price
|
||||
"""
|
||||
if lookback is None:
|
||||
lookback = self.input_shape[1] if self.input_shape[1] else 60
|
||||
|
||||
# Get and prepare data
|
||||
input_data = self.get_latest_data(symbol, timeframe, lookback)
|
||||
|
||||
# Make prediction
|
||||
outputs = self.session.run([self.output_name], {self.input_name: input_data})
|
||||
prediction = outputs[0][0][0]
|
||||
|
||||
return float(prediction)
|
||||
|
||||
def predict_batch(self, symbol: str, timeframe: int, n_predictions: int = 5) -> list:
|
||||
"""
|
||||
Make multiple predictions.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol
|
||||
timeframe: MT5 timeframe
|
||||
n_predictions: Number of predictions to make
|
||||
|
||||
Returns:
|
||||
List of predictions
|
||||
"""
|
||||
predictions = []
|
||||
for _ in range(n_predictions):
|
||||
pred = self.predict(symbol, timeframe)
|
||||
predictions.append(pred)
|
||||
return predictions
|
||||
|
||||
def cleanup(self):
|
||||
"""Clean up MT5 connection."""
|
||||
mt5.shutdown()
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function."""
|
||||
parser = argparse.ArgumentParser(description='Make predictions using ONNX model')
|
||||
parser.add_argument('--model', type=str, required=True,
|
||||
help='Path to ONNX model file')
|
||||
parser.add_argument('--symbol', type=str, default='XAUUSD',
|
||||
help='Trading symbol')
|
||||
parser.add_argument('--timeframe', type=str, default='H1',
|
||||
choices=['M1', 'M5', 'M15', 'M30', 'H1', 'H4', 'D1'],
|
||||
help='Timeframe')
|
||||
parser.add_argument('--scaler', type=str, default=None,
|
||||
help='Path to saved scaler (optional)')
|
||||
parser.add_argument('--predictions', type=int, default=1,
|
||||
help='Number of predictions to make')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Convert timeframe
|
||||
timeframe_map = {
|
||||
'M1': mt5.TIMEFRAME_M1,
|
||||
'M5': mt5.TIMEFRAME_M5,
|
||||
'M15': mt5.TIMEFRAME_M15,
|
||||
'M30': mt5.TIMEFRAME_M30,
|
||||
'H1': mt5.TIMEFRAME_H1,
|
||||
'H4': mt5.TIMEFRAME_H4,
|
||||
'D1': mt5.TIMEFRAME_D1
|
||||
}
|
||||
timeframe = timeframe_map[args.timeframe]
|
||||
|
||||
# Create predictor
|
||||
predictor = ONNXPredictor(args.model, args.scaler)
|
||||
|
||||
try:
|
||||
# Get current price
|
||||
symbol_info = mt5.symbol_info(args.symbol)
|
||||
current_price = symbol_info.bid if symbol_info else 0
|
||||
|
||||
print(f"\nCurrent {args.symbol} price: {current_price:.5f}")
|
||||
print(f"Making {args.predictions} prediction(s)...\n")
|
||||
|
||||
# Make predictions
|
||||
if args.predictions == 1:
|
||||
prediction = predictor.predict(args.symbol, timeframe)
|
||||
print(f"Predicted next price: {prediction:.5f}")
|
||||
print(f"Expected change: {(prediction - current_price):.5f} "
|
||||
f"({((prediction - current_price) / current_price * 100):.2f}%)")
|
||||
else:
|
||||
predictions = predictor.predict_batch(args.symbol, timeframe, args.predictions)
|
||||
print("Predictions:")
|
||||
for i, pred in enumerate(predictions, 1):
|
||||
change = pred - current_price
|
||||
change_pct = (change / current_price * 100) if current_price > 0 else 0
|
||||
print(f" {i}. {pred:.5f} (change: {change:+.5f}, {change_pct:+.2f}%)")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\nError: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
predictor.cleanup()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,17 @@
|
||||
# ONNX and Machine Learning
|
||||
onnx>=1.12.0
|
||||
onnxruntime>=1.12.0
|
||||
tensorflow>=2.10.0
|
||||
tf2onnx>=1.13.0
|
||||
|
||||
# Data Processing
|
||||
pandas>=1.3.0
|
||||
numpy>=1.21.0
|
||||
scikit-learn>=1.0.0
|
||||
|
||||
# MetaTrader 5 Integration
|
||||
MetaTrader5>=5.0.45
|
||||
|
||||
# Visualization and Utilities
|
||||
matplotlib>=3.4.0
|
||||
tqdm>=4.64.0
|
||||
@@ -0,0 +1,364 @@
|
||||
"""
|
||||
ONNX Model Training Script for MetaTrader 5
|
||||
|
||||
This script trains a neural network model for price prediction and exports it to ONNX format.
|
||||
Based on MQL5 ONNX documentation: https://www.mql5.com/en/docs/onnx/onnx_prepare
|
||||
|
||||
Usage:
|
||||
python train_onnx_model.py --symbol XAUUSD --timeframe H1 --lookback 60 --epochs 50
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timedelta
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import MetaTrader5 as mt5
|
||||
import tensorflow as tf
|
||||
from tensorflow import keras
|
||||
from tensorflow.keras import layers
|
||||
from sklearn.preprocessing import MinMaxScaler
|
||||
from sklearn.model_selection import train_test_split
|
||||
import tf2onnx
|
||||
import onnx
|
||||
from tqdm import tqdm
|
||||
|
||||
|
||||
class ONNXModelTrainer:
|
||||
"""
|
||||
Trainer class for creating ONNX models from MT5 data.
|
||||
"""
|
||||
|
||||
def __init__(self, symbol: str, timeframe: int, lookback: int = 60,
|
||||
prediction_horizon: int = 1, features: list = None):
|
||||
"""
|
||||
Initialize the trainer.
|
||||
|
||||
Args:
|
||||
symbol: Trading symbol (e.g., 'XAUUSD', 'EURUSD')
|
||||
timeframe: MT5 timeframe constant
|
||||
lookback: Number of bars to look back for prediction
|
||||
prediction_horizon: Number of bars ahead to predict
|
||||
features: List of features to use (default: OHLC + volume)
|
||||
"""
|
||||
self.symbol = symbol
|
||||
self.timeframe = timeframe
|
||||
self.lookback = lookback
|
||||
self.prediction_horizon = prediction_horizon
|
||||
self.features = features or ['open', 'high', 'low', 'close', 'tick_volume']
|
||||
|
||||
self.scaler = MinMaxScaler()
|
||||
self.model = None
|
||||
|
||||
# Initialize MT5
|
||||
if not mt5.initialize():
|
||||
raise RuntimeError(f"MT5 initialization failed: {mt5.last_error()}")
|
||||
|
||||
def fetch_data(self, start_date: datetime, end_date: datetime) -> pd.DataFrame:
|
||||
"""
|
||||
Fetch historical data from MT5.
|
||||
|
||||
Args:
|
||||
start_date: Start date for data
|
||||
end_date: End date for data
|
||||
|
||||
Returns:
|
||||
DataFrame with OHLCV data
|
||||
"""
|
||||
print(f"Fetching data for {self.symbol} from {start_date} to {end_date}...")
|
||||
|
||||
rates = mt5.copy_rates_range(self.symbol, self.timeframe, start_date, end_date)
|
||||
|
||||
if rates is None or len(rates) == 0:
|
||||
raise ValueError(f"No data available for {self.symbol} in the specified date range")
|
||||
|
||||
df = pd.DataFrame(rates)
|
||||
df['time'] = pd.to_datetime(df['time'], unit='s')
|
||||
df.set_index('time', inplace=True)
|
||||
|
||||
print(f"Fetched {len(df)} bars")
|
||||
return df
|
||||
|
||||
def prepare_features(self, df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
Prepare features for training.
|
||||
|
||||
Args:
|
||||
df: Raw OHLCV data
|
||||
|
||||
Returns:
|
||||
DataFrame with features
|
||||
"""
|
||||
feature_df = df[self.features].copy()
|
||||
|
||||
# Add technical indicators as features
|
||||
feature_df['rsi'] = self._calculate_rsi(df['close'], period=14)
|
||||
feature_df['ema_20'] = df['close'].ewm(span=20).mean()
|
||||
feature_df['ema_50'] = df['close'].ewm(span=50).mean()
|
||||
feature_df['atr'] = self._calculate_atr(df, period=14)
|
||||
|
||||
# Price changes
|
||||
feature_df['price_change'] = df['close'].pct_change()
|
||||
feature_df['high_low_ratio'] = df['high'] / df['low']
|
||||
|
||||
# Volume features
|
||||
feature_df['volume_ma'] = df['tick_volume'].rolling(window=20).mean()
|
||||
feature_df['volume_ratio'] = df['tick_volume'] / feature_df['volume_ma']
|
||||
|
||||
# Drop NaN values
|
||||
feature_df = feature_df.dropna()
|
||||
|
||||
return feature_df
|
||||
|
||||
def _calculate_rsi(self, prices: pd.Series, period: int = 14) -> pd.Series:
|
||||
"""Calculate RSI indicator."""
|
||||
delta = prices.diff()
|
||||
gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
|
||||
loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
|
||||
rs = gain / loss
|
||||
rsi = 100 - (100 / (1 + rs))
|
||||
return rsi
|
||||
|
||||
def _calculate_atr(self, df: pd.DataFrame, period: int = 14) -> pd.Series:
|
||||
"""Calculate ATR indicator."""
|
||||
high_low = df['high'] - df['low']
|
||||
high_close = np.abs(df['high'] - df['close'].shift())
|
||||
low_close = np.abs(df['low'] - df['close'].shift())
|
||||
tr = pd.concat([high_low, high_close, low_close], axis=1).max(axis=1)
|
||||
atr = tr.rolling(window=period).mean()
|
||||
return atr
|
||||
|
||||
def create_sequences(self, data: np.ndarray, target: np.ndarray) -> tuple:
|
||||
"""
|
||||
Create sequences for LSTM/RNN training.
|
||||
|
||||
Args:
|
||||
data: Feature data
|
||||
target: Target values
|
||||
|
||||
Returns:
|
||||
Tuple of (X, y) sequences
|
||||
"""
|
||||
X, y = [], []
|
||||
|
||||
for i in range(self.lookback, len(data) - self.prediction_horizon + 1):
|
||||
X.append(data[i - self.lookback:i])
|
||||
y.append(target[i + self.prediction_horizon - 1])
|
||||
|
||||
return np.array(X), np.array(y)
|
||||
|
||||
def build_model(self, input_shape: tuple) -> keras.Model:
|
||||
"""
|
||||
Build the neural network model.
|
||||
|
||||
Args:
|
||||
input_shape: Shape of input data (lookback, features)
|
||||
|
||||
Returns:
|
||||
Compiled Keras model
|
||||
"""
|
||||
model = keras.Sequential([
|
||||
layers.LSTM(128, return_sequences=True, input_shape=input_shape),
|
||||
layers.Dropout(0.2),
|
||||
layers.LSTM(64, return_sequences=True),
|
||||
layers.Dropout(0.2),
|
||||
layers.LSTM(32),
|
||||
layers.Dropout(0.2),
|
||||
layers.Dense(16, activation='relu'),
|
||||
layers.Dense(1) # Predict next close price
|
||||
])
|
||||
|
||||
model.compile(
|
||||
optimizer=keras.optimizers.Adam(learning_rate=0.001),
|
||||
loss='mse',
|
||||
metrics=['mae']
|
||||
)
|
||||
|
||||
return model
|
||||
|
||||
def train(self, epochs: int = 50, batch_size: int = 32,
|
||||
validation_split: float = 0.2, verbose: int = 1):
|
||||
"""
|
||||
Train the model.
|
||||
|
||||
Args:
|
||||
epochs: Number of training epochs
|
||||
batch_size: Batch size for training
|
||||
validation_split: Fraction of data to use for validation
|
||||
verbose: Verbosity level
|
||||
"""
|
||||
# Fetch data (last 2 years)
|
||||
end_date = datetime.now()
|
||||
start_date = end_date - timedelta(days=730)
|
||||
|
||||
df = self.fetch_data(start_date, end_date)
|
||||
feature_df = self.prepare_features(df)
|
||||
|
||||
# Prepare data
|
||||
feature_data = feature_df.values
|
||||
target_data = df['close'].values[feature_df.index]
|
||||
|
||||
# Scale features
|
||||
feature_data_scaled = self.scaler.fit_transform(feature_data)
|
||||
|
||||
# Create sequences
|
||||
X, y = self.create_sequences(feature_data_scaled, target_data)
|
||||
|
||||
# Split data
|
||||
X_train, X_test, y_train, y_test = train_test_split(
|
||||
X, y, test_size=validation_split, shuffle=False
|
||||
)
|
||||
|
||||
print(f"\nTraining data shape: {X_train.shape}")
|
||||
print(f"Validation data shape: {X_test.shape}")
|
||||
|
||||
# Build model
|
||||
self.model = self.build_model((X_train.shape[1], X_train.shape[2]))
|
||||
|
||||
print("\nModel architecture:")
|
||||
self.model.summary()
|
||||
|
||||
# Train model
|
||||
print("\nTraining model...")
|
||||
history = self.model.fit(
|
||||
X_train, y_train,
|
||||
batch_size=batch_size,
|
||||
epochs=epochs,
|
||||
validation_data=(X_test, y_test),
|
||||
verbose=verbose,
|
||||
callbacks=[
|
||||
keras.callbacks.EarlyStopping(
|
||||
monitor='val_loss',
|
||||
patience=10,
|
||||
restore_best_weights=True
|
||||
),
|
||||
keras.callbacks.ReduceLROnPlateau(
|
||||
monitor='val_loss',
|
||||
factor=0.5,
|
||||
patience=5,
|
||||
min_lr=0.0001
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
# Evaluate
|
||||
train_loss = self.model.evaluate(X_train, y_train, verbose=0)
|
||||
test_loss = self.model.evaluate(X_test, y_test, verbose=0)
|
||||
|
||||
print(f"\nTraining Loss: {train_loss[0]:.4f}, MAE: {train_loss[1]:.4f}")
|
||||
print(f"Validation Loss: {test_loss[0]:.4f}, MAE: {test_loss[1]:.4f}")
|
||||
|
||||
return history
|
||||
|
||||
def export_to_onnx(self, output_path: str):
|
||||
"""
|
||||
Export the trained model to ONNX format.
|
||||
|
||||
Args:
|
||||
output_path: Path to save ONNX model
|
||||
"""
|
||||
if self.model is None:
|
||||
raise ValueError("Model must be trained before exporting")
|
||||
|
||||
print(f"\nExporting model to ONNX format: {output_path}")
|
||||
|
||||
# Get input shape
|
||||
input_shape = (1, self.lookback, len(self.features) + 7) # +7 for added features
|
||||
|
||||
# Create dummy input
|
||||
dummy_input = np.random.randn(*input_shape).astype(np.float32)
|
||||
|
||||
# Convert to ONNX
|
||||
spec = (tf.TensorSpec((None, self.lookback, len(self.features) + 7), tf.float32, name="input"),)
|
||||
output_path_onnx = tf2onnx.convert.from_keras(
|
||||
self.model,
|
||||
input_signature=spec,
|
||||
opset=13,
|
||||
output_path=output_path
|
||||
)
|
||||
|
||||
print(f"✓ ONNX model saved to: {output_path}")
|
||||
|
||||
# Verify ONNX model
|
||||
try:
|
||||
onnx_model = onnx.load(output_path)
|
||||
onnx.checker.check_model(onnx_model)
|
||||
print("✓ ONNX model validation passed")
|
||||
except Exception as e:
|
||||
print(f"⚠ ONNX model validation warning: {e}")
|
||||
|
||||
def cleanup(self):
|
||||
"""Clean up MT5 connection."""
|
||||
mt5.shutdown()
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function."""
|
||||
parser = argparse.ArgumentParser(description='Train ONNX model for MT5 price prediction')
|
||||
parser.add_argument('--symbol', type=str, default='XAUUSD', help='Trading symbol')
|
||||
parser.add_argument('--timeframe', type=str, default='H1',
|
||||
choices=['M1', 'M5', 'M15', 'M30', 'H1', 'H4', 'D1'],
|
||||
help='Timeframe')
|
||||
parser.add_argument('--lookback', type=int, default=60,
|
||||
help='Number of bars to look back')
|
||||
parser.add_argument('--epochs', type=int, default=50, help='Training epochs')
|
||||
parser.add_argument('--batch-size', type=int, default=32, help='Batch size')
|
||||
parser.add_argument('--output', type=str, default='models',
|
||||
help='Output directory for ONNX model')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Convert timeframe string to MT5 constant
|
||||
timeframe_map = {
|
||||
'M1': mt5.TIMEFRAME_M1,
|
||||
'M5': mt5.TIMEFRAME_M5,
|
||||
'M15': mt5.TIMEFRAME_M15,
|
||||
'M30': mt5.TIMEFRAME_M30,
|
||||
'H1': mt5.TIMEFRAME_H1,
|
||||
'H4': mt5.TIMEFRAME_H4,
|
||||
'D1': mt5.TIMEFRAME_D1
|
||||
}
|
||||
timeframe = timeframe_map[args.timeframe]
|
||||
|
||||
# Create output directory
|
||||
os.makedirs(args.output, exist_ok=True)
|
||||
|
||||
# Create trainer
|
||||
trainer = ONNXModelTrainer(
|
||||
symbol=args.symbol,
|
||||
timeframe=timeframe,
|
||||
lookback=args.lookback
|
||||
)
|
||||
|
||||
try:
|
||||
# Train model
|
||||
trainer.train(epochs=args.epochs, batch_size=args.batch_size)
|
||||
|
||||
# Export to ONNX
|
||||
model_name = f"{args.symbol}_{args.timeframe}_model.onnx"
|
||||
output_path = os.path.join(args.output, model_name)
|
||||
trainer.export_to_onnx(output_path)
|
||||
|
||||
# Save scaler for consistent normalization
|
||||
scaler_path = os.path.join(args.output, f"{args.symbol}_{args.timeframe}_scaler.pkl")
|
||||
import pickle
|
||||
with open(scaler_path, 'wb') as f:
|
||||
pickle.dump(trainer.scaler, f)
|
||||
print(f"✓ Scaler saved to: {scaler_path}")
|
||||
print(f" Use this with predict_with_onnx.py for consistent normalization")
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print("Training completed successfully!")
|
||||
print(f"ONNX model saved to: {output_path}")
|
||||
print(f"{'='*60}\n")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\nError: {e}")
|
||||
sys.exit(1)
|
||||
finally:
|
||||
trainer.cleanup()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user