From 64250389df0f37d280582801f322ee88af78732f Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Fri, 29 Dec 2023 00:01:11 -0800 Subject: [PATCH] TRIMA, VWMA, Typical Price. --- README.md | 9 +- strategy/README.md | 137 +++++++++++++++ strategy/backtest.go | 2 + strategy/testdata/trima_strategy.csv | 252 +++++++++++++++++++++++++++ strategy/testdata/vwma_strategy.csv | 252 +++++++++++++++++++++++++++ strategy/trima_strategy.go | 120 +++++++++++++ strategy/trima_strategy_test.go | 55 ++++++ strategy/vwma_strategy.go | 120 +++++++++++++ strategy/vwma_strategy_test.go | 55 ++++++ trend/README.md | 155 ++++++++++++++++ trend/testdata/trima_even.csv | 252 +++++++++++++++++++++++++++ trend/testdata/trima_odd.csv | 252 +++++++++++++++++++++++++++ trend/testdata/typical_price.csv | 252 +++++++++++++++++++++++++++ trend/testdata/vwma.csv | 252 +++++++++++++++++++++++++++ trend/trima.go | 75 ++++++++ trend/trima_test.go | 70 ++++++++ trend/typical_price.go | 33 ++++ trend/typical_price_test.go | 42 +++++ trend/vwma.go | 51 ++++++ trend/vwma_test.go | 42 +++++ 20 files changed, 2474 insertions(+), 4 deletions(-) create mode 100644 strategy/testdata/trima_strategy.csv create mode 100644 strategy/testdata/vwma_strategy.csv create mode 100644 strategy/trima_strategy.go create mode 100644 strategy/trima_strategy_test.go create mode 100644 strategy/vwma_strategy.go create mode 100644 strategy/vwma_strategy_test.go create mode 100644 trend/testdata/trima_even.csv create mode 100644 trend/testdata/trima_odd.csv create mode 100644 trend/testdata/typical_price.csv create mode 100644 trend/testdata/vwma.csv create mode 100644 trend/trima.go create mode 100644 trend/trima_test.go create mode 100644 trend/typical_price.go create mode 100644 trend/typical_price_test.go create mode 100644 trend/vwma.go create mode 100644 trend/vwma_test.go diff --git a/README.md b/README.md index 82cd787..eafef9f 100644 --- a/README.md +++ b/README.md @@ -46,10 +46,10 @@ The following list of indicators are currently supported by this package: - [Simple Moving Average (SMA)](trend/README.md#type-sma) - [Since Change](helper/README.md#func-since) - [Triple Exponential Moving Average (TEMA)](trend/README.md#type-tema) -- Triangular Moving Average (TRIMA) +- [Triangular Moving Average (TRIMA)](trend/README.md#type-trima) - Triple Exponential Average (TRIX) -- Typical Price -- Volume Weighted Moving Average (VWMA) +- [Typical Price](trend/README.md#type-typicalprice) +- [Volume Weighted Moving Average (VWMA)](trend/README.md#type-vwma) - Vortex Indicator ### Momentum Indicators @@ -109,7 +109,8 @@ The following list of strategies are currently supported by this package: - [Random Index (KDJ) Strategy](strategy/README.md#type-kdjstrategy) - [Moving Average Convergence Divergence (MACD) Strategy](strategy/README.md#type-macdstrategy) - [Qstick Strategy](strategy/README.md#type-qstickstrategy) -- Volume Weighted Moving Average (VWMA) Strategy +- [Triangular Moving Average (TRIMA) Strategy](strategy/README.md#type-trimastrategy) +- [Volume Weighted Moving Average (VWMA) Strategy](strategy/README.md#type-vwmastrategy) ### Momentum Strategies diff --git a/strategy/README.md b/strategy/README.md index 75f9695..bbb862d 100644 --- a/strategy/README.md +++ b/strategy/README.md @@ -78,6 +78,16 @@ The information provided on this project is strictly for informational purposes - [func \(q \*QstickStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#QstickStrategy.Report>) - [type Result](<#Result>) - [type Strategy](<#Strategy>) +- [type TrimaStrategy](<#TrimaStrategy>) + - [func NewTrimaStrategy\(\) \*TrimaStrategy](<#NewTrimaStrategy>) + - [func \(t \*TrimaStrategy\) Compute\(c \<\-chan \*asset.Snapshot\) \<\-chan Action](<#TrimaStrategy.Compute>) + - [func \(\*TrimaStrategy\) Name\(\) string](<#TrimaStrategy.Name>) + - [func \(t \*TrimaStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#TrimaStrategy.Report>) +- [type VwmaStrategy](<#VwmaStrategy>) + - [func NewVwmaStrategy\(\) \*VwmaStrategy](<#NewVwmaStrategy>) + - [func \(v \*VwmaStrategy\) Compute\(c \<\-chan \*asset.Snapshot\) \<\-chan Action](<#VwmaStrategy.Compute>) + - [func \(\*VwmaStrategy\) Name\(\) string](<#VwmaStrategy.Name>) + - [func \(v \*VwmaStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#VwmaStrategy.Report>) ## Constants @@ -94,6 +104,27 @@ const ( ) ``` + + +```go +const ( + // DefaultTrimaStrategyShortPeriod is the first TRIMA period. + DefaultTrimaStrategyShortPeriod = 20 + + // DefaultTrimaStrategyLongPeriod is the second TRIMA period. + DefaultTrimaStrategyLongPeriod = 50 +) +``` + + + +```go +const ( + // DefaultVwmaStrategyPeriod is the default VWMA period. + DefaultVwmaStrategyPeriod = 20 +) +``` + ## func [ActionsToAnnotations]() @@ -661,4 +692,110 @@ type Strategy interface { } ``` + +## type [TrimaStrategy]() + +TrimaStrategy represents the configuration parameters for calculating the TRIMA strategy. A bullish cross occurs when the short TRIMA moves above the long TRIMA. A bearish cross occurs when the short TRIMA moves below the long TRIME. + +```go +type TrimaStrategy struct { + Strategy + + // Trima1 represents the configuration parameters for calculating the short TRIMA. + Short *trend.Trima[float64] + + // Trima2 represents the configuration parameters for calculating the long TRIMA. + Long *trend.Trima[float64] +} +``` + + +### func [NewTrimaStrategy]() + +```go +func NewTrimaStrategy() *TrimaStrategy +``` + +NewTrimaStrategy function initializes a new TRIMA strategy instance with the default parameters. + + +### func \(\*TrimaStrategy\) [Compute]() + +```go +func (t *TrimaStrategy) Compute(c <-chan *asset.Snapshot) <-chan Action +``` + +Compute processes the provided asset snapshots and generates a stream of actionable recommendations. + + +### func \(\*TrimaStrategy\) [Name]() + +```go +func (*TrimaStrategy) Name() string +``` + +Name returns the name of the strategy. + + +### func \(\*TrimaStrategy\) [Report]() + +```go +func (t *TrimaStrategy) Report(c <-chan *asset.Snapshot) *helper.Report +``` + +Report processes the provided asset snapshots and generates a report annotated with the recommended actions. + + +## type [VwmaStrategy]() + +VwmaStrategy represents the configuration parameters for calculating the VWMA strategy. The VwmaStrategy function uses SMA and VWMA indicators to provide a BUY action when VWMA is above SMA, and a SELL signal when VWMA is below SMA, a HOLD otherwse. + +```go +type VwmaStrategy struct { + Strategy + + // VWMA indicator. + Vwma *trend.Vwma[float64] + + // SMA indicator. + Sma *trend.Sma[float64] +} +``` + + +### func [NewVwmaStrategy]() + +```go +func NewVwmaStrategy() *VwmaStrategy +``` + +NewVwmaStrategy function initializes a new VWMA strategy instance with the default parameters. + + +### func \(\*VwmaStrategy\) [Compute]() + +```go +func (v *VwmaStrategy) Compute(c <-chan *asset.Snapshot) <-chan Action +``` + +Compute processes the provided asset snapshots and generates a stream of actionable recommendations. + + +### func \(\*VwmaStrategy\) [Name]() + +```go +func (*VwmaStrategy) Name() string +``` + +Name returns the name of the strategy. + + +### func \(\*VwmaStrategy\) [Report]() + +```go +func (v *VwmaStrategy) 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/backtest.go b/strategy/backtest.go index 55ab08c..019349a 100644 --- a/strategy/backtest.go +++ b/strategy/backtest.go @@ -117,6 +117,8 @@ func (b *Backtest) allStrategies() []Strategy { NewKdjStrategy(), NewMacdStrategy(), NewQstickStrategy(), + NewTrimaStrategy(), + NewVwmaStrategy(), } } diff --git a/strategy/testdata/trima_strategy.csv b/strategy/testdata/trima_strategy.csv new file mode 100644 index 0000000..d265186 --- /dev/null +++ b/strategy/testdata/trima_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 +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 +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 +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.02 +0,0.01 +0,0.01 +0,0.01 +0,0.02 +0,0.01 +0,0 +0,0.02 +0,0.03 +0,0.03 +0,0.02 +0,0.01 +0,0 +0,0.01 +0,0.02 +0,0.02 +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.01 +0,0 +0,-0 +0,0 +0,0.01 +0,0 +0,0.01 +0,0.03 +0,0.03 +0,0.04 +0,0.05 +0,0.05 +0,0.05 +0,0.04 +0,0.05 +0,0.05 +0,0.06 +0,0.06 +0,0.06 +0,0.06 +0,0.05 +0,0.05 +0,0.04 +0,0.05 +0,0.05 +0,0.05 +0,0.07 +0,0.07 +0,0.07 +0,0.07 +0,0.07 +0,0.07 +0,0.07 +0,0.08 +0,0.07 +0,0.07 +0,0.08 +0,0.08 +0,0.07 +0,0.08 +0,0.08 +0,0.09 +0,0.09 +0,0.09 +0,0.09 +0,0.09 +0,0.1 +0,0.1 +0,0.1 +0,0.11 +0,0.09 +0,0.13 +0,0.14 +0,0.12 +0,0.12 +0,0.12 +0,0.12 +0,0.11 +0,0.11 +0,0.1 +0,0.1 +0,0.1 +0,0.1 +0,0.11 +0,0.11 +0,0.11 +0,0.11 +0,0.12 +0,0.13 +0,0.13 +0,0.13 +0,0.13 +0,0.13 +0,0.13 +0,0.14 +0,0.14 +0,0.15 +0,0.15 +0,0.16 +0,0.15 +0,0.16 +0,0.16 +0,0.15 +0,0.14 +0,0.13 +0,0.13 +0,0.12 +0,0.12 +0,0.12 +0,0.1 +0,0.09 +0,0.07 +0,0.07 +0,0.08 +0,0.08 +-1,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +1,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.08 +0,0.07 diff --git a/strategy/testdata/vwma_strategy.csv b/strategy/testdata/vwma_strategy.csv new file mode 100644 index 0000000..5a35099 --- /dev/null +++ b/strategy/testdata/vwma_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 +1,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 +1,0.02 +0,0.01 +0,0.02 +0,0.03 +0,0.03 +0,0.03 +0,0.03 +0,0.02 +0,0.02 +-1,0.03 +1,0.03 +-1,0.03 +0,0.03 +0,0.03 +0,0.03 +0,0.03 +0,0.03 +0,0.03 +0,0.03 +0,0.03 +0,0.03 +0,0.03 +0,0.03 +0,0.03 +0,0.03 +1,0.03 +0,0.04 +0,0.04 +-1,0.04 +0,0.04 +0,0.04 +0,0.04 +1,0.04 +0,0.02 +0,0.02 +0,0 +-1,-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,-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 +-1,-0.02 +1,-0.02 +-1,-0.01 +0,-0.01 +0,-0.01 +0,-0.01 +1,-0.01 +0,-0.01 +0,-0.01 +0,0 +0,0.01 +0,0.01 +0,0.01 +0,-0.01 +0,-0.02 +-1,-0.02 +0,-0.02 +1,-0.02 +-1,-0.03 +0,-0.03 +0,-0.03 +0,-0.03 +0,-0.03 +0,-0.03 +0,-0.03 +0,-0.03 +0,-0.03 +0,-0.03 +0,-0.03 +0,-0.03 +1,-0.03 +0,-0.03 +0,-0.03 +0,-0.03 +0,-0.04 +0,-0.04 +0,-0.04 +-1,-0.04 +1,-0.04 +0,-0.03 +0,-0.03 +0,-0.03 +0,-0.03 +0,-0.03 +-1,-0.03 +0,-0.03 +0,-0.03 +0,-0.03 +0,-0.03 +0,-0.03 +0,-0.03 +0,-0.03 +0,-0.03 +0,-0.03 +1,-0.03 +0,-0.03 +0,-0.03 +0,-0.03 +-1,-0.03 +1,-0.03 +-1,-0.03 +0,-0.03 +0,-0.03 +0,-0.03 +1,-0.03 +0,-0.02 +0,-0.04 +0,-0.04 +0,-0.04 +0,-0.04 +0,-0.05 +0,-0.05 +0,-0.05 +0,-0.05 +0,-0.05 +0,-0.06 +0,-0.05 +0,-0.05 +0,-0.04 +0,-0.05 +0,-0.04 +0,-0.03 +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.01 +0,-0.01 +0,-0.01 +0,-0.02 +0,-0.02 +0,-0.03 +0,-0.03 +0,-0.04 +0,-0.04 +0,-0.04 +0,-0.06 +0,-0.07 +0,-0.08 +0,-0.08 +0,-0.07 +0,-0.07 +0,-0.07 +0,-0.06 +0,-0.06 +0,-0.07 +-1,-0.07 +0,-0.07 +0,-0.07 +1,-0.07 +0,-0.08 +-1,-0.09 +1,-0.09 +0,-0.08 +0,-0.09 +0,-0.09 +-1,-0.1 +0,-0.1 +0,-0.1 +0,-0.1 +0,-0.1 +1,-0.1 +0,-0.11 +0,-0.12 +0,-0.12 +0,-0.11 +0,-0.1 +0,-0.11 +0,-0.1 +0,-0.09 +0,-0.08 +0,-0.08 +0,-0.08 +0,-0.08 +-1,-0.08 +0,-0.08 +0,-0.08 +0,-0.08 +0,-0.08 diff --git a/strategy/trima_strategy.go b/strategy/trima_strategy.go new file mode 100644 index 0000000..083e9a6 --- /dev/null +++ b/strategy/trima_strategy.go @@ -0,0 +1,120 @@ +// Copyright (c) 2021-2023 Onur Cinar. +// The source code is provided under GNU AGPLv3 License. +// https://github.com/cinar/indicator + +package strategy + +import ( + "github.com/cinar/indicator/asset" + "github.com/cinar/indicator/helper" + "github.com/cinar/indicator/trend" +) + +const ( + // DefaultTrimaStrategyShortPeriod is the first TRIMA period. + DefaultTrimaStrategyShortPeriod = 20 + + // DefaultTrimaStrategyLongPeriod is the second TRIMA period. + DefaultTrimaStrategyLongPeriod = 50 +) + +// TrimaStrategy represents the configuration parameters for calculating the TRIMA strategy. +// A bullish cross occurs when the short TRIMA moves above the long TRIMA. +// A bearish cross occurs when the short TRIMA moves below the long TRIME. +type TrimaStrategy struct { + Strategy + + // Trima1 represents the configuration parameters for calculating the short TRIMA. + Short *trend.Trima[float64] + + // Trima2 represents the configuration parameters for calculating the long TRIMA. + Long *trend.Trima[float64] +} + +// NewTrimaStrategy function initializes a new TRIMA strategy instance +// with the default parameters. +func NewTrimaStrategy() *TrimaStrategy { + t := &TrimaStrategy{ + Short: trend.NewTrima[float64](), + Long: trend.NewTrima[float64](), + } + + t.Short.Period = DefaultTrimaStrategyShortPeriod + t.Long.Period = DefaultTrimaStrategyLongPeriod + + return t +} + +// Name returns the name of the strategy. +func (*TrimaStrategy) Name() string { + return "TRIMA Strategy" +} + +// Compute processes the provided asset snapshots and generates a +// stream of actionable recommendations. +func (t *TrimaStrategy) Compute(c <-chan *asset.Snapshot) <-chan Action { + closings := helper.Duplicate(asset.SnapshotsAsClosings(c), 2) + + shorts := t.Short.Compute(closings[0]) + longs := t.Long.Compute(closings[1]) + + shorts = helper.Skip(shorts, t.Long.IdlePeriod()-t.Short.IdlePeriod()) + + actions := NormalizeActions(helper.Operate(shorts, longs, func(short, long float64) Action { + if short > long { + return Buy + } + + if long > short { + return Sell + } + + return Hold + })) + + // TRIMA starts only after the a full periods for each EMA used. + actions = helper.Shift(actions, t.Long.IdlePeriod(), Hold) + + return actions +} + +// Report processes the provided asset snapshots and generates a +// report annotated with the recommended actions. +func (t *TrimaStrategy) Report(c <-chan *asset.Snapshot) *helper.Report { + // + // snapshots[0] -> dates + // snapshots[1] -> closings[0] -> shorts + // closings[1] -> longs + // closings[2] -> closings + // snapshots[2] -> actions -> annotations + // -> outcomes + // + snapshots := helper.Duplicate(c, 3) + + dates := asset.SnapshotsAsDates(snapshots[0]) + closings := helper.Duplicate(asset.SnapshotsAsClosings(snapshots[1]), 3) + + shorts := t.Short.Compute(closings[0]) + longs := t.Long.Compute(closings[1]) + + shorts = helper.Skip(shorts, t.Long.IdlePeriod()-t.Short.IdlePeriod()) + shorts = helper.Shift(shorts, t.Long.IdlePeriod(), 0) + longs = helper.Shift(longs, t.Long.IdlePeriod(), 0) + + actions, outcomes := ComputeWithOutcome(t, snapshots[2]) + annotations := ActionsToAnnotations(actions) + outcomes = helper.MultiplyBy(outcomes, 100) + + report := helper.NewReport(t.Name(), dates) + report.AddChart() + report.AddChart() + + report.AddColumn(helper.NewNumericReportColumn("Close", closings[2])) + report.AddColumn(helper.NewNumericReportColumn("Short", shorts), 1) + report.AddColumn(helper.NewNumericReportColumn("Long", longs), 1) + report.AddColumn(helper.NewAnnotationReportColumn(annotations), 0, 1) + + report.AddColumn(helper.NewNumericReportColumn("Outcome", outcomes), 2) + + return report +} diff --git a/strategy/trima_strategy_test.go b/strategy/trima_strategy_test.go new file mode 100644 index 0000000..edfa5b6 --- /dev/null +++ b/strategy/trima_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 strategy_test + +import ( + "os" + "testing" + + "github.com/cinar/indicator/asset" + "github.com/cinar/indicator/helper" + "github.com/cinar/indicator/strategy" +) + +func TestTrimaStrategy(t *testing.T) { + snapshots, err := helper.ReadFromCsvFile[asset.Snapshot]("testdata/repository/brk-b.csv", true) + if err != nil { + t.Fatal(err) + } + + results, err := helper.ReadFromCsvFile[strategy.Result]("testdata/trima_strategy.csv", true) + if err != nil { + t.Fatal(err) + } + + trima := strategy.NewTrimaStrategy() + actions, outcomes := strategy.ComputeWithOutcome(trima, snapshots) + + outcomes = helper.RoundDigits(outcomes, 2) + + err = strategy.CheckResults(results, actions, outcomes) + if err != nil { + t.Fatal(err) + } +} + +func TestTrimaStrategyReport(t *testing.T) { + snapshots, err := helper.ReadFromCsvFile[asset.Snapshot]("testdata/repository/brk-b.csv", true) + if err != nil { + t.Fatal(err) + } + + trima := strategy.NewTrimaStrategy() + + report := trima.Report(snapshots) + + fileName := "trima_strategy.html" + defer os.Remove(fileName) + + err = report.WriteToFile(fileName) + if err != nil { + t.Fatal(err) + } +} diff --git a/strategy/vwma_strategy.go b/strategy/vwma_strategy.go new file mode 100644 index 0000000..18b635b --- /dev/null +++ b/strategy/vwma_strategy.go @@ -0,0 +1,120 @@ +// Copyright (c) 2021-2023 Onur Cinar. +// The source code is provided under GNU AGPLv3 License. +// https://github.com/cinar/indicator + +package strategy + +import ( + "github.com/cinar/indicator/asset" + "github.com/cinar/indicator/helper" + "github.com/cinar/indicator/trend" +) + +const ( + // DefaultVwmaStrategyPeriod is the default VWMA period. + DefaultVwmaStrategyPeriod = 20 +) + +// VwmaStrategy represents the configuration parameters for calculating the VWMA strategy. +// The VwmaStrategy function uses SMA and VWMA indicators to provide a BUY action when +// VWMA is above SMA, and a SELL signal when VWMA is below SMA, a HOLD otherwse. +type VwmaStrategy struct { + Strategy + + // VWMA indicator. + Vwma *trend.Vwma[float64] + + // SMA indicator. + Sma *trend.Sma[float64] +} + +// NewVwmaStrategy function initializes a new VWMA strategy instance with the default parameters. +func NewVwmaStrategy() *VwmaStrategy { + v := &VwmaStrategy{ + Vwma: trend.NewVwma[float64](), + Sma: trend.NewSma[float64](), + } + + v.Vwma.Period = DefaultVwmaStrategyPeriod + v.Sma.Period = DefaultVwmaStrategyPeriod + + return v +} + +// Name returns the name of the strategy. +func (*VwmaStrategy) Name() string { + return "VWMA Strategy" +} + +// Compute processes the provided asset snapshots and generates a stream of actionable recommendations. +func (v *VwmaStrategy) Compute(c <-chan *asset.Snapshot) <-chan Action { + smas, vwmas := v.calculateSmaAndVwma(c) + + actions := NormalizeActions(helper.Operate(smas, vwmas, func(sma, vwma float64) Action { + if vwma > sma { + return Buy + } + + if sma > vwma { + return Sell + } + + return Hold + })) + + // VWMA starts only after the a full period. + actions = helper.Shift(actions, v.Vwma.Period-1, Hold) + + return actions +} + +// Report processes the provided asset snapshots and generates a +// report annotated with the recommended actions. +func (v *VwmaStrategy) Report(c <-chan *asset.Snapshot) *helper.Report { + // + // snapshots[0] -> dates + // snapshots[1] -> closings + // snapshots[2] -> sma + // vwma + // snapshots[3] -> actions -> annotations + // -> outcomes + // + snapshots := helper.Duplicate(c, 4) + + dates := asset.SnapshotsAsDates(snapshots[0]) + closings := asset.SnapshotsAsClosings(snapshots[1]) + + smas, vwmas := v.calculateSmaAndVwma(snapshots[2]) + smas = helper.Shift(smas, v.Vwma.Period-1, 0) + vwmas = helper.Shift(vwmas, v.Vwma.Period-1, 0) + + actions, outcomes := ComputeWithOutcome(v, snapshots[3]) + annotations := ActionsToAnnotations(actions) + outcomes = helper.MultiplyBy(outcomes, 100) + + report := helper.NewReport(v.Name(), dates) + report.AddChart() + report.AddChart() + + report.AddColumn(helper.NewNumericReportColumn("Close", closings)) + report.AddColumn(helper.NewNumericReportColumn("SMA", smas), 1) + report.AddColumn(helper.NewNumericReportColumn("VWMA", vwmas), 1) + report.AddColumn(helper.NewAnnotationReportColumn(annotations), 0, 1) + + report.AddColumn(helper.NewNumericReportColumn("Outcome", outcomes), 2) + + return report +} + +// calculateSmaAndVwma calculates the SMA and VWMA using the given channel of snapshots. +func (v *VwmaStrategy) calculateSmaAndVwma(c <-chan *asset.Snapshot) (<-chan float64, <-chan float64) { + snapshots := helper.Duplicate(c, 2) + + closings := helper.Duplicate(asset.SnapshotsAsClosings(snapshots[0]), 2) + volume := helper.Map(snapshots[1], func(s *asset.Snapshot) float64 { return float64(s.Volume) }) + + smas := v.Sma.Compute(closings[0]) + vwmas := v.Vwma.Compute(closings[1], volume) + + return smas, vwmas +} diff --git a/strategy/vwma_strategy_test.go b/strategy/vwma_strategy_test.go new file mode 100644 index 0000000..beec300 --- /dev/null +++ b/strategy/vwma_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 strategy_test + +import ( + "os" + "testing" + + "github.com/cinar/indicator/asset" + "github.com/cinar/indicator/helper" + "github.com/cinar/indicator/strategy" +) + +func TestVwmaStrategy(t *testing.T) { + snapshots, err := helper.ReadFromCsvFile[asset.Snapshot]("testdata/repository/brk-b.csv", true) + if err != nil { + t.Fatal(err) + } + + results, err := helper.ReadFromCsvFile[strategy.Result]("testdata/vwma_strategy.csv", true) + if err != nil { + t.Fatal(err) + } + + vwma := strategy.NewVwmaStrategy() + actions, outcomes := strategy.ComputeWithOutcome(vwma, snapshots) + + outcomes = helper.RoundDigits(outcomes, 2) + + err = strategy.CheckResults(results, actions, outcomes) + if err != nil { + t.Fatal(err) + } +} + +func TestVwmaStrategyReport(t *testing.T) { + snapshots, err := helper.ReadFromCsvFile[asset.Snapshot]("testdata/repository/brk-b.csv", true) + if err != nil { + t.Fatal(err) + } + + vwma := strategy.NewVwmaStrategy() + + report := vwma.Report(snapshots) + + fileName := "vwma_strategy.html" + defer os.Remove(fileName) + + err = report.WriteToFile(fileName) + if err != nil { + t.Fatal(err) + } +} diff --git a/trend/README.md b/trend/README.md index 1055c2c..c8639ce 100644 --- a/trend/README.md +++ b/trend/README.md @@ -69,6 +69,17 @@ The information provided on this project is strictly for informational purposes - [func NewTema\[T helper.Number\]\(\) \*Tema\[T\]](<#NewTema>) - [func \(t \*Tema\[T\]\) Compute\(c \<\-chan T\) \<\-chan T](<#Tema[T].Compute>) - [func \(t \*Tema\[T\]\) IdlePeriod\(\) int](<#Tema[T].IdlePeriod>) +- [type Trima](<#Trima>) + - [func NewTrima\[T helper.Number\]\(\) \*Trima\[T\]](<#NewTrima>) + - [func \(t \*Trima\[T\]\) Compute\(c \<\-chan T\) \<\-chan T](<#Trima[T].Compute>) + - [func \(t \*Trima\[T\]\) IdlePeriod\(\) int](<#Trima[T].IdlePeriod>) +- [type TypicalPrice](<#TypicalPrice>) + - [func NewTypicalPrice\[T helper.Number\]\(\) \*TypicalPrice\[T\]](<#NewTypicalPrice>) + - [func \(t \*TypicalPrice\[T\]\) Compute\(high, low, closing \<\-chan T\) \<\-chan T](<#TypicalPrice[T].Compute>) +- [type Vwma](<#Vwma>) + - [func NewVwma\[T helper.Number\]\(\) \*Vwma\[T\]](<#NewVwma>) + - [func \(v \*Vwma\[T\]\) Compute\(closing, volume \<\-chan T\) \<\-chan T](<#Vwma[T].Compute>) + - [func \(v \*Vwma\[T\]\) IdlePeriod\(\) int](<#Vwma[T].IdlePeriod>) ## Constants @@ -167,6 +178,24 @@ const ( ) ``` + + +```go +const ( + // DefaultTrimaPeriod is the default period for TRIMA. + DefaultTrimaPeriod = 15 +) +``` + + + +```go +const ( + // DefaultVwmaPeriod is the default period for the VWMA. + DefaultVwmaPeriod = 20 +) +``` + ## type [Apo]() @@ -742,4 +771,130 @@ func (t *Tema[T]) IdlePeriod() int IdlePeriod is the initial period that TEMA won't yield any results. + +## type [Trima]() + +Trima represents the configuration parameters for calculating the Triangular Moving Average \(TRIMA\). + +If period is even: + +``` +TRIMA = SMA(period / 2, SMA((period / 2) + 1, values)) +``` + +If period is odd: + +``` +TRIMA = SMA((period + 1) / 2, SMA((period + 1) / 2, values)) +``` + +```go +type Trima[T helper.Number] struct { + // Time period. + Period int +} +``` + + +### func [NewTrima]() + +```go +func NewTrima[T helper.Number]() *Trima[T] +``` + +NewTrima function initializes a new TRIMA instance with the default parameters. + + +### func \(\*Trima\[T\]\) [Compute]() + +```go +func (t *Trima[T]) Compute(c <-chan T) <-chan T +``` + +Compute function takes a channel of numbers and computes the TRIMA and the signal line. + + +### func \(\*Trima\[T\]\) [IdlePeriod]() + +```go +func (t *Trima[T]) IdlePeriod() int +``` + +IdlePeriod is the initial period that TRIMA won't yield any results. + + +## type [TypicalPrice]() + +TypicalPrice represents the configuration parameters for calculating the Typical Price. It is another approximation of average price for each period and can be used as a filter for moving average systems. + +``` +Typical Price = (High + Low + Closing) / 3 +``` + +```go +type TypicalPrice[T helper.Number] struct { +} +``` + + +### func [NewTypicalPrice]() + +```go +func NewTypicalPrice[T helper.Number]() *TypicalPrice[T] +``` + +NewTypicalPrice function initializes a new Typical Price instance with the default parameters. + + +### func \(\*TypicalPrice\[T\]\) [Compute]() + +```go +func (t *TypicalPrice[T]) Compute(high, low, closing <-chan T) <-chan T +``` + +Compute function takes a channel of numbers and computes the Typical Price and the signal line. + + +## type [Vwma]() + +Vwma represents the configuration parameters for calculating the Volume Weighted Moving Average \(VWMA\) It averages the price data with an emphasis on volume, meaning areas with higher volume will have a greater weight. + +``` +VWMA = Sum(Price * Volume) / Sum(Volume) +``` + +```go +type Vwma[T helper.Number] struct { + // Time period. + Period int +} +``` + + +### func [NewVwma]() + +```go +func NewVwma[T helper.Number]() *Vwma[T] +``` + +NewVwma function initializes a new VWMA instance with the default parameters. + + +### func \(\*Vwma\[T\]\) [Compute]() + +```go +func (v *Vwma[T]) Compute(closing, volume <-chan T) <-chan T +``` + +Compute function takes a channel of numbers and computes the VWMA and the signal line. + + +### func \(\*Vwma\[T\]\) [IdlePeriod]() + +```go +func (v *Vwma[T]) IdlePeriod() int +``` + +IdlePeriod is the initial period that VWMA won't yield any results. + Generated by [gomarkdoc]() diff --git a/trend/testdata/trima_even.csv b/trend/testdata/trima_even.csv new file mode 100644 index 0000000..0e0ae3c --- /dev/null +++ b/trend/testdata/trima_even.csv @@ -0,0 +1,252 @@ +Close,Trima +318.600006,0 +315.839996,0 +316.149994,0 +310.570007,0 +307.779999,0 +305.820007,0 +305.98999,0 +306.390015,0 +311.450012,0 +312.329987,0 +309.290009,0 +301.910004,0 +300,0 +300.029999,0 +302,0 +307.820007,0 +302.690002,0 +306.48999,0 +305.549988,0 +303.429993,306.73 +309.059998,306.08 +308.899994,305.57 +309.910004,305.28 +314.549988,305.27 +312.899994,305.46 +318.690002,305.79 +315.529999,306.23 +316.350006,306.78 +320.369995,307.47 +318.929993,308.35 +317.640015,309.39 +314.859985,310.49 +308.299988,311.51 +305.230011,312.35 +309.869995,313.04 +310.420013,313.55 +311.299988,313.92 +311.899994,314.14 +310.950012,314.18 +309.170013,314 +307.329987,313.58 +311.519989,313.06 +310.570007,312.5 +311.859985,312.02 +308.51001,311.6 +308.429993,311.2 +312.970001,310.89 +308.480011,310.59 +307.209991,310.29 +309.890015,310.08 +313.73999,310.02 +310.790009,310.05 +309.630005,310.1 +308.179993,310.1 +308.23999,310.03 +302.720001,309.92 +303.160004,309.75 +303.070007,309.51 +304.019989,309.27 +304.660004,309.01 +305.179993,308.68 +304.619995,308.22 +307.75,307.76 +312.450012,307.35 +316.970001,307.04 +311.119995,306.82 +311.369995,306.72 +304.820007,306.73 +303.630005,306.79 +302.880005,306.85 +305.329987,306.97 +297.880005,307.1 +302.01001,307.24 +293.51001,307.22 +301.059998,307.02 +303.850006,306.67 +299.730011,306.14 +298.369995,305.48 +298.920013,304.75 +302.140015,304.03 +302.320007,303.29 +305.299988,302.62 +305.079987,302.04 +308.769989,301.65 +310.309998,301.52 +309.070007,301.58 +310.390015,301.8 +312.51001,302.25 +312.619995,302.89 +313.700012,303.68 +314.549988,304.58 +318.049988,305.63 +319.73999,306.74 +323.790009,307.97 +324.630005,309.18 +323.089996,310.44 +323.820007,311.77 +324.329987,313.11 +326.049988,314.45 +324.339996,315.75 +320.529999,317.01 +326.230011,318.23 +328.549988,319.42 +330.170013,320.53 +325.859985,321.51 +323.220001,322.36 +320,323.06 +323.880005,323.62 +326.140015,324.08 +324.869995,324.43 +322.98999,324.7 +322.640015,324.88 +322.48999,324.93 +323.529999,324.84 +323.75,324.67 +327.390015,324.54 +329.76001,324.48 +330.390015,324.53 +329.130005,324.6 +323.109985,324.66 +320.200012,324.68 +319.019989,324.66 +320.600006,324.64 +322.190002,324.67 +321.079987,324.74 +323.119995,324.78 +329.480011,324.79 +328.579987,324.69 +333.410004,324.57 +335.420013,324.54 +335.950012,324.66 +335.290009,324.96 +333.600006,325.41 +336.390015,326 +335.899994,326.74 +339.820007,327.66 +338.309998,328.7 +338.670013,329.83 +338.609985,331.02 +336.959991,332.19 +335.25,333.25 +334.119995,334.14 +335.339996,334.91 +334.149994,335.54 +336.910004,336.04 +341,336.43 +342,336.69 +341.559998,336.9 +341.459991,337.05 +340.899994,337.18 +341.130005,337.36 +343.369995,337.62 +345.350006,337.99 +343.540009,338.42 +341.089996,338.91 +344.25,339.43 +345.339996,339.96 +342.429993,340.47 +346.609985,341 +345.76001,341.54 +349.630005,342.13 +347.579987,342.71 +349.799988,343.23 +349.309998,343.73 +349.809998,344.21 +351.959991,344.73 +352.26001,345.28 +351.190002,345.88 +353.809998,346.53 +349.98999,347.18 +362.579987,347.9 +363.730011,348.69 +358.019989,349.52 +356.980011,350.38 +358.350006,351.26 +358.480011,352.12 +354.5,352.94 +354.109985,353.71 +353.190002,354.4 +352.559998,355.05 +352.089996,355.57 +350.570007,355.85 +354.26001,355.94 +354.299988,355.94 +355.929993,355.85 +355.549988,355.65 +358.290009,355.43 +361.059998,355.25 +360.200012,355.1 +362.459991,355.06 +360.470001,355.06 +361.670013,355.26 +361.799988,355.65 +363.149994,356.16 +365.519989,356.77 +367.779999,357.52 +367.820007,358.38 +369.5,359.29 +367.859985,360.2 +370.429993,361.12 +370.480011,362.04 +366.820007,362.93 +363.279999,363.74 +360.160004,364.44 +361.709991,365.04 +359.420013,365.47 +357.779999,365.7 +357.059998,365.72 +350.299988,365.52 +348.079987,365.03 +343.040009,364.23 +343.690002,363.13 +345.059998,361.81 +346.339996,360.35 +345.450012,358.78 +348.559998,357.14 +348.429993,355.49 +345.660004,353.82 +345.089996,352.23 +346.230011,350.77 +345.390015,349.55 +340.890015,348.54 +338.660004,347.69 +335.859985,346.91 +336.839996,346.17 +338.630005,345.49 +336.899994,344.81 +336.160004,344.12 +331.709991,343.42 +337.410004,342.68 +341.329987,341.93 +343.75,341.18 +349.019989,340.55 +351.809998,340.12 +346.630005,339.87 +346.170013,339.77 +346.299988,339.85 +348.179993,340.15 +350.559998,340.7 +350.01001,341.48 +354.25,342.47 +356.790009,343.61 +359.859985,344.82 +358.929993,346 +361.329987,347.17 +361,348.39 +361.799988,349.68 +362.679993,351.01 +361.339996,352.34 +360.049988,353.58 +358.690002,354.75 diff --git a/trend/testdata/trima_odd.csv b/trend/testdata/trima_odd.csv new file mode 100644 index 0000000..001e863 --- /dev/null +++ b/trend/testdata/trima_odd.csv @@ -0,0 +1,252 @@ +Close,Trima +318.600006,0 +315.839996,0 +316.149994,0 +310.570007,0 +307.779999,0 +305.820007,0 +305.98999,0 +306.390015,0 +311.450012,0 +312.329987,0 +309.290009,0 +301.910004,0 +300,0 +300.029999,0 +302,308.1 +307.820007,307.44 +302.690002,306.75 +306.48999,306.03 +305.549988,305.35 +303.429993,304.84 +309.059998,304.59 +308.899994,304.56 +309.910004,304.73 +314.549988,304.97 +312.899994,305.51 +318.690002,306.34 +315.529999,307.38 +316.350006,308.59 +320.369995,309.85 +318.929993,311.12 +317.640015,312.38 +314.859985,313.55 +308.299988,314.49 +305.230011,315.02 +309.869995,315.31 +310.420013,315.31 +311.299988,314.99 +311.899994,314.4 +310.950012,313.58 +309.170013,312.67 +307.329987,311.82 +311.519989,311.28 +310.570007,310.84 +311.859985,310.51 +308.51001,310.28 +308.429993,310.1 +312.970001,310.06 +308.480011,310.1 +307.209991,310.16 +309.890015,310.08 +313.73999,310.05 +310.790009,309.98 +309.630005,309.97 +308.179993,310.01 +308.23999,309.94 +302.720001,309.8 +303.160004,309.59 +303.070007,309.31 +304.019989,308.82 +304.660004,308.25 +305.179993,307.59 +304.619995,306.89 +307.75,306.25 +312.450012,305.85 +316.970001,305.73 +311.119995,305.84 +311.369995,306.22 +304.820007,306.7 +303.630005,307.22 +302.880005,307.78 +305.329987,308.3 +297.880005,308.44 +302.01001,308.13 +293.51001,307.42 +301.059998,306.44 +303.850006,305.44 +299.730011,304.4 +298.369995,303.32 +298.920013,302.17 +302.140015,301.32 +302.320007,300.71 +305.299988,300.56 +305.079987,300.63 +308.769989,300.79 +310.309998,301.18 +309.070007,301.81 +310.390015,302.72 +312.51001,303.72 +312.619995,304.88 +313.700012,305.99 +314.549988,307.18 +318.049988,308.44 +319.73999,309.68 +323.790009,310.98 +324.630005,312.33 +323.089996,313.68 +323.820007,315.04 +324.329987,316.44 +326.049988,317.87 +324.339996,319.26 +320.529999,320.51 +326.230011,321.56 +328.549988,322.46 +330.170013,323.3 +325.859985,324 +323.220001,324.51 +320,324.75 +323.880005,324.89 +326.140015,325.1 +324.869995,325.25 +322.98999,325.25 +322.640015,325.03 +322.48999,324.72 +323.529999,324.43 +323.75,324.3 +327.390015,324.22 +329.76001,324.12 +330.390015,324.12 +329.130005,324.31 +323.109985,324.62 +320.200012,324.95 +319.019989,325.2 +320.600006,325.35 +322.190002,325.36 +321.079987,325.18 +323.119995,324.79 +329.480011,324.32 +328.579987,323.93 +333.410004,323.77 +335.420013,323.95 +335.950012,324.41 +335.290009,325.16 +333.600006,326.24 +336.390015,327.64 +335.899994,329.14 +339.820007,330.72 +338.309998,332.18 +338.670013,333.43 +338.609985,334.48 +336.959991,335.36 +335.25,336.06 +334.119995,336.52 +335.339996,336.87 +334.149994,336.96 +336.910004,336.95 +341,336.93 +342,336.91 +341.559998,336.94 +341.459991,337.05 +340.899994,337.29 +341.130005,337.64 +343.369995,338.21 +345.350006,338.94 +343.540009,339.68 +341.089996,340.35 +344.25,340.98 +345.339996,341.58 +342.429993,342.1 +346.609985,342.61 +345.76001,343.02 +349.630005,343.36 +347.579987,343.73 +349.799988,344.24 +349.309998,344.8 +349.809998,345.36 +351.959991,346.05 +352.26001,346.74 +351.190002,347.47 +353.809998,348.21 +349.98999,348.92 +362.579987,349.69 +363.730011,350.61 +358.019989,351.59 +356.980011,352.5 +358.350006,353.41 +358.480011,354.36 +354.5,355.25 +354.109985,356.16 +353.190002,356.73 +352.559998,356.9 +352.089996,356.85 +350.570007,356.62 +354.26001,356.23 +354.299988,355.66 +355.929993,355.11 +355.549988,354.51 +358.290009,354.14 +361.059998,354.07 +360.200012,354.23 +362.459991,354.67 +360.470001,355.27 +361.670013,356.05 +361.799988,356.9 +363.149994,357.85 +365.519989,358.83 +367.779999,359.78 +367.820007,360.73 +369.5,361.6 +367.859985,362.49 +370.429993,363.4 +370.480011,364.35 +366.820007,365.24 +363.279999,365.99 +360.160004,366.51 +361.709991,366.81 +359.420013,366.85 +357.779999,366.62 +357.059998,366.04 +350.299988,365 +348.079987,363.62 +343.040009,361.96 +343.690002,360.16 +345.059998,358.19 +346.339996,356.18 +345.450012,354.13 +348.559998,352.16 +348.429993,350.47 +345.660004,349.04 +345.089996,347.96 +346.230011,347.18 +345.390015,346.66 +340.890015,346.26 +338.660004,345.94 +335.859985,345.56 +336.839996,345.03 +338.630005,344.43 +336.899994,343.66 +336.160004,342.7 +331.709991,341.52 +337.410004,340.37 +341.329987,339.37 +343.75,338.69 +349.019989,338.38 +351.809998,338.39 +346.630005,338.68 +346.170013,339.28 +346.299988,340.33 +348.179993,341.59 +350.559998,342.96 +350.01001,344.3 +354.25,345.54 +356.790009,346.64 +359.859985,347.8 +358.929993,349.01 +361.329987,350.22 +361,351.46 +361.799988,352.74 +362.679993,354.11 +361.339996,355.51 +360.049988,356.89 +358.690002,358.04 diff --git a/trend/testdata/typical_price.csv b/trend/testdata/typical_price.csv new file mode 100644 index 0000000..88f7a0e --- /dev/null +++ b/trend/testdata/typical_price.csv @@ -0,0 +1,252 @@ +High,Low,Close,TypicalPrice +318.600006,308.700012,318.600006,315.3 +319.559998,313.299988,315.839996,316.23 +316.380005,312.75,316.149994,315.09 +315.660004,308.730011,310.570007,311.65 +310.290009,306.350006,307.779999,308.14 +309.380005,304.920013,305.820007,306.71 +307.48999,305.089996,305.98999,306.19 +308.339996,304.709991,306.390015,306.48 +311.910004,305.459991,311.450012,309.61 +318.910004,310.820007,312.329987,314.02 +316.359985,308.399994,309.290009,311.35 +306.959991,299.450012,301.910004,302.77 +302.470001,297.76001,300,300.08 +301.480011,297.149994,300.029999,299.55 +304.190002,297,302,301.06 +308.540009,304.160004,307.820007,306.84 +306.5,297.640015,302.690002,302.28 +306.570007,300.929993,306.48999,304.66 +308.579987,304.649994,305.549988,306.26 +307.459991,303.26001,303.429993,304.72 +309.380005,305.23999,309.059998,307.89 +309.040009,305.619995,308.899994,307.85 +312.390015,307.380005,309.910004,309.89 +316.890015,311.25,314.549988,314.23 +314.230011,310,312.899994,312.38 +320.160004,313.380005,318.690002,317.41 +320.5,314.75,315.529999,316.93 +316.799988,313.339996,316.350006,315.5 +320.570007,316.600006,320.369995,319.18 +321.320007,317.720001,318.929993,319.32 +318.420013,315.790009,317.640015,317.28 +318.519989,314.25,314.859985,315.88 +315.540009,307.75,308.299988,310.53 +307.23999,303.859985,305.230011,305.44 +310.01001,304.359985,309.869995,308.08 +312.730011,306.850006,310.420013,310 +312.829987,307.5,311.299988,310.54 +312.549988,307.709991,311.899994,310.72 +313.679993,309.579987,310.950012,311.4 +311.730011,308.339996,309.170013,309.75 +309.51001,306.809998,307.329987,307.88 +311.859985,305.790009,311.519989,309.72 +312.670013,306.380005,310.570007,309.87 +312.600006,308.299988,311.859985,310.92 +311.549988,305.920013,308.51001,308.66 +308.799988,305.600006,308.429993,307.61 +314.149994,306.630005,312.970001,311.25 +313.410004,308.01001,308.480011,309.97 +311.420013,306.98999,307.209991,308.54 +309.980011,305.279999,309.890015,308.38 +313.73999,309.619995,313.73999,312.37 +314.100006,309.040009,310.790009,311.31 +310.369995,308.279999,309.630005,309.43 +310.200012,306.869995,308.179993,308.42 +308.410004,305.480011,308.23999,307.38 +307.299988,300.5,302.720001,303.51 +305.269989,301.769989,303.160004,303.4 +305.559998,300.25,303.070007,302.96 +305.619995,300.01001,304.019989,303.22 +305.779999,302.01001,304.660004,304.15 +306.149994,303.410004,305.179993,304.91 +305.619995,302.079987,304.619995,304.11 +308.100006,301.450012,307.75,305.77 +312.660004,308.5,312.450012,311.2 +317.290009,312.429993,316.970001,315.56 +316.5,310.230011,311.119995,312.62 +312.679993,309.25,311.369995,311.1 +313.179993,303.940002,304.820007,307.31 +306.720001,301.920013,303.630005,304.09 +306.589996,300.76001,302.880005,303.41 +307.549988,301.679993,305.329987,304.85 +300.549988,294.899994,297.880005,297.78 +304.429993,295.359985,302.01001,300.6 +301.299988,292.420013,293.51001,295.74 +301.51001,295.059998,301.059998,299.21 +305.630005,302.25,303.850006,303.91 +307.049988,299.649994,299.730011,302.14 +302.079987,296.299988,298.369995,298.92 +299.5,293.390015,298.920013,297.27 +303.209991,298.970001,302.140015,301.44 +302.720001,300.589996,302.320007,301.88 +305.380005,303.359985,305.299988,304.68 +307.470001,302.579987,305.079987,305.04 +308.809998,304.98999,308.769989,307.52 +311.5,308.23999,310.309998,310.02 +311,307.070007,309.070007,309.05 +311.070007,307.850006,310.390015,309.77 +313.220001,309.049988,312.51001,311.59 +313.700012,310.329987,312.619995,312.22 +315.940002,311.769989,313.700012,313.8 +316.920013,313.720001,314.549988,315.06 +318.809998,313.26001,318.049988,316.71 +321.880005,318.119995,319.73999,319.91 +323.980011,319,323.790009,322.26 +325.720001,322.5,324.630005,324.28 +324.549988,322.76001,323.089996,323.47 +324.369995,321.320007,323.820007,323.17 +324.850006,321.609985,324.329987,323.6 +326.399994,324.299988,326.049988,325.58 +327.100006,324.109985,324.339996,325.18 +323.73999,319,320.529999,321.09 +326.910004,322.109985,326.230011,325.08 +328.809998,325.190002,328.549988,327.52 +331.839996,328.570007,330.170013,330.19 +330.25,322.76001,325.859985,326.29 +328.070007,323.059998,323.220001,324.78 +325.98999,317.410004,320,321.13 +325.160004,322.619995,323.880005,323.89 +330.690002,325.790009,326.140015,327.54 +326.880005,323.480011,324.869995,325.08 +326.160004,320.149994,322.98999,323.1 +322.959991,319.809998,322.640015,321.8 +324.23999,320.540009,322.48999,322.42 +323.829987,320.130005,323.529999,322.5 +324.690002,322.359985,323.75,323.6 +328.26001,324.820007,327.390015,326.82 +329.980011,325.850006,329.76001,328.53 +333.940002,329.119995,330.390015,331.15 +331.48999,328.350006,329.130005,329.66 +329.269989,322.970001,323.109985,325.12 +323,319.559998,320.200012,320.92 +320.559998,317.709991,319.019989,319.1 +322.630005,319.670013,320.600006,320.97 +322.470001,319,322.190002,321.22 +322.410004,319.390015,321.079987,320.96 +323.220001,319.529999,323.119995,321.96 +330.670013,324.420013,329.480011,328.19 +330.890015,327.570007,328.579987,329.01 +334.160004,328.679993,333.410004,332.08 +335.820007,331.429993,335.420013,334.22 +336.320007,334.100006,335.950012,335.46 +337.589996,334.920013,335.290009,335.93 +335.350006,332.220001,333.600006,333.72 +336.619995,332.200012,336.390015,335.07 +340.380005,334.089996,335.899994,336.79 +341.679993,335.540009,339.820007,339.01 +341.299988,337.660004,338.309998,339.09 +339.279999,336.619995,338.670013,338.19 +341.350006,336.369995,338.609985,338.78 +338.850006,335.660004,336.959991,337.16 +337.470001,334.190002,335.25,335.64 +335.829987,331.839996,334.119995,333.93 +336.730011,334.369995,335.339996,335.48 +336.399994,332.609985,334.149994,334.39 +337.01001,334.140015,336.910004,336.02 +342.5,338.399994,341,340.63 +342.079987,338.410004,342,340.83 +341.890015,338.700012,341.559998,340.72 +341.799988,338.910004,341.459991,340.72 +344.070007,340.390015,340.899994,341.79 +343.480011,339.869995,341.130005,341.49 +343.839996,340.929993,343.369995,342.71 +346.440002,344.309998,345.350006,345.37 +346.209991,343.450012,343.540009,344.4 +345,340.51001,341.089996,342.2 +345.720001,341.089996,344.25,343.69 +347.25,343.540009,345.339996,345.38 +345.380005,341.98999,342.429993,343.27 +346.790009,342.850006,346.609985,345.42 +347.619995,345.100006,345.76001,346.16 +351.190002,346.279999,349.630005,349.03 +349.660004,345.540009,347.579987,347.59 +351.089996,347.519989,349.799988,349.47 +351.269989,348.600006,349.309998,349.73 +351,348.320007,349.809998,349.71 +352.329987,350.209991,351.959991,351.5 +353.420013,351.25,352.26001,352.31 +352.890015,349.690002,351.190002,351.26 +354.470001,349.420013,353.809998,352.57 +355.109985,349.390015,349.98999,351.5 +364.630005,355.149994,362.579987,360.79 +364.25,358.850006,363.730011,362.28 +364.429993,356.059998,358.019989,359.5 +362.350006,355.920013,356.980011,358.42 +359.25,353.200012,358.350006,356.93 +358.950012,356.809998,358.480011,358.08 +357.920013,353.670013,354.5,355.36 +358.720001,353.380005,354.109985,355.4 +356.299988,351.880005,353.190002,353.79 +354.299988,351.25,352.559998,352.7 +354.179993,349.609985,352.089996,351.96 +353.5,349.660004,350.570007,351.24 +354.320007,351.540009,354.26001,353.37 +357.230011,354.130005,354.299988,355.22 +357.350006,352.920013,355.929993,355.4 +358.410004,354.529999,355.549988,356.16 +358.589996,354.01001,358.290009,356.96 +362.679993,358.600006,361.059998,360.78 +362.470001,359.25,360.200012,360.64 +363.390015,360.600006,362.459991,362.15 +366.470001,360,360.470001,362.31 +362.799988,359.26001,361.670013,361.24 +363.299988,360.869995,361.799988,361.99 +364.829987,361.769989,363.149994,363.25 +366.609985,364.51001,365.519989,365.55 +370.429993,365.470001,367.779999,367.89 +370.839996,365.970001,367.820007,368.21 +370.220001,368.26001,369.5,369.33 +370.200012,367.519989,367.859985,368.53 +371.329987,367.790009,370.429993,369.85 +373.339996,368.459991,370.480011,370.76 +371.339996,366.730011,366.820007,368.3 +367.200012,362.940002,363.279999,364.47 +363.420013,359.76001,360.160004,361.11 +361.890015,357.269989,361.709991,360.29 +360.790009,357.950012,359.420013,359.39 +360.519989,354.269989,357.779999,357.52 +359.470001,356.670013,357.059998,357.73 +357.5,348.549988,350.299988,352.12 +350,345.410004,348.079987,347.83 +348.23999,342.130005,343.040009,344.47 +344.01001,339.51001,343.690002,342.4 +345.940002,342.369995,345.059998,344.46 +348.76001,341.859985,346.339996,345.65 +345.899994,342.829987,345.450012,344.73 +349.51001,345.5,348.559998,347.86 +349.600006,344.920013,348.429993,347.65 +348.660004,343.019989,345.660004,345.78 +348.440002,343.880005,345.089996,345.8 +349.940002,345.829987,346.230011,347.33 +348.410004,344.149994,345.390015,345.98 +344.829987,339.959991,340.890015,341.89 +342.690002,338.450012,338.660004,339.93 +340,334.350006,335.859985,336.74 +338.880005,333.48999,336.839996,336.4 +339.850006,337.769989,338.630005,338.75 +339.619995,336.549988,336.899994,337.69 +338.320007,335.459991,336.160004,336.65 +336.190002,330.579987,331.709991,332.83 +338.359985,332.179993,337.410004,335.98 +341.48999,337.5,341.329987,340.11 +345.329987,340.579987,343.75,343.22 +349.390015,344.5,349.019989,347.64 +354.350006,349.790009,351.809998,351.98 +354.029999,344.059998,346.630005,348.24 +346.950012,344.299988,346.170013,345.81 +348,344.690002,346.299988,346.33 +350.109985,346.880005,348.179993,348.39 +351.200012,348.600006,350.559998,350.12 +350.649994,348.809998,350.01001,349.82 +355.950012,351.25,354.25,353.82 +357.309998,354.480011,356.790009,356.19 +360,357.230011,359.859985,359.03 +360.559998,358.070007,358.929993,359.19 +362.609985,358.179993,361.329987,360.71 +363.029999,360.25,361,361.43 +362.459991,360.049988,361.799988,361.44 +363.190002,361.23999,362.679993,362.37 +362.640015,359.579987,361.339996,361.19 +362.119995,359.209991,360.049988,360.46 +361.519989,358.299988,358.690002,359.5 diff --git a/trend/testdata/vwma.csv b/trend/testdata/vwma.csv new file mode 100644 index 0000000..0e882e4 --- /dev/null +++ b/trend/testdata/vwma.csv @@ -0,0 +1,252 @@ +Close,Volume,Vwma +318.600006,7919700,0 +315.839996,4351600,0 +316.149994,3025700,0 +310.570007,3835800,0 +307.779999,3877400,0 +305.820007,4130800,0 +305.98999,2351700,0 +306.390015,3326000,0 +311.450012,4366700,0 +312.329987,5042800,0 +309.290009,4056900,0 +301.910004,5103900,0 +300,8305700,0 +300.029999,3842200,0 +302,3090700,0 +307.820007,3264600,0 +302.690002,3560100,0 +306.48999,2460400,0 +305.549988,2730900,0 +303.429993,2628200,307.83 +309.059998,2846200,306.76 +308.899994,3298300,306.32 +309.910004,3549900,306.1 +314.549988,5121200,306.44 +312.899994,3416300,306.66 +318.690002,3647900,307.28 +315.529999,4397400,307.79 +316.350006,3049100,308.18 +320.369995,2999500,308.47 +318.929993,3070300,308.64 +317.640015,2773000,308.95 +314.859985,3478900,309.74 +308.299988,3406000,310.88 +305.230011,3614600,311.2 +309.869995,3770100,311.55 +310.420013,3086700,311.68 +311.299988,2234300,312.15 +311.899994,2299800,312.36 +310.950012,2856600,312.58 +309.170013,3031200,312.79 +307.329987,3474600,312.66 +311.519989,3653400,312.79 +310.570007,3518300,312.82 +311.859985,4421400,312.62 +308.51001,5385700,312.28 +308.429993,2973100,311.77 +312.970001,3786700,311.59 +308.480011,3370000,311.22 +307.209991,3461200,310.61 +309.890015,2808000,310.2 +313.73999,3261500,310.06 +310.790009,2907100,309.85 +309.630005,2410700,309.92 +308.179993,2801700,310.1 +308.23999,2720500,310.04 +302.720001,4131100,309.56 +303.160004,2899500,309.22 +303.070007,2736400,308.87 +304.019989,3656200,308.52 +304.660004,3652200,308.29 +305.179993,4736800,308.12 +304.619995,3397200,307.77 +307.75,3152100,307.63 +312.450012,4493000,307.67 +316.970001,4889800,308.27 +311.119995,3609700,308.41 +311.369995,2701600,308.27 +304.820007,3929500,308.07 +303.630005,5294800,307.77 +302.880005,4993000,307.35 +305.329987,5251500,306.93 +297.880005,7162800,305.96 +302.01001,6325700,305.55 +293.51001,15609400,303.5 +301.059998,6056000,303.22 +303.850006,4724000,303.27 +299.730011,3086300,303.17 +298.369995,4015800,302.98 +298.920013,3905400,302.78 +302.140015,3833900,302.69 +302.320007,2436500,302.56 +305.299988,2650000,302.57 +305.079987,2694000,302.47 +308.769989,5020200,302.33 +310.309998,4862300,302 +309.070007,2740300,301.86 +310.390015,2314500,301.8 +312.51001,3131400,302.03 +312.619995,2330900,302.2 +313.700012,3109500,302.55 +314.549988,2662600,302.75 +318.049988,3323300,303.76 +319.73999,2975400,304.48 +323.790009,3425500,307.91 +324.630005,3581200,309.42 +323.089996,2406200,310.34 +323.820007,2428400,311.37 +324.329987,2405700,312.7 +326.049988,2261900,314.09 +324.339996,2552200,315.3 +320.529999,2718600,316.07 +326.230011,2950000,317.05 +328.549988,2909600,318.14 +330.170013,2461300,319.48 +325.859985,3366500,320.65 +323.220001,2653800,321.34 +320,3185600,321.71 +323.880005,3869500,322.36 +326.140015,3302400,322.96 +324.869995,2283400,323.54 +322.98999,2639800,323.93 +322.640015,2548900,324.21 +322.48999,1937300,324.39 +323.529999,2190000,324.39 +323.75,2139500,324.35 +327.390015,3046800,324.58 +329.76001,2805000,324.88 +330.390015,4322900,325.33 +329.130005,2762500,325.49 +323.109985,4029300,325.37 +320.200012,3071500,325.32 +319.019989,4245400,324.83 +320.600006,3229400,324.42 +322.190002,3231800,324.07 +321.079987,6175000,323.69 +323.119995,3375300,323.68 +329.480011,3962200,324.21 +328.579987,3091800,324.44 +333.410004,3181400,324.8 +335.420013,3727800,325.4 +335.950012,2759300,325.94 +335.290009,2619200,326.44 +333.600006,2873400,326.86 +336.390015,2953000,327.38 +335.899994,5164600,328.11 +339.820007,4095200,328.81 +338.309998,8486200,329.82 +338.670013,3751700,330.22 +338.609985,4507000,330.74 +336.959991,3303300,331.4 +335.25,4451700,332.05 +334.119995,3220900,332.84 +335.339996,2625600,333.44 +334.149994,3175100,333.94 +336.910004,2498900,335.11 +341,4520600,336.01 +342,2047400,336.53 +341.559998,2870700,337.07 +341.459991,2548300,337.38 +340.899994,2940800,337.63 +341.130005,2966500,337.84 +343.369995,2754900,338.14 +345.350006,2897100,338.62 +343.540009,2831800,338.9 +341.089996,2669300,339.21 +344.25,2359500,339.35 +345.339996,2565300,339.75 +342.429993,3032100,339.95 +346.609985,3146000,340.4 +345.76001,3301900,340.89 +349.630005,3269400,341.81 +347.579987,3014000,342.54 +349.799988,2682900,343.2 +349.309998,2706700,343.98 +349.809998,2473300,344.54 +351.959991,2621600,345.18 +352.26001,2293300,345.58 +351.190002,3085900,346.1 +353.809998,2942000,346.71 +349.98999,2842000,347.18 +362.579987,5379900,348.89 +363.730011,3428800,350 +358.019989,4424600,350.8 +356.980011,3098800,351.45 +358.350006,2475200,352.18 +358.480011,1990700,352.69 +354.5,2863700,353.09 +354.109985,2196100,353.66 +353.190002,2847700,354.01 +352.559998,2870600,354.4 +352.089996,2540000,354.56 +350.570007,2363300,354.76 +354.26001,2239500,354.97 +354.299988,2521100,355.21 +355.929993,2136800,355.47 +355.549988,1728000,355.64 +358.290009,2285600,355.88 +361.059998,3058300,356.42 +360.200012,2842300,356.75 +362.459991,2637900,357.36 +360.470001,2976800,357.01 +361.670013,2655800,356.81 +361.799988,3263800,357.02 +363.149994,3019100,357.38 +365.519989,2921600,357.79 +367.779999,2898400,358.31 +367.820007,3261400,359.1 +369.5,3670100,360 +367.859985,11595000,361.74 +370.429993,3130900,362.58 +370.480011,2603700,363.32 +366.820007,2268400,363.92 +363.279999,3178600,364.22 +360.160004,3969400,364.36 +361.709991,2556200,364.52 +359.420013,3063900,364.52 +357.779999,3535400,364.38 +357.059998,2731700,364.24 +350.299988,4932900,363.43 +348.079987,3527600,362.71 +343.040009,3151700,361.94 +343.690002,3244600,361.14 +345.059998,3027300,360.43 +346.339996,3174700,359.7 +345.450012,2762800,358.92 +348.559998,2858600,358.16 +348.429993,2620800,357.36 +345.660004,2677500,356.29 +345.089996,2804800,353.61 +346.230011,3117800,352.38 +345.390015,2998600,351.29 +340.890015,2977100,350.24 +338.660004,2741300,349.07 +335.859985,3466100,347.62 +336.839996,2794200,346.55 +338.630005,2355700,345.61 +336.899994,2623200,344.52 +336.160004,2685400,343.59 +331.709991,3608200,342.3 +337.410004,2634700,341.73 +341.329987,3066900,341.64 +343.75,2789700,341.63 +349.019989,3433700,341.88 +351.809998,4409100,342.38 +346.630005,5486200,342.62 +346.170013,3062900,342.52 +346.299988,2602400,342.43 +348.179993,3052100,342.57 +350.559998,3701100,342.93 +350.01001,2196200,343.01 +354.25,3387500,343.5 +356.790009,3572900,344.37 +359.859985,2822500,345.3 +358.929993,3260000,346.52 +361.329987,3215300,347.68 +361,2918800,348.62 +361.799988,2110200,349.53 +362.679993,1282000,350.37 +361.339996,2580300,351.93 +360.049988,2953500,352.93 +358.690002,3141100,353.8 diff --git a/trend/trima.go b/trend/trima.go new file mode 100644 index 0000000..e77adfa --- /dev/null +++ b/trend/trima.go @@ -0,0 +1,75 @@ +// Copyright (c) 2021-2023 Onur Cinar. +// The source code is provided under GNU AGPLv3 License. +// https://github.com/cinar/indicator + +package trend + +import ( + "github.com/cinar/indicator/helper" +) + +const ( + // DefaultTrimaPeriod is the default period for TRIMA. + DefaultTrimaPeriod = 15 +) + +// Trima represents the configuration parameters for calculating the +// Triangular Moving Average (TRIMA). +// +// If period is even: +// +// TRIMA = SMA(period / 2, SMA((period / 2) + 1, values)) +// +// If period is odd: +// +// TRIMA = SMA((period + 1) / 2, SMA((period + 1) / 2, values)) +type Trima[T helper.Number] struct { + // Time period. + Period int +} + +// NewTrima function initializes a new TRIMA instance +// with the default parameters. +func NewTrima[T helper.Number]() *Trima[T] { + return &Trima[T]{ + Period: DefaultTrimaPeriod, + } +} + +// Compute function takes a channel of numbers and computes the TRIMA +// and the signal line. +func (t *Trima[T]) Compute(c <-chan T) <-chan T { + period1, period2 := t.calculatePeriods() + + sma1 := NewSma[T]() + sma1.Period = period1 + + sma2 := NewSma[T]() + sma2.Period = period2 + + trima := sma1.Compute(sma2.Compute(c)) + + return trima +} + +// IdlePeriod is the initial period that TRIMA won't yield any results. +func (t *Trima[T]) IdlePeriod() int { + period1, period2 := t.calculatePeriods() + return period1 + period2 - 2 +} + +// calculatePeriods calculates the individual periods to use based on the +// TRIMA period. +func (t *Trima[T]) calculatePeriods() (int, int) { + var period1, period2 int + + if t.Period%2 == 0 { + period1 = t.Period / 2 + period2 = period1 + 1 + } else { + period1 = (t.Period + 1) / 2 + period2 = period1 + } + + return period1, period2 +} diff --git a/trend/trima_test.go b/trend/trima_test.go new file mode 100644 index 0000000..e5cd0b0 --- /dev/null +++ b/trend/trima_test.go @@ -0,0 +1,70 @@ +// Copyright (c) 2021-2023 Onur Cinar. +// The source code is provided under GNU AGPLv3 License. +// https://github.com/cinar/indicator + +package trend_test + +import ( + "testing" + + "github.com/cinar/indicator/helper" + "github.com/cinar/indicator/trend" +) + +func TestTrimaWithOddPeriod(t *testing.T) { + type Data struct { + Close float64 + Trima float64 + } + + input, err := helper.ReadFromCsvFile[Data]("testdata/trima_odd.csv", true) + if err != nil { + t.Fatal(err) + } + + inputs := helper.Duplicate(input, 2) + closing := helper.Map(inputs[0], func(d *Data) float64 { return d.Close }) + expected := helper.Map(inputs[1], func(d *Data) float64 { return d.Trima }) + + trima := trend.NewTrima[float64]() + trima.Period = 15 + + actual := trima.Compute(closing) + actual = helper.RoundDigits(actual, 2) + + expected = helper.Skip(expected, trima.IdlePeriod()) + + err = helper.CheckEquals(actual, expected) + if err != nil { + t.Fatal(err) + } +} + +func TestTrimaWithEvenPeriod(t *testing.T) { + type Data struct { + Close float64 + Trima float64 + } + + input, err := helper.ReadFromCsvFile[Data]("testdata/trima_even.csv", true) + if err != nil { + t.Fatal(err) + } + + inputs := helper.Duplicate(input, 2) + closing := helper.Map(inputs[0], func(d *Data) float64 { return d.Close }) + expected := helper.Map(inputs[1], func(d *Data) float64 { return d.Trima }) + + trima := trend.NewTrima[float64]() + trima.Period = 20 + + actual := trima.Compute(closing) + actual = helper.RoundDigits(actual, 2) + + expected = helper.Skip(expected, trima.IdlePeriod()) + + err = helper.CheckEquals(actual, expected) + if err != nil { + t.Fatal(err) + } +} diff --git a/trend/typical_price.go b/trend/typical_price.go new file mode 100644 index 0000000..abcd497 --- /dev/null +++ b/trend/typical_price.go @@ -0,0 +1,33 @@ +// Copyright (c) 2021-2023 Onur Cinar. +// The source code is provided under GNU AGPLv3 License. +// https://github.com/cinar/indicator + +package trend + +import ( + "github.com/cinar/indicator/helper" +) + +// TypicalPrice represents the configuration parameters for calculating the Typical Price. +// It is another approximation of average price for each period and can be used as a +// filter for moving average systems. +// +// Typical Price = (High + Low + Closing) / 3 +type TypicalPrice[T helper.Number] struct { +} + +// NewTypicalPrice function initializes a new Typical Price instance with the default parameters. +func NewTypicalPrice[T helper.Number]() *TypicalPrice[T] { + return &TypicalPrice[T]{} +} + +// Compute function takes a channel of numbers and computes the Typical Price and the signal line. +func (t *TypicalPrice[T]) Compute(high, low, closing <-chan T) <-chan T { + return helper.DivideBy( + helper.Add( + helper.Add(high, low), + closing, + ), + 3, + ) +} diff --git a/trend/typical_price_test.go b/trend/typical_price_test.go new file mode 100644 index 0000000..0d8ace6 --- /dev/null +++ b/trend/typical_price_test.go @@ -0,0 +1,42 @@ +// Copyright (c) 2021-2023 Onur Cinar. +// The source code is provided under GNU AGPLv3 License. +// https://github.com/cinar/indicator + +package trend_test + +import ( + "testing" + + "github.com/cinar/indicator/helper" + "github.com/cinar/indicator/trend" +) + +func TestTypicalPrice(t *testing.T) { + type Data struct { + High float64 + Low float64 + Close float64 + TypicalPrice float64 + } + + input, err := helper.ReadFromCsvFile[Data]("testdata/typical_price.csv", true) + if err != nil { + t.Fatal(err) + } + + inputs := helper.Duplicate(input, 4) + high := helper.Map(inputs[0], func(d *Data) float64 { return d.High }) + low := helper.Map(inputs[1], func(d *Data) float64 { return d.Low }) + closing := helper.Map(inputs[2], func(d *Data) float64 { return d.Close }) + expected := helper.Map(inputs[3], func(d *Data) float64 { return d.TypicalPrice }) + + typicalPrice := trend.NewTypicalPrice[float64]() + + actual := typicalPrice.Compute(high, low, closing) + actual = helper.RoundDigits(actual, 2) + + err = helper.CheckEquals(actual, expected) + if err != nil { + t.Fatal(err) + } +} diff --git a/trend/vwma.go b/trend/vwma.go new file mode 100644 index 0000000..efd1b76 --- /dev/null +++ b/trend/vwma.go @@ -0,0 +1,51 @@ +// Copyright (c) 2021-2023 Onur Cinar. +// The source code is provided under GNU AGPLv3 License. +// https://github.com/cinar/indicator + +package trend + +import ( + "github.com/cinar/indicator/helper" +) + +const ( + // DefaultVwmaPeriod is the default period for the VWMA. + DefaultVwmaPeriod = 20 +) + +// Vwma represents the configuration parameters for calculating the Volume Weighted Moving Average (VWMA) +// It averages the price data with an emphasis on volume, meaning areas with higher volume will have a +// greater weight. +// +// VWMA = Sum(Price * Volume) / Sum(Volume) +type Vwma[T helper.Number] struct { + // Time period. + Period int +} + +// NewVwma function initializes a new VWMA instance with the default parameters. +func NewVwma[T helper.Number]() *Vwma[T] { + return &Vwma[T]{ + Period: DefaultVwmaPeriod, + } +} + +// Compute function takes a channel of numbers and computes the VWMA and the signal line. +func (v *Vwma[T]) Compute(closing, volume <-chan T) <-chan T { + volumes := helper.Duplicate(volume, 2) + + sum := NewMovingSum[T]() + sum.Period = v.Period + + return helper.Divide( + sum.Compute( + helper.Multiply(closing, volumes[0]), + ), + sum.Compute(volumes[1]), + ) +} + +// IdlePeriod is the initial period that VWMA won't yield any results. +func (v *Vwma[T]) IdlePeriod() int { + return v.Period - 1 +} diff --git a/trend/vwma_test.go b/trend/vwma_test.go new file mode 100644 index 0000000..86e6b6c --- /dev/null +++ b/trend/vwma_test.go @@ -0,0 +1,42 @@ +// Copyright (c) 2021-2023 Onur Cinar. +// The source code is provided under GNU AGPLv3 License. +// https://github.com/cinar/indicator + +package trend_test + +import ( + "testing" + + "github.com/cinar/indicator/helper" + "github.com/cinar/indicator/trend" +) + +func TestVwma(t *testing.T) { + type Data struct { + Close float64 + Volume int64 + Vwma float64 + } + + input, err := helper.ReadFromCsvFile[Data]("testdata/vwma.csv", true) + if err != nil { + t.Fatal(err) + } + + inputs := helper.Duplicate(input, 3) + closing := helper.Map(inputs[0], func(d *Data) float64 { return d.Close }) + volume := helper.Map(inputs[1], func(d *Data) float64 { return float64(d.Volume) }) + expected := helper.Map(inputs[2], func(d *Data) float64 { return d.Vwma }) + + vwma := trend.NewVwma[float64]() + + actual := vwma.Compute(closing, volume) + actual = helper.RoundDigits(actual, 2) + + expected = helper.Skip(expected, vwma.IdlePeriod()) + + err = helper.CheckEquals(actual, expected) + if err != nil { + t.Fatal(err) + } +}