Skip to content

Commit

Permalink
Smoothed Moving Average (SMMA) Strategy added. (#249)
Browse files Browse the repository at this point in the history
# Describe Request

Smoothed Moving Average (SMMA) Strategy added.

# Change Type

New Strategy.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced the Smoothed Moving Average (SMMA) Strategy to the
Indicator Go module.
- Added associated methods for the SMMA strategy, enhancing trend
analysis capabilities.

- **Bug Fixes**
- Updated documentation to reflect new function signatures and improve
clarity.

- **Tests**
- Established a testing framework for the SMMA strategy, validating
computation and reporting functionalities.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • Loading branch information
cinar authored Dec 24, 2024
1 parent ee386c8 commit cf5415f
Show file tree
Hide file tree
Showing 7 changed files with 531 additions and 4 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
80 changes: 80 additions & 0 deletions strategy/trend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>)
Expand Down Expand Up @@ -141,6 +147,18 @@ const (
)
```

<a name="DefaultSmmaStrategyShortPeriod"></a>

```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
)
```

<a name="DefaultTrimaStrategyShortPeriod"></a>

```go
Expand Down Expand Up @@ -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.

<a name="SmmaStrategy"></a>
## type [SmmaStrategy](<https://github.com/cinar/indicator/blob/master/strategy/trend/smma_strategy.go#L28-L36>)

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]
}
```

<a name="NewSmmaStrategy"></a>
### func [NewSmmaStrategy](<https://github.com/cinar/indicator/blob/master/strategy/trend/smma_strategy.go#L39>)

```go
func NewSmmaStrategy() *SmmaStrategy
```

NewSmmaStrategy function initializes a new SMMA strategy instance.

<a name="NewSmmaStrategyWith"></a>
### func [NewSmmaStrategyWith](<https://github.com/cinar/indicator/blob/master/strategy/trend/smma_strategy.go#L47>)

```go
func NewSmmaStrategyWith(shortPeriod, longPeriod int) *SmmaStrategy
```

NewSmmaStrategyWith function initializes a new SMMA strategy instance with the given parameters.

<a name="SmmaStrategy.Compute"></a>
### func \(\*SmmaStrategy\) [Compute](<https://github.com/cinar/indicator/blob/master/strategy/trend/smma_strategy.go#L63>)

```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.

<a name="SmmaStrategy.Name"></a>
### func \(\*SmmaStrategy\) [Name](<https://github.com/cinar/indicator/blob/master/strategy/trend/smma_strategy.go#L55>)

```go
func (s *SmmaStrategy) Name() string
```

Name returns the name of the strategy.

<a name="SmmaStrategy.Report"></a>
### func \(\*SmmaStrategy\) [Report](<https://github.com/cinar/indicator/blob/master/strategy/trend/smma_strategy.go#L95>)

```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.

<a name="TrimaStrategy"></a>
## type [TrimaStrategy](<https://github.com/cinar/indicator/blob/master/strategy/trend/trima_strategy.go#L25-L31>)

Expand Down
136 changes: 136 additions & 0 deletions strategy/trend/smma_strategy.go
Original file line number Diff line number Diff line change
@@ -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
}
56 changes: 56 additions & 0 deletions strategy/trend/smma_strategy_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
Loading

0 comments on commit cf5415f

Please sign in to comment.