diff --git a/README.md b/README.md
index ef3a3d6..93ce0fa 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,7 @@ The following list of indicators are currently supported by this package:
- [Balance of Power (BoP)](trend/README.md#type-bop)
- Chande Forecast Oscillator (CFO)
- Community Channel Index (CMI)
-- Double Exponential Moving Average (DEMA)
+- [Double Exponential Moving Average (DEMA)](trend/README.md#type-dema)
- [Exponential Moving Average (EMA)](trend/README.md#type-ema)
- Mass Index (MI)
- Moving Average Convergence Divergence (MACD)
@@ -104,6 +104,7 @@ The following list of strategies are currently supported by this package:
- [Absolute Price Oscillator (APO) Strategy](strategy/README.md#type-apostrategy)
- [Aroon Strategy](strategy/README.md#type-aroonstrategy)
- [Balance of Power (BoP) Strategy](strategy/README.md#type-bopstrategy)
+- [Double Exponential Moving Average (DEMA) Strategy](strategy/README.md#type-demastrategy)
- Chande Forecast Oscillator Strategy
- KDJ Strategy
- MACD Strategy
diff --git a/helper/abs_test.go b/helper/abs_test.go
index 26fe4bb..2afd985 100644
--- a/helper/abs_test.go
+++ b/helper/abs_test.go
@@ -5,19 +5,19 @@
package helper_test
import (
- "reflect"
"testing"
"github.com/cinar/indicator/helper"
)
func TestAbs(t *testing.T) {
- input := []int{-10, 20, -4, -5}
- expected := []int{10, 20, 4, 5}
+ input := helper.SliceToChan([]int{-10, 20, -4, -5})
+ expected := helper.SliceToChan([]int{10, 20, 4, 5})
- actual := helper.ChanToSlice(helper.Abs(helper.SliceToChan(input)))
+ actual := helper.Abs(input)
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("actual %v expected %v", actual, expected)
+ err := helper.CheckEquals(actual, expected)
+ if err != nil {
+ t.Fatal(err)
}
}
diff --git a/helper/apply_test.go b/helper/apply_test.go
index aaebeb5..80a0294 100644
--- a/helper/apply_test.go
+++ b/helper/apply_test.go
@@ -5,21 +5,21 @@
package helper_test
import (
- "reflect"
"testing"
"github.com/cinar/indicator/helper"
)
func TestApply(t *testing.T) {
- input := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
- expected := []int{2, 4, 6, 8, 10, 12, 14, 16, 18, 20}
+ input := helper.SliceToChan([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
+ expected := helper.SliceToChan([]int{2, 4, 6, 8, 10, 12, 14, 16, 18, 20})
- actual := helper.ChanToSlice(helper.Apply(helper.SliceToChan(input), func(n int) int {
+ actual := helper.Apply(input, func(n int) int {
return n * 2
- }))
+ })
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("actual %v expected %v", actual, expected)
+ err := helper.CheckEquals(actual, expected)
+ if err != nil {
+ t.Fatal(err)
}
}
diff --git a/helper/change.go b/helper/change.go
index 836d214..eaad685 100644
--- a/helper/change.go
+++ b/helper/change.go
@@ -13,6 +13,7 @@ package helper
// fmt.Println(helper.ChanToSlice(output)) // [4, 3, 3, -3, -7, -1, 2, 3]
func Change[T Number](c <-chan T, before int) <-chan T {
cs := Duplicate(c, 2)
+ cs[0] = Buffered(cs[0], before)
cs[1] = Skip(cs[1], before)
return Subtract(cs[1], cs[0])
diff --git a/helper/change_percent.go b/helper/change_percent.go
index d52ea32..8a1b942 100644
--- a/helper/change_percent.go
+++ b/helper/change_percent.go
@@ -14,5 +14,6 @@ package helper
// fmt.Println(helper.ChanToSlice(actual)) // [400, 150, 60, -60, -87.5, -50, 200, 300]
func ChangePercent[T Number](c <-chan T, before int) <-chan T {
cs := Duplicate(c, 2)
+ cs[1] = Buffered(cs[1], before)
return MultiplyBy(Divide(Change(cs[0], before), cs[1]), 100)
}
diff --git a/helper/change_percent_test.go b/helper/change_percent_test.go
index 86dc18e..6bfd3f1 100644
--- a/helper/change_percent_test.go
+++ b/helper/change_percent_test.go
@@ -5,19 +5,19 @@
package helper_test
import (
- "reflect"
"testing"
"github.com/cinar/indicator/helper"
)
func TestChangePercent(t *testing.T) {
- input := []float64{1, 2, 5, 5, 8, 2, 1, 1, 3, 4}
- expected := []float64{400, 150, 60, -60, -87.5, -50, 200, 300}
+ input := helper.SliceToChan([]float64{1, 2, 5, 5, 8, 2, 1, 1, 3, 4})
+ expected := helper.SliceToChan([]float64{400, 150, 60, -60, -87.5, -50, 200, 300})
- actual := helper.ChanToSlice(helper.ChangePercent(helper.SliceToChan(input), 2))
+ actual := helper.ChangePercent(input, 2)
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("actual %v expected %v", actual, expected)
+ err := helper.CheckEquals(actual, expected)
+ if err != nil {
+ t.Fatal(err)
}
}
diff --git a/helper/change_test.go b/helper/change_test.go
index c18f484..c6d0f4c 100644
--- a/helper/change_test.go
+++ b/helper/change_test.go
@@ -5,19 +5,19 @@
package helper_test
import (
- "reflect"
"testing"
"github.com/cinar/indicator/helper"
)
func TestChange(t *testing.T) {
- input := []int{1, 2, 5, 5, 8, 2, 1, 1, 3, 4}
- expected := []int{4, 3, 3, -3, -7, -1, 2, 3}
+ input := helper.SliceToChan([]int{1, 2, 5, 5, 8, 2, 1, 1, 3, 4})
+ expected := helper.SliceToChan([]int{4, 3, 3, -3, -7, -1, 2, 3})
- actual := helper.ChanToSlice(helper.Change(helper.SliceToChan(input), 2))
+ actual := helper.Change(input, 2)
- if !reflect.DeepEqual(actual, expected) {
- t.Fatalf("actual %v expected %v", actual, expected)
+ err := helper.CheckEquals(actual, expected)
+ if err != nil {
+ t.Fatal(err)
}
}
diff --git a/helper/slice_to_chan.go b/helper/slice_to_chan.go
index 1532e6c..5b7dfe2 100644
--- a/helper/slice_to_chan.go
+++ b/helper/slice_to_chan.go
@@ -15,13 +15,15 @@ package helper
// fmt.Println(<- c) // 6
// fmt.Println(<- c) // 8
func SliceToChan[T any](slice []T) <-chan T {
- c := make(chan T, len(slice))
+ c := make(chan T)
- for _, n := range slice {
- c <- n
- }
+ go func() {
+ defer close(c)
- close(c)
+ for _, n := range slice {
+ c <- n
+ }
+ }()
return c
}
diff --git a/strategy/README.md b/strategy/README.md
index e56ce11..87a4a51 100644
--- a/strategy/README.md
+++ b/strategy/README.md
@@ -24,6 +24,7 @@ The information provided on this project is strictly for informational purposes
## Index
+- [Constants](<#constants>)
- [func ActionsToAnnotations\(ac \<\-chan Action\) \<\-chan string](<#ActionsToAnnotations>)
- [func ComputeWithOutcome\(s Strategy, c \<\-chan \*asset.Snapshot\) \(\<\-chan Action, \<\-chan float64\)](<#ComputeWithOutcome>)
- [func NormalizeActions\(ac \<\-chan Action\) \<\-chan Action](<#NormalizeActions>)
@@ -54,9 +55,28 @@ The information provided on this project is strictly for informational purposes
- [func \(b \*BuyAndHoldStrategy\) Compute\(snapshots \<\-chan \*asset.Snapshot\) \<\-chan Action](<#BuyAndHoldStrategy.Compute>)
- [func \(b \*BuyAndHoldStrategy\) Name\(\) string](<#BuyAndHoldStrategy.Name>)
- [func \(b \*BuyAndHoldStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#BuyAndHoldStrategy.Report>)
+- [type DemaStrategy](<#DemaStrategy>)
+ - [func NewDemaStrategy\(\) \*DemaStrategy](<#NewDemaStrategy>)
+ - [func \(d \*DemaStrategy\) Compute\(c \<\-chan \*asset.Snapshot\) \<\-chan Action](<#DemaStrategy.Compute>)
+ - [func \(\*DemaStrategy\) Name\(\) string](<#DemaStrategy.Name>)
+ - [func \(d \*DemaStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#DemaStrategy.Report>)
- [type Strategy](<#Strategy>)
+## Constants
+
+
+
+```go
+const (
+ // DefaultDemaStrategyPeriod1 is the first DEMA period.
+ DefaultDemaStrategyPeriod1 = 5
+
+ // DefaultDemaStrategyPeriod2 is the second DEMA period.
+ DefaultDemaStrategyPeriod2 = 35
+)
+```
+
## func [ActionsToAnnotations]()
@@ -375,6 +395,61 @@ func (b *BuyAndHoldStrategy) Report(c <-chan *asset.Snapshot) *helper.Report
Report processes the provided asset snapshots and generates a report annotated with the recommended actions.
+
+## type [DemaStrategy]()
+
+DemaStrategy represents the configuration parameters for calculating the DEMA strategy. A bullish cross occurs when DEMA with 5 days period moves above DEMA with 35 days period. A bearish cross occurs when DEMA with 35 days period moves above DEMA With 5 days period.
+
+```go
+type DemaStrategy struct {
+ Strategy
+
+ // Dema1 represents the configuration parameters for
+ // calculating the first DEMA.
+ Dema1 *trend.Dema[float64]
+
+ // Dema2 represents the configuration parameters for
+ // calculating the second DEMA.
+ Dema2 *trend.Dema[float64]
+}
+```
+
+
+### func [NewDemaStrategy]()
+
+```go
+func NewDemaStrategy() *DemaStrategy
+```
+
+NewDemaStrategy function initializes a new DEMA strategy instance with the default parameters.
+
+
+### func \(\*DemaStrategy\) [Compute]()
+
+```go
+func (d *DemaStrategy) Compute(c <-chan *asset.Snapshot) <-chan Action
+```
+
+Compute processes the provided asset snapshots and generates a stream of actionable recommendations.
+
+
+### func \(\*DemaStrategy\) [Name]()
+
+```go
+func (*DemaStrategy) Name() string
+```
+
+Name returns the name of the strategy.
+
+
+### func \(\*DemaStrategy\) [Report]()
+
+```go
+func (d *DemaStrategy) Report(c <-chan *asset.Snapshot) *helper.Report
+```
+
+Report processes the provided asset snapshots and generates a report annotated with the recommended actions.
+
## type [Strategy]()
diff --git a/strategy/backtest.go b/strategy/backtest.go
index 8d2a603..5ec5847 100644
--- a/strategy/backtest.go
+++ b/strategy/backtest.go
@@ -111,8 +111,9 @@ func (b *Backtest) allStrategies() []Strategy {
return []Strategy{
NewApoStrategy(),
NewAroonStrategy(),
- NewBuyAndHoldStrategy(),
NewBopStrategy(),
+ NewBuyAndHoldStrategy(),
+ NewDemaStrategy(),
}
}
diff --git a/strategy/dema_strategy.go b/strategy/dema_strategy.go
new file mode 100644
index 0000000..7d91d75
--- /dev/null
+++ b/strategy/dema_strategy.go
@@ -0,0 +1,128 @@
+// Copyright (c) 2021-2023 Onur Cinar.
+// The source code is provided under GNU AGPLv3 License.
+// https://github.com/cinar/indicator
+
+package strategy
+
+import (
+ "fmt"
+
+ "github.com/cinar/indicator/asset"
+ "github.com/cinar/indicator/helper"
+ "github.com/cinar/indicator/trend"
+)
+
+const (
+ // DefaultDemaStrategyPeriod1 is the first DEMA period.
+ DefaultDemaStrategyPeriod1 = 5
+
+ // DefaultDemaStrategyPeriod2 is the second DEMA period.
+ DefaultDemaStrategyPeriod2 = 35
+)
+
+// DemaStrategy represents the configuration parameters for calculating the DEMA strategy.
+// A bullish cross occurs when DEMA with 5 days period moves above DEMA with 35 days period.
+// A bearish cross occurs when DEMA with 35 days period moves above DEMA With 5 days period.
+type DemaStrategy struct {
+ Strategy
+
+ // Dema1 represents the configuration parameters for
+ // calculating the first DEMA.
+ Dema1 *trend.Dema[float64]
+
+ // Dema2 represents the configuration parameters for
+ // calculating the second DEMA.
+ Dema2 *trend.Dema[float64]
+}
+
+// NewDemaStrategy function initializes a new DEMA strategy instance
+// with the default parameters.
+func NewDemaStrategy() *DemaStrategy {
+ dema1 := trend.NewDema[float64]()
+ dema1.Ema1.Period = DefaultDemaStrategyPeriod1
+ dema1.Ema2.Period = DefaultDemaStrategyPeriod1
+
+ dema2 := trend.NewDema[float64]()
+ dema2.Ema1.Period = DefaultDemaStrategyPeriod2
+ dema2.Ema2.Period = DefaultDemaStrategyPeriod2
+
+ return &DemaStrategy{
+ Dema1: dema1,
+ Dema2: dema2,
+ }
+}
+
+// Name returns the name of the strategy.
+func (*DemaStrategy) Name() string {
+ return "DEMA Strategy"
+}
+
+// Compute processes the provided asset snapshots and generates a
+// stream of actionable recommendations.
+func (d *DemaStrategy) Compute(c <-chan *asset.Snapshot) <-chan Action {
+ closings := helper.Duplicate(asset.SnapshotsAsClosings(c), 2)
+
+ demas1 := d.Dema1.Compute(closings[0])
+ demas1 = helper.Shift(demas1, d.Dema1.IdlePeriod(), 0)
+
+ demas2 := d.Dema2.Compute(closings[1])
+ demas2 = helper.Shift(demas2, d.Dema2.IdlePeriod(), 0)
+
+ actions := NormalizeActions(helper.Operate(demas1, demas2, func(dema1, dema2 float64) Action {
+ if dema1 > dema2 {
+ return Buy
+ }
+
+ if dema2 > dema1 {
+ return Sell
+ }
+
+ return Hold
+ }))
+
+ // DEMA starts only after the a full periods for each EMA used.
+ actions = helper.Skip(actions, d.Dema2.IdlePeriod())
+ actions = helper.Shift(actions, d.Dema2.IdlePeriod(), Hold)
+
+ return actions
+}
+
+// Report processes the provided asset snapshots and generates a
+// report annotated with the recommended actions.
+func (d *DemaStrategy) Report(c <-chan *asset.Snapshot) *helper.Report {
+ //
+ // snapshots[0] -> dates
+ // snapshots[1] -> closings[0] -> demas1
+ // closings[1] -> demas2
+ // closings[2] -> closings
+ // snapshots[2] -> actions -> annotations
+ // -> outcomes
+ //
+ snapshots := helper.Duplicate(c, 3)
+
+ dates := asset.SnapshotsAsDates(snapshots[0])
+ closings := helper.Duplicate(asset.SnapshotsAsClosings(snapshots[1]), 3)
+
+ demas1 := d.Dema1.Compute(closings[0])
+ demas1 = helper.Shift(demas1, d.Dema1.IdlePeriod(), 0)
+
+ demas2 := d.Dema2.Compute(closings[1])
+ demas2 = helper.Shift(demas2, d.Dema2.IdlePeriod(), 0)
+
+ actions, outcomes := ComputeWithOutcome(d, snapshots[2])
+ annotations := ActionsToAnnotations(actions)
+ outcomes = helper.MultiplyBy(outcomes, 100)
+
+ report := helper.NewReport(d.Name(), dates)
+ report.AddChart()
+ report.AddChart()
+
+ report.AddColumn(helper.NewNumericReportColumn("Close", closings[2]))
+ report.AddColumn(helper.NewNumericReportColumn(fmt.Sprintf("Dema %d-day", d.Dema1.Ema1.Period), demas1), 1)
+ report.AddColumn(helper.NewNumericReportColumn(fmt.Sprintf("Dema %d-day", d.Dema2.Ema1.Period), demas2), 1)
+ report.AddColumn(helper.NewAnnotationReportColumn(annotations), 0, 1)
+
+ report.AddColumn(helper.NewNumericReportColumn("Outcome", outcomes), 2)
+
+ return report
+}
diff --git a/strategy/dema_strategy_test.go b/strategy/dema_strategy_test.go
new file mode 100644
index 0000000..88b94bb
--- /dev/null
+++ b/strategy/dema_strategy_test.go
@@ -0,0 +1,68 @@
+// Copyright (c) 2021-2023 Onur Cinar.
+// The source code is provided under GNU AGPLv3 License.
+// https://github.com/cinar/indicator
+
+package strategy_test
+
+import (
+ "os"
+ "testing"
+
+ "github.com/cinar/indicator/asset"
+ "github.com/cinar/indicator/helper"
+ "github.com/cinar/indicator/strategy"
+)
+
+func TestDemaStrategy(t *testing.T) {
+ type Result struct {
+ Action strategy.Action
+ Outcome float64
+ }
+
+ snapshots, err := helper.ReadFromCsvFile[asset.Snapshot]("testdata/repository/brk-b.csv", true)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ results, err := helper.ReadFromCsvFile[Result]("testdata/dema_strategy.csv", true)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ dema := strategy.NewDemaStrategy()
+ actions, outcomes := strategy.ComputeWithOutcome(dema, snapshots)
+
+ outcomes = helper.RoundDigits(outcomes, 2)
+
+ for result := range results {
+ action := <-actions
+ outcome := <-outcomes
+
+ if action != result.Action {
+ t.Fatalf("actual %v expected %v", action, result.Action)
+ }
+
+ if outcome != result.Outcome {
+ t.Fatalf("actual %v expected %v", outcome, result.Outcome)
+ }
+ }
+}
+
+func TestDemaStrategyReport(t *testing.T) {
+ snapshots, err := helper.ReadFromCsvFile[asset.Snapshot]("testdata/repository/brk-b.csv", true)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ dema := strategy.NewDemaStrategy()
+
+ report := dema.Report(snapshots)
+
+ fileName := "dema_strategy.html"
+ defer os.Remove(fileName)
+
+ err = report.WriteToFile(fileName)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
diff --git a/strategy/testdata/dema_strategy.csv b/strategy/testdata/dema_strategy.csv
new file mode 100644
index 0000000..dce333c
--- /dev/null
+++ b/strategy/testdata/dema_strategy.csv
@@ -0,0 +1,252 @@
+Action,Outcome
+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
+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
+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,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,-0
+0,0
+0,0
+0,0.01
+0,0
+0,-0.01
+0,0.01
+0,0.02
+0,0.02
+0,0.01
+0,0
+0,-0.01
+0,0
+0,0.01
+0,0.01
+0,-0
+0,-0
+0,-0
+0,0
+0,0
+0,0.01
+0,0.02
+0,0.02
+0,0.02
+0,0
+0,-0.01
+0,-0.01
+0,-0.01
+0,-0
+0,-0.01
+0,0
+0,0.02
+0,0.02
+0,0.03
+0,0.04
+0,0.04
+0,0.04
+0,0.03
+0,0.04
+0,0.04
+0,0.05
+0,0.05
+0,0.05
+0,0.05
+0,0.04
+0,0.04
+0,0.03
+0,0.04
+0,0.03
+0,0.04
+0,0.06
+0,0.06
+0,0.06
+0,0.06
+0,0.06
+0,0.06
+0,0.06
+0,0.07
+0,0.06
+0,0.06
+0,0.07
+0,0.07
+0,0.06
+0,0.07
+0,0.07
+0,0.08
+0,0.08
+0,0.08
+0,0.08
+0,0.08
+0,0.09
+0,0.09
+0,0.09
+0,0.1
+0,0.08
+0,0.12
+0,0.13
+0,0.11
+0,0.1
+0,0.11
+0,0.11
+0,0.1
+0,0.1
+0,0.09
+0,0.09
+0,0.09
+0,0.09
+0,0.1
+0,0.1
+0,0.1
+0,0.1
+0,0.11
+0,0.12
+0,0.11
+0,0.12
+0,0.12
+0,0.12
+0,0.12
+0,0.12
+0,0.13
+0,0.14
+0,0.14
+0,0.14
+0,0.14
+0,0.15
+0,0.15
+0,0.14
+0,0.12
+0,0.11
+0,0.12
+0,0.11
+0,0.11
+0,0.11
+0,0.08
+0,0.08
+0,0.06
+0,0.06
+0,0.07
+0,0.07
+0,0.07
+0,0.08
+0,0.08
+0,0.07
+-1,0.07
+1,0.07
+0,0.07
+-1,0.05
+0,0.05
+0,0.05
+0,0.05
+0,0.05
+0,0.05
+0,0.05
+0,0.05
+0,0.05
+0,0.05
+0,0.05
+0,0.05
+0,0.05
+0,0.05
+0,0.05
+0,0.05
+0,0.05
+0,0.05
+0,0.05
+0,0.05
+0,0.05
+0,0.05
+0,0.05
+0,0.05
+0,0.05
+0,0.05
+0,0.05
+0,0.05
+0,0.05
+1,0.05
diff --git a/trend/README.md b/trend/README.md
index 4f606bb..75921f3 100644
--- a/trend/README.md
+++ b/trend/README.md
@@ -37,6 +37,10 @@ The information provided on this project is strictly for informational purposes
- [type Bop](<#Bop>)
- [func NewBop\[T helper.Number\]\(\) \*Bop\[T\]](<#NewBop>)
- [func \(\*Bop\[T\]\) Compute\(opening, high, low, closing \<\-chan T\) \<\-chan T](<#Bop[T].Compute>)
+- [type Dema](<#Dema>)
+ - [func NewDema\[T helper.Number\]\(\) \*Dema\[T\]](<#NewDema>)
+ - [func \(d \*Dema\[T\]\) Compute\(c \<\-chan T\) \<\-chan T](<#Dema[T].Compute>)
+ - [func \(d \*Dema\[T\]\) IdlePeriod\(\) int](<#Dema[T].IdlePeriod>)
- [type Ema](<#Ema>)
- [func NewEma\[T helper.Number\]\(\) \*Ema\[T\]](<#NewEma>)
- [func \(ema \*Ema\[T\]\) Compute\(c \<\-chan T\) \<\-chan T](<#Ema[T].Compute>)
@@ -277,6 +281,64 @@ func (*Bop[T]) Compute(opening, high, low, closing <-chan T) <-chan T
Compute processes a channel of open, high, low, and close values, computing the BOP for each entry.
+
+## type [Dema]()
+
+Dema represents the parameters for calculating the Double Exponential Moving Average \(DEMA\). A bullish cross occurs when DEMA with 5 days period moves above DEMA with 35 days period. A bearish cross occurs when DEMA with 35 days period moves above DEMA With 5 days period.
+
+```
+DEMA = (2 * EMA1(values)) - EMA2(EMA1(values))
+```
+
+Example:
+
+```
+dema := trend.NewDema[float64]()
+dema.Ema1.Period = 10
+dema.Ema2.Period = 16
+
+result := dema.Compute(input)
+```
+
+```go
+type Dema[T helper.Number] struct {
+ // Ema1 represents the configuration parameters for
+ // calculating the first EMA.
+ Ema1 *Ema[T]
+
+ // Ema2 represents the configuration parameters for
+ // calculating the second EMA.
+ Ema2 *Ema[T]
+}
+```
+
+
+### func [NewDema]()
+
+```go
+func NewDema[T helper.Number]() *Dema[T]
+```
+
+NewDema function initializes a new DEMA instance with the default parameters.
+
+
+### func \(\*Dema\[T\]\) [Compute]()
+
+```go
+func (d *Dema[T]) Compute(c <-chan T) <-chan T
+```
+
+Compute function takes a channel of numbers and computes the DEMA over the specified period.
+
+
+### func \(\*Dema\[T\]\) [IdlePeriod]()
+
+```go
+func (d *Dema[T]) IdlePeriod() int
+```
+
+IdlePeriod is the initial period that DEMA won't yield any results.
+
## type [Ema]()
diff --git a/trend/dema.go b/trend/dema.go
new file mode 100644
index 0000000..6fd08e2
--- /dev/null
+++ b/trend/dema.go
@@ -0,0 +1,56 @@
+// Copyright (c) 2021-2023 Onur Cinar.
+// The source code is provided under GNU AGPLv3 License.
+// https://github.com/cinar/indicator
+
+package trend
+
+import "github.com/cinar/indicator/helper"
+
+// Dema represents the parameters for calculating the Double Exponential Moving Average (DEMA).
+// A bullish cross occurs when DEMA with 5 days period moves above DEMA with 35 days period.
+// A bearish cross occurs when DEMA with 35 days period moves above DEMA With 5 days period.
+//
+// DEMA = (2 * EMA1(values)) - EMA2(EMA1(values))
+//
+// Example:
+//
+// dema := trend.NewDema[float64]()
+// dema.Ema1.Period = 10
+// dema.Ema2.Period = 16
+//
+// result := dema.Compute(input)
+type Dema[T helper.Number] struct {
+ // Ema1 represents the configuration parameters for
+ // calculating the first EMA.
+ Ema1 *Ema[T]
+
+ // Ema2 represents the configuration parameters for
+ // calculating the second EMA.
+ Ema2 *Ema[T]
+}
+
+// NewDema function initializes a new DEMA instance
+// with the default parameters.
+func NewDema[T helper.Number]() *Dema[T] {
+ return &Dema[T]{
+ Ema1: NewEma[T](),
+ Ema2: NewEma[T](),
+ }
+}
+
+// Compute function takes a channel of numbers and computes the DEMA
+// over the specified period.
+func (d *Dema[T]) Compute(c <-chan T) <-chan T {
+ ema1 := helper.Duplicate(d.Ema1.Compute(c), 2)
+ ema2 := d.Ema2.Compute(ema1[1])
+
+ doubleEma1 := helper.MultiplyBy(ema1[0], 2)
+ doubleEma1 = helper.Buffered(doubleEma1, d.Ema2.Period)
+
+ return helper.Subtract(doubleEma1, ema2)
+}
+
+// IdlePeriod is the initial period that DEMA won't yield any results.
+func (d *Dema[T]) IdlePeriod() int {
+ return d.Ema1.Period + d.Ema2.Period - 2
+}
diff --git a/trend/dema_test.go b/trend/dema_test.go
new file mode 100644
index 0000000..07bfc33
--- /dev/null
+++ b/trend/dema_test.go
@@ -0,0 +1,39 @@
+// Copyright (c) 2021-2023 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/helper"
+ "github.com/cinar/indicator/trend"
+)
+
+func TestDema(t *testing.T) {
+ input := helper.SliceToChan([]float64{
+ 22.27, 22.19, 22.08, 22.17, 22.18, 22.13, 22.23, 22.43, 22.24, 22.29,
+ 22.15, 22.39, 22.38, 22.61, 23.36, 24.05, 23.75, 23.83, 23.95, 23.63,
+ 23.82, 23.87, 23.65, 23.19, 23.10, 23.33, 22.68, 23.10, 22.40, 22.17,
+ 22.15, 22.39, 22.38, 22.61, 23.36, 24.05, 23.75, 23.83, 23.95, 23.63,
+ 22.27, 22.19, 22.08, 22.17, 22.18, 22.13, 22.23, 22.43, 22.24, 22.29,
+ 23.82, 23.87, 23.65, 23.19, 23.10, 23.33, 22.68, 23.10, 22.40, 22.17,
+ })
+
+ expected := helper.SliceToChan([]float64{
+ 22.51, 22.7, 22.88, 23.01, 23.06, 23.08, 23.16, 23.11, 23.15, 23.05,
+ 22.92, 22.81, 22.74, 22.66, 22.63, 22.74, 22.97, 23.12, 23.27, 23.43,
+ 23.52, 23.34,
+ })
+
+ dema := trend.NewDema[float64]()
+ actual := dema.Compute(input)
+
+ actual = helper.RoundDigits(actual, 2)
+
+ err := helper.CheckEquals(actual, expected)
+ if err != nil {
+ t.Fatal(err)
+ }
+}