feature: add some new ma indicators

This commit is contained in:
zenix 2022-04-19 19:22:22 +09:00
parent fcaef0219a
commit 22d8c2efff
16 changed files with 474 additions and 28 deletions

68
pkg/indicator/dema.go Normal file
View File

@ -0,0 +1,68 @@
package indicator
import (
"github.com/c9s/bbgo/pkg/types"
)
// Refer: Double Exponential Moving Average
// Refer URL: https://investopedia.com/terms/d/double-exponential-moving-average.asp
//go:generate callbackgen -type DEMA
type DEMA struct {
types.IntervalWindow
Values types.Float64Slice
a1 *EWMA
a2 *EWMA
UpdateCallbacks []func(value float64)
}
func (inc *DEMA) Update(value float64) {
if len(inc.Values) == 0 {
inc.a1 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}}
inc.a2 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}}
}
inc.a1.Update(value)
inc.a2.Update(inc.a1.Last())
inc.Values.Push(2*inc.a1.Last() - inc.a2.Last())
if len(inc.Values) > MaxNumOfEWMA {
inc.Values = inc.Values[MaxNumOfEWMATruncateSize-1:]
}
}
func (inc *DEMA) Last() float64 {
return inc.Values.Last()
}
func (inc *DEMA) Index(i int) float64 {
if len(inc.Values)-i-1 >= 0 {
return inc.Values[len(inc.Values)-1-i]
}
return 0
}
func (inc *DEMA) Length() int {
return len(inc.Values)
}
var _ types.Series = &DEMA{}
func (inc *DEMA) calculateAndUpdate(allKLines []types.KLine) {
for _, k := range allKLines {
inc.Update(k.Close.Float64())
inc.EmitUpdate(inc.Last())
}
}
func (inc *DEMA) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
if inc.Interval != interval {
return
}
inc.calculateAndUpdate(window)
}
func (inc *DEMA) Bind(updater KLineWindowUpdater) {
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
}

View File

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

View File

@ -124,32 +124,6 @@ func ewma(prices []float64, multiplier float64) float64 {
return prices[end]*multiplier + (1-multiplier)*ewma(prices[:end], multiplier)
}
type KLinePriceMapper func(k types.KLine) float64
func KLineOpenPriceMapper(k types.KLine) float64 {
return k.Open.Float64()
}
func KLineClosePriceMapper(k types.KLine) float64 {
return k.Close.Float64()
}
func KLineTypicalPriceMapper(k types.KLine) float64 {
return (k.High.Float64() + k.Low.Float64() + k.Close.Float64()) / 3.
}
func MapKLinePrice(kLines []types.KLine, f KLinePriceMapper) (prices []float64) {
for _, k := range kLines {
prices = append(prices, f(k))
}
return prices
}
type KLineWindowUpdater interface {
OnKLineWindowUpdate(func(interval types.Interval, window types.KLineWindow))
}
func (inc *EWMA) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
if inc.Interval != interval {
return

73
pkg/indicator/hull.go Normal file
View File

@ -0,0 +1,73 @@
package indicator
import (
"math"
"github.com/c9s/bbgo/pkg/types"
)
// Refer: Hull Moving Average
// Refer URL: https://fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/hull-moving-average
//go:generate callbackgen -type HULL
type HULL struct {
types.IntervalWindow
ma1 *EWMA
ma2 *EWMA
result *EWMA
UpdateCallbacks []func(value float64)
}
func (inc *HULL) Update(value float64) {
if inc.result.Length() == 0 {
inc.ma1 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window/2}}
inc.ma2 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}}
inc.result = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, int(math.Sqrt(float64(inc.Window)))}}
}
inc.ma1.Update(value)
inc.ma2.Update(value)
inc.result.Update(2 * inc.ma1.Last() - inc.ma2.Last())
}
func (inc *HULL) Last() float64 {
return inc.result.Last()
}
func (inc *HULL) Index(i int) float64 {
return inc.result.Index(i)
}
func (inc *HULL) Length() int {
return inc.result.Length()
}
var _ types.Series = &HULL{}
// TODO: should we just ignore the possible overlapping?
func (inc *HULL) calculateAndUpdate(allKLines []types.KLine) {
doable := false
if inc.ma1.Length() == 0 {
doable = true
}
for _, k := range allKLines {
if !doable && k.StartTime.After(inc.ma1.LastOpenTime) {
doable = true
}
if doable {
inc.Update(k.Close.Float64())
inc.EmitUpdate(inc.Last())
}
}
}
func (inc *HULL) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
if inc.Interval != interval {
return
}
inc.calculateAndUpdate(window)
}
func (inc *HULL) Bind(updater KLineWindowUpdater) {
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
}

View File

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

View File

@ -37,11 +37,11 @@ func (l *Line) Bind(updater KLineWindowUpdater) {
}
func (l *Line) Last() float64 {
return (l.end-l.start) / float64(l.startIndex - l.endIndex) * float64(l.endIndex) + l.end
return (l.end-l.start)/float64(l.startIndex-l.endIndex)*float64(l.endIndex) + l.end
}
func (l *Line) Index(i int) float64 {
return (l.end-l.start) / float64(l.startIndex - l.endIndex) * float64(l.endIndex - i) + l.end
return (l.end-l.start)/float64(l.startIndex-l.endIndex)*float64(l.endIndex-i) + l.end
}
func (l *Line) Length() int {

73
pkg/indicator/tema.go Normal file
View File

@ -0,0 +1,73 @@
package indicator
import (
"github.com/c9s/bbgo/pkg/types"
)
// Refer: Triple Exponential Moving Average (TEMA)
// URL: https://investopedia.com/terms/t/triple-exponential-moving-average.asp
//go:generate callbackgen -type TEMA
type TEMA struct {
types.IntervalWindow
Values types.Float64Slice
A1 *EWMA
A2 *EWMA
A3 *EWMA
UpdateCallbacks []func(value float64)
}
func (inc *TEMA) Update(value float64) {
if len(inc.Values) == 0 {
inc.A1 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}}
inc.A2 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}}
inc.A3 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}}
}
inc.A1.Update(value)
a1 := inc.A1.Last()
inc.A2.Update(a1)
a2 := inc.A2.Last()
inc.A3.Update(a2)
a3 := inc.A3.Last()
inc.Values.Push(3*a1 - 3*a2 + a3)
}
func (inc *TEMA) Last() float64 {
if len(inc.Values) > 0 {
return inc.Values[len(inc.Values)-1]
}
return 0.0
}
func (inc *TEMA) Index(i int) float64 {
if i >= len(inc.Values) {
return 0
}
return inc.Values[len(inc.Values)-i-1]
}
func (inc *TEMA) Length() int {
return len(inc.Values)
}
var _ types.Series = &TEMA{}
func (inc *TEMA) calculateAndUpdate(allKLines []types.KLine) {
for _, k := range allKLines {
inc.Update(k.Close.Float64())
inc.EmitUpdate(inc.Last())
}
}
func (inc *TEMA) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
if inc.Interval != interval {
return
}
inc.calculateAndUpdate(window)
}
func (inc *TEMA) Bind(updater KLineWindowUpdater) {
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
}

View File

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

1
pkg/indicator/till.go Normal file
View File

@ -0,0 +1 @@
package indicator

1
pkg/indicator/tsf.go Normal file
View File

@ -0,0 +1 @@
package indicator

29
pkg/indicator/util.go Normal file
View File

@ -0,0 +1,29 @@
package indicator
import "github.com/c9s/bbgo/pkg/types"
type KLinePriceMapper func(k types.KLine) float64
func KLineOpenPriceMapper(k types.KLine) float64 {
return k.Open.Float64()
}
func KLineClosePriceMapper(k types.KLine) float64 {
return k.Close.Float64()
}
func KLineTypicalPriceMapper(k types.KLine) float64 {
return (k.High.Float64() + k.Low.Float64() + k.Close.Float64()) / 3.
}
func MapKLinePrice(kLines []types.KLine, f KLinePriceMapper) (prices []float64) {
for _, k := range kLines {
prices = append(prices, f(k))
}
return prices
}
type KLineWindowUpdater interface {
OnKLineWindowUpdate(func(interval types.Interval, window types.KLineWindow))
}

1
pkg/indicator/vidya.go Normal file
View File

@ -0,0 +1 @@
package indicator

87
pkg/indicator/wwma.go Normal file
View File

@ -0,0 +1,87 @@
package indicator
import (
"time"
"github.com/c9s/bbgo/pkg/types"
)
// Refer: Welles Wilder's Moving Average
// Refer URL: http://fxcorporate.com/help/MS/NOTFIFO/i_WMA.html
const MaxNumOfWWMA = 5_000
const MaxNumOfWWMATruncateSize = 100
//go:generate callbackgen -type WWMA
type WWMA struct {
types.IntervalWindow
Values types.Float64Slice
LastOpenTime time.Time
UpdateCallbacks []func(value float64)
}
func (inc *WWMA) Update(value float64) {
if len(inc.Values) == 0 {
inc.Values.Push(value)
return
} else if len(inc.Values) > MaxNumOfWWMA {
inc.Values = inc.Values[MaxNumOfWWMATruncateSize-1:]
}
last := inc.Last()
wma := last + (value-last)/float64(inc.Window)
inc.Values.Push(wma)
}
func (inc *WWMA) Last() float64 {
if len(inc.Values) == 0 {
return 0
}
return inc.Values[len(inc.Values)-1]
}
func (inc *WWMA) Index(i int) float64 {
if i >= len(inc.Values) {
return 0
}
return inc.Values[len(inc.Values)-1-i]
}
func (inc *WWMA) Length() int {
return len(inc.Values)
}
func (inc *WWMA) calculateAndUpdate(allKLines []types.KLine) {
if len(allKLines) < inc.Window {
// we can't calculate
return
}
doable := false
for _, k := range allKLines {
if !doable && k.StartTime.After(inc.LastOpenTime) {
doable = true
}
if doable {
inc.Update(k.Close.Float64())
inc.LastOpenTime = k.StartTime.Time()
inc.EmitUpdate(inc.Last())
}
}
}
func (inc *WWMA) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
if inc.Interval != interval {
return
}
inc.calculateAndUpdate(window)
}
func (inc *WWMA) Bind(updater KLineWindowUpdater) {
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
}
var _ types.Series = &WWMA{}

View File

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

64
pkg/indicator/zlema.go Normal file
View File

@ -0,0 +1,64 @@
package indicator
import (
"github.com/c9s/bbgo/pkg/types"
)
// Refer: Zero Lag Exponential Moving Average
// Refer URL: https://en.wikipedia.org/wiki/Zero_lag_exponential_moving_average
//go:generate callbackgen -type ZLEMA
type ZLEMA struct {
types.IntervalWindow
data *EWMA
zlema *EWMA
lag int
UpdateCallbacks []func(value float64)
}
func (inc *ZLEMA) Index(i int) float64 {
return inc.zlema.Index(i)
}
func (inc *ZLEMA) Last() float64 {
return inc.zlema.Last()
}
func (inc *ZLEMA) Length() int {
return inc.zlema.Length()
}
func (inc *ZLEMA) Update(value float64) {
if inc.lag == 0 || inc.zlema == nil {
inc.data = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}}
inc.zlema = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}}
inc.lag = (inc.Window - 1) / 2
}
inc.data.Update(value)
data := inc.data.Last()
emaData := 2*data - inc.data.Index(inc.lag)
inc.zlema.Update(emaData)
}
var _ types.Series = &ZLEMA{}
func (inc *ZLEMA) calculateAndUpdate(allKLines []types.KLine) {
for _, k := range allKLines {
inc.Update(k.Close.Float64())
inc.EmitUpdate(inc.Last())
}
}
func (inc *ZLEMA) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
if inc.Interval != interval {
return
}
inc.calculateAndUpdate(window)
}
func (inc *ZLEMA) Bind(updater KLineWindowUpdater) {
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
}

View File

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