Skip to content

Commit

Permalink
1.03
Browse files Browse the repository at this point in the history
1. Arrows.
2. New alert.
3. cTrader version.
4. MTF.
  • Loading branch information
EarnForex authored Oct 31, 2022
1 parent 1ca43ae commit 080eca6
Show file tree
Hide file tree
Showing 3 changed files with 747 additions and 90 deletions.
312 changes: 312 additions & 0 deletions QQE.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,312 @@
// -------------------------------------------------------------------------------
//
// QQE - Qualitative Quantitative Estimation.
// Calculated as two indicators:
// 1) MA on RSI
// 2) Difference of MA on RSI and MA of MA of ATR of MA of RSI
// The signal for buy is when blue line crosses level 50 from below after crossing the yellow line from below.
// The signal for sell is when blue line crosses level 50 from above after crossing the yellow line from above.
//
// Version 1.03
// Copyright 2010-2022, EarnForex.com
// https://www.earnforex.com/metatrader-indicators/QQE/
// -------------------------------------------------------------------------------
using System;
using cAlgo.API;
using cAlgo.API.Indicators;

namespace cAlgo.Indicators
{
[Levels(50)]
[Indicator(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class QQE : Indicator
{
public enum ENUM_CANDLE_TO_CHECK
{
Current,
Previous
}

[Parameter(DefaultValue = 5, MinValue = 1)]
public int SF { get; set; }

[Parameter(DefaultValue = false)]
public bool AlertOnCrossover { get; set; }

[Parameter(DefaultValue = false)]
public bool AlertOnLevel { get; set; }

[Parameter(DefaultValue = 50, MinValue = 1, MaxValue = 99)]
public int AlertLevel { get; set; }

[Parameter(DefaultValue = true)]
public bool ArrowsOnCrossover { get; set; }

[Parameter(DefaultValue = "Green")]
public string CrossoverUpArrow { get; set; }

[Parameter(DefaultValue = "Red")]
public string CrossoverDnArrow { get; set; }

[Parameter(DefaultValue = true)]
public bool ArrowsOnLevel { get; set; }

[Parameter(DefaultValue = "Green")]
public string LevelUpArrow { get; set; }

[Parameter(DefaultValue = "Red")]
public string LevelDnArrow { get; set; }

[Parameter("Enable email alerts", DefaultValue = false)]
public bool EnableEmailAlerts { get; set; }

[Parameter("AlertEmail: Email From", DefaultValue = "")]
public string AlertEmailFrom { get; set; }

[Parameter("AlertEmail: Email To", DefaultValue = "")]
public string AlertEmailTo { get; set; }

[Parameter("Upper timeframe")]
public TimeFrame UpperTimeframe { get; set; }

[Parameter(DefaultValue = "QQE-")]
public string ObjectPrefix { get; set; }

[Output("RSI MA", LineColor = "DodgerBlue", Thickness = 2)]
public IndicatorDataSeries RsiMa { get; set; }

[Output("Smoothed", LineColor = "Yellow")]
public IndicatorDataSeries TrLevelSlow { get; set; }

private IndicatorDataSeries AtrRsi;
private IndicatorDataSeries TrLevelSlow_;
private IndicatorDataSeries MaAtrRsi_, MaAtrRsi_Wilders_;
private RelativeStrengthIndex Rsi_;
private MovingAverage RsiMa_;

private bool UseUpperTimeFrame;

private Bars customBars;

private const int RSI_Period = 14;
private int Wilders_Period;
private double Wilders_Multiplier;

private int Is_Initialized_MaAtrRsi = -1;
private int Is_Initialized_MaAtrRsi_Wilders = -1;

private int prev_index = -1;
private int CI_zero = 0;

private DateTime LastAlertTimeCross, LastAlertTimeLevel, unix_epoch;

protected override void Initialize()
{
Wilders_Period = RSI_Period * 2 - 1;
Wilders_Multiplier = 2.0 / (Wilders_Period + 1.0);

if (UpperTimeframe <= TimeFrame)
{
Print("UpperTimeframe <= current timeframe. Ignored.");
UseUpperTimeFrame = false;
customBars = Bars;
}
else
{
UseUpperTimeFrame = true;
customBars = MarketData.GetBars(UpperTimeframe);
}

Rsi_ = Indicators.RelativeStrengthIndex(customBars.ClosePrices, RSI_Period);
RsiMa_ = Indicators.MovingAverage(Rsi_.Result, SF, MovingAverageType.Exponential);
AtrRsi = CreateDataSeries();
MaAtrRsi_ = CreateDataSeries();
MaAtrRsi_Wilders_ = CreateDataSeries();
TrLevelSlow_ = CreateDataSeries();

unix_epoch = new DateTime(1970, 1, 1, 0, 0, 0);
LastAlertTimeCross = unix_epoch;
LastAlertTimeLevel = unix_epoch;
}

protected override void OnDestroy()
{
if ((ArrowsOnLevel) || (ArrowsOnCrossover))
{
var icons = Chart.FindAllObjects(ChartObjectType.Icon);
for (int i = icons.Length - 1; i >= 0; i--)
{
if (icons[i].Name.StartsWith(ObjectPrefix))
Chart.RemoveObject(icons[i].Name);
}
}
}

public override void Calculate(int index)
{
int customIndex = index;
int cnt = 0; // How many bars of the current timeframe should be recalculated.
if (UseUpperTimeFrame)
{
customIndex = customBars.OpenTimes.GetIndexByTime(Bars.OpenTimes[index]);
if (index == 0) CI_zero = customIndex; // customIndex at zero. Because an upper timeframe index may start from non-zero value.
if (customIndex <= CI_zero + SF) return; // Too early to calculate anything.
// Find how many current timeframe bars should be recalculated:
while (customBars.OpenTimes.GetIndexByTime(Bars.OpenTimes[index - cnt]) == customIndex)
{
cnt++;
}
}
else
{
cnt = 1; // Non-MTF.
if (customIndex <= SF) return; // Too early to calculate anything.
}


RsiMa[index] = RsiMa_.Result[customIndex];

AtrRsi[customIndex] = Math.Abs(RsiMa_.Result[customIndex - 1] - RsiMa_.Result[customIndex]);

if (customIndex <= CI_zero + SF + Wilders_Period + 1) return; // Too early to calculate MaAtrRsi.

if (Is_Initialized_MaAtrRsi == -1)
{
// Simple average for the first value.
MaAtrRsi_[customIndex] = GetAverage(AtrRsi, customIndex, Wilders_Period);
Is_Initialized_MaAtrRsi = customIndex;
}
else if (Is_Initialized_MaAtrRsi < customIndex) // On next index.
{
// Fail-safe for NaN.
if (double.IsNaN(MaAtrRsi_[customIndex - 1])) MaAtrRsi_[customIndex] = GetAverage(AtrRsi, customIndex, Wilders_Period);
// Exponential average formula.
else MaAtrRsi_[customIndex] = (AtrRsi[customIndex] - MaAtrRsi_[customIndex - 1]) * Wilders_Multiplier + MaAtrRsi_[customIndex - 1];
}

if (customIndex <= CI_zero + SF + Wilders_Period + Wilders_Period) return; // Too early to calculate MaAtrRsi_Wilders.

if (Is_Initialized_MaAtrRsi_Wilders == -1)
{
// Simple average for the first value.
MaAtrRsi_Wilders_[customIndex] = GetAverage(MaAtrRsi_, customIndex, Wilders_Period);
Is_Initialized_MaAtrRsi_Wilders = customIndex;
}
else if (Is_Initialized_MaAtrRsi_Wilders < customIndex) // On next index.
{
// Fail-safe for NaN.
if (double.IsNaN(MaAtrRsi_Wilders_[customIndex - 1])) MaAtrRsi_Wilders_[customIndex] = GetAverage(MaAtrRsi_, customIndex, Wilders_Period);
// Exponential average formula.
else MaAtrRsi_Wilders_[customIndex] = (MaAtrRsi_[customIndex] - MaAtrRsi_Wilders_[customIndex - 1]) * Wilders_Multiplier + MaAtrRsi_Wilders_[customIndex - 1];
}

double rsi1 = RsiMa_.Result[customIndex - 1];
double rsi0 = RsiMa_.Result[customIndex];
double dar = MaAtrRsi_Wilders_[customIndex] * 4.236;

double tr = TrLevelSlow_[customIndex - 1];
if (double.IsNaN(tr)) tr = 0;
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_[customIndex] = tr;

TrLevelSlow[index] = TrLevelSlow_[customIndex];

int cnt_prev = 2;
if (UseUpperTimeFrame)
{
for (int i = 1; i < cnt; i++)
{
TrLevelSlow[index - i] = TrLevelSlow[index];
RsiMa[index - i] = RsiMa[index];
}

int ci_temp = customBars.OpenTimes.GetIndexByTime(Bars.OpenTimes[index - cnt]); // Latest finished bar.
// Find pre-latest finished bar.
cnt_prev = cnt + 1;
while (customBars.OpenTimes.GetIndexByTime(Bars.OpenTimes[index - cnt_prev]) == ci_temp)
{
cnt_prev++;
}
}

// Arrows
if (ArrowsOnCrossover)
{
if ((RsiMa[index - cnt_prev] < TrLevelSlow[index - cnt_prev]) && (RsiMa[index - cnt] > TrLevelSlow[index - cnt])) // Cross up.
{
Chart.DrawIcon(ObjectPrefix + "C" + Bars.OpenTimes[index - cnt + 1].ToString(), ChartIconType.UpTriangle, index - cnt + 1, Bars.LowPrices[index - cnt + 1], Color.FromName(CrossoverUpArrow));
}
else if ((RsiMa[index - cnt_prev] > TrLevelSlow[index - cnt_prev]) && (RsiMa[index - cnt] < TrLevelSlow[index - cnt])) // Cross down.
{
Chart.DrawIcon(ObjectPrefix + "C" + Bars.OpenTimes[index - cnt + 1].ToString(), ChartIconType.DownTriangle, index - cnt + 1, Bars.HighPrices[index - cnt + 1], Color.FromName(CrossoverDnArrow));
}
}
if (ArrowsOnLevel)
{
if ((RsiMa[index - cnt_prev] < AlertLevel) && (RsiMa[index - cnt] > AlertLevel))
{
Chart.DrawIcon(ObjectPrefix + "L" + Bars.OpenTimes[index - cnt + 1].ToString(), ChartIconType.UpArrow, index - cnt + 1, Bars.LowPrices[index - cnt + 1], Color.FromName(LevelUpArrow));
}
else if ((RsiMa[index - cnt_prev] > AlertLevel) && (RsiMa[index - cnt] < AlertLevel))
{
Chart.DrawIcon(ObjectPrefix + "L" + Bars.OpenTimes[index - cnt + 1].ToString(), ChartIconType.DownArrow, index - cnt + 1, Bars.LowPrices[index - cnt + 1], Color.FromName(LevelDnArrow));
}
}

// Alerts
if (!EnableEmailAlerts) return; // No need to go further.
if ((!AlertOnCrossover) && (!AlertOnLevel)) return;

if (AlertOnLevel)
{
if ((LastAlertTimeLevel > unix_epoch) && (((RsiMa[index - cnt_prev] < AlertLevel) && (RsiMa[index - cnt] > AlertLevel)) || ((RsiMa[index - cnt_prev] > AlertLevel) && (RsiMa[index - cnt] < AlertLevel))) && (Bars.OpenTimes[index - cnt] > LastAlertTimeLevel))
{
string Text = "QQE: " + Symbol.Name + " - " + TimeFrame.Name + " - Level Cross Up";
if ((RsiMa[index - cnt_prev] > AlertLevel) && (RsiMa[index - cnt] < AlertLevel)) Text = "QQE: " + Symbol.Name + " - " + TimeFrame.Name + " - Level Cross Down";
Notifications.SendEmail(AlertEmailFrom, AlertEmailTo, "QQE Alert - " + Symbol.Name + " @ " + TimeFrame.Name, Text);
LastAlertTimeLevel = Bars.OpenTimes[index - cnt];
}
}

if (AlertOnCrossover)
{
if ((LastAlertTimeCross > unix_epoch) && (((RsiMa[index - cnt_prev] < TrLevelSlow[index - cnt_prev]) && (RsiMa[index - cnt] > TrLevelSlow[index - cnt])) || ((RsiMa[index - cnt_prev] > TrLevelSlow[index - cnt_prev]) && (RsiMa[index - cnt] < TrLevelSlow[index - cnt]))) && (Bars.OpenTimes[index - cnt] > LastAlertTimeCross))
{
string Text = "QQE: " + Symbol.Name + " - " + TimeFrame.Name + " - RSI MA crossed Smoothed Line from below.";
if ((RsiMa[index - cnt_prev] > TrLevelSlow[index - cnt_prev]) && (RsiMa[index - cnt] < TrLevelSlow[index - cnt])) Text = "QQE: " + Symbol.Name + " - " + TimeFrame.Name + " - RSI MA crossed Smoothed Line from above.";
Notifications.SendEmail(AlertEmailFrom, AlertEmailTo, "QQE Alert - " + Symbol.Name + " @ " + TimeFrame.Name, Text);
LastAlertTimeCross = Bars.OpenTimes[index - cnt];
}
}

if ((LastAlertTimeLevel == unix_epoch) && (prev_index == index)) LastAlertTimeLevel = Bars.OpenTimes.LastValue;
if ((LastAlertTimeCross == unix_epoch) && (prev_index == index)) LastAlertTimeCross = Bars.OpenTimes.LastValue;
prev_index = index;
}

// Simple moving average to seed the first value of the exponential moving average.
private double GetAverage(DataSeries series, int index, int period)
{
var lastIndex = index - period;
double sum = 0;

for (var i = index; i > lastIndex; i--)
{
sum += series[i];
}

return sum / period;
}
}
}
Loading

0 comments on commit 080eca6

Please sign in to comment.