Bollinger Bands® with validation tests

This commit is contained in:
Miha Kralj
2022-11-08 18:26:04 -08:00
parent 206e931209
commit 773f3fbbfe
6 changed files with 166 additions and 2 deletions
+1 -1
View File
@@ -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);
+73
View File
@@ -0,0 +1,73 @@
namespace QuanTAlib;
using System;
/* <summary>
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.
</summary> */
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);
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>0.1.15</Version>
<Version>0.1.16</Version>
<releaseNotes>
</releaseNotes>
<Title>QuanTAlib</Title>
+58
View File
@@ -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);
}
}
+16
View File
@@ -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);
+17
View File
@@ -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()
{