diff --git a/README.md b/README.md index 23952c2..273a65e 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,7 @@ The following list of strategies are currently supported by this package: - [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) +- [Smoothed Moving Average (SMMA) Strategy](strategy/trend/README.md#type-smmastrategy) - [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) diff --git a/strategy/trend/README.md b/strategy/trend/README.md index a880ee9..cc03efd 100644 --- a/strategy/trend/README.md +++ b/strategy/trend/README.md @@ -85,6 +85,12 @@ The information provided on this project is strictly for informational purposes - [func \(q \*QstickStrategy\) Compute\(c \<\-chan \*asset.Snapshot\) \<\-chan strategy.Action](<#QstickStrategy.Compute>) - [func \(\*QstickStrategy\) Name\(\) string](<#QstickStrategy.Name>) - [func \(q \*QstickStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#QstickStrategy.Report>) +- [type SmmaStrategy](<#SmmaStrategy>) + - [func NewSmmaStrategy\(\) \*SmmaStrategy](<#NewSmmaStrategy>) + - [func NewSmmaStrategyWith\(shortPeriod, longPeriod int\) \*SmmaStrategy](<#NewSmmaStrategyWith>) + - [func \(s \*SmmaStrategy\) Compute\(snapshots \<\-chan \*asset.Snapshot\) \<\-chan strategy.Action](<#SmmaStrategy.Compute>) + - [func \(s \*SmmaStrategy\) Name\(\) string](<#SmmaStrategy.Name>) + - [func \(s \*SmmaStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#SmmaStrategy.Report>) - [type TrimaStrategy](<#TrimaStrategy>) - [func NewTrimaStrategy\(\) \*TrimaStrategy](<#NewTrimaStrategy>) - [func \(t \*TrimaStrategy\) Compute\(c \<\-chan \*asset.Snapshot\) \<\-chan strategy.Action](<#TrimaStrategy.Compute>) @@ -141,6 +147,18 @@ const ( ) ``` + + +```go +const ( + // DefaultSmmaStrategyShortPeriod is the default short-term SMMA period of 20. + DefaultSmmaStrategyShortPeriod = 20 + + // DefaultSmmaStrategyLongPeriod is the default short-term SMMA period of 50. + DefaultSmmaStrategyLongPeriod = 50 +) +``` + ```go @@ -772,6 +790,68 @@ func (q *QstickStrategy) Report(c <-chan *asset.Snapshot) *helper.Report Report processes the provided asset snapshots and generates a report annotated with the recommended actions. + +## type [SmmaStrategy]() + +SmmaStrategy represents the configuration parameters for calculating the Smooted Moving Averge \(SMMA\) strategy. A short\-term SMMA crossing above the long\-term SMMA suggests a bullish trend, while crossing below the long\-term SMMA indicates a bearish trend. + +```go +type SmmaStrategy struct { + // ShortSmma represents the configuration parameters for calculating the + // short-term Smooted Moving Averge (SMMA). + ShortSmma *trend.Smma[float64] + + // LongSmma represents the configuration parameters for calculating the + // long-term Smooted Moving Averge (SMMA). + LongSmma *trend.Smma[float64] +} +``` + + +### func [NewSmmaStrategy]() + +```go +func NewSmmaStrategy() *SmmaStrategy +``` + +NewSmmaStrategy function initializes a new SMMA strategy instance. + + +### func [NewSmmaStrategyWith]() + +```go +func NewSmmaStrategyWith(shortPeriod, longPeriod int) *SmmaStrategy +``` + +NewSmmaStrategyWith function initializes a new SMMA strategy instance with the given parameters. + + +### func \(\*SmmaStrategy\) [Compute]() + +```go +func (s *SmmaStrategy) Compute(snapshots <-chan *asset.Snapshot) <-chan strategy.Action +``` + +Compute processes the provided asset snapshots and generates a stream of actionable recommendations. + + +### func \(\*SmmaStrategy\) [Name]() + +```go +func (s *SmmaStrategy) Name() string +``` + +Name returns the name of the strategy. + + +### func \(\*SmmaStrategy\) [Report]() + +```go +func (s *SmmaStrategy) Report(c <-chan *asset.Snapshot) *helper.Report +``` + +Report processes the provided asset snapshots and generates a report annotated with the recommended actions. + ## type [TrimaStrategy]() diff --git a/strategy/trend/smma_strategy.go b/strategy/trend/smma_strategy.go new file mode 100644 index 0000000..3f1e2dc --- /dev/null +++ b/strategy/trend/smma_strategy.go @@ -0,0 +1,136 @@ +// 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 ( + // DefaultSmmaStrategyShortPeriod is the default short-term SMMA period of 20. + DefaultSmmaStrategyShortPeriod = 20 + + // DefaultSmmaStrategyLongPeriod is the default short-term SMMA period of 50. + DefaultSmmaStrategyLongPeriod = 50 +) + +// SmmaStrategy represents the configuration parameters for calculating the +// Smooted Moving Averge (SMMA) strategy. A short-term SMMA crossing above +// the long-term SMMA suggests a bullish trend, while crossing below the +// long-term SMMA indicates a bearish trend. +type SmmaStrategy struct { + // ShortSmma represents the configuration parameters for calculating the + // short-term Smooted Moving Averge (SMMA). + ShortSmma *trend.Smma[float64] + + // LongSmma represents the configuration parameters for calculating the + // long-term Smooted Moving Averge (SMMA). + LongSmma *trend.Smma[float64] +} + +// NewSmmaStrategy function initializes a new SMMA strategy instance. +func NewSmmaStrategy() *SmmaStrategy { + return NewSmmaStrategyWith( + DefaultSmmaStrategyShortPeriod, + DefaultSmmaStrategyLongPeriod, + ) +} + +// NewSmmaStrategyWith function initializes a new SMMA strategy instance with the given parameters. +func NewSmmaStrategyWith(shortPeriod, longPeriod int) *SmmaStrategy { + return &SmmaStrategy{ + ShortSmma: trend.NewSmmaWithPeriod[float64](shortPeriod), + LongSmma: trend.NewSmmaWithPeriod[float64](longPeriod), + } +} + +// Name returns the name of the strategy. +func (s *SmmaStrategy) Name() string { + return fmt.Sprintf("SMMA Strategy (%d,%d)", + s.ShortSmma.Period, + s.LongSmma.Period, + ) +} + +// Compute processes the provided asset snapshots and generates a stream of actionable recommendations. +func (s *SmmaStrategy) Compute(snapshots <-chan *asset.Snapshot) <-chan strategy.Action { + closingsSplice := helper.Duplicate(asset.SnapshotsAsClosings(snapshots), 2) + + shortSmmas := s.ShortSmma.Compute(closingsSplice[0]) + longSmmas := s.LongSmma.Compute(closingsSplice[1]) + + commonPeriod := helper.CommonPeriod(s.ShortSmma.Period, s.LongSmma.Period) + shortSmmas = helper.SyncPeriod(commonPeriod, s.ShortSmma.Period, shortSmmas) + longSmmas = helper.SyncPeriod(commonPeriod, s.LongSmma.Period, longSmmas) + + actions := helper.Operate(shortSmmas, longSmmas, func(shortSmma, longSmma float64) strategy.Action { + // A short-perios SMMA value crossing above long-period SMMA suggests a bullish trend. + if shortSmma > longSmma { + return strategy.Buy + } + + // A short-period SMMA value crossing below long-period SMMA suggests a bearish trend. + if longSmma > shortSmma { + return strategy.Sell + } + + return strategy.Hold + }) + + // SMMA 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 (s *SmmaStrategy) Report(c <-chan *asset.Snapshot) *helper.Report { + // + // snapshots[0] -> dates + // snapshots[1] -> closings[0] -> closings + // closings[1] -> short-period SMMA + // closings[2] -> long-period SMMA + // snapshots[2] -> actions -> annotations + // -> outcomes + // + snapshots := helper.Duplicate(c, 3) + + dates := asset.SnapshotsAsDates(snapshots[0]) + closings := helper.Duplicate(asset.SnapshotsAsClosings(snapshots[1]), 3) + + shortSmmas := s.ShortSmma.Compute(closings[1]) + longSmmas := s.LongSmma.Compute(closings[2]) + + actions, outcomes := strategy.ComputeWithOutcome(s, snapshots[2]) + annotations := strategy.ActionsToAnnotations(actions) + outcomes = helper.MultiplyBy(outcomes, 100) + + commonPeriod := helper.CommonPeriod(s.ShortSmma.Period, s.LongSmma.Period) + dates = helper.SyncPeriod(commonPeriod, 0, dates) + closings[0] = helper.Skip(closings[0], commonPeriod) + shortSmmas = helper.SyncPeriod(commonPeriod, s.ShortSmma.Period, shortSmmas) + longSmmas = helper.SyncPeriod(commonPeriod, s.LongSmma.Period, longSmmas) + annotations = helper.Skip(annotations, commonPeriod) + outcomes = helper.Skip(outcomes, commonPeriod) + + report := helper.NewReport(s.Name(), dates) + report.AddChart() + report.AddChart() + + report.AddColumn(helper.NewNumericReportColumn("Close", closings[0])) + report.AddColumn(helper.NewNumericReportColumn("MACD", shortSmmas), 1) + report.AddColumn(helper.NewNumericReportColumn("Signal", longSmmas), 1) + report.AddColumn(helper.NewAnnotationReportColumn(annotations), 0, 1) + + report.AddColumn(helper.NewNumericReportColumn("Outcome", outcomes), 2) + + return report +} diff --git a/strategy/trend/smma_strategy_test.go b/strategy/trend/smma_strategy_test.go new file mode 100644 index 0000000..80eff01 --- /dev/null +++ b/strategy/trend/smma_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 TestSmmaStrategy(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/smma_strategy.csv", true) + if err != nil { + t.Fatal(err) + } + + expected := helper.Map(results, func(r *strategy.Result) strategy.Action { return r.Action }) + + smma := trend.NewSmmaStrategy() + actual := smma.Compute(snapshots) + + err = helper.CheckEquals(actual, expected) + if err != nil { + t.Fatal(err) + } +} + +func TestSmmaStrategyReport(t *testing.T) { + snapshots, err := helper.ReadFromCsvFile[asset.Snapshot]("testdata/brk-b.csv", true) + if err != nil { + t.Fatal(err) + } + + smma := trend.NewSmmaStrategy() + + report := smma.Report(snapshots) + + fileName := "smma_strategy.html" + defer os.Remove(fileName) + + err = report.WriteToFile(fileName) + if err != nil { + t.Fatal(err) + } +} diff --git a/strategy/trend/testdata/smma_strategy.csv b/strategy/trend/testdata/smma_strategy.csv new file mode 100644 index 0000000..f01f4d9 --- /dev/null +++ b/strategy/trend/testdata/smma_strategy.csv @@ -0,0 +1,253 @@ +Action +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 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +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 +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 +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 diff --git a/strategy/trend/trend.go b/strategy/trend/trend.go index cb977db..141fe79 100644 --- a/strategy/trend/trend.go +++ b/strategy/trend/trend.go @@ -33,6 +33,7 @@ func AllStrategies() []strategy.Strategy { NewKdjStrategy(), NewMacdStrategy(), NewQstickStrategy(), + NewSmmaStrategy(), NewTrimaStrategy(), NewTripleMovingAverageCrossoverStrategy(), NewTsiStrategy(), diff --git a/trend/README.md b/trend/README.md index b5abca7..c1efe36 100644 --- a/trend/README.md +++ b/trend/README.md @@ -1475,7 +1475,7 @@ func NewSmma[T helper.Number]() *Smma[T] NewSmma function initializes a new SMMA instance with the default parameters. -### func [NewSmmaWithPeriod]() +### func [NewSmmaWithPeriod]() ```go func NewSmmaWithPeriod[T helper.Number](period int) *Smma[T] @@ -1484,7 +1484,7 @@ func NewSmmaWithPeriod[T helper.Number](period int) *Smma[T] NewSmmaWithPeriod function initializes a new SMMA instance with the given period. -### func \(\*Smma\[T\]\) [Compute]() +### func \(\*Smma\[T\]\) [Compute]() ```go func (s *Smma[T]) Compute(c <-chan T) <-chan T @@ -1493,7 +1493,7 @@ func (s *Smma[T]) Compute(c <-chan T) <-chan T Compute function takes a channel of numbers and computes the SMMA over the specified period. -### func \(\*Smma\[T\]\) [IdlePeriod]() +### func \(\*Smma\[T\]\) [IdlePeriod]() ```go func (s *Smma[T]) IdlePeriod() int @@ -1502,7 +1502,7 @@ func (s *Smma[T]) IdlePeriod() int IdlePeriod is the initial period that SMMA yield any results. -### func \(\*Smma\[T\]\) [String]() +### func \(\*Smma\[T\]\) [String]() ```go func (s *Smma[T]) String() string