From b3741771e3997c3a98676f1dbe705af27136515a Mon Sep 17 00:00:00 2001 From: zenix Date: Tue, 26 Apr 2022 17:32:31 +0900 Subject: [PATCH] fix: window update in indicators. add: cumulative average, triangular moving average --- pkg/indicator/ca_callbacks.go | 15 +++++++ pkg/indicator/cma.go | 63 +++++++++++++++++++++++++++++ pkg/indicator/dema.go | 9 ++++- pkg/indicator/hull.go | 9 +++++ pkg/indicator/rma.go | 1 + pkg/indicator/tema.go | 9 ++++- pkg/indicator/till.go | 7 +++- pkg/indicator/tma.go | 73 ++++++++++++++++++++++++++++++++++ pkg/indicator/tma_callbacks.go | 15 +++++++ pkg/indicator/vidya.go | 9 ++++- pkg/indicator/wwma.go | 1 + pkg/indicator/zlema.go | 18 ++++++++- 12 files changed, 219 insertions(+), 10 deletions(-) create mode 100644 pkg/indicator/ca_callbacks.go create mode 100644 pkg/indicator/cma.go create mode 100644 pkg/indicator/tma.go create mode 100644 pkg/indicator/tma_callbacks.go diff --git a/pkg/indicator/ca_callbacks.go b/pkg/indicator/ca_callbacks.go new file mode 100644 index 000000000..4883dc6a6 --- /dev/null +++ b/pkg/indicator/ca_callbacks.go @@ -0,0 +1,15 @@ +// Code generated by "callbackgen -type CA"; DO NOT EDIT. + +package indicator + +import () + +func (inc *CA) OnUpdate(cb func(value float64)) { + inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) +} + +func (inc *CA) EmitUpdate(value float64) { + for _, cb := range inc.UpdateCallbacks { + cb(value) + } +} diff --git a/pkg/indicator/cma.go b/pkg/indicator/cma.go new file mode 100644 index 000000000..8040c8707 --- /dev/null +++ b/pkg/indicator/cma.go @@ -0,0 +1,63 @@ +package indicator + +import ( + "github.com/c9s/bbgo/pkg/types" +) + +// Refer: Cumulative Moving Average, Cumulative Average +// Refer: https://en.wikipedia.org/wiki/Moving_average +//go:generate callbackgen -type CA +type CA struct { + Interval types.Interval + Values types.Float64Slice + length float64 + UpdateCallbacks []func(value float64) +} + +func (inc *CA) Update(x float64) { + newVal := (inc.Values.Last()*inc.length + x) / (inc.length + 1.) + inc.length += 1 + inc.Values.Push(newVal) + if len(inc.Values) > MaxNumOfEWMA { + inc.Values = inc.Values[MaxNumOfEWMATruncateSize-1:] + } +} + +func (inc *CA) Last() float64 { + if len(inc.Values) == 0 { + return 0 + } + return inc.Values[len(inc.Values)-1] +} + +func (inc *CA) Index(i int) float64 { + if i >= len(inc.Values) { + return 0 + } + return inc.Values[len(inc.Values)-1-i] +} + +func (inc *CA) Length() int { + return len(inc.Values) +} + +var _ types.Series = &CA{} + +func (inc *CA) calculateAndUpdate(allKLines []types.KLine) { + for _, k := range allKLines { + inc.Update(k.Close.Float64()) + inc.EmitUpdate(inc.Last()) + } +} + +func (inc *CA) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { + if inc.Interval != interval { + return + } + + inc.calculateAndUpdate(window) +} + +func (inc *CA) Bind(updater KLineWindowUpdater) { + updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) +} diff --git a/pkg/indicator/dema.go b/pkg/indicator/dema.go index 17dd388e0..bc476134a 100644 --- a/pkg/indicator/dema.go +++ b/pkg/indicator/dema.go @@ -49,8 +49,13 @@ func (inc *DEMA) Length() int { var _ types.Series = &DEMA{} func (inc *DEMA) calculateAndUpdate(allKLines []types.KLine) { - for _, k := range allKLines { - inc.Update(k.Close.Float64()) + if inc.a1 == nil { + for _, k := range allKLines { + inc.Update(k.Close.Float64()) + inc.EmitUpdate(inc.Last()) + } + } else { + inc.Update(allKLines[len(allKLines)-1].Close.Float64()) inc.EmitUpdate(inc.Last()) } } diff --git a/pkg/indicator/hull.go b/pkg/indicator/hull.go index a5ab47db3..0c8347f9b 100644 --- a/pkg/indicator/hull.go +++ b/pkg/indicator/hull.go @@ -30,14 +30,23 @@ func (inc *HULL) Update(value float64) { } func (inc *HULL) Last() float64 { + if inc.result == nil { + return 0 + } return inc.result.Last() } func (inc *HULL) Index(i int) float64 { + if inc.result == nil { + return 0 + } return inc.result.Index(i) } func (inc *HULL) Length() int { + if inc.result == nil { + return 0 + } return inc.result.Length() } diff --git a/pkg/indicator/rma.go b/pkg/indicator/rma.go index 19124e491..4418ab54a 100644 --- a/pkg/indicator/rma.go +++ b/pkg/indicator/rma.go @@ -6,6 +6,7 @@ import ( "github.com/c9s/bbgo/pkg/types" ) +// Refer: Running Moving Average //go:generate callbackgen -type RMA type RMA struct { types.IntervalWindow diff --git a/pkg/indicator/tema.go b/pkg/indicator/tema.go index d93ca00b5..91d53a63d 100644 --- a/pkg/indicator/tema.go +++ b/pkg/indicator/tema.go @@ -54,8 +54,13 @@ func (inc *TEMA) Length() int { var _ types.Series = &TEMA{} func (inc *TEMA) calculateAndUpdate(allKLines []types.KLine) { - for _, k := range allKLines { - inc.Update(k.Close.Float64()) + if inc.A1 == nil { + for _, k := range allKLines { + inc.Update(k.Close.Float64()) + inc.EmitUpdate(inc.Last()) + } + } else { + inc.Update(allKLines[len(allKLines)-1].Close.Float64()) inc.EmitUpdate(inc.Last()) } } diff --git a/pkg/indicator/till.go b/pkg/indicator/till.go index 3a9e04fc5..73f97ead5 100644 --- a/pkg/indicator/till.go +++ b/pkg/indicator/till.go @@ -53,7 +53,7 @@ func (inc *TILL) Update(value float64) { } func (inc *TILL) Last() float64 { - if inc.e1.Length() == 0 { + if inc.e1 == nil || inc.e1.Length() == 0 { return 0 } e3 := inc.e3.Last() @@ -64,7 +64,7 @@ func (inc *TILL) Last() float64 { } func (inc *TILL) Index(i int) float64 { - if inc.e1.Length() <= i { + if inc.e1 == nil || inc.e1.Length() <= i { return 0 } e3 := inc.e3.Index(i) @@ -75,6 +75,9 @@ func (inc *TILL) Index(i int) float64 { } func (inc *TILL) Length() int { + if inc.e1 == nil { + return 0 + } return inc.e1.Length() } diff --git a/pkg/indicator/tma.go b/pkg/indicator/tma.go new file mode 100644 index 000000000..482f3936c --- /dev/null +++ b/pkg/indicator/tma.go @@ -0,0 +1,73 @@ +package indicator + +import ( + "github.com/c9s/bbgo/pkg/types" +) + +// Refer: Triangular Moving Average +// Refer URL: https://ja.wikipedia.org/wiki/移動平均 +//go:generate callbackgen -type TMA +type TMA struct { + types.IntervalWindow + s1 *SMA + s2 *SMA + UpdateCallbacks []func(value float64) +} + +func (inc *TMA) Update(value float64) { + if inc.s1 == nil { + w := (inc.Window + 1) / 2 + inc.s1 = &SMA{IntervalWindow: types.IntervalWindow{inc.Interval, w}} + inc.s2 = &SMA{IntervalWindow: types.IntervalWindow{inc.Interval, w}} + } + + inc.s1.Update(value) + inc.s2.Update(inc.s1.Last()) +} + +func (inc *TMA) Last() float64 { + if inc.s2 == nil { + return 0 + } + return inc.s2.Last() +} + +func (inc *TMA) Index(i int) float64 { + if inc.s2 == nil { + return 0 + } + return inc.s2.Index(i) +} + +func (inc *TMA) Length() int { + if inc.s2 == nil { + return 0 + } + return inc.s2.Length() +} + +var _ types.Series = &TMA{} + +func (inc *TMA) calculateAndUpdate(allKLines []types.KLine) { + if inc.s1 == nil { + for _, k := range allKLines { + inc.Update(k.Close.Float64()) + inc.EmitUpdate(inc.Last()) + } + } else { + inc.Update(allKLines[len(allKLines)-1].Close.Float64()) + inc.EmitUpdate(inc.Last()) + } +} + +func (inc *TMA) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { + if inc.Interval != interval { + return + } + + inc.calculateAndUpdate(window) +} + +func (inc *TMA) Bind(updater KLineWindowUpdater) { + updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) +} diff --git a/pkg/indicator/tma_callbacks.go b/pkg/indicator/tma_callbacks.go new file mode 100644 index 000000000..7c468f55d --- /dev/null +++ b/pkg/indicator/tma_callbacks.go @@ -0,0 +1,15 @@ +// Code generated by "callbackgen -type TMA"; DO NOT EDIT. + +package indicator + +import () + +func (inc *TMA) OnUpdate(cb func(value float64)) { + inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) +} + +func (inc *TMA) EmitUpdate(value float64) { + for _, cb := range inc.UpdateCallbacks { + cb(value) + } +} diff --git a/pkg/indicator/vidya.go b/pkg/indicator/vidya.go index df58745a3..658e89ac1 100644 --- a/pkg/indicator/vidya.go +++ b/pkg/indicator/vidya.go @@ -69,8 +69,13 @@ func (inc *VIDYA) Length() int { var _ types.Series = &VIDYA{} func (inc *VIDYA) calculateAndUpdate(allKLines []types.KLine) { - for _, k := range allKLines { - inc.Update(k.Close.Float64()) + if inc.input.Length() == 0 { + for _, k := range allKLines { + inc.Update(k.Close.Float64()) + inc.EmitUpdate(inc.Last()) + } + } else { + inc.Update(allKLines[len(allKLines)-1].Close.Float64()) inc.EmitUpdate(inc.Last()) } } diff --git a/pkg/indicator/wwma.go b/pkg/indicator/wwma.go index 8f2430232..13fd1b8d1 100644 --- a/pkg/indicator/wwma.go +++ b/pkg/indicator/wwma.go @@ -7,6 +7,7 @@ import ( // Refer: Welles Wilder's Moving Average // Refer URL: http://fxcorporate.com/help/MS/NOTFIFO/i_WMA.html +// TODO: Cannot see any difference between RMA and this const MaxNumOfWWMA = 5_000 const MaxNumOfWWMATruncateSize = 100 diff --git a/pkg/indicator/zlema.go b/pkg/indicator/zlema.go index 474fbe745..4ed97d84a 100644 --- a/pkg/indicator/zlema.go +++ b/pkg/indicator/zlema.go @@ -19,14 +19,23 @@ type ZLEMA struct { } func (inc *ZLEMA) Index(i int) float64 { + if inc.zlema == nil { + return 0 + } return inc.zlema.Index(i) } func (inc *ZLEMA) Last() float64 { + if inc.zlema == nil { + return 0 + } return inc.zlema.Last() } func (inc *ZLEMA) Length() int { + if inc.zlema == nil { + return 0 + } return inc.zlema.Length() } @@ -49,8 +58,13 @@ func (inc *ZLEMA) Update(value float64) { var _ types.Series = &ZLEMA{} func (inc *ZLEMA) calculateAndUpdate(allKLines []types.KLine) { - for _, k := range allKLines { - inc.Update(k.Close.Float64()) + if inc.zlema == nil { + for _, k := range allKLines { + inc.Update(k.Close.Float64()) + inc.EmitUpdate(inc.Last()) + } + } else { + inc.Update(allKLines[len(allKLines)-1].Close.Float64()) inc.EmitUpdate(inc.Last()) } }