Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ADx, Keltner Chan, CRSI #30

Merged
merged 13 commits into from
Mar 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ignore:
- testutils.go
- example/**/*
- scripts/**/*
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
46 changes: 46 additions & 0 deletions cached_indicator.go
Original file line number Diff line number Diff line change
@@ -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
}
8 changes: 2 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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.4.1
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
github.com/sdcoffey/big v0.7.0
github.com/stretchr/testify v1.7.0
)
77 changes: 12 additions & 65 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,72 +1,19 @@
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/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/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=
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=
32 changes: 32 additions & 0 deletions indicator_average_true_range.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package techan

import "github.com/sdcoffey/big"

type averageTrueRangeIndicator struct {
series *TimeSeries
window int
}

// 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 {
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; i > index-atr.window; i-- {
sum = sum.Add(NewTrueRangeIndicator(atr.series).Calculate(i))
}

return sum.Div(big.NewFromInt(atr.window))
}
24 changes: 24 additions & 0 deletions indicator_average_true_range_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package techan

import "testing"

func TestAverageTrueRangeIndicator(t *testing.T) {
atrIndicator := NewAverageTrueRangeIndicator(mockedTimeSeries, 3)

expectedValues := []float64{
0,
0,
0,
2,
2,
2,
2,
2,
2,
2,
2.3467,
2.3467,
}

indicatorEquals(t, expectedValues, atrIndicator)
}
45 changes: 45 additions & 0 deletions indicator_exponential_moving_average.go
Original file line number Diff line number Diff line change
@@ -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 }
62 changes: 62 additions & 0 deletions indicator_exponential_moving_average_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package techan

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestExponentialMovingAverage(t *testing.T) {
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)

emaStruct, ok := ema.(cachedIndicator)
assert.True(t, ok)
assert.EqualValues(t, 1001, len(emaStruct.cache()))
})
}

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)
}
}
40 changes: 40 additions & 0 deletions indicator_keltner_channel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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.Neg(),
window: window,
}
}

func (kci keltnerChannelIndicator) Calculate(index int) big.Decimal {
if index <= kci.window-1 {
return big.ZERO
}

coefficient := big.NewFromInt(2).Mul(kci.mul)

return kci.ema.Calculate(index).Add(kci.atr.Calculate(index).Mul(coefficient))
}
Loading