858 lines
No EOL
28 KiB
MQL5
858 lines
No EOL
28 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| ATR QQE Trading System.mq5 |
|
|
//| Advanced QQE with Flexible Exit & Risk Management |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2025"
|
|
#property link ""
|
|
#property version "3.00"
|
|
#property strict
|
|
#property description "Advanced QQE system with QQE/ATR exits and equity-based risk management"
|
|
|
|
#include <Trade\Trade.mqh>
|
|
#include <Trade\PositionInfo.mqh>
|
|
#include <Trade\OrderInfo.mqh>
|
|
|
|
// Enums (must be declared before inputs)
|
|
enum ENUM_EXIT_METHOD
|
|
{
|
|
EXIT_ATR, // ATR-based exits only
|
|
EXIT_QQE, // QQE-based exits only
|
|
EXIT_HYBRID // Both QQE and ATR exits
|
|
};
|
|
|
|
// EA input parameters
|
|
input group "Risk Management Method"
|
|
input bool Use_Equity_Risk = false; // Use equity % risk instead of ATR
|
|
input double Risk_Percent = 2.0; // Risk % of equity per trade
|
|
input bool Use_Fixed_Lot = true; // Use fixed lot size when not using equity risk
|
|
|
|
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 "Exit Strategy Selection"
|
|
input ENUM_EXIT_METHOD Exit_Method = EXIT_ATR; // Exit method
|
|
input bool Use_QQE_Exit_Signals = false; // Enable QQE-based exits
|
|
input bool Use_ATR_Trailing = true; // Enable ATR trailing stops
|
|
input bool Use_Fixed_TP = false; // Use fixed take profit
|
|
|
|
input group "QQE Parameters (Proper QQE Implementation)"
|
|
input int QQE_RSI_Period = 14; // QQE RSI Period
|
|
input int QQE_Smoothing_Factor = 5; // QQE Smoothing Factor (SF)
|
|
input double QQE_Signal_Level = 50; // QQE Signal Level (center line)
|
|
input bool Use_QQE_Crossover = true; // Use QQE Main/Smoothed crossover signals
|
|
input bool Use_QQE_Level = false; // Use QQE level crossing signals
|
|
|
|
input group "QQE Exit Parameters"
|
|
input bool QQE_Exit_On_Opposite_Signal = true; // Close on opposite QQE signal
|
|
input bool QQE_Exit_On_Overbought = false; // Close longs at overbought (>70)
|
|
input bool QQE_Exit_On_Oversold = false; // Close shorts at oversold (<30)
|
|
input double QQE_Overbought_Level = 70; // Overbought level for exits
|
|
input double QQE_Oversold_Level = 30; // Oversold level for exits
|
|
|
|
input group "Trading Parameters"
|
|
input bool Manage_SL = true; // Manage Stop Loss
|
|
input bool Manage_TP = true; // Manage Take Profit
|
|
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 variables
|
|
int Wilders_Period;
|
|
int StartBar;
|
|
|
|
// QQE buffers
|
|
double TrLevelSlow[]; // QQE Smoothed line
|
|
double AtrRsi[]; // ATR of RSI
|
|
double MaAtrRsi[]; // MA of ATR RSI
|
|
double Rsi[]; // Raw RSI values
|
|
double RsiMa[]; // QQE Main line (MA of RSI)
|
|
double MaMaAtrRsi[]; // MA of MA of ATR RSI
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert initialization function |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit()
|
|
{
|
|
// Initialize trade operations
|
|
trade.SetExpertMagicNumber(123456);
|
|
|
|
// Initialize market information
|
|
digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
|
point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
|
|
// Initialize QQE calculation parameters
|
|
Wilders_Period = QQE_RSI_Period * 2 - 1;
|
|
StartBar = MathMax(QQE_Smoothing_Factor, Wilders_Period);
|
|
|
|
// 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 QQE arrays
|
|
ArraySetAsSeries(TrLevelSlow, true);
|
|
ArraySetAsSeries(AtrRsi, true);
|
|
ArraySetAsSeries(MaAtrRsi, true);
|
|
ArraySetAsSeries(Rsi, true);
|
|
ArraySetAsSeries(RsiMa, true);
|
|
ArraySetAsSeries(MaMaAtrRsi, true);
|
|
|
|
// Validate settings (cannot modify input parameters, only check them)
|
|
if(Use_Equity_Risk && Risk_Percent <= 0)
|
|
{
|
|
Print("Error: Risk percentage must be greater than 0. Please set Risk_Percent > 0 in inputs.");
|
|
return(INIT_PARAMETERS_INCORRECT);
|
|
}
|
|
|
|
// Validate other critical parameters
|
|
if(ATR_Period <= 0)
|
|
{
|
|
Print("Error: ATR_Period must be greater than 0");
|
|
return(INIT_PARAMETERS_INCORRECT);
|
|
}
|
|
if(QQE_RSI_Period <= 0)
|
|
{
|
|
Print("Error: QQE_RSI_Period must be greater than 0");
|
|
return(INIT_PARAMETERS_INCORRECT);
|
|
}
|
|
if(QQE_Smoothing_Factor <= 0)
|
|
{
|
|
Print("Error: QQE_Smoothing_Factor must be greater than 0");
|
|
return(INIT_PARAMETERS_INCORRECT);
|
|
}
|
|
|
|
// Set timer
|
|
if(!EventSetTimer(Check_Interval_Seconds))
|
|
{
|
|
Print("Error setting timer");
|
|
return(INIT_FAILED);
|
|
}
|
|
|
|
RefreshPositions();
|
|
|
|
Print("ATR QQE Advanced Trading System initialized successfully");
|
|
Print("Exit Method: ", EnumToString(Exit_Method));
|
|
Print("Risk Management: ", Use_Equity_Risk ? "Equity-based" : "ATR-based");
|
|
Print("ATR Period: ", ATR_Period, " QQE RSI Period: ", QQE_RSI_Period);
|
|
Print("StartBar requirement: ", StartBar, " bars");
|
|
|
|
// Initial update to check data availability
|
|
UpdateIndicators();
|
|
return(INIT_SUCCEEDED);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert deinitialization function |
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int reason)
|
|
{
|
|
IndicatorRelease(atrHandle);
|
|
IndicatorRelease(rsiHandle);
|
|
EventKillTimer();
|
|
Comment("");
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Timer function |
|
|
//+------------------------------------------------------------------+
|
|
void OnTimer()
|
|
{
|
|
if(!IsTimeToCheck())
|
|
return;
|
|
|
|
UpdateIndicators();
|
|
ManagePositions();
|
|
CheckForEntrySignals();
|
|
|
|
lastCheckTime = TimeCurrent();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert tick function |
|
|
//+------------------------------------------------------------------+
|
|
void OnTick()
|
|
{
|
|
if(Process_On_Bar_Close)
|
|
{
|
|
datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);
|
|
if(currentBarTime != lastBarTime)
|
|
{
|
|
lastBarTime = currentBarTime;
|
|
UpdateIndicators();
|
|
ManagePositions();
|
|
CheckForEntrySignals();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if(IsTimeToCheck())
|
|
{
|
|
UpdateIndicators();
|
|
ManagePositions();
|
|
CheckForEntrySignals();
|
|
lastCheckTime = TimeCurrent();
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if it's time to process |
|
|
//+------------------------------------------------------------------+
|
|
bool IsTimeToCheck()
|
|
{
|
|
return (TimeCurrent() - lastCheckTime >= Check_Interval_Seconds);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate lot size based on equity risk |
|
|
//+------------------------------------------------------------------+
|
|
double CalculateLotSize(double entryPrice, double stopLoss)
|
|
{
|
|
if(!Use_Equity_Risk || Use_Fixed_Lot)
|
|
{
|
|
return Lot_Size; // Use fixed lot size
|
|
}
|
|
|
|
double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
double riskAmount = accountBalance * Risk_Percent / 100.0;
|
|
|
|
double stopDistance = MathAbs(entryPrice - stopLoss);
|
|
if(stopDistance == 0) return Lot_Size;
|
|
|
|
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
|
|
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
|
|
|
|
if(tickValue == 0 || tickSize == 0) return Lot_Size;
|
|
|
|
double lotSize = riskAmount / (stopDistance / tickSize * tickValue);
|
|
|
|
// Apply lot size limits
|
|
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
|
|
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
|
|
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
|
|
|
|
lotSize = MathMax(lotSize, minLot);
|
|
lotSize = MathMin(lotSize, maxLot);
|
|
|
|
// Round to lot step
|
|
lotSize = MathFloor(lotSize / lotStep) * lotStep;
|
|
|
|
return lotSize;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Exponential Moving Average |
|
|
//+------------------------------------------------------------------+
|
|
void CalculateEMA(int begin, int period, const double &price[], double &result[])
|
|
{
|
|
if(period <= 0) return;
|
|
if(begin < 0) begin = 0;
|
|
|
|
int priceSize = ArraySize(price);
|
|
int resultSize = ArraySize(result);
|
|
|
|
// Ensure we don't go out of bounds
|
|
if(priceSize == 0 || resultSize == 0) return;
|
|
if(begin >= priceSize || begin >= resultSize) return;
|
|
|
|
double SmoothFactor = 2.0 / (1.0 + period);
|
|
|
|
for(int i = begin; i >= 0; i--)
|
|
{
|
|
// Check bounds before accessing arrays
|
|
if(i >= priceSize || i >= resultSize) continue;
|
|
if(i + 1 >= resultSize)
|
|
{
|
|
// Initialize first value
|
|
if(price[i] == EMPTY_VALUE)
|
|
result[i] = 0;
|
|
else
|
|
result[i] = price[i];
|
|
continue;
|
|
}
|
|
|
|
if(price[i] == EMPTY_VALUE)
|
|
result[i] = 0;
|
|
else
|
|
result[i] = price[i] * SmoothFactor + result[i + 1] * (1.0 - SmoothFactor);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate proper QQE values |
|
|
//+------------------------------------------------------------------+
|
|
bool CalculateQQE(int bars_needed)
|
|
{
|
|
int total_bars = bars_needed + StartBar + 10;
|
|
|
|
// Check if we have enough bars
|
|
int available_bars = iBars(_Symbol, PERIOD_CURRENT);
|
|
if(available_bars < total_bars)
|
|
{
|
|
total_bars = available_bars - 1;
|
|
if(total_bars < StartBar + 10)
|
|
{
|
|
Print("Not enough historical data. Need at least ", StartBar + 10, " bars, have ", available_bars);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Ensure minimum size
|
|
if(total_bars < 50)
|
|
{
|
|
Print("Insufficient bars for QQE calculation: ", total_bars);
|
|
return false;
|
|
}
|
|
|
|
// Resize arrays safely
|
|
if(!ArrayResize(Rsi, total_bars) ||
|
|
!ArrayResize(RsiMa, total_bars) ||
|
|
!ArrayResize(AtrRsi, total_bars) ||
|
|
!ArrayResize(MaAtrRsi, total_bars) ||
|
|
!ArrayResize(MaMaAtrRsi, total_bars) ||
|
|
!ArrayResize(TrLevelSlow, total_bars))
|
|
{
|
|
Print("Failed to resize QQE arrays");
|
|
return false;
|
|
}
|
|
|
|
// Get RSI values with error checking
|
|
if(CopyBuffer(rsiHandle, 0, 0, total_bars, Rsi) != total_bars)
|
|
{
|
|
Print("Error getting RSI values. Requested: ", total_bars, " Error: ", GetLastError());
|
|
return false;
|
|
}
|
|
|
|
// Initialize arrays
|
|
ArrayInitialize(RsiMa, 0.0);
|
|
ArrayInitialize(AtrRsi, 0.0);
|
|
ArrayInitialize(MaAtrRsi, 0.0);
|
|
ArrayInitialize(MaMaAtrRsi, 0.0);
|
|
ArrayInitialize(TrLevelSlow, 0.0);
|
|
|
|
// Calculate RSI MA with bounds checking
|
|
if(total_bars > QQE_Smoothing_Factor)
|
|
{
|
|
CalculateEMA(total_bars - 1, QQE_Smoothing_Factor, Rsi, RsiMa);
|
|
}
|
|
|
|
// Calculate ATR of RSI MA
|
|
for(int i = total_bars - 2; i >= 0; i--)
|
|
{
|
|
if(i + 1 < total_bars && i >= 0)
|
|
AtrRsi[i] = MathAbs(RsiMa[i + 1] - RsiMa[i]);
|
|
}
|
|
|
|
// Calculate smoothed values with bounds checking
|
|
if(total_bars > Wilders_Period)
|
|
{
|
|
CalculateEMA(total_bars - 2, Wilders_Period, AtrRsi, MaAtrRsi);
|
|
CalculateEMA(total_bars - 2, Wilders_Period, MaAtrRsi, MaMaAtrRsi);
|
|
}
|
|
|
|
// Calculate QQE Smoothed line
|
|
int start_index = total_bars - StartBar - 1;
|
|
if(start_index < 0) start_index = total_bars - 1;
|
|
|
|
double tr = 0;
|
|
double rsi1 = 0;
|
|
|
|
if(start_index >= 0 && start_index < total_bars)
|
|
{
|
|
tr = TrLevelSlow[start_index];
|
|
rsi1 = RsiMa[start_index];
|
|
}
|
|
|
|
for(int i = start_index - 1; i >= 0; i--)
|
|
{
|
|
if(i < 0 || i >= total_bars) continue;
|
|
|
|
double rsi0 = RsiMa[i];
|
|
double dar = MaMaAtrRsi[i] * 4.236;
|
|
double dv = tr;
|
|
|
|
if(rsi0 < tr)
|
|
{
|
|
tr = rsi0 + dar;
|
|
if((rsi1 < dv) && (tr > dv)) tr = dv;
|
|
}
|
|
else if(rsi0 > tr)
|
|
{
|
|
tr = rsi0 - dar;
|
|
if((rsi1 > dv) && (tr < dv)) tr = dv;
|
|
}
|
|
|
|
TrLevelSlow[i] = tr;
|
|
rsi1 = rsi0;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update indicators |
|
|
//+------------------------------------------------------------------+
|
|
void UpdateIndicators()
|
|
{
|
|
double atrBuffer[];
|
|
|
|
if(CopyBuffer(atrHandle, 0, ATR_Shift, 1, atrBuffer) > 0)
|
|
{
|
|
atrValue = NormalizeDouble(atrBuffer[0], digits);
|
|
if(atrValue <= 0)
|
|
{
|
|
Print("Warning: ATR calculation returned zero. Using fallback value.");
|
|
atrValue = 0.0001;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Print("Error getting ATR value: ", GetLastError());
|
|
atrValue = 0.0001; // Use fallback
|
|
return;
|
|
}
|
|
|
|
// Only calculate QQE if we have enough bars
|
|
if(iBars(_Symbol, PERIOD_CURRENT) > StartBar + 50)
|
|
{
|
|
if(!CalculateQQE(50)) // Reduced from 100 to 50
|
|
{
|
|
Print("Error calculating QQE values");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Print("Waiting for more historical data. Current bars: ", iBars(_Symbol, PERIOD_CURRENT), " Need: ", StartBar + 50);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check for QQE entry signals |
|
|
//+------------------------------------------------------------------+
|
|
void CheckForEntrySignals()
|
|
{
|
|
if(positionCount >= Max_Positions)
|
|
return;
|
|
|
|
// Check if we have enough data and valid indicators
|
|
if(ArraySize(RsiMa) < 3 || ArraySize(TrLevelSlow) < 3 || atrValue <= 0)
|
|
{
|
|
Print("Insufficient data for signal generation. RSI MA size: ", ArraySize(RsiMa), " ATR: ", atrValue);
|
|
return;
|
|
}
|
|
|
|
// Additional validation
|
|
if(RsiMa[1] == 0 || TrLevelSlow[1] == 0 || RsiMa[2] == 0 || TrLevelSlow[2] == 0)
|
|
{
|
|
Print("Invalid QQE values detected. Skipping signal check.");
|
|
return;
|
|
}
|
|
|
|
double currentRsiMa = RsiMa[1];
|
|
double prevRsiMa = RsiMa[2];
|
|
double currentSmoothed = TrLevelSlow[1];
|
|
double prevSmoothed = TrLevelSlow[2];
|
|
|
|
bool buySignal = false;
|
|
bool sellSignal = false;
|
|
|
|
// QQE Crossover signals
|
|
if(Use_QQE_Crossover)
|
|
{
|
|
if(prevRsiMa <= prevSmoothed && currentRsiMa > currentSmoothed)
|
|
{
|
|
buySignal = true;
|
|
Print("QQE Crossover Buy Signal");
|
|
}
|
|
|
|
if(prevRsiMa >= prevSmoothed && currentRsiMa < currentSmoothed)
|
|
{
|
|
sellSignal = true;
|
|
Print("QQE Crossover Sell Signal");
|
|
}
|
|
}
|
|
|
|
// QQE Level crossing signals
|
|
if(Use_QQE_Level && !buySignal && !sellSignal)
|
|
{
|
|
double prevLevel = RsiMa[2];
|
|
double currentLevel = RsiMa[1];
|
|
|
|
if(prevLevel < QQE_Signal_Level && currentLevel > QQE_Signal_Level)
|
|
{
|
|
buySignal = true;
|
|
Print("QQE Level Buy Signal");
|
|
}
|
|
|
|
if(prevLevel > QQE_Signal_Level && currentLevel < QQE_Signal_Level)
|
|
{
|
|
sellSignal = true;
|
|
Print("QQE Level Sell Signal");
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
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;
|
|
|
|
double lotSize = CalculateLotSize(currentPrice, stopLoss);
|
|
|
|
if(!trade.Buy(lotSize, _Symbol, 0, stopLoss, takeProfit, "QQE ATR Buy"))
|
|
{
|
|
Print("Buy order failed with error: ", GetLastError());
|
|
}
|
|
else
|
|
{
|
|
Print("QQE Buy executed - Lot Size: ", lotSize, " Entry: ", currentPrice);
|
|
if(Use_Equity_Risk)
|
|
Print("Risk: ", Risk_Percent, "% of equity = $", (AccountInfoDouble(ACCOUNT_BALANCE) * Risk_Percent / 100.0));
|
|
}
|
|
}
|
|
|
|
// Execute Sell Signal
|
|
if(sellSignal && Allow_Short_Positions)
|
|
{
|
|
currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
|
|
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;
|
|
|
|
double lotSize = CalculateLotSize(currentPrice, stopLoss);
|
|
|
|
if(!trade.Sell(lotSize, _Symbol, 0, stopLoss, takeProfit, "QQE ATR Sell"))
|
|
{
|
|
Print("Sell order failed with error: ", GetLastError());
|
|
}
|
|
else
|
|
{
|
|
Print("QQE Sell executed - Lot Size: ", lotSize, " Entry: ", currentPrice);
|
|
if(Use_Equity_Risk)
|
|
Print("Risk: ", Risk_Percent, "% of equity = $", (AccountInfoDouble(ACCOUNT_BALANCE) * Risk_Percent / 100.0));
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check for QQE exit signals |
|
|
//+------------------------------------------------------------------+
|
|
bool CheckQQEExitSignals(ENUM_POSITION_TYPE posType)
|
|
{
|
|
if(!Use_QQE_Exit_Signals)
|
|
return false;
|
|
|
|
if(ArraySize(RsiMa) < 3 || ArraySize(TrLevelSlow) < 3)
|
|
return false;
|
|
|
|
double currentRsiMa = RsiMa[1];
|
|
double prevRsiMa = RsiMa[2];
|
|
double currentSmoothed = TrLevelSlow[1];
|
|
double prevSmoothed = TrLevelSlow[2];
|
|
|
|
// Opposite crossover signals
|
|
if(QQE_Exit_On_Opposite_Signal)
|
|
{
|
|
if(posType == POSITION_TYPE_BUY)
|
|
{
|
|
// Exit long on bearish crossover
|
|
if(prevRsiMa >= prevSmoothed && currentRsiMa < currentSmoothed)
|
|
{
|
|
Print("QQE Exit Signal: Long position - bearish crossover detected");
|
|
return true;
|
|
}
|
|
}
|
|
else if(posType == POSITION_TYPE_SELL)
|
|
{
|
|
// Exit short on bullish crossover
|
|
if(prevRsiMa <= prevSmoothed && currentRsiMa > currentSmoothed)
|
|
{
|
|
Print("QQE Exit Signal: Short position - bullish crossover detected");
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Overbought/Oversold exits
|
|
if(QQE_Exit_On_Overbought && posType == POSITION_TYPE_BUY)
|
|
{
|
|
if(currentRsiMa > QQE_Overbought_Level)
|
|
{
|
|
Print("QQE Exit Signal: Long position - overbought level reached");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if(QQE_Exit_On_Oversold && posType == POSITION_TYPE_SELL)
|
|
{
|
|
if(currentRsiMa < QQE_Oversold_Level)
|
|
{
|
|
Print("QQE Exit Signal: Short position - oversold level reached");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Refresh list of position tickets |
|
|
//+------------------------------------------------------------------+
|
|
void RefreshPositions()
|
|
{
|
|
int total = PositionsTotal();
|
|
int count = 0;
|
|
|
|
for(int i = 0; i < total; i++)
|
|
{
|
|
if(posInfo.SelectByIndex(i))
|
|
{
|
|
if(posInfo.Symbol() == _Symbol && posInfo.Magic() == 123456)
|
|
count++;
|
|
}
|
|
}
|
|
|
|
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()
|
|
{
|
|
RefreshPositions();
|
|
|
|
double bufferInPoints = Extra_Buffer_Pips * 10 * point;
|
|
|
|
// Display current info
|
|
string info = "QQE ATR Advanced Trading System\n";
|
|
info += "Exit Method: " + EnumToString(Exit_Method) + "\n";
|
|
info += "Risk Method: " + (Use_Equity_Risk ? "Equity " + DoubleToString(Risk_Percent, 1) + "%" : "ATR-based") + "\n";
|
|
info += "Current ATR: " + DoubleToString(atrValue, digits) + "\n";
|
|
if(ArraySize(RsiMa) > 1 && ArraySize(TrLevelSlow) > 1)
|
|
{
|
|
info += "RSI MA: " + DoubleToString(RsiMa[1], 2) + "\n";
|
|
info += "QQE Smoothed: " + DoubleToString(TrLevelSlow[1], 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]))
|
|
{
|
|
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)
|
|
{
|
|
// Check for QQE-based exits first
|
|
if((Exit_Method == EXIT_QQE || Exit_Method == EXIT_HYBRID) && CheckQQEExitSignals(posType))
|
|
{
|
|
if(trade.PositionClose(ticket))
|
|
{
|
|
Print("Position #", ticket, " closed by QQE exit signal");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Only proceed with ATR management if using ATR exits
|
|
if(Exit_Method == EXIT_QQE)
|
|
return;
|
|
|
|
double newSL = 0, newTP = 0;
|
|
double trailingStartLevel = 0;
|
|
double currentPrice = 0;
|
|
bool modifyPosition = false;
|
|
|
|
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
|
|
|
if(posType == POSITION_TYPE_BUY)
|
|
{
|
|
currentPrice = bid;
|
|
|
|
if(Manage_SL)
|
|
{
|
|
newSL = NormalizeDouble(openPrice - (atrValue * SL_ATR_Multiplier) - bufferInPoints, digits);
|
|
}
|
|
else
|
|
{
|
|
newSL = currentSL;
|
|
}
|
|
|
|
if(Manage_TP && !Close_At_Profit_Target)
|
|
{
|
|
newTP = NormalizeDouble(openPrice + (atrValue * TP_ATR_Multiplier), digits);
|
|
}
|
|
else
|
|
{
|
|
newTP = currentTP;
|
|
}
|
|
|
|
if(Close_At_Profit_Target)
|
|
{
|
|
double profitTarget = openPrice + (atrValue * TP_ATR_Multiplier);
|
|
if(currentPrice >= profitTarget)
|
|
{
|
|
if(trade.PositionClose(ticket))
|
|
{
|
|
Print("Position #", ticket, " closed at ATR profit target");
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(Use_ATR_Trailing && 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;
|
|
|
|
if(Manage_SL)
|
|
{
|
|
newSL = NormalizeDouble(openPrice + (atrValue * SL_ATR_Multiplier) + bufferInPoints, digits);
|
|
}
|
|
else
|
|
{
|
|
newSL = currentSL;
|
|
}
|
|
|
|
if(Manage_TP && !Close_At_Profit_Target)
|
|
{
|
|
newTP = NormalizeDouble(openPrice - (atrValue * TP_ATR_Multiplier), digits);
|
|
}
|
|
else
|
|
{
|
|
newTP = currentTP;
|
|
}
|
|
|
|
if(Close_At_Profit_Target)
|
|
{
|
|
double profitTarget = openPrice - (atrValue * TP_ATR_Multiplier);
|
|
if(currentPrice <= profitTarget)
|
|
{
|
|
if(trade.PositionClose(ticket))
|
|
{
|
|
Print("Position #", ticket, " closed at ATR profit target");
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(Use_ATR_Trailing && 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 SL doesn't move 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;
|
|
}
|
|
|
|
// Apply modifications
|
|
if((MathAbs(currentSL - newSL) > Point() && Manage_SL) ||
|
|
(MathAbs(currentTP - newTP) > Point() && Manage_TP) ||
|
|
modifyPosition)
|
|
{
|
|
if(trade.PositionModify(ticket, newSL, newTP))
|
|
{
|
|
string action = modifyPosition ? "ATR Trailing Stop" : "ATR SL/TP Update";
|
|
Print(action, " applied to position #", ticket);
|
|
}
|
|
else
|
|
{
|
|
Print("Failed to modify position #", ticket, " Error: ", GetLastError());
|
|
}
|
|
}
|
|
} |