Author: www.Wolfforex.com, 2025
Price Data Components
Series array that contains open time of each bar
0 Views
0 Downloads
0 Favorites
NVI
//+------------------------------------------------------------------+
//|                                            Negative Volume Index |
//|                                    Copyright © 2025 Wolfforex.com|
//|                                        https://www.wolfforex.com |
//+------------------------------------------------------------------+
#property copyright "www.Wolfforex.com, 2025"
#property version   "2.01"
#property strict

#property description "Negative Volume Index calculates only price changes accompanied by negative change in volume."
#property description "This implementation includes the following features:"
#property description " * Multi-timeframe (MTF) option"
#property description " * Positive Volume Index switch"

#property indicator_separate_window
#property indicator_buffers 2
#property indicator_plots 1
#property indicator_color1 clrRed
#property indicator_type1 DRAW_LINE
#property indicator_width1 2
#property indicator_label1 "NVI"

input int Shift = 0; // Indicator shift
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_CURRENT; // Timeframe
input bool Positive = false; // Positive Volume Index?
input ENUM_APPLIED_VOLUME VolumeType = VOLUME_TICK;

// Buffers:
double NVI[];
double UpperTFShift[];

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

void OnInit()
{
    IndicatorSetInteger(INDICATOR_DIGITS, 4);
    string name = "NVI";

    SetIndexBuffer(0, NVI, INDICATOR_DATA);
    SetIndexBuffer(1, UpperTFShift, INDICATOR_DATA);
    PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
    PlotIndexSetInteger(0, PLOT_SHIFT, Shift);

    // Setting values for the higher timeframe:
    Timeframe = InpTimeframe;
    if (InpTimeframe < Period())
    {
        Timeframe = (ENUM_TIMEFRAMES)Period();
    }
    else if (InpTimeframe > Period())
    {
        name += " @ " + EnumToString(Timeframe);
        StringReplace(name, "PERIOD_", "");
    }
    IndicatorSetString(INDICATOR_SHORTNAME, name);
    
    deltaHighTF = 0;
    if (Timeframe > Period())
    {
        deltaHighTF = Timeframe / Period();
    }
}

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 ((iBars(_Symbol, Timeframe) < 2) || (rates_total < 2)) return 0; // Not enough bars.

    int Upper_RT = iBars(_Symbol, Timeframe);

    // Starting position for calculations.
    int pos;

    pos = prev_calculated - 1 - deltaHighTF;
    if (pos < 0) // Pre-fill upper timeframe buffer.
    {
        pos = 0;
        if (Timeframe != Period())
        {
            for (int i = pos; i < rates_total && !IsStopped(); i++)
            {
                int index = rates_total - 1 - i;
                int shift = index;
                if (Timeframe != Period()) shift = iBarShift(_Symbol, Timeframe, iTime(_Symbol, PERIOD_CURRENT, index));
                UpperTFShift[i] = Upper_RT - 1 - shift;
            }
        }
        pos = 1; // Start from pre-oldest bar.
        NVI[0] = 1;
    }
    for (int i = pos; i < rates_total && !IsStopped(); i++)
    {
        int index = rates_total - 1 - i;
        int shift = index;
        if (Timeframe != Period())
        {
            shift = iBarShift(_Symbol, Timeframe, iTime(_Symbol, PERIOD_CURRENT, index));
            if (Upper_RT - 1 - shift == UpperTFShift[i - 1]) // If previous upper timeframe shift equals current, then current indicator values should be the same as previous. No need to re-calculate them.
            {
                NVI[i] = NVI[i - 1];
                UpperTFShift[i] = Upper_RT - 1 - shift;
                continue;
            }
        }

        long vol_cur, vol_prev;
        if (VolumeType == VOLUME_REAL)
        {
            vol_cur = volume[i];
            vol_prev = volume[i - 1];
        }
        else
        {
            vol_cur = tick_volume[i];
            vol_prev = tick_volume[i - 1];
        }

        if (((!Positive) && (vol_cur < vol_prev)) || ((Positive) && (vol_cur > vol_prev))) NVI[i] = NVI[i - 1] * close[i] / close[i - 1]; // If volume declined, apply the price change. Inverted for Positive Volume Index.
        else NVI[i] = NVI[i - 1]; // Otherwise, leave unchanged.

        UpperTFShift[i] = Upper_RT - 1 - shift;
    }

    return rates_total;
}
//+------------------------------------------------------------------+

Comments