diff --git a/Source/Basics/Abstracts.cs b/Source/Basics/Abstracts.cs
index 28b08ffb..d20be702 100644
--- a/Source/Basics/Abstracts.cs
+++ b/Source/Basics/Abstracts.cs
@@ -158,7 +158,7 @@ public abstract class Single_TBars_Indicator : TSeries
for (int i = 0; i < data.Count; i++) { base.Add(TValue: data[i], update: false); }
}
-public new void Add((System.DateTime t, double o, double h, double l, double c, double v) TBar)
+public void Add((System.DateTime t, double o, double h, double l, double c, double v) TBar)
=> this.Add(TBar: TBar, update: false);
public void Add(bool update)
=> this.Add(TBar: this._bars[this._bars.Count - 1], update: update);
diff --git a/Source/Indicators/BBANDS_Series.cs b/Source/Indicators/BBANDS_Series.cs
new file mode 100644
index 00000000..169f8f35
--- /dev/null
+++ b/Source/Indicators/BBANDS_Series.cs
@@ -0,0 +1,73 @@
+namespace QuanTAlib;
+using System;
+
+/*
+BBANDS: Bollinger Bands®
+ Price channels created by John Bollinger, depict volatility as standard deviation boundary
+ line range from a moving average of price. The bands automatically widen when volatility
+ increases and contract when volatility decreases. Their dynamic nature allows them to be
+ used on different securities with the standard settings.
+
+ Mid Band = simple moving average (SMA)
+ Upper Band = SMA + (standard deviation of price x multiplier)
+ Lower Band = SMA - (standard deviation of price x multiplier)
+ Bandwidth = Width of the channel: (Upper-Lower)/SMA
+ %B = The location of the data point within the channel: (Price-Lower)/(Upper/Lower)
+ Z-Score = number of standard deviations of the data point from SMA
+
+Sources:
+ https://www.investopedia.com/terms/b/bollingerbands.asp
+ https://school.stockcharts.com/doku.php?id=technical_indicators:bollinger_bands
+
+Note:
+ Bollinger Bands® is a registered trademark of John A. Bollinger.
+
+ */
+
+public class BBANDS_Series : Single_TSeries_Indicator
+{
+ public SMA_Series Mid { get; }
+ public ADD_Series Upper { get; }
+ public SUB_Series Lower { get; }
+ public DIV_Series PercentB { get; }
+ public DIV_Series Bandwidth { get; }
+ public DIV_Series Zscore { get; }
+
+ private readonly SDEV_Series _sdev;
+ private readonly MUL_Series _mulsdev;
+ private readonly SUB_Series _pbdnd;
+ private readonly SUB_Series _pbdvr;
+ private readonly SUB_Series _zdnd;
+
+ public BBANDS_Series(TSeries source, int period = 26, double multiplier = 2.0, bool useNaN = false)
+ : base(source, period: 0, useNaN)
+ {
+ this.Mid = new(source: source, period: period, useNaN: useNaN);
+
+ _sdev = new(source, period, useNaN: useNaN);
+ _mulsdev = new(_sdev, multiplier);
+ this.Upper = new(Mid, _mulsdev);
+ this.Lower = new(Mid, _mulsdev);
+
+ _pbdnd = new(source, Lower);
+ _pbdvr = new(Upper, Lower);
+
+ this.PercentB = new(_pbdnd, _pbdvr);
+ this.Bandwidth = new(_pbdvr, Mid);
+
+ _zdnd = new(source, Mid);
+ this.Zscore = new(_zdnd, _sdev);
+
+ if (source.Count > 0)
+ { base.Add(this.Bandwidth); }
+ }
+ public override void Add((System.DateTime t, double v) TValue, bool update)
+ {
+ double _bbandwidth;
+ if (update)
+ { _sdev.Add(TValue, true); }
+ _bbandwidth = this.Bandwidth[(this.Count < this.Bandwidth.Count) ? this.Count : this.Bandwidth.Count - 1].v;
+ var result = (TValue.t, _bbandwidth);
+ base.Add(result, update);
+ }
+}
\ No newline at end of file
diff --git a/Source/QuanTAlib.csproj b/Source/QuanTAlib.csproj
index f8c323d7..1bf85a74 100644
--- a/Source/QuanTAlib.csproj
+++ b/Source/QuanTAlib.csproj
@@ -1,7 +1,7 @@
- 0.1.15
+ 0.1.16
QuanTAlib
diff --git a/Tests/MovingAvg/BBANDS_Test.cs b/Tests/MovingAvg/BBANDS_Test.cs
new file mode 100644
index 00000000..cd59b273
--- /dev/null
+++ b/Tests/MovingAvg/BBANDS_Test.cs
@@ -0,0 +1,58 @@
+using Xunit;
+using System;
+using QuanTAlib;
+
+namespace MovingAvg;
+public class BBANDS_Test
+{
+ [Fact]
+ public void Add_Test()
+ {
+ TSeries a = new() { 0, 1, 2, 3, 4, 5 };
+ BBANDS_Series c = new(a, 4,2.5);
+ Assert.Equal(6, c.Count);
+ a.Add(5);
+ Assert.Equal(a.Count, c.Count);
+ Assert.Equal(a.Count, c.Mid.Count);
+ Assert.Equal(a.Count, c.Upper.Count);
+ Assert.Equal(a.Count, c.Lower.Count);
+ Assert.Equal(a.Count, c.PercentB.Count);
+ Assert.Equal(a.Count, c.Zscore.Count);
+ Assert.Equal(a.Count, c.Bandwidth.Count);
+
+ a.Add(0, update: true);
+ Assert.Equal(a.Count, c.Count);
+ Assert.Equal(a.Count, c.Mid.Count);
+ Assert.Equal(a.Count, c.Upper.Count);
+ Assert.Equal(a.Count, c.Lower.Count);
+ Assert.Equal(a.Count, c.PercentB.Count);
+ Assert.Equal(a.Count, c.Zscore.Count);
+ Assert.Equal(a.Count, c.Bandwidth.Count);
+ }
+
+ [Fact]
+ public void Edge_Test()
+ {
+ TSeries a = new() { double.NaN, double.Epsilon, double.PositiveInfinity, double.MaxValue };
+ BBANDS_Series c = new(a, 4, 2.5);
+ Assert.Equal(a.Count, c.Count);
+ a.Add(double.NaN);
+ Assert.Equal(a.Count, c.Count);
+ Assert.Equal(a.Count, c.Mid.Count);
+ Assert.Equal(a.Count, c.Upper.Count);
+ Assert.Equal(a.Count, c.Lower.Count);
+ Assert.Equal(a.Count, c.PercentB.Count);
+ Assert.Equal(a.Count, c.Zscore.Count);
+ Assert.Equal(a.Count, c.Bandwidth.Count);
+ a.Add(double.PositiveInfinity);
+ Assert.Equal(a.Count, c.Count);
+ Assert.Equal(a.Count, c.Mid.Count);
+ Assert.Equal(a.Count, c.Upper.Count);
+ Assert.Equal(a.Count, c.Lower.Count);
+ Assert.Equal(a.Count, c.PercentB.Count);
+ Assert.Equal(a.Count, c.Zscore.Count);
+ Assert.Equal(a.Count, c.Bandwidth.Count);
+
+ }
+
+}
diff --git a/Tests/Validations/Skender_Stock.cs b/Tests/Validations/Skender_Stock.cs
index 7b383598..2ee8329d 100644
--- a/Tests/Validations/Skender_Stock.cs
+++ b/Tests/Validations/Skender_Stock.cs
@@ -160,9 +160,25 @@ public class Skender_Stock
var SK = this.quotes.GetMacd(12,26,9);
Assert.Equal(Math.Round((double)SK.Last().Macd!, 8), Math.Round(QL.Last().v, 8));
+ Assert.Equal(Math.Round((double)SK.Last().Signal!, 8), Math.Round(QL.Signal.Last().v, 8));
}
[Fact]
+ public void BBANDS()
+ {
+ BBANDS_Series QL = new(this.bars.Close, this.period, 2.0, useNaN: false);
+ var SK = this.quotes.GetBollingerBands(this.period, 2.0);
+
+ Assert.Equal(Math.Round((double)SK.Last().Sma!, 8), Math.Round(QL.Mid.Last().v, 8));
+ Assert.Equal(Math.Round((double)SK.Last().UpperBand!, 8), Math.Round(QL.Upper.Last().v, 8));
+ Assert.Equal(Math.Round((double)SK.Last().LowerBand!, 8), Math.Round(QL.Lower.Last().v, 8));
+ Assert.Equal(Math.Round((double)SK.Last().Width!, 8), Math.Round(QL.Bandwidth.Last().v, 8));
+ Assert.Equal(Math.Round((double)SK.Last().PercentB!, 8), Math.Round(QL.PercentB.Last().v, 8));
+ Assert.Equal(Math.Round((double)SK.Last().ZScore!, 8), Math.Round(QL.Zscore.Last().v, 8));
+ }
+
+
+ [Fact]
public void RSI()
{
RSI_Series QL = new(this.bars.Close, this.period, useNaN: false);
diff --git a/Tests/Validations/TA_LIB.cs b/Tests/Validations/TA_LIB.cs
index 7c7ca807..e4f97f60 100644
--- a/Tests/Validations/TA_LIB.cs
+++ b/Tests/Validations/TA_LIB.cs
@@ -164,8 +164,25 @@ public class TA_LIB
MACD_Series QL = new(this.bars.Close, slow: 26, fast: 12, signal: 9, false);
Core.Macd(this.inclose, 0, this.bars.Count - 1, outMacd: this.TALIB, outMacdSignal: macdSignal, outMacdHist: macdHist, out int outBegIdx, out _);
Assert.Equal(Math.Round(this.TALIB[this.TALIB.Length - outBegIdx - 1], 8), Math.Round(QL.Last().v, 8));
+ Assert.Equal(Math.Round(macdSignal[macdSignal.Length - outBegIdx - 1], 8), Math.Round(QL.Signal.Last().v, 8));
}
+ [Fact]
+ public void BBANDS()
+ {
+ double[] outMiddle = new double[this.bars.Count];
+ double[] outUpper = new double[this.bars.Count];
+ double[] outLower = new double[this.bars.Count];
+ BBANDS_Series QL = new(this.bars.Close, period:26, multiplier:2.0, false);
+ Core.Bbands(this.inclose, 0, this.bars.Count - 1, outRealUpperBand: outUpper, outRealMiddleBand: outMiddle, outRealLowerBand: outLower, out int outBegIdx, out _, optInTimePeriod:26, optInNbDevUp:2.0, optInNbDevDn:2.0);
+ Assert.Equal(Math.Round(outUpper[outUpper.Length - outBegIdx - 1], 8), Math.Round(QL.Upper.Last().v, 8));
+ Assert.Equal(Math.Round(outMiddle[outMiddle.Length - outBegIdx - 1], 8), Math.Round(QL.Mid.Last().v, 8));
+ Assert.Equal(Math.Round(outLower[outLower.Length - outBegIdx - 1], 8), Math.Round(QL.Lower.Last().v, 8));
+
+ }
+
+
+
[Fact]
public void HL2()
{