From a9983c5ea4079412a7484ad987828f0f24e54f02 Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Sat, 19 Oct 2024 13:38:54 -0700 Subject: [PATCH] Volume Weighted Average Price Strategy added. --- README.md | 2 +- strategy/volume/README.md | 63 +++++ ...volume_weighted_average_price_strategy.csv | 252 ++++++++++++++++++ strategy/volume/volume.go | 1 + .../volume_weighted_average_price_strategy.go | 118 ++++++++ ...me_weighted_average_price_strategy_test.go | 55 ++++ 6 files changed, 490 insertions(+), 1 deletion(-) create mode 100644 strategy/volume/testdata/volume_weighted_average_price_strategy.csv create mode 100644 strategy/volume/volume_weighted_average_price_strategy.go create mode 100644 strategy/volume/volume_weighted_average_price_strategy_test.go diff --git a/README.md b/README.md index 91c6484..987febd 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ The following list of strategies are currently supported by this package: - [Force Index Strategy](strategy/volume/README.md#type-forceindexstrategy) - [Money Flow Index Strategy](strategy/volume/README.md#type-moneyflowindexstrategy) - [Negative Volume Index Strategy](strategy/volume/README.md#type-negativevolumeindexstrategy) -- Volume Weighted Average Price Strategy +- [Volume Weighted Average Price Strategy](strategy/volume/README.md#type-volumeweightedaveragepricestrategy) ### 🧪 Compound Strategies diff --git a/strategy/volume/README.md b/strategy/volume/README.md index 302b9b5..a504dad 100644 --- a/strategy/volume/README.md +++ b/strategy/volume/README.md @@ -50,6 +50,12 @@ The information provided on this project is strictly for informational purposes - [func \(n \*NegativeVolumeIndexStrategy\) Compute\(snapshots \<\-chan \*asset.Snapshot\) \<\-chan strategy.Action](<#NegativeVolumeIndexStrategy.Compute>) - [func \(n \*NegativeVolumeIndexStrategy\) Name\(\) string](<#NegativeVolumeIndexStrategy.Name>) - [func \(n \*NegativeVolumeIndexStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#NegativeVolumeIndexStrategy.Report>) +- [type VolumeWeightedAveragePriceStrategy](<#VolumeWeightedAveragePriceStrategy>) + - [func NewVolumeWeightedAveragePriceStrategy\(\) \*VolumeWeightedAveragePriceStrategy](<#NewVolumeWeightedAveragePriceStrategy>) + - [func NewVolumeWeightedAveragePriceStrategyWith\(period int\) \*VolumeWeightedAveragePriceStrategy](<#NewVolumeWeightedAveragePriceStrategyWith>) + - [func \(v \*VolumeWeightedAveragePriceStrategy\) Compute\(snapshots \<\-chan \*asset.Snapshot\) \<\-chan strategy.Action](<#VolumeWeightedAveragePriceStrategy.Compute>) + - [func \(v \*VolumeWeightedAveragePriceStrategy\) Name\(\) string](<#VolumeWeightedAveragePriceStrategy.Name>) + - [func \(v \*VolumeWeightedAveragePriceStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#VolumeWeightedAveragePriceStrategy.Report>) ## Constants @@ -321,4 +327,61 @@ func (n *NegativeVolumeIndexStrategy) Report(c <-chan *asset.Snapshot) *helper.R Report processes the provided asset snapshots and generates a report annotated with the recommended actions. + +## type [VolumeWeightedAveragePriceStrategy]() + +VolumeWeightedAveragePriceStrategy represents the configuration parameters for calculating the Volume Weighted Average Price strategy. Recommends a Buy action when the closing crosses below the VWAP, recommends a Sell action when the closing crosses above the VWAP, and recommends a Hold action otherwise. + +```go +type VolumeWeightedAveragePriceStrategy struct { + // VolumeWeightedAveragePrice is the Volume Weighted Average Price indicator instance. + VolumeWeightedAveragePrice *volume.Vwap[float64] +} +``` + + +### func [NewVolumeWeightedAveragePriceStrategy]() + +```go +func NewVolumeWeightedAveragePriceStrategy() *VolumeWeightedAveragePriceStrategy +``` + +NewVolumeWeightedAveragePriceStrategy function initializes a new Volume Weighted Average Price strategy instance with the default parameters. + + +### func [NewVolumeWeightedAveragePriceStrategyWith]() + +```go +func NewVolumeWeightedAveragePriceStrategyWith(period int) *VolumeWeightedAveragePriceStrategy +``` + +NewVolumeWeightedAveragePriceStrategyWith function initializes a new Volume Weighted Average Price strategy instance with the given parameters. + + +### func \(\*VolumeWeightedAveragePriceStrategy\) [Compute]() + +```go +func (v *VolumeWeightedAveragePriceStrategy) Compute(snapshots <-chan *asset.Snapshot) <-chan strategy.Action +``` + +Compute processes the provided asset snapshots and generates a stream of actionable recommendations. + + +### func \(\*VolumeWeightedAveragePriceStrategy\) [Name]() + +```go +func (v *VolumeWeightedAveragePriceStrategy) Name() string +``` + +Name returns the name of the strategy. + + +### func \(\*VolumeWeightedAveragePriceStrategy\) [Report]() + +```go +func (v *VolumeWeightedAveragePriceStrategy) 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]() diff --git a/strategy/volume/testdata/volume_weighted_average_price_strategy.csv b/strategy/volume/testdata/volume_weighted_average_price_strategy.csv new file mode 100644 index 0000000..e389b57 --- /dev/null +++ b/strategy/volume/testdata/volume_weighted_average_price_strategy.csv @@ -0,0 +1,252 @@ +Action +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +1 +-1 +1 +-1 +-1 +1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +-1 +1 +1 +-1 +1 +1 +1 +-1 +-1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +-1 +-1 +-1 +-1 +-1 +1 +1 +1 +1 +1 +1 +1 +1 +-1 +1 +1 +1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +1 +1 +1 +-1 +-1 +1 +1 +1 +1 +1 +-1 +-1 +-1 +-1 +1 +1 +1 +1 +1 +1 +1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +1 +1 +1 +1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +1 +1 +1 +1 +1 +1 +1 +1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 diff --git a/strategy/volume/volume.go b/strategy/volume/volume.go index 07d52aa..97cc359 100644 --- a/strategy/volume/volume.go +++ b/strategy/volume/volume.go @@ -29,5 +29,6 @@ func AllStrategies() []strategy.Strategy { NewForceIndexStrategy(), NewMoneyFlowIndexStrategy(), NewNegativeVolumeIndexStrategy(), + NewVolumeWeightedAveragePriceStrategy(), } } diff --git a/strategy/volume/volume_weighted_average_price_strategy.go b/strategy/volume/volume_weighted_average_price_strategy.go new file mode 100644 index 0000000..3314990 --- /dev/null +++ b/strategy/volume/volume_weighted_average_price_strategy.go @@ -0,0 +1,118 @@ +// Copyright (c) 2021-2024 Onur Cinar. +// The source code is provided under GNU AGPLv3 License. +// https://github.com/cinar/indicator + +package volume + +import ( + "fmt" + + "github.com/cinar/indicator/v2/asset" + "github.com/cinar/indicator/v2/helper" + "github.com/cinar/indicator/v2/strategy" + "github.com/cinar/indicator/v2/volume" +) + +// VolumeWeightedAveragePriceStrategy represents the configuration parameters for calculating the Volume Weighted +// Average Price strategy. Recommends a Buy action when the closing crosses below the VWAP, recommends a Sell +// action when the closing crosses above the VWAP, and recommends a Hold action otherwise. +type VolumeWeightedAveragePriceStrategy struct { + // VolumeWeightedAveragePrice is the Volume Weighted Average Price indicator instance. + VolumeWeightedAveragePrice *volume.Vwap[float64] +} + +// NewVolumeWeightedAveragePriceStrategy function initializes a new Volume Weighted Average Price strategy +// instance with the default parameters. +func NewVolumeWeightedAveragePriceStrategy() *VolumeWeightedAveragePriceStrategy { + return NewVolumeWeightedAveragePriceStrategyWith( + volume.DefaultVwapPeriod, + ) +} + +// NewVolumeWeightedAveragePriceStrategyWith function initializes a new Volume Weighted Average Price strategy +// instance with the given parameters. +func NewVolumeWeightedAveragePriceStrategyWith(period int) *VolumeWeightedAveragePriceStrategy { + return &VolumeWeightedAveragePriceStrategy{ + VolumeWeightedAveragePrice: volume.NewVwapWithPeriod[float64](period), + } +} + +// Name returns the name of the strategy. +func (v *VolumeWeightedAveragePriceStrategy) Name() string { + return fmt.Sprintf("Volume Weighted Average Price Strategy (%d)", v.VolumeWeightedAveragePrice.IdlePeriod()+1) +} + +// Compute processes the provided asset snapshots and generates a stream of actionable recommendations. +func (v *VolumeWeightedAveragePriceStrategy) Compute(snapshots <-chan *asset.Snapshot) <-chan strategy.Action { + snapshotsSplice := helper.Duplicate(snapshots, 2) + + closingsSplice := helper.Duplicate( + asset.SnapshotsAsClosings(snapshotsSplice[0]), + 2, + ) + + volumes := asset.SnapshotsAsVolumes(snapshotsSplice[1]) + + vwaps := v.VolumeWeightedAveragePrice.Compute(closingsSplice[1], volumes) + closingsSplice[0] = helper.Skip(closingsSplice[0], v.VolumeWeightedAveragePrice.IdlePeriod()) + + actions := helper.Operate(closingsSplice[0], vwaps, func(closing, vwap float64) strategy.Action { + if vwap > closing { + return strategy.Buy + } + + if vwap < closing { + return strategy.Sell + } + + return strategy.Hold + }) + + // Volume Weighted Average Price starts only after a full period. + actions = helper.Shift(actions, v.VolumeWeightedAveragePrice.IdlePeriod(), strategy.Hold) + + return actions +} + +// Report processes the provided asset snapshots and generates a report annotated with the recommended actions. +func (v *VolumeWeightedAveragePriceStrategy) Report(c <-chan *asset.Snapshot) *helper.Report { + // + // snapshots[0] -> dates + // snapshots[1] -> closings[0] -> closings + // closings[1] -> vwap + // snapshots[2] -> volumes + // snapshots[3] -> actions -> annotations + // -> outcomes + // + snapshots := helper.Duplicate(c, 4) + + dates := helper.Skip(asset.SnapshotsAsDates(snapshots[0]), v.VolumeWeightedAveragePrice.IdlePeriod()) + + closingsSplice := helper.Duplicate( + asset.SnapshotsAsClosings(snapshots[1]), + 2, + ) + volumes := asset.SnapshotsAsVolumes(snapshots[2]) + + vwaps := v.VolumeWeightedAveragePrice.Compute(closingsSplice[0], volumes) + + closingsSplice[1] = helper.Skip(closingsSplice[1], v.VolumeWeightedAveragePrice.IdlePeriod()) + + actions, outcomes := strategy.ComputeWithOutcome(v, snapshots[3]) + actions = helper.Skip(actions, v.VolumeWeightedAveragePrice.IdlePeriod()) + outcomes = helper.Skip(outcomes, v.VolumeWeightedAveragePrice.IdlePeriod()) + + annotations := strategy.ActionsToAnnotations(actions) + outcomes = helper.MultiplyBy(outcomes, 100) + + report := helper.NewReport(v.Name(), dates) + report.AddChart() + + report.AddColumn(helper.NewNumericReportColumn("Close", closingsSplice[1])) + report.AddColumn(helper.NewNumericReportColumn("VWAP", vwaps)) + report.AddColumn(helper.NewAnnotationReportColumn(annotations)) + + report.AddColumn(helper.NewNumericReportColumn("Outcome", outcomes), 1) + + return report +} diff --git a/strategy/volume/volume_weighted_average_price_strategy_test.go b/strategy/volume/volume_weighted_average_price_strategy_test.go new file mode 100644 index 0000000..2922305 --- /dev/null +++ b/strategy/volume/volume_weighted_average_price_strategy_test.go @@ -0,0 +1,55 @@ +// Copyright (c) 2021-2024 Onur Cinar. +// The source code is provided under GNU AGPLv3 License. +// https://github.com/cinar/indicator + +package volume_test + +import ( + "os" + "testing" + + "github.com/cinar/indicator/v2/asset" + "github.com/cinar/indicator/v2/helper" + "github.com/cinar/indicator/v2/strategy" + "github.com/cinar/indicator/v2/strategy/volume" +) + +func TestVolumeWeightedAveragePriceStrategy(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/volume_weighted_average_price_strategy.csv", true) + if err != nil { + t.Fatal(err) + } + + expected := helper.Map(results, func(r *strategy.Result) strategy.Action { return r.Action }) + + vwaps := volume.NewVolumeWeightedAveragePriceStrategy() + actual := vwaps.Compute(snapshots) + + err = helper.CheckEquals(actual, expected) + if err != nil { + t.Fatal(err) + } +} + +func TestVolumeWeightedAveragePriceStrategyReport(t *testing.T) { + snapshots, err := helper.ReadFromCsvFile[asset.Snapshot]("testdata/brk-b.csv", true) + if err != nil { + t.Fatal(err) + } + + vwaps := volume.NewVolumeWeightedAveragePriceStrategy() + report := vwaps.Report(snapshots) + + fileName := "volume_weighted_average_price_strategy.html" + defer os.Remove(fileName) + + err = report.WriteToFile(fileName) + if err != nil { + t.Fatal(err) + } +}