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