factorzoo: add customized indicators

This commit is contained in:
austin362667 2022-08-08 23:50:42 +08:00
parent bdb04a4322
commit d282568614
14 changed files with 345 additions and 599 deletions

View File

@ -1,133 +0,0 @@
package factorzoo
import (
"fmt"
"math"
"time"
"github.com/c9s/bbgo/pkg/indicator"
"github.com/c9s/bbgo/pkg/types"
log "github.com/sirupsen/logrus"
)
var zeroTime time.Time
type KLineValueMapper func(k types.KLine) float64
//go:generate callbackgen -type AVD
type AVD struct {
types.SeriesBase
types.IntervalWindow
// Values
Values types.Float64Slice
LastValue float64
EndTime time.Time
UpdateCallbacks []func(val float64)
}
func (inc *AVD) Index(i int) float64 {
if inc.Values == nil {
return 0
}
return inc.Values.Index(i)
}
func (inc *AVD) Last() float64 {
if inc.Values.Length() == 0 {
return 0
}
return inc.Values.Last()
}
func (inc *AVD) Length() int {
if inc.Values == nil {
return 0
}
return inc.Values.Length()
}
//var _ types.SeriesExtend = &AVD{}
func (inc *AVD) Update(klines []types.KLine) {
if inc.Values == nil {
inc.SeriesBase.Series = inc
}
if len(klines) < inc.Window {
return
}
var end = len(klines) - 1
var lastKLine = klines[end]
if inc.EndTime != zeroTime && lastKLine.GetEndTime().Before(inc.EndTime) {
return
}
var recentT = klines[end-(inc.Window-1) : end+1]
val, err := calculateCorrelation(recentT, inc.Window, KLineAmplitudeMapper, indicator.KLineVolumeMapper)
if err != nil {
log.WithError(err).Error("can not calculate")
return
}
val *= -1
inc.Values.Push(val)
inc.LastValue = val
if len(inc.Values) > indicator.MaxNumOfVOL {
inc.Values = inc.Values[indicator.MaxNumOfVOLTruncateSize-1:]
}
inc.EndTime = klines[end].GetEndTime().Time()
inc.EmitUpdate(val)
}
func (inc *AVD) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
if inc.Interval != interval {
return
}
inc.Update(window)
}
func (inc *AVD) Bind(updater indicator.KLineWindowUpdater) {
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
}
func calculateCorrelation(klines []types.KLine, window int, valA KLineValueMapper, valB KLineValueMapper) (float64, error) {
length := len(klines)
if length == 0 || length < window {
return 0.0, fmt.Errorf("insufficient elements for calculating VOL with window = %d", window)
}
sumA, sumB, sumAB, squareSumA, squareSumB := 0., 0., 0., 0., 0.
for _, k := range klines {
// sum of elements of array A
sumA += valA(k)
// sum of elements of array B
sumB += valB(k)
// sum of A[i] * B[i].
sumAB = sumAB + valA(k)*valB(k)
// sum of square of array elements.
squareSumA = squareSumA + valA(k)*valA(k)
squareSumB = squareSumB + valB(k)*valB(k)
}
// calculating correlation coefficient.
corr := (float64(window)*sumAB - sumA*sumB) /
math.Sqrt((float64(window)*squareSumA-sumA*sumA)*(float64(window)*squareSumB-sumB*sumB))
return corr, nil
}
func KLineAmplitudeMapper(k types.KLine) float64 {
return k.High.Div(k.Low).Float64()
}

View File

@ -1,15 +0,0 @@
// Code generated by "callbackgen -type AVD"; DO NOT EDIT.
package factorzoo
import ()
func (inc *AVD) OnUpdate(cb func(val float64)) {
inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb)
}
func (inc *AVD) EmitUpdate(val float64) {
for _, cb := range inc.UpdateCallbacks {
cb(val)
}
}

View File

@ -1,131 +0,0 @@
package factorzoo
import (
"fmt"
"time"
"github.com/c9s/bbgo/pkg/indicator"
"github.com/c9s/bbgo/pkg/types"
log "github.com/sirupsen/logrus"
)
//go:generate callbackgen -type LSBAR
type LSBAR struct {
types.SeriesBase
types.IntervalWindow
// Values
Values types.Float64Slice
LastValue float64
EndTime time.Time
UpdateCallbacks []func(val float64)
}
func (inc *LSBAR) Index(i int) float64 {
if inc.Values == nil {
return 0
}
return inc.Values.Index(i)
}
func (inc *LSBAR) Last() float64 {
if inc.Values.Length() == 0 {
return 0
}
return inc.Values.Last()
}
func (inc *LSBAR) Length() int {
if inc.Values == nil {
return 0
}
return inc.Values.Length()
}
//var _ types.SeriesExtend = &LSBAR{}
func (inc *LSBAR) Update(klines []types.KLine) {
if inc.Values == nil {
inc.SeriesBase.Series = inc
}
if len(klines) < inc.Window {
return
}
var end = len(klines) - 1
var lastKLine = klines[end]
if inc.EndTime != zeroTime && lastKLine.GetEndTime().Before(inc.EndTime) {
return
}
var recentT = klines[end-(inc.Window-1) : end+1]
val, err := calculateBar(recentT, inc.Window, indicator.KLineOpenPriceMapper, indicator.KLineClosePriceMapper, indicator.KLineHighPriceMapper, indicator.KLineLowPriceMapper)
if err != nil {
log.WithError(err).Error("can not calculate")
return
}
inc.Values.Push(val)
inc.LastValue = val
if len(inc.Values) > indicator.MaxNumOfVOL {
inc.Values = inc.Values[indicator.MaxNumOfVOLTruncateSize-1:]
}
inc.EndTime = klines[end].GetEndTime().Time()
inc.EmitUpdate(val)
}
func (inc *LSBAR) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
if inc.Interval != interval {
return
}
inc.Update(window)
}
func (inc *LSBAR) Bind(updater indicator.KLineWindowUpdater) {
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
}
func calculateBar(klines []types.KLine, window int, valO KLineValueMapper, valC KLineValueMapper, valH KLineValueMapper, valL KLineValueMapper) (float64, error) {
length := len(klines)
if length == 0 || length < window {
return 0.0, fmt.Errorf("insufficient elements for calculating VOL with window = %d", window)
}
s1 := 0.
s2 := 0.
for _, p := range klines[length-int(window/12) : length-1] {
bar := (valO(p) - valC(p)) / (valH(p) - valL(p))
if valO(p) > valC(p) {
s1 += bar
} else if valO(p) < valC(p) {
s2 += -bar
}
}
s := s1 / s2
l1 := 0.
l2 := 0.
for _, p := range klines[length-window : length-1] {
bar := (valO(p) - valC(p)) / (valH(p) - valL(p))
if valO(p) > valC(p) {
l1 += bar
} else if valO(p) < valC(p) {
l2 += -bar
}
}
l := l1 / l2
alpha := s / l
return alpha, nil
}

View File

@ -1,15 +0,0 @@
// Code generated by "callbackgen -type LSBAR"; DO NOT EDIT.
package factorzoo
import ()
func (inc *LSBAR) OnUpdate(cb func(val float64)) {
inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb)
}
func (inc *LSBAR) EmitUpdate(val float64) {
for _, cb := range inc.UpdateCallbacks {
cb(val)
}
}

View File

@ -1,15 +0,0 @@
// Code generated by "callbackgen -type MOM2"; DO NOT EDIT.
package factorzoo
import ()
func (inc *MOM2) OnUpdate(cb func(val float64)) {
inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb)
}
func (inc *MOM2) EmitUpdate(val float64) {
for _, cb := range inc.UpdateCallbacks {
cb(val)
}
}

View File

@ -6,10 +6,12 @@ import (
"github.com/c9s/bbgo/pkg/indicator"
"github.com/c9s/bbgo/pkg/types"
log "github.com/sirupsen/logrus"
)
// gap jump momentum
// if the gap between current open price and previous close price gets larger
// meaning an opening price jump was happened, the larger momentum we get is our alpha, MOM
//go:generate callbackgen -type MOM
type MOM struct {
types.SeriesBase
@ -19,6 +21,9 @@ type MOM struct {
Values types.Float64Slice
LastValue float64
opens *types.Queue
closes *types.Queue
EndTime time.Time
UpdateCallbacks []func(val float64)
@ -47,40 +52,31 @@ func (inc *MOM) Length() int {
//var _ types.SeriesExtend = &MOM{}
func (inc *MOM) Update(klines []types.KLine) {
if inc.Values == nil {
func (inc *MOM) Update(open, close float64) {
if inc.SeriesBase.Series == nil {
inc.SeriesBase.Series = inc
inc.opens = types.NewQueue(inc.Window)
inc.closes = types.NewQueue(inc.Window + 1)
}
if len(klines) < inc.Window {
return
inc.opens.Update(open)
inc.closes.Update(close)
if inc.opens.Length() >= inc.Window && inc.closes.Length() >= inc.Window {
gap := inc.opens.Last()/inc.closes.Index(1) - 1
inc.Values.Push(gap)
}
}
var end = len(klines) - 1
var lastKLine = klines[end]
if inc.EndTime != zeroTime && lastKLine.GetEndTime().Before(inc.EndTime) {
return
func (inc *MOM) CalculateAndUpdate(allKLines []types.KLine) {
if len(inc.Values) == 0 {
for _, k := range allKLines {
inc.PushK(k)
}
inc.EmitUpdate(inc.Last())
} else {
k := allKLines[len(allKLines)-1]
inc.PushK(k)
inc.EmitUpdate(inc.Last())
}
var recentT = klines[end-(inc.Window-1) : end+1]
val, err := calculateMomentum(recentT, inc.Window, indicator.KLineOpenPriceMapper, indicator.KLineClosePriceMapper)
if err != nil {
log.WithError(err).Error("can not calculate")
return
}
inc.Values.Push(val)
inc.LastValue = val
if len(inc.Values) > indicator.MaxNumOfVOL {
inc.Values = inc.Values[indicator.MaxNumOfVOLTruncateSize-1:]
}
inc.EndTime = klines[end].GetEndTime().Time()
inc.EmitUpdate(val)
}
func (inc *MOM) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
@ -88,13 +84,23 @@ func (inc *MOM) handleKLineWindowUpdate(interval types.Interval, window types.KL
return
}
inc.Update(window)
inc.CalculateAndUpdate(window)
}
func (inc *MOM) Bind(updater indicator.KLineWindowUpdater) {
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
}
func (inc *MOM) PushK(k types.KLine) {
if inc.EndTime != zeroTime && k.EndTime.Before(inc.EndTime) {
return
}
inc.Update(k.Open.Float64(), k.Close.Float64())
inc.EndTime = k.EndTime.Time()
inc.EmitUpdate(inc.Last())
}
func calculateMomentum(klines []types.KLine, window int, valA KLineValueMapper, valB KLineValueMapper) (float64, error) {
length := len(klines)
if length == 0 || length < window {

View File

@ -1,107 +0,0 @@
package factorzoo
import (
"fmt"
"time"
"github.com/c9s/bbgo/pkg/indicator"
"github.com/c9s/bbgo/pkg/types"
log "github.com/sirupsen/logrus"
)
//go:generate callbackgen -type MOM2
type MOM2 struct {
types.SeriesBase
types.IntervalWindow
// Values
Values types.Float64Slice
LastValue float64
EndTime time.Time
UpdateCallbacks []func(val float64)
}
func (inc *MOM2) Index(i int) float64 {
if inc.Values == nil {
return 0
}
return inc.Values.Index(i)
}
func (inc *MOM2) Last() float64 {
if inc.Values.Length() == 0 {
return 0
}
return inc.Values.Last()
}
func (inc *MOM2) Length() int {
if inc.Values == nil {
return 0
}
return inc.Values.Length()
}
//var _ types.SeriesExtend = &MOM2{}
func (inc *MOM2) Update(klines []types.KLine) {
if inc.Values == nil {
inc.SeriesBase.Series = inc
}
if len(klines) < inc.Window {
return
}
var end = len(klines) - 1
var lastKLine = klines[end]
if inc.EndTime != zeroTime && lastKLine.GetEndTime().Before(inc.EndTime) {
return
}
var recentT = klines[end-(inc.Window-1) : end+1]
val, err := calculateMomentum2(recentT, inc.Window, indicator.KLineHighPriceMapper, indicator.KLineLowPriceMapper, indicator.KLineClosePriceMapper, indicator.KLineVolumeMapper)
if err != nil {
log.WithError(err).Error("can not calculate")
return
}
inc.Values.Push(val)
inc.LastValue = val
if len(inc.Values) > indicator.MaxNumOfVOL {
inc.Values = inc.Values[indicator.MaxNumOfVOLTruncateSize-1:]
}
inc.EndTime = klines[end].GetEndTime().Time()
inc.EmitUpdate(val)
}
func (inc *MOM2) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
if inc.Interval != interval {
return
}
inc.Update(window)
}
func (inc *MOM2) Bind(updater indicator.KLineWindowUpdater) {
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
}
func calculateMomentum2(klines []types.KLine, window int, valA KLineValueMapper, valB KLineValueMapper, valC KLineValueMapper, valD KLineValueMapper) (float64, error) {
length := len(klines)
if length == 0 || length < window {
return 0.0, fmt.Errorf("insufficient elements for calculating VOL with window = %d", window)
}
momentum := (valA(klines[length-1]) + valB(klines[length-1]) + valC(klines[length-1])/3.) * valD(klines[length-1])
return momentum, nil
}

View File

@ -4,12 +4,12 @@ package factorzoo
import ()
func (inc *PMR) OnUpdate(cb func(val float64)) {
inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb)
func (inc *PMR) OnUpdate(cb func(value float64)) {
inc.updateCallbacks = append(inc.updateCallbacks, cb)
}
func (inc *PMR) EmitUpdate(val float64) {
for _, cb := range inc.UpdateCallbacks {
cb(val)
func (inc *PMR) EmitUpdate(value float64) {
for _, cb := range inc.updateCallbacks {
cb(value)
}
}

View File

@ -1,86 +1,74 @@
package factorzoo
import (
"fmt"
"time"
"github.com/c9s/bbgo/pkg/indicator"
"github.com/c9s/bbgo/pkg/types"
log "github.com/sirupsen/logrus"
"gonum.org/v1/gonum/stat"
)
// price mean reversion
// assume that the quotient of SMA over close price will dynamically revert into one.
// so this fraction value is our alpha, PMR
//go:generate callbackgen -type PMR
type PMR struct {
types.SeriesBase
types.IntervalWindow
types.SeriesBase
// Values
Values types.Float64Slice
LastValue float64
Values types.Float64Slice
SMA *indicator.SMA
EndTime time.Time
UpdateCallbacks []func(val float64)
updateCallbacks []func(value float64)
}
func (inc *PMR) Index(i int) float64 {
if inc.Values == nil {
return 0
var _ types.SeriesExtend = &PMR{}
func (inc *PMR) Update(price float64) {
if inc.SeriesBase.Series == nil {
inc.SeriesBase.Series = inc
inc.SMA = &indicator.SMA{IntervalWindow: inc.IntervalWindow}
}
inc.SMA.Update(price)
if inc.SMA.Length() >= inc.Window {
reversion := inc.SMA.Last() / price
inc.Values.Push(reversion)
}
return inc.Values.Index(i)
}
func (inc *PMR) Last() float64 {
if inc.Values.Length() == 0 {
if len(inc.Values) == 0 {
return 0
}
return inc.Values.Last()
return inc.Values[len(inc.Values)-1]
}
func (inc *PMR) Index(i int) float64 {
if i >= len(inc.Values) {
return 0
}
return inc.Values[len(inc.Values)-1-i]
}
func (inc *PMR) Length() int {
if inc.Values == nil {
return 0
}
return inc.Values.Length()
return len(inc.Values)
}
//var _ types.SeriesExtend = &PMR{}
func (inc *PMR) Update(klines []types.KLine) {
if inc.Values == nil {
inc.SeriesBase.Series = inc
func (inc *PMR) CalculateAndUpdate(allKLines []types.KLine) {
if len(inc.Values) == 0 {
for _, k := range allKLines {
inc.PushK(k)
}
inc.EmitUpdate(inc.Last())
} else {
k := allKLines[len(allKLines)-1]
inc.PushK(k)
inc.EmitUpdate(inc.Last())
}
if len(klines) < inc.Window {
return
}
var end = len(klines) - 1
var lastKLine = klines[end]
if inc.EndTime != zeroTime && lastKLine.GetEndTime().Before(inc.EndTime) {
return
}
var recentT = klines[end-(inc.Window-1) : end+1]
val, err := calculateReversion(recentT, inc.Window, indicator.KLineClosePriceMapper)
if err != nil {
log.WithError(err).Error("can not calculate")
return
}
inc.Values.Push(val)
inc.LastValue = val
if len(inc.Values) > indicator.MaxNumOfVOL {
inc.Values = inc.Values[indicator.MaxNumOfVOLTruncateSize-1:]
}
inc.EndTime = klines[end].GetEndTime().Time()
inc.EmitUpdate(val)
}
func (inc *PMR) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
@ -88,25 +76,33 @@ func (inc *PMR) handleKLineWindowUpdate(interval types.Interval, window types.KL
return
}
inc.Update(window)
inc.CalculateAndUpdate(window)
}
func (inc *PMR) Bind(updater indicator.KLineWindowUpdater) {
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
}
func calculateReversion(klines []types.KLine, window int, val KLineValueMapper) (float64, error) {
length := len(klines)
if length == 0 || length < window {
return 0.0, fmt.Errorf("insufficient elements for calculating VOL with window = %d", window)
func (inc *PMR) PushK(k types.KLine) {
if inc.EndTime != zeroTime && k.EndTime.Before(inc.EndTime) {
return
}
ma := 0.
for _, p := range klines[length-window : length-1] {
ma += val(p)
}
ma /= float64(window)
reversion := ma / val(klines[length-1])
return reversion, nil
inc.Update(indicator.KLineClosePriceMapper(k))
inc.EndTime = k.EndTime.Time()
inc.EmitUpdate(inc.Last())
}
func CalculateKLinesPMR(allKLines []types.KLine, window int) float64 {
return pmr(indicator.MapKLinePrice(allKLines, indicator.KLineClosePriceMapper), window)
}
func pmr(prices []float64, window int) float64 {
var end = len(prices) - 1
if end == 0 {
return prices[0]
}
reversion := -stat.Mean(prices[end-window:end], nil) / prices[end]
return reversion
}

View File

@ -0,0 +1,115 @@
package factorzoo
import (
"time"
"github.com/c9s/bbgo/pkg/indicator"
"github.com/c9s/bbgo/pkg/types"
"gonum.org/v1/gonum/stat"
)
// price volume divergence
// if the correlation of two time series gets smaller, they are diverging.
// so the negative value of the correlation of close price and volume is our alpha, PVD
var zeroTime time.Time
type KLineValueMapper func(k types.KLine) float64
//go:generate callbackgen -type PVD
type PVD struct {
types.IntervalWindow
types.SeriesBase
Values types.Float64Slice
Prices *types.Queue
Volumes *types.Queue
EndTime time.Time
updateCallbacks []func(value float64)
}
var _ types.SeriesExtend = &PVD{}
func (inc *PVD) Update(price float64, volume float64) {
if inc.SeriesBase.Series == nil {
inc.SeriesBase.Series = inc
inc.Prices = types.NewQueue(inc.Window)
inc.Volumes = types.NewQueue(inc.Window)
}
inc.Prices.Update(price)
inc.Volumes.Update(volume)
if inc.Prices.Length() >= inc.Window && inc.Volumes.Length() >= inc.Window {
divergence := -types.Correlation(inc.Prices, inc.Volumes, inc.Window)
inc.Values.Push(divergence)
}
}
func (inc *PVD) Last() float64 {
if len(inc.Values) == 0 {
return 0
}
return inc.Values[len(inc.Values)-1]
}
func (inc *PVD) Index(i int) float64 {
if i >= len(inc.Values) {
return 0
}
return inc.Values[len(inc.Values)-1-i]
}
func (inc *PVD) Length() int {
return len(inc.Values)
}
func (inc *PVD) CalculateAndUpdate(allKLines []types.KLine) {
if len(inc.Values) == 0 {
for _, k := range allKLines {
inc.PushK(k)
}
inc.EmitUpdate(inc.Last())
} else {
k := allKLines[len(allKLines)-1]
inc.PushK(k)
inc.EmitUpdate(inc.Last())
}
}
func (inc *PVD) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
if inc.Interval != interval {
return
}
inc.CalculateAndUpdate(window)
}
func (inc *PVD) Bind(updater indicator.KLineWindowUpdater) {
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
}
func (inc *PVD) PushK(k types.KLine) {
if inc.EndTime != zeroTime && k.EndTime.Before(inc.EndTime) {
return
}
inc.Update(indicator.KLineClosePriceMapper(k), indicator.KLineVolumeMapper(k))
inc.EndTime = k.EndTime.Time()
inc.EmitUpdate(inc.Last())
}
func CalculateKLinesPVD(allKLines []types.KLine, window int) float64 {
return pvd(indicator.MapKLinePrice(allKLines, indicator.KLineClosePriceMapper), indicator.MapKLinePrice(allKLines, indicator.KLineVolumeMapper), window)
}
func pvd(prices []float64, volumes []float64, window int) float64 {
var end = len(prices) - 1
if end == 0 {
return prices[0]
}
divergence := -stat.Correlation(prices[end-window:end], volumes[end-window:end], nil)
return divergence
}

View File

@ -0,0 +1,15 @@
// Code generated by "callbackgen -type PVD"; DO NOT EDIT.
package factorzoo
import ()
func (inc *PVD) OnUpdate(cb func(value float64)) {
inc.updateCallbacks = append(inc.updateCallbacks, cb)
}
func (inc *PVD) EmitUpdate(value float64) {
for _, cb := range inc.updateCallbacks {
cb(value)
}
}

View File

@ -1,64 +1,70 @@
package factorzoo
import (
"fmt"
"time"
"github.com/c9s/bbgo/pkg/indicator"
"github.com/c9s/bbgo/pkg/types"
log "github.com/sirupsen/logrus"
)
// simply internal return rate over certain timeframe(interval)
//go:generate callbackgen -type RR
type RR struct {
types.IntervalWindow
types.SeriesBase
// Values
Values types.Float64Slice
prices *types.Queue
Values types.Float64Slice
EndTime time.Time
UpdateCallbacks []func(val float64)
updateCallbacks []func(value float64)
}
var _ types.SeriesExtend = &RR{}
func (inc *RR) Update(price float64) {
if inc.SeriesBase.Series == nil {
inc.SeriesBase.Series = inc
inc.prices = types.NewQueue(inc.Window)
}
inc.prices.Update(price)
irr := inc.prices.Last()/inc.prices.Index(1) - 1
inc.Values.Push(irr)
}
func (inc *RR) Last() float64 {
if len(inc.Values) == 0 {
return 0.0
return 0
}
return inc.Values[len(inc.Values)-1]
}
func (inc *RR) CalculateAndUpdate(klines []types.KLine) {
if len(klines) < inc.Window {
return
func (inc *RR) Index(i int) float64 {
if i >= len(inc.Values) {
return 0
}
var end = len(klines) - 1
var lastKLine = klines[end]
return inc.Values[len(inc.Values)-1-i]
}
if inc.EndTime != zeroTime && lastKLine.GetEndTime().Before(inc.EndTime) {
return
func (inc *RR) Length() int {
return len(inc.Values)
}
func (inc *RR) CalculateAndUpdate(allKLines []types.KLine) {
if len(inc.Values) == 0 {
for _, k := range allKLines {
inc.PushK(k)
}
inc.EmitUpdate(inc.Last())
} else {
k := allKLines[len(allKLines)-1]
inc.PushK(k)
inc.EmitUpdate(inc.Last())
}
var recentT = klines[end-(inc.Window-1) : end+1]
val, err := calculateReturn(recentT, inc.Window, indicator.KLineClosePriceMapper)
if err != nil {
log.WithError(err).Error("can not calculate")
return
}
inc.Values.Push(val)
if len(inc.Values) > indicator.MaxNumOfVOL {
inc.Values = inc.Values[indicator.MaxNumOfVOLTruncateSize-1:]
}
inc.EndTime = klines[end].GetEndTime().Time()
inc.EmitUpdate(val)
}
func (inc *RR) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
@ -73,13 +79,34 @@ func (inc *RR) Bind(updater indicator.KLineWindowUpdater) {
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
}
func calculateReturn(klines []types.KLine, window int, val KLineValueMapper) (float64, error) {
length := len(klines)
if length == 0 || length < window {
return 0.0, fmt.Errorf("insufficient elements for calculating VOL with window = %d", window)
func (inc *RR) BindK(target indicator.KLineClosedEmitter, symbol string, interval types.Interval) {
target.OnKLineClosed(types.KLineWith(symbol, interval, inc.PushK))
}
func (inc *RR) PushK(k types.KLine) {
if inc.EndTime != zeroTime && k.EndTime.Before(inc.EndTime) {
return
}
rate := val(klines[length-1])/val(klines[length-2]) - 1
return rate, nil
inc.Update(indicator.KLineClosePriceMapper(k))
inc.EndTime = k.EndTime.Time()
inc.EmitUpdate(inc.Last())
}
func (inc *RR) LoadK(allKLines []types.KLine) {
for _, k := range allKLines {
inc.PushK(k)
}
inc.EmitUpdate(inc.Last())
}
//func calculateReturn(klines []types.KLine, window int, val KLineValueMapper) (float64, error) {
// length := len(klines)
// if length == 0 || length < window {
// return 0.0, fmt.Errorf("insufficient elements for calculating VOL with window = %d", window)
// }
//
// rate := val(klines[length-1])/val(klines[length-2]) - 1
//
// return rate, nil
//}

View File

@ -4,12 +4,12 @@ package factorzoo
import ()
func (inc *RR) OnUpdate(cb func(val float64)) {
inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb)
func (inc *RR) OnUpdate(cb func(value float64)) {
inc.updateCallbacks = append(inc.updateCallbacks, cb)
}
func (inc *RR) EmitUpdate(val float64) {
for _, cb := range inc.UpdateCallbacks {
cb(val)
func (inc *RR) EmitUpdate(value float64) {
for _, cb := range inc.updateCallbacks {
cb(value)
}
}

View File

@ -6,10 +6,12 @@ import (
"github.com/c9s/bbgo/pkg/indicator"
"github.com/c9s/bbgo/pkg/types"
log "github.com/sirupsen/logrus"
)
// quarterly volume momentum
// assume that the quotient of volume SMA over latest volume will dynamically revert into one.
// so this fraction value is our alpha, PMR
//go:generate callbackgen -type VMOM
type VMOM struct {
types.SeriesBase
@ -19,6 +21,8 @@ type VMOM struct {
Values types.Float64Slice
LastValue float64
volumes *types.Queue
EndTime time.Time
UpdateCallbacks []func(val float64)
@ -45,42 +49,31 @@ func (inc *VMOM) Length() int {
return inc.Values.Length()
}
//var _ types.SeriesExtend = &VMOM{}
var _ types.SeriesExtend = &VMOM{}
func (inc *VMOM) Update(klines []types.KLine) {
if inc.Values == nil {
func (inc *VMOM) Update(volume float64) {
if inc.SeriesBase.Series == nil {
inc.SeriesBase.Series = inc
inc.volumes = types.NewQueue(inc.Window)
}
if len(klines) < inc.Window {
return
inc.volumes.Update(volume)
if inc.volumes.Length() >= inc.Window {
v := inc.volumes.Last() / inc.volumes.Mean()
inc.Values.Push(v)
}
}
var end = len(klines) - 1
var lastKLine = klines[end]
if inc.EndTime != zeroTime && lastKLine.GetEndTime().Before(inc.EndTime) {
return
func (inc *VMOM) CalculateAndUpdate(allKLines []types.KLine) {
if len(inc.Values) == 0 {
for _, k := range allKLines {
inc.PushK(k)
}
inc.EmitUpdate(inc.Last())
} else {
k := allKLines[len(allKLines)-1]
inc.PushK(k)
inc.EmitUpdate(inc.Last())
}
var recentT = klines[end-(inc.Window-1) : end+1]
val, err := calculateVolumeMomentum(recentT, inc.Window, indicator.KLineVolumeMapper, indicator.KLineClosePriceMapper)
if err != nil {
log.WithError(err).Error("can not calculate")
return
}
inc.Values.Push(val)
inc.LastValue = val
if len(inc.Values) > indicator.MaxNumOfVOL {
inc.Values = inc.Values[indicator.MaxNumOfVOLTruncateSize-1:]
}
inc.EndTime = klines[end].GetEndTime().Time()
inc.EmitUpdate(val)
}
func (inc *VMOM) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
@ -88,13 +81,23 @@ func (inc *VMOM) handleKLineWindowUpdate(interval types.Interval, window types.K
return
}
inc.Update(window)
inc.CalculateAndUpdate(window)
}
func (inc *VMOM) Bind(updater indicator.KLineWindowUpdater) {
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
}
func (inc *VMOM) PushK(k types.KLine) {
if inc.EndTime != zeroTime && k.EndTime.Before(inc.EndTime) {
return
}
inc.Update(k.Volume.Float64())
inc.EndTime = k.EndTime.Time()
inc.EmitUpdate(inc.Last())
}
func calculateVolumeMomentum(klines []types.KLine, window int, valV KLineValueMapper, valP KLineValueMapper) (float64, error) {
length := len(klines)
if length == 0 || length < window {