Skip to content

Commit

Permalink
Average True Range (ATR) is added.
Browse files Browse the repository at this point in the history
  • Loading branch information
cinar committed Jan 2, 2024
1 parent c86cfee commit 6ae2703
Show file tree
Hide file tree
Showing 9 changed files with 546 additions and 5 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ The following list of indicators are currently supported by this package:
### 🎢 Volatility Indicators

- [Acceleration Bands](volatility/README.md#type-accelerationbands)
- Actual True Range (ATR)
- [Actual True Range (ATR)](volatility/README.md#type-atr)
- [Bollinger Band Width](volatility/README.md#type-bollingerbandwidth)
- [Bollinger Bands](volatility/README.md#type-bollingerbands)
- Chandelier Exit
Expand Down
18 changes: 18 additions & 0 deletions helper/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ The information provided on this project is strictly for informational purposes
- [func Multiply\[T Number\]\(ac, bc \<\-chan T\) \<\-chan T](<#Multiply>)
- [func MultiplyBy\[T Number\]\(c \<\-chan T, m T\) \<\-chan T](<#MultiplyBy>)
- [func Operate\[A any, B any, R any\]\(ac \<\-chan A, bc \<\-chan B, o func\(A, B\) R\) \<\-chan R](<#Operate>)
- [func Operate3\[A any, B any, C any, R any\]\(ac \<\-chan A, bc \<\-chan B, cc \<\-chan C, o func\(A, B, C\) R\) \<\-chan R](<#Operate3>)
- [func Pipe\[T any\]\(f \<\-chan T, t chan\<\- T\)](<#Pipe>)
- [func Pow\[T Number\]\(c \<\-chan T, y T\) \<\-chan T](<#Pow>)
- [func ReadFromCsvFile\[T any\]\(fileName string, hasHeader bool\) \(\<\-chan \*T, error\)](<#ReadFromCsvFile>)
Expand Down Expand Up @@ -537,6 +538,23 @@ add := helper.Operate(ac, bc, func(a, b int) int {
})
```

<a name="Operate3"></a>
## func [Operate3](<https://github.com/cinar/indicator/blob/v2/helper/operate3.go#L15>)

```go
func Operate3[A any, B any, C any, R any](ac <-chan A, bc <-chan B, cc <-chan C, o func(A, B, C) R) <-chan R
```

Operate3 applies the provided operate function to corresponding values from three numeric input channels and sends the resulting values to an output channel.

Example:

```
add := helper.Operate3(ac, bc, cc, func(a, b, c int) int {
return a + b + c
})
```

<a name="Pipe"></a>
## func [Pipe](<https://github.com/cinar/indicator/blob/v2/helper/pipe.go#L16>)

Expand Down
46 changes: 46 additions & 0 deletions helper/operate3.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) 2021-2023 Onur Cinar.
// The source code is provided under GNU AGPLv3 License.
// https://github.com/cinar/indicator

package helper

// Operate3 applies the provided operate function to corresponding values from three
// numeric input channels and sends the resulting values to an output channel.
//
// Example:
//
// add := helper.Operate3(ac, bc, cc, func(a, b, c int) int {
// return a + b + c
// })
func Operate3[A any, B any, C any, R any](ac <-chan A, bc <-chan B, cc <-chan C, o func(A, B, C) R) <-chan R {
rc := make(chan R)

go func() {
defer close(rc)

for {
an, ok := <-ac
if !ok {
break
}

bn, ok := <-bc
if !ok {
break
}

cn, ok := <-cc
if !ok {
break
}

rc <- o(an, bn, cn)
}

Drain(ac)
Drain(bc)
Drain(cc)
}()

return rc
}
76 changes: 76 additions & 0 deletions helper/operate3_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright (c) 2021-2023 Onur Cinar.
// The source code is provided under GNU AGPLv3 License.
// https://github.com/cinar/indicator

package helper_test

import (
"reflect"
"testing"

"github.com/cinar/indicator/helper"
)

func TestOperate3(t *testing.T) {
ac := helper.SliceToChan([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
bc := helper.SliceToChan([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
cc := helper.SliceToChan([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})

expected := []int{3, 6, 9, 12, 15, 18, 21, 24, 27, 30}

actual := helper.ChanToSlice(helper.Operate3(ac, bc, cc, func(a, b, c int) int {
return a + b + c
}))

if !reflect.DeepEqual(actual, expected) {
t.Fatalf("actual %v expected %v", actual, expected)
}
}

func TestOperate3FirstEnds(t *testing.T) {
ac := helper.SliceToChan([]int{1, 2, 3, 4, 5, 6, 7, 8})
bc := helper.SliceToChan([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
cc := helper.SliceToChan([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})

expected := []int{3, 6, 9, 12, 15, 18, 21, 24}

actual := helper.ChanToSlice(helper.Operate3(ac, bc, cc, func(a, b, c int) int {
return a + b + c
}))

if !reflect.DeepEqual(actual, expected) {
t.Fatalf("actual %v expected %v", actual, expected)
}
}

func TestOperate3SecondEnds(t *testing.T) {
ac := helper.SliceToChan([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
bc := helper.SliceToChan([]int{1, 2, 3, 4, 5, 6, 7, 8})
cc := helper.SliceToChan([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})

expected := []int{3, 6, 9, 12, 15, 18, 21, 24}

actual := helper.ChanToSlice(helper.Operate3(ac, bc, cc, func(a, b, c int) int {
return a + b + c
}))

if !reflect.DeepEqual(actual, expected) {
t.Fatalf("actual %v expected %v", actual, expected)
}
}

func TestOperate3ThirdEnds(t *testing.T) {
ac := helper.SliceToChan([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
bc := helper.SliceToChan([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
cc := helper.SliceToChan([]int{1, 2, 3, 4, 5, 6, 7, 8})

expected := []int{3, 6, 9, 12, 15, 18, 21, 24}

actual := helper.ChanToSlice(helper.Operate3(ac, bc, cc, func(a, b, c int) int {
return a + b + c
}))

if !reflect.DeepEqual(actual, expected) {
t.Fatalf("actual %v expected %v", actual, expected)
}
}
8 changes: 4 additions & 4 deletions helper/operate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ func TestOperateFirstEnds(t *testing.T) {
}

func TestOperateSecondEnds(t *testing.T) {
ac := helper.SliceToChan([]float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
bc := helper.SliceToChan([]float64{1, 2, 3, 4, 5, 6, 7, 8})
ac := helper.SliceToChan([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
bc := helper.SliceToChan([]int{1, 2, 3, 4, 5, 6, 7, 8})

expected := []float64{2, 4, 6, 8, 10, 12, 14, 16}
expected := []int{2, 4, 6, 8, 10, 12, 14, 16}

actual := helper.ChanToSlice(helper.Operate(ac, bc, func(a, b float64) float64 {
actual := helper.ChanToSlice(helper.Operate(ac, bc, func(a, b int) int {
return a + b
}))

Expand Down
55 changes: 55 additions & 0 deletions volatility/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ The information provided on this project is strictly for informational purposes
- [func NewAccelerationBands\[T helper.Number\]\(\) \*AccelerationBands\[T\]](<#NewAccelerationBands>)
- [func \(a \*AccelerationBands\[T\]\) Compute\(high, low, closing \<\-chan T\) \(\<\-chan T, \<\-chan T, \<\-chan T\)](<#AccelerationBands[T].Compute>)
- [func \(a \*AccelerationBands\[T\]\) IdlePeriod\(\) int](<#AccelerationBands[T].IdlePeriod>)
- [type Atr](<#Atr>)
- [func NewAtr\[T helper.Number\]\(\) \*Atr\[T\]](<#NewAtr>)
- [func \(a \*Atr\[T\]\) Compute\(highs, lows, closings \<\-chan T\) \<\-chan T](<#Atr[T].Compute>)
- [func \(a \*Atr\[T\]\) IdlePeriod\(\) int](<#Atr[T].IdlePeriod>)
- [type BollingerBandWidth](<#BollingerBandWidth>)
- [func NewBollingerBandWidth\[T helper.Number\]\(\) \*BollingerBandWidth\[T\]](<#NewBollingerBandWidth>)
- [func \(b \*BollingerBandWidth\[T\]\) Compute\(c \<\-chan T\) \<\-chan T](<#BollingerBandWidth[T].Compute>)
Expand Down Expand Up @@ -116,6 +120,57 @@ func (a *AccelerationBands[T]) IdlePeriod() int

IdlePeriod is the initial period that Acceleration Bands won't yield any results.

<a name="Atr"></a>
## type [Atr](<https://github.com/cinar/indicator/blob/v2/volatility/atr.go#L25-L28>)

Atr represents the configuration parameters for calculating the Average True Range \(ATR\). It is a technical analysis indicator that measures market volatility by decomposing the entire range of stock prices for that period.

```
TR = Max((High - Low), (High - Closing), (Closing - Low))
ATR = SMA TR
```

Example:

```
atr := volatility.NewAtr()
atr.Compute(values)
```

```go
type Atr[T helper.Number] struct {
// Sma is the SMA for the ATR.
Sma *trend.Sma[T]
}
```

<a name="NewAtr"></a>
### func [NewAtr](<https://github.com/cinar/indicator/blob/v2/volatility/atr.go#L31>)

```go
func NewAtr[T helper.Number]() *Atr[T]
```

NewAtr function initializes a new ATR instance with the default parameters.

<a name="Atr[T].Compute"></a>
### func \(\*Atr\[T\]\) [Compute](<https://github.com/cinar/indicator/blob/v2/volatility/atr.go#L38>)

```go
func (a *Atr[T]) Compute(highs, lows, closings <-chan T) <-chan T
```

Compute function takes a channel of numbers and computes the ATR over the specified period.

<a name="Atr[T].IdlePeriod"></a>
### func \(\*Atr\[T\]\) [IdlePeriod](<https://github.com/cinar/indicator/blob/v2/volatility/atr.go#L49>)

```go
func (a *Atr[T]) IdlePeriod() int
```

IdlePeriod is the initial period that Acceleration Bands won't yield any results.

<a name="BollingerBandWidth"></a>
## type [BollingerBandWidth](<https://github.com/cinar/indicator/blob/v2/volatility/bollinger_band_width.go#L24-L27>)

Expand Down
51 changes: 51 additions & 0 deletions volatility/atr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) 2021-2023 Onur Cinar.
// The source code is provided under GNU AGPLv3 License.
// https://github.com/cinar/indicator

package volatility

import (
"math"

"github.com/cinar/indicator/helper"
"github.com/cinar/indicator/trend"
)

// Atr represents the configuration parameters for calculating the Average True Range (ATR).
// It is a technical analysis indicator that measures market volatility by decomposing the
// entire range of stock prices for that period.
//
// TR = Max((High - Low), (High - Closing), (Closing - Low))
// ATR = SMA TR
//
// Example:
//
// atr := volatility.NewAtr()
// atr.Compute(values)
type Atr[T helper.Number] struct {
// Sma is the SMA for the ATR.
Sma *trend.Sma[T]
}

// NewAtr function initializes a new ATR instance with the default parameters.
func NewAtr[T helper.Number]() *Atr[T] {
return &Atr[T]{
Sma: trend.NewSma[T](),
}
}

// Compute function takes a channel of numbers and computes the ATR over the specified period.
func (a *Atr[T]) Compute(highs, lows, closings <-chan T) <-chan T {
tr := helper.Operate3(highs, lows, closings, func(high, low, closing T) T {
return T(math.Max(float64(high-low), math.Max(float64(high-closing), float64(closing-low))))
})

atr := a.Sma.Compute(tr)

return atr
}

// IdlePeriod is the initial period that Acceleration Bands won't yield any results.
func (a *Atr[T]) IdlePeriod() int {
return a.Sma.Period - 1
}
43 changes: 43 additions & 0 deletions volatility/atr_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) 2021-2023 Onur Cinar.
// The source code is provided under GNU AGPLv3 License.
// https://github.com/cinar/indicator

package volatility_test

import (
"testing"

"github.com/cinar/indicator/helper"
"github.com/cinar/indicator/volatility"
)

func TestAtr(t *testing.T) {
type Data struct {
High float64
Low float64
Close float64
Atr float64
}

input, err := helper.ReadFromCsvFile[Data]("testdata/atr.csv", true)
if err != nil {
t.Fatal(err)
}

inputs := helper.Duplicate(input, 4)
highs := helper.Map(inputs[0], func(d *Data) float64 { return d.High })
lows := helper.Map(inputs[1], func(d *Data) float64 { return d.Low })
closings := helper.Map(inputs[2], func(d *Data) float64 { return d.Close })
expected := helper.Map(inputs[3], func(d *Data) float64 { return d.Atr })

atr := volatility.NewAtr[float64]()
actual := atr.Compute(highs, lows, closings)
actual = helper.RoundDigits(actual, 2)

expected = helper.Skip(expected, atr.IdlePeriod())

err = helper.CheckEquals(actual, expected)
if err != nil {
t.Fatal(err)
}
}
Loading

0 comments on commit 6ae2703

Please sign in to comment.