CandleWicksDisplay

Price Data Components
Series array that contains the highest prices of each barSeries array that contains open prices of each barSeries array that contains close prices for each bar
Miscellaneous
It plays sound alertsIt sends emails
0 Views
0 Downloads
0 Favorites
CandleWicksDisplay
#property strict
#property description "Displays the candle wicks' length above and below the candles."
#property description "Alerts when the candle's wick (shadow) reaches a certain length."
#property description "You can choose whether to use pips or %."
#property description "Email settings are under Tools->Options->Email."
#property description "Notifications settings are under Tools->Options->Notifications."

// The indicator uses only objects for display, but the line below is required for it to work.
#property indicator_chart_window
#property indicator_plots 0

enum PipsPerc
{
    Pips,
    Percentage
};

input PipsPerc Units = Pips; // Standard pips or percentage points.
input int DisplayWickLimit = 5; // DisplayWickLimit in standard pips or %.
input color DisplayHighWickColorBullish = clrRed;
input color DisplayHighWickColorBearish = clrFireBrick;
input color DisplayLowWickColorBullish = clrLimeGreen;
input color DisplayLowWickColorBearish = clrGreen;
input int UpperWickLimit = 10; // UpperWickLimit in broker pips or % for alerts.
input int LowerWickLimit = 10; // LowerWickLimit in broker pips or % for alerts.
input bool WaitForClose = true; // WaitForClose - Wait for a candle to close before checking wicks' length?
// If > 0 and <= 100, displays length only for bars where Open & Close within top or bottom % of candle.
input int TopBottomPercent = 0; // TopBottomPercent: Body within top/bottom percentage part.
input bool LessThan = false; // LessThan: display wicks lower than DisplayWickLimit.
input bool SoundAlert = false;
input bool VisualAlert = false;
input bool EmailAlert = false;
input bool NotificationAlert = false;
input bool AlertOnLimit = false;
input bool AlertOnEqualTopBottomWicks = false;
input bool IgnoreAlertsOnFirstRun = true;
input string FontFace = "Verdana";
input int FontSize = 10;
input int MaxCandles = 500;
input bool UseRawPips = false; // UseRawPips: if true, will use points instead of pips.
input bool ShowAverageWickSize = false; // Show average wick size
input int AvgCandles = 50; // AvgBars: how many candles to calculate the average for.
input color AverageWickSizeColor = clrLightGray;
input ENUM_BASE_CORNER Corner = CORNER_LEFT_UPPER;
input int Distance_X = 3;
input int Distance_Y = 12;
input bool DrawTextAsBackground = false; // DrawTextAsBackground: if true, the text will be drawn as background.
input bool RemoveOldLabels = false; // RemoveOldLabels: Delete labels beyond MaxCandles?
input string ObjectPrefix = "CWLD-";

// Global variables.
datetime AlertDone; // Time of the bar of the last alert.
double Poin; // Traditional point value.
bool CanDoAlerts; // Fuse to prevent alerts on initialization or timeframe change.

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
void OnInit()
{
    if (ShowAverageWickSize)
    {
        // Average for upper wicks.
        ObjectCreate(ChartID(), ObjectPrefix + "Upper", OBJ_LABEL, 0, 0, 0);
        ObjectSetString(ChartID(), ObjectPrefix + "Upper", OBJPROP_TEXT, "");
        ObjectSetInteger(ChartID(), ObjectPrefix + "Upper", OBJPROP_FONTSIZE, FontSize);
        ObjectSetString(ChartID(), ObjectPrefix + "Upper", OBJPROP_FONT, FontFace);
        ObjectSetInteger(ChartID(), ObjectPrefix + "Upper", OBJPROP_COLOR, AverageWickSizeColor);
        ObjectSetInteger(ChartID(), ObjectPrefix + "Upper", OBJPROP_CORNER, Corner);
        ObjectSetInteger(ChartID(), ObjectPrefix + "Upper", OBJPROP_XDISTANCE, Distance_X);
        ObjectSetInteger(ChartID(), ObjectPrefix + "Upper", OBJPROP_YDISTANCE, Distance_Y);
        ObjectSetInteger(ChartID(), ObjectPrefix + "Upper", OBJPROP_BACK, DrawTextAsBackground);
        // Average for lower wicks.
        ObjectCreate(ChartID(), ObjectPrefix + "Lower", OBJ_LABEL, 0, 0, 0);
        ObjectSetString(ChartID(), ObjectPrefix + "Lower", OBJPROP_TEXT, "");
        ObjectSetInteger(ChartID(), ObjectPrefix + "Lower", OBJPROP_FONTSIZE, FontSize);
        ObjectSetString(ChartID(), ObjectPrefix + "Lower", OBJPROP_FONT, FontFace);
        ObjectSetInteger(ChartID(), ObjectPrefix + "Lower", OBJPROP_COLOR, AverageWickSizeColor);
        ObjectSetInteger(ChartID(), ObjectPrefix + "Lower", OBJPROP_CORNER, Corner);
        ObjectSetInteger(ChartID(), ObjectPrefix + "Lower", OBJPROP_XDISTANCE, Distance_X);
        ObjectSetInteger(ChartID(), ObjectPrefix + "Lower", OBJPROP_YDISTANCE, Distance_Y + 12);
        ObjectSetInteger(ChartID(), ObjectPrefix + "Lower", OBJPROP_BACK, DrawTextAsBackground);
        // Average for all wicks.
        ObjectCreate(ChartID(), ObjectPrefix + "All", OBJ_LABEL, 0, 0, 0);
        ObjectSetString(ChartID(), ObjectPrefix + "All", OBJPROP_TEXT, "");
        ObjectSetInteger(ChartID(), ObjectPrefix + "All", OBJPROP_FONTSIZE, FontSize);
        ObjectSetString(ChartID(), ObjectPrefix + "All", OBJPROP_FONT, FontFace);
        ObjectSetInteger(ChartID(), ObjectPrefix + "All", OBJPROP_COLOR, AverageWickSizeColor);
        ObjectSetInteger(ChartID(), ObjectPrefix + "All", OBJPROP_CORNER, Corner);
        ObjectSetInteger(ChartID(), ObjectPrefix + "All", OBJPROP_XDISTANCE, Distance_X);
        ObjectSetInteger(ChartID(), ObjectPrefix + "All", OBJPROP_YDISTANCE, Distance_Y + 24);
        ObjectSetInteger(ChartID(), ObjectPrefix + "All", OBJPROP_BACK, DrawTextAsBackground);
    }
    
    Poin = _Point;
    //Checking for unconventional Point digits number
    if ((!UseRawPips) && (((_Point == 0.00001) || (_Point == 0.001)))) Poin *= 10;
    if (!WaitForClose) CanDoAlerts = false;
    else CanDoAlerts = true;
}

void OnDeinit(const int reason)
{
    ObjectsDeleteAll(ChartID(), ObjectPrefix);
}

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
    // Redraw wick labels.
    if (id == CHARTEVENT_CHART_CHANGE) RedrawVisibleWickLabels();
}

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(Open, true);
    ArraySetAsSeries(High, true);
    ArraySetAsSeries(Low, true);
    ArraySetAsSeries(Close, true);

    string name, length;
    bool DoAlert = false;
    int index = 0;

    if (WaitForClose) index = 1;

    int counted_bars = prev_calculated;
    if (counted_bars > 0) counted_bars--;
    int limit = rates_total - counted_bars;
    if ((MaxCandles != 0) && (limit > MaxCandles)) limit = MaxCandles;

    for (int i = 0; i < limit; i++)
    {
        if ((TopBottomPercent > 0) && (TopBottomPercent <= 100))
        {
            double percent = (High[i] - Low[i]) * TopBottomPercent / 100;
            if (!(((Open[i] >= NormalizeDouble(High[i] - percent, _Digits)) && (Close[i] >= NormalizeDouble(High[i] - percent, _Digits))) ||
                  ((Open[i] <= NormalizeDouble(Low[i]  + percent, _Digits)) && (Close[i] <= NormalizeDouble(Low[i]  + percent, _Digits))))) continue;
        }

        if ((Units == Percentage) && (High[i] == Low[i])) continue; // Avoid zero-divide error.

        // Upper wick length display.
        if (
            ((!LessThan) &&
            (((Units == Pips) && (NormalizeDouble(High[i] - MathMax(Open[i], Close[i]), _Digits) >= NormalizeDouble(DisplayWickLimit * Poin, _Digits)))
            ||
             ((Units == Percentage) && ((High[i] - MathMax(Open[i], Close[i])) / (High[i] - Low[i]) * 100 >= DisplayWickLimit)))) ||
            ((LessThan) &&
            (((Units == Pips) && (NormalizeDouble(High[i] - MathMax(Open[i], Close[i]), _Digits) <= NormalizeDouble(DisplayWickLimit * Poin, _Digits)))
            ||
             ((Units == Percentage) && ((High[i] - MathMax(Open[i], Close[i])) / (High[i] - Low[i]) * 100 <= DisplayWickLimit))))
        )
        {
            name = GenerateObjectName("Red-", Time[i]);
            if (Units == Pips) length = DoubleToString(MathRound((High[i] - MathMax(Open[i], Close[i])) / Poin), 0);
            else if (Units == Percentage) length = DoubleToString(MathRound((High[i] - MathMax(Open[i], Close[i])) / (High[i] - Low[i]) * 100), 0);
            if (ObjectFind(0, name) != -1) ObjectDelete(0, name);
            color object_color;
            if (Close[i] >= Open[i]) object_color = DisplayHighWickColorBullish;
            else object_color = DisplayHighWickColorBearish;
            if (object_color != clrNONE)
            {
                ObjectCreate(0, name, OBJ_TEXT, 0, Time[i], High[i]);
                ObjectSetString(0, name, OBJPROP_TEXT, length);
                ObjectSetInteger(0, name, OBJPROP_FONTSIZE, FontSize);
                ObjectSetString(0, name, OBJPROP_FONT, FontFace);
                ObjectSetInteger(0, name, OBJPROP_COLOR, object_color);
                int visible_bars = (int)ChartGetInteger(0, CHART_VISIBLE_BARS);
                int first_bar = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR);
                int last_bar = first_bar - visible_bars + 1;
                if ((i <= first_bar) && (i >= last_bar)) RedrawOneWickLabel(i, last_bar);
                ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_UPPER);
                ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
                ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
            }
        }
        else // Remove tick labels that are no longer needed.
        {
            name = GenerateObjectName("Red-", Time[i]);
            if (ObjectFind(0, name) != -1) ObjectDelete(0, name);
        }
        // Lower wick length display.
        if (
            ((!LessThan) &&
            (((Units == Pips) && (NormalizeDouble(MathMin(Open[i], Close[i]) - Low[i], _Digits) >= NormalizeDouble(DisplayWickLimit * Poin, _Digits)))
            ||
            ((Units == Percentage) && ((MathMin(Open[i], Close[i]) - Low[i]) / (High[i] - Low[i]) * 100 >= DisplayWickLimit)))) ||
            ((LessThan) &&
            (((Units == Pips) && (NormalizeDouble(MathMin(Open[i], Close[i]) - Low[i], _Digits) <= NormalizeDouble(DisplayWickLimit * Poin, _Digits)))
            ||
            ((Units == Percentage) && ((MathMin(Open[i], Close[i]) - Low[i]) / (High[i] - Low[i]) * 100 <= DisplayWickLimit))))
        )
        {
            name = GenerateObjectName("Green-", Time[i]);
            if (Units == Pips) length = DoubleToString(MathRound((MathMin(Open[i], Close[i]) - Low[i]) / Poin), 0);
            else if (Units == Percentage) length = DoubleToString(MathRound((MathMin(Open[i], Close[i]) - Low[i]) / (High[i] - Low[i]) * 100), 0);
            if (ObjectFind(0, name) != -1) ObjectDelete(0, name);
            color object_color;
            if (Close[i] >= Open[i]) object_color = DisplayLowWickColorBullish;
            else object_color = DisplayLowWickColorBearish;
            if (object_color != clrNONE)
            {
                ObjectCreate(0, name, OBJ_TEXT, 0, Time[i], Low[i]);
                ObjectSetString(0, name, OBJPROP_TEXT, length);
                ObjectSetInteger(0, name, OBJPROP_FONTSIZE, FontSize);
                ObjectSetString(0, name, OBJPROP_FONT, FontFace);
                ObjectSetInteger(0, name, OBJPROP_COLOR, object_color);
                ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_UPPER);
                ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
                ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
            }
        }
        else // Remove tick labels that are no longer needed.
        {
            name = GenerateObjectName("Green-", Time[i]);
            if (ObjectFind(0, name) != -1) ObjectDelete(0, name);
        }
    }

    if ((RemoveOldLabels) && (MaxCandles > 0)) ClearOldLabels();

    if (ShowAverageWickSize)
    {
        limit = rates_total;
        if ((AvgCandles != 0) && (limit > AvgCandles)) limit = AvgCandles;
        double upper = 0;
        double lower = 0;
        for (int i = 0; i < limit; i++)
        {
            upper += High[i] - MathMax(Open[i], Close[i]);
            lower += MathMin(Open[i], Close[i]) - Low[i];
        }
        ObjectSetString(ChartID(), ObjectPrefix + "Upper", OBJPROP_TEXT, "Avg Upper: " + DoubleToString(upper / limit / Poin, 1));
        ObjectSetString(ChartID(), ObjectPrefix + "Lower", OBJPROP_TEXT, "Avg Lower: " + DoubleToString(lower / limit / Poin, 1));
        ObjectSetString(ChartID(), ObjectPrefix + "All", OBJPROP_TEXT, "Avg All: " + DoubleToString((upper + lower) / (limit * 2) / Poin, 1));
    }

    // Already sent an alert for this candle or ignoring the initial alert when attaching the indicator first time.
    // We probably also do not want the alert to sound on index = 0 when IgnoreAlertsOnFirstRun is false but we could
    // get out-of-nowhere alerts during chart data updates.
    if ((AlertDone == Time[index]) || ((counted_bars == 0) && ((IgnoreAlertsOnFirstRun) || (index == 0))))
    {
        // For the case of IgnoreAlertsOnFirstRun.
        if (index != 0) AlertDone = Time[index];
        return(rates_total);
    }

    if ((Units == Percentage) && (High[index] == Low[index])) return(rates_total); // Avoid zero-divide error.

    if (AlertOnLimit)
    {
        bool UpperPipsCondition = ((Units == Pips) && (((!LessThan) && (NormalizeDouble(High[index] - MathMax(Open[index], Close[index]), _Digits) >= NormalizeDouble(UpperWickLimit * Point(), _Digits))) || ((LessThan) && (NormalizeDouble(High[index] - MathMax(Open[index], Close[index]), _Digits) <= NormalizeDouble(UpperWickLimit * Point(), _Digits)))));
        bool UpperPercentageCondition = ((Units == Percentage) && (((!LessThan) && (High[index] - MathMax(Open[index], Close[index])) / (High[index] - Low[index]) * 100 >= UpperWickLimit) || ((LessThan) && (High[index] - MathMax(Open[index], Close[index])) / (High[index] - Low[index]) * 100 <= UpperWickLimit)));
        bool LowerPipsCondition = ((Units == Pips) && (((!LessThan) && (NormalizeDouble(MathMin(Open[index], Close[index]) - Low[index], _Digits) >= NormalizeDouble(LowerWickLimit * Point(), _Digits))) || ((LessThan) && (NormalizeDouble(MathMin(Open[index], Close[index]) - Low[index], _Digits) <= NormalizeDouble(LowerWickLimit * Point(), _Digits)))));
        bool LowerPercentageCondition = ((Units == Percentage) && (((!LessThan) && (MathMin(Open[index], Close[index]) - Low[index]) / (High[index] - Low[index]) * 100 >= LowerWickLimit) || ((LessThan) && (MathMin(Open[index], Close[index]) - Low[index]) / (High[index] - Low[index]) * 100 <= LowerWickLimit)));

        // Adjust for color == none disabling:
        if (((Close[index] >= Low[index]) && (DisplayHighWickColorBullish == clrNONE)) || ((Close[index] < Low[index]) && (DisplayHighWickColorBearish == clrNONE)))
        {
            UpperPipsCondition = false;
            UpperPercentageCondition = false;
        }
        if (((Close[index] >= Low[index]) && (DisplayLowWickColorBullish == clrNONE)) || ((Close[index] < Low[index]) && (DisplayLowWickColorBearish == clrNONE)))
        {
            LowerPipsCondition = false;
            LowerPercentageCondition = false;
        }

        if ((UpperPipsCondition) || (UpperPercentageCondition) || (LowerPipsCondition) || (LowerPercentageCondition))
            DoAlert = true;
        else CanDoAlerts = true;

        if ((DoAlert) && (CanDoAlerts))
        {
            string dir = "";
            if ((UpperPipsCondition) || (UpperPercentageCondition)) dir = "Upper";
            if ((LowerPipsCondition) || (LowerPercentageCondition))
            {
                if (dir == "Upper") dir += "+";
                dir += "Lower";
            }

            string units = "";
            if (Units == Pips) units = "pips";
            if (Units == Percentage) units = "%";

            if (VisualAlert) Alert(dir + " " + units + " wick limit reached!");
            if (SoundAlert) PlaySound("alert.wav");
            if (EmailAlert) SendMail("CandleWick Alert", Symbol() + " " + TimeToString(Time[index]) + " " + dir + " " + units + " - wick limit reached!");
            if (NotificationAlert) SendNotification(Symbol() + " " + TimeToString(Time[index]) + " " + dir + " " + units + " - wick limit reached!");
            AlertDone = Time[index];
        }
    }

    if (AlertOnEqualTopBottomWicks)
    {
        bool EqualWicksCondition = (High[index] - MathMax(Open[index], Close[index]) == MathMin(Open[index], Close[index]) - Low[index]);

        if (EqualWicksCondition)
        {
            if (ObjectFind(ChartID(), GenerateObjectName("Green-", Time[index])) < 0) return rates_total; // No object means that the limit conditions aren't satisfied, so no alert is needed.
            if (VisualAlert) Alert(Symbol() + " " + TimeToString(Time[index]) + " Upper wick equals lower wick!");
            if (SoundAlert) PlaySound("alert.wav");
            if (EmailAlert) SendMail("CandleWick Alert", Symbol() + " " + TimeToString(Time[index]) + " Upper wick equals lower wick!");
            if (NotificationAlert) SendNotification(Symbol() + " " + TimeToString(Time[index]) + " " " Upper wick equals lower wick!");
            AlertDone = Time[index];
        }
    }

    return(rates_total);
}

// Required only for High wicks.
void RedrawVisibleWickLabels()
{
    int visible_bars = (int)ChartGetInteger(0, CHART_VISIBLE_BARS);
    int first_bar = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR);
    int last_bar = first_bar - visible_bars + 1;

    // Process all bars on the current screen.
    for (int i = first_bar; i >= last_bar; i--) RedrawOneWickLabel(i, last_bar);
}

void RedrawOneWickLabel(const int i, const int last_bar)
{
    int x, y, cw;
    uint w, h;
    datetime t;
    double p, high;
    string length, name;

    // For further use.
    high = iHigh(NULL, 0, i);

    name = GenerateObjectName("Red-", iTime(NULL, Period(), i));
    if (ObjectFind(0, name) == -1) return;
    // Needed only for y; x is used as a dummy.
    ChartTimePriceToXY(0, 0, iTime(NULL, Period(), last_bar), high, x, y);
    // Get the height of the text based on font and its size. Negative because OS-dependent, *10 because set in 1/10 of pt.
    TextSetFont(FontFace, FontSize * -10);
    length = DoubleToString(MathRound((high - MathMax(iOpen(NULL, 0, i), iClose(NULL, 0, i))) / Poin), 0);
    TextGetSize(length, w, h);

    // Normal shift of the text upward will result in negative Y coordinate and the price level will be moved down.
    // To work around this, we have to calculate the "price difference" of the necessary "pixel difference" (price height of h).
    // Then, add it to the High of the bar.
    if (y - (int)h - 2 < 0)
    {
        if (!ChartXYToTimePrice(0, x, y + h + 2, cw, t, p)) Print("Error: ", GetLastError());
        double diff = high - p;
        p = high + diff;
    }
    // Normal way.
    else ChartXYToTimePrice(0, x, y - h - 2, cw, t, p);

    ObjectSetDouble(0, name, OBJPROP_PRICE, p);
}

string GenerateObjectName(const string prefix, const datetime time)
{
    return(ObjectPrefix + prefix + TimeToString(time));
}

void ClearOldLabels()
{
    if (MaxCandles >= iBars(Symbol(), Period())) return; // Nothing to delete yet.
    
    int objects_total = ObjectsTotal(0, -1, OBJ_TEXT);
    
    for (int i = objects_total - 1; i >= 0; i--)
    {
        string object_name = ObjectName(0, i, -1, OBJ_TEXT);
        datetime object_time = (datetime)ObjectGetInteger(0, object_name, OBJPROP_TIME, 0);
        if (object_time <= iTime(Symbol(), Period(), MaxCandles)) ObjectDelete(0, object_name);
    }
}
//+------------------------------------------------------------------+

Comments