diff --git a/README.md b/README.md index 273a65e..6ae82fb 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,7 @@ The following list of strategies are currently supported by this package: ### 📈 Trend Strategies +- [Alligator Strategy](strategy/trend/README.md#type-alligatorstrategy) - [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) diff --git a/helper/README.md b/helper/README.md index da34873..57369c8 100644 --- a/helper/README.md +++ b/helper/README.md @@ -803,7 +803,7 @@ func Remove(t *testing.T, name string) Remove removes the file with the given name. -## func [RemoveAll]() +## func [RemoveAll]() ```go func RemoveAll(t *testing.T, path string) diff --git a/strategy/trend/README.md b/strategy/trend/README.md index cc03efd..6f59dcb 100644 --- a/strategy/trend/README.md +++ b/strategy/trend/README.md @@ -26,6 +26,12 @@ The information provided on this project is strictly for informational purposes - [Constants](<#constants>) - [func AllStrategies\(\) \[\]strategy.Strategy](<#AllStrategies>) +- [type AlligatorStrategy](<#AlligatorStrategy>) + - [func NewAlligatorStrategy\(\) \*AlligatorStrategy](<#NewAlligatorStrategy>) + - [func NewAlligatorStrategyWith\(jawPeriod, teethPeriod, lipPeriod int\) \*AlligatorStrategy](<#NewAlligatorStrategyWith>) + - [func \(a \*AlligatorStrategy\) Compute\(snapshots \<\-chan \*asset.Snapshot\) \<\-chan strategy.Action](<#AlligatorStrategy.Compute>) + - [func \(a \*AlligatorStrategy\) Name\(\) string](<#AlligatorStrategy.Name>) + - [func \(a \*AlligatorStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#AlligatorStrategy.Report>) - [type ApoStrategy](<#ApoStrategy>) - [func NewApoStrategy\(\) \*ApoStrategy](<#NewApoStrategy>) - [func \(a \*ApoStrategy\) Compute\(snapshots \<\-chan \*asset.Snapshot\) \<\-chan strategy.Action](<#ApoStrategy.Compute>) @@ -123,6 +129,21 @@ The information provided on this project is strictly for informational purposes ## Constants + + +```go +const ( + // DefaultAlligatorStrategyJawPeriod is the default jaw period of 13. + DefaultAlligatorStrategyJawPeriod = 13 + + // DefaultAlligatorStrategyTeethPeriod is the default teeth period of 8. + DefaultAlligatorStrategyTeethPeriod = 8 + + // DefaultAlligatorStrategyLipPeriod is the default lip period of 5. + DefaultAlligatorStrategyLipPeriod = 5 +) +``` + ```go @@ -213,6 +234,69 @@ func AllStrategies() []strategy.Strategy AllStrategies returns a slice containing references to all available trend strategies. + +## type [AlligatorStrategy]() + +AlligatorStrategy represents the configuration parameters for calculating the Alligator strategy. It is a technical indicator to help identify the presence and the direction of the trend. It uses three Smooted Moving Averges \(SMMAs\). + +```go +type AlligatorStrategy struct { + // Jaw represents the slowest moving aveage. + Jaw *trend.Smma[float64] + + // Teeth represents the medium moving average. + Teeth *trend.Smma[float64] + + // Lip represents the fastest moving average. + Lip *trend.Smma[float64] +} +``` + + +### func [NewAlligatorStrategy]() + +```go +func NewAlligatorStrategy() *AlligatorStrategy +``` + +NewAlligatorStrategy function initializes a new Alligator strategy instance. + + +### func [NewAlligatorStrategyWith]() + +```go +func NewAlligatorStrategyWith(jawPeriod, teethPeriod, lipPeriod int) *AlligatorStrategy +``` + +NewAlligatorStrategyWith function initializes a new Alligator strategy instance with the given parameters. + + +### func \(\*AlligatorStrategy\) [Compute]() + +```go +func (a *AlligatorStrategy) Compute(snapshots <-chan *asset.Snapshot) <-chan strategy.Action +``` + +Compute processes the provided asset snapshots and generates a stream of actionable recommendations. + + +### func \(\*AlligatorStrategy\) [Name]() + +```go +func (a *AlligatorStrategy) Name() string +``` + +Name returns the name of the strategy. + + +### func \(\*AlligatorStrategy\) [Report]() + +```go +func (a *AlligatorStrategy) Report(c <-chan *asset.Snapshot) *helper.Report +``` + +Report processes the provided asset snapshots and generates a report annotated with the recommended actions. + ## type [ApoStrategy]() diff --git a/strategy/trend/alligator_strategy.go b/strategy/trend/alligator_strategy.go new file mode 100644 index 0000000..16d8dd5 --- /dev/null +++ b/strategy/trend/alligator_strategy.go @@ -0,0 +1,146 @@ +// Copyright (c) 2021-2024 Onur Cinar. +// The source code is provided under GNU AGPLv3 License. +// https://github.com/cinar/indicator + +package trend + +import ( + "fmt" + + "github.com/cinar/indicator/v2/asset" + "github.com/cinar/indicator/v2/helper" + "github.com/cinar/indicator/v2/strategy" + "github.com/cinar/indicator/v2/trend" +) + +const ( + // DefaultAlligatorStrategyJawPeriod is the default jaw period of 13. + DefaultAlligatorStrategyJawPeriod = 13 + + // DefaultAlligatorStrategyTeethPeriod is the default teeth period of 8. + DefaultAlligatorStrategyTeethPeriod = 8 + + // DefaultAlligatorStrategyLipPeriod is the default lip period of 5. + DefaultAlligatorStrategyLipPeriod = 5 +) + +// AlligatorStrategy represents the configuration parameters for calculating the +// Alligator strategy. It is a technical indicator to help identify the presence +// and the direction of the trend. It uses three Smooted Moving Averges (SMMAs). +type AlligatorStrategy struct { + // Jaw represents the slowest moving aveage. + Jaw *trend.Smma[float64] + + // Teeth represents the medium moving average. + Teeth *trend.Smma[float64] + + // Lip represents the fastest moving average. + Lip *trend.Smma[float64] +} + +// NewAlligatorStrategy function initializes a new Alligator strategy instance. +func NewAlligatorStrategy() *AlligatorStrategy { + return NewAlligatorStrategyWith( + DefaultAlligatorStrategyJawPeriod, + DefaultAlligatorStrategyTeethPeriod, + DefaultAlligatorStrategyLipPeriod, + ) +} + +// NewAlligatorStrategyWith function initializes a new Alligator strategy instance with the given parameters. +func NewAlligatorStrategyWith(jawPeriod, teethPeriod, lipPeriod int) *AlligatorStrategy { + return &AlligatorStrategy{ + Jaw: trend.NewSmmaWithPeriod[float64](jawPeriod), + Teeth: trend.NewSmmaWithPeriod[float64](teethPeriod), + Lip: trend.NewSmmaWithPeriod[float64](lipPeriod), + } +} + +// Name returns the name of the strategy. +func (a *AlligatorStrategy) Name() string { + return fmt.Sprintf("Alligator Strategy (%d,%d,%d)", + a.Jaw.Period, + a.Teeth.Period, + a.Lip.Period, + ) +} + +// Compute processes the provided asset snapshots and generates a stream of actionable recommendations. +func (a *AlligatorStrategy) Compute(snapshots <-chan *asset.Snapshot) <-chan strategy.Action { + closingsSplice := helper.Duplicate(asset.SnapshotsAsClosings(snapshots), 3) + + jaws := a.Jaw.Compute(closingsSplice[0]) + teeths := a.Teeth.Compute(closingsSplice[1]) + lips := a.Lip.Compute(closingsSplice[2]) + + commonPeriod := helper.CommonPeriod(a.Jaw.Period, a.Teeth.Period, a.Lip.Period) + jaws = helper.SyncPeriod(commonPeriod, a.Jaw.Period, jaws) + teeths = helper.SyncPeriod(commonPeriod, a.Teeth.Period, teeths) + lips = helper.SyncPeriod(commonPeriod, a.Lip.Period, lips) + + actions := helper.Operate3(jaws, teeths, lips, func(jaw, teeth, lip float64) strategy.Action { + if lip > teeth && lip > jaw { + return strategy.Buy + } + + if lip < teeth && lip < jaw { + return strategy.Sell + } + + return strategy.Hold + }) + + // Alligator strategy starts only after a full period. + actions = helper.Shift(actions, commonPeriod, strategy.Hold) + + return actions +} + +// Report processes the provided asset snapshots and generates a +// report annotated with the recommended actions. +func (a *AlligatorStrategy) Report(c <-chan *asset.Snapshot) *helper.Report { + // + // snapshots[0] -> dates + // snapshots[1] -> closings[0] -> closings + // closings[1] -> jaw + // closings[2] -> teeth + // closings[3] -> lip + // snapshots[2] -> actions -> annotations + // -> outcomes + // + snapshots := helper.Duplicate(c, 3) + + dates := asset.SnapshotsAsDates(snapshots[0]) + closingsSplice := helper.Duplicate(asset.SnapshotsAsClosings(snapshots[1]), 4) + + jaws := a.Jaw.Compute(closingsSplice[1]) + teeths := a.Teeth.Compute(closingsSplice[2]) + lips := a.Lip.Compute(closingsSplice[3]) + + actions, outcomes := strategy.ComputeWithOutcome(a, snapshots[2]) + annotations := strategy.ActionsToAnnotations(actions) + outcomes = helper.MultiplyBy(outcomes, 100) + + commonPeriod := helper.CommonPeriod(a.Jaw.Period, a.Teeth.Period, a.Lip.Period) + dates = helper.SyncPeriod(commonPeriod, 0, dates) + closingsSplice[0] = helper.Skip(closingsSplice[0], commonPeriod) + jaws = helper.SyncPeriod(commonPeriod, a.Jaw.Period, jaws) + teeths = helper.SyncPeriod(commonPeriod, a.Teeth.Period, teeths) + lips = helper.SyncPeriod(commonPeriod, a.Lip.Period, lips) + annotations = helper.Skip(annotations, commonPeriod) + outcomes = helper.Skip(outcomes, commonPeriod) + + report := helper.NewReport(a.Name(), dates) + report.AddChart() + report.AddChart() + + report.AddColumn(helper.NewNumericReportColumn("Close", closingsSplice[0])) + report.AddColumn(helper.NewNumericReportColumn("Jaw", jaws), 1) + report.AddColumn(helper.NewNumericReportColumn("Teeth", teeths), 1) + report.AddColumn(helper.NewNumericReportColumn("Lip", lips), 1) + report.AddColumn(helper.NewAnnotationReportColumn(annotations), 0, 1) + + report.AddColumn(helper.NewNumericReportColumn("Outcome", outcomes), 2) + + return report +} diff --git a/strategy/trend/alligator_strategy_test.go b/strategy/trend/alligator_strategy_test.go new file mode 100644 index 0000000..978efd7 --- /dev/null +++ b/strategy/trend/alligator_strategy_test.go @@ -0,0 +1,54 @@ +// 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/asset" + "github.com/cinar/indicator/v2/helper" + "github.com/cinar/indicator/v2/strategy" + "github.com/cinar/indicator/v2/strategy/trend" +) + +func TestAlligatorStrategy(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/alligator_strategy.csv", true) + if err != nil { + t.Fatal(err) + } + + expected := helper.Map(results, func(r *strategy.Result) strategy.Action { return r.Action }) + + alligator := trend.NewAlligatorStrategy() + actual := alligator.Compute(snapshots) + + err = helper.CheckEquals(actual, expected) + if err != nil { + t.Fatal(err) + } +} + +func TestAlligatorStrategyReport(t *testing.T) { + snapshots, err := helper.ReadFromCsvFile[asset.Snapshot]("testdata/brk-b.csv", true) + if err != nil { + t.Fatal(err) + } + + alligator := trend.NewAlligatorStrategy() + report := alligator.Report(snapshots) + + fileName := "alligator_strategy.html" + defer helper.Remove(t, fileName) + + err = report.WriteToFile(fileName) + if err != nil { + t.Fatal(err) + } +} diff --git a/strategy/trend/testdata/alligator_strategy.csv b/strategy/trend/testdata/alligator_strategy.csv new file mode 100644 index 0000000..003391d --- /dev/null +++ b/strategy/trend/testdata/alligator_strategy.csv @@ -0,0 +1,253 @@ +Action +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +0 +0 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +1 +1 +1 +1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +0 +-1 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +0 +0 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +-1 +0 +0 +0 +0 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 diff --git a/strategy/trend/trend.go b/strategy/trend/trend.go index 141fe79..6e8478c 100644 --- a/strategy/trend/trend.go +++ b/strategy/trend/trend.go @@ -23,6 +23,7 @@ import "github.com/cinar/indicator/v2/strategy" // AllStrategies returns a slice containing references to all available trend strategies. func AllStrategies() []strategy.Strategy { return []strategy.Strategy{ + NewAlligatorStrategy(), NewApoStrategy(), NewAroonStrategy(), NewBopStrategy(),