/// <summary>
        /// Returns True if the index bar is a doji bar
        /// </summary>
        /// <param name="bars"></param>
        /// <param name="index">The bar index number in a market series</param>
        /// <returns>bool</returns>
        public static bool IsDojiBar(this Bars bars, int index)
        {
            double barBodyRange = bars.GetBarRange(index, true);
            double barRange     = bars.GetBarRange(index);

            double meanBarRange = bars.GetAverageBarRange(index - 1, 50);

            return(barRange < meanBarRange / 3 && barBodyRange / barRange < 0.5);
        }
        /// <summary>
        /// Returns True if the index bar is an engulfing bar
        /// </summary>
        /// <param name="bars"></param>
        /// <param name="index">The bar index number in a market series</param>
        /// <returns>bool</returns>
        public static bool IsEngulfingBar(this Bars bars, int index)
        {
            double barBodyRange     = bars.GetBarRange(index, true);
            double previousBarRange = bars.GetBarRange(index - 1);

            BarType barType         = bars.GetBarType(index);
            BarType previousBarType = bars.GetBarType(index - 1);

            return(barBodyRange > previousBarRange && barType != previousBarType);
        }
        /// <summary>
        /// Returns the minimum bar range in a market series
        /// </summary>
        /// <param name="bars"></param>
        /// <param name="index">The start bar index</param>
        /// <param name="periods">The number of previous bars</param>
        /// <param name="useBarBody">Use bar open and close price instead of high and low?</param>
        /// <returns>double</returns>
        public static double GetMinBarRange(this Bars bars, int index, int periods, bool useBarBody = false)
        {
            var minRange = double.MaxValue;

            for (var i = index; i >= index - periods; i--)
            {
                minRange = Math.Min(minRange, bars.GetBarRange(i, useBarBody: useBarBody));
            }

            return(minRange);
        }
        /// <summary>
        /// Returns the median bar range of x previous bars on a market series
        /// </summary>
        /// <param name="bars"></param>
        /// <param name="index">Bar index in market series, the calculation will begin from this bar</param>
        /// <param name="periods">The number of x previous bars or look back bars</param>
        /// <param name="useBarBody">Use bar open and close price instead of high and low?</param>
        /// <returns>double</returns>
        public static double GetMedianBarRange(this Bars bars, int index, int periods, bool useBarBody = false)
        {
            var barRanges = new List <double>();

            for (var iBarIndex = index; iBarIndex >= index - periods; iBarIndex--)
            {
                double iBarRange = bars.GetBarRange(iBarIndex, useBarBody);

                barRanges.Add(iBarRange);
            }

            return(barRanges.Median());
        }
        /// <summary>
        /// Returns the volume profile of x latest bars in a market series
        /// </summary>
        /// <param name="bars"></param>
        /// <param name="index">Last Bar Index</param>
        /// <param name="periods">Number of previous bars before provided index</param>
        /// <param name="symbol">The market series symbol</param>
        /// <returns>List<PriceVolume></returns>
        public static List <PriceLevel> GetVolumeProfile(this Bars bars, int index, int periods, Symbol symbol)
        {
            var result = new List <PriceLevel>();

            for (var i = index; i > index - periods; i--)
            {
                var barRange = bars.GetBarRange(i);

                var barVolume = bars.TickVolumes[i];

                if (barRange <= 0 || barVolume <= 0)
                {
                    continue;
                }

                var percentageAboveBarClose = (bars.HighPrices[i] - bars.ClosePrices[i]) / barRange;
                var percentageBelowBarClose = (bars.ClosePrices[i] - bars.LowPrices[i]) / barRange;

                var bullishVolume = barVolume * percentageBelowBarClose;
                var bearishVolume = barVolume * percentageAboveBarClose;

                var barRangeInPips = symbol.ToPips(barRange);

                var bullishVolumePerPips = bullishVolume / barRangeInPips;
                var bearishVolumePerPips = bearishVolume / barRangeInPips;

                for (var level = bars.LowPrices[i]; level <= bars.HighPrices[i]; level += symbol.PipSize)
                {
                    level = Math.Round(level, symbol.Digits);

                    var priceLevel = result.FirstOrDefault(pLevel => pLevel.Level == level);

                    if (priceLevel == null)
                    {
                        priceLevel = new PriceLevel
                        {
                            Level = level
                        };

                        result.Add(priceLevel);
                    }

                    priceLevel.BullishVolume += bullishVolumePerPips;
                    priceLevel.BearishVolume += bearishVolumePerPips;
                }
            }

            return(result);
        }
        /// <summary>
        /// Returns True if the index bar is a rejection bar
        /// </summary>
        /// <param name="bars"></param>
        /// <param name="index">The bar index number in a market series</param>
        /// <returns>bool</returns>
        public static bool IsRejectionBar(this Bars bars, int index)
        {
            double barBodyRange = bars.GetBarRange(index, true);
            double barRange     = bars.GetBarRange(index);

            BarType barType = bars.GetBarType(index);

            double meanBarRange = bars.GetAverageBarRange(index - 1, 50);

            if (barBodyRange / barRange < 0.3 && barRange > meanBarRange)
            {
                var barMiddle        = barRange * 0.5 + bars.LowPrices[index];
                var barFirstQuartile = barRange * 0.25 + bars.LowPrices[index];
                var barThirdQuartile = barRange * 0.75 + bars.LowPrices[index];

                if (bars.OpenPrices[index] > barMiddle && bars.ClosePrices[index] > barThirdQuartile && barType == BarType.Bullish ||
                    bars.OpenPrices[index] < barMiddle && bars.ClosePrices[index] < barFirstQuartile && barType == BarType.Bearish)
                {
                    return(true);
                }
            }

            return(false);
        }
        /// <summary>
        /// Returns the maximum bar range in a market series
        /// </summary>
        /// <param name="bars"></param>
        /// <param name="index">The start bar index</param>
        /// <param name="periods">The number of previous bars</param>
        /// <param name="barType">The type of bars</param>
        /// <param name="useBarBody">Use bar open and close price instead of high and low?</param>
        /// <returns>double</returns>
        public static double GetMaxBarRange(this Bars bars, int index, int periods, BarType barType, bool useBarBody = false)
        {
            var maxRange = double.MinValue;

            for (var i = index; i >= index - periods; i--)
            {
                if (bars.GetBarType(i) != barType)
                {
                    continue;
                }

                maxRange = Math.Max(maxRange, bars.GetBarRange(i, useBarBody: useBarBody));
            }

            return(maxRange);
        }
        /// <summary>
        /// Returns the average bar range of x previous bars on a market series
        /// </summary>
        /// <param name="bars"></param>
        /// <param name="index">Bar index in market series, the calculation will begin from this bar</param>
        /// <param name="periods">The number of x previous bars or look back bars</param>
        /// <param name="barType">The type of bars</param>
        /// <param name="useBarBody">Use bar open and close price instead of high and low?</param>
        /// <returns>double</returns>
        public static double GetAverageBarRange(this Bars bars, int index, int periods, BarType barType, bool useBarBody = false)
        {
            var barRanges = new List <double>();

            for (var iBarIndex = index; iBarIndex >= index - periods; iBarIndex--)
            {
                if (bars.GetBarType(iBarIndex) != barType)
                {
                    continue;
                }

                double iBarRange = bars.GetBarRange(iBarIndex, useBarBody);

                barRanges.Add(iBarRange);
            }

            return(barRanges.Average());
        }
 /// <summary>
 /// Returns a bar volume strength
 /// </summary>
 /// <param name="bars"></param>
 /// <param name="index">Bar index</param>
 /// <returns>double</returns>
 public static double GetBarVolumeStrength(this Bars bars, int index)
 {
     return(bars.GetBarRange(index) / bars.TickVolumes[index]);;
 }
        /// <summary>
        /// Returns the range of a bar in a market series
        /// </summary>
        /// <param name="bars"></param>
        /// <param name="index">Bar index in market series</param>
        /// <param name="symbol">The market series symbol</param>
        /// <param name="returnType">The return type</param>
        /// <param name="useBarBody">Use bar open and close price instead of high and low?</param>
        /// <returns>double</returns>
        public static double GetBarRange(this Bars bars, int index, Symbol symbol, PriceValueType returnType, bool useBarBody = false)
        {
            double range = bars.GetBarRange(index, useBarBody);

            return(symbol.ChangePriceValueType(range, returnType));
        }