Donchian Ultimate

Price Data Components
Series array that contains open time of each barSeries array that contains the highest prices of each barSeries array that contains open prices of each barSeries array that contains close prices for each barSeries array that contains the lowest prices of each bar
Miscellaneous
It issuies visual alerts to the screenIt sends emails
0 Views
0 Downloads
0 Favorites
Donchian Ultimate
//+------------------------------------------------------------------+
//|                                                Donchian Ultimate |
//|                                    Copyright © 2025 Wolfforex.com|
//|                                        https://www.wolfforex.com |
//+------------------------------------------------------------------+
#property version   "1.00"
#property strict

#property description "A classic Donchian Channel indicator with extra features:"
#property description " * MTF support"
#property description " * Multiple boundary calculation options"
#property description " * Support and resistance zones"
#property description " * Alert system"

#property indicator_chart_window
#property indicator_buffers 9
#property indicator_plots 7
#property indicator_label1 "Upper Line"
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrGreen
#property indicator_width1 2
#property indicator_label2 "Lower Line"
#property indicator_type2 DRAW_LINE
#property indicator_color2 clrRed
#property indicator_width2 2
#property indicator_label3 "Mid Line"
#property indicator_type3 DRAW_LINE
#property indicator_color3 clrBlue
#property indicator_width3 1
#property indicator_label4 "Resistance"
#property indicator_type4 DRAW_LINE
#property indicator_style4 STYLE_DOT
#property indicator_color4 clrPaleGreen
#property indicator_width4 1
#property indicator_label5 "Support"
#property indicator_type5 DRAW_LINE
#property indicator_style5 STYLE_DOT
#property indicator_color5 clrSalmon
#property indicator_width5 1
#property indicator_label6 "Resistance Span"
#property indicator_type6 DRAW_FILLING
#property indicator_color6 clrPaleGreen, clrPaleGreen
#property indicator_width6 1
#property indicator_label7 "Support Span"
#property indicator_type7 DRAW_FILLING
#property indicator_color7 clrSalmon, clrSalmon
#property indicator_width7 1

// Enumeration for price type:
enum ENUM_PRICE_TYPE
{
    PRICE_HH_LL, // Highest High (Lowest Low)
    PRICE_AVER_HHHO_LLLO, // Average Highest High, Highest Open (Lowest Low, Lowest Open)
    PRICE_AVER_HHHC_LLLC, // Average Highest High, Highest Close (Lowest Low, Lowest Close)
    PRICE_HO_LO, // Highest Open (Lowest Open)
    PRICE_HC_LC // Highest Close (Lowest Close)
};

// Enumeration for alert candle:
enum ENUM_ALERT_CANDLE
{
    ALERT_PREVIOUS_CANDLE, // Previous
    ALERT_CURRENT_CANDLE // Current
};

input int InpPeriod = 20; // Period
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_CURRENT; // Timeframe
input ENUM_PRICE_TYPE PriceType = PRICE_HH_LL;
input int Shift = 0;
input bool IsShowResistanceSpan = true; // Show Resistance Span
input bool IsShowSupportSpan = true; // Show Support Span
input ENUM_ALERT_CANDLE AlertCandle = ALERT_PREVIOUS_CANDLE; // Alert Candle
input bool IsAlertMidLineBullishCrossing = true; // Alert About Bullish Crossing of Mid Line
input bool IsAlertMidLineBearishCrossing = true; // Alert About Bearish Crossing of Mid Line
input bool IsAlertCandleCloseInsideResistance = true; // Alert About Candle Close Inside Resistance
input bool IsAlertCandleCloseInsideSupport = true; // Alert About Candle Close Inside Support
input bool IsShowAlert = false; // Show Alert
input bool IsSendEmail = false; // Send Email
input bool IsSendNotification = false; // Send Notification

double UpBuffer[];
double DownBuffer[];
double ResistanceBuffer[];
double SupportBuffer[];
double ResistanceFillingBuffer[];
double SupportFillingBuffer[];
double MidBuffer[];
double ResistanceFillingAddBuffer[];
double SupportFillingAddBuffer[];

ENUM_TIMEFRAMES Timeframe; // Timeframe of operation
int deltaHighTF; // Difference in candles count from the higher timeframe

// Global variables for alerts
bool IsMidLineBullishCrossing; // Variable for storing that it is bullish crossing of mid line
bool IsMidLineBearishCrossing; // Variable for storing that it is bearish crossing of mid line
bool IsCandleCloseInsideResistance; // Variable for storing that candle closes inside resistance
bool IsCandleCloseInsideSupport; // Variable for storing that candle closes inside support
string MidLineBullishCrossingAlertMessage; // Message for alerting that it is bullish crossing of mid line
string MidLineBearishCrossingAlertMessage; // Message for alerting that it is bearish crossing of mid line
string CandleCloseInsideResistanceAlertMessage; // Message for alerting that candle closes inside resistance
string CandleCloseInsideSupportAlertMessage; // Message for alerting that candle closes inside support
int RatesTotal;
int PrevCalculated;
string AlertPrefix;

int OnInit()
{
    IndicatorSetInteger(INDICATOR_DIGITS, _Digits);

    SetIndexBuffer(0, UpBuffer, INDICATOR_DATA);
    SetIndexBuffer(1, DownBuffer, INDICATOR_DATA);
    SetIndexBuffer(2, MidBuffer, INDICATOR_DATA);
    SetIndexBuffer(3, ResistanceBuffer, INDICATOR_DATA);
    SetIndexBuffer(4, SupportBuffer, INDICATOR_DATA);
    SetIndexBuffer(5, ResistanceFillingBuffer, INDICATOR_DATA);
    SetIndexBuffer(6, ResistanceFillingAddBuffer, INDICATOR_CALCULATIONS);
    SetIndexBuffer(7, SupportFillingBuffer, INDICATOR_DATA);
    SetIndexBuffer(8, SupportFillingAddBuffer, INDICATOR_CALCULATIONS);

    for (int i = 0; i < 9; i++)
    {
        PlotIndexSetInteger(i, PLOT_DRAW_BEGIN, InpPeriod - 1 + Shift);
        PlotIndexSetDouble(i, PLOT_EMPTY_VALUE, EMPTY_VALUE);
    }

    ArraySetAsSeries(UpBuffer, false);
    ArraySetAsSeries(DownBuffer, false);
    ArraySetAsSeries(MidBuffer, false);
    ArraySetAsSeries(ResistanceBuffer, false);
    ArraySetAsSeries(SupportBuffer, false);
    ArraySetAsSeries(ResistanceFillingBuffer, false);
    ArraySetAsSeries(SupportFillingBuffer, false);
    ArraySetAsSeries(ResistanceFillingAddBuffer, false);
    ArraySetAsSeries(SupportFillingAddBuffer, false);

    // Initializing global variables:
    MidLineBullishCrossingAlertMessage = "Bullish Crossing of Mid Line";
    MidLineBearishCrossingAlertMessage = "Bearish Crossing of Mid Line";
    CandleCloseInsideResistanceAlertMessage = "Candle Close Inside Resistance";
    CandleCloseInsideSupportAlertMessage = "Candle Close Inside Support";
    IsMidLineBullishCrossing = false;
    IsMidLineBearishCrossing = false;
    IsCandleCloseInsideResistance = false;
    IsCandleCloseInsideSupport = false;
    RatesTotal = 0;
    PrevCalculated = 0;

    // Setting values for high timeframe:
    Timeframe = InpTimeframe;
    if (InpTimeframe < Period())
    {
        Timeframe = Period();
    }
    AlertPrefix = _Symbol + " @ " + EnumToString(_Period);
    if (Timeframe != Period()) AlertPrefix += " (" + EnumToString(Timeframe) + ")";
    StringReplace(AlertPrefix, "PERIOD_", "");

    deltaHighTF = 0;
    if (PeriodSeconds(Timeframe) > PeriodSeconds())
    {
        deltaHighTF = PeriodSeconds(Timeframe) / PeriodSeconds();
    }

    return INIT_SUCCEEDED;
}

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[])
{
    if (rates_total < InpPeriod)
    {
        return 0;
    }

    double buffer[];
    int toCopy;
    if ((prev_calculated > rates_total) || (prev_calculated < 0))
    {
        toCopy = rates_total;
    }
    else
    {
        toCopy = rates_total - prev_calculated;
        if (prev_calculated > 0)
        {
            toCopy++;
        }
    }

    if (CopyClose(_Symbol, Timeframe, 0, toCopy, buffer) <= 0)
    {
        return 0;
    }

    RatesTotal = rates_total;
    PrevCalculated = prev_calculated;

    // Preliminary calculations:
    int pos = prev_calculated - 1 - deltaHighTF;

    int startIndex = InpPeriod - 1 + Shift;
    if (pos < startIndex)
    {
        for (int i = 0; i < startIndex; i++)
        {
            UpBuffer[i] = EMPTY_VALUE;
            DownBuffer[i] = EMPTY_VALUE;
            ResistanceBuffer[i] = EMPTY_VALUE;
            SupportBuffer[i] = EMPTY_VALUE;
            MidBuffer[i] = EMPTY_VALUE;
            ResistanceFillingBuffer[i] = EMPTY_VALUE;
            SupportFillingBuffer[i] = EMPTY_VALUE;
            ResistanceFillingAddBuffer[i] = EMPTY_VALUE;
            SupportFillingAddBuffer[i] = EMPTY_VALUE;
        }
        pos = startIndex;
    }

    for (int i = pos; i < rates_total && !IsStopped(); i++)
    {
        int index = rates_total - 1 - i + Shift;

        UpBuffer[i] = GetUpLineValue(index);
        DownBuffer[i] = GetDownLineValue(index);

        ResistanceBuffer[i] = GetResistanceValue(index);

        if (IsShowResistanceSpan)
        {
            ResistanceFillingAddBuffer[i] = ResistanceBuffer[i];
            ResistanceFillingBuffer[i] = UpBuffer[i];
        }
        else
        {
            ResistanceFillingAddBuffer[i] = EMPTY_VALUE;
            ResistanceFillingBuffer[i] = EMPTY_VALUE;
            ResistanceBuffer[i] = EMPTY_VALUE;
        }

        SupportBuffer[i] = GetSupportValue(index);

        if (IsShowSupportSpan)
        {
            SupportFillingAddBuffer[i] = DownBuffer[i];
            SupportFillingBuffer[i] = SupportBuffer[i];
        }
        else
        {
            SupportFillingAddBuffer[i] = EMPTY_VALUE;
            SupportFillingBuffer[i] = EMPTY_VALUE;
            SupportBuffer[i] = EMPTY_VALUE;
        }

        MidBuffer[i] = (UpBuffer[i] + DownBuffer[i]) / 2;
    }

    HandleAlerts();

    return rates_total;
}

//+------------------------------------------------------------------+
//| Getting the value for the upper Donchian channel line.           |
//+------------------------------------------------------------------+
double GetUpLineValue(int index)
{
    int shift = iBarShift(_Symbol, Timeframe, iTime(_Symbol, PERIOD_CURRENT, index));

    switch(PriceType)
    {
    case PRICE_HH_LL:
        return iHigh(_Symbol, Timeframe, iHighest(_Symbol, Timeframe, MODE_HIGH, InpPeriod, shift));
    case PRICE_AVER_HHHO_LLLO:
        return (iHigh(_Symbol, Timeframe, iHighest(_Symbol, Timeframe, MODE_HIGH, InpPeriod, shift)) + iOpen(_Symbol, Timeframe, iHighest(_Symbol, Timeframe, MODE_OPEN, InpPeriod, shift))) / 2;
    case PRICE_AVER_HHHC_LLLC:
        return (iHigh(_Symbol, Timeframe, iHighest(_Symbol, Timeframe, MODE_HIGH, InpPeriod, shift)) + iClose(_Symbol, Timeframe, iHighest(_Symbol, Timeframe, MODE_CLOSE, InpPeriod, shift))) / 2;
    case PRICE_HO_LO:
        return iOpen(_Symbol, Timeframe, iHighest(_Symbol, Timeframe, MODE_OPEN, InpPeriod, shift));
    case PRICE_HC_LC:
        return iClose(_Symbol, Timeframe, iHighest(_Symbol, Timeframe, MODE_CLOSE, InpPeriod, shift));
    default:
        return EMPTY_VALUE;
    }
}

//+------------------------------------------------------------------+
//| Getting the value for the lower Donchian channel line.           |
//+------------------------------------------------------------------+
double GetDownLineValue(int index)
{
    int shift = iBarShift(_Symbol, Timeframe, iTime(_Symbol, PERIOD_CURRENT, index));

    switch(PriceType)
    {
    case PRICE_HH_LL:
        return iLow(_Symbol, Timeframe, iLowest(_Symbol, Timeframe, MODE_LOW, InpPeriod, shift));
    case PRICE_AVER_HHHO_LLLO:
        return (iLow(_Symbol, Timeframe, iLowest(_Symbol, Timeframe, MODE_LOW, InpPeriod, shift)) + iOpen(_Symbol, Timeframe, iLowest(_Symbol, Timeframe, MODE_OPEN, InpPeriod, shift))) / 2;
    case PRICE_AVER_HHHC_LLLC:
        return (iLow(_Symbol, Timeframe, iLowest(_Symbol, Timeframe, MODE_LOW, InpPeriod, shift)) + iClose(_Symbol, Timeframe, iLowest(_Symbol, Timeframe, MODE_CLOSE, InpPeriod, shift))) / 2;
    case PRICE_HO_LO:
        return iOpen(_Symbol, Timeframe, iLowest(_Symbol, Timeframe, MODE_OPEN, InpPeriod, shift));
    case PRICE_HC_LC:
        return iClose(_Symbol, Timeframe, iLowest(_Symbol, Timeframe, MODE_CLOSE, InpPeriod, shift));
    default:
        return EMPTY_VALUE;
    }
}

//+------------------------------------------------------------------+
//| Getting the resistance area's lower boundary.                    |
//+------------------------------------------------------------------+
double GetResistanceValue(int index)
{
    int shift = iBarShift(_Symbol, Timeframe, iTime(_Symbol, PERIOD_CURRENT, index));

    switch(PriceType)
    {
    case PRICE_HH_LL:
        return iLow(_Symbol, Timeframe, iHighest(_Symbol, Timeframe, MODE_LOW, InpPeriod, shift));
    case PRICE_AVER_HHHO_LLLO:
        return (iLow(_Symbol, Timeframe, iHighest(_Symbol, Timeframe, MODE_LOW, InpPeriod, shift)) + iOpen(_Symbol, Timeframe, iHighest(_Symbol, Timeframe, MODE_OPEN, InpPeriod, shift))) / 2;
    case PRICE_AVER_HHHC_LLLC:
        return (iLow(_Symbol, Timeframe, iHighest(_Symbol, Timeframe, MODE_LOW, InpPeriod, shift)) + iClose(_Symbol, Timeframe, iHighest(_Symbol, Timeframe, MODE_CLOSE, InpPeriod, shift))) / 2;
    case PRICE_HO_LO:
    case PRICE_HC_LC:
        return iLow(_Symbol, Timeframe, iHighest(_Symbol, Timeframe, MODE_LOW, InpPeriod, shift));
    default:
        return EMPTY_VALUE;
    }
}

//+------------------------------------------------------------------+
//| Getting the support area's upper boundary.                       |
//+------------------------------------------------------------------+
double GetSupportValue(int index)
{
    int shift = iBarShift(_Symbol, Timeframe, iTime(_Symbol, PERIOD_CURRENT, index));

    switch(PriceType)
    {
    case PRICE_HH_LL:
        return iHigh(_Symbol, Timeframe, iLowest(_Symbol, Timeframe, MODE_HIGH, InpPeriod, shift));
    case PRICE_AVER_HHHO_LLLO:
        return (iHigh(_Symbol, Timeframe, iLowest(_Symbol, Timeframe, MODE_HIGH, InpPeriod, shift)) + iOpen(_Symbol, Timeframe, iLowest(_Symbol, Timeframe, MODE_OPEN, InpPeriod, shift))) / 2;
    case PRICE_AVER_HHHC_LLLC:
        return (iHigh(_Symbol, Timeframe, iLowest(_Symbol, Timeframe, MODE_HIGH, InpPeriod, shift)) + iClose(_Symbol, Timeframe, iLowest(_Symbol, Timeframe, MODE_CLOSE, InpPeriod, shift))) / 2;
    case PRICE_HO_LO:
    case PRICE_HC_LC:
        return iHigh(_Symbol, Timeframe, iLowest(_Symbol, Timeframe, MODE_HIGH, InpPeriod, shift));
    default:
        return EMPTY_VALUE;
    }
}

void HandleAlerts()
{
    if ((!IsShowAlert) && (!IsSendEmail) && (!IsSendNotification)) return; // No alerts are needed

    if (!PrevCalculated)
    {
        RefreshGlobalVariables(); // Refresh alert global variables after attaching indicator.
    }
    else if (PrevCalculated != RatesTotal)
    {
        ResetGlobalVariables(); // Reset alert global variables after new candle forming.
    }

    string isMidLineBullishCrossingMessage = NULL;
    string isMidLineBearishCrossingMessage = NULL;
    string isCandleCloseInsideResistanceMessage = NULL;
    string isCandleCloseInsideSupportMessage = NULL;

    // Checking for alerts and saving info about it
    if ((IsAlertMidLineBullishCrossing) &&
            (!IsMidLineBullishCrossing) &&
            (HasMidLineBullishCrossing()))
    {
        isMidLineBullishCrossingMessage = MidLineBullishCrossingAlertMessage;
        IsMidLineBullishCrossing = true;
    }

    if ((IsAlertMidLineBearishCrossing) &&
            (!IsMidLineBearishCrossing) &&
            (HasMidLineBearishCrossing()))
    {
        isMidLineBearishCrossingMessage = MidLineBearishCrossingAlertMessage;
        IsMidLineBearishCrossing = true;
    }

    if ((IsAlertCandleCloseInsideResistance) &&
            (!IsCandleCloseInsideResistance) &&
            (HasCandleCloseInsideResistance()))
    {
        isCandleCloseInsideResistanceMessage = CandleCloseInsideResistanceAlertMessage;
        IsCandleCloseInsideResistance = true;
    }

    if ((IsAlertCandleCloseInsideSupport) &&
            (!IsCandleCloseInsideSupport) &&
            (HasCandleCloseInsideSupport()))
    {
        isCandleCloseInsideSupportMessage = CandleCloseInsideSupportAlertMessage;
        IsCandleCloseInsideSupport = true;
    }

    IssueAlerts(isMidLineBullishCrossingMessage);
    IssueAlerts(isMidLineBearishCrossingMessage);
    IssueAlerts(isCandleCloseInsideResistanceMessage);
    IssueAlerts(isCandleCloseInsideSupportMessage);
}

void IssueAlerts(string message)
{
    if (message == NULL) return;
    if (IsShowAlert)
    {
        string native_alert = message;
        if (Timeframe != Period()) native_alert = "(" + EnumToString(Timeframe) + ") " + native_alert;
        StringReplace(native_alert, "PERIOD_", "");
        Alert(native_alert);
    }
    message = AlertPrefix + ": " + message;
    if (IsSendEmail)
    {
        SendMail("Donchian Ultimate Alert", message);
    }
    if (IsSendNotification)
    {
        SendNotification(message);
    }
}

//+------------------------------------------------------------------+
//| Checking if there is a mid line bullish crossing.                |
//+------------------------------------------------------------------+
bool HasMidLineBullishCrossing()
{
    int shift = AlertCandle == ALERT_PREVIOUS_CANDLE ? 1 : 0;
    int index = RatesTotal - 1 - shift;

    return ((iOpen(_Symbol, PERIOD_CURRENT, shift) < MidBuffer[index]) &&
           (iClose(_Symbol, PERIOD_CURRENT, shift) > MidBuffer[index]));
}

//+------------------------------------------------------------------+
//| Checking if there is a mid line bearish crossing.                |
//+------------------------------------------------------------------+
bool HasMidLineBearishCrossing()
{
    int shift = AlertCandle == ALERT_PREVIOUS_CANDLE ? 1 : 0;
    int index = RatesTotal - 1 - shift;

    return ((iOpen(_Symbol, PERIOD_CURRENT, shift) > MidBuffer[index]) &&
           (iClose(_Symbol, PERIOD_CURRENT, shift) < MidBuffer[index]));
}

//+------------------------------------------------------------------+
//| Checking whether a candle closes inside the resistance area.     |
//+------------------------------------------------------------------+
bool HasCandleCloseInsideResistance()
{
    int shift = AlertCandle == ALERT_PREVIOUS_CANDLE ? 1 : 0;
    int index = RatesTotal - 1 - shift;

    return ((iOpen(_Symbol, PERIOD_CURRENT, shift) < ResistanceBuffer[index]) &&
           (iClose(_Symbol, PERIOD_CURRENT, shift) > ResistanceBuffer[index]) &&
           (iClose(_Symbol, PERIOD_CURRENT, shift) < UpBuffer[index]));
}

//+------------------------------------------------------------------+
//| Checking whether a candle closes inside the support area.        |
//+------------------------------------------------------------------+
bool HasCandleCloseInsideSupport()
{
    int shift = AlertCandle == ALERT_PREVIOUS_CANDLE ? 1 : 0;
    int index = RatesTotal - 1 - shift;

    return ((iOpen(_Symbol, PERIOD_CURRENT, shift) > SupportBuffer[index]) &&
           (iClose(_Symbol, PERIOD_CURRENT, shift) < SupportBuffer[index]) &&
           (iClose(_Symbol, PERIOD_CURRENT, shift) > DownBuffer[index]));
}

void ResetGlobalVariables()
{
    IsMidLineBullishCrossing = false;
    IsMidLineBearishCrossing = false;
    IsCandleCloseInsideResistance = false;
    IsCandleCloseInsideSupport = false;
}

void RefreshGlobalVariables()
{
    IsMidLineBullishCrossing = HasMidLineBullishCrossing();
    IsMidLineBearishCrossing = HasMidLineBearishCrossing();
    IsCandleCloseInsideResistance = HasCandleCloseInsideResistance();
    IsCandleCloseInsideSupport = HasCandleCloseInsideSupport();
}
//+------------------------------------------------------------------+

Comments