-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1. Arrows. 2. New alert. 3. cTrader version. 4. MTF.
- Loading branch information
Showing
3 changed files
with
747 additions
and
90 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
Oops, something went wrong.