diff --git a/README.md b/README.md
index e141b44c..68c635d0 100644
--- a/README.md
+++ b/README.md
@@ -151,6 +151,7 @@ The following list of strategies are currently supported by this package:
- [Double Exponential Moving Average (DEMA) Strategy](strategy/trend/README.md#DemaStrategy)
- [Envelope Strategy](strategy/trend/README.md#EnvelopeStrategy)
- [Golden Cross Strategy](strategy/trend/README.md#GoldenCrossStrategy)
+- [Hull Moving Average (HMA) Strategy](strategy/trend/README.md#HmaStrategy)
- [Kaufman's Adaptive Moving Average (KAMA) Strategy](strategy/trend/README.md#KamaStrategy)
- [Moving Average Convergence Divergence (MACD) Strategy](strategy/trend/README.md#MacdStrategy)
- [Qstick Strategy](strategy/trend/README.md#QstickStrategy)
diff --git a/strategy/trend/README.md b/strategy/trend/README.md
index 83575f56..246563c6 100644
--- a/strategy/trend/README.md
+++ b/strategy/trend/README.md
@@ -74,6 +74,12 @@ The information provided on this project is strictly for informational purposes
- [func \(t \*GoldenCrossStrategy\) Compute\(c \<\-chan \*asset.Snapshot\) \<\-chan strategy.Action](<#GoldenCrossStrategy.Compute>)
- [func \(\*GoldenCrossStrategy\) Name\(\) string](<#GoldenCrossStrategy.Name>)
- [func \(t \*GoldenCrossStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#GoldenCrossStrategy.Report>)
+- [type HmaStrategy](<#HmaStrategy>)
+ - [func NewHmaStrategy\(\) \*HmaStrategy](<#NewHmaStrategy>)
+ - [func NewHmaStrategyWith\(period int\) \*HmaStrategy](<#NewHmaStrategyWith>)
+ - [func \(h \*HmaStrategy\) Compute\(snapshots \<\-chan \*asset.Snapshot\) \<\-chan strategy.Action](<#HmaStrategy.Compute>)
+ - [func \(h \*HmaStrategy\) Name\(\) string](<#HmaStrategy.Name>)
+ - [func \(h \*HmaStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#HmaStrategy.Report>)
- [type KamaStrategy](<#KamaStrategy>)
- [func NewKamaStrategy\(\) \*KamaStrategy](<#NewKamaStrategy>)
- [func NewKamaStrategyWith\(erPeriod, fastScPeriod, slowScPeriod int\) \*KamaStrategy](<#NewKamaStrategyWith>)
@@ -218,6 +224,15 @@ const (
)
```
+
+
+```go
+const (
+ // DefaultHmaStrategyPeriod is the default period for the HMA strategy.
+ DefaultHmaStrategyPeriod = 9
+)
+```
+
```go
@@ -730,6 +745,63 @@ func (t *GoldenCrossStrategy) Report(c <-chan *asset.Snapshot) *helper.Report
Report processes the provided asset snapshots and generates a report annotated with the recommended actions.
+
+## type [HmaStrategy]()
+
+HmaStrategy represents the configuration parameters for calculating the HMA strategy. A closing price crossing above the HMA suggests a bullish trend, while crossing below the HMA indicates a bearish trend.
+
+```go
+type HmaStrategy struct {
+ // Hma represents the configuration parameters for calculating the Hull Moving Average.
+ Hma *trend.Hma[float64]
+}
+```
+
+
+### func [NewHmaStrategy]()
+
+```go
+func NewHmaStrategy() *HmaStrategy
+```
+
+NewHmaStrategy function initializes a new HMA strategy instance with the default parameters.
+
+
+### func [NewHmaStrategyWith]()
+
+```go
+func NewHmaStrategyWith(period int) *HmaStrategy
+```
+
+NewHmaStrategyWith function initializes a new HMA strategy instance with the given period.
+
+
+### func \(\*HmaStrategy\) [Compute]()
+
+```go
+func (h *HmaStrategy) Compute(snapshots <-chan *asset.Snapshot) <-chan strategy.Action
+```
+
+Compute processes the provided asset snapshots and generates a stream of actionable recommendations.
+
+
+### func \(\*HmaStrategy\) [Name]()
+
+```go
+func (h *HmaStrategy) Name() string
+```
+
+Name returns the name of the strategy.
+
+
+### func \(\*HmaStrategy\) [Report]()
+
+```go
+func (h *HmaStrategy) Report(c <-chan *asset.Snapshot) *helper.Report
+```
+
+Report processes the provided asset snapshots and generates a report annotated with the recommended actions.
+
## type [KamaStrategy]()
diff --git a/strategy/trend/hma_strategy.go b/strategy/trend/hma_strategy.go
new file mode 100644
index 00000000..f29013cc
--- /dev/null
+++ b/strategy/trend/hma_strategy.go
@@ -0,0 +1,99 @@
+// Copyright (c) 2021-2026 Onur Cinar.
+// The source code is provided under GNU AGPLv3 License.
+// https://github.com/cinar/indicator
+
+package trend
+
+import (
+ "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 (
+ // DefaultHmaStrategyPeriod is the default period for the HMA strategy.
+ DefaultHmaStrategyPeriod = 9
+)
+
+// HmaStrategy represents the configuration parameters for calculating the HMA strategy. A closing price crossing
+// above the HMA suggests a bullish trend, while crossing below the HMA indicates a bearish trend.
+type HmaStrategy struct {
+ // Hma represents the configuration parameters for calculating the Hull Moving Average.
+ Hma *trend.Hma[float64]
+}
+
+// NewHmaStrategy function initializes a new HMA strategy instance with the default parameters.
+func NewHmaStrategy() *HmaStrategy {
+ return NewHmaStrategyWith(DefaultHmaStrategyPeriod)
+}
+
+// NewHmaStrategyWith function initializes a new HMA strategy instance with the given period.
+func NewHmaStrategyWith(period int) *HmaStrategy {
+ return &HmaStrategy{
+ Hma: trend.NewHmaWithPeriod[float64](period),
+ }
+}
+
+// Name returns the name of the strategy.
+func (h *HmaStrategy) Name() string {
+ return h.Hma.String() + " Strategy"
+}
+
+// Compute processes the provided asset snapshots and generates a stream of actionable recommendations.
+func (h *HmaStrategy) Compute(snapshots <-chan *asset.Snapshot) <-chan strategy.Action {
+ closingsSplice := helper.Duplicate(asset.SnapshotsAsClosings(snapshots), 2)
+ closingsSplice[1] = helper.Skip(closingsSplice[1], h.Hma.IdlePeriod())
+
+ hmas := h.Hma.Compute(closingsSplice[0])
+
+ actions := helper.Operate(hmas, closingsSplice[1], func(hma, closing float64) strategy.Action {
+ if closing > hma {
+ return strategy.Buy
+ }
+
+ if closing < hma {
+ return strategy.Sell
+ }
+
+ return strategy.Hold
+ })
+
+ // HMA starts only after a full period.
+ actions = helper.Shift(actions, h.Hma.IdlePeriod(), strategy.Hold)
+
+ return actions
+}
+
+// Report processes the provided asset snapshots and generates a report annotated with the recommended actions.
+func (h *HmaStrategy) Report(c <-chan *asset.Snapshot) *helper.Report {
+ //
+ // snapshots[0] -> dates
+ // snapshots[1] -> closings[0] -> closings
+ // closings[1] -> hma
+ // snapshots[2] -> actions -> annotations
+ // -> outcomes
+ //
+ snapshotsSplice := helper.Duplicate(c, 3)
+
+ dates := asset.SnapshotsAsDates(snapshotsSplice[0])
+ closingsSplice := helper.Duplicate(asset.SnapshotsAsClosings(snapshotsSplice[1]), 2)
+
+ hmas := h.Hma.Compute(closingsSplice[0])
+ hmas = helper.Shift(hmas, h.Hma.IdlePeriod(), 0)
+
+ actions, outcomes := strategy.ComputeWithOutcome(h, snapshotsSplice[2])
+ annotations := strategy.ActionsToAnnotations(actions)
+ outcomes = helper.MultiplyBy(outcomes, 100)
+
+ report := helper.NewReport(h.Name(), dates)
+ report.AddChart()
+
+ report.AddColumn(helper.NewNumericReportColumn("Close", closingsSplice[1]))
+ report.AddColumn(helper.NewNumericReportColumn("HMA", hmas))
+ report.AddColumn(helper.NewAnnotationReportColumn(annotations))
+
+ report.AddColumn(helper.NewNumericReportColumn("Outcome", outcomes), 1)
+
+ return report
+}
diff --git a/strategy/trend/hma_strategy_test.go b/strategy/trend/hma_strategy_test.go
new file mode 100644
index 00000000..212b855c
--- /dev/null
+++ b/strategy/trend/hma_strategy_test.go
@@ -0,0 +1,73 @@
+// Copyright (c) 2021-2026 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 TestHmaStrategy(t *testing.T) {
+ snapshots, err := helper.ReadFromCsvFile[asset.Snapshot]("testdata/brk-b.csv")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ results, err := helper.ReadFromCsvFile[strategy.Result]("testdata/hma_strategy.csv")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ expected := helper.Map(results, func(r *strategy.Result) strategy.Action { return r.Action })
+
+ h := trend.NewHmaStrategy()
+ actual := h.Compute(snapshots)
+
+ err = helper.CheckEquals(actual, expected)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestHmaStrategyReport(t *testing.T) {
+ snapshots, err := helper.ReadFromCsvFile[asset.Snapshot]("testdata/brk-b.csv")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ h := trend.NewHmaStrategy()
+
+ report := h.Report(snapshots)
+
+ fileName := "hma_strategy.html"
+ defer helper.Remove(t, fileName)
+
+ err = report.WriteToFile(fileName)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestHmaStrategyHold(t *testing.T) {
+ snapshots := helper.SliceToChan([]*asset.Snapshot{
+ {Close: 10},
+ {Close: 10},
+ {Close: 10},
+ })
+
+ h := trend.NewHmaStrategyWith(2)
+ actions := h.Compute(snapshots)
+
+ for action := range actions {
+ if action != strategy.Hold {
+ t.Fatalf("expected Hold action, got %v", action)
+ }
+ }
+}
+
diff --git a/strategy/trend/testdata/hma_strategy.csv b/strategy/trend/testdata/hma_strategy.csv
new file mode 100644
index 00000000..0b32f52b
--- /dev/null
+++ b/strategy/trend/testdata/hma_strategy.csv
@@ -0,0 +1,252 @@
+Action
+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
+-1
+1
+1
+1
+1
+1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+1
+1
+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 ad6c95e0..d42e195f 100644
--- a/strategy/trend/trend.go
+++ b/strategy/trend/trend.go
@@ -31,6 +31,7 @@ func AllStrategies() []strategy.Strategy {
NewCfoStrategy(),
NewDemaStrategy(),
NewGoldenCrossStrategy(),
+ NewHmaStrategy(),
NewKamaStrategy(),
NewKdjStrategy(),
NewMacdStrategy(),
diff --git a/strategy/trend/trend_test.go b/strategy/trend/trend_test.go
new file mode 100644
index 00000000..3438b724
--- /dev/null
+++ b/strategy/trend/trend_test.go
@@ -0,0 +1,18 @@
+// Copyright (c) 2021-2026 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/strategy/trend"
+)
+
+func TestAllStrategies(t *testing.T) {
+ strategies := trend.AllStrategies()
+ if len(strategies) != 19 {
+ t.Fatalf("expected 19 strategies, got %d", len(strategies))
+ }
+}