mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 01:01:56 +00:00
factorzoo: add customized indicators
This commit is contained in:
parent
bdb04a4322
commit
d282568614
|
@ -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()
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
115
pkg/strategy/factorzoo/factors/price_volume_divergence.go
Normal file
115
pkg/strategy/factorzoo/factors/price_volume_divergence.go
Normal 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
|
||||
}
|
15
pkg/strategy/factorzoo/factors/pvd_callbacks.go
Normal file
15
pkg/strategy/factorzoo/factors/pvd_callbacks.go
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
//}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue
Block a user