Make VWAP better

This commit is contained in:
なるみ 2022-03-29 03:19:29 +08:00
parent 98d4815d1d
commit 18aa60077b
7 changed files with 81 additions and 95 deletions

View File

@ -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()
@ -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 {

View File

@ -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}}
@ -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) {

View File

@ -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()
@ -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 {

View File

@ -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) {

View File

@ -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)
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]
}
inc.WeightedSum += price * volume
inc.VolumeSum += volume
vwap := inc.WeightedSum / inc.VolumeSum
inc.Values.Push(vwap)
}
func (inc *VWAP) calculateAndUpdate(kLines []types.KLine) {
if len(kLines) < inc.Window {
return
}
var priceF = KLineTypicalPriceMapper
var dataLen = len(kLines)
// 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
}
for _, k := range kLines {
if inc.EndTime != zeroTime && k.EndTime.Before(inc.EndTime) {
continue
}
inc.Update(k, priceF)
}
// update vwap
for i := from; i < dataLen; i++ {
inc.update(kLines[i], priceF, 1.0) // add kline
if i >= inc.Window {
inc.update(kLines[i-inc.Window], priceF, -1.0) // pop kline
}
vwap := inc.WeightedSum / inc.VolumeSum
inc.Values.Push(vwap)
inc.EmitUpdate(vwap)
inc.EndTime = kLines[i].EndTime.Time()
}
inc.EmitUpdate(inc.Last())
inc.EndTime = kLines[len(kLines)-1].EndTime.Time()
}
func (inc *VWAP) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
@ -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()
}

View File

@ -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)
@ -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)

View File

@ -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()
}