Skip to content

Commit

Permalink
Kaufman's Adaptive Moving Average (KAMA) indicator is added (#151)
Browse files Browse the repository at this point in the history
# Describe Request

Kaufman's Adaptive Moving Average (KAMA) indicator is added.

Fixed #24 

# Change Type

Trend indicator.
  • Loading branch information
cinar committed Jun 10, 2024
1 parent 7fc7abb commit 902c276
Show file tree
Hide file tree
Showing 5 changed files with 347 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ The following list of indicators are currently supported by this package:
- [Hull Moving Average (HMA)](trend/README.md#type-hma)
- [Double Exponential Moving Average (DEMA)](trend/README.md#type-dema)
- [Exponential Moving Average (EMA)](trend/README.md#type-ema)
- [Kaufman's Adaptive Moving Average (KAMA)](trend/README.md#type-kama)
- [Mass Index (MI)](trend/README.md#type-massindex)
- [Moving Average Convergence Divergence (MACD)](trend/README.md#type-macd)
- [Moving Least Square (MLS)](trend/README.md#type-mls)
Expand Down
89 changes: 89 additions & 0 deletions trend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ The information provided on this project is strictly for informational purposes
- [func \(h \*Hma\[T\]\) Compute\(values \<\-chan T\) \<\-chan T](<#Hma[T].Compute>)
- [func \(h \*Hma\[T\]\) IdlePeriod\(\) int](<#Hma[T].IdlePeriod>)
- [func \(h \*Hma\[T\]\) String\(\) string](<#Hma[T].String>)
- [type Kama](<#Kama>)
- [func NewKama\[T helper.Number\]\(\) \*Kama\[T\]](<#NewKama>)
- [func \(k \*Kama\[T\]\) Compute\(closings \<\-chan T\) \<\-chan T](<#Kama[T].Compute>)
- [func \(k \*Kama\[T\]\) IdlePeriod\(\) int](<#Kama[T].IdlePeriod>)
- [func \(k \*Kama\[T\]\) String\(\) string](<#Kama[T].String>)
- [type Kdj](<#Kdj>)
- [func NewKdj\[T helper.Number\]\(\) \*Kdj\[T\]](<#NewKdj>)
- [func \(kdj \*Kdj\[T\]\) Compute\(high, low, closing \<\-chan T\) \(\<\-chan T, \<\-chan T, \<\-chan T\)](<#Kdj[T].Compute>)
Expand Down Expand Up @@ -160,6 +165,21 @@ const (
)
```

<a name="DefaultKamaErPeriod"></a>

```go
const (
// DefaultKamaErPeriod is the default Efficiency Ratio (ER) period of 10.
DefaultKamaErPeriod = 10

// DefaultKamaFastScPeriod is the default Fast Smoothing Constant (SC) period of 2.
DefaultKamaFastScPeriod = 2

// DefaultKamaSlowScPeriod is the default Slow Smoothing Constant (SC) period of 30.
DefaultKamaSlowScPeriod = 30
)
```

<a name="DefaultKdjMinMaxPeriod"></a>

```go
Expand Down Expand Up @@ -642,6 +662,75 @@ func (h *Hma[T]) String() string

String is the string representation of the HMA.

<a name="Kama"></a>
## type [Kama](<https://github.com/cinar/indicator/blob/master/trend/kama.go#L39-L48>)

Kama represents the parameters for calculating the Kaufman's Adaptive Moving Average \(KAMA\). It is a type of moving average that adapts to market noise or volatility. It tracks prices closely during periods of small price swings and low noise.

```
Direction = Abs(Close - Previous Close Period Ago)
Volatility = MovingSum(Period, Abs(Close - Previous Close))
Efficiency Ratio (ER) = Direction / Volatility
Smoothing Constant (SC) = (ER * (2/(Fast + 1) - 2/(Slow + 1)) + (2/(Slow + 1)))^2
KAMA = Previous KAMA + SC * (Price - Previous KAMA)
```

Example:

```
kama := trend.NewKama[float64]()
result := kama.Compute(c)
```

```go
type Kama[T helper.Number] struct {
// ErPeriod is the Efficiency Ratio time period.
ErPeriod int

// FastScPeriod is the Fast Smoothing Constant time period.
FastScPeriod int

// SlowScPeriod is the Slow Smoothing Constant time period.
SlowScPeriod int
}
```

<a name="NewKama"></a>
### func [NewKama](<https://github.com/cinar/indicator/blob/master/trend/kama.go#L51>)

```go
func NewKama[T helper.Number]() *Kama[T]
```

NewKama function initializes a new KAMA instance with the default parameters.

<a name="Kama[T].Compute"></a>
### func \(\*Kama\[T\]\) [Compute](<https://github.com/cinar/indicator/blob/master/trend/kama.go#L60>)

```go
func (k *Kama[T]) Compute(closings <-chan T) <-chan T
```

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

<a name="Kama[T].IdlePeriod"></a>
### func \(\*Kama\[T\]\) [IdlePeriod](<https://github.com/cinar/indicator/blob/master/trend/kama.go#L132>)

```go
func (k *Kama[T]) IdlePeriod() int
```

IdlePeriod is the initial period that KAMA yield any results.

<a name="Kama[T].String"></a>
### func \(\*Kama\[T\]\) [String](<https://github.com/cinar/indicator/blob/master/trend/kama.go#L137>)

```go
func (k *Kama[T]) String() string
```

String is the string representation of the KAMA.

<a name="Kdj"></a>
## type [Kdj](<https://github.com/cinar/indicator/blob/master/trend/kdj.go#L41-L53>)

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

package trend

import (
"fmt"
"log"

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

const (
// DefaultKamaErPeriod is the default Efficiency Ratio (ER) period of 10.
DefaultKamaErPeriod = 10

// DefaultKamaFastScPeriod is the default Fast Smoothing Constant (SC) period of 2.
DefaultKamaFastScPeriod = 2

// DefaultKamaSlowScPeriod is the default Slow Smoothing Constant (SC) period of 30.
DefaultKamaSlowScPeriod = 30
)

// Kama represents the parameters for calculating the Kaufman's Adaptive Moving Average (KAMA).
// It is a type of moving average that adapts to market noise or volatility. It tracks prices
// closely during periods of small price swings and low noise.
//
// Direction = Abs(Close - Previous Close Period Ago)
// Volatility = MovingSum(Period, Abs(Close - Previous Close))
// Efficiency Ratio (ER) = Direction / Volatility
// Smoothing Constant (SC) = (ER * (2/(Fast + 1) - 2/(Slow + 1)) + (2/(Slow + 1)))^2
// KAMA = Previous KAMA + SC * (Price - Previous KAMA)
//
// Example:
//
// kama := trend.NewKama[float64]()
// result := kama.Compute(c)
type Kama[T helper.Number] struct {
// ErPeriod is the Efficiency Ratio time period.
ErPeriod int

// FastScPeriod is the Fast Smoothing Constant time period.
FastScPeriod int

// SlowScPeriod is the Slow Smoothing Constant time period.
SlowScPeriod int
}

// NewKama function initializes a new KAMA instance with the default parameters.
func NewKama[T helper.Number]() *Kama[T] {
return &Kama[T]{
ErPeriod: DefaultKamaErPeriod,
FastScPeriod: DefaultKamaFastScPeriod,
SlowScPeriod: DefaultKamaSlowScPeriod,
}
}

// Compute function takes a channel of numbers and computes the KAMA over the specified period.
func (k *Kama[T]) Compute(closings <-chan T) <-chan T {
closingsSplice := helper.Duplicate(closings, 3)

// Direction = Abs(Close - Previous Close Period Ago)
directions := helper.Abs(
helper.Change(closingsSplice[0], k.ErPeriod),
)

// Volatility = MovingSum(Period, Abs(Close - Previous Close))
movingSum := NewMovingSumWithPeriod[T](k.ErPeriod)
volatilitys := movingSum.Compute(
helper.Abs(
helper.Change(closingsSplice[1], 1),
),
)

// Efficiency Ratio (ER) = Direction / Volatility
ers := helper.Divide(directions, volatilitys)

// Smoothing Constant (SC) = (ER * (2/(Slow + 1) - 2/(Fast + 1)) + (2/(Slow + 1)))^2
fastSc := T(2.0) / T(k.FastScPeriod+1)
slowSc := T(2.0) / T(k.SlowScPeriod+1)

log.Printf("fastSc=%v", fastSc)
log.Printf("slowSc=%v", slowSc)

scs := helper.Pow(
helper.IncrementBy(
helper.MultiplyBy(
ers,
(fastSc-slowSc),
),
slowSc,
),
2,
)

// KAMA = Previous KAMA + SC * (Price - Previous KAMA)
closingsSplice[2] = helper.Skip(closingsSplice[2], k.ErPeriod-1)

kama := make(chan T)

go func() {
defer close(kama)
defer helper.Drain(scs)
defer helper.Drain(closingsSplice[2])

prevKama, ok := <-closingsSplice[2]
if !ok {
return
}

for {
closing, ok := <-closingsSplice[2]
if !ok {
break
}

sc := <-scs

prevKama = prevKama + sc*(closing-prevKama)
kama <- prevKama
}
}()

return kama
}

// IdlePeriod is the initial period that KAMA yield any results.
func (k *Kama[T]) IdlePeriod() int {
return k.ErPeriod
}

// String is the string representation of the KAMA.
func (k *Kama[T]) String() string {
return fmt.Sprintf("KAMA(%d,%d,%d)",
k.ErPeriod,
k.FastScPeriod,
k.SlowScPeriod,
)
}
67 changes: 67 additions & 0 deletions trend/kama_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright (c) 2021-2024 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/v2/helper"
"github.com/cinar/indicator/v2/trend"
)

func TestKama(t *testing.T) {
type Data struct {
Close float64
Kama float64
}

input, err := helper.ReadFromCsvFile[Data]("testdata/kama.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.Kama })

kama := trend.NewKama[float64]()
actual := kama.Compute(closing)

actual = helper.RoundDigits(actual, 2)
expected = helper.Skip(expected, kama.IdlePeriod())

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

func TestKamaEmpty(t *testing.T) {
input := helper.SliceToChan([]float64{})
expected := helper.SliceToChan([]float64{})

kama := trend.NewKama[float64]()
actual := kama.Compute(input)

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

func TestKamaString(t *testing.T) {
expected := "KAMA(1,2,3)"

kama := trend.NewKama[float64]()
kama.ErPeriod = 1
kama.FastScPeriod = 2
kama.SlowScPeriod = 3

actual := kama.String()

if actual != expected {
t.Fatalf("actual %v expected %v", actual, expected)
}
}
50 changes: 50 additions & 0 deletions trend/testdata/kama.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
Close,Kama
110.46,0
109.80,0
110.17,0
109.82,0
110.15,0
109.31,0
109.05,0
107.94,0
107.76,0
109.24,0
109.40,109.24
108.50,109.22
107.96,109.12
108.55,109.10
108.85,109.09
110.44,109.12
109.89,109.14
110.70,109.28
110.79,109.44
110.22,109.46
110.00,109.47
109.27,109.46
106.69,109.39
107.07,109.32
107.92,109.29
107.95,109.18
107.70,109.08
107.97,108.95
106.09,108.42
106.03,108.02
107.65,108.00
109.54,108.01
110.26,108.26
110.38,108.48
111.94,108.91
113.59,109.67
113.98,110.49
113.91,111.11
112.62,111.46
112.20,111.61
111.10,111.57
110.18,111.55
111.13,111.54
111.55,111.54
112.08,111.55
111.95,111.57
111.60,111.57
111.39,111.55
112.25,111.56

0 comments on commit 902c276

Please sign in to comment.