385 lines
15 KiB
MQL5
385 lines
15 KiB
MQL5
|
//+------------------------------------------------------------------+
|
||
|
//| QQE.mq5 |
|
||
|
//| Copyright © 2010-2022, https://t.me/ForexEaPremium |
|
||
|
//+------------------------------------------------------------------+
|
||
|
#property copyright "https://t.me/ForexEaPremium"
|
||
|
#property link "https://t.me/ForexEaPremium"
|
||
|
#property version "1.03"
|
||
|
|
||
|
#property description "QQE - Qualitative Quantitative Estimation."
|
||
|
#property description "Calculated as two indicators:"
|
||
|
#property description "1) MA on RSI"
|
||
|
#property description "2) Difference of MA on RSI and MA of MA of ATR of MA of RSI"
|
||
|
#property description "The signal for buy is when blue line crosses level 50 from below after crossing the yellow line from below."
|
||
|
#property description "The signal for sell is when blue line crosses level 50 from above after crossing the yellow line from above."
|
||
|
|
||
|
#property indicator_separate_window
|
||
|
#property indicator_buffers 6
|
||
|
#property indicator_plots 2
|
||
|
#property indicator_width1 2
|
||
|
#property indicator_color1 clrDodgerBlue
|
||
|
#property indicator_type1 DRAW_LINE
|
||
|
#property indicator_label1 "RSI MA"
|
||
|
#property indicator_color2 clrYellow
|
||
|
#property indicator_type2 DRAW_LINE
|
||
|
#property indicator_style2 STYLE_DOT
|
||
|
#property indicator_label2 "Smoothed"
|
||
|
#property indicator_level1 50
|
||
|
#property indicator_levelcolor clrAqua
|
||
|
#property indicator_levelstyle STYLE_DOT
|
||
|
|
||
|
// Inputs
|
||
|
input int SF = 5; // Smoothing Factor
|
||
|
input bool AlertOnCrossover = false;
|
||
|
input bool AlertOnLevel = false;
|
||
|
input int AlertLevel = 50;
|
||
|
input bool ArrowsOnCrossover = true;
|
||
|
input color CrossoverUpArrow = clrGreen;
|
||
|
input color CrossoverDnArrow = clrRed;
|
||
|
input bool ArrowsOnLevel = true;
|
||
|
input color LevelUpArrow = clrGreen;
|
||
|
input color LevelDnArrow = clrRed;
|
||
|
input bool NativeAlerts = true;
|
||
|
input bool EmailAlerts = false;
|
||
|
input bool NotificationAlerts = true;
|
||
|
input ENUM_TIMEFRAMES UpperTimeframe = PERIOD_CURRENT;
|
||
|
input string ObjectPrefix = "QQE-";
|
||
|
|
||
|
// Global variables:
|
||
|
int RSI_Period = 14;
|
||
|
int Wilders_Period;
|
||
|
int StartBar;
|
||
|
datetime LastAlertTimeCross, LastAlertTimeLevel;
|
||
|
|
||
|
// For MTF support:
|
||
|
int QQE_handle;
|
||
|
|
||
|
// Buffers:
|
||
|
double TrLevelSlow[];
|
||
|
double AtrRsi[];
|
||
|
double MaAtrRsi[];
|
||
|
double Rsi[];
|
||
|
double RsiMa[];
|
||
|
double MaMaAtrRsi[];
|
||
|
|
||
|
// Indicator handles
|
||
|
int myRSI;
|
||
|
|
||
|
void OnInit()
|
||
|
{
|
||
|
LastAlertTimeCross = 0;
|
||
|
LastAlertTimeLevel = 0;
|
||
|
Wilders_Period = RSI_Period * 2 - 1;
|
||
|
StartBar = MathMax(SF, Wilders_Period);
|
||
|
|
||
|
SetIndexBuffer(0, RsiMa, INDICATOR_DATA);
|
||
|
SetIndexBuffer(1, TrLevelSlow, INDICATOR_DATA);
|
||
|
SetIndexBuffer(2, AtrRsi, INDICATOR_CALCULATIONS);
|
||
|
SetIndexBuffer(3, MaAtrRsi, INDICATOR_CALCULATIONS);
|
||
|
SetIndexBuffer(4, Rsi, INDICATOR_CALCULATIONS);
|
||
|
SetIndexBuffer(5, MaMaAtrRsi, INDICATOR_CALCULATIONS);
|
||
|
|
||
|
ArraySetAsSeries(RsiMa, true);
|
||
|
ArraySetAsSeries(TrLevelSlow, true);
|
||
|
ArraySetAsSeries(AtrRsi, true);
|
||
|
ArraySetAsSeries(MaAtrRsi, true);
|
||
|
ArraySetAsSeries(Rsi, true);
|
||
|
ArraySetAsSeries(MaMaAtrRsi, true);
|
||
|
|
||
|
PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0);
|
||
|
PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, 0);
|
||
|
|
||
|
PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, StartBar);
|
||
|
PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, StartBar);
|
||
|
|
||
|
IndicatorSetInteger(INDICATOR_DIGITS, 2);
|
||
|
|
||
|
myRSI = iRSI(Symbol(), Period(), RSI_Period, PRICE_CLOSE);
|
||
|
|
||
|
if (PeriodSeconds(UpperTimeframe) > PeriodSeconds())
|
||
|
{
|
||
|
string IndicatorFileName = MQLInfoString(MQL_PROGRAM_NAME);
|
||
|
QQE_handle = iCustom(Symbol(), UpperTimeframe, IndicatorFileName, SF, false, false, AlertLevel, false, LevelUpArrow, LevelDnArrow, false, CrossoverUpArrow, CrossoverDnArrow, false, false, false, UpperTimeframe, ObjectPrefix);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
QQE_handle = INVALID_HANDLE;
|
||
|
if (PeriodSeconds(UpperTimeframe) < PeriodSeconds())
|
||
|
{
|
||
|
Print("UpperTimeframe should be above the current timeframe.");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
IndicatorSetString(INDICATOR_SHORTNAME, "QQE(" + IntegerToString(SF) + ")");
|
||
|
}
|
||
|
|
||
|
void OnDeinit(const int reason)
|
||
|
{
|
||
|
ObjectsDeleteAll(ChartID(), ObjectPrefix);
|
||
|
}
|
||
|
|
||
|
int OnCalculate(const int rates_total,
|
||
|
const int prev_calculated,
|
||
|
const datetime &Time[],
|
||
|
const double &open[],
|
||
|
const double &High[],
|
||
|
const double &Low[],
|
||
|
const double &close[],
|
||
|
const long &tick_volume[],
|
||
|
const long &volume[],
|
||
|
const int &spread[])
|
||
|
{
|
||
|
ArraySetAsSeries(Time, true);
|
||
|
ArraySetAsSeries(High, true);
|
||
|
ArraySetAsSeries(Low, true);
|
||
|
|
||
|
if (rates_total <= StartBar) return 0;
|
||
|
|
||
|
int counted = prev_calculated - 1;
|
||
|
if (counted < 1)
|
||
|
{
|
||
|
for (int i = rates_total - StartBar; 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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool rec_only_latest_upper_bar = false; // Recalculate only the latest upper timeframe bar.
|
||
|
if ((counted > 0) && (QQE_handle != INVALID_HANDLE))
|
||
|
{
|
||
|
counted = prev_calculated - PeriodSeconds(UpperTimeframe) / PeriodSeconds(); // Make the indicator redraw all current bars that constitute the upper timeframe bar.
|
||
|
rec_only_latest_upper_bar = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
counted = rates_total - counted - 1;
|
||
|
if (counted > rates_total - StartBar - 1) counted = rates_total - StartBar - 1;
|
||
|
}
|
||
|
|
||
|
if (QQE_handle == INVALID_HANDLE)
|
||
|
{
|
||
|
if (CopyBuffer(myRSI, 0, 0, counted + 2, Rsi) != counted + 2) return 0;
|
||
|
|
||
|
// Fills "counted" cells of RsiMA with EMA of Rsi.
|
||
|
CalculateEMA(counted + 1, SF, Rsi, RsiMa);
|
||
|
|
||
|
for (int i = counted; i >= 0; i--)
|
||
|
{
|
||
|
AtrRsi[i] = MathAbs(RsiMa[i + 1] - RsiMa[i]);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
for (int i = counted; i >= 0; i--)
|
||
|
{
|
||
|
double buf[1];
|
||
|
if (rec_only_latest_upper_bar)
|
||
|
if (Time[i] < iTime(Symbol(), UpperTimeframe, 0)) continue; // Skip bars older than the upper current bar.
|
||
|
int n = CopyBuffer(QQE_handle, 0, Time[i], 1, buf);
|
||
|
if (n == 1) RsiMa[i] = buf[0];
|
||
|
else return prev_calculated;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int i = counted + 1;
|
||
|
double tr = 0, rsi1 = 0;
|
||
|
|
||
|
if (QQE_handle == INVALID_HANDLE)
|
||
|
{
|
||
|
// Fills "counted" cells of MaAtrRsi with EMA of AtrRsi.
|
||
|
CalculateEMA(counted, Wilders_Period, AtrRsi, MaAtrRsi);
|
||
|
tr = TrLevelSlow[i];
|
||
|
rsi1 = RsiMa[i];
|
||
|
CalculateEMA(counted, Wilders_Period, MaAtrRsi, MaMaAtrRsi);
|
||
|
}
|
||
|
|
||
|
while (i > 0)
|
||
|
{
|
||
|
i--;
|
||
|
if (QQE_handle == INVALID_HANDLE)
|
||
|
{
|
||
|
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;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
double buf[1];
|
||
|
if (rec_only_latest_upper_bar)
|
||
|
if (Time[i] < iTime(Symbol(), UpperTimeframe, 0)) continue; // Skip bars older than the upper current bar.
|
||
|
int n = CopyBuffer(QQE_handle, 1, Time[i], 1, buf);
|
||
|
if (n == 1) TrLevelSlow[i] = buf[0];
|
||
|
else return prev_calculated;
|
||
|
}
|
||
|
|
||
|
// Arrows:
|
||
|
if ((i > 0) || (QQE_handle != INVALID_HANDLE)) // In MTF mode, check as soon as possible.
|
||
|
{
|
||
|
// Prepare for multi-timeframe mode.
|
||
|
int cur_i = i;
|
||
|
int pre_i = i + 1;
|
||
|
// Actual MTF (to avoid non-existing signals):
|
||
|
if ((QQE_handle != INVALID_HANDLE) && (i < PeriodSeconds(UpperTimeframe) / PeriodSeconds())) // Can safely skip this step if processing old bars.
|
||
|
{
|
||
|
// Find the bar that corresponds to the upper timeframe's latest finished bar.
|
||
|
int customIndex = iBarShift(Symbol(), UpperTimeframe, Time[cur_i]);
|
||
|
cur_i++;
|
||
|
while ((cur_i < rates_total) && (iBarShift(Symbol(), UpperTimeframe, Time[cur_i]) == customIndex))
|
||
|
{
|
||
|
cur_i++;
|
||
|
}
|
||
|
if (cur_i == rates_total) return prev_calculated;
|
||
|
// Find the bar that corresponds to the upper timeframe's pre-latest finished bar.
|
||
|
customIndex = iBarShift(Symbol(), UpperTimeframe, Time[cur_i]);
|
||
|
pre_i = cur_i + 1;
|
||
|
while ((pre_i < rates_total) && (iBarShift(Symbol(), UpperTimeframe, Time[pre_i])) == customIndex)
|
||
|
{
|
||
|
pre_i++;
|
||
|
}
|
||
|
if (pre_i == rates_total) return prev_calculated;
|
||
|
cur_i = pre_i - 1; // Use oldest lower timeframe bar inside that upper timeframe bar.
|
||
|
}
|
||
|
|
||
|
if (ArrowsOnCrossover)
|
||
|
{
|
||
|
string name = ObjectPrefix + "CArrow" + TimeToString(Time[cur_i]);
|
||
|
if ((RsiMa[pre_i] < TrLevelSlow[pre_i]) && (RsiMa[cur_i] > TrLevelSlow[cur_i]))
|
||
|
{
|
||
|
ObjectCreate(ChartID(), name, OBJ_ARROW_THUMB_UP, 0, Time[cur_i], Low[cur_i] - 1 * _Point);
|
||
|
ObjectSetInteger(ChartID(), name, OBJPROP_COLOR, LevelUpArrow);
|
||
|
ObjectSetInteger(ChartID(), name, OBJPROP_ANCHOR, ANCHOR_TOP);
|
||
|
ObjectSetInteger(ChartID(), name, OBJPROP_WIDTH, 5);
|
||
|
}
|
||
|
else if ((RsiMa[pre_i] > TrLevelSlow[pre_i]) && (RsiMa[cur_i] < TrLevelSlow[cur_i]))
|
||
|
{
|
||
|
ObjectCreate(ChartID(), name, OBJ_ARROW_THUMB_DOWN, 0, Time[cur_i], High[cur_i] + 1 * _Point);
|
||
|
ObjectSetInteger(ChartID(), name, OBJPROP_COLOR, LevelDnArrow);
|
||
|
ObjectSetInteger(ChartID(), name, OBJPROP_ANCHOR, ANCHOR_BOTTOM);
|
||
|
ObjectSetInteger(ChartID(), name, OBJPROP_WIDTH, 5);
|
||
|
}
|
||
|
}
|
||
|
if (ArrowsOnLevel)
|
||
|
{
|
||
|
string name = ObjectPrefix + "LArrow" + TimeToString(Time[cur_i]);
|
||
|
if ((RsiMa[pre_i] < AlertLevel) && (RsiMa[cur_i] > AlertLevel))
|
||
|
{
|
||
|
ObjectCreate(ChartID(), name, OBJ_ARROW_UP, 0, Time[cur_i], Low[cur_i] - 1 * _Point);
|
||
|
ObjectSetInteger(ChartID(), name, OBJPROP_COLOR, LevelUpArrow);
|
||
|
ObjectSetInteger(ChartID(), name, OBJPROP_ANCHOR, ANCHOR_TOP);
|
||
|
ObjectSetInteger(ChartID(), name, OBJPROP_WIDTH, 5);
|
||
|
}
|
||
|
else if ((RsiMa[pre_i] > AlertLevel) && (RsiMa[cur_i] < AlertLevel))
|
||
|
{
|
||
|
ObjectCreate(ChartID(), name, OBJ_ARROW_DOWN, 0, Time[cur_i], High[cur_i] + 1 * _Point);
|
||
|
ObjectSetInteger(ChartID(), name, OBJPROP_COLOR, LevelDnArrow);
|
||
|
ObjectSetInteger(ChartID(), name, OBJPROP_ANCHOR, ANCHOR_BOTTOM);
|
||
|
ObjectSetInteger(ChartID(), name, OBJPROP_WIDTH, 5);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ((!NativeAlerts) && (!EmailAlerts) && (!NotificationAlerts)) return rates_total;
|
||
|
if ((!AlertOnCrossover) && (!AlertOnLevel)) return rates_total;
|
||
|
|
||
|
// Prepare for multi-timeframe mode.
|
||
|
int pre_i = 2;
|
||
|
if (QQE_handle != INVALID_HANDLE)
|
||
|
{
|
||
|
// Find the bar that corresponds to the upper timeframe's latest finished bar.
|
||
|
int cnt = 1;
|
||
|
int customIndex = iBarShift(Symbol(), UpperTimeframe, Time[0]);
|
||
|
while ((cnt < rates_total) && (iBarShift(Symbol(), UpperTimeframe, Time[cnt]) == customIndex))
|
||
|
{
|
||
|
cnt++;
|
||
|
}
|
||
|
i = cnt;
|
||
|
// Find the bar that corresponds to the upper timeframe's pre-latest finished bar.
|
||
|
customIndex = iBarShift(Symbol(), UpperTimeframe, Time[cnt]);
|
||
|
cnt++;
|
||
|
while ((cnt < rates_total) && (iBarShift(Symbol(), UpperTimeframe, Time[cnt]) == customIndex))
|
||
|
{
|
||
|
cnt++;
|
||
|
}
|
||
|
pre_i = cnt;
|
||
|
}
|
||
|
else i = 1; // Non-MTF.
|
||
|
|
||
|
if (AlertOnLevel)
|
||
|
{
|
||
|
if ((LastAlertTimeLevel > 0) && (((RsiMa[pre_i] < AlertLevel) && (RsiMa[i] > AlertLevel)) || ((RsiMa[pre_i] > AlertLevel) && (RsiMa[i] < AlertLevel))) && (Time[i - 1] > LastAlertTimeLevel))
|
||
|
{
|
||
|
string TextNative = IntegerToString(AlertLevel) + " level Cross ";
|
||
|
if ((RsiMa[pre_i] > AlertLevel) && (RsiMa[i] < AlertLevel)) TextNative += "Down";
|
||
|
else TextNative += "Up";
|
||
|
string Text = "QQE " + Symbol() + ", TF: " + TimeframeToString((ENUM_TIMEFRAMES)Period()) + " " + TextNative;
|
||
|
DoAlerts(Text, TextNative);
|
||
|
LastAlertTimeLevel = Time[i - 1];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (AlertOnCrossover)
|
||
|
{
|
||
|
if ((LastAlertTimeCross > 0) && (((RsiMa[pre_i] < TrLevelSlow[pre_i]) && (RsiMa[i] > TrLevelSlow[i])) || ((RsiMa[pre_i] > TrLevelSlow[pre_i]) && (RsiMa[i] < TrLevelSlow[i]))) && (Time[i - 1] > LastAlertTimeCross))
|
||
|
{
|
||
|
string TextNative = "RSI MA crossed Smoothed Line from ";
|
||
|
if ((RsiMa[pre_i] > TrLevelSlow[pre_i]) && (RsiMa[i] < TrLevelSlow[i])) TextNative += "above.";
|
||
|
else TextNative += "below.";
|
||
|
string Text = "QQE " + Symbol() + ", TF: " + TimeframeToString((ENUM_TIMEFRAMES)Period()) + " " + TextNative;
|
||
|
DoAlerts(Text, TextNative);
|
||
|
LastAlertTimeCross = Time[i - 1];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (LastAlertTimeLevel == 0) LastAlertTimeLevel = Time[0];
|
||
|
if (LastAlertTimeCross == 0) LastAlertTimeCross = Time[0];
|
||
|
|
||
|
return rates_total;
|
||
|
}
|
||
|
|
||
|
void DoAlerts(string Text, string TextNative)
|
||
|
{
|
||
|
if (NativeAlerts) Alert(TextNative);
|
||
|
if (EmailAlerts) SendMail(Text, Text);
|
||
|
if (NotificationAlerts) SendNotification(Text);
|
||
|
}
|
||
|
|
||
|
string TimeframeToString(ENUM_TIMEFRAMES P)
|
||
|
{
|
||
|
return StringSubstr(EnumToString(P), 7);
|
||
|
}
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Exponential Moving Average |
|
||
|
//| Fills the buffer array with EMA values. |
|
||
|
//+------------------------------------------------------------------+
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|