mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
Make VWAP better
This commit is contained in:
parent
98d4815d1d
commit
18aa60077b
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user