Files
2026-02-13 08:03:25 +01:00

532 lines
19 KiB
Plaintext

//+------------------------------------------------------------------+
//| RSIDivergence_EA.mq5 |
//| RSI Divergence ONNX EA for MT5 |
//| |
//+------------------------------------------------------------------+
#property copyright "RSI Divergence ONNX EA"
#property link ""
#property version "1.00"
#property description "Expert Advisor using ONNX model to identify genuine RSI divergences"
#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\\BTCUSD_H1_rsi_divergence_model.onnx"; // ONNX Model Path
input string InpScalerPath = "models\\BTCUSD_H1_rsi_divergence_scaler.pkl"; // Scaler Path (not used in MQL5, for reference)
input string InpFeaturesPath = "models\\BTCUSD_H1_rsi_divergence_features.pkl"; // Features Path (not used in MQL5, for reference)
input int InpLookback = 60; // Lookback Period (bars)
input double InpMinConfidence = 0.7; // Minimum Confidence (0-1)
input group "=== Trading Settings ==="
input double InpLotSize = 0.01; // Lot Size
input int InpMagicNumber = 88001; // Magic Number
input int InpSlippage = 3; // Slippage (points)
input int InpStopLoss = 100; // Stop Loss (pips, 0 = disabled)
input int InpTakeProfit = 200; // Take Profit (pips, 0 = disabled)
input int InpMaxBarsInTrade = 20; // Max Bars in Trade (0 = disabled)
input group "=== Divergence Filter ==="
input bool InpUseRegularBullish = true; // Trade Regular Bullish Divergence
input bool InpUseRegularBearish = true; // Trade Regular Bearish Divergence
input bool InpUseHiddenBullish = true; // Trade Hidden Bullish Divergence
input bool InpUseHiddenBearish = true; // Trade Hidden Bearish Divergence
input group "=== Risk Management ==="
input bool InpUseTrailingStop = false; // Use Trailing Stop
input int InpTrailingStopPips = 50; // Trailing Stop (pips)
input int InpTrailingStepPips = 10; // Trailing Step (pips)
//--- Global variables
CTrade trade;
long onnx_handle = INVALID_HANDLE;
datetime last_bar_time = 0;
// Divergence type constants (must match Python model)
#define DIV_NONE 0
#define DIV_REGULAR_BULLISH 1
#define DIV_REGULAR_BEARISH 2
#define DIV_HIDDEN_BULLISH 3
#define DIV_HIDDEN_BEARISH 4
// Feature calculation buffers
double rsi_buffer[];
double ema20_buffer[];
double ema50_buffer[];
double atr_buffer[];
double sma20_buffer[];
double sma50_buffer[];
//+------------------------------------------------------------------+
//| 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);
Print("Model file should be in: ", TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL5\\Files\\models\\");
return(INIT_FAILED);
}
// Get model info
long input_count = OnnxGetInputCount(onnx_handle);
long 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);
}
// Initialize indicator buffers
ArraySetAsSeries(rsi_buffer, true);
ArraySetAsSeries(ema20_buffer, true);
ArraySetAsSeries(ema50_buffer, true);
ArraySetAsSeries(atr_buffer, true);
ArraySetAsSeries(sma20_buffer, true);
ArraySetAsSeries(sma50_buffer, true);
Print("RSI Divergence EA initialized successfully");
Print(" Symbol: ", _Symbol);
Print(" Timeframe: ", EnumToString(PERIOD_CURRENT));
Print(" Lookback: ", InpLookback);
Print(" Min Confidence: ", InpMinConfidence);
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)
{
// Check trailing stop on current bar
if(InpUseTrailingStop)
{
ApplyTrailingStop();
}
return; // Still the same bar
}
last_bar_time = current_bar_time;
// Close positions that have been open too long
if(InpMaxBarsInTrade > 0)
{
CloseOldPositions();
}
// 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) < 5)
{
Print("ERROR: Invalid output from ONNX model");
return;
}
// Get predicted class and confidence
int predicted_class = 0;
double max_prob = 0.0;
for(int i = 0; i < 5; i++)
{
if(output_data[i] > max_prob)
{
max_prob = output_data[i];
predicted_class = i;
}
}
double confidence = max_prob;
// Check if confidence meets threshold
if(confidence < InpMinConfidence)
{
return; // Not confident enough
}
// Check if we should trade this divergence type
bool should_trade = false;
int signal_type = 0; // 1 = BUY, -1 = SELL
if(predicted_class == DIV_REGULAR_BULLISH && InpUseRegularBullish)
{
should_trade = true;
signal_type = 1; // BUY
}
else if(predicted_class == DIV_REGULAR_BEARISH && InpUseRegularBearish)
{
should_trade = true;
signal_type = -1; // SELL
}
else if(predicted_class == DIV_HIDDEN_BULLISH && InpUseHiddenBullish)
{
should_trade = true;
signal_type = 1; // BUY
}
else if(predicted_class == DIV_HIDDEN_BEARISH && InpUseHiddenBearish)
{
should_trade = true;
signal_type = -1; // SELL
}
if(!should_trade)
{
return; // Divergence type not enabled
}
// Check if we already have a position
if(PositionSelect(_Symbol))
{
return; // Already in a position
}
// Execute trade
double price = (signal_type == 1) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID);
double sl = 0, tp = 0;
// Calculate stop loss and take profit
if(InpStopLoss > 0)
{
sl = (signal_type == 1) ? price - InpStopLoss * _Point * 10 : price + InpStopLoss * _Point * 10;
}
if(InpTakeProfit > 0)
{
tp = (signal_type == 1) ? price + InpTakeProfit * _Point * 10 : price - InpTakeProfit * _Point * 10;
}
// Open position
string divergence_name = "";
if(predicted_class == DIV_REGULAR_BULLISH) divergence_name = "Regular Bullish";
else if(predicted_class == DIV_REGULAR_BEARISH) divergence_name = "Regular Bearish";
else if(predicted_class == DIV_HIDDEN_BULLISH) divergence_name = "Hidden Bullish";
else if(predicted_class == DIV_HIDDEN_BEARISH) divergence_name = "Hidden Bearish";
if(signal_type == 1)
{
if(trade.Buy(InpLotSize, _Symbol, price, sl, tp, divergence_name + " Divergence (Conf: " + DoubleToString(confidence, 2) + ")"))
{
Print("BUY order opened: ", divergence_name, " Divergence, Confidence: ", confidence);
}
else
{
Print("ERROR: Failed to open BUY order: ", trade.ResultRetcodeDescription());
}
}
else
{
if(trade.Sell(InpLotSize, _Symbol, price, sl, tp, divergence_name + " Divergence (Conf: " + DoubleToString(confidence, 2) + ")"))
{
Print("SELL order opened: ", divergence_name, " Divergence, Confidence: ", confidence);
}
else
{
Print("ERROR: Failed to open SELL order: ", trade.ResultRetcodeDescription());
}
}
}
//+------------------------------------------------------------------+
//| Prepare input data for ONNX model |
//+------------------------------------------------------------------+
bool PrepareInputData(float &input_data[])
{
// We need to prepare features in the same order as training
// This should match the feature_cols from the Python training script
int lookback = InpLookback;
int num_features = 20; // Adjust based on your actual feature count
// Resize input array: (1, lookback, num_features)
ArrayResize(input_data, lookback * num_features);
ArrayInitialize(input_data, 0.0);
// Get price data
double close[], open[], high[], low[], volume[];
ArraySetAsSeries(close, true);
ArraySetAsSeries(open, true);
ArraySetAsSeries(high, true);
ArraySetAsSeries(low, true);
ArraySetAsSeries(volume, true);
CopyClose(_Symbol, PERIOD_CURRENT, 0, lookback, close);
CopyOpen(_Symbol, PERIOD_CURRENT, 0, lookback, open);
CopyHigh(_Symbol, PERIOD_CURRENT, 0, lookback, high);
CopyLow(_Symbol, PERIOD_CURRENT, 0, lookback, low);
CopyTickVolume(_Symbol, PERIOD_CURRENT, 0, lookback, volume);
// Calculate technical indicators
int rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE);
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);
int sma20_handle = iMA(_Symbol, PERIOD_CURRENT, 20, 0, MODE_SMA, PRICE_CLOSE);
int sma50_handle = iMA(_Symbol, PERIOD_CURRENT, 50, 0, MODE_SMA, PRICE_CLOSE);
int atr_handle = iATR(_Symbol, PERIOD_CURRENT, 14);
ArraySetAsSeries(rsi_buffer, true);
ArraySetAsSeries(ema20_buffer, true);
ArraySetAsSeries(ema50_buffer, true);
ArraySetAsSeries(sma20_buffer, true);
ArraySetAsSeries(sma50_buffer, true);
ArraySetAsSeries(atr_buffer, true);
if(CopyBuffer(rsi_handle, 0, 0, lookback, rsi_buffer) <= 0) return false;
if(CopyBuffer(ema20_handle, 0, 0, lookback, ema20_buffer) <= 0) return false;
if(CopyBuffer(ema50_handle, 0, 0, lookback, ema50_buffer) <= 0) return false;
if(CopyBuffer(sma20_handle, 0, 0, lookback, sma20_buffer) <= 0) return false;
if(CopyBuffer(sma50_handle, 0, 0, lookback, sma50_buffer) <= 0) return false;
if(CopyBuffer(atr_handle, 0, 0, lookback, atr_buffer) <= 0) return false;
// Release indicator handles
IndicatorRelease(rsi_handle);
IndicatorRelease(ema20_handle);
IndicatorRelease(ema50_handle);
IndicatorRelease(sma20_handle);
IndicatorRelease(sma50_handle);
IndicatorRelease(atr_handle);
// Prepare features (must match Python feature order)
// Note: Features need to be normalized - this is a simplified version
// In production, you should use the same scaler from Python
for(int i = 0; i < lookback; i++)
{
int idx = i * num_features;
int bar_idx = lookback - 1 - i; // Reverse for time series
// Basic OHLCV (normalized)
input_data[idx + 0] = (float)(close[bar_idx] / close[0] - 1.0); // Normalized close
input_data[idx + 1] = (float)(open[bar_idx] / close[0] - 1.0); // Normalized open
input_data[idx + 2] = (float)(high[bar_idx] / close[0] - 1.0); // Normalized high
input_data[idx + 3] = (float)(low[bar_idx] / close[0] - 1.0); // Normalized low
input_data[idx + 4] = (float)(volume[bar_idx] / 1000000.0); // Normalized volume
// Returns
if(bar_idx < lookback - 1)
{
input_data[idx + 5] = (float)((close[bar_idx] - close[bar_idx + 1]) / close[bar_idx + 1]);
}
// Ratios
input_data[idx + 6] = (float)(high[bar_idx] / (low[bar_idx] + 1e-10));
input_data[idx + 7] = (float)(close[bar_idx] / (open[bar_idx] + 1e-10));
// Moving averages (normalized)
input_data[idx + 8] = (float)(sma20_buffer[bar_idx] / close[0] - 1.0);
input_data[idx + 9] = (float)(sma50_buffer[bar_idx] / close[0] - 1.0);
input_data[idx + 10] = (float)(ema20_buffer[bar_idx] / close[0] - 1.0);
input_data[idx + 11] = (float)(ema50_buffer[bar_idx] / close[0] - 1.0);
// ATR
input_data[idx + 12] = (float)(atr_buffer[bar_idx] / close[0]);
// RSI (normalized to 0-1)
input_data[idx + 13] = (float)(rsi_buffer[bar_idx] / 100.0);
// Volume features
double volume_ma = 0;
for(int j = 0; j < 20 && (bar_idx + j) < lookback; j++)
{
volume_ma += volume[bar_idx + j];
}
volume_ma /= 20.0;
input_data[idx + 14] = (float)(volume[bar_idx] / (volume_ma + 1e-10));
// Price position (simplified)
double min_low = low[bar_idx];
double max_high = high[bar_idx];
for(int j = 0; j < 20 && (bar_idx + j) < lookback; j++)
{
if(low[bar_idx + j] < min_low) min_low = low[bar_idx + j];
if(high[bar_idx + j] > max_high) max_high = high[bar_idx + j];
}
input_data[idx + 15] = (float)((close[bar_idx] - min_low) / (max_high - min_low + 1e-10));
// Additional features (pad with zeros if needed)
for(int j = 16; j < num_features; j++)
{
input_data[idx + j] = 0.0;
}
}
return true;
}
//+------------------------------------------------------------------+
//| Run ONNX model |
//+------------------------------------------------------------------+
bool RunONNXModel(float &input_data[], float &output_data[])
{
if(onnx_handle == INVALID_HANDLE)
{
return false;
}
// Get input/output names
string input_name = OnnxGetInputName(onnx_handle, 0);
string output_name = OnnxGetOutputName(onnx_handle, 0);
// Prepare input shape: (1, lookback, num_features)
long input_shape[] = {1, InpLookback, 20}; // Adjust num_features as needed
long output_shape[] = {1, 5}; // 5 classes
// Run model
if(!OnnxRun(onnx_handle, ONNX_NO_CONVERSION, input_data, input_shape, 3,
output_data, output_shape))
{
Print("ERROR: OnnxRun failed. Error: ", GetLastError());
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| Apply trailing stop |
//+------------------------------------------------------------------+
void ApplyTrailingStop()
{
if(!PositionSelect(_Symbol))
{
return;
}
if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber)
{
return;
}
double position_open_price = PositionGetDouble(POSITION_PRICE_OPEN);
double position_sl = PositionGetDouble(POSITION_SL);
double position_tp = PositionGetDouble(POSITION_TP);
long position_type = PositionGetInteger(POSITION_TYPE);
double current_price = (position_type == POSITION_TYPE_BUY) ?
SymbolInfoDouble(_Symbol, SYMBOL_BID) :
SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double trailing_distance = InpTrailingStopPips * _Point * 10;
double trailing_step = InpTrailingStepPips * _Point * 10;
if(position_type == POSITION_TYPE_BUY)
{
double new_sl = current_price - trailing_distance;
if(new_sl > position_open_price &&
(position_sl == 0 || new_sl > position_sl + trailing_step))
{
trade.PositionModify(_Symbol, new_sl, position_tp);
}
}
else if(position_type == POSITION_TYPE_SELL)
{
double new_sl = current_price + trailing_distance;
if(new_sl < position_open_price &&
(position_sl == 0 || new_sl < position_sl - trailing_step))
{
trade.PositionModify(_Symbol, new_sl, position_tp);
}
}
}
//+------------------------------------------------------------------+
//| Close positions that have been open too long |
//+------------------------------------------------------------------+
void CloseOldPositions()
{
if(!PositionSelect(_Symbol))
{
return;
}
if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber)
{
return;
}
datetime position_open_time = (datetime)PositionGetInteger(POSITION_TIME);
datetime current_time = TimeCurrent();
int bars_open = Bars(_Symbol, PERIOD_CURRENT, position_open_time, current_time);
if(bars_open >= InpMaxBarsInTrade)
{
trade.PositionClose(_Symbol);
Print("Position closed: Max bars in trade reached (", bars_open, " bars)");
}
}
//+------------------------------------------------------------------+