532 lines
19 KiB
Plaintext
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)");
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|