bbgo_origin/pkg/indicator/vwma.go

121 lines
3.0 KiB
Go
Raw Permalink Normal View History

package indicator
import (
"time"
"github.com/c9s/bbgo/pkg/datatype/floats"
"github.com/c9s/bbgo/pkg/types"
)
// vwma implements the volume weighted moving average (VWMA) indicator:
//
// Calculation:
// pv = element-wise multiplication of close prices and volumes
// VWMA = SMA(pv, window) / SMA(volumes, window)
//
// Volume Weighted Moving Average
//- https://www.motivewave.com/studies/volume_weighted_moving_average.htm
//
// The Volume Weighted Moving Average (VWMA) is a technical analysis indicator that is used to smooth price data and reduce the lag
// associated with traditional moving averages. It is calculated by taking the weighted moving average of the input data, with the
// weighting factors determined by the volume of the security. This resulting average is then plotted on the price chart as a line,
// which can be used to make predictions about future price movements. The VWMA is typically more accurate than other simple moving
// averages, as it takes into account the volume of the security, but may be less reliable in markets with low trading volume.
//go:generate callbackgen -type VWMA
type VWMA struct {
types.SeriesBase
types.IntervalWindow
2022-07-14 07:57:17 +00:00
Values floats.Slice
2022-07-14 07:57:17 +00:00
PriceVolumeSMA *SMA
VolumeSMA *SMA
EndTime time.Time
2022-07-14 07:57:17 +00:00
updateCallbacks []func(value float64)
}
func (inc *VWMA) Last() float64 {
if len(inc.Values) == 0 {
return 0.0
}
return inc.Values[len(inc.Values)-1]
}
func (inc *VWMA) Index(i int) float64 {
length := len(inc.Values)
if length == 0 || length-i-1 < 0 {
return 0
}
return inc.Values[length-i-1]
}
func (inc *VWMA) Length() int {
return len(inc.Values)
}
var _ types.SeriesExtend = &VWMA{}
2022-07-14 07:57:17 +00:00
func (inc *VWMA) Update(price, volume float64) {
if inc.PriceVolumeSMA == nil {
inc.PriceVolumeSMA = &SMA{IntervalWindow: inc.IntervalWindow}
inc.SeriesBase.Series = inc
}
2022-07-14 07:57:17 +00:00
if inc.VolumeSMA == nil {
inc.VolumeSMA = &SMA{IntervalWindow: inc.IntervalWindow}
}
2022-07-14 07:57:17 +00:00
inc.PriceVolumeSMA.Update(price * volume)
inc.VolumeSMA.Update(volume)
2022-07-14 07:57:17 +00:00
pv := inc.PriceVolumeSMA.Last()
v := inc.VolumeSMA.Last()
vwma := pv / v
inc.Values.Push(vwma)
}
func (inc *VWMA) PushK(k types.KLine) {
if inc.EndTime != zeroTime && k.EndTime.Before(inc.EndTime) {
return
}
inc.Update(k.Close.Float64(), k.Volume.Float64())
}
2022-07-14 07:57:17 +00:00
func (inc *VWMA) CalculateAndUpdate(allKLines []types.KLine) {
if len(allKLines) < inc.Window {
return
}
2022-07-14 07:57:17 +00:00
var last = allKLines[len(allKLines)-1]
2022-07-14 07:57:17 +00:00
if inc.VolumeSMA == nil {
for _, k := range allKLines {
if inc.EndTime != zeroTime && k.EndTime.Before(inc.EndTime) {
return
}
2022-07-14 07:57:17 +00:00
inc.Update(k.Close.Float64(), k.Volume.Float64())
}
} else {
inc.Update(last.Close.Float64(), last.Volume.Float64())
}
2022-07-14 07:57:17 +00:00
inc.EndTime = last.EndTime.Time()
inc.EmitUpdate(inc.Values.Last())
}
func (inc *VWMA) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
if inc.Interval != interval {
return
}
inc.CalculateAndUpdate(window)
}
func (inc *VWMA) Bind(updater KLineWindowUpdater) {
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
}