Skip to content

Commit

Permalink
Volume Weighted Moving Average (VWMA) (#110)
Browse files Browse the repository at this point in the history
* Vwma indicator
Fixes #109

* Additional tests.
  • Loading branch information
cinar authored Jun 30, 2022
1 parent f8c8f86 commit 70e4e65
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 5 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
13 changes: 13 additions & 0 deletions helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
19 changes: 19 additions & 0 deletions trend_indicators.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
12 changes: 12 additions & 0 deletions trend_indicators.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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

Expand Down
19 changes: 19 additions & 0 deletions trend_indicators_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
36 changes: 36 additions & 0 deletions trend_strategies.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
22 changes: 22 additions & 0 deletions trend_strategies.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down
68 changes: 63 additions & 5 deletions trend_strategies_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

0 comments on commit 70e4e65

Please sign in to comment.