From 5dc69a61757f1947525cd0e550871baf3d4d5dea Mon Sep 17 00:00:00 2001 From: zenix Date: Thu, 21 Apr 2022 19:28:11 +0900 Subject: [PATCH] fix: fix change, feature: implement vidya and till --- pkg/indicator/hull.go | 24 +++---- pkg/indicator/till.go | 108 +++++++++++++++++++++++++++++++ pkg/indicator/till_callbacks.go | 15 +++++ pkg/indicator/tsf.go | 1 - pkg/indicator/vidya.go | 85 ++++++++++++++++++++++++ pkg/indicator/vidya_callbacks.go | 15 +++++ pkg/indicator/wwma.go | 2 +- pkg/types/indicator.go | 2 +- 8 files changed, 237 insertions(+), 15 deletions(-) create mode 100644 pkg/indicator/till_callbacks.go delete mode 100644 pkg/indicator/tsf.go create mode 100644 pkg/indicator/vidya_callbacks.go diff --git a/pkg/indicator/hull.go b/pkg/indicator/hull.go index bb7e3f2c0..2860740ef 100644 --- a/pkg/indicator/hull.go +++ b/pkg/indicator/hull.go @@ -11,22 +11,22 @@ import ( //go:generate callbackgen -type HULL type HULL struct { types.IntervalWindow - ma1 *EWMA - ma2 *EWMA + ma1 *EWMA + ma2 *EWMA result *EWMA - UpdateCallbacks []func(value float64) + 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.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()) + inc.result.Update(2*inc.ma1.Last() - inc.ma2.Last()) } func (inc *HULL) Last() float64 { @@ -49,7 +49,7 @@ func (inc *HULL) calculateAndUpdate(allKLines []types.KLine) { if inc.ma1.Length() == 0 { doable = true } - for _, k := range allKLines { + for _, k := range allKLines { if !doable && k.StartTime.After(inc.ma1.LastOpenTime) { doable = true } @@ -57,17 +57,17 @@ func (inc *HULL) calculateAndUpdate(allKLines []types.KLine) { inc.Update(k.Close.Float64()) inc.EmitUpdate(inc.Last()) } - } + } } func (inc *HULL) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { - if inc.Interval != interval { - return - } + if inc.Interval != interval { + return + } - inc.calculateAndUpdate(window) + inc.calculateAndUpdate(window) } func (inc *HULL) Bind(updater KLineWindowUpdater) { - updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) + updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) } diff --git a/pkg/indicator/till.go b/pkg/indicator/till.go index 722d45a36..bc131e639 100644 --- a/pkg/indicator/till.go +++ b/pkg/indicator/till.go @@ -1 +1,109 @@ package indicator + +import ( + "github.com/c9s/bbgo/pkg/types" +) + +const defaultVolumeFactor = 0.7 + +// Refer: Tillson T3 Moving Average +// Refer URL: https://tradingpedia.com/forex-trading-indicator/t3-moving-average-indicator/ +//go:generate callbackgen -type TILL +type TILL struct { + types.IntervalWindow + VolumeFactor float64 + e1 *EWMA + e2 *EWMA + e3 *EWMA + e4 *EWMA + e5 *EWMA + e6 *EWMA + c1 float64 + c2 float64 + c3 float64 + c4 float64 + UpdateCallbacks []func(value float64) +} + +func (inc *TILL) Update(value float64) { + if inc.e1 == nil || inc.e1.Length() == 0 { + if inc.VolumeFactor == 0 { + inc.VolumeFactor = defaultVolumeFactor + } + inc.e1 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}} + inc.e2 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}} + inc.e3 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}} + inc.e4 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}} + inc.e5 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}} + inc.e6 = &EWMA{IntervalWindow: types.IntervalWindow{inc.Interval, inc.Window}} + square := inc.VolumeFactor * inc.VolumeFactor + cube := inc.VolumeFactor * square + inc.c1 = -cube + inc.c2 = 3.*square + 3.*cube + inc.c3 = -6.*square - 3*inc.VolumeFactor - 3*cube + inc.c4 = 1 + 3*inc.VolumeFactor + cube + 3*square + } + + inc.e1.Update(value) + inc.e2.Update(inc.e1.Last()) + inc.e3.Update(inc.e2.Last()) + inc.e4.Update(inc.e3.Last()) + inc.e5.Update(inc.e4.Last()) + inc.e6.Update(inc.e5.Last()) +} + +func (inc *TILL) Last() float64 { + if inc.e1.Length() == 0 { + return 0 + } + e3 := inc.e3.Last() + e4 := inc.e4.Last() + e5 := inc.e5.Last() + e6 := inc.e6.Last() + return inc.c1*e6 + inc.c2*e5 + inc.c3*e4 + inc.c4*e3 +} + +func (inc *TILL) Index(i int) float64 { + if inc.e1.Length() <= i { + return 0 + } + e3 := inc.e3.Index(i) + e4 := inc.e4.Index(i) + e5 := inc.e5.Index(i) + e6 := inc.e6.Index(i) + return inc.c1*e6 + inc.c2*e5 + inc.c3*e4 + inc.c4*e3 +} + +func (inc *TILL) Length() int { + return inc.e1.Length() +} + +var _ types.Series = &TILL{} + +func (inc *TILL) calculateAndUpdate(allKLines []types.KLine) { + doable := false + if inc.e1.Length() == 0 { + doable = true + } + for _, k := range allKLines { + if !doable && k.StartTime.After(inc.e1.LastOpenTime) { + doable = true + } + if doable { + inc.Update(k.Close.Float64()) + inc.EmitUpdate(inc.Last()) + } + } +} + +func (inc *TILL) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { + if inc.Interval != interval { + return + } + + inc.calculateAndUpdate(window) +} + +func (inc *TILL) Bind(updater KLineWindowUpdater) { + updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) +} diff --git a/pkg/indicator/till_callbacks.go b/pkg/indicator/till_callbacks.go new file mode 100644 index 000000000..53d89cb8d --- /dev/null +++ b/pkg/indicator/till_callbacks.go @@ -0,0 +1,15 @@ +// Code generated by "callbackgen -type TILL"; DO NOT EDIT. + +package indicator + +import () + +func (inc *TILL) OnUpdate(cb func(value float64)) { + inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) +} + +func (inc *TILL) EmitUpdate(value float64) { + for _, cb := range inc.UpdateCallbacks { + cb(value) + } +} diff --git a/pkg/indicator/tsf.go b/pkg/indicator/tsf.go deleted file mode 100644 index 722d45a36..000000000 --- a/pkg/indicator/tsf.go +++ /dev/null @@ -1 +0,0 @@ -package indicator diff --git a/pkg/indicator/vidya.go b/pkg/indicator/vidya.go index 722d45a36..f1b119ecc 100644 --- a/pkg/indicator/vidya.go +++ b/pkg/indicator/vidya.go @@ -1 +1,86 @@ package indicator + +import ( + "math" + + "github.com/c9s/bbgo/pkg/types" +) + +// Refer: Variable Index Dynamic Average +// Refer URL: https://metatrader5.com/en/terminal/help/indicators/trend_indicators/vida +//go:generate callbackgen -type VIDYA +type VIDYA struct { + types.IntervalWindow + Values types.Float64Slice + input types.Float64Slice + + UpdateCallbacks []func(value float64) +} + +func (inc *VIDYA) Update(value float64) { + if inc.Values.Length() == 0 { + inc.Values.Push(value) + inc.input.Push(value) + return + } + inc.input.Push(value) + if len(inc.input) > MaxNumOfEWMA { + inc.input = inc.input[MaxNumOfEWMATruncateSize-1:] + } + upsum := 0. + downsum := 0. + for i := 0; i < inc.Window; i++ { + if len(inc.input) <= i+1 { + break + } + diff := inc.input.Index(i) - inc.input.Index(i+1) + if diff > 0 { + upsum += diff + } else { + downsum += -diff + } + + } + if upsum == 0 && downsum == 0 { + return + } + CMO := math.Abs((upsum - downsum) / (upsum + downsum)) + alpha := 2. / float64(inc.Window+1) + inc.Values.Push(value*alpha*CMO + inc.Values.Last()*(1.-alpha*CMO)) + if inc.Values.Length() > MaxNumOfEWMA { + inc.Values = inc.Values[MaxNumOfEWMATruncateSize-1:] + } +} + +func (inc *VIDYA) Last() float64 { + return inc.Values.Last() +} + +func (inc *VIDYA) Index(i int) float64 { + return inc.Values.Index(i) +} + +func (inc *VIDYA) Length() int { + return inc.Values.Length() +} + +var _ types.Series = &VIDYA{} + +func (inc *VIDYA) calculateAndUpdate(allKLines []types.KLine) { + for _, k := range allKLines { + inc.Update(k.Close.Float64()) + inc.EmitUpdate(inc.Last()) + } +} + +func (inc *VIDYA) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { + if inc.Interval != interval { + return + } + + inc.calculateAndUpdate(window) +} + +func (inc *VIDYA) Bind(updater KLineWindowUpdater) { + updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) +} diff --git a/pkg/indicator/vidya_callbacks.go b/pkg/indicator/vidya_callbacks.go new file mode 100644 index 000000000..b78e797c4 --- /dev/null +++ b/pkg/indicator/vidya_callbacks.go @@ -0,0 +1,15 @@ +// Code generated by "callbackgen -type VIDYA"; DO NOT EDIT. + +package indicator + +import () + +func (inc *VIDYA) OnUpdate(cb func(value float64)) { + inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) +} + +func (inc *VIDYA) EmitUpdate(value float64) { + for _, cb := range inc.UpdateCallbacks { + cb(value) + } +} diff --git a/pkg/indicator/wwma.go b/pkg/indicator/wwma.go index 022da3ed6..8f2430232 100644 --- a/pkg/indicator/wwma.go +++ b/pkg/indicator/wwma.go @@ -1,8 +1,8 @@ package indicator import ( - "time" "github.com/c9s/bbgo/pkg/types" + "time" ) // Refer: Welles Wilder's Moving Average diff --git a/pkg/types/indicator.go b/pkg/types/indicator.go index 72d34e001..1873894a9 100644 --- a/pkg/types/indicator.go +++ b/pkg/types/indicator.go @@ -502,7 +502,7 @@ func (c *ChangeResult) Length() int { // offset: if not given, offset is 1. func Change(a Series, offset ...int) Series { o := 1 - if len(offset) == 0 { + if len(offset) > 0 { o = offset[0] }