diff --git a/README.md b/README.md index 1d2153d..f2f56aa 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ The following list of indicators are currently supported by this package: - [Since Change](helper/README.md#func-since) - [Triple Exponential Moving Average (TEMA)](trend/README.md#type-tema) - [Triangular Moving Average (TRIMA)](trend/README.md#type-trima) -- Triple Exponential Average (TRIX) +- [Triple Exponential Average (TRIX)](trend/README.md#type-trix) - [Typical Price](trend/README.md#type-typicalprice) - [Volume Weighted Moving Average (VWMA)](trend/README.md#type-vwma) - Vortex Indicator diff --git a/helper/README.md b/helper/README.md index 593d916..7911b3e 100644 --- a/helper/README.md +++ b/helper/README.md @@ -33,6 +33,7 @@ The information provided on this project is strictly for informational purposes - [func ChanToSlice\[T any\]\(c \<\-chan T\) \[\]T](<#ChanToSlice>) - [func Change\[T Number\]\(c \<\-chan T, before int\) \<\-chan T](<#Change>) - [func ChangePercent\[T Number\]\(c \<\-chan T, before int\) \<\-chan T](<#ChangePercent>) +- [func ChangeRatio\[T Number\]\(c \<\-chan T, before int\) \<\-chan T](<#ChangeRatio>) - [func CheckEquals\[T comparable\]\(inputs ...\<\-chan T\) error](<#CheckEquals>) - [func DecrementBy\[T Number\]\(c \<\-chan T, d T\) \<\-chan T](<#DecrementBy>) - [func Divide\[T Number\]\(ac, bc \<\-chan T\) \<\-chan T](<#Divide>) @@ -244,6 +245,23 @@ actual := helper.ChangePercent(c, 2)) fmt.Println(helper.ChanToSlice(actual)) // [400, 150, 60, -60, -87.5, -50, 200, 300] ``` + +## func [ChangeRatio]() + +```go +func ChangeRatio[T Number](c <-chan T, before int) <-chan T +``` + +ChangeRatio calculates the ratio change between the current value and the value N positions before. + +Example: + +``` +c := helper.ChanToSlice([]float64{1, 2, 5, 5, 8, 2, 1, 1, 3, 4}) +actual := helper.ChangeRatio(c, 2)) +fmt.Println(helper.ChanToSlice(actual)) // [400, 150, 60, -60, -87.5, -50, 200, 300] +``` + ## func [CheckEquals]() diff --git a/helper/change_percent.go b/helper/change_percent.go index 8a1b942..95489fe 100644 --- a/helper/change_percent.go +++ b/helper/change_percent.go @@ -13,7 +13,5 @@ package helper // actual := helper.ChangePercent(c, 2)) // fmt.Println(helper.ChanToSlice(actual)) // [400, 150, 60, -60, -87.5, -50, 200, 300] func ChangePercent[T Number](c <-chan T, before int) <-chan T { - cs := Duplicate(c, 2) - cs[1] = Buffered(cs[1], before) - return MultiplyBy(Divide(Change(cs[0], before), cs[1]), 100) + return MultiplyBy(ChangeRatio(c, before), 100) } diff --git a/helper/change_ratio.go b/helper/change_ratio.go new file mode 100644 index 0000000..ec173de --- /dev/null +++ b/helper/change_ratio.go @@ -0,0 +1,19 @@ +// Copyright (c) 2021-2023 Onur Cinar. +// The source code is provided under GNU AGPLv3 License. +// https://github.com/cinar/indicator + +package helper + +// ChangeRatio calculates the ratio change between the current +// value and the value N positions before. +// +// Example: +// +// c := helper.ChanToSlice([]float64{1, 2, 5, 5, 8, 2, 1, 1, 3, 4}) +// actual := helper.ChangeRatio(c, 2)) +// fmt.Println(helper.ChanToSlice(actual)) // [400, 150, 60, -60, -87.5, -50, 200, 300] +func ChangeRatio[T Number](c <-chan T, before int) <-chan T { + cs := Duplicate(c, 2) + cs[1] = Buffered(cs[1], before) + return Divide(Change(cs[0], before), cs[1]) +} diff --git a/helper/change_ratio_test.go b/helper/change_ratio_test.go new file mode 100644 index 0000000..6550d4e --- /dev/null +++ b/helper/change_ratio_test.go @@ -0,0 +1,23 @@ +// Copyright (c) 2021-2023 Onur Cinar. +// The source code is provided under GNU AGPLv3 License. +// https://github.com/cinar/indicator + +package helper_test + +import ( + "testing" + + "github.com/cinar/indicator/helper" +) + +func TestChangeRatio(t *testing.T) { + input := helper.SliceToChan([]float64{1, 2, 5, 5, 8, 2, 1, 1, 3, 4}) + expected := helper.SliceToChan([]float64{4, 1.5, 0.6, -0.6, -0.875, -0.5, 2, 3}) + + actual := helper.ChangeRatio(input, 2) + + err := helper.CheckEquals(actual, expected) + if err != nil { + t.Fatal(err) + } +} diff --git a/trend/README.md b/trend/README.md index 1b71ad7..c5fc161 100644 --- a/trend/README.md +++ b/trend/README.md @@ -40,6 +40,7 @@ The information provided on this project is strictly for informational purposes - [func \(d \*Dema\[T\]\) IdlePeriod\(\) int](<#Dema[T].IdlePeriod>) - [type Ema](<#Ema>) - [func NewEma\[T helper.Number\]\(\) \*Ema\[T\]](<#NewEma>) + - [func NewEmaWithPeriod\[T helper.Number\]\(period int\) \*Ema\[T\]](<#NewEmaWithPeriod>) - [func \(ema \*Ema\[T\]\) Compute\(c \<\-chan T\) \<\-chan T](<#Ema[T].Compute>) - [type Kdj](<#Kdj>) - [func NewKdj\[T helper.Number\]\(\) \*Kdj\[T\]](<#NewKdj>) @@ -76,6 +77,10 @@ The information provided on this project is strictly for informational purposes - [func NewTrima\[T helper.Number\]\(\) \*Trima\[T\]](<#NewTrima>) - [func \(t \*Trima\[T\]\) Compute\(c \<\-chan T\) \<\-chan T](<#Trima[T].Compute>) - [func \(t \*Trima\[T\]\) IdlePeriod\(\) int](<#Trima[T].IdlePeriod>) +- [type Trix](<#Trix>) + - [func NewTrix\[T helper.Number\]\(\) \*Trix\[T\]](<#NewTrix>) + - [func \(t \*Trix\[T\]\) Compute\(c \<\-chan T\) \<\-chan T](<#Trix[T].Compute>) + - [func \(t \*Trix\[T\]\) IdlePeriod\(\) int](<#Trix[T].IdlePeriod>) - [type TypicalPrice](<#TypicalPrice>) - [func NewTypicalPrice\[T helper.Number\]\(\) \*TypicalPrice\[T\]](<#NewTypicalPrice>) - [func \(\*TypicalPrice\[T\]\) Compute\(high, low, closing \<\-chan T\) \<\-chan T](<#TypicalPrice[T].Compute>) @@ -199,6 +204,15 @@ const ( ) ``` + + +```go +const ( + // DefaultTrixPeriod is the default time period for TRIX. + DefaultTrixPeriod = 15 +) +``` + ```go @@ -398,7 +412,7 @@ func (d *Dema[T]) IdlePeriod() int IdlePeriod is the initial period that DEMA won't yield any results. -## type [Ema]() +## type [Ema]() Ema represents the parameters for calculating the Exponential Moving Average. @@ -422,7 +436,7 @@ type Ema[T helper.Number] struct { ``` -### func [NewEma]() +### func [NewEma]() ```go func NewEma[T helper.Number]() *Ema[T] @@ -430,8 +444,17 @@ func NewEma[T helper.Number]() *Ema[T] NewEma function initializes a new EMA instance with the default parameters. + +### func [NewEmaWithPeriod]() + +```go +func NewEmaWithPeriod[T helper.Number](period int) *Ema[T] +``` + +NewEmaWithPeriod function initializes a new EMA instance with the given period. + -### func \(\*Ema\[T\]\) [Compute]() +### func \(\*Ema\[T\]\) [Compute]() ```go func (ema *Ema[T]) Compute(c <-chan T) <-chan T @@ -878,6 +901,59 @@ func (t *Trima[T]) IdlePeriod() int IdlePeriod is the initial period that TRIMA won't yield any results. + +## type [Trix]() + +Trix represents the configuration parameters for calculating the Triple Exponential Average \(TRIX\). TRIX indicator is an oscillator used to identify oversold and overbought markets, and it can also be used as a momentum indicator. Like many oscillators, TRIX oscillates around a zero line. + +``` +EMA1 = EMA(period, values) +EMA2 = EMA(period, EMA1) +EMA3 = EMA(period, EMA2) +TRIX = (EMA3 - Previous EMA3) / Previous EMA3 +``` + +Example: + +``` +trix := trend.NewTrix[float64]() +result := trix.Compute(values) +``` + +```go +type Trix[T helper.Number] struct { + // Time period. + Period int +} +``` + + +### func [NewTrix]() + +```go +func NewTrix[T helper.Number]() *Trix[T] +``` + +NewTrix function initializes a new TRIX instance with the default parameters. + + +### func \(\*Trix\[T\]\) [Compute]() + +```go +func (t *Trix[T]) Compute(c <-chan T) <-chan T +``` + +Compute function takes a channel of numbers and computes the TRIX and the signal line. + + +### func \(\*Trix\[T\]\) [IdlePeriod]() + +```go +func (t *Trix[T]) IdlePeriod() int +``` + +IdlePeriod is the initial period that TRIX won't yield any results. + ## type [TypicalPrice]() diff --git a/trend/ema.go b/trend/ema.go index 02a90aa..dbe8de9 100644 --- a/trend/ema.go +++ b/trend/ema.go @@ -14,8 +14,7 @@ const ( DefaultEmaSmoothing = 2 ) -// Ema represents the parameters for calculating -// the Exponential Moving Average. +// Ema represents the parameters for calculating the Exponential Moving Average. // // Example: // @@ -31,8 +30,7 @@ type Ema[T helper.Number] struct { Smoothing T } -// NewEma function initializes a new EMA instance -// with the default parameters. +// NewEma function initializes a new EMA instance with the default parameters. func NewEma[T helper.Number]() *Ema[T] { return &Ema[T]{ Period: DefaultEmaPeriod, @@ -40,8 +38,15 @@ func NewEma[T helper.Number]() *Ema[T] { } } -// Compute function takes a channel of numbers and computes the EMA -// over the specified period. +// NewEmaWithPeriod function initializes a new EMA instance with the given period. +func NewEmaWithPeriod[T helper.Number](period int) *Ema[T] { + ema := NewEma[T]() + ema.Period = period + + return ema +} + +// Compute function takes a channel of numbers and computes the EMA over the specified period. func (ema *Ema[T]) Compute(c <-chan T) <-chan T { result := make(chan T, cap(c)) diff --git a/trend/ema_test.go b/trend/ema_test.go index ffadcf8..fc2c11c 100644 --- a/trend/ema_test.go +++ b/trend/ema_test.go @@ -29,8 +29,7 @@ func TestEma(t *testing.T) { 23.23, 23.08, 22.92, } - ema := trend.NewEma[float64]() - ema.Period = 10 + ema := trend.NewEmaWithPeriod[float64](10) ema.Smoothing = 2 actual := helper.ChanToSlice(helper.RoundDigits(ema.Compute(input), 2)) diff --git a/trend/testdata/trix.csv b/trend/testdata/trix.csv new file mode 100644 index 0000000..5510029 --- /dev/null +++ b/trend/testdata/trix.csv @@ -0,0 +1,252 @@ +Close,Trix +318.600006,0 +315.839996,0 +316.149994,0 +310.570007,0 +307.779999,0 +305.820007,0 +305.98999,0 +306.390015,0 +311.450012,0 +312.329987,0 +309.290009,0 +301.910004,0 +300,0 +300.029999,0 +302,0 +307.820007,0 +302.690002,0 +306.48999,0 +305.549988,0 +303.429993,0 +309.059998,0 +308.899994,0 +309.910004,0 +314.549988,0 +312.899994,0 +318.690002,0 +315.529999,0 +316.350006,0 +320.369995,0 +318.929993,0 +317.640015,0 +314.859985,0 +308.299988,0 +305.230011,0 +309.869995,0 +310.420013,0 +311.299988,0 +311.899994,0 +310.950012,0 +309.170013,0 +307.329987,0 +311.519989,0 +310.570007,0 +311.859985,0.0002 +308.51001,0.0001 +308.429993,0.0001 +312.970001,0.0001 +308.480011,0 +307.209991,-0 +309.890015,-0.0001 +313.73999,-0.0001 +310.790009,-0.0001 +309.630005,-0.0001 +308.179993,-0.0001 +308.23999,-0.0001 +302.720001,-0.0002 +303.160004,-0.0003 +303.070007,-0.0003 +304.019989,-0.0004 +304.660004,-0.0005 +305.179993,-0.0006 +304.619995,-0.0006 +307.75,-0.0006 +312.450012,-0.0006 +316.970001,-0.0005 +311.119995,-0.0004 +311.369995,-0.0003 +304.820007,-0.0003 +303.630005,-0.0003 +302.880005,-0.0003 +305.329987,-0.0003 +297.880005,-0.0004 +302.01001,-0.0004 +293.51001,-0.0006 +301.059998,-0.0007 +303.850006,-0.0007 +299.730011,-0.0008 +298.369995,-0.0009 +298.920013,-0.0009 +302.140015,-0.001 +302.320007,-0.001 +305.299988,-0.001 +305.079987,-0.0009 +308.769989,-0.0008 +310.309998,-0.0007 +309.070007,-0.0005 +310.390015,-0.0004 +312.51001,-0.0002 +312.619995,-0.0001 +313.700012,0.0001 +314.549988,0.0003 +318.049988,0.0005 +319.73999,0.0007 +323.790009,0.0009 +324.630005,0.0011 +323.089996,0.0013 +323.820007,0.0015 +324.329987,0.0017 +326.049988,0.0019 +324.339996,0.002 +320.529999,0.0021 +326.230011,0.0021 +328.549988,0.0022 +330.170013,0.0023 +325.859985,0.0023 +323.220001,0.0023 +320,0.0022 +323.880005,0.0022 +326.140015,0.0021 +324.869995,0.002 +322.98999,0.0019 +322.640015,0.0018 +322.48999,0.0017 +323.529999,0.0016 +323.75,0.0015 +327.390015,0.0014 +329.76001,0.0014 +330.390015,0.0014 +329.130005,0.0013 +323.109985,0.0013 +320.200012,0.0012 +319.019989,0.0011 +320.600006,0.0009 +322.190002,0.0008 +321.079987,0.0007 +323.119995,0.0006 +329.480011,0.0005 +328.579987,0.0005 +333.410004,0.0006 +335.420013,0.0006 +335.950012,0.0007 +335.290009,0.0008 +333.600006,0.0009 +336.390015,0.001 +335.899994,0.0011 +339.820007,0.0012 +338.309998,0.0013 +338.670013,0.0014 +338.609985,0.0015 +336.959991,0.0016 +335.25,0.0016 +334.119995,0.0016 +335.339996,0.0015 +334.149994,0.0015 +336.910004,0.0014 +341,0.0014 +342,0.0014 +341.559998,0.0014 +341.459991,0.0014 +340.899994,0.0014 +341.130005,0.0014 +343.369995,0.0014 +345.350006,0.0014 +343.540009,0.0014 +341.089996,0.0014 +344.25,0.0014 +345.339996,0.0014 +342.429993,0.0014 +346.609985,0.0014 +345.76001,0.0014 +349.630005,0.0014 +347.579987,0.0014 +349.799988,0.0014 +349.309998,0.0015 +349.809998,0.0015 +351.959991,0.0015 +352.26001,0.0015 +351.190002,0.0015 +353.809998,0.0016 +349.98999,0.0016 +362.579987,0.0016 +363.730011,0.0017 +358.019989,0.0018 +356.980011,0.0018 +358.350006,0.0019 +358.480011,0.0019 +354.5,0.0019 +354.109985,0.0019 +353.190002,0.0018 +352.559998,0.0017 +352.089996,0.0016 +350.570007,0.0015 +354.26001,0.0013 +354.299988,0.0012 +355.929993,0.0012 +355.549988,0.0011 +358.290009,0.001 +361.059998,0.001 +360.200012,0.001 +362.459991,0.001 +360.470001,0.001 +361.670013,0.0011 +361.799988,0.0011 +363.149994,0.0011 +365.519989,0.0011 +367.779999,0.0012 +367.820007,0.0013 +369.5,0.0013 +367.859985,0.0014 +370.429993,0.0015 +370.480011,0.0015 +366.820007,0.0015 +363.279999,0.0015 +360.160004,0.0015 +361.709991,0.0014 +359.420013,0.0013 +357.779999,0.0011 +357.059998,0.001 +350.299988,0.0008 +348.079987,0.0006 +343.040009,0.0003 +343.690002,0 +345.059998,-0.0003 +346.339996,-0.0005 +345.450012,-0.0007 +348.559998,-0.0009 +348.429993,-0.0011 +345.660004,-0.0012 +345.089996,-0.0013 +346.230011,-0.0014 +345.390015,-0.0014 +340.890015,-0.0015 +338.660004,-0.0016 +335.859985,-0.0017 +336.839996,-0.0018 +338.630005,-0.0019 +336.899994,-0.0019 +336.160004,-0.002 +331.709991,-0.0021 +337.410004,-0.0021 +341.329987,-0.0021 +343.75,-0.002 +349.019989,-0.0019 +351.809998,-0.0018 +346.630005,-0.0016 +346.170013,-0.0014 +346.299988,-0.0012 +348.179993,-0.001 +350.559998,-0.0009 +350.01001,-0.0007 +354.25,-0.0005 +356.790009,-0.0003 +359.859985,-0 +358.929993,0.0002 +361.329987,0.0004 +361,0.0006 +361.799988,0.0009 +362.679993,0.001 +361.339996,0.0012 +360.049988,0.0013 +358.690002,0.0014 diff --git a/trend/trix.go b/trend/trix.go new file mode 100644 index 0000000..d5a1b72 --- /dev/null +++ b/trend/trix.go @@ -0,0 +1,61 @@ +// Copyright (c) 2021-2023 Onur Cinar. +// The source code is provided under GNU AGPLv3 License. +// https://github.com/cinar/indicator + +package trend + +import ( + "github.com/cinar/indicator/helper" +) + +const ( + // DefaultTrixPeriod is the default time period for TRIX. + DefaultTrixPeriod = 15 +) + +// Trix represents the configuration parameters for calculating the Triple Exponential Average (TRIX). +// TRIX indicator is an oscillator used to identify oversold and overbought markets, and it can also +// be used as a momentum indicator. Like many oscillators, TRIX oscillates around a zero line. +// +// EMA1 = EMA(period, values) +// EMA2 = EMA(period, EMA1) +// EMA3 = EMA(period, EMA2) +// TRIX = (EMA3 - Previous EMA3) / Previous EMA3 +// +// Example: +// +// trix := trend.NewTrix[float64]() +// result := trix.Compute(values) +type Trix[T helper.Number] struct { + // Time period. + Period int +} + +// NewTrix function initializes a new TRIX instance with the default parameters. +func NewTrix[T helper.Number]() *Trix[T] { + return &Trix[T]{ + Period: DefaultTrixPeriod, + } +} + +// Compute function takes a channel of numbers and computes the TRIX and the signal line. +func (t *Trix[T]) Compute(c <-chan T) <-chan T { + ema1 := NewEmaWithPeriod[T](t.Period) + ema2 := NewEmaWithPeriod[T](t.Period) + ema3 := NewEmaWithPeriod[T](t.Period) + + emas := ema3.Compute( + ema2.Compute( + ema1.Compute(c), + ), + ) + + trix := helper.ChangeRatio[T](emas, 1) + + return trix +} + +// IdlePeriod is the initial period that TRIX won't yield any results. +func (t *Trix[T]) IdlePeriod() int { + return (t.Period * 3) - 3 + 1 +} diff --git a/trend/trix_test.go b/trend/trix_test.go new file mode 100644 index 0000000..d801997 --- /dev/null +++ b/trend/trix_test.go @@ -0,0 +1,40 @@ +// Copyright (c) 2021-2023 Onur Cinar. +// The source code is provided under GNU AGPLv3 License. +// https://github.com/cinar/indicator + +package trend_test + +import ( + "testing" + + "github.com/cinar/indicator/helper" + "github.com/cinar/indicator/trend" +) + +func TestTrix(t *testing.T) { + type Data struct { + Close float64 + Trix float64 + } + + input, err := helper.ReadFromCsvFile[Data]("testdata/trix.csv", true) + if err != nil { + t.Fatal(err) + } + + inputs := helper.Duplicate(input, 2) + closing := helper.Map(inputs[0], func(d *Data) float64 { return d.Close }) + expected := helper.Map(inputs[1], func(d *Data) float64 { return d.Trix }) + + trix := trend.NewTrix[float64]() + + actual := trix.Compute(closing) + actual = helper.RoundDigits(actual, 4) + + expected = helper.Skip(expected, trix.IdlePeriod()) + + err = helper.CheckEquals(actual, expected) + if err != nil { + t.Fatal(err) + } +}