642 lines
24 KiB
Plaintext
642 lines
24 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| 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>
|
|
|
|
//--- Resource: Embed ONNX model in EA
|
|
// Based on: https://www.mql5.com/en/docs/onnx/onnx_test
|
|
// Path is relative to MQL5 directory (not starting with \\Files\\)
|
|
#resource "Files\\XAUUSD_H1_model.onnx" as uchar ExtModel[]
|
|
|
|
//--- Input parameters
|
|
input group "ONNX Model Settings"
|
|
input string InpModelPath = ""; // ONNX Model Path (leave empty to use embedded resource)
|
|
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 bool InpUsePredictedSLTP = true; // Use Predicted SL/TP (based on prediction & volatility)
|
|
input int InpStopLoss = 50; // Stop Loss (pips) - used if InpUsePredictedSLTP=false
|
|
input int InpTakeProfit = 100; // Take Profit (pips) - used if InpUsePredictedSLTP=false
|
|
input double InpSLMultiplier = 1.5; // SL Multiplier (ATR-based, e.g., 1.5 = 1.5x ATR)
|
|
input double InpTPMultiplier = 2.0; // TP Multiplier (ATR-based, e.g., 2.0 = 2x ATR)
|
|
input double InpMinSLATR = 0.5; // Minimum SL (ATR multiplier)
|
|
input double InpMinTPATR = 1.0; // Minimum TP (ATR multiplier)
|
|
|
|
input group "Prediction Settings"
|
|
input double InpPredictionThreshold = 0.00005; // Min Prediction Change (0.005% as decimal, e.g., 0.00005 = 0.005%)
|
|
input bool InpUseConfidence = true; // Use Confidence Filter
|
|
input double InpMinConfidence = 0.1; // Minimum Confidence (0.1 = 10%)
|
|
|
|
//--- 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
|
|
// Based on: https://www.mql5.com/en/docs/onnx/onnx_test
|
|
Print("Loading ONNX model from embedded resource...");
|
|
|
|
// Create model from resource buffer
|
|
onnx_handle = OnnxCreateFromBuffer(ExtModel, ONNX_DEBUG_LOGS);
|
|
|
|
if(onnx_handle == INVALID_HANDLE)
|
|
{
|
|
int error = GetLastError();
|
|
Print("ERROR: Failed to create ONNX model from resource. Error: ", error);
|
|
Print("Make sure the model file exists at: MQL5\\Files\\XAUUSD_H1_model.onnx");
|
|
Print("Then recompile the EA to embed it as a resource.");
|
|
return(INIT_FAILED);
|
|
}
|
|
|
|
// Set input shape - per MQL5 documentation
|
|
const long ExtInputShape[] = {1, InpLookback, 13}; // batch=1, lookback bars, 13 features
|
|
if(!OnnxSetInputShape(onnx_handle, 0, ExtInputShape))
|
|
{
|
|
Print("OnnxSetInputShape failed, error ", GetLastError());
|
|
OnnxRelease(onnx_handle);
|
|
return(INIT_FAILED);
|
|
}
|
|
|
|
// Set output shape - per MQL5 documentation
|
|
const long ExtOutputShape[] = {1, 1}; // batch=1, single output value
|
|
if(!OnnxSetOutputShape(onnx_handle, 0, ExtOutputShape))
|
|
{
|
|
Print("OnnxSetOutputShape failed, error ", GetLastError());
|
|
OnnxRelease(onnx_handle);
|
|
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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// Check if input data is valid
|
|
if(ArraySize(input_data) != InpLookback * 13)
|
|
{
|
|
Print("ERROR: Input data size mismatch. Expected: ", InpLookback * 13, ", Got: ", ArraySize(input_data));
|
|
return;
|
|
}
|
|
|
|
// Convert flat array to matrixf for OnnxRun
|
|
// Shape: [lookback, features] = [60, 13] - batch dimension is added automatically
|
|
matrixf input_matrix;
|
|
input_matrix.Resize(InpLookback, 13);
|
|
|
|
// Fill matrix from flat array
|
|
int idx = 0;
|
|
for(int i = 0; i < InpLookback; i++)
|
|
{
|
|
for(int j = 0; j < 13; j++)
|
|
{
|
|
if(idx >= ArraySize(input_data))
|
|
{
|
|
Print("ERROR: Index out of bounds when filling matrix. idx=", idx, ", array size=", ArraySize(input_data));
|
|
return;
|
|
}
|
|
input_matrix[i][j] = input_data[idx++];
|
|
}
|
|
}
|
|
|
|
// Verify matrix is not empty
|
|
if(input_matrix.Rows() == 0 || input_matrix.Cols() == 0)
|
|
{
|
|
Print("ERROR: Input matrix is empty. Rows: ", input_matrix.Rows(), ", Cols: ", input_matrix.Cols());
|
|
return;
|
|
}
|
|
|
|
Print("Input matrix prepared: Rows=", input_matrix.Rows(), ", Cols=", input_matrix.Cols());
|
|
|
|
// Run ONNX model - use matrixf and vectorf per MQL5 documentation
|
|
vectorf output_vector(1);
|
|
if(!RunONNXModel(input_matrix, output_vector))
|
|
{
|
|
Print("ERROR: Failed to run ONNX model");
|
|
return;
|
|
}
|
|
|
|
// Get prediction
|
|
if(output_vector.Size() == 0)
|
|
{
|
|
Print("ERROR: Empty output from ONNX model");
|
|
return;
|
|
}
|
|
|
|
// Model now predicts price change percentage directly (e.g., -0.003 = -0.3%)
|
|
double predicted_change_pct = output_vector[0];
|
|
double current_price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
|
|
// Check if prediction is percentage (between -1 and 1) or absolute price (old format)
|
|
double price_change_pct;
|
|
double predicted_price;
|
|
|
|
if(MathAbs(predicted_change_pct) < 1.0)
|
|
{
|
|
// New format: percentage (e.g., -0.003 = -0.3%)
|
|
price_change_pct = predicted_change_pct * 100.0; // Convert to percentage
|
|
predicted_price = current_price * (1.0 + predicted_change_pct); // Calculate predicted price
|
|
}
|
|
else
|
|
{
|
|
// Old format: absolute price
|
|
predicted_price = predicted_change_pct;
|
|
double price_change = predicted_price - current_price;
|
|
price_change_pct = (price_change / current_price) * 100.0;
|
|
}
|
|
|
|
// Calculate confidence (for percentage predictions: 0.001 = 0.1% = 10% confidence)
|
|
double confidence;
|
|
if(MathAbs(price_change_pct) < 1.0)
|
|
{
|
|
// It's a decimal percentage (e.g., 0.001 = 0.1%)
|
|
confidence = MathMin(MathAbs(predicted_change_pct) / 0.01, 1.0); // 0.01 = 1% = 100% confidence
|
|
}
|
|
else
|
|
{
|
|
// It's already in percentage form
|
|
confidence = MathMin(MathAbs(price_change_pct) / 1.0, 1.0);
|
|
}
|
|
|
|
last_prediction = predicted_price;
|
|
last_confidence = confidence;
|
|
|
|
// Calculate ATR for dynamic SL/TP
|
|
double atr_value = 0.0;
|
|
double atr_array[];
|
|
ArraySetAsSeries(atr_array, true);
|
|
int atr_handle = iATR(_Symbol, PERIOD_CURRENT, 14);
|
|
if(atr_handle != INVALID_HANDLE)
|
|
{
|
|
if(CopyBuffer(atr_handle, 0, 0, 1, atr_array) > 0)
|
|
{
|
|
atr_value = atr_array[0];
|
|
}
|
|
IndicatorRelease(atr_handle);
|
|
}
|
|
|
|
// Calculate predicted SL/TP based on prediction, confidence, and volatility
|
|
double predicted_sl = 0.0;
|
|
double predicted_tp = 0.0;
|
|
if(InpUsePredictedSLTP && atr_value > 0)
|
|
{
|
|
// Calculate SL/TP based on ATR, prediction, and confidence
|
|
double predicted_move = MathAbs(predicted_price - current_price);
|
|
|
|
// SL: Based on ATR and confidence
|
|
// Higher confidence = tighter SL, lower confidence = wider SL
|
|
double sl_atr_mult = InpSLMultiplier / MathMax(confidence, 0.1);
|
|
sl_atr_mult = MathMax(sl_atr_mult, InpMinSLATR);
|
|
predicted_sl = atr_value * sl_atr_mult;
|
|
|
|
// TP: Use a fraction of predicted move (not the full move)
|
|
// Take 30-50% of predicted move as TP, but ensure minimum
|
|
double tp_fraction = 0.3 + (confidence * 0.2); // 30-50% based on confidence
|
|
double tp_from_prediction = predicted_move * tp_fraction;
|
|
|
|
// Also calculate TP from ATR multiplier
|
|
double tp_from_atr = atr_value * InpTPMultiplier;
|
|
|
|
// Use the smaller of the two (more conservative)
|
|
predicted_tp = MathMin(tp_from_prediction, tp_from_atr);
|
|
predicted_tp = MathMax(predicted_tp, atr_value * InpMinTPATR); // Minimum TP
|
|
|
|
// Ensure TP is at least 1.5x SL for risk/reward
|
|
if(predicted_tp < predicted_sl * 1.5)
|
|
{
|
|
predicted_tp = predicted_sl * 1.5;
|
|
}
|
|
|
|
// Cap TP at maximum 80% of predicted move (don't be too greedy)
|
|
double max_tp = predicted_move * 0.8;
|
|
if(predicted_tp > max_tp)
|
|
{
|
|
predicted_tp = max_tp;
|
|
}
|
|
}
|
|
|
|
// Log prediction
|
|
Print("Prediction: Current=", current_price,
|
|
" Predicted Change=", price_change_pct, "%",
|
|
" Predicted Price=", predicted_price,
|
|
" Confidence=", confidence,
|
|
" ATR=", atr_value);
|
|
if(InpUsePredictedSLTP && predicted_sl > 0 && predicted_tp > 0)
|
|
{
|
|
Print(" Predicted SL=", predicted_sl, " (", predicted_sl/current_price*100, "%)",
|
|
" Predicted TP=", predicted_tp, " (", predicted_tp/current_price*100, "%)");
|
|
}
|
|
|
|
// Check if we should trade
|
|
if(!InpUseConfidence || confidence >= InpMinConfidence)
|
|
{
|
|
// Check if prediction is significant
|
|
// price_change_pct is in percentage (e.g., 5.72 = 5.72%)
|
|
// InpPredictionThreshold is in decimal (e.g., 0.00005 = 0.005%)
|
|
// Convert threshold to percentage for comparison
|
|
double threshold_pct = InpPredictionThreshold * 100.0;
|
|
double abs_change_pct = MathAbs(price_change_pct); // Already in percentage
|
|
|
|
Print("Trade Check: Change=", price_change_pct, "% Threshold=", threshold_pct, "% Confidence=", confidence);
|
|
|
|
if(abs_change_pct >= threshold_pct)
|
|
{
|
|
// 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 > threshold_pct)
|
|
{
|
|
Print(">>> Opening BUY position: Change=", price_change_pct, "% Threshold=", threshold_pct, "%");
|
|
OpenBuyPosition(predicted_sl, predicted_tp);
|
|
}
|
|
else if(price_change_pct < -threshold_pct)
|
|
{
|
|
Print(">>> Opening SELL position: Change=", price_change_pct, "% Threshold=", threshold_pct, "%");
|
|
OpenSellPosition(predicted_sl, predicted_tp);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Print("Prediction below threshold: Change=", price_change_pct, "% < Threshold=", threshold_pct, "%");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Print("Confidence too low: ", confidence, " < ", InpMinConfidence);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 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 = 13; // OHLC(4) + volume(1) + RSI(1) + EMA20(1) + EMA50(1) + ATR(1) + price_change(1) + high_low_ratio(1) + volume_ma(1) + volume_ratio(1) = 13
|
|
|
|
ArrayResize(input_array, lookback * features);
|
|
ArrayInitialize(input_array, 0.0);
|
|
|
|
// Get historical data
|
|
double open[], high[], low[], close[];
|
|
long volume[]; // CopyTickVolume requires long[] not double[]
|
|
ArraySetAsSeries(open, true);
|
|
ArraySetAsSeries(high, true);
|
|
ArraySetAsSeries(low, true);
|
|
ArraySetAsSeries(close, true);
|
|
ArraySetAsSeries(volume, true);
|
|
|
|
int copied_open = CopyOpen(_Symbol, PERIOD_CURRENT, 0, lookback + 50, open);
|
|
if(copied_open < lookback)
|
|
{
|
|
Print("ERROR: CopyOpen failed. Got ", copied_open, " bars, need ", lookback);
|
|
return false;
|
|
}
|
|
int copied_high = CopyHigh(_Symbol, PERIOD_CURRENT, 0, lookback + 50, high);
|
|
if(copied_high < lookback)
|
|
{
|
|
Print("ERROR: CopyHigh failed. Got ", copied_high, " bars, need ", lookback);
|
|
return false;
|
|
}
|
|
int copied_low = CopyLow(_Symbol, PERIOD_CURRENT, 0, lookback + 50, low);
|
|
if(copied_low < lookback)
|
|
{
|
|
Print("ERROR: CopyLow failed. Got ", copied_low, " bars, need ", lookback);
|
|
return false;
|
|
}
|
|
int copied_close = CopyClose(_Symbol, PERIOD_CURRENT, 0, lookback + 50, close);
|
|
if(copied_close < lookback)
|
|
{
|
|
Print("ERROR: CopyClose failed. Got ", copied_close, " bars, need ", lookback);
|
|
return false;
|
|
}
|
|
int copied_volume = CopyTickVolume(_Symbol, PERIOD_CURRENT, 0, lookback + 50, volume);
|
|
if(copied_volume < lookback)
|
|
{
|
|
Print("ERROR: CopyTickVolume failed. Got ", copied_volume, " bars, need ", 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);
|
|
|
|
// Calculate volume MA for normalization
|
|
double volume_ma[];
|
|
ArraySetAsSeries(volume_ma, true);
|
|
ArrayResize(volume_ma, lookback);
|
|
ArrayInitialize(volume_ma, 0.0);
|
|
|
|
// Calculate volume MA (20-period rolling average)
|
|
for(int j = 0; j < lookback; j++)
|
|
{
|
|
double sum = 0.0;
|
|
int count = 0;
|
|
for(int k = j; k < j + 20 && k < ArraySize(volume); k++)
|
|
{
|
|
sum += (double)volume[k]; // Convert long to double
|
|
count++;
|
|
}
|
|
volume_ma[j] = count > 0 ? sum / count : (double)volume[j]; // Convert long to double
|
|
}
|
|
|
|
// Prepare features - MUST match Python training exactly (13 features)
|
|
// IMPORTANT: This uses simplified normalization. For best results, implement MinMaxScaler from training.
|
|
// The scaler is saved as models/XAUUSD_H1_scaler.pkl - you may need to export scaler parameters to MQL5
|
|
int idx = 0;
|
|
for(int i = 0; i < lookback; i++)
|
|
{
|
|
// Feature 1-4: OHLC (raw values, will be normalized by scaler)
|
|
input_array[idx++] = (float)open[i];
|
|
input_array[idx++] = (float)high[i];
|
|
input_array[idx++] = (float)low[i];
|
|
input_array[idx++] = (float)close[i];
|
|
|
|
// Feature 5: Volume (normalized by 1,000,000)
|
|
input_array[idx++] = (float)((double)volume[i] / 1000000.0); // Convert long to double first
|
|
|
|
// Feature 6: RSI (normalized by 100)
|
|
input_array[idx++] = (float)(rsi[i] / 100.0);
|
|
|
|
// Feature 7: EMA20 normalized difference
|
|
input_array[idx++] = (float)((ema20[i] - close[i]) / close[i]);
|
|
|
|
// Feature 8: EMA50 normalized difference
|
|
input_array[idx++] = (float)((ema50[i] - close[i]) / close[i]);
|
|
|
|
// Feature 9: ATR normalized
|
|
input_array[idx++] = (float)(atr[i] / close[i]);
|
|
|
|
// Feature 10: Price change (percentage)
|
|
double price_change = i > 0 ? (close[i] - close[i+1]) / close[i+1] : 0.0;
|
|
input_array[idx++] = (float)price_change;
|
|
|
|
// Feature 11: High/Low ratio
|
|
input_array[idx++] = (float)(high[i] / low[i]);
|
|
|
|
// Feature 12: Volume MA (normalized by 1,000,000)
|
|
input_array[idx++] = (float)(volume_ma[i] / 1000000.0);
|
|
|
|
// Feature 13: Volume ratio
|
|
double vol_ratio = volume_ma[i] > 0 ? (double)volume[i] / volume_ma[i] : 1.0; // Convert long to double
|
|
input_array[idx++] = (float)vol_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(matrixf &input_matrix, vectorf &output_vector)
|
|
{
|
|
if(onnx_handle == INVALID_HANDLE)
|
|
return false;
|
|
|
|
// Run model - shapes are already set in OnInit per MQL5 documentation
|
|
// Based on: https://www.mql5.com/en/docs/onnx/onnx_test
|
|
// OnnxRun expects matrixf and vectorf, not flat arrays
|
|
if(!OnnxRun(onnx_handle, ONNX_DEBUG_LOGS | ONNX_NO_CONVERSION, input_matrix, output_vector))
|
|
{
|
|
Print("ERROR: Failed to run ONNX model. Error: ", GetLastError());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Open buy position |
|
|
//+------------------------------------------------------------------+
|
|
void OpenBuyPosition(double predicted_sl = 0.0, double predicted_tp = 0.0)
|
|
{
|
|
double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
|
double sl = 0.0;
|
|
double tp = 0.0;
|
|
|
|
if(InpUsePredictedSLTP && predicted_sl > 0 && predicted_tp > 0)
|
|
{
|
|
// Use predicted SL/TP
|
|
sl = price - predicted_sl;
|
|
tp = price + predicted_tp;
|
|
Print("Using predicted SL/TP: SL=", sl, " TP=", tp);
|
|
}
|
|
else
|
|
{
|
|
// Use fixed SL/TP from input parameters
|
|
sl = InpStopLoss > 0 ? price - InpStopLoss * _Point * 10 : 0;
|
|
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(), " Price: ", price, " SL: ", sl, " TP: ", tp);
|
|
}
|
|
else
|
|
{
|
|
Print("Failed to open buy order. Error: ", trade.ResultRetcodeDescription());
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Open sell position |
|
|
//+------------------------------------------------------------------+
|
|
void OpenSellPosition(double predicted_sl = 0.0, double predicted_tp = 0.0)
|
|
{
|
|
double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
double sl = 0.0;
|
|
double tp = 0.0;
|
|
|
|
if(InpUsePredictedSLTP && predicted_sl > 0 && predicted_tp > 0)
|
|
{
|
|
// Use predicted SL/TP
|
|
sl = price + predicted_sl;
|
|
tp = price - predicted_tp;
|
|
Print("Using predicted SL/TP: SL=", sl, " TP=", tp);
|
|
}
|
|
else
|
|
{
|
|
// Use fixed SL/TP from input parameters
|
|
sl = InpStopLoss > 0 ? price + InpStopLoss * _Point * 10 : 0;
|
|
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(), " Price: ", price, " SL: ", sl, " TP: ", tp);
|
|
}
|
|
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");
|
|
}
|
|
}
|
|
}
|