mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-21 22:43:52 +00:00
feature: add some new ma indicators
This commit is contained in:
parent
fcaef0219a
commit
22d8c2efff
68
pkg/indicator/dema.go
Normal file
68
pkg/indicator/dema.go
Normal 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)
|
||||
}
|
15
pkg/indicator/dema_callbacks.go
Normal file
15
pkg/indicator/dema_callbacks.go
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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
73
pkg/indicator/hull.go
Normal 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)
|
||||
}
|
15
pkg/indicator/hull_callbacks.go
Normal file
15
pkg/indicator/hull_callbacks.go
Normal 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)
|
||||
}
|
||||
}
|
73
pkg/indicator/tema.go
Normal file
73
pkg/indicator/tema.go
Normal 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)
|
||||
}
|
15
pkg/indicator/tema_callbacks.go
Normal file
15
pkg/indicator/tema_callbacks.go
Normal 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
1
pkg/indicator/till.go
Normal file
|
@ -0,0 +1 @@
|
|||
package indicator
|
1
pkg/indicator/tsf.go
Normal file
1
pkg/indicator/tsf.go
Normal file
|
@ -0,0 +1 @@
|
|||
package indicator
|
29
pkg/indicator/util.go
Normal file
29
pkg/indicator/util.go
Normal 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
1
pkg/indicator/vidya.go
Normal file
|
@ -0,0 +1 @@
|
|||
package indicator
|
87
pkg/indicator/wwma.go
Normal file
87
pkg/indicator/wwma.go
Normal 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{}
|
15
pkg/indicator/wwma_callbacks.go
Normal file
15
pkg/indicator/wwma_callbacks.go
Normal 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
64
pkg/indicator/zlema.go
Normal 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)
|
||||
}
|
15
pkg/indicator/zlema_callbacks.go
Normal file
15
pkg/indicator/zlema_callbacks.go
Normal 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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user