diff --git a/pkg/indicator/dema_test.go b/pkg/indicator/dema_test.go new file mode 100644 index 0000000000..f98f5c31d1 --- /dev/null +++ b/pkg/indicator/dema_test.go @@ -0,0 +1,55 @@ +package indicator + +import ( + "encoding/json" + "testing" + + "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/types" + "github.com/stretchr/testify/assert" +) + +/* +python: + +import pandas as pd +s = pd.Series(([0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9]) +ma1 = s.ewm(span=16).mean() +ma2 = ma1.ewm(span=16).mean() +result = (2 * ma1 - ma2) +print(result) +*/ +func Test_DEMA(t *testing.T) { + var Delta = 4e-2 + var randomPrices = []byte(`[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]`) + var input []fixedpoint.Value + if err := json.Unmarshal(randomPrices, &input); err != nil { + panic(err) + } + tests := []struct { + name string + kLines []types.KLine + want float64 + next float64 + all int + }{ + { + name: "random_case", + kLines: buildKLines(input), + want: 6.420838, + next: 5.609367, + all: 50, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dema := DEMA{IntervalWindow: types.IntervalWindow{Window: 16}} + dema.calculateAndUpdate(tt.kLines) + last := dema.Last() + assert.InDelta(t, tt.want, last, Delta) + assert.InDelta(t, tt.next, dema.Index(1), Delta) + assert.Equal(t, tt.all, dema.Length()) + }) + } +} diff --git a/pkg/indicator/hull.go b/pkg/indicator/hull.go index 2860740ef6..a5ab47db3e 100644 --- a/pkg/indicator/hull.go +++ b/pkg/indicator/hull.go @@ -19,7 +19,7 @@ type HULL struct { } func (inc *HULL) Update(value float64) { - if inc.result.Length() == 0 { + if inc.result == nil { 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)))}} @@ -46,7 +46,7 @@ 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 { + if inc.ma1 == nil || inc.ma1.Length() == 0 { doable = true } for _, k := range allKLines { diff --git a/pkg/indicator/hull_test.go b/pkg/indicator/hull_test.go new file mode 100644 index 0000000000..a6a5cb99bd --- /dev/null +++ b/pkg/indicator/hull_test.go @@ -0,0 +1,55 @@ +package indicator + +import ( + "encoding/json" + "testing" + + "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/types" + "github.com/stretchr/testify/assert" +) + +/* +python: + +import pandas as pd +s = pd.Series(([0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9]) +ma1 = s.ewm(span=8).mean() +ma2 = s.ewm(span=16).mean() +result = (2 * ma1 - ma2).ewm(span=4).mean() +print(result) +*/ +func Test_HULL(t *testing.T) { + var Delta = 1.5e-2 + var randomPrices = []byte(`[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]`) + var input []fixedpoint.Value + if err := json.Unmarshal(randomPrices, &input); err != nil { + panic(err) + } + tests := []struct { + name string + kLines []types.KLine + want float64 + next float64 + all int + }{ + { + name: "random_case", + kLines: buildKLines(input), + want: 6.002935, + next: 5.167056, + all: 50, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + hull := HULL{IntervalWindow: types.IntervalWindow{Window: 16}} + hull.calculateAndUpdate(tt.kLines) + last := hull.Last() + assert.InDelta(t, tt.want, last, Delta) + assert.InDelta(t, tt.next, hull.Index(1), Delta) + assert.Equal(t, tt.all, hull.Length()) + }) + } +} diff --git a/pkg/indicator/tema_test.go b/pkg/indicator/tema_test.go new file mode 100644 index 0000000000..d36c6a5302 --- /dev/null +++ b/pkg/indicator/tema_test.go @@ -0,0 +1,56 @@ +package indicator + +import ( + "encoding/json" + "testing" + + "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/types" + "github.com/stretchr/testify/assert" +) + +/* +python: + +import pandas as pd +s = pd.Series(([0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9]) +ma1 = s.ewm(span=16).mean() +ma2 = ma1.ewm(span=16).mean() +ma3 = ma2.ewm(span=16).mean() +result = (3 * ma1 - 3 * ma2 + ma3) +print(result) +*/ +func Test_TEMA(t *testing.T) { + var Delta = 4.3e-2 + var randomPrices = []byte(`[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]`) + var input []fixedpoint.Value + if err := json.Unmarshal(randomPrices, &input); err != nil { + panic(err) + } + tests := []struct { + name string + kLines []types.KLine + want float64 + next float64 + all int + }{ + { + name: "random_case", + kLines: buildKLines(input), + want: 7.163145, + next: 6.106229, + all: 50, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tema := TEMA{IntervalWindow: types.IntervalWindow{Window: 16}} + tema.calculateAndUpdate(tt.kLines) + last := tema.Last() + assert.InDelta(t, tt.want, last, Delta) + assert.InDelta(t, tt.next, tema.Index(1), Delta) + assert.Equal(t, tt.all, tema.Length()) + }) + } +} diff --git a/pkg/indicator/till.go b/pkg/indicator/till.go index bc131e639a..3a9e04fc52 100644 --- a/pkg/indicator/till.go +++ b/pkg/indicator/till.go @@ -41,7 +41,7 @@ func (inc *TILL) Update(value float64) { 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.c4 = 1. + 3.*inc.VolumeFactor + cube + 3.*square } inc.e1.Update(value) @@ -82,7 +82,7 @@ var _ types.Series = &TILL{} func (inc *TILL) calculateAndUpdate(allKLines []types.KLine) { doable := false - if inc.e1.Length() == 0 { + if inc.e1 == nil { doable = true } for _, k := range allKLines { diff --git a/pkg/indicator/till_test.go b/pkg/indicator/till_test.go new file mode 100644 index 0000000000..1c2c96e697 --- /dev/null +++ b/pkg/indicator/till_test.go @@ -0,0 +1,65 @@ +package indicator + +import ( + "encoding/json" + "testing" + + "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/types" + "github.com/stretchr/testify/assert" +) + +/* +python: + +import pandas as pd +s = pd.Series(([0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9]) +ma1 = s.ewm(span=16).mean() +ma2 = ma1.ewm(span=16).mean() +ma3 = ma2.ewm(span=16).mean() +ma4 = ma3.ewm(span=16).mean() +ma5 = ma4.ewm(span=16).mean() +ma6 = ma5.ewm(span=16).mean() +square = 0.7 * 0.7 +cube = 0.7 ** 3 +c1 = -cube +c2 = 3 * square + 3 * cube +c3 = -6 * square - 3 * 0.7 - 3 * cube +c4 = 1 + 3 * 0.7 + cube + 3 * square +result = (c1 * ma6 + c2 * ma5 + c3 * ma4 + c4 * ma3) +print(result) +*/ +func Test_TILL(t *testing.T) { + var Delta = 0.18 + var randomPrices = []byte(`[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]`) + var input []fixedpoint.Value + if err := json.Unmarshal(randomPrices, &input); err != nil { + panic(err) + } + tests := []struct { + name string + kLines []types.KLine + want float64 + next float64 + all int + }{ + { + name: "random_case", + kLines: buildKLines(input), + want: 4.528608, + next: 4.457134, + all: 50, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + till := TILL{IntervalWindow: types.IntervalWindow{Window: 16}} + till.calculateAndUpdate(tt.kLines) + last := till.Last() + assert.InDelta(t, tt.want, last, Delta) + assert.InDelta(t, tt.next, till.Index(1), Delta) + assert.Equal(t, tt.all, till.Length()) + }) + } +} diff --git a/pkg/indicator/vidya.go b/pkg/indicator/vidya.go index f1b119ecc2..df58745a35 100644 --- a/pkg/indicator/vidya.go +++ b/pkg/indicator/vidya.go @@ -27,7 +27,7 @@ func (inc *VIDYA) Update(value float64) { if len(inc.input) > MaxNumOfEWMA { inc.input = inc.input[MaxNumOfEWMATruncateSize-1:] } - upsum := 0. + /*upsum := 0. downsum := 0. for i := 0; i < inc.Window; i++ { if len(inc.input) <= i+1 { @@ -44,7 +44,9 @@ func (inc *VIDYA) Update(value float64) { if upsum == 0 && downsum == 0 { return } - CMO := math.Abs((upsum - downsum) / (upsum + downsum)) + CMO := math.Abs((upsum - downsum) / (upsum + downsum))*/ + change := types.Change(&inc.input) + CMO := math.Abs(types.Sum(change, inc.Window) / types.Sum(types.Abs(change), inc.Window)) alpha := 2. / float64(inc.Window+1) inc.Values.Push(value*alpha*CMO + inc.Values.Last()*(1.-alpha*CMO)) if inc.Values.Length() > MaxNumOfEWMA { diff --git a/pkg/indicator/vidya_test.go b/pkg/indicator/vidya_test.go new file mode 100644 index 0000000000..8ac1df255d --- /dev/null +++ b/pkg/indicator/vidya_test.go @@ -0,0 +1,19 @@ +package indicator + +import ( + "testing" + + "github.com/c9s/bbgo/pkg/types" + "github.com/stretchr/testify/assert" +) + +func Test_VIDYA(t *testing.T) { + vidya := &VIDYA{IntervalWindow: types.IntervalWindow{Window: 16}} + vidya.Update(1) + assert.Equal(t, vidya.Last(), 1.) + vidya.Update(2) + newV := 2./17.*2. + 1.*(1.-2./17.) + assert.Equal(t, vidya.Last(), newV) + vidya.Update(1) + assert.Equal(t, vidya.Last(), vidya.Index(1)) +} diff --git a/pkg/indicator/zlema.go b/pkg/indicator/zlema.go index 1a45a2d456..474fbe745d 100644 --- a/pkg/indicator/zlema.go +++ b/pkg/indicator/zlema.go @@ -11,7 +11,7 @@ import ( type ZLEMA struct { types.IntervalWindow - data *EWMA + data types.Float64Slice zlema *EWMA lag int @@ -32,13 +32,17 @@ func (inc *ZLEMA) Length() int { 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.lag = int((float64(inc.Window)-1.)/2. + 0.5) } - inc.data.Update(value) - data := inc.data.Last() - emaData := 2*data - inc.data.Index(inc.lag) + inc.data.Push(value) + if len(inc.data) > MaxNumOfEWMA { + inc.data = inc.data[MaxNumOfEWMATruncateSize-1:] + } + if inc.lag >= inc.data.Length() { + return + } + emaData := 2.*value - inc.data[len(inc.data)-1-inc.lag] inc.zlema.Update(emaData) } diff --git a/pkg/indicator/zlema_test.go b/pkg/indicator/zlema_test.go new file mode 100644 index 0000000000..a209d3b656 --- /dev/null +++ b/pkg/indicator/zlema_test.go @@ -0,0 +1,55 @@ +package indicator + +import ( + "encoding/json" + "testing" + + "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/types" + "github.com/stretchr/testify/assert" +) + +/* +python: + +import pandas as pd +s = pd.Series(([0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9]) +lag = int((16-1)/2) +emadata = s + (s - s.shift(lag)) +result = emadata.ewm(span=16).mean() +print(result) +*/ +func Test_ZLEMA(t *testing.T) { + var Delta = 6.5e-2 + var randomPrices = []byte(`[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]`) + var input []fixedpoint.Value + if err := json.Unmarshal(randomPrices, &input); err != nil { + panic(err) + } + tests := []struct { + name string + kLines []types.KLine + want float64 + next float64 + all int + }{ + { + name: "random_case", + kLines: buildKLines(input), + want: 6.622881, + next: 5.231044, + all: 42, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + zlema := ZLEMA{IntervalWindow: types.IntervalWindow{Window: 16}} + zlema.calculateAndUpdate(tt.kLines) + last := zlema.Last() + assert.InDelta(t, tt.want, last, Delta) + assert.InDelta(t, tt.next, zlema.Index(1), Delta) + assert.Equal(t, tt.all, zlema.Length()) + }) + } +}