#property copyright "ArteInVolo"
#property link      "https://vk.com/tradelearning"
#property version   "1.00"

#include <MovingAverages.mqh>

#property indicator_separate_window
#property indicator_buffers 8
#property indicator_plots   4

#property indicator_label1  "Volume"
#property indicator_type1   DRAW_COLOR_HISTOGRAM
#property indicator_color1  clrDeepSkyBlue, clrRed, clrLightGray, clrLime, clrBlack, clrDarkViolet
#property indicator_style1  STYLE_SOLID
#property indicator_width1  2

#property indicator_label2  "AverageVolume"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrDeepSkyBlue
#property indicator_style2  STYLE_DOT
#property indicator_width2  1

#property indicator_label3  "Delta"
#property indicator_type3   DRAW_COLOR_HISTOGRAM
#property indicator_color3  clrDarkOrange
#property indicator_style3  STYLE_SOLID
#property indicator_width3  2

#property indicator_label4  "AverageDelta"
#property indicator_type4   DRAW_LINE
#property indicator_color4  Black
#property indicator_style4  STYLE_SOLID
#property indicator_width4  1

#define NATR 5

// *** Input data *** //

input int InpATRRange = 5;
input int InpOSRPeriodMA = 30;
input int InpDeltaPeriodMA = 30;
input int InpLookBack = 20;
input double InpVolumeFactor = 1;
input bool InpUseVolumeMA = false;
input bool InpUseClosePrice = true;
input bool InpUseCumulativeDelta = true;
input color InpFontColor = Black;
input color InpUpTrendColor = Lime;
input color InpDownTrendColor = OrangeRed;
input ENUM_APPLIED_VOLUME InpVolumeType = VOLUME_REAL;

// *** Indicator buffers *** //

double VolumeBuffer[];
double VolumeColors[];
double VolumeCalculations[];
double DeltaBuffer[];
double DeltaColors[];
double DeltaCalculations[];
double AverageVolumeBuffer[];
double AverageDeltaBuffer[];

// *** Global *** //

int Chart = 0;
int FontSize = 13;
int XDistance = 5;
int YDistance = 5;
int DeltaFactor = 2;
string FontName = "Trebuchet MS";

// *** Constants *** //

enum VolumeColorIndex { NEUTRAL, CLIMAX_HIGH, LOW, CHURN, CLIMAX_LOW, CLIMAX_CHURN };

// *** Functions *** //

int OnInit()
{
    SetIndexBuffer(0, VolumeBuffer, INDICATOR_DATA);
    SetIndexBuffer(1, VolumeColors, INDICATOR_COLOR_INDEX);
    SetIndexBuffer(2, AverageVolumeBuffer, INDICATOR_DATA);
    SetIndexBuffer(3, DeltaBuffer, INDICATOR_DATA);
    SetIndexBuffer(4, DeltaColors, INDICATOR_COLOR_INDEX);
    SetIndexBuffer(5, AverageDeltaBuffer, INDICATOR_DATA);
    SetIndexBuffer(6, VolumeCalculations, INDICATOR_CALCULATIONS);
    SetIndexBuffer(7, DeltaCalculations, INDICATOR_CALCULATIONS);
    
    ArrayInitialize(VolumeBuffer, 0);
    ArrayInitialize(VolumeColors, 0);
    ArrayInitialize(VolumeCalculations, 0);
    ArrayInitialize(AverageVolumeBuffer, 0);
    ArrayInitialize(DeltaBuffer, 0);
    ArrayInitialize(DeltaColors, 0);
    ArrayInitialize(DeltaCalculations, 0);
    ArrayInitialize(AverageDeltaBuffer, 0);

    IndicatorSetString(INDICATOR_SHORTNAME, "VsaVolumes");
    IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
    
    Chart = ChartWindowFind();
    
    return(INIT_SUCCEEDED);
}

int OnCalculate(
    const int bars, 
    const int counted, 
    const datetime &time[], 
    const double &open[], 
    const double &high[], 
    const double &low[], 
    const double &close[], 
    const long &tickVolume[], 
    const long &volume[], 
    const int &spread[])
{
    int start = MathMax(counted - 1, 1);

    if (InpVolumeType == VOLUME_TICK)
    {
        CalculateVolume(start, bars, tickVolume, high, low, open, close);
        CalculateDelta(start, bars, tickVolume, close);
    }
    else
    {
        CalculateVolume(start, bars, volume, high, low, open, close);
        CalculateDelta(start, bars, volume, close);
    }

    ShowPanel(close);
    PriceRange();
    TrendDetector();
    SignalDescription();
    BarTimer(time);
    Spread();

    return(bars);
}

int CalculateVolume(int start, int bars, const long &volume[], const double &high[], const double &low[], const double &open[], const double &close[])
{
    for (int i = start; i < bars; i++)
    {
        int volMinIndex = ArrayMinimum(volume, i - InpLookBack, InpLookBack + 1);

        if (volMinIndex < 0)
        {
            volMinIndex = 0;
        }

        double priceMin = low[i], priceMax = high[i];
    
        if (InpUseClosePrice)
        {
            priceMin = MathMin(open[i], close[i]);
            priceMax = MathMax(open[i], close[i]);
        }
    
        double volClimaxCurrent = volume[i] * (priceMax - priceMin), volChurnCurrent = 0;
        
        if (volClimaxCurrent > 0)
        {
            volChurnCurrent = volume[i] / (priceMax - priceMin);
        }
       
        double volClimaxLocal = 0, volChurnLocal = 0, climax = 0, churn = 0;

        for (int n = i; n > i - InpLookBack && n > 0; n--)
        {
            double priceMinLocal = low[n], priceMaxLocal = high[n];
    
            if (InpUseClosePrice)
            {
                priceMinLocal = MathMin(open[n], close[n]);
                priceMaxLocal = MathMax(open[n], close[n]);
            }
        
            climax = volume[n] * (priceMaxLocal - priceMinLocal); 
            
            // Previous maximal price range can be found here
            
            if (climax >= volClimaxLocal)
            {
                volClimaxLocal = climax;
            }
            
            // Previous consolidation can be found here
            
            if (climax > 0)
            {           
                churn = volume[n] / (priceMaxLocal - priceMinLocal);
                
                if (churn >= volChurnLocal) 
                {
                    volChurnLocal = churn; 
                }
            }
        }

        // By default volume is neutral

        VolumeColors[i] = NEUTRAL;     
        VolumeCalculations[i] = double(volume[i]);
        VolumeBuffer[i] = double(MathPow(volume[i], InpVolumeFactor));
        
        // volume is low and all previous values were higher
        
        if (i == volMinIndex)
        {
            VolumeColors[i] = LOW;
        }
        
        // When volume is equal to one seen before mark it as accummulation / distribution - profit is taken
        
        if (volChurnCurrent == volChurnLocal)
        {
            VolumeColors[i] = CHURN;
        }
        
        // When volume is higher than all previous and price is going up - start or end of the up trend
        
        if (volClimaxCurrent == volClimaxLocal && close[i] > (priceMax + priceMin) / 2)
        {
            VolumeColors[i] = CLIMAX_HIGH;
        }
        
        // When volume is extra high and price is not changing - absolute consolidation or fast accummulation / distribution
        
        if (volClimaxCurrent == volClimaxLocal && volChurnCurrent == volChurnLocal)
        {
            VolumeColors[i] = CLIMAX_CHURN;
        }
        
        // When volume is higher than all previous and price is going down - start or end of the down trend
        
        if (volClimaxCurrent == volClimaxLocal && close[i] < (priceMax + priceMin) / 2)
        {
            VolumeColors[i] = CLIMAX_LOW;
        }
    }

    ArrayFill(AverageVolumeBuffer, start, bars - start, 0);

    if (InpUseVolumeMA)
    {
        ExponentialMAOnBuffer(bars, start + 1, 1, InpOSRPeriodMA, VolumeBuffer, AverageVolumeBuffer);
    }

    return(0);
}

int CalculateDelta(int start, int bars, const long &volume[], const double &close[])
{
    DeltaBuffer[0] = 0;

    for (int i = start; i < bars; i++)
    {
        double extraVolume = MathPow(volume[i], InpVolumeFactor);
    
        DeltaBuffer[i] = double(close[i] > close[i - 1] ? extraVolume : -extraVolume);
        DeltaBuffer[i] = InpUseCumulativeDelta ? DeltaBuffer[i] : DeltaBuffer[i] / DeltaFactor;
        DeltaCalculations[i] = double(close[i] > close[i - 1] ? DeltaBuffer[i - 1] + extraVolume : DeltaBuffer[i - 1] - extraVolume);
    }
    
    ArrayFill(AverageDeltaBuffer, start, bars - start, 0);
    
    if (InpUseCumulativeDelta)
    {
        ExponentialMAOnBuffer(bars, start + 1, 1, InpDeltaPeriodMA, DeltaBuffer, AverageDeltaBuffer);
        ArrayFill(DeltaBuffer, 0, bars, 0);
    }
    
    return(0);
}
    
int ShowPanel(const double &close[])
{
    // Show delta values and compare it with previous values to find new players on market

    string volumeDeltaDescription = "BELOW";
    string volumeDelta = StringFormat("DELTA : %s", DoubleToString(DeltaCalculations[ArraySize(DeltaCalculations) - 1] / MathPow(PeriodSeconds(), 5), 0));
    color volumeDeltaColor = InpDownTrendColor;
    
    if (DeltaCalculations[ArraySize(DeltaCalculations) - 1] > 0) 
    {
        volumeDeltaDescription = "ABOVE";
        volumeDeltaColor = InpUpTrendColor;
    }
    
    ShowLabel("VolumeDelta", volumeDelta, YDistance + 60, FontSize, volumeDeltaColor);
    ShowLabel("VolumeDeltaDescription", volumeDeltaDescription + " AVERAGE", YDistance + 80, FontSize - 5, InpFontColor);
    
    // Show volume and compare it with previous values to know whether volume is growing or falling
    
    color volumeAverageColor = InpDownTrendColor;   
    string volumeAverageDescription = "BELOW";
    string volumeAverage = StringFormat("VOLUME : %s", DoubleToString(VolumeCalculations[ArraySize(VolumeCalculations) - 1], 0));
    
    if (IsRising(VolumeCalculations)) 
    {
        volumeAverageDescription = "ABOVE";
        volumeAverageColor = InpUpTrendColor;
    }
    
    ShowLabel("VolumeAverage", volumeAverage, YDistance + 95, FontSize, volumeAverageColor);
    ShowLabel("VolumeAverageDescription", volumeAverageDescription + " AVERAGE", YDistance + 115, FontSize - 5, InpFontColor);

    return(0);
}

bool IsRising(const double &list[])
{
    int count = ArraySize(list);

    for (int i = count - 1; i > count - InpLookBack && i > 0; i--) 
    {
        if (MathAbs(list[count - 1]) < MathAbs(list[i]))
        {
            return(false);
        }
    }
    
    return(true);
}

void ShowLabel(const string name, const string value, const int positionTop, const int fontSize, const color fontColor)
{
    if (ObjectFind(ChartID(), name) < 0)
    {
        ObjectCreate(ChartID(), name, OBJ_LABEL, Chart, 0, 0);
    }

    ObjectSetInteger(ChartID(), name, OBJPROP_CORNER,CORNER_RIGHT_UPPER);
    ObjectSetInteger(ChartID(), name, OBJPROP_ANCHOR, ANCHOR_RIGHT_UPPER);
    ObjectSetInteger(ChartID(), name, OBJPROP_YDISTANCE, positionTop);
    ObjectSetInteger(ChartID(), name, OBJPROP_XDISTANCE, XDistance);
    ObjectSetInteger(ChartID(), name, OBJPROP_FONTSIZE, fontSize);
    ObjectSetInteger(ChartID(), name, OBJPROP_COLOR, fontColor);
    ObjectSetString(ChartID(), name, OBJPROP_FONT, FontName);
    ObjectSetString(ChartID(), name, OBJPROP_TEXT, value);
}

int SignalDescription()
{
    int volumeColor = int(VolumeColors[ArraySize(VolumeColors) - 2]);
    
    switch (volumeColor)
    {
        case NEUTRAL : ShowLabel("SignalDescription", "NO SIGNAL - NEUTRAL", YDistance + 130, FontSize - 6, InpFontColor); break;
        case LOW : ShowLabel("SignalDescription", "END OF UP/DOWN TREND - PULLBACK MID-TREND", YDistance + 130, FontSize - 6, InpFontColor); break;
        case CHURN : ShowLabel("SignalDescription", "END OF UP/DOWN TREND - PROFIT TAKING MID-TREND", YDistance + 130, FontSize - 6, InpFontColor); break;
        case CLIMAX_LOW : ShowLabel("SignalDescription", "START/END OF DOWN TREND - PULLBACK ON UP TREND", YDistance + 130, FontSize - 6, InpDownTrendColor); break;
        case CLIMAX_HIGH : ShowLabel("SignalDescription", "START/END OF UP TREND - PULLBACK ON DOWN TREND", YDistance + 130, FontSize - 6, InpUpTrendColor); break;
        case CLIMAX_CHURN : ShowLabel("SignalDescription", "TREND TOP OR BOTTOM - REVERSAL OR CONTINUATION", YDistance + 130, FontSize - 6, InpFontColor); break;
    }

    return(0);
}

int BarTimer(const datetime &Time[])
{
    int count = ArraySize(Time);
    int sec = int(TimeCurrent()) - int(Time[count - 1]);
    double pc = 100.0 * sec / PeriodSeconds(_Period);
    string barTimer = StringFormat("BAR : %s%%", DoubleToString(pc, 0));
   
    ShowLabel("BarTimer", barTimer, YDistance, FontSize - 5, InpFontColor);
    
    return(0);
}

int TrendDetector()
{
    double adx[];
    int adxHandle = iADX(_Symbol, _Period, InpOSRPeriodMA);

    CopyBuffer(adxHandle, 0, 0, InpATRRange, adx);
    ArraySetAsSeries(adx, true);

    ShowLabel("ADX", StringFormat("ADX : %s", DoubleToString(adx[0], _Digits)), YDistance + 30, FontSize - 5, InpFontColor);
    
    return(0);
}

int PriceRange()
{
    double range[];
    int rangeHandle = iATR(_Symbol, _Period, InpATRRange);

    CopyBuffer(rangeHandle, 0, 0, InpATRRange, range);
    ArraySetAsSeries(range, true);

    ShowLabel("ATRRange", StringFormat("ATR : %s", DoubleToString(range[0], _Digits)), YDistance + 15, FontSize - 5, InpFontColor);
    
    return(0);
}

int Spread()
{
    bool spreadFloat = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD_FLOAT);
    string spread = StringFormat("SPREAD %s : %I64d", spreadFloat ? "FLOATING" : "FIXED", SymbolInfoInteger(_Symbol, SYMBOL_SPREAD));
    
    ShowLabel("Spread", spread, YDistance + 45, FontSize - 5, InpFontColor);

    return(0);
}