Skip to content

Commit

Permalink
Make VWAP better
Browse files Browse the repository at this point in the history
  • Loading branch information
narumiruna committed Mar 29, 2022
1 parent 98d4815 commit 18aa600
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 95 deletions.
11 changes: 5 additions & 6 deletions pkg/indicator/ad.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type AD struct {
UpdateCallbacks []func(value float64)
}

func (inc *AD) update(kLine types.KLine) {
func (inc *AD) Update(kLine types.KLine) {
close := kLine.Close.Float64()
high := kLine.High.Float64()
low := kLine.Low.Float64()
Expand All @@ -42,16 +42,15 @@ func (inc *AD) Last() float64 {
}

func (inc *AD) calculateAndUpdate(kLines []types.KLine) {
for i, k := range kLines {
for _, k := range kLines {
if inc.EndTime != zeroTime && k.EndTime.Before(inc.EndTime) {
continue
}

inc.update(k)
inc.EmitUpdate(inc.Last())
inc.EndTime = kLines[i].EndTime.Time()
inc.Update(k)
}

inc.EmitUpdate(inc.Last())
inc.EndTime = kLines[len(kLines)-1].EndTime.Time()
}
func (inc *AD) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
if inc.Interval != interval {
Expand Down
21 changes: 9 additions & 12 deletions pkg/indicator/macd.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ type MACD struct {

func (inc *MACD) calculateMACD(kLines []types.KLine, priceF KLinePriceMapper) float64 {
for _, kline := range kLines {
inc.update(kline, priceF)
inc.Update(kline, priceF)
}
return inc.Values[len(inc.Values)-1]
}

func (inc *MACD) update(kLine types.KLine, priceF KLinePriceMapper) {
func (inc *MACD) Update(kLine types.KLine, priceF KLinePriceMapper) {
if len(inc.Values) == 0 {
inc.FastEWMA = EWMA{IntervalWindow: types.IntervalWindow{Window: inc.ShortPeriod}}
inc.SlowEWMA = EWMA{IntervalWindow: types.IntervalWindow{Window: inc.LongPeriod}}
Expand Down Expand Up @@ -67,18 +67,15 @@ func (inc *MACD) calculateAndUpdate(kLines []types.KLine) {

var priceF = KLineClosePriceMapper

var index = len(kLines) - 1
var kline = kLines[index]
if inc.EndTime != zeroTime && kline.EndTime.Before(inc.EndTime) {
return
}

for i, kLine := range kLines {
inc.update(kLine, priceF)
inc.EmitUpdate(inc.Values[len(inc.Values)-1])
inc.EndTime = kLines[i].EndTime.Time()
for _, k := range kLines {
if inc.EndTime != zeroTime && k.EndTime.Before(inc.EndTime) {
continue
}
inc.Update(k, priceF)
}

inc.EmitUpdate(inc.Values[len(inc.Values)-1])
inc.EndTime = kLines[len(kLines)-1].EndTime.Time()
}

func (inc *MACD) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
Expand Down
12 changes: 5 additions & 7 deletions pkg/indicator/obv.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type OBV struct {
UpdateCallbacks []func(value float64)
}

func (inc *OBV) update(kLine types.KLine, priceF KLinePriceMapper) {
func (inc *OBV) Update(kLine types.KLine, priceF KLinePriceMapper) {
price := priceF(kLine)
volume := kLine.Volume.Float64()

Expand All @@ -49,16 +49,14 @@ func (inc *OBV) Last() float64 {
func (inc *OBV) calculateAndUpdate(kLines []types.KLine) {
var priceF = KLineClosePriceMapper

for i, k := range kLines {
for _, k := range kLines {
if inc.EndTime != zeroTime && k.EndTime.Before(inc.EndTime) {
continue
}

inc.update(k, priceF)
inc.EmitUpdate(inc.Last())
inc.EndTime = kLines[i].EndTime.Time()
inc.Update(k, priceF)
}

inc.EmitUpdate(inc.Last())
inc.EndTime = kLines[len(kLines)-1].EndTime.Time()
}
func (inc *OBV) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
if inc.Interval != interval {
Expand Down
8 changes: 4 additions & 4 deletions pkg/indicator/stoch.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,15 @@ func (inc *STOCH) calculateAndUpdate(kLines []types.KLine) {
return
}

for i, k := range kLines {
for _, k := range kLines {
if inc.EndTime != zeroTime && k.EndTime.Before(inc.EndTime) {
continue
}

inc.update(k)
inc.EmitUpdate(inc.LastK(), inc.LastD())
inc.EndTime = kLines[i].EndTime.Time()
}

inc.EmitUpdate(inc.LastK(), inc.LastD())
inc.EndTime = kLines[len(kLines)-1].EndTime.Time()
}

func (inc *STOCH) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
Expand Down
91 changes: 36 additions & 55 deletions pkg/indicator/vwap.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,81 +19,54 @@ Volume-Weighted Average Price (VWAP) Explained
type VWAP struct {
types.IntervalWindow
Values types.Float64Slice
Prices types.Float64Slice
Volumes types.Float64Slice
WeightedSum float64
VolumeSum float64
EndTime time.Time

EndTime time.Time
UpdateCallbacks []func(value float64)
}

func (inc *VWAP) calculateVWAP(kLines []types.KLine, priceF KLinePriceMapper) (vwap float64) {
for i, k := range kLines {
inc.update(k, priceF, 1.0) // add kline

// if window size is not zero, then we do not apply sliding window method
if inc.Window != 0 && len(inc.Values) >= inc.Window {
inc.update(kLines[i-inc.Window], priceF, -1.0) // pop kline
}
vwap = inc.WeightedSum / inc.VolumeSum
inc.Values.Push(vwap)
func (inc *VWAP) Last() float64 {
if len(inc.Values) == 0 {
return 0.0
}

return vwap
return inc.Values[len(inc.Values)-1]
}

func (inc *VWAP) update(kLine types.KLine, priceF KLinePriceMapper, multiplier float64) {
// multiplier = 1 or -1
func (inc *VWAP) Update(kLine types.KLine, priceF KLinePriceMapper) {
price := priceF(kLine)
volume := kLine.Volume.Float64()

inc.WeightedSum += multiplier * price * volume
inc.VolumeSum += multiplier * volume
}
inc.Prices.Push(price)
inc.Volumes.Push(volume)

func (inc *VWAP) calculateAndUpdate(kLines []types.KLine) {
if len(kLines) < inc.Window {
return
if inc.Window != 0 && len(inc.Prices) > inc.Window {
popIndex := len(inc.Prices) - inc.Window - 1
inc.WeightedSum -= inc.Prices[popIndex] * inc.Volumes[popIndex]
inc.VolumeSum -= inc.Volumes[popIndex]
}

var priceF = KLineTypicalPriceMapper
var dataLen = len(kLines)
inc.WeightedSum += price * volume
inc.VolumeSum += volume

// init the values from the kline data
var from = 1
if len(inc.Values) == 0 {
// for the first value, we should use the close price
price := priceF(kLines[0])
volume := kLines[0].Volume.Float64()

inc.Values = []float64{price}
inc.WeightedSum = price * volume
inc.VolumeSum = volume
} else {
// update vwap with the existing values
for i := dataLen - 1; i > 0; i-- {
var k = kLines[i]
if k.EndTime.After(inc.EndTime) {
from = i
} else {
break
}
}
}
vwap := inc.WeightedSum / inc.VolumeSum
inc.Values.Push(vwap)
}

// update vwap
for i := from; i < dataLen; i++ {
inc.update(kLines[i], priceF, 1.0) // add kline
func (inc *VWAP) calculateAndUpdate(kLines []types.KLine) {
var priceF = KLineTypicalPriceMapper

if i >= inc.Window {
inc.update(kLines[i-inc.Window], priceF, -1.0) // pop kline
for _, k := range kLines {
if inc.EndTime != zeroTime && k.EndTime.Before(inc.EndTime) {
continue
}
vwap := inc.WeightedSum / inc.VolumeSum

inc.Values.Push(vwap)
inc.EmitUpdate(vwap)

inc.EndTime = kLines[i].EndTime.Time()
inc.Update(k, priceF)
}

inc.EmitUpdate(inc.Last())
inc.EndTime = kLines[len(kLines)-1].EndTime.Time()
}

func (inc *VWAP) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
Expand All @@ -107,3 +80,11 @@ func (inc *VWAP) handleKLineWindowUpdate(interval types.Interval, window types.K
func (inc *VWAP) Bind(updater KLineWindowUpdater) {
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
}

func CalculateVWAP(klines []types.KLine, priceF KLinePriceMapper, window int) float64 {
vwap := VWAP{IntervalWindow: types.IntervalWindow{Window: window}}
for _, k := range klines {
vwap.Update(k, priceF)
}
return vwap.Last()
}
21 changes: 10 additions & 11 deletions pkg/indicator/vwap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ import (
"github.com/c9s/bbgo/pkg/types"
)

var trivialPrices = []byte(`[0]`)
var trivialVolumes = []byte(`[1]`)
var easyPrices = []byte(`[1, 2, 3]`)
var easyVolumes = []byte(`[4, 5, 6]`)
var windowPrices = []byte(`[1, 2, 3, 4]`)
var windowVolumes = []byte(`[4, 5, 6, 7]`)
var randomPrices = []byte(`[0.6046702879796195, 0.9405190880450124, 0.6645700532184904, 0.4377241871869802, 0.4246474970712657, 0.6868330728671094, 0.06564701921747622, 0.15652925473279125, 0.09697951891448456, 0.3009218605852871]`)
var randomVolumes = []byte(`[0.5152226285020653, 0.8136499609900968, 0.21427387258237493, 0.380667189299686, 0.31806817433032986, 0.4688998449024232, 0.2830441511804452, 0.2931118573368158, 0.6790946759202162, 0.2185630525927643]`)

func Test_calculateVWAP(t *testing.T) {
var trivialPrices = []byte(`[0]`)
var trivialVolumes = []byte(`[1]`)
var easyPrices = []byte(`[1, 2, 3]`)
var easyVolumes = []byte(`[4, 5, 6]`)
var windowPrices = []byte(`[1, 2, 3, 4]`)
var windowVolumes = []byte(`[4, 5, 6, 7]`)
var randomPrices = []byte(`[0.6046702879796195, 0.9405190880450124, 0.6645700532184904, 0.4377241871869802, 0.4246474970712657, 0.6868330728671094, 0.06564701921747622, 0.15652925473279125, 0.09697951891448456, 0.3009218605852871]`)
var randomVolumes = []byte(`[0.5152226285020653, 0.8136499609900968, 0.21427387258237493, 0.380667189299686, 0.31806817433032986, 0.4688998449024232, 0.2830441511804452, 0.2931118573368158, 0.6790946759202162, 0.2185630525927643]`)

buildKLines := func(pb, vb []byte) (kLines []types.KLine) {
var prices, volumes []fixedpoint.Value
_ = json.Unmarshal(pb, &prices)
Expand Down Expand Up @@ -63,9 +63,8 @@ func Test_calculateVWAP(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
vwap := VWAP{IntervalWindow: types.IntervalWindow{Window: tt.window}}
priceF := KLineTypicalPriceMapper
got := vwap.calculateVWAP(tt.kLines, priceF)
got := CalculateVWAP(tt.kLines, priceF, tt.window)
diff := math.Trunc((got-tt.want)*100) / 100
if diff != 0 {
t.Errorf("calculateVWAP() = %v, want %v", got, tt.want)
Expand Down
12 changes: 12 additions & 0 deletions pkg/types/float_slice.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,15 @@ func (s Float64Slice) DivScalar(x float64) Float64Slice {
}
return values
}

func (s Float64Slice) ElementwiseProduct(other Float64Slice) Float64Slice {
var values Float64Slice
for i, v := range s {
values.Push(v * other[i])
}
return values
}

func (s Float64Slice) Dot(other Float64Slice) float64 {
return s.ElementwiseProduct(other).Sum()
}

0 comments on commit 18aa600

Please sign in to comment.