feat: add ERMT PME 1.6 with live input-driven phase and basket protection Body create versioned 1.6 EA and module set wire phase triggers and safety floors to live EA inputs preserve runner sizing with original-position partial math add equity-aware daily loss and drawdown protection add FX basket de-risking for overlapping pairs remove hidden profile overrides from manual trail and partial settings keep 1.5 baseline intact while validating 1.6 behavior
298 lines
No EOL
10 KiB
MQL5
298 lines
No EOL
10 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| QQE_ATR_EA.mq5 |
|
|
//| Generated by your AI Coding Partner |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Your AI Coding Partner"
|
|
#property link ""
|
|
#property version "1.03"
|
|
|
|
#include <Trade\Trade.mqh>
|
|
|
|
//--- Custom Enumerations for Dropdown Menus
|
|
enum ENUM_SIGNAL_MODE
|
|
{
|
|
MODE_CROSSOVER = 0, // Crossover Only
|
|
MODE_THRESHOLD = 1, // Threshold (50 Level) Only
|
|
MODE_BOTH = 2 // Both Crossover and Threshold
|
|
};
|
|
|
|
//--- EA Inputs
|
|
input string General_Settings = "--- Trading Settings ---";
|
|
input double LotSize = 0.1; // Trading Lot Size
|
|
input ENUM_SIGNAL_MODE SignalMode = MODE_BOTH; // Entry Signal Logic
|
|
input ulong MagicNumber = 123456; // Magic Number
|
|
|
|
input string Risk_Settings = "--- Dynamic Risk (ATR) ---";
|
|
input int AtrPeriod = 14; // ATR Period
|
|
input double AtrSlMultiplier = 1.5; // ATR Stop Loss Multiplier
|
|
input double AtrTpMultiplier = 2.0; // ATR Take Profit Multiplier
|
|
|
|
input string Indicator_Settings = "--- QQE Settings ---";
|
|
input int SF = 5; // Smoothing Factor
|
|
input int RSI_Period = 14; // RSI Period
|
|
|
|
//--- Global Variables & Objects
|
|
CTrade trade;
|
|
int myRSI;
|
|
int myATR;
|
|
int Wilders_Period;
|
|
int StartBar;
|
|
int prev_calculated = 0;
|
|
datetime last_bar_time;
|
|
|
|
//--- Arrays for Standalone Indicator Calculations
|
|
double RsiMa[];
|
|
double TrLevelSlow[];
|
|
double AtrRsi[];
|
|
double MaAtrRsi[];
|
|
double Rsi[];
|
|
double MaMaAtrRsi[];
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Exponential Moving Average Engine |
|
|
//+------------------------------------------------------------------+
|
|
void CalculateEMA(int begin, int period, const double &price[], double &result[])
|
|
{
|
|
double SmoothFactor = 2.0 / (1.0 + period);
|
|
for (int i = begin; i >= 0; i--)
|
|
{
|
|
if (price[i] == EMPTY_VALUE) result[i] = 0;
|
|
else result[i] = price[i] * SmoothFactor + result[i + 1] * (1.0 - SmoothFactor);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Core QQE Mathematics translated from Indicator Loop |
|
|
//+------------------------------------------------------------------+
|
|
bool UpdateQQEData()
|
|
{
|
|
int rates_total = iBars(_Symbol, _Period);
|
|
if(rates_total <= StartBar) return false;
|
|
|
|
// Manage dynamic arrays for EA processing
|
|
if(ArraySize(Rsi) != rates_total)
|
|
{
|
|
ArrayResize(Rsi, rates_total);
|
|
ArrayResize(RsiMa, rates_total);
|
|
ArrayResize(TrLevelSlow, rates_total);
|
|
ArrayResize(AtrRsi, rates_total);
|
|
ArrayResize(MaAtrRsi, rates_total);
|
|
ArrayResize(MaMaAtrRsi, rates_total);
|
|
|
|
ArraySetAsSeries(Rsi, true);
|
|
ArraySetAsSeries(RsiMa, true);
|
|
ArraySetAsSeries(TrLevelSlow, true);
|
|
ArraySetAsSeries(AtrRsi, true);
|
|
ArraySetAsSeries(MaAtrRsi, true);
|
|
ArraySetAsSeries(MaMaAtrRsi, true);
|
|
}
|
|
|
|
int counted = prev_calculated - 1;
|
|
if (counted < 1)
|
|
{
|
|
for (int i = 0; i < rates_total; i++)
|
|
{
|
|
TrLevelSlow[i] = 0.0; AtrRsi[i] = 0.0; MaAtrRsi[i] = 0.0;
|
|
Rsi[i] = 0.0; RsiMa[i] = 0.0; MaMaAtrRsi[i] = 0.0;
|
|
}
|
|
counted = rates_total - StartBar - 1;
|
|
}
|
|
else
|
|
{
|
|
counted = rates_total - counted - 1;
|
|
if (counted > rates_total - StartBar - 1) counted = rates_total - StartBar - 1;
|
|
}
|
|
|
|
if (CopyBuffer(myRSI, 0, 0, counted + 2, Rsi) != counted + 2) return false;
|
|
|
|
CalculateEMA(counted + 1, SF, Rsi, RsiMa);
|
|
for (int i = counted; i >= 0; i--) AtrRsi[i] = MathAbs(RsiMa[i + 1] - RsiMa[i]);
|
|
CalculateEMA(counted, Wilders_Period, AtrRsi, MaAtrRsi);
|
|
|
|
int i = counted + 1;
|
|
double tr = TrLevelSlow[i];
|
|
double rsi1 = RsiMa[i];
|
|
CalculateEMA(counted, Wilders_Period, MaAtrRsi, MaMaAtrRsi);
|
|
|
|
while (i > 0)
|
|
{
|
|
i--;
|
|
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;
|
|
}
|
|
|
|
prev_calculated = rates_total;
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Execute Trade with Dynamic ATR TP/SL |
|
|
//+------------------------------------------------------------------+
|
|
void ExecuteTrade(ENUM_ORDER_TYPE type)
|
|
{
|
|
// Fetch Current Price
|
|
double price = (type == ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
|
|
// Fetch Current ATR Value
|
|
double atrArray[];
|
|
ArraySetAsSeries(atrArray, true);
|
|
if(CopyBuffer(myATR, 0, 0, 1, atrArray) <= 0)
|
|
{
|
|
Print("Failed to copy ATR data. Order aborted.");
|
|
return;
|
|
}
|
|
double currentATR = atrArray[0];
|
|
|
|
// Calculate Dynamic Risk Distances
|
|
double slDistance = currentATR * AtrSlMultiplier;
|
|
double tpDistance = currentATR * AtrTpMultiplier;
|
|
|
|
double sl = 0, tp = 0;
|
|
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
|
|
|
if (type == ORDER_TYPE_BUY)
|
|
{
|
|
sl = price - slDistance;
|
|
tp = price + tpDistance;
|
|
|
|
trade.Buy(LotSize, _Symbol, NormalizeDouble(price, digits), NormalizeDouble(sl, digits), NormalizeDouble(tp, digits), "QQE Buy");
|
|
}
|
|
else if (type == ORDER_TYPE_SELL)
|
|
{
|
|
sl = price + slDistance;
|
|
tp = price - tpDistance;
|
|
|
|
trade.Sell(LotSize, _Symbol, NormalizeDouble(price, digits), NormalizeDouble(sl, digits), NormalizeDouble(tp, digits), "QQE Sell");
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Close Opposite Positions logic |
|
|
//+------------------------------------------------------------------+
|
|
void CloseOppositePositions(ENUM_POSITION_TYPE pos_type)
|
|
{
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber)
|
|
{
|
|
if(PositionGetInteger(POSITION_TYPE) == pos_type)
|
|
{
|
|
trade.PositionClose(ticket);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert initialization function |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit()
|
|
{
|
|
// Initialize Trade settings
|
|
trade.SetExpertMagicNumber(MagicNumber);
|
|
|
|
// Initialize QQE mathematical periods
|
|
Wilders_Period = RSI_Period * 2 - 1;
|
|
StartBar = MathMax(SF, Wilders_Period);
|
|
|
|
// Request RSI handle
|
|
myRSI = iRSI(_Symbol, _Period, RSI_Period, PRICE_CLOSE);
|
|
if(myRSI == INVALID_HANDLE)
|
|
{
|
|
Print("Error creating RSI indicator handle.");
|
|
return(INIT_FAILED);
|
|
}
|
|
|
|
// Request ATR handle
|
|
myATR = iATR(_Symbol, _Period, AtrPeriod);
|
|
if(myATR == INVALID_HANDLE)
|
|
{
|
|
Print("Error creating ATR indicator handle.");
|
|
return(INIT_FAILED);
|
|
}
|
|
|
|
return(INIT_SUCCEEDED);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert deinitialization function |
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int reason)
|
|
{
|
|
IndicatorRelease(myRSI);
|
|
IndicatorRelease(myATR);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert tick function |
|
|
//+------------------------------------------------------------------+
|
|
void OnTick()
|
|
{
|
|
// 1. Only process data on a new bar to avoid mid-candle repainting
|
|
datetime current_time = iTime(_Symbol, _Period, 0);
|
|
if(current_time == last_bar_time) return;
|
|
|
|
// 2. Update QQE arrays mathematically
|
|
if(!UpdateQQEData()) return;
|
|
|
|
// 3. Evaluate Trading Logic on the last completed bar (Index 1) and the one before (Index 2)
|
|
double currentRsiMa = RsiMa[1];
|
|
double prevRsiMa = RsiMa[2];
|
|
double currentTr = TrLevelSlow[1];
|
|
double prevTr = TrLevelSlow[2];
|
|
|
|
// Crossover Logic: RsiMA (Blue line) crosses TrLevelSlow (Yellow line)
|
|
bool isCrossUp = (prevRsiMa <= prevTr && currentRsiMa > currentTr);
|
|
bool isCrossDn = (prevRsiMa >= prevTr && currentRsiMa < currentTr);
|
|
|
|
// Level 50 Logic (Per the original code's description)
|
|
bool isLevelCrossUp = (prevRsiMa < 50 && currentRsiMa >= 50 && currentRsiMa > currentTr);
|
|
bool isLevelCrossDn = (prevRsiMa > 50 && currentRsiMa <= 50 && currentRsiMa < currentTr);
|
|
|
|
// Filter signals based on the user's selected input mode
|
|
bool buySignal = false;
|
|
bool sellSignal = false;
|
|
|
|
if(SignalMode == MODE_CROSSOVER || SignalMode == MODE_BOTH)
|
|
{
|
|
if(isCrossUp) buySignal = true;
|
|
if(isCrossDn) sellSignal = true;
|
|
}
|
|
|
|
if(SignalMode == MODE_THRESHOLD || SignalMode == MODE_BOTH)
|
|
{
|
|
if(isLevelCrossUp) buySignal = true;
|
|
if(isLevelCrossDn) sellSignal = true;
|
|
}
|
|
|
|
// 4. Execute Trades
|
|
if (buySignal)
|
|
{
|
|
CloseOppositePositions(POSITION_TYPE_SELL);
|
|
if(PositionsTotal() == 0) ExecuteTrade(ORDER_TYPE_BUY);
|
|
last_bar_time = current_time; // Mark bar as handled
|
|
}
|
|
else if (sellSignal)
|
|
{
|
|
CloseOppositePositions(POSITION_TYPE_BUY);
|
|
if(PositionsTotal() == 0) ExecuteTrade(ORDER_TYPE_SELL);
|
|
last_bar_time = current_time; // Mark bar as handled
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+ |