From 9dc17f9855afbb2eb82ad5938a3abb8de44a0bfc Mon Sep 17 00:00:00 2001 From: sdcoffey Date: Tue, 23 Feb 2021 08:19:19 -0800 Subject: [PATCH 01/13] upgrade big --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 609828e..a606d88 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.12 require ( github.com/davecgh/go-spew v1.1.0 github.com/pmezard/go-difflib v1.0.0 - github.com/sdcoffey/big v0.4.1 + github.com/sdcoffey/big v0.6.0 github.com/stretchr/testify v1.2.1 golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect honnef.co/go/tools v0.0.1-2020.1.6 // indirect diff --git a/go.sum b/go.sum index 9515a14..c4b4966 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,8 @@ github.com/sdcoffey/big v0.0.0-20180413224939-438f3d83db4c h1:avDKoFuBXMQAcu0sxd github.com/sdcoffey/big v0.0.0-20180413224939-438f3d83db4c/go.mod h1:WzOYJJhNOp7m1u1MrNQaYSHmgqHGkZThC+VgDqdFl0I= github.com/sdcoffey/big v0.4.1 h1:DyK4tHRbbAfdIZV/VbxrEz/ZpcVv5GWNNw+G6AOhfpM= github.com/sdcoffey/big v0.4.1/go.mod h1:spXh3ZRHdlXkg4SzauLC8ahWmHpxWL5/6drgufL73es= +github.com/sdcoffey/big v0.6.0 h1:0C+nB/91mHQGAxlgvDy/hFO3MMv3dTnCuNA3qfhEYYQ= +github.com/sdcoffey/big v0.6.0/go.mod h1:spXh3ZRHdlXkg4SzauLC8ahWmHpxWL5/6drgufL73es= github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.1 h1:52QO5WkIUcHGIR7EnGagH88x1bUzqGXTC5/1bDTUQ7U= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= From f766c9e1a027f0557dfd0a7854cbebe932e3b9cf Mon Sep 17 00:00:00 2001 From: sdcoffey Date: Tue, 23 Feb 2021 09:41:57 -0800 Subject: [PATCH 02/13] add true range indicator --- indicator_true_range.go | 26 ++++++++++++++++++++++++++ indicator_true_range_test.go | 23 +++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 indicator_true_range.go create mode 100644 indicator_true_range_test.go diff --git a/indicator_true_range.go b/indicator_true_range.go new file mode 100644 index 0000000..67cb91e --- /dev/null +++ b/indicator_true_range.go @@ -0,0 +1,26 @@ +package techan + +import "github.com/sdcoffey/big" + +type trueRangeIndicator struct { + series *TimeSeries +} + +// NewTrueRangeIndicator returns a base indicator +// which calculates the true rangat the current point in time for a series +// https://www.investopedia.com/terms/a/atr.asp +func NewTrueRangeIndicator(series *TimeSeries) Indicator { + return trueRangeIndicator{ + series: series, + } +} + +func (tri trueRangeIndicator) Calculate(index int) big.Decimal { + candle := tri.series.Candles[index] + previousClose := tri.series.Candles[index-1].ClosePrice + + trueHigh := big.MaxSlice(candle.MaxPrice, previousClose) + trueLow := big.MinSlice(candle.MinPrice, previousClose) + + return trueHigh.Sub(trueLow) +} diff --git a/indicator_true_range_test.go b/indicator_true_range_test.go new file mode 100644 index 0000000..202a247 --- /dev/null +++ b/indicator_true_range_test.go @@ -0,0 +1,23 @@ +package techan + +import ( + "testing" +) + +func TestTrueRangeIndicator(t *testing.T) { + ts := mockTimeSeriesOCHL( + []float64{10, 15, 20, 10}, + []float64{11, 16, 21, 11}, + []float64{12, 17, 22, 12}, + []float64{13, 18, 23, 13}, + []float64{14, 19, 24, 14}, + []float64{15, 20, 25, 15}, + ) + + trueRangeIndicator := NewTrueRangeIndicator(ts) + + decimalEquals(t, 10, trueRangeIndicator.Calculate(4)) + decimalEquals(t, 10, trueRangeIndicator.Calculate(3)) + decimalEquals(t, 10, trueRangeIndicator.Calculate(2)) + decimalEquals(t, 10, trueRangeIndicator.Calculate(1)) +} From 63fa19d3f5216d28946eb2b1aa60133b9bf1049d Mon Sep 17 00:00:00 2001 From: sdcoffey Date: Fri, 26 Feb 2021 18:03:39 -0800 Subject: [PATCH 03/13] wip --- indicator_average_true_range.go | 32 ++++++++++++++++++++ indicator_average_true_range_test.go | 24 +++++++++++++++ indicator_keltner_channel.go | 37 +++++++++++++++++++++++ indicator_keltner_channel_test.go | 26 ++++++++++++++++ indicator_moving_average.go | 6 ++-- indicator_moving_average_test.go | 42 +++++++++++++++----------- indicator_relative_vigor_index_test.go | 24 +++++++-------- indicator_true_range.go | 4 +++ testutils.go | 10 +++--- 9 files changed, 167 insertions(+), 38 deletions(-) create mode 100644 indicator_average_true_range.go create mode 100644 indicator_average_true_range_test.go create mode 100644 indicator_keltner_channel.go create mode 100644 indicator_keltner_channel_test.go diff --git a/indicator_average_true_range.go b/indicator_average_true_range.go new file mode 100644 index 0000000..b379a6a --- /dev/null +++ b/indicator_average_true_range.go @@ -0,0 +1,32 @@ +package techan + +import "github.com/sdcoffey/big" + +type averageTrueRangeIndicator struct { + series *TimeSeries + window int +} + +// NewAverageTrueRangeIndicator returns a base indicator that caculates the average true range of the +// underlying over a window +// https://www.investopedia.com/terms/a/atr.asp +func NewAverageTrueRangeIndicator(series *TimeSeries, window int) Indicator { + return averageTrueRangeIndicator{ + series: series, + window: window, + } +} + +func (atr averageTrueRangeIndicator) Calculate(index int) big.Decimal { + if index < atr.window { + return big.ZERO + } + + sum := big.ZERO + + for i := index - atr.window + 1; i <= index; i++ { + sum = sum.Add(NewTrueRangeIndicator(atr.series).Calculate(i)) + } + + return sum.Div(big.NewFromInt(atr.window)) +} diff --git a/indicator_average_true_range_test.go b/indicator_average_true_range_test.go new file mode 100644 index 0000000..23a2722 --- /dev/null +++ b/indicator_average_true_range_test.go @@ -0,0 +1,24 @@ +package techan + +import "testing" + +func TestAverageTrueRangeIndicator(t *testing.T) { + ts := mockTimeSeriesOCHL( + []float64{10, 15, 20, 10}, + []float64{11, 16, 21, 11}, + []float64{12, 17, 22, 12}, + []float64{13, 18, 23, 13}, + []float64{14, 19, 24, 14}, + []float64{15, 20, 25, 15}, + []float64{16, 20, 26, 16}, + ) + + trueRangeIndicator := NewAverageTrueRangeIndicator(ts, 3) + + decimalEquals(t, 10, trueRangeIndicator.Calculate(5)) + decimalEquals(t, 10, trueRangeIndicator.Calculate(4)) + decimalEquals(t, 10, trueRangeIndicator.Calculate(3)) + decimalEquals(t, 0, trueRangeIndicator.Calculate(2)) + decimalEquals(t, 0, trueRangeIndicator.Calculate(1)) + decimalEquals(t, 0, trueRangeIndicator.Calculate(0)) +} diff --git a/indicator_keltner_channel.go b/indicator_keltner_channel.go new file mode 100644 index 0000000..137de54 --- /dev/null +++ b/indicator_keltner_channel.go @@ -0,0 +1,37 @@ +package techan + +import "github.com/sdcoffey/big" + +type keltnerChannelIndicator struct { + ema Indicator + atr Indicator + mul big.Decimal + window int +} + +func NewKeltnerChannelUpperIndicator(series *TimeSeries, window int) Indicator { + return keltnerChannelIndicator{ + atr: NewAverageTrueRangeIndicator(series, window), + ema: NewEMAIndicator(NewClosePriceIndicator(series), window), + mul: big.ONE, + window: window, + } +} + +func NewKeltnerChannelLowerIndicator(series *TimeSeries, window int) Indicator { + return keltnerChannelIndicator{ + atr: NewAverageTrueRangeIndicator(series, window), + ema: NewEMAIndicator(NewClosePriceIndicator(series), window), + mul: big.ONE, + } +} + +func (kci keltnerChannelIndicator) Calculate(index int) big.Decimal { + if index < kci.window { + return big.ZERO + } + + coefficient := big.NewFromInt(2).Mul(kci.mul) + + return kci.ema.Calculate(index).Add(kci.atr.Calculate(index).Mul(coefficient)) +} diff --git a/indicator_keltner_channel_test.go b/indicator_keltner_channel_test.go new file mode 100644 index 0000000..f3e1285 --- /dev/null +++ b/indicator_keltner_channel_test.go @@ -0,0 +1,26 @@ +package techan + +import "testing" + +func TestKeltnerChannel(t *testing.T) { + t.Run("Upper", func(t *testing.T) { + ts := mockTimeSeriesOCHL( + []float64{10, 15, 20, 10}, + []float64{11, 16, 21, 11}, + []float64{12, 17, 22, 12}, + []float64{13, 18, 23, 13}, + []float64{14, 19, 24, 14}, + []float64{15, 20, 25, 15}, + []float64{16, 20, 26, 16}, + ) + + upper := NewKeltnerChannelUpperIndicator(ts, 3) + + decimalEquals(t, 40, upper.Calculate(5)) + decimalEquals(t, 39, upper.Calculate(4)) + decimalEquals(t, 38, upper.Calculate(3)) + decimalEquals(t, 37, upper.Calculate(2)) + decimalEquals(t, 0, upper.Calculate(1)) + decimalEquals(t, 0, upper.Calculate(0)) + }) +} diff --git a/indicator_moving_average.go b/indicator_moving_average.go index 017390a..8d0f6c5 100644 --- a/indicator_moving_average.go +++ b/indicator_moving_average.go @@ -37,14 +37,14 @@ func NewEMAIndicator(indicator Indicator, window int) Indicator { return &emaIndicator{ Indicator: indicator, window: window, - alpha: big.NewDecimal(2.0 / float64(window+1)), + alpha: big.NewDecimal(2.0).Div(big.NewFromInt(window +1)), resultCache: make([]*big.Decimal, 10000), } } func (ema *emaIndicator) Calculate(index int) big.Decimal { - if index == 0 { - return ema.Indicator.Calculate(index) + if index < ema.window { + return big.ZERO } else if len(ema.resultCache) > index && ema.resultCache[index] != nil { return *ema.resultCache[index] } diff --git a/indicator_moving_average_test.go b/indicator_moving_average_test.go index 5a5a6b3..06619fd 100644 --- a/indicator_moving_average_test.go +++ b/indicator_moving_average_test.go @@ -1,30 +1,23 @@ package techan import ( + "math" "testing" + "math/big" "github.com/stretchr/testify/assert" ) func TestSimpleMovingAverage(t *testing.T) { - ts := mockTimeSeriesFl(1, 2, 3, 4, 3, 4, 5, 4, 3, 3, 4, 3, 2) - - sma := NewSimpleMovingAverage(NewClosePriceIndicator(ts), 3) - - decimalEquals(t, 1, sma.Calculate(0)) - decimalEquals(t, 1.5, sma.Calculate(1)) - - decimalEquals(t, 2, sma.Calculate(2)) - decimalEquals(t, 3, sma.Calculate(3)) - decimalEquals(t, 10.0/3.0, sma.Calculate(4)) - decimalEquals(t, 11.0/3.0, sma.Calculate(5)) - decimalEquals(t, 4, sma.Calculate(6)) - decimalEquals(t, 13.0/3.0, sma.Calculate(7)) - decimalEquals(t, 4, sma.Calculate(8)) - decimalEquals(t, 10.0/3.0, sma.Calculate(9)) - decimalEquals(t, 10.0/3.0, sma.Calculate(10)) - decimalEquals(t, 10.0/3.0, sma.Calculate(11)) - decimalEquals(t, 3, sma.Calculate(12)) + //ts := mockTimeSeriesFl(15, 16, 17, 18, 17, 18, 16) + // + //sma := NewSimpleMovingAverage(NewClosePriceIndicator(ts), 3) + + nan := math.NaN() + println(nan) + + bnan := big.NewFloat(nan) + print(bnan.String()) } func TestExponentialMovingAverage(t *testing.T) { @@ -62,6 +55,19 @@ func TestExponentialMovingAverage(t *testing.T) { assert.EqualValues(t, 10001, len(ema.(*emaIndicator).resultCache)) }) + + t.Run("Very simple", func(t *testing.T) { + ts := mockTimeSeriesFl(15, 16, 17, 18, 19, 20, 21) + + ema := NewEMAIndicator(NewClosePriceIndicator(ts), 3) + + decimalEquals(t, 16, ema.Calculate(5)) + decimalEquals(t, 15, ema.Calculate(4)) + decimalEquals(t, 14, ema.Calculate(3)) + decimalEquals(t, 13, ema.Calculate(2)) + decimalEquals(t, 0, ema.Calculate(1)) + decimalEquals(t, 0, ema.Calculate(0)) + }) } func TestNewMMAIndicator(t *testing.T) { diff --git a/indicator_relative_vigor_index_test.go b/indicator_relative_vigor_index_test.go index 8549149..1b900db 100644 --- a/indicator_relative_vigor_index_test.go +++ b/indicator_relative_vigor_index_test.go @@ -8,10 +8,10 @@ import ( func TestRelativeVigorIndexIndicator_Calculate(t *testing.T) { series := mockTimeSeriesOCHL( - []string{"10", "12", "12", "8"}, - []string{"11", "14", "14", "9"}, - []string{"8", "19", "20", "8"}, - []string{"9", "10", "11", "8"}, + []float64{10, 12, 12, 8}, + []float64{11, 14, 14, 9}, + []float64{8, 19, 20, 8}, + []float64{9, 10, 11, 8}, ) rvii := NewRelativeVigorIndexIndicator(series) @@ -29,14 +29,14 @@ func TestRelativeVigorIndexIndicator_Calculate(t *testing.T) { func TestRelativeVigorIndexSignalLine_Calculate(t *testing.T) { series := mockTimeSeriesOCHL( - []string{"10", "12", "12", "8"}, - []string{"11", "14", "14", "9"}, - []string{"8", "19", "20", "8"}, - []string{"9", "10", "11", "8"}, - []string{"11", "14", "14", "9"}, - []string{"9", "10", "11", "8"}, - []string{"10", "12", "12", "8"}, - []string{"9", "10", "11", "8"}, + []float64{10, 12, 12, 8}, + []float64{11, 14, 14, 9}, + []float64{8, 19, 20, 8}, + []float64{9, 10, 11, 8}, + []float64{11, 14, 14, 9}, + []float64{9, 10, 11, 8}, + []float64{10, 12, 12, 8}, + []float64{9, 10, 11, 8}, ) signalLine := NewRelativeVigorSignalLine(series) diff --git a/indicator_true_range.go b/indicator_true_range.go index 67cb91e..aa25600 100644 --- a/indicator_true_range.go +++ b/indicator_true_range.go @@ -16,6 +16,10 @@ func NewTrueRangeIndicator(series *TimeSeries) Indicator { } func (tri trueRangeIndicator) Calculate(index int) big.Decimal { + if index-1 < 0 { + return big.ZERO + } + candle := tri.series.Candles[index] previousClose := tri.series.Candles[index-1].ClosePrice diff --git a/testutils.go b/testutils.go index 4927db3..ea540f3 100644 --- a/testutils.go +++ b/testutils.go @@ -34,14 +34,14 @@ func randomTimeSeries(size int) *TimeSeries { return mockTimeSeries(vals...) } -func mockTimeSeriesOCHL(values ...[]string) *TimeSeries { +func mockTimeSeriesOCHL(values ...[]float64) *TimeSeries { ts := NewTimeSeries() for i, ochl := range values { candle := NewCandle(NewTimePeriod(time.Unix(int64(i), 0), time.Second)) - candle.OpenPrice = big.NewFromString(ochl[0]) - candle.ClosePrice = big.NewFromString(ochl[1]) - candle.MaxPrice = big.NewFromString(ochl[2]) - candle.MinPrice = big.NewFromString(ochl[3]) + candle.OpenPrice = big.NewDecimal(ochl[0]) + candle.ClosePrice = big.NewDecimal(ochl[1]) + candle.MaxPrice = big.NewDecimal(ochl[2]) + candle.MinPrice = big.NewDecimal(ochl[3]) candle.Volume = big.NewDecimal(float64(i)) ts.AddCandle(candle) From 24c7e5046321f5aeec01a9a94e81ffbae7768895 Mon Sep 17 00:00:00 2001 From: sdcoffey Date: Fri, 26 Feb 2021 18:41:55 -0800 Subject: [PATCH 04/13] tidy --- go.mod | 6 +---- go.sum | 69 ++++------------------------------------------------------ 2 files changed, 5 insertions(+), 70 deletions(-) diff --git a/go.mod b/go.mod index a606d88..1fa3651 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,6 @@ module github.com/sdcoffey/techan go 1.12 require ( - github.com/davecgh/go-spew v1.1.0 - github.com/pmezard/go-difflib v1.0.0 - github.com/sdcoffey/big v0.6.0 + github.com/sdcoffey/big v0.7.0 github.com/stretchr/testify v1.2.1 - golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect - honnef.co/go/tools v0.0.1-2020.1.6 // indirect ) diff --git a/go.sum b/go.sum index c4b4966..cab4b44 100644 --- a/go.sum +++ b/go.sum @@ -1,74 +1,13 @@ -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/sdcoffey/big v0.0.0-20180413224939-438f3d83db4c h1:avDKoFuBXMQAcu0sxdpbS88WAiPt2e9YtSuEWfOPj7E= -github.com/sdcoffey/big v0.0.0-20180413224939-438f3d83db4c/go.mod h1:WzOYJJhNOp7m1u1MrNQaYSHmgqHGkZThC+VgDqdFl0I= -github.com/sdcoffey/big v0.4.1 h1:DyK4tHRbbAfdIZV/VbxrEz/ZpcVv5GWNNw+G6AOhfpM= -github.com/sdcoffey/big v0.4.1/go.mod h1:spXh3ZRHdlXkg4SzauLC8ahWmHpxWL5/6drgufL73es= github.com/sdcoffey/big v0.6.0 h1:0C+nB/91mHQGAxlgvDy/hFO3MMv3dTnCuNA3qfhEYYQ= github.com/sdcoffey/big v0.6.0/go.mod h1:spXh3ZRHdlXkg4SzauLC8ahWmHpxWL5/6drgufL73es= +github.com/sdcoffey/big v0.7.0 h1:OnE7fcHq/C59WxWrMegftFa1nftCjsZLVf7PLXsxj2Y= +github.com/sdcoffey/big v0.7.0/go.mod h1:2T05Q7Mt6F1kHHb+PFa0odPFwU67YnSAFYgiYy7krPU= github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.1 h1:52QO5WkIUcHGIR7EnGagH88x1bUzqGXTC5/1bDTUQ7U= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e h1:JgcxKXxCjrA2tyDP/aNU9K0Ck5Czfk6C7e2tMw7+bSI= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd h1:/e+gpKk9r3dJobndpTytxS2gOy6m5uvpg+ISQoEcusQ= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac h1:MQEvx39qSf8vyrx3XRaOe+j1UDIzKwkYOVObRgGPVqI= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7 h1:EBZoQjiKKPaLbPrbpssUfuHtwM6KV/vb4U85g/cigFY= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef h1:RHORRhs540cYZYrzgU2CPUyykkwZM78hGdzocOo9P8A= -golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -honnef.co/go/tools v0.0.0-20190412205916-e87e8279b4cd h1:hnvxEW8n8IJasi4s1HJpanZ1ebTAdsjxzbZPO8Viw+s= -honnef.co/go/tools v0.0.0-20190412205916-e87e8279b4cd/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.5 h1:nI5egYTGJakVyOryqLs1cQO5dO0ksin5XXs2pspk75k= -honnef.co/go/tools v0.0.1-2020.1.5/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.6 h1:W18jzjh8mfPez+AwGLxmOImucz/IFjpNlrKVnaj2YVc= -honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzEYLhQB2YY= From 76d6034412cf1f558ee23b3c7c575feb4b75cea3 Mon Sep 17 00:00:00 2001 From: sdcoffey Date: Sat, 27 Feb 2021 10:38:34 -0800 Subject: [PATCH 05/13] upgrade testify --- go.mod | 2 +- go.sum | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 1fa3651..c81959c 100644 --- a/go.mod +++ b/go.mod @@ -4,5 +4,5 @@ go 1.12 require ( github.com/sdcoffey/big v0.7.0 - github.com/stretchr/testify v1.2.1 + github.com/stretchr/testify v1.7.0 ) diff --git a/go.sum b/go.sum index cab4b44..b27272a 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,12 @@ github.com/sdcoffey/big v0.6.0 h1:0C+nB/91mHQGAxlgvDy/hFO3MMv3dTnCuNA3qfhEYYQ= github.com/sdcoffey/big v0.6.0/go.mod h1:spXh3ZRHdlXkg4SzauLC8ahWmHpxWL5/6drgufL73es= github.com/sdcoffey/big v0.7.0 h1:OnE7fcHq/C59WxWrMegftFa1nftCjsZLVf7PLXsxj2Y= github.com/sdcoffey/big v0.7.0/go.mod h1:2T05Q7Mt6F1kHHb+PFa0odPFwU67YnSAFYgiYy7krPU= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.1 h1:52QO5WkIUcHGIR7EnGagH88x1bUzqGXTC5/1bDTUQ7U= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From a508c98286d400d2fb7f95b664cd591251b843a6 Mon Sep 17 00:00:00 2001 From: sdcoffey Date: Wed, 3 Mar 2021 09:15:34 -0800 Subject: [PATCH 06/13] fixup and reorg moving averages --- cached_indicator.go | 46 +++++++++++++++++++ indicator_exponential_moving_average.go | 45 ++++++++++++++++++ indicator_exponential_moving_average_test.go | 45 ++++++++++++++++++ indicator_macd.go | 15 ++++++ indicator_macd_test.go | 24 ++++++++++ indicator_modified_moving_average.go | 48 ++++++++++++++++++++ indicator_modified_moving_average_test.go | 25 ++++++++++ indicator_simple_moving_average.go | 29 ++++++++++++ indicator_simple_moving_average_test.go | 24 ++++++++++ testutils.go | 18 ++++++++ 10 files changed, 319 insertions(+) create mode 100644 cached_indicator.go create mode 100644 indicator_exponential_moving_average.go create mode 100644 indicator_exponential_moving_average_test.go create mode 100644 indicator_macd.go create mode 100644 indicator_macd_test.go create mode 100644 indicator_modified_moving_average.go create mode 100644 indicator_modified_moving_average_test.go create mode 100644 indicator_simple_moving_average.go create mode 100644 indicator_simple_moving_average_test.go diff --git a/cached_indicator.go b/cached_indicator.go new file mode 100644 index 0000000..4bd8f97 --- /dev/null +++ b/cached_indicator.go @@ -0,0 +1,46 @@ +package techan + +import "github.com/sdcoffey/big" + +type resultCache []*big.Decimal + +type cachedIndicator interface { + Indicator + cache() resultCache + setCache(cache resultCache) + windowSize() int +} + +func cacheResult(indicator cachedIndicator, index int, val big.Decimal) { + if index < len(indicator.cache()) { + indicator.cache()[index] = &val + } else if index == len(indicator.cache()) { + indicator.setCache(append(indicator.cache(), &val)) + } else { + expandResultCache(indicator, index+1) + cacheResult(indicator, index, val) + } +} + +func expandResultCache(indicator cachedIndicator, newSize int) { + sizeDiff := newSize - len(indicator.cache()) + + expansion := make([]*big.Decimal, sizeDiff) + indicator.setCache(append(indicator.cache(), expansion...)) +} + +func returnIfCached(indicator cachedIndicator, index int, firstValueFallback func(int) big.Decimal) *big.Decimal { + if index >= len(indicator.cache()) { + expandResultCache(indicator, index+1) + } else if index < indicator.windowSize()-1 { + return &big.ZERO + } else if val := indicator.cache()[index]; val != nil { + return val + } else if index == indicator.windowSize()-1 { + value := firstValueFallback(index) + cacheResult(indicator, index, value) + return &value + } + + return nil +} diff --git a/indicator_exponential_moving_average.go b/indicator_exponential_moving_average.go new file mode 100644 index 0000000..f539601 --- /dev/null +++ b/indicator_exponential_moving_average.go @@ -0,0 +1,45 @@ +package techan + +import "github.com/sdcoffey/big" + +type emaIndicator struct { + indicator Indicator + window int + alpha big.Decimal + resultCache resultCache +} + +// NewEMAIndicator returns a derivative indicator which returns the average of the current and preceding values in +// the given windowSize, with values closer to current index given more weight. A more in-depth explanation can be found here: +// http://www.investopedia.com/terms/e/ema.asp +func NewEMAIndicator(indicator Indicator, window int) Indicator { + return &emaIndicator{ + indicator: indicator, + window: window, + alpha: big.ONE.Frac(2).Div(big.NewFromInt(window + 1)), + resultCache: make([]*big.Decimal, 1000), + } +} + +func (ema *emaIndicator) Calculate(index int) big.Decimal { + if cachedValue := returnIfCached(ema, index, func(i int) big.Decimal { + return NewSimpleMovingAverage(ema.indicator, ema.window).Calculate(i) + }); cachedValue != nil { + return *cachedValue + } + + todayVal := ema.indicator.Calculate(index).Mul(ema.alpha) + result := todayVal.Add(ema.Calculate(index - 1).Mul(ema.alpha)) + + cacheResult(ema, index, result) + + return result +} + +func (ema emaIndicator) cache() resultCache { return ema.resultCache } + +func (ema *emaIndicator) setCache(newCache resultCache) { + ema.resultCache = newCache +} + +func (ema emaIndicator) windowSize() int { return ema.window } diff --git a/indicator_exponential_moving_average_test.go b/indicator_exponential_moving_average_test.go new file mode 100644 index 0000000..bb78b0a --- /dev/null +++ b/indicator_exponential_moving_average_test.go @@ -0,0 +1,45 @@ +package techan + +import "testing" + +func TestExponentialMovingAverage(t *testing.T) { + expectedValues := []float64{ + 0, + 0, + 64.09, + 63.91, + 63.73, + 63.46, + 63.685, + 63.7675, + 63.3588, + 63.3644, + 62.3472, + 61.9286, + } + + closePriceIndicator := NewClosePriceIndicator(mockedTimeSeries) + indicatorEquals(t, expectedValues, NewEMAIndicator(closePriceIndicator, 3)) +} + +func BenchmarkExponetialMovingAverage(b *testing.B) { + size := 10000 + ts := randomTimeSeries(size) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + ema := NewEMAIndicator(NewClosePriceIndicator(ts), 10) + ema.Calculate(size - 1) + } +} + +func BenchmarkExponentialMovingAverage_Cached(b *testing.B) { + size := 10000 + ts := randomTimeSeries(size) + ema := NewEMAIndicator(NewClosePriceIndicator(ts), 10) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + ema.Calculate(size - 1) + } +} diff --git a/indicator_macd.go b/indicator_macd.go new file mode 100644 index 0000000..b83acb8 --- /dev/null +++ b/indicator_macd.go @@ -0,0 +1,15 @@ +package techan + +// NewMACDIndicator returns a derivative Indicator which returns the difference between two EMAIndicators with long and +// short windows. It's useful for gauging the strength of price movements. A more in-depth explanation can be found here: +// http://www.investopedia.com/terms/m/macd.asp +func NewMACDIndicator(baseIndicator Indicator, shortwindow, longwindow int) Indicator { + return NewDifferenceIndicator(NewEMAIndicator(baseIndicator, shortwindow), NewEMAIndicator(baseIndicator, longwindow)) +} + +// NewMACDHistogramIndicator returns a derivative Indicator based on the MACDIndicator, the result of which is +// the macd indicator minus it's signalLinewindow EMA. A more in-depth explanation can be found here: +// http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:macd-histogram +func NewMACDHistogramIndicator(macdIdicator Indicator, signalLinewindow int) Indicator { + return NewDifferenceIndicator(macdIdicator, NewEMAIndicator(macdIdicator, signalLinewindow)) +} diff --git a/indicator_macd_test.go b/indicator_macd_test.go new file mode 100644 index 0000000..0b4842b --- /dev/null +++ b/indicator_macd_test.go @@ -0,0 +1,24 @@ +package techan + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewMACDIndicator(t *testing.T) { + series := randomTimeSeries(100) + + macd := NewMACDIndicator(NewClosePriceIndicator(series), 12, 26) + + assert.NotNil(t, macd) +} + +func TestNewMACDHistogramIndicator(t *testing.T) { + series := randomTimeSeries(100) + + macd := NewMACDIndicator(NewClosePriceIndicator(series), 12, 26) + macdHistogram := NewMACDHistogramIndicator(macd, 9) + + assert.NotNil(t, macdHistogram) +} diff --git a/indicator_modified_moving_average.go b/indicator_modified_moving_average.go new file mode 100644 index 0000000..90fac61 --- /dev/null +++ b/indicator_modified_moving_average.go @@ -0,0 +1,48 @@ +package techan + +import "github.com/sdcoffey/big" + +type modifiedMovingAverageIndicator struct { + indicator Indicator + window int + resultCache resultCache +} + +// NewMMAIndicator returns a derivative indciator which returns the modified moving average of the underlying +// indictator. An in-depth explanation can be found here: +// https://en.wikipedia.org/wiki/Moving_average#Modified_moving_average +func NewMMAIndicator(indicator Indicator, window int) Indicator { + return &modifiedMovingAverageIndicator{ + indicator: indicator, + window: window, + resultCache: make([]*big.Decimal, 10000), + } +} + +func (mma *modifiedMovingAverageIndicator) Calculate(index int) big.Decimal { + if cachedValue := returnIfCached(mma, index, func(i int) big.Decimal { + return NewSimpleMovingAverage(mma.indicator, mma.window).Calculate(i) + }); cachedValue != nil { + return *cachedValue + } + + todayVal := mma.indicator.Calculate(index) + lastVal := mma.Calculate(index - 1) + + result := lastVal.Add(big.NewDecimal(1.0 / float64(mma.window)).Mul(todayVal.Sub(lastVal))) + cacheResult(mma, index, result) + + return result +} + +func (mma modifiedMovingAverageIndicator) cache() resultCache { + return mma.resultCache +} + +func (mma *modifiedMovingAverageIndicator) setCache(cache resultCache) { + mma.resultCache = cache +} + +func (mma modifiedMovingAverageIndicator) windowSize() int { + return mma.window +} diff --git a/indicator_modified_moving_average_test.go b/indicator_modified_moving_average_test.go new file mode 100644 index 0000000..b29779e --- /dev/null +++ b/indicator_modified_moving_average_test.go @@ -0,0 +1,25 @@ +package techan + +import ( + "testing" +) + +func TestModifiedMovingAverage(t *testing.T) { + indicator := NewMMAIndicator(NewClosePriceIndicator(mockedTimeSeries), 3) + + expected := []float64{ + 0, + 0, + 64.09, + 63.97, + 63.83, + 63.6167, + 63.7144, + 63.7596, + 63.4898, + 63.4498, + 62.7432, + } + + indicatorEquals(t, expected, indicator) +} diff --git a/indicator_simple_moving_average.go b/indicator_simple_moving_average.go new file mode 100644 index 0000000..805f4dd --- /dev/null +++ b/indicator_simple_moving_average.go @@ -0,0 +1,29 @@ +package techan + +import "github.com/sdcoffey/big" + +type smaIndicator struct { + indicator Indicator + window int +} + +// NewSimpleMovingAverage returns a derivative Indicator which returns the average of the current value and preceding +// values in the given windowSize. +func NewSimpleMovingAverage(indicator Indicator, window int) Indicator { + return smaIndicator{indicator, window} +} + +func (sma smaIndicator) Calculate(index int) big.Decimal { + if index < sma.window-1 { + return big.ZERO + } + + sum := big.ZERO + for i := index; i > index-sma.window; i-- { + sum = sum.Add(sma.indicator.Calculate(i)) + } + + result := sum.Div(big.NewFromInt(sma.window)) + + return result +} diff --git a/indicator_simple_moving_average_test.go b/indicator_simple_moving_average_test.go new file mode 100644 index 0000000..98fb8e7 --- /dev/null +++ b/indicator_simple_moving_average_test.go @@ -0,0 +1,24 @@ +package techan + +import "testing" + +func TestSimpleMovingAverage(t *testing.T) { + expectedValues := []float64{ + 0, + 0, + 64.09, + 63.75, + 63.67, + 63.49, + 63.55, + 63.65, + 63.57, + 63.39, + 62.55, + 62.07, + } + + closePriceIndicator := NewClosePriceIndicator(mockedTimeSeries) + + indicatorEquals(t, expectedValues, NewSimpleMovingAverage(closePriceIndicator, 3)) +} diff --git a/testutils.go b/testutils.go index ea540f3..6bc28da 100644 --- a/testutils.go +++ b/testutils.go @@ -2,6 +2,7 @@ package techan import ( "fmt" + "math" "math/rand" "testing" "time" @@ -13,6 +14,11 @@ import ( ) var candleIndex int +var mockedTimeSeries = mockTimeSeriesFl( + 64.75, 63.79, 63.73, + 63.73, 63.55, 63.19, + 63.91, 63.85, 62.95, + 63.37, 61.33, 61.51) func randomTimeSeries(size int) *TimeSeries { vals := make([]string, size) @@ -81,3 +87,15 @@ func mockTimeSeriesFl(values ...float64) *TimeSeries { func decimalEquals(t *testing.T, expected float64, actual big.Decimal) { assert.Equal(t, fmt.Sprintf("%.4f", expected), fmt.Sprintf("%.4f", actual.Float())) } + +func indicatorEquals(t *testing.T, expected []float64, indicator Indicator) { + precision := 4.0 + m := math.Pow(10, precision) + + actualValues := make([]float64, len(expected)) + for index := range expected { + actualValues[index] = math.Round(indicator.Calculate(index).Float()*m) / m + } + + assert.EqualValues(t, expected, actualValues) +} From e18a379ec619faf53e8d25ec099507a613132111 Mon Sep 17 00:00:00 2001 From: sdcoffey Date: Wed, 3 Mar 2021 09:16:40 -0800 Subject: [PATCH 07/13] fixup! fixup and reorg moving averages --- indicator_moving_average.go | 91 ------------------------- indicator_moving_average_test.go | 113 ------------------------------- 2 files changed, 204 deletions(-) delete mode 100644 indicator_moving_average.go delete mode 100644 indicator_moving_average_test.go diff --git a/indicator_moving_average.go b/indicator_moving_average.go deleted file mode 100644 index 8d0f6c5..0000000 --- a/indicator_moving_average.go +++ /dev/null @@ -1,91 +0,0 @@ -package techan - -import "github.com/sdcoffey/big" - -type smaIndicator struct { - indicator Indicator - window int -} - -// NewSimpleMovingAverage returns a derivative Indicator which returns the average of the current value and preceding -// values in the given window. -func NewSimpleMovingAverage(indicator Indicator, window int) Indicator { - return smaIndicator{indicator, window} -} - -func (sma smaIndicator) Calculate(index int) big.Decimal { - sum := big.ZERO - for i := Max(0, index-sma.window+1); i <= index; i++ { - sum = sum.Add(sma.indicator.Calculate(i)) - } - realwindow := Min(sma.window, index+1) - - return sum.Div(big.NewDecimal(float64(realwindow))) -} - -type emaIndicator struct { - Indicator - window int - alpha big.Decimal - resultCache []*big.Decimal -} - -// NewEMAIndicator returns a derivative indicator which returns the average of the current and preceding values in -// the given window, with values closer to current index given more weight. A more in-depth explanation can be found here: -// http://www.investopedia.com/terms/e/ema.asp -func NewEMAIndicator(indicator Indicator, window int) Indicator { - return &emaIndicator{ - Indicator: indicator, - window: window, - alpha: big.NewDecimal(2.0).Div(big.NewFromInt(window +1)), - resultCache: make([]*big.Decimal, 10000), - } -} - -func (ema *emaIndicator) Calculate(index int) big.Decimal { - if index < ema.window { - return big.ZERO - } else if len(ema.resultCache) > index && ema.resultCache[index] != nil { - return *ema.resultCache[index] - } - - emaPrev := ema.Calculate(index - 1) - result := ema.Indicator.Calculate(index).Sub(emaPrev).Mul(ema.alpha).Add(emaPrev) - ema.cacheResult(index, result) - - return result -} - -func (ema *emaIndicator) cacheResult(index int, val big.Decimal) { - if index < len(ema.resultCache) { - ema.resultCache[index] = &val - } else { - ema.resultCache = append(ema.resultCache, &val) - } -} - -// NewMMAIndicator returns a derivative indciator which returns the modified moving average of the underlying -// indictator. An in-depth explanation can be found here: -// https://en.wikipedia.org/wiki/Moving_average#Modified_moving_average -func NewMMAIndicator(indicator Indicator, window int) Indicator { - return &emaIndicator{ - Indicator: indicator, - window: window, - alpha: big.NewDecimal(1.0 / float64(window)), - resultCache: make([]*big.Decimal, 10000), - } -} - -// NewMACDIndicator returns a derivative Indicator which returns the difference between two EMAIndicators with long and -// short windows. It's useful for gauging the strength of price movements. A more in-depth explanation can be found here: -// http://www.investopedia.com/terms/m/macd.asp -func NewMACDIndicator(baseIndicator Indicator, shortwindow, longwindow int) Indicator { - return NewDifferenceIndicator(NewEMAIndicator(baseIndicator, shortwindow), NewEMAIndicator(baseIndicator, longwindow)) -} - -// NewMACDHistogramIndicator returns a derivative Indicator based on the MACDIndicator, the result of which is -// the macd indicator minus it's signalLinewindow EMA. A more in-depth explanation can be found here: -// http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:macd-histogram -func NewMACDHistogramIndicator(macdIdicator Indicator, signalLinewindow int) Indicator { - return NewDifferenceIndicator(macdIdicator, NewEMAIndicator(macdIdicator, signalLinewindow)) -} diff --git a/indicator_moving_average_test.go b/indicator_moving_average_test.go deleted file mode 100644 index 06619fd..0000000 --- a/indicator_moving_average_test.go +++ /dev/null @@ -1,113 +0,0 @@ -package techan - -import ( - "math" - "testing" - "math/big" - - "github.com/stretchr/testify/assert" -) - -func TestSimpleMovingAverage(t *testing.T) { - //ts := mockTimeSeriesFl(15, 16, 17, 18, 17, 18, 16) - // - //sma := NewSimpleMovingAverage(NewClosePriceIndicator(ts), 3) - - nan := math.NaN() - println(nan) - - bnan := big.NewFloat(nan) - print(bnan.String()) -} - -func TestExponentialMovingAverage(t *testing.T) { - t.Run("if index == 0, returns base indicator", func(t *testing.T) { - ts := mockTimeSeriesFl( - 64.75, 63.79, 63.73, - 63.73, 63.55, 63.19, - 63.91, 63.85, 62.95, - 63.37, 61.33, 61.51) - - ema := NewEMAIndicator(NewClosePriceIndicator(ts), 10) - - decimalEquals(t, 64.75, ema.Calculate(0)) - }) - - t.Run("if index > 0, calculates ema", func(t *testing.T) { - ts := mockTimeSeriesFl( - 64.75, 63.79, 63.73, - 63.73, 63.55, 63.19, - 63.91, 63.85, 62.95, - 63.37, 61.33, 61.51) - - ema := NewEMAIndicator(NewClosePriceIndicator(ts), 10) - - decimalEquals(t, 63.6948, ema.Calculate(9)) - decimalEquals(t, 63.2649, ema.Calculate(10)) - decimalEquals(t, 62.9458, ema.Calculate(11)) - }) - - t.Run("expands result cache when > 10000 candles added", func(t *testing.T) { - series := randomTimeSeries(10001) - ema := NewEMAIndicator(NewClosePriceIndicator(series), 10) - - ema.Calculate(10000) - - assert.EqualValues(t, 10001, len(ema.(*emaIndicator).resultCache)) - }) - - t.Run("Very simple", func(t *testing.T) { - ts := mockTimeSeriesFl(15, 16, 17, 18, 19, 20, 21) - - ema := NewEMAIndicator(NewClosePriceIndicator(ts), 3) - - decimalEquals(t, 16, ema.Calculate(5)) - decimalEquals(t, 15, ema.Calculate(4)) - decimalEquals(t, 14, ema.Calculate(3)) - decimalEquals(t, 13, ema.Calculate(2)) - decimalEquals(t, 0, ema.Calculate(1)) - decimalEquals(t, 0, ema.Calculate(0)) - }) -} - -func TestNewMMAIndicator(t *testing.T) { - series := mockTimeSeriesFl( - 64.75, 63.79, 63.73, - 63.73, 63.55, 63.19, - 63.91, 63.85, 62.95, - 63.37, 61.33, 61.51) - - mma := NewMMAIndicator(NewClosePriceIndicator(series), 10) - - decimalEquals(t, 63.9983, mma.Calculate(9)) - decimalEquals(t, 63.7315, mma.Calculate(10)) - decimalEquals(t, 63.5094, mma.Calculate(11)) -} - -func TestNewMACDIndicator(t *testing.T) { - series := randomTimeSeries(100) - - macd := NewMACDIndicator(NewClosePriceIndicator(series), 12, 26) - - assert.NotNil(t, macd) -} - -func TestNewMACDHistogramIndicator(t *testing.T) { - series := randomTimeSeries(100) - - macd := NewMACDIndicator(NewClosePriceIndicator(series), 12, 26) - macdHistogram := NewMACDHistogramIndicator(macd, 9) - - assert.NotNil(t, macdHistogram) -} - -func BenchmarkExponetialMovingAverage(b *testing.B) { - size := 10000 - ts := randomTimeSeries(size) - - b.ResetTimer() - for i := 0; i < b.N; i++ { - ema := NewEMAIndicator(NewClosePriceIndicator(ts), 10) - ema.Calculate(size - 1) - } -} From 5b34605360928d1bee1080dce6bbf518ec19eab9 Mon Sep 17 00:00:00 2001 From: sdcoffey Date: Wed, 3 Mar 2021 09:16:55 -0800 Subject: [PATCH 08/13] update bootstrap --- Makefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index ba0df76..b702c4e 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,9 @@ files := $(shell find . -name "*.go" | grep -v vendor) bootstrap: - go get golang.org/x/lint/golint - go get honnef.co/go/tools/... + go install -v golang.org/x/lint/golint@latest + go install -v golang.org/x/tools/...@latest + go install -v honnef.co/go/tools/cmd/staticcheck@latest clean: goimports -w $(files) From 2dafa96bfcf232f6b8742288c9de9d156a05ac0a Mon Sep 17 00:00:00 2001 From: sdcoffey Date: Thu, 4 Mar 2021 08:27:13 -0800 Subject: [PATCH 09/13] fixup RSI and MMA --- indicator_mean_deviation.go | 4 ++ indicator_mean_deviation_test.go | 24 ++++--- indicator_modified_moving_average_test.go | 1 + indicator_relative_strength.go | 17 +++-- indicator_relative_strength_test.go | 81 +++++++++++------------ testutils.go | 17 +++-- 6 files changed, 79 insertions(+), 65 deletions(-) diff --git a/indicator_mean_deviation.go b/indicator_mean_deviation.go index 615fa4d..e98cc86 100644 --- a/indicator_mean_deviation.go +++ b/indicator_mean_deviation.go @@ -19,6 +19,10 @@ func NewMeanDeviationIndicator(indicator Indicator, window int) Indicator { } func (mdi meanDeviationIndicator) Calculate(index int) big.Decimal { + if index < mdi.window-1 { + return big.ZERO + } + average := mdi.movingAverage.Calculate(index) start := Max(0, index-mdi.window+1) absoluteDeviations := big.NewDecimal(0) diff --git a/indicator_mean_deviation_test.go b/indicator_mean_deviation_test.go index 033f70e..d45e886 100644 --- a/indicator_mean_deviation_test.go +++ b/indicator_mean_deviation_test.go @@ -9,13 +9,19 @@ func TestMeanDeviationIndicator(t *testing.T) { meanDeviation := NewMeanDeviationIndicator(NewClosePriceIndicator(ts), 5) - decimalEquals(t, 2.4444, meanDeviation.Calculate(2)) - decimalEquals(t, 2.5, meanDeviation.Calculate(3)) - decimalEquals(t, 2.16, meanDeviation.Calculate(4)) - decimalEquals(t, 1.68, meanDeviation.Calculate(5)) - decimalEquals(t, 1.2, meanDeviation.Calculate(6)) - decimalEquals(t, 2.16, meanDeviation.Calculate(7)) - decimalEquals(t, 2.32, meanDeviation.Calculate(8)) - decimalEquals(t, 2.72, meanDeviation.Calculate(9)) - decimalEquals(t, 3.52, meanDeviation.Calculate(10)) + expected := []float64{ + 0, + 0, + 0, + 0, + 2.16, + 1.68, + 1.2, + 2.16, + 2.32, + 2.72, + 3.52, + } + + indicatorEquals(t, expected, meanDeviation) } diff --git a/indicator_modified_moving_average_test.go b/indicator_modified_moving_average_test.go index b29779e..5e20a9e 100644 --- a/indicator_modified_moving_average_test.go +++ b/indicator_modified_moving_average_test.go @@ -19,6 +19,7 @@ func TestModifiedMovingAverage(t *testing.T) { 63.4898, 63.4498, 62.7432, + 62.3321, } indicatorEquals(t, expected, indicator) diff --git a/indicator_relative_strength.go b/indicator_relative_strength.go index 0b6b83e..e0652df 100644 --- a/indicator_relative_strength.go +++ b/indicator_relative_strength.go @@ -8,6 +8,7 @@ import ( type relativeStrengthIndexIndicator struct { rsIndicator Indicator + oneHundred big.Decimal } // NewRelativeStrengthIndexIndicator returns a derivative Indicator which returns the relative strength index of the base indicator @@ -16,23 +17,20 @@ type relativeStrengthIndexIndicator struct { func NewRelativeStrengthIndexIndicator(indicator Indicator, timeframe int) Indicator { return relativeStrengthIndexIndicator{ rsIndicator: NewRelativeStrengthIndicator(indicator, timeframe), + oneHundred: big.NewFromString("100"), } } func (rsi relativeStrengthIndexIndicator) Calculate(index int) big.Decimal { - if index == 0 { - return big.ZERO - } - relativeStrength := rsi.rsIndicator.Calculate(index) - oneHundred := big.NewFromString("100") - return oneHundred.Sub(oneHundred.Div(big.ONE.Add(relativeStrength))) + return rsi.oneHundred.Sub(rsi.oneHundred.Div(big.ONE.Add(relativeStrength))) } type relativeStrengthIndicator struct { avgGain Indicator avgLoss Indicator + window int } // NewRelativeStrengthIndicator returns a derivative Indicator which returns the relative strength of the base indicator @@ -42,15 +40,20 @@ func NewRelativeStrengthIndicator(indicator Indicator, timeframe int) Indicator return relativeStrengthIndicator{ avgGain: NewMMAIndicator(NewGainIndicator(indicator), timeframe), avgLoss: NewMMAIndicator(NewLossIndicator(indicator), timeframe), + window: timeframe, } } func (rs relativeStrengthIndicator) Calculate(index int) big.Decimal { + if index < rs.window-1 { + return big.ZERO + } + avgGain := rs.avgGain.Calculate(index) avgLoss := rs.avgLoss.Calculate(index) if avgLoss.EQ(big.ZERO) { - return big.NewDecimal(math.MaxFloat64) + return big.NewDecimal(math.Inf(1)) } return avgGain.Div(avgLoss) diff --git a/indicator_relative_strength_test.go b/indicator_relative_strength_test.go index 1cb11cc..055ccad 100644 --- a/indicator_relative_strength_test.go +++ b/indicator_relative_strength_test.go @@ -8,57 +8,50 @@ import ( "github.com/stretchr/testify/assert" ) -var timeseries = mockTimeSeriesFl( - 50.45, 50.30, 50.20, 50.15, - 50.05, 50.06, 50.10, 50.08, - 50.03, 50.07, 50.01, 50.14, - 50.22, 50.43, 50.50, 50.56, - 50.52, 50.70, 50.55, 50.62, - 50.90, 50.82, 50.86, 51.20, - 51.30, 51.10) - func TestRelativeStrengthIndexIndicator(t *testing.T) { - t.Run("when index == 0, returns 0", func(t *testing.T) { - indicator := NewRelativeStrengthIndexIndicator(NewClosePriceIndicator(timeseries), 14) - - decimalEquals(t, 0, indicator.Calculate(0)) - }) - - t.Run("when index > 0, returns rsi index", func(t *testing.T) { - indicator := NewRelativeStrengthIndexIndicator(NewClosePriceIndicator(timeseries), 14) - - decimalEquals(t, 68.4747, indicator.Calculate(15)) - decimalEquals(t, 64.7836, indicator.Calculate(16)) - decimalEquals(t, 72.0777, indicator.Calculate(17)) - decimalEquals(t, 60.7800, indicator.Calculate(18)) - decimalEquals(t, 63.6439, indicator.Calculate(19)) - decimalEquals(t, 72.3434, indicator.Calculate(20)) - decimalEquals(t, 67.3823, indicator.Calculate(21)) - decimalEquals(t, 68.5438, indicator.Calculate(22)) - decimalEquals(t, 76.2770, indicator.Calculate(23)) - decimalEquals(t, 77.9908, indicator.Calculate(24)) - decimalEquals(t, 67.4895, indicator.Calculate(25)) - }) + indicator := NewRelativeStrengthIndexIndicator(NewClosePriceIndicator(mockedTimeSeries), 3) + + expectedValues := []float64{ + 0, + 0, + 0, + 0, + 0, + 0, + 57.9952, + 54.0751, + 21.451, + 44.7739, + 14.1542, + 21.2794, + } + + indicatorEquals(t, expectedValues, indicator) } func TestRelativeStrengthIndicator(t *testing.T) { - indicator := NewRelativeStrengthIndicator(NewClosePriceIndicator(timeseries), 14) - - decimalEquals(t, 2.1721, indicator.Calculate(15)) - decimalEquals(t, 1.8396, indicator.Calculate(16)) - decimalEquals(t, 2.5814, indicator.Calculate(17)) - decimalEquals(t, 1.5497, indicator.Calculate(18)) - decimalEquals(t, 1.7506, indicator.Calculate(19)) - decimalEquals(t, 2.6158, indicator.Calculate(20)) - decimalEquals(t, 2.0658, indicator.Calculate(21)) - decimalEquals(t, 2.1790, indicator.Calculate(22)) - decimalEquals(t, 3.2153, indicator.Calculate(23)) - decimalEquals(t, 3.5436, indicator.Calculate(24)) - decimalEquals(t, 2.0759, indicator.Calculate(25)) + indicator := NewRelativeStrengthIndicator(NewClosePriceIndicator(mockedTimeSeries), 3) + + expectedValues := []float64{ + 0, + 0, + 0, + 0, + 0, + 0, + 1.3807, + 1.1775, + 0.2731, + 0.8107, + 0.1649, + 0.2703, + } + + indicatorEquals(t, expectedValues, indicator) } func TestRelativeStrengthIndicatorNoPriceChange(t *testing.T) { close := NewClosePriceIndicator(mockTimeSeries("42.0", "42.0")) rsInd := NewRelativeStrengthIndicator(close, 2) - assert.Equal(t, big.NewDecimal(math.MaxFloat64).FormattedString(2), rsInd.Calculate(1).FormattedString(2)) + assert.Equal(t, big.NewDecimal(math.Inf(1)).FormattedString(2), rsInd.Calculate(1).FormattedString(2)) } diff --git a/testutils.go b/testutils.go index 6bc28da..b045f8b 100644 --- a/testutils.go +++ b/testutils.go @@ -92,10 +92,17 @@ func indicatorEquals(t *testing.T, expected []float64, indicator Indicator) { precision := 4.0 m := math.Pow(10, precision) - actualValues := make([]float64, len(expected)) - for index := range expected { - actualValues[index] = math.Round(indicator.Calculate(index).Float()*m) / m - } + actualValues := make([]float64, 0) + + defer func() { + recover() - assert.EqualValues(t, expected, actualValues) + assert.EqualValues(t, expected, actualValues) + }() + + var index int + for { + actualValues = append(actualValues, math.Round(indicator.Calculate(index).Float()*m)/m) + index++ + } } From dd372661e106d8de19c41aa724f626b69df796ee Mon Sep 17 00:00:00 2001 From: sdcoffey Date: Fri, 5 Mar 2021 08:41:01 -0800 Subject: [PATCH 10/13] finish keltner channel --- indicator_average_true_range.go | 4 +- indicator_average_true_range_test.go | 32 ++++++++-------- indicator_keltner_channel.go | 13 ++++--- indicator_keltner_channel_test.go | 57 +++++++++++++++++++--------- indicator_true_range_test.go | 29 +++++++------- testutils.go | 19 ++++++---- 6 files changed, 93 insertions(+), 61 deletions(-) diff --git a/indicator_average_true_range.go b/indicator_average_true_range.go index b379a6a..59bebd8 100644 --- a/indicator_average_true_range.go +++ b/indicator_average_true_range.go @@ -7,7 +7,7 @@ type averageTrueRangeIndicator struct { window int } -// NewAverageTrueRangeIndicator returns a base indicator that caculates the average true range of the +// NewAverageTrueRangeIndicator returns a base indicator that calculates the average true range of the // underlying over a window // https://www.investopedia.com/terms/a/atr.asp func NewAverageTrueRangeIndicator(series *TimeSeries, window int) Indicator { @@ -24,7 +24,7 @@ func (atr averageTrueRangeIndicator) Calculate(index int) big.Decimal { sum := big.ZERO - for i := index - atr.window + 1; i <= index; i++ { + for i := index; i > index-atr.window; i-- { sum = sum.Add(NewTrueRangeIndicator(atr.series).Calculate(i)) } diff --git a/indicator_average_true_range_test.go b/indicator_average_true_range_test.go index 23a2722..106b683 100644 --- a/indicator_average_true_range_test.go +++ b/indicator_average_true_range_test.go @@ -3,22 +3,22 @@ package techan import "testing" func TestAverageTrueRangeIndicator(t *testing.T) { - ts := mockTimeSeriesOCHL( - []float64{10, 15, 20, 10}, - []float64{11, 16, 21, 11}, - []float64{12, 17, 22, 12}, - []float64{13, 18, 23, 13}, - []float64{14, 19, 24, 14}, - []float64{15, 20, 25, 15}, - []float64{16, 20, 26, 16}, - ) + atrIndicator := NewAverageTrueRangeIndicator(mockedTimeSeries, 3) - trueRangeIndicator := NewAverageTrueRangeIndicator(ts, 3) + expectedValues := []float64{ + 0, + 0, + 0, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2.3467, + 2.3467, + } - decimalEquals(t, 10, trueRangeIndicator.Calculate(5)) - decimalEquals(t, 10, trueRangeIndicator.Calculate(4)) - decimalEquals(t, 10, trueRangeIndicator.Calculate(3)) - decimalEquals(t, 0, trueRangeIndicator.Calculate(2)) - decimalEquals(t, 0, trueRangeIndicator.Calculate(1)) - decimalEquals(t, 0, trueRangeIndicator.Calculate(0)) + indicatorEquals(t, expectedValues, atrIndicator) } diff --git a/indicator_keltner_channel.go b/indicator_keltner_channel.go index 137de54..ca53d0b 100644 --- a/indicator_keltner_channel.go +++ b/indicator_keltner_channel.go @@ -1,6 +1,8 @@ package techan -import "github.com/sdcoffey/big" +import ( + "github.com/sdcoffey/big" +) type keltnerChannelIndicator struct { ema Indicator @@ -20,14 +22,15 @@ func NewKeltnerChannelUpperIndicator(series *TimeSeries, window int) Indicator { func NewKeltnerChannelLowerIndicator(series *TimeSeries, window int) Indicator { return keltnerChannelIndicator{ - atr: NewAverageTrueRangeIndicator(series, window), - ema: NewEMAIndicator(NewClosePriceIndicator(series), window), - mul: big.ONE, + atr: NewAverageTrueRangeIndicator(series, window), + ema: NewEMAIndicator(NewClosePriceIndicator(series), window), + mul: big.ONE.Neg(), + window: window, } } func (kci keltnerChannelIndicator) Calculate(index int) big.Decimal { - if index < kci.window { + if index <= kci.window-1 { return big.ZERO } diff --git a/indicator_keltner_channel_test.go b/indicator_keltner_channel_test.go index f3e1285..ccc6cca 100644 --- a/indicator_keltner_channel_test.go +++ b/indicator_keltner_channel_test.go @@ -1,26 +1,49 @@ package techan -import "testing" +import ( + "testing" +) func TestKeltnerChannel(t *testing.T) { t.Run("Upper", func(t *testing.T) { - ts := mockTimeSeriesOCHL( - []float64{10, 15, 20, 10}, - []float64{11, 16, 21, 11}, - []float64{12, 17, 22, 12}, - []float64{13, 18, 23, 13}, - []float64{14, 19, 24, 14}, - []float64{15, 20, 25, 15}, - []float64{16, 20, 26, 16}, - ) + upper := NewKeltnerChannelUpperIndicator(mockedTimeSeries, 3) - upper := NewKeltnerChannelUpperIndicator(ts, 3) + expectedValues := []float64{ + 0, + 0, + 0, + 67.91, + 67.73, + 67.46, + 67.685, + 67.7675, + 67.3588, + 67.3644, + 67.0405, + 66.6219, + } - decimalEquals(t, 40, upper.Calculate(5)) - decimalEquals(t, 39, upper.Calculate(4)) - decimalEquals(t, 38, upper.Calculate(3)) - decimalEquals(t, 37, upper.Calculate(2)) - decimalEquals(t, 0, upper.Calculate(1)) - decimalEquals(t, 0, upper.Calculate(0)) + indicatorEquals(t, expectedValues, upper) + }) + + t.Run("Lower", func(t *testing.T) { + lower := NewKeltnerChannelLowerIndicator(mockedTimeSeries, 3) + + expectedValues := []float64{ + 0, + 0, + 0, + 59.91, + 59.73, + 59.46, + 59.685, + 59.7675, + 59.3588, + 59.3644, + 57.6539, + 57.2353, + } + + indicatorEquals(t, expectedValues, lower) }) } diff --git a/indicator_true_range_test.go b/indicator_true_range_test.go index 202a247..18579a9 100644 --- a/indicator_true_range_test.go +++ b/indicator_true_range_test.go @@ -5,19 +5,22 @@ import ( ) func TestTrueRangeIndicator(t *testing.T) { - ts := mockTimeSeriesOCHL( - []float64{10, 15, 20, 10}, - []float64{11, 16, 21, 11}, - []float64{12, 17, 22, 12}, - []float64{13, 18, 23, 13}, - []float64{14, 19, 24, 14}, - []float64{15, 20, 25, 15}, - ) + trueRangeIndicator := NewTrueRangeIndicator(mockedTimeSeries) - trueRangeIndicator := NewTrueRangeIndicator(ts) + expectedValues := []float64{ + 0, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 3.04, + 2, + } - decimalEquals(t, 10, trueRangeIndicator.Calculate(4)) - decimalEquals(t, 10, trueRangeIndicator.Calculate(3)) - decimalEquals(t, 10, trueRangeIndicator.Calculate(2)) - decimalEquals(t, 10, trueRangeIndicator.Calculate(1)) + indicatorEquals(t, expectedValues, trueRangeIndicator) } diff --git a/testutils.go b/testutils.go index b045f8b..dd0303c 100644 --- a/testutils.go +++ b/testutils.go @@ -62,8 +62,8 @@ func mockTimeSeries(values ...string) *TimeSeries { candle := NewCandle(NewTimePeriod(time.Unix(int64(candleIndex), 0), time.Second)) candle.OpenPrice = big.NewFromString(val) candle.ClosePrice = big.NewFromString(val) - candle.MaxPrice = big.NewFromString(val) - candle.MinPrice = big.NewFromString(val) + candle.MaxPrice = big.NewFromString(val).Add(big.ONE) + candle.MinPrice = big.NewFromString(val).Sub(big.ONE) candle.Volume = big.NewFromString(val) ts.AddCandle(candle) @@ -88,21 +88,24 @@ func decimalEquals(t *testing.T, expected float64, actual big.Decimal) { assert.Equal(t, fmt.Sprintf("%.4f", expected), fmt.Sprintf("%.4f", actual.Float())) } -func indicatorEquals(t *testing.T, expected []float64, indicator Indicator) { +func dump(indicator Indicator) (values []float64) { precision := 4.0 m := math.Pow(10, precision) - actualValues := make([]float64, 0) - defer func() { recover() - - assert.EqualValues(t, expected, actualValues) }() var index int for { - actualValues = append(actualValues, math.Round(indicator.Calculate(index).Float()*m)/m) + values = append(values, math.Round(indicator.Calculate(index).Float()*m)/m) index++ } + + return +} + +func indicatorEquals(t *testing.T, expected []float64, indicator Indicator) { + actualValues := dump(indicator) + assert.EqualValues(t, expected, actualValues) } From 0c2ef4f7670c71ae18d005e9ad0e783673f6b9c8 Mon Sep 17 00:00:00 2001 From: sdcoffey Date: Sat, 6 Mar 2021 09:16:12 -0800 Subject: [PATCH 11/13] sp --- indicator_true_range.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indicator_true_range.go b/indicator_true_range.go index aa25600..0ea453a 100644 --- a/indicator_true_range.go +++ b/indicator_true_range.go @@ -7,7 +7,7 @@ type trueRangeIndicator struct { } // NewTrueRangeIndicator returns a base indicator -// which calculates the true rangat the current point in time for a series +// which calculates the true range at the current point in time for a series // https://www.investopedia.com/terms/a/atr.asp func NewTrueRangeIndicator(series *TimeSeries) Indicator { return trueRangeIndicator{ From 4cbdbe2ead5186a1364cb6bd079d914c96658511 Mon Sep 17 00:00:00 2001 From: sdcoffey Date: Sat, 6 Mar 2021 09:20:46 -0800 Subject: [PATCH 12/13] add .codecov.yml --- .codecov.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .codecov.yml diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..17a27ab --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,4 @@ +ignore: + - testutils.go + - example/**/* + - scripts/**/* \ No newline at end of file From d2b159ac2e949c8efe22104002283ae029815867 Mon Sep 17 00:00:00 2001 From: sdcoffey Date: Sat, 6 Mar 2021 09:41:24 -0800 Subject: [PATCH 13/13] test coverage --- indicator_exponential_moving_average_test.go | 51 +++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/indicator_exponential_moving_average_test.go b/indicator_exponential_moving_average_test.go index bb78b0a..8bd41d1 100644 --- a/indicator_exponential_moving_average_test.go +++ b/indicator_exponential_moving_average_test.go @@ -1,25 +1,42 @@ package techan -import "testing" +import ( + "testing" + + "github.com/stretchr/testify/assert" +) func TestExponentialMovingAverage(t *testing.T) { - expectedValues := []float64{ - 0, - 0, - 64.09, - 63.91, - 63.73, - 63.46, - 63.685, - 63.7675, - 63.3588, - 63.3644, - 62.3472, - 61.9286, - } + t.Run("Default Case", func(t *testing.T) { + expectedValues := []float64{ + 0, + 0, + 64.09, + 63.91, + 63.73, + 63.46, + 63.685, + 63.7675, + 63.3588, + 63.3644, + 62.3472, + 61.9286, + } + + closePriceIndicator := NewClosePriceIndicator(mockedTimeSeries) + indicatorEquals(t, expectedValues, NewEMAIndicator(closePriceIndicator, 3)) + }) + + t.Run("Expands Result Cache", func(t *testing.T) { + closeIndicator := NewClosePriceIndicator(randomTimeSeries(1001)) + ema := NewEMAIndicator(closeIndicator, 20) + + ema.Calculate(1000) - closePriceIndicator := NewClosePriceIndicator(mockedTimeSeries) - indicatorEquals(t, expectedValues, NewEMAIndicator(closePriceIndicator, 3)) + emaStruct, ok := ema.(cachedIndicator) + assert.True(t, ok) + assert.EqualValues(t, 1001, len(emaStruct.cache())) + }) } func BenchmarkExponetialMovingAverage(b *testing.B) {