From 70e4e656b319e7fd9d35b153cf7fb303d31bf9f2 Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Thu, 30 Jun 2022 10:52:56 -0700 Subject: [PATCH] Volume Weighted Moving Average (VWMA) (#110) * Vwma indicator Fixes #109 * Additional tests. --- README.md | 2 ++ helper.go | 13 ++++++++ trend_indicators.go | 19 +++++++++++ trend_indicators.md | 12 +++++++ trend_indicators_test.go | 19 +++++++++++ trend_strategies.go | 36 +++++++++++++++++++++ trend_strategies.md | 22 +++++++++++++ trend_strategies_test.go | 68 +++++++++++++++++++++++++++++++++++++--- 8 files changed, 186 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a3e1007..8f1cc43 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ The following list of indicators are currently supported by this package: - [Triangular Moving Average (TRIMA)](trend_indicators.md#triangular-moving-average-trima) - [Triple Exponential Average (TRIX)](trend_indicators.md#triple-exponential-average-trix) - [Typical Price](trend_indicators.md#typical-price) +- [Volume Weighted Moving Average (VWMA)](trend_indicators.md#volume-weighted-moving-average-vwma) - [Vortex Indicator](trend_indicators.md#vortex-indicator) ### Momentum Indicators @@ -96,6 +97,7 @@ The following list of strategies are currently supported by this package: - [KDJ Strategy](trend_strategies.md#kdj-strategy) - [MACD Strategy](trend_strategies.md#macd-strategy) - [Trend Strategy](trend_strategies.md#trend-strategy) +- [Volume Weighted Moving Average (VWMA) Strategy](trend_strategies.md#volume-weighted-moving-average-vwma-strategy) ### Momentum Strategies diff --git a/helper.go b/helper.go index 11ff2e4..e6f80cc 100644 --- a/helper.go +++ b/helper.go @@ -256,6 +256,19 @@ func testEqualsInt(t *testing.T, actual, expected []int) { } } +// Test equals action array. +func testEqualsAction(t *testing.T, actual, expected []Action) { + if len(actual) != len(expected) { + t.Fatal("not the same size") + } + + for i := 0; i < len(expected); i++ { + if actual[i] != expected[i] { + t.Fatalf("at %d actual %d expected %d", i, actual[i], expected[i]) + } + } +} + // Sqrt of given values. func sqrt(values []float64) []float64 { result := make([]float64, len(values)) diff --git a/trend_indicators.go b/trend_indicators.go index 6cde5fd..89255c7 100644 --- a/trend_indicators.go +++ b/trend_indicators.go @@ -619,3 +619,22 @@ func Vortex(high, low, closing []float64) ([]float64, []float64) { return plusVi, minusVi } + +// The Vwma function calculates the Volume Weighted Moving Average (VWMA) +// averaging the price data with an emphasis on volume, meaning areas +// with higher volume will have a greater weight. +// +// VWMA = Sum(Price * Volume) / Sum(Volume) for a given Period. +// +// Returns vwma +func Vwma(period int, closing []float64, volume []int64) []float64 { + floatVolume := asFloat64(volume) + vwma := divide(Sum(period, multiply(closing, floatVolume)), Sum(period, floatVolume)) + + return vwma +} + +// The DefaultVwma function calculates VWMA with a period of 20. +func DefaultVwma(closing []float64, volume []int64) []float64 { + return Vwma(20, closing, volume) +} diff --git a/trend_indicators.md b/trend_indicators.md index 1daaa0d..1939a2b 100644 --- a/trend_indicators.md +++ b/trend_indicators.md @@ -24,6 +24,7 @@ Trend indicators measure the direction and strength of a trend. - [Triangular Moving Average (TRIMA)](#triangular-moving-average-trima) - [Triple Exponential Average (TRIX)](#triple-exponential-average-trix) - [Typical Price](#typical-price) +- [Volume Weighted Moving Average (VWMA)](#volume-weighted-moving-average-vwma) - [Vortex Indicator](#vortex-indicator) #### Absolute Price Oscillator (APO) @@ -336,6 +337,17 @@ Typical Price = (High + Low + Closing) / 3 ```Golang ta, sma20 := indicator.TypicalPrice(high, low, closing) ``` +#### Volume Weighted Moving Average (VWMA) + +The [Vwma](https://pkg.go.dev/github.com/cinar/indicator#Vwma) function calculates the Volume Weighted Moving Average (VWMA) averaging the price data with an emphasis on volume, meaning areas with higher volume will have a greater weight. + +``` +VWMA = Sum(Price * Volume) / Sum(Volume) for a given Period. +``` + +```Golang +vwma := indicator.Vwma(period, closing, volume) +``` #### Vortex Indicator diff --git a/trend_indicators_test.go b/trend_indicators_test.go index 7eb1924..9080eab 100644 --- a/trend_indicators_test.go +++ b/trend_indicators_test.go @@ -292,3 +292,22 @@ func TestVortex(t *testing.T) { testEquals(t, roundDigitsAll(plusVi, 5), expectedPlusVi) testEquals(t, roundDigitsAll(minusVi, 5), expectedMinusVi) } + +func TestVwma(t *testing.T) { + closing := []float64{20, 21, 21, 19, 16} + volume := []int64{100, 50, 40, 50, 100} + expected := []float64{20, 20.33, 20.47, 20.29, 17.84} + period := 3 + + actual := Vwma(period, closing, volume) + testEquals(t, roundDigitsAll(actual, 2), expected) +} + +func TestDefaultVwma(t *testing.T) { + closing := []float64{20, 21, 21, 19, 16} + volume := []int64{100, 50, 40, 50, 100} + expected := []float64{20, 20.33, 20.47, 20.17, 18.94} + + actual := DefaultVwma(closing, volume) + testEquals(t, roundDigitsAll(actual, 2), expected) +} diff --git a/trend_strategies.go b/trend_strategies.go index 81bc308..978e737 100644 --- a/trend_strategies.go +++ b/trend_strategies.go @@ -155,3 +155,39 @@ func MakeTrendStrategy(count uint) StrategyFunction { return TrendStrategy(asset, count) } } + +// 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 signal otherwse. +// +// Returns actions +func VwmaStrategy(asset *Asset, period int) []Action { + actions := make([]Action, len(asset.Date)) + + sma := Sma(period, asset.Closing) + vwma := Vwma(period, asset.Closing, asset.Volume) + + for i := 0; i < len(actions); i++ { + if vwma[i] > sma[i] { + actions[i] = BUY + } else if vwma[i] < sma[i] { + actions[i] = SELL + } else { + actions[i] = HOLD + } + } + + return actions +} + +// Makes a VWMA strategy for the given period. +func MakeVwmaStrategy(period int) StrategyFunction { + return func(asset *Asset) []Action { + return VwmaStrategy(asset, period) + } +} + +// Default VWMA strategy function. +func DefaultVwmaStrategy(asset *Asset) []Action { + return VwmaStrategy(asset, 20) +} diff --git a/trend_strategies.md b/trend_strategies.md index f29d2e6..f883785 100644 --- a/trend_strategies.md +++ b/trend_strategies.md @@ -6,6 +6,7 @@ Trend strategies generate signals based on a trend indicator. - [MACD Strategy](#macd-strategy) - [KDJ Strategy](#kdj-strategy) - [Trend Strategy](#trend-strategy) +- [Volume Weighted Moving Average (VWMA) Strategy](#volume-weighted-moving-average-vwma-strategy) #### Chande Forecast Oscillator Strategy @@ -59,6 +60,27 @@ strategy := indicator.MakeTrendStrategy(4) actions := strategy(asset) ``` +#### Volume Weighted Moving Average (VWMA) Strategy + +The [VwmaStrategy](https://pkg.go.dev/github.com/cinar/indicator#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_ signal otherwse. + +```golang +actions := indicator.VwmaStrategy(asset, 3) +``` + +The function signature of [VwmaStrategy](https://pkg.go.dev/github.com/cinar/indicator#VwmaStrategy) does not match the [StrategyFunction](https://pkg.go.dev/github.com/cinar/indicator#StrategyFunction) type, as it requires an additional _period_ parameter. The [MakeVwmaStrategy](https://pkg.go.dev/github.com/cinar/indicator#MakeVwmaStrategy) function can be used to return a [StrategyFunction](https://pkg.go.dev/github.com/cinar/indicator#StrategyFunction) instance based on the given _count_ value. + +```golang +strategy := indicator.MakeTrendStrategy(4) +actions := strategy(asset) +``` + +The [DefaultVwmaStrategy](https://pkg.go.dev/github.com/cinar/indicator#DefaultVwmaStrategy) function can be used with the default period of 20. + +```golang +actions := indicator.DefaultVwmaStrategy(asset) +``` + ## 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. diff --git a/trend_strategies_test.go b/trend_strategies_test.go index 315a7c7..9330cbb 100644 --- a/trend_strategies_test.go +++ b/trend_strategies_test.go @@ -36,11 +36,69 @@ func TestTrendStrategy(t *testing.T) { HOLD, HOLD, BUY, HOLD, SELL, } - actions := TrendStrategy(asset, 2) + actual := TrendStrategy(asset, 2) + testEqualsAction(t, actual, expected) +} + +func TestVwmaStrategy(t *testing.T) { + asset := &Asset{ + Date: []time.Time{ + time.Now(), time.Now(), time.Now(), time.Now(), time.Now(), + }, + Opening: []float64{ + 0, 0, 0, 0, 0, + }, + Closing: []float64{ + 20, 21, 21, 19, 16, + }, + High: []float64{ + 0, 0, 0, 0, 0, + }, + Low: []float64{ + 0, 0, 0, 0, 0, + }, + Volume: []int64{ + 100, 50, 40, 50, 100, + }, + } - for i := 0; i < len(expected); i++ { - if actions[i] != expected[i] { - t.Fatalf("at %d actual %d expected %d", i, actions[i], expected[i]) - } + expected := []Action{ + HOLD, SELL, SELL, SELL, SELL, } + + period := 3 + + strategy := MakeVwmaStrategy(period) + actual := strategy(asset) + testEqualsAction(t, actual, expected) +} + +func TestDefaultVwmaStrategy(t *testing.T) { + asset := &Asset{ + Date: []time.Time{ + time.Now(), time.Now(), time.Now(), time.Now(), time.Now(), + }, + Opening: []float64{ + 0, 0, 0, 0, 0, + }, + Closing: []float64{ + 20, 21, 21, 19, 16, + }, + High: []float64{ + 0, 0, 0, 0, 0, + }, + Low: []float64{ + 0, 0, 0, 0, 0, + }, + Volume: []int64{ + 100, 50, 40, 50, 100, + }, + } + + expected := []Action{ + HOLD, SELL, SELL, SELL, SELL, + } + + actual := DefaultVwmaStrategy(asset) + testEqualsAction(t, actual, expected) }