From f269d9732a08fa359dc5d1d46fbf46c5932fddf5 Mon Sep 17 00:00:00 2001 From: Onur Cinar <onur.cinar@gmail.com> Date: Sat, 30 Dec 2023 14:55:30 -0800 Subject: [PATCH] Bollinger Bands strategy added. --- README.md | 2 +- strategy/volatility/README.md | 94 +++++++ .../volatility/bollinger_bands_strategy.go | 118 ++++++++ .../bollinger_bands_strategy_test.go | 55 ++++ .../testdata/bollinger_bands_strategy.csv | 252 ++++++++++++++++++ strategy/volatility/testdata/brk-b.csv | 1 + strategy/volatility/volatility.go | 28 ++ 7 files changed, 549 insertions(+), 1 deletion(-) create mode 100644 strategy/volatility/README.md create mode 100644 strategy/volatility/bollinger_bands_strategy.go create mode 100644 strategy/volatility/bollinger_bands_strategy_test.go create mode 100644 strategy/volatility/testdata/bollinger_bands_strategy.csv create mode 120000 strategy/volatility/testdata/brk-b.csv create mode 100644 strategy/volatility/volatility.go diff --git a/README.md b/README.md index 983ddc6..da7c7ad 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ The following list of strategies are currently supported by this package: ### 🎢 Volatility Strategies -- Bollinger Bands Strategy +- [Bollinger Bands Strategy](strategy/volatility/README.md#type-bollingerbandsstrategy) - Projection Oscillator Strategy ### 📢 Volume Strategies diff --git a/strategy/volatility/README.md b/strategy/volatility/README.md new file mode 100644 index 0000000..10ea811 --- /dev/null +++ b/strategy/volatility/README.md @@ -0,0 +1,94 @@ +<!-- Code generated by gomarkdoc. DO NOT EDIT --> + +# volatility + +```go +import "github.com/cinar/indicator/strategy/volatility" +``` + +Package volatility contains the volatility strategy functions. + +This package belongs to the Indicator project. Indicator is a Golang module that supplies a variety of technical indicators, strategies, and a backtesting framework for analysis. + +### License + +``` +Copyright (c) 2021-2023 Onur Cinar. +The source code is provided under GNU AGPLv3 License. +https://github.com/cinar/indicator +``` + +### Disclaimer + +The information provided on this project is strictly for informational purposes and is not to be construed as advice or solicitation to buy or sell any security. + +## Index + +- [func AllStrategies\(\) \[\]strategy.Strategy](<#AllStrategies>) +- [type BollingerBandsStrategy](<#BollingerBandsStrategy>) + - [func NewBollingerBandsStrategy\(\) \*BollingerBandsStrategy](<#NewBollingerBandsStrategy>) + - [func \(b \*BollingerBandsStrategy\) Compute\(snapshots \<\-chan \*asset.Snapshot\) \<\-chan strategy.Action](<#BollingerBandsStrategy.Compute>) + - [func \(\*BollingerBandsStrategy\) Name\(\) string](<#BollingerBandsStrategy.Name>) + - [func \(b \*BollingerBandsStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#BollingerBandsStrategy.Report>) + + +<a name="AllStrategies"></a> +## func [AllStrategies](<https://github.com/cinar/indicator/blob/v2/strategy/volatility/volatility.go#L24>) + +```go +func AllStrategies() []strategy.Strategy +``` + +AllStrategies returns a slice containing references to all available volatility strategies. + +<a name="BollingerBandsStrategy"></a> +## type [BollingerBandsStrategy](<https://github.com/cinar/indicator/blob/v2/strategy/volatility/bollinger_bands_strategy.go#L17-L22>) + +BollingerBandsStrategy represents the configuration parameters for calculating the Bollinger Bands strategy. A closing value crossing above the upper band suggets a Buy signal, while crossing below the lower band indivates a Sell signal. + +```go +type BollingerBandsStrategy struct { + strategy.Strategy + + // BollingerBands represents the configuration parameters for calculating the Bollinger Bands. + BollingerBands *volatility.BollingerBands[float64] +} +``` + +<a name="NewBollingerBandsStrategy"></a> +### func [NewBollingerBandsStrategy](<https://github.com/cinar/indicator/blob/v2/strategy/volatility/bollinger_bands_strategy.go#L25>) + +```go +func NewBollingerBandsStrategy() *BollingerBandsStrategy +``` + +NewBollingerBandsStrategy function initializes a new Bollinger Bands strategy instance. + +<a name="BollingerBandsStrategy.Compute"></a> +### func \(\*BollingerBandsStrategy\) [Compute](<https://github.com/cinar/indicator/blob/v2/strategy/volatility/bollinger_bands_strategy.go#L37>) + +```go +func (b *BollingerBandsStrategy) Compute(snapshots <-chan *asset.Snapshot) <-chan strategy.Action +``` + +Compute processes the provided asset snapshots and generates a stream of actionable recommendations. + +<a name="BollingerBandsStrategy.Name"></a> +### func \(\*BollingerBandsStrategy\) [Name](<https://github.com/cinar/indicator/blob/v2/strategy/volatility/bollinger_bands_strategy.go#L32>) + +```go +func (*BollingerBandsStrategy) Name() string +``` + +Name returns the name of the strategy. + +<a name="BollingerBandsStrategy.Report"></a> +### func \(\*BollingerBandsStrategy\) [Report](<https://github.com/cinar/indicator/blob/v2/strategy/volatility/bollinger_bands_strategy.go#L82>) + +```go +func (b *BollingerBandsStrategy) Report(c <-chan *asset.Snapshot) *helper.Report +``` + +Report processes the provided asset snapshots and generates a report annotated with the recommended actions. + +Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>) diff --git a/strategy/volatility/bollinger_bands_strategy.go b/strategy/volatility/bollinger_bands_strategy.go new file mode 100644 index 0000000..06a3fba --- /dev/null +++ b/strategy/volatility/bollinger_bands_strategy.go @@ -0,0 +1,118 @@ +// Copyright (c) 2021-2023 Onur Cinar. +// The source code is provided under GNU AGPLv3 License. +// https://github.com/cinar/indicator + +package volatility + +import ( + "github.com/cinar/indicator/asset" + "github.com/cinar/indicator/helper" + "github.com/cinar/indicator/strategy" + "github.com/cinar/indicator/volatility" +) + +// BollingerBandsStrategy represents the configuration parameters for calculating the Bollinger Bands strategy. +// A closing value crossing above the upper band suggets a Buy signal, while crossing below the lower band +// indivates a Sell signal. +type BollingerBandsStrategy struct { + strategy.Strategy + + // BollingerBands represents the configuration parameters for calculating the Bollinger Bands. + BollingerBands *volatility.BollingerBands[float64] +} + +// NewBollingerBandsStrategy function initializes a new Bollinger Bands strategy instance. +func NewBollingerBandsStrategy() *BollingerBandsStrategy { + return &BollingerBandsStrategy{ + BollingerBands: volatility.NewBollingerBands[float64](), + } +} + +// Name returns the name of the strategy. +func (*BollingerBandsStrategy) Name() string { + return "Bollinger Bands Strategy" +} + +// Compute processes the provided asset snapshots and generates a stream of actionable recommendations. +func (b *BollingerBandsStrategy) Compute(snapshots <-chan *asset.Snapshot) <-chan strategy.Action { + closings := helper.Duplicate( + asset.SnapshotsAsClosings(snapshots), + 3, + ) + + uppers, middles, lowers := b.BollingerBands.Compute(closings[0]) + + go helper.Drain(middles) + + closings[1] = helper.Skip(closings[1], b.BollingerBands.IdlePeriod()) + + aboveUppers := helper.Subtract( + closings[1], + uppers, + ) + + closings[2] = helper.Skip(closings[2], b.BollingerBands.IdlePeriod()) + + belowLowers := helper.Subtract( + closings[2], + lowers, + ) + + actions := helper.Operate(aboveUppers, belowLowers, func(aboveUpper, belowLower float64) strategy.Action { + if aboveUpper > 0 { + return strategy.Buy + } + + if belowLower < 0 { + return strategy.Sell + } + + return strategy.Hold + }) + + actions = strategy.NormalizeActions(actions) + + // Bollinger Bands starts only after a full period. + actions = helper.Shift(actions, b.BollingerBands.IdlePeriod(), strategy.Hold) + + return actions +} + +// Report processes the provided asset snapshots and generates a report annotated with the recommended actions. +func (b *BollingerBandsStrategy) Report(c <-chan *asset.Snapshot) *helper.Report { + // + // snapshots[0] -> dates + // snapshots[1] -> closings[0] -> closings + // closings[1] -> upper + // -> middle + // -> lower + // snapshots[2] -> actions -> annotations + // -> outcomes + // + snapshots := helper.Duplicate(c, 3) + + dates := asset.SnapshotsAsDates(snapshots[0]) + closings := helper.Duplicate(asset.SnapshotsAsClosings(snapshots[1]), 2) + + uppers, middles, lowers := b.BollingerBands.Compute(closings[0]) + uppers = helper.Shift(uppers, b.BollingerBands.IdlePeriod(), 0) + middles = helper.Shift(middles, b.BollingerBands.IdlePeriod(), 0) + lowers = helper.Shift(lowers, b.BollingerBands.IdlePeriod(), 0) + + actions, outcomes := strategy.ComputeWithOutcome(b, snapshots[2]) + annotations := strategy.ActionsToAnnotations(actions) + outcomes = helper.MultiplyBy(outcomes, 100) + + report := helper.NewReport(b.Name(), dates) + report.AddChart() + + report.AddColumn(helper.NewNumericReportColumn("Close", closings[1])) + report.AddColumn(helper.NewNumericReportColumn("Upper", uppers)) + report.AddColumn(helper.NewNumericReportColumn("Middle", middles)) + report.AddColumn(helper.NewNumericReportColumn("Lower", lowers)) + report.AddColumn(helper.NewAnnotationReportColumn(annotations)) + + report.AddColumn(helper.NewNumericReportColumn("Outcome", outcomes), 1) + + return report +} diff --git a/strategy/volatility/bollinger_bands_strategy_test.go b/strategy/volatility/bollinger_bands_strategy_test.go new file mode 100644 index 0000000..b18f97e --- /dev/null +++ b/strategy/volatility/bollinger_bands_strategy_test.go @@ -0,0 +1,55 @@ +// Copyright (c) 2021-2023 Onur Cinar. +// The source code is provided under GNU AGPLv3 License. +// https://github.com/cinar/indicator + +package volatility_test + +import ( + "os" + "testing" + + "github.com/cinar/indicator/asset" + "github.com/cinar/indicator/helper" + "github.com/cinar/indicator/strategy" + "github.com/cinar/indicator/strategy/volatility" +) + +func TestBollingerBandsStrategy(t *testing.T) { + snapshots, err := helper.ReadFromCsvFile[asset.Snapshot]("testdata/brk-b.csv", true) + if err != nil { + t.Fatal(err) + } + + results, err := helper.ReadFromCsvFile[strategy.Result]("testdata/bollinger_bands_strategy.csv", true) + if err != nil { + t.Fatal(err) + } + + bb := volatility.NewBollingerBandsStrategy() + actions, outcomes := strategy.ComputeWithOutcome(bb, snapshots) + outcomes = helper.RoundDigits(outcomes, 2) + + err = strategy.CheckResults(results, actions, outcomes) + if err != nil { + t.Fatal(err) + } +} + +func TestBollingerBandsStrategyReport(t *testing.T) { + snapshots, err := helper.ReadFromCsvFile[asset.Snapshot]("testdata/brk-b.csv", true) + if err != nil { + t.Fatal(err) + } + + bb := volatility.NewBollingerBandsStrategy() + + report := bb.Report(snapshots) + + fileName := "bollinger_bands_strategy.html" + defer os.Remove(fileName) + + err = report.WriteToFile(fileName) + if err != nil { + t.Fatal(err) + } +} diff --git a/strategy/volatility/testdata/bollinger_bands_strategy.csv b/strategy/volatility/testdata/bollinger_bands_strategy.csv new file mode 100644 index 0000000..630dfde --- /dev/null +++ b/strategy/volatility/testdata/bollinger_bands_strategy.csv @@ -0,0 +1,252 @@ +Action,Outcome +0,0 +0,0 +0,0 +0,0 +0,0 +0,0 +0,0 +0,0 +0,0 +0,0 +0,0 +0,0 +0,0 +0,0 +0,0 +0,0 +0,0 +0,0 +0,0 +0,0 +0,0 +0,0 +0,0 +1,0 +0,-0.01 +0,0.01 +0,0 +0,0.01 +0,0.02 +0,0.01 +0,0.01 +0,0 +0,-0.02 +0,-0.03 +0,-0.01 +0,-0.01 +0,-0.01 +0,-0.01 +0,-0.01 +0,-0.02 +0,-0.02 +0,-0.01 +0,-0.01 +0,-0.01 +0,-0.02 +0,-0.02 +0,-0.01 +0,-0.02 +0,-0.02 +0,-0.01 +0,-0 +0,-0.01 +0,-0.02 +0,-0.02 +0,-0.02 +-1,-0.04 +0,-0.04 +0,-0.04 +0,-0.04 +0,-0.04 +0,-0.04 +0,-0.04 +0,-0.04 +0,-0.04 +1,-0.04 +0,-0.06 +0,-0.05 +0,-0.07 +0,-0.08 +0,-0.08 +0,-0.07 +-1,-0.1 +0,-0.1 +0,-0.1 +0,-0.1 +0,-0.1 +0,-0.1 +0,-0.1 +0,-0.1 +0,-0.1 +0,-0.1 +0,-0.1 +0,-0.1 +0,-0.1 +0,-0.1 +0,-0.1 +0,-0.1 +0,-0.1 +0,-0.1 +0,-0.1 +0,-0.1 +0,-0.1 +0,-0.1 +1,-0.1 +0,-0.09 +0,-0.1 +0,-0.1 +0,-0.09 +0,-0.09 +0,-0.09 +0,-0.1 +0,-0.09 +0,-0.08 +0,-0.08 +0,-0.09 +0,-0.1 +0,-0.11 +0,-0.1 +0,-0.09 +0,-0.09 +0,-0.1 +0,-0.1 +0,-0.1 +0,-0.1 +0,-0.1 +0,-0.09 +0,-0.08 +0,-0.08 +0,-0.08 +0,-0.1 +0,-0.11 +0,-0.11 +0,-0.1 +0,-0.1 +0,-0.1 +0,-0.1 +0,-0.08 +0,-0.08 +0,-0.07 +0,-0.06 +0,-0.06 +0,-0.06 +0,-0.07 +0,-0.06 +0,-0.06 +0,-0.05 +0,-0.06 +0,-0.05 +0,-0.05 +0,-0.06 +0,-0.06 +0,-0.07 +0,-0.06 +0,-0.07 +0,-0.06 +0,-0.05 +0,-0.04 +0,-0.05 +0,-0.05 +0,-0.05 +0,-0.05 +0,-0.04 +0,-0.04 +0,-0.04 +0,-0.05 +0,-0.04 +0,-0.04 +0,-0.04 +0,-0.03 +0,-0.03 +0,-0.02 +0,-0.03 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.01 +0,-0.02 +0,0.01 +0,0.02 +0,0 +0,-0 +0,0 +0,0 +0,-0.01 +0,-0.01 +0,-0.01 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.01 +0,-0.01 +0,-0.01 +0,-0.01 +0,0 +0,0.01 +0,0.01 +0,0.01 +0,0.01 +0,0.01 +0,0.01 +0,0.01 +0,0.02 +0,0.03 +0,0.03 +0,0.03 +0,0.03 +0,0.03 +0,0.03 +0,0.02 +0,0.01 +0,0.01 +0,0.01 +0,0 +0,-0 +0,-0 +-1,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 +0,-0.02 diff --git a/strategy/volatility/testdata/brk-b.csv b/strategy/volatility/testdata/brk-b.csv new file mode 120000 index 0000000..53db519 --- /dev/null +++ b/strategy/volatility/testdata/brk-b.csv @@ -0,0 +1 @@ +../../../asset/testdata/repository/brk-b.csv \ No newline at end of file diff --git a/strategy/volatility/volatility.go b/strategy/volatility/volatility.go new file mode 100644 index 0000000..0bea739 --- /dev/null +++ b/strategy/volatility/volatility.go @@ -0,0 +1,28 @@ +// Package volatility contains the volatility strategy functions. +// +// This package belongs to the Indicator project. Indicator is +// a Golang module that supplies a variety of technical +// indicators, strategies, and a backtesting framework +// for analysis. +// +// # License +// +// Copyright (c) 2021-2023 Onur Cinar. +// The source code is provided under GNU AGPLv3 License. +// https://github.com/cinar/indicator +// +// # Disclaimer +// +// The information provided on this project is strictly for +// informational purposes and is not to be construed as +// advice or solicitation to buy or sell any security. +package volatility + +import "github.com/cinar/indicator/strategy" + +// AllStrategies returns a slice containing references to all available volatility strategies. +func AllStrategies() []strategy.Strategy { + return []strategy.Strategy{ + NewBollingerBandsStrategy(), + } +}