From 519284526f8d267522b4030386cf57d425d6585a Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Sun, 9 Jun 2024 18:43:04 -0700 Subject: [PATCH 1/3] Kaufman's Adaptive Moving Average (KAMA) strategy is added. --- strategy/trend/README.md | 63 ++++++ strategy/trend/kama_strategy.go | 114 ++++++++++ strategy/trend/kama_strategy_test.go | 56 +++++ strategy/trend/testdata/kama_strategy.csv | 252 ++++++++++++++++++++++ trend/README.md | 16 +- trend/kama.go | 15 +- 6 files changed, 510 insertions(+), 6 deletions(-) create mode 100644 strategy/trend/kama_strategy.go create mode 100644 strategy/trend/kama_strategy_test.go create mode 100644 strategy/trend/testdata/kama_strategy.csv diff --git a/strategy/trend/README.md b/strategy/trend/README.md index 50085ed..cde0f85 100644 --- a/strategy/trend/README.md +++ b/strategy/trend/README.md @@ -57,6 +57,12 @@ The information provided on this project is strictly for informational purposes - [func \(t \*GoldenCrossStrategy\) Compute\(c \<\-chan \*asset.Snapshot\) \<\-chan strategy.Action](<#GoldenCrossStrategy.Compute>) - [func \(\*GoldenCrossStrategy\) Name\(\) string](<#GoldenCrossStrategy.Name>) - [func \(t \*GoldenCrossStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#GoldenCrossStrategy.Report>) +- [type KamaStrategy](<#KamaStrategy>) + - [func NewKamaStrategy\(\) \*KamaStrategy](<#NewKamaStrategy>) + - [func NewKamaStrategyWith\(erPeriod, fastScPeriod, slowScPeriod int\) \*KamaStrategy](<#NewKamaStrategyWith>) + - [func \(k \*KamaStrategy\) Compute\(snapshots \<\-chan \*asset.Snapshot\) \<\-chan strategy.Action](<#KamaStrategy.Compute>) + - [func \(k \*KamaStrategy\) Name\(\) string](<#KamaStrategy.Name>) + - [func \(k \*KamaStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#KamaStrategy.Report>) - [type KdjStrategy](<#KdjStrategy>) - [func NewKdjStrategy\(\) \*KdjStrategy](<#NewKdjStrategy>) - [func \(kdj \*KdjStrategy\) Compute\(c \<\-chan \*asset.Snapshot\) \<\-chan strategy.Action](<#KdjStrategy.Compute>) @@ -484,6 +490,63 @@ func (t *GoldenCrossStrategy) Report(c <-chan *asset.Snapshot) *helper.Report Report processes the provided asset snapshots and generates a report annotated with the recommended actions. + +## type [KamaStrategy]() + +KamaStrategy represents the configuration parameters for calculating the KAMA strategy. A closing price crossing above the KAMA suggests a bullish trend, while crossing below the KAMA indicats a bearish trend. + +```go +type KamaStrategy struct { + // Kama represents the configuration parameters for calculating the Kaufman's Adaptive Moving Average (KAMA). + Kama *trend.Kama[float64] +} +``` + + +### func [NewKamaStrategy]() + +```go +func NewKamaStrategy() *KamaStrategy +``` + +NewKamaStrategy function initializes a new KAMA strategy instance. + + +### func [NewKamaStrategyWith]() + +```go +func NewKamaStrategyWith(erPeriod, fastScPeriod, slowScPeriod int) *KamaStrategy +``` + +NewKamaStrategyWith function initializes a new KAMA strategy instance with the given parameters. + + +### func \(\*KamaStrategy\) [Compute]() + +```go +func (k *KamaStrategy) Compute(snapshots <-chan *asset.Snapshot) <-chan strategy.Action +``` + +Compute processes the provided asset snapshots and generates a stream of actionable recommendations. + + +### func \(\*KamaStrategy\) [Name]() + +```go +func (k *KamaStrategy) Name() string +``` + +Name returns the name of the strategy. + + +### func \(\*KamaStrategy\) [Report]() + +```go +func (k *KamaStrategy) Report(c <-chan *asset.Snapshot) *helper.Report +``` + +Report processes the provided asset snapshots and generates a report annotated with the recommended actions. + ## type [KdjStrategy]() diff --git a/strategy/trend/kama_strategy.go b/strategy/trend/kama_strategy.go new file mode 100644 index 0000000..f7ad66c --- /dev/null +++ b/strategy/trend/kama_strategy.go @@ -0,0 +1,114 @@ +// Copyright (c) 2021-2024 Onur Cinar. +// The source code is provided under GNU AGPLv3 License. +// https://github.com/cinar/indicator + +package trend + +import ( + "github.com/cinar/indicator/v2/asset" + "github.com/cinar/indicator/v2/helper" + "github.com/cinar/indicator/v2/strategy" + "github.com/cinar/indicator/v2/trend" +) + +// KamaStrategy represents the configuration parameters for calculating the KAMA strategy. A closing price crossing +// above the KAMA suggests a bullish trend, while crossing below the KAMA indicats a bearish trend. +type KamaStrategy struct { + // Kama represents the configuration parameters for calculating the Kaufman's Adaptive Moving Average (KAMA). + Kama *trend.Kama[float64] +} + +// NewKamaStrategy function initializes a new KAMA strategy instance. +func NewKamaStrategy() *KamaStrategy { + return NewKamaStrategyWith( + trend.DefaultKamaErPeriod, + trend.DefaultKamaFastScPeriod, + trend.DefaultKamaSlowScPeriod, + ) +} + +// NewKamaStrategyWith function initializes a new KAMA strategy instance with the given parameters. +func NewKamaStrategyWith(erPeriod, fastScPeriod, slowScPeriod int) *KamaStrategy { + return &KamaStrategy{ + Kama: trend.NewKamaWith[float64]( + erPeriod, + fastScPeriod, + slowScPeriod, + ), + } +} + +// Name returns the name of the strategy. +func (k *KamaStrategy) Name() string { + return k.Kama.String() +} + +// Compute processes the provided asset snapshots and generates a stream of actionable recommendations. +func (k *KamaStrategy) Compute(snapshots <-chan *asset.Snapshot) <-chan strategy.Action { + closingsSplice := helper.Duplicate(asset.SnapshotsAsClosings(snapshots), 2) + closingsSplice[1] = helper.Skip(closingsSplice[1], k.Kama.IdlePeriod()) + + kamas := k.Kama.Compute(closingsSplice[0]) + + actions := helper.Operate(kamas, closingsSplice[1], func(kama, closing float64) strategy.Action { + // A closing price crossing above the KAMA suggests a bullish trend. + if closing > kama { + return strategy.Buy + } + + // While crossing below the KAMA indicats a bearish trend. + if closing < kama { + return strategy.Sell + } + + return strategy.Hold + }) + + // KAMA starts only after a full period. + actions = helper.Shift(actions, k.Kama.IdlePeriod(), strategy.Hold) + + actions = strategy.NormalizeActions(actions) + + return actions +} + +// Report processes the provided asset snapshots and generates a report annotated with the recommended actions. +func (k *KamaStrategy) Report(c <-chan *asset.Snapshot) *helper.Report { + // + // snapshots[0] -> dates + // snapshots[1] -> closings[0] -> closings + // closings[1] -> kama + // snapshots[2] -> actions -> annotations + // -> outcomes + // + snapshotsSplice := helper.Duplicate(c, 3) + + dates := helper.Skip( + asset.SnapshotsAsDates(snapshotsSplice[0]), + k.Kama.IdlePeriod(), + ) + + closingsSplice := helper.Duplicate(asset.SnapshotsAsClosings(snapshotsSplice[1]), 2) + closingsSplice[1] = helper.Skip(closingsSplice[1], k.Kama.IdlePeriod()) + + kamas := k.Kama.Compute(closingsSplice[0]) + + actions, outcomes := strategy.ComputeWithOutcome(k, snapshotsSplice[2]) + actions = helper.Skip(actions, k.Kama.IdlePeriod()) + outcomes = helper.Skip(outcomes, k.Kama.IdlePeriod()) + + annotations := strategy.ActionsToAnnotations(actions) + outcomes = helper.MultiplyBy(outcomes, 100) + + report := helper.NewReport(k.Name(), dates) + report.AddChart() + report.AddChart() + + report.AddColumn(helper.NewNumericReportColumn("Close", closingsSplice[1])) + report.AddColumn(helper.NewNumericReportColumn("KAMA", kamas), 1) + report.AddColumn(helper.NewAnnotationReportColumn(annotations), 0, 1) + + report.AddColumn(helper.NewNumericReportColumn("Outcome", outcomes), 2) + + return report +} diff --git a/strategy/trend/kama_strategy_test.go b/strategy/trend/kama_strategy_test.go new file mode 100644 index 0000000..bf8279a --- /dev/null +++ b/strategy/trend/kama_strategy_test.go @@ -0,0 +1,56 @@ +// Copyright (c) 2021-2024 Onur Cinar. +// The source code is provided under GNU AGPLv3 License. +// https://github.com/cinar/indicator + +package trend_test + +import ( + "os" + "testing" + + "github.com/cinar/indicator/v2/asset" + "github.com/cinar/indicator/v2/helper" + "github.com/cinar/indicator/v2/strategy" + "github.com/cinar/indicator/v2/strategy/trend" +) + +func TestKamaStrategy(t *testing.T) { + snapshots, err := helper.ReadFromCsvFile[asset.Snapshot]("testdata/brk-b.csv", true) + if err != nil { + t.Fatal(err) + } + + results, err := helper.ReadFromCsvFile[strategy.Result]("testdata/kama_strategy.csv", true) + if err != nil { + t.Fatal(err) + } + + expected := helper.Map(results, func(r *strategy.Result) strategy.Action { return r.Action }) + + kama := trend.NewKamaStrategy() + actual := kama.Compute(snapshots) + + err = helper.CheckEquals(actual, expected) + if err != nil { + t.Fatal(err) + } +} + +func TestKamaStrategyReport(t *testing.T) { + snapshots, err := helper.ReadFromCsvFile[asset.Snapshot]("testdata/brk-b.csv", true) + if err != nil { + t.Fatal(err) + } + + kama := trend.NewKamaStrategy() + + report := kama.Report(snapshots) + + fileName := "kama_strategy.html" + defer os.Remove(fileName) + + err = report.WriteToFile(fileName) + if err != nil { + t.Fatal(err) + } +} diff --git a/strategy/trend/testdata/kama_strategy.csv b/strategy/trend/testdata/kama_strategy.csv new file mode 100644 index 0000000..2055589 --- /dev/null +++ b/strategy/trend/testdata/kama_strategy.csv @@ -0,0 +1,252 @@ +Action +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +-1 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +-1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +-1 +0 +1 +-1 +0 +0 +1 +-1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +-1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +-1 +1 +0 +0 +0 +-1 +0 +0 +1 +0 +-1 +0 +0 +0 +0 +1 +0 +0 +0 +-1 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +-1 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +-1 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +-1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +-1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +1 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +-1 diff --git a/trend/README.md b/trend/README.md index 3339a1d..fbbdfa4 100644 --- a/trend/README.md +++ b/trend/README.md @@ -56,6 +56,7 @@ The information provided on this project is strictly for informational purposes - [func \(h \*Hma\[T\]\) String\(\) string](<#Hma[T].String>) - [type Kama](<#Kama>) - [func NewKama\[T helper.Number\]\(\) \*Kama\[T\]](<#NewKama>) + - [func NewKamaWith\[T helper.Number\]\(erPeriod, fastScPeriod, slowScPeriod int\) \*Kama\[T\]](<#NewKamaWith>) - [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>) @@ -704,8 +705,17 @@ func NewKama[T helper.Number]() *Kama[T] NewKama function initializes a new KAMA instance with the default parameters. + +### func [NewKamaWith]() + +```go +func NewKamaWith[T helper.Number](erPeriod, fastScPeriod, slowScPeriod int) *Kama[T] +``` + +NewKamaWith function initializes a new KAMA instance with the given parameters. + -### func \(\*Kama\[T\]\) [Compute]() +### func \(\*Kama\[T\]\) [Compute]() ```go func (k *Kama[T]) Compute(closings <-chan T) <-chan T @@ -714,7 +724,7 @@ 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. -### func \(\*Kama\[T\]\) [IdlePeriod]() +### func \(\*Kama\[T\]\) [IdlePeriod]() ```go func (k *Kama[T]) IdlePeriod() int @@ -723,7 +733,7 @@ func (k *Kama[T]) IdlePeriod() int IdlePeriod is the initial period that KAMA yield any results. -### func \(\*Kama\[T\]\) [String]() +### func \(\*Kama\[T\]\) [String]() ```go func (k *Kama[T]) String() string diff --git a/trend/kama.go b/trend/kama.go index 9df6e69..b92898a 100644 --- a/trend/kama.go +++ b/trend/kama.go @@ -49,10 +49,19 @@ type Kama[T helper.Number] struct { // NewKama function initializes a new KAMA instance with the default parameters. func NewKama[T helper.Number]() *Kama[T] { + return NewKamaWith[T]( + DefaultKamaErPeriod, + DefaultKamaFastScPeriod, + DefaultKamaSlowScPeriod, + ) +} + +// NewKamaWith function initializes a new KAMA instance with the given parameters. +func NewKamaWith[T helper.Number](erPeriod, fastScPeriod, slowScPeriod int) *Kama[T] { return &Kama[T]{ - ErPeriod: DefaultKamaErPeriod, - FastScPeriod: DefaultKamaFastScPeriod, - SlowScPeriod: DefaultKamaSlowScPeriod, + ErPeriod: erPeriod, + FastScPeriod: fastScPeriod, + SlowScPeriod: slowScPeriod, } } From 8ecd12b88f08c30f376fd2ee1f398359709cd10c Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Sun, 9 Jun 2024 18:44:51 -0700 Subject: [PATCH 2/3] Kaufman's Adaptive Moving Average (KAMA) strategy is added. --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 55ae6a2..d45a458 100644 --- a/README.md +++ b/README.md @@ -114,13 +114,14 @@ The following list of strategies are currently supported by this package: - [Absolute Price Oscillator (APO) Strategy](strategy/trend/README.md#type-apostrategy) - [Aroon Strategy](strategy/trend/README.md#type-aroonstrategy) - [Balance of Power (BoP) Strategy](strategy/trend/README.md#type-bopstrategy) -- [Double Exponential Moving Average (DEMA) Strategy](strategy/trend/README.md#type-demastrategy) - Chande Forecast Oscillator Strategy - [Community Channel Index (CCI) Strategy](strategy/trend/README.md#type-ccistrategy) +- [Double Exponential Moving Average (DEMA) Strategy](strategy/trend/README.md#type-demastrategy) - [Golden Cross Strategy](strategy/trend/README.md#type-goldencrossstrategy) -- [Random Index (KDJ) Strategy](strategy/trend/README.md#type-kdjstrategy) +- [Kaufman's Adaptive Moving Average (KAMA) Strategy](strategy/trend/README.md#type-kamastrategy) - [Moving Average Convergence Divergence (MACD) Strategy](strategy/trend/README.md#type-macdstrategy) - [Qstick Strategy](strategy/trend/README.md#type-qstickstrategy) +- [Random Index (KDJ) Strategy](strategy/trend/README.md#type-kdjstrategy) - [Triangular Moving Average (TRIMA) Strategy](strategy/trend/README.md#type-trimastrategy) - [Triple Exponential Average (TRIX) Strategy](strategy/trend/README.md#type-trixstrategy) - [Triple Moving Average Crossover Strategy](strategy/trend/README.md#type-triplemovingaveragecrossoverstrategy) From 9d44b219b95526c29f6ea099ec3bbe7cc461d47b Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Sun, 9 Jun 2024 18:47:33 -0700 Subject: [PATCH 3/3] Kaufman's Adaptive Moving Average (KAMA) strategy is added. --- strategy/trend/trend.go | 1 + 1 file changed, 1 insertion(+) diff --git a/strategy/trend/trend.go b/strategy/trend/trend.go index 4d8d812..a90b35e 100644 --- a/strategy/trend/trend.go +++ b/strategy/trend/trend.go @@ -29,6 +29,7 @@ func AllStrategies() []strategy.Strategy { NewCciStrategy(), NewDemaStrategy(), NewGoldenCrossStrategy(), + NewKamaStrategy(), NewKdjStrategy(), NewMacdStrategy(), NewQstickStrategy(),