/// <summary>
        /// Returns the range between an index interval in a market series
        /// </summary>
        /// <param name="bars"></param>
        /// <param name="startIndex">Start index</param>
        /// <param name="endIndex">End index</param>
        /// <param name="useBarBody">Use bar body (open and close) instead of shadows (high and low)</param>
        /// <returns>double</returns>
        public static double GetRange(this Bars bars, int startIndex, int endIndex, bool useBarBody = false)
        {
            double min = double.MaxValue, max = double.MinValue;

            for (var i = startIndex; i <= endIndex; i++)
            {
                double barLow, barHigh;

                if (useBarBody)
                {
                    barLow  = bars.GetBarType(i) == BarType.Bullish ? bars.OpenPrices[i] : bars.ClosePrices[i];
                    barHigh = bars.GetBarType(i) == BarType.Bullish ? bars.ClosePrices[i] : bars.OpenPrices[i];
                }
                else
                {
                    barLow  = bars.LowPrices[i];
                    barHigh = bars.HighPrices[i];
                }

                min = Math.Min(min, barLow);
                max = Math.Max(max, barHigh);
            }

            return(max - min);
        }
        /// <summary>
        /// Returns True if the index bar is a three bar reversal
        /// </summary>
        /// <param name="bars"></param>
        /// <param name="index">The bar index number in a market series</param>
        /// <returns>bool</returns>
        public static bool IsThreeBarReversal(this Bars bars, int index)
        {
            var result = false;

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

            if (barType == BarType.Bullish && previousBarType == BarType.Bearish && bars.GetBarType(index - 2) == BarType.Bearish)
            {
                if (bars.LowPrices[index - 1] < bars.LowPrices[index - 2] && bars.LowPrices[index - 1] < bars.LowPrices[index])
                {
                    if (bars.ClosePrices[index] > bars.OpenPrices[index - 1])
                    {
                        result = true;
                    }
                }
            }
            else if (barType == BarType.Bearish && previousBarType == BarType.Bullish && bars.GetBarType(index - 2) == BarType.Bullish)
            {
                if (bars.HighPrices[index - 1] > bars.HighPrices[index - 2] && bars.HighPrices[index - 1] > bars.HighPrices[index])
                {
                    if (bars.ClosePrices[index] < bars.OpenPrices[index - 1])
                    {
                        result = true;
                    }
                }
            }

            return(result);
        }
        /// <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 True if the index bar is an inside bar
        /// </summary>
        /// <param name="bars"></param>
        /// <param name="index">The bar index number in a market series</param>
        /// <returns>bool</returns>
        public static bool IsInsideBar(this Bars bars, int index)
        {
            BarType barType         = bars.GetBarType(index);
            BarType previousBarType = bars.GetBarType(index - 1);

            if (bars.HighPrices[index] < bars.HighPrices[index - 1] &&
                bars.LowPrices[index] > bars.LowPrices[index - 1] &&
                barType != previousBarType)
            {
                return(true);
            }

            return(false);
        }
        /// <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="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 GetMinBarRange(this Bars bars, int index, int periods, BarType barType, bool useBarBody = false)
        {
            var minRange = double.MaxValue;

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

                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="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 GetMedianBarRange(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.Median());
        }
        /// <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);
        }