581 lines
19 KiB
MQL5
581 lines
19 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| ATR QQE Trading System.mq5 |
|
|
//| QQE-Based Entry with ATR Risk Management |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2025"
|
|
#property link ""
|
|
#property version "1.00"
|
|
#property strict
|
|
#property description "QQE-based trading system with dynamic ATR risk management"
|
|
|
|
#include <Trade\Trade.mqh>
|
|
#include <Trade\PositionInfo.mqh>
|
|
#include <Trade\OrderInfo.mqh>
|
|
|
|
// EA input parameters
|
|
input group "ATR Risk Parameters"
|
|
input int ATR_Period = 14; // ATR Period
|
|
input double SL_ATR_Multiplier = 3.0; // Stop Loss ATR Multiplier
|
|
input double TP_ATR_Multiplier = 3.5; // Take Profit ATR Multiplier
|
|
input double Trail_Start_Multiplier = 2.5; // Trailing Start ATR Multiplier
|
|
input double Trail_Step_Multiplier = 0.5; // Trailing Step ATR Multiplier
|
|
input int ATR_Shift = 1; // ATR shift (1 for previous bar)
|
|
input double Extra_Buffer_Pips = 5; // Extra buffer in pips
|
|
|
|
input group "QQE Parameters"
|
|
input int QQE_RSI_Period = 14; // QQE RSI Period
|
|
input int QQE_Smoothing_Factor = 5; // QQE Smoothing Factor
|
|
input double QQE_Fast_Period = 2.618; // QQE Fast Period
|
|
input double QQE_Slow_Period = 4.236; // QQE Slow Period
|
|
input double QQE_Signal_Level = 50; // QQE Signal Level (50 = center line)
|
|
input bool Use_QQE_Divergence = true; // Use QQE divergence signals
|
|
input bool Use_QQE_Crossover = true; // Use QQE crossover signals
|
|
|
|
input group "Trading Parameters"
|
|
input bool Manage_SL = true; // Manage Stop Loss
|
|
input bool Manage_TP = true; // Manage Take Profit
|
|
input bool Use_Trailing_Stop = true; // Use Trailing Stop
|
|
input bool Close_At_Profit_Target = false; // Close position at profit target instead of setting TP
|
|
input double Lot_Size = 0.1; // Fixed Lot Size
|
|
input bool Allow_Long_Positions = true; // Allow Long Positions
|
|
input bool Allow_Short_Positions = true; // Allow Short Positions
|
|
input int Max_Positions = 1; // Maximum concurrent positions
|
|
|
|
input group "Timing Parameters"
|
|
input int Check_Interval_Seconds = 5; // Interval between checks (seconds)
|
|
input bool Process_On_Bar_Close = true; // Process only on bar close
|
|
|
|
// Global variables
|
|
CTrade trade;
|
|
CPositionInfo posInfo;
|
|
COrderInfo orderInfo;
|
|
double atrValue;
|
|
double point;
|
|
int digits;
|
|
datetime lastCheckTime = 0;
|
|
datetime lastBarTime = 0;
|
|
int atrHandle;
|
|
int rsiHandle;
|
|
int positionCount = 0;
|
|
ulong positionTickets[];
|
|
|
|
// QQE calculation arrays
|
|
double qqeMain[];
|
|
double qqeSignal[];
|
|
double rsiValues[];
|
|
double qqePrevMain[];
|
|
double qqePrevSignal[];
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert initialization function |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit()
|
|
{
|
|
// Initialize trade operations
|
|
trade.SetExpertMagicNumber(123456); // Use a fixed magic number
|
|
|
|
// Initialize market information
|
|
digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
|
point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
|
|
// Create indicator handles
|
|
atrHandle = iATR(_Symbol, PERIOD_CURRENT, ATR_Period);
|
|
rsiHandle = iRSI(_Symbol, PERIOD_CURRENT, QQE_RSI_Period, PRICE_CLOSE);
|
|
|
|
if(atrHandle == INVALID_HANDLE || rsiHandle == INVALID_HANDLE)
|
|
{
|
|
Print("Error creating indicator handles");
|
|
return(INIT_FAILED);
|
|
}
|
|
|
|
// Initialize arrays
|
|
ArraySetAsSeries(qqeMain, true);
|
|
ArraySetAsSeries(qqeSignal, true);
|
|
ArraySetAsSeries(rsiValues, true);
|
|
ArraySetAsSeries(qqePrevMain, true);
|
|
ArraySetAsSeries(qqePrevSignal, true);
|
|
|
|
// Set timer
|
|
if(!EventSetTimer(Check_Interval_Seconds))
|
|
{
|
|
Print("Error setting timer");
|
|
return(INIT_FAILED);
|
|
}
|
|
|
|
// Initial identification of positions
|
|
RefreshPositions();
|
|
|
|
Print("ATR QQE Trading System initialized successfully");
|
|
return(INIT_SUCCEEDED);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert deinitialization function |
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int reason)
|
|
{
|
|
// Release indicator handles
|
|
IndicatorRelease(atrHandle);
|
|
IndicatorRelease(rsiHandle);
|
|
|
|
// Remove timer
|
|
EventKillTimer();
|
|
|
|
// Clean up
|
|
Comment("");
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Timer function |
|
|
//+------------------------------------------------------------------+
|
|
void OnTimer()
|
|
{
|
|
// Check if it's time to update
|
|
if(!IsTimeToCheck())
|
|
return;
|
|
|
|
// Update indicators and manage positions
|
|
UpdateIndicators();
|
|
ManagePositions();
|
|
CheckForEntrySignals();
|
|
|
|
// Record the check time
|
|
lastCheckTime = TimeCurrent();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert tick function |
|
|
//+------------------------------------------------------------------+
|
|
void OnTick()
|
|
{
|
|
// Check if we process only on bar close
|
|
if(Process_On_Bar_Close)
|
|
{
|
|
datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);
|
|
if(currentBarTime != lastBarTime)
|
|
{
|
|
lastBarTime = currentBarTime;
|
|
UpdateIndicators();
|
|
ManagePositions();
|
|
CheckForEntrySignals();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Otherwise, check if it's time to update based on timer
|
|
if(IsTimeToCheck())
|
|
{
|
|
UpdateIndicators();
|
|
ManagePositions();
|
|
CheckForEntrySignals();
|
|
lastCheckTime = TimeCurrent();
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if it's time to process |
|
|
//+------------------------------------------------------------------+
|
|
bool IsTimeToCheck()
|
|
{
|
|
if(TimeCurrent() - lastCheckTime >= Check_Interval_Seconds)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate QQE indicator |
|
|
//+------------------------------------------------------------------+
|
|
void CalculateQQE()
|
|
{
|
|
// Get RSI values
|
|
if(CopyBuffer(rsiHandle, 0, 0, 100, rsiValues) <= 0)
|
|
{
|
|
Print("Error getting RSI values");
|
|
return;
|
|
}
|
|
|
|
// Resize QQE arrays
|
|
ArrayResize(qqeMain, 100);
|
|
ArrayResize(qqeSignal, 100);
|
|
|
|
// Calculate QQE
|
|
for(int i = 99; i >= 0; i--)
|
|
{
|
|
if(i == 99) // First calculation
|
|
{
|
|
qqeMain[i] = rsiValues[i];
|
|
qqeSignal[i] = rsiValues[i];
|
|
}
|
|
else
|
|
{
|
|
// QQE Main calculation (smoothed RSI)
|
|
double smoothingFactor = 2.0 / (QQE_Smoothing_Factor + 1.0);
|
|
qqeMain[i] = qqeMain[i+1] + smoothingFactor * (rsiValues[i] - qqeMain[i+1]);
|
|
|
|
// QQE Signal calculation
|
|
double fastFactor = 2.0 / (QQE_Fast_Period + 1.0);
|
|
double slowFactor = 2.0 / (QQE_Slow_Period + 1.0);
|
|
|
|
// Calculate intermediate value
|
|
double intermediate = qqeSignal[i+1] + fastFactor * (qqeMain[i] - qqeSignal[i+1]);
|
|
|
|
// Apply slow smoothing
|
|
qqeSignal[i] = qqeSignal[i+1] + slowFactor * (intermediate - qqeSignal[i+1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update indicators |
|
|
//+------------------------------------------------------------------+
|
|
void UpdateIndicators()
|
|
{
|
|
double atrBuffer[];
|
|
|
|
// Get current ATR value
|
|
if(CopyBuffer(atrHandle, 0, ATR_Shift, 1, atrBuffer) > 0)
|
|
{
|
|
atrValue = NormalizeDouble(atrBuffer[0], digits);
|
|
|
|
if(atrValue <= 0)
|
|
{
|
|
Print("Warning: ATR calculation returned zero or negative value. Using fallback value.");
|
|
atrValue = 0.0001; // Fallback to prevent errors
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Print("Error getting ATR value: ", GetLastError());
|
|
}
|
|
|
|
// Calculate QQE values
|
|
CalculateQQE();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check for QQE entry signals |
|
|
//+------------------------------------------------------------------+
|
|
void CheckForEntrySignals()
|
|
{
|
|
// Check if we have reached maximum positions
|
|
if(positionCount >= Max_Positions)
|
|
return;
|
|
|
|
// Make sure we have enough QQE data
|
|
if(ArraySize(qqeMain) < 3 || ArraySize(qqeSignal) < 3)
|
|
return;
|
|
|
|
// Get current QQE values
|
|
double currentQQEMain = qqeMain[0];
|
|
double currentQQESignal = qqeSignal[0];
|
|
double prevQQEMain = qqeMain[1];
|
|
double prevQQESignal = qqeSignal[1];
|
|
|
|
bool buySignal = false;
|
|
bool sellSignal = false;
|
|
|
|
// QQE Crossover signals
|
|
if(Use_QQE_Crossover)
|
|
{
|
|
// Buy signal: QQE Main crosses above QQE Signal and both are below the signal level
|
|
if(prevQQEMain <= prevQQESignal && currentQQEMain > currentQQESignal &&
|
|
currentQQEMain < QQE_Signal_Level)
|
|
{
|
|
buySignal = true;
|
|
}
|
|
|
|
// Sell signal: QQE Main crosses below QQE Signal and both are above the signal level
|
|
if(prevQQEMain >= prevQQESignal && currentQQEMain < currentQQESignal &&
|
|
currentQQEMain > QQE_Signal_Level)
|
|
{
|
|
sellSignal = true;
|
|
}
|
|
}
|
|
|
|
// QQE Divergence signals (simplified version)
|
|
if(Use_QQE_Divergence && !buySignal && !sellSignal)
|
|
{
|
|
// Buy signal: QQE bounces off oversold level
|
|
if(prevQQEMain < 30 && currentQQEMain > 30 && currentQQEMain > prevQQEMain)
|
|
{
|
|
buySignal = true;
|
|
}
|
|
|
|
// Sell signal: QQE bounces off overbought level
|
|
if(prevQQEMain > 70 && currentQQEMain < 70 && currentQQEMain < prevQQEMain)
|
|
{
|
|
sellSignal = true;
|
|
}
|
|
}
|
|
|
|
double currentPrice;
|
|
double stopLoss, takeProfit;
|
|
double bufferInPoints = Extra_Buffer_Pips * 10 * point;
|
|
|
|
// Execute Buy Signal
|
|
if(buySignal && Allow_Long_Positions)
|
|
{
|
|
currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
|
|
|
// Calculate SL and TP
|
|
stopLoss = Manage_SL ? NormalizeDouble(currentPrice - (atrValue * SL_ATR_Multiplier) - bufferInPoints, digits) : 0;
|
|
takeProfit = Manage_TP && !Close_At_Profit_Target ? NormalizeDouble(currentPrice + (atrValue * TP_ATR_Multiplier), digits) : 0;
|
|
|
|
// Execute the buy order
|
|
if(!trade.Buy(Lot_Size, _Symbol, 0, stopLoss, takeProfit, "QQE ATR Strategy Buy"))
|
|
{
|
|
Print("Buy order failed with error: ", GetLastError());
|
|
}
|
|
else
|
|
{
|
|
Print("QQE Buy signal executed at ", currentPrice, " QQE Main: ", currentQQEMain, " QQE Signal: ", currentQQESignal);
|
|
Print("SL: ", stopLoss, " TP: ", takeProfit);
|
|
}
|
|
}
|
|
|
|
// Execute Sell Signal
|
|
if(sellSignal && Allow_Short_Positions)
|
|
{
|
|
currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
|
|
// Calculate SL and TP
|
|
stopLoss = Manage_SL ? NormalizeDouble(currentPrice + (atrValue * SL_ATR_Multiplier) + bufferInPoints, digits) : 0;
|
|
takeProfit = Manage_TP && !Close_At_Profit_Target ? NormalizeDouble(currentPrice - (atrValue * TP_ATR_Multiplier), digits) : 0;
|
|
|
|
// Execute the sell order
|
|
if(!trade.Sell(Lot_Size, _Symbol, 0, stopLoss, takeProfit, "QQE ATR Strategy Sell"))
|
|
{
|
|
Print("Sell order failed with error: ", GetLastError());
|
|
}
|
|
else
|
|
{
|
|
Print("QQE Sell signal executed at ", currentPrice, " QQE Main: ", currentQQEMain, " QQE Signal: ", currentQQESignal);
|
|
Print("SL: ", stopLoss, " TP: ", takeProfit);
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Refresh list of position tickets |
|
|
//+------------------------------------------------------------------+
|
|
void RefreshPositions()
|
|
{
|
|
int total = PositionsTotal();
|
|
int count = 0;
|
|
|
|
// First, count valid positions
|
|
for(int i = 0; i < total; i++)
|
|
{
|
|
if(posInfo.SelectByIndex(i))
|
|
{
|
|
if(posInfo.Symbol() == _Symbol && posInfo.Magic() == 123456)
|
|
count++;
|
|
}
|
|
}
|
|
|
|
// Resize array and fill with position tickets
|
|
ArrayResize(positionTickets, count);
|
|
positionCount = 0;
|
|
|
|
for(int i = 0; i < total; i++)
|
|
{
|
|
if(posInfo.SelectByIndex(i))
|
|
{
|
|
if(posInfo.Symbol() == _Symbol && posInfo.Magic() == 123456)
|
|
{
|
|
positionTickets[positionCount] = posInfo.Ticket();
|
|
positionCount++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Manage all positions |
|
|
//+------------------------------------------------------------------+
|
|
void ManagePositions()
|
|
{
|
|
// Get current open positions
|
|
RefreshPositions();
|
|
|
|
// Convert buffer from pips to points
|
|
double bufferInPoints = Extra_Buffer_Pips * 10 * point;
|
|
|
|
// Display current info
|
|
string info = "QQE ATR Trading System\n";
|
|
info += "Current ATR: " + DoubleToString(atrValue, digits) + "\n";
|
|
if(ArraySize(qqeMain) > 0 && ArraySize(qqeSignal) > 0)
|
|
{
|
|
info += "QQE Main: " + DoubleToString(qqeMain[0], 2) + "\n";
|
|
info += "QQE Signal: " + DoubleToString(qqeSignal[0], 2) + "\n";
|
|
}
|
|
info += "Positions: " + IntegerToString(positionCount) + "/" + IntegerToString(Max_Positions);
|
|
Comment(info);
|
|
|
|
// Process existing positions
|
|
for(int i = 0; i < positionCount; i++)
|
|
{
|
|
if(posInfo.SelectByTicket(positionTickets[i]))
|
|
{
|
|
// Process the position
|
|
ProcessPosition(posInfo.PositionType(), posInfo.PriceOpen(),
|
|
posInfo.StopLoss(), posInfo.TakeProfit(),
|
|
posInfo.Ticket(), bufferInPoints);
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Process a single position |
|
|
//+------------------------------------------------------------------+
|
|
void ProcessPosition(ENUM_POSITION_TYPE posType, double openPrice,
|
|
double currentSL, double currentTP, ulong ticket,
|
|
double bufferInPoints)
|
|
{
|
|
double newSL = 0, newTP = 0;
|
|
double trailingStartLevel = 0;
|
|
double currentPrice = 0;
|
|
bool modifyPosition = false;
|
|
|
|
// Get current bid/ask
|
|
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
|
|
|
// Set SL and TP based on position type
|
|
if(posType == POSITION_TYPE_BUY)
|
|
{
|
|
currentPrice = bid;
|
|
|
|
// Calculate new stop loss if needed
|
|
if(Manage_SL)
|
|
{
|
|
newSL = NormalizeDouble(openPrice - (atrValue * SL_ATR_Multiplier) - bufferInPoints, digits);
|
|
}
|
|
else
|
|
{
|
|
newSL = currentSL;
|
|
}
|
|
|
|
// Calculate new take profit if needed
|
|
if(Manage_TP && !Close_At_Profit_Target)
|
|
{
|
|
newTP = NormalizeDouble(openPrice + (atrValue * TP_ATR_Multiplier), digits);
|
|
}
|
|
else
|
|
{
|
|
newTP = currentTP;
|
|
}
|
|
|
|
// Check for profit target if closing instead of TP
|
|
if(Close_At_Profit_Target)
|
|
{
|
|
double profitTarget = openPrice + (atrValue * TP_ATR_Multiplier);
|
|
if(currentPrice >= profitTarget)
|
|
{
|
|
if(trade.PositionClose(ticket))
|
|
{
|
|
Print("Position #", ticket, " closed at profit target!");
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check for trailing stop if enabled
|
|
if(Use_Trailing_Stop && Manage_SL)
|
|
{
|
|
trailingStartLevel = NormalizeDouble(openPrice + (atrValue * Trail_Start_Multiplier), digits);
|
|
|
|
if(currentPrice >= trailingStartLevel)
|
|
{
|
|
double trailingSL = NormalizeDouble(currentPrice - (atrValue * Trail_Step_Multiplier) - bufferInPoints, digits);
|
|
|
|
if(trailingSL > currentSL || currentSL == 0)
|
|
{
|
|
newSL = trailingSL;
|
|
modifyPosition = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if(posType == POSITION_TYPE_SELL)
|
|
{
|
|
currentPrice = ask;
|
|
|
|
// Calculate new stop loss if needed
|
|
if(Manage_SL)
|
|
{
|
|
newSL = NormalizeDouble(openPrice + (atrValue * SL_ATR_Multiplier) + bufferInPoints, digits);
|
|
}
|
|
else
|
|
{
|
|
newSL = currentSL;
|
|
}
|
|
|
|
// Calculate new take profit if needed
|
|
if(Manage_TP && !Close_At_Profit_Target)
|
|
{
|
|
newTP = NormalizeDouble(openPrice - (atrValue * TP_ATR_Multiplier), digits);
|
|
}
|
|
else
|
|
{
|
|
newTP = currentTP;
|
|
}
|
|
|
|
// Check for profit target if closing instead of TP
|
|
if(Close_At_Profit_Target)
|
|
{
|
|
double profitTarget = openPrice - (atrValue * TP_ATR_Multiplier);
|
|
if(currentPrice <= profitTarget)
|
|
{
|
|
if(trade.PositionClose(ticket))
|
|
{
|
|
Print("Position #", ticket, " closed at profit target!");
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check for trailing stop if enabled
|
|
if(Use_Trailing_Stop && Manage_SL)
|
|
{
|
|
trailingStartLevel = NormalizeDouble(openPrice - (atrValue * Trail_Start_Multiplier), digits);
|
|
|
|
if(currentPrice <= trailingStartLevel)
|
|
{
|
|
double trailingSL = NormalizeDouble(currentPrice + (atrValue * Trail_Step_Multiplier) + bufferInPoints, digits);
|
|
|
|
if(trailingSL < currentSL || currentSL == 0)
|
|
{
|
|
newSL = trailingSL;
|
|
modifyPosition = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure we're not moving SL in wrong direction
|
|
if(posType == POSITION_TYPE_BUY && currentSL > 0 && newSL < currentSL)
|
|
{
|
|
newSL = currentSL;
|
|
}
|
|
else if(posType == POSITION_TYPE_SELL && currentSL > 0 && newSL > currentSL)
|
|
{
|
|
newSL = currentSL;
|
|
}
|
|
|
|
// Check if SL or TP needs to be changed
|
|
if((MathAbs(currentSL - newSL) > Point() && Manage_SL) ||
|
|
(MathAbs(currentTP - newTP) > Point() && Manage_TP) ||
|
|
modifyPosition)
|
|
{
|
|
if(trade.PositionModify(ticket, newSL, newTP))
|
|
{
|
|
string action = modifyPosition ? "Trailing Stop" : "SL/TP Update";
|
|
Print(action, " applied to position #", ticket, " New SL: ", newSL, " New TP: ", newTP);
|
|
}
|
|
else
|
|
{
|
|
Print("Failed to modify position #", ticket, " Error: ", GetLastError());
|
|
}
|
|
}
|
|
}
|