Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
72 changes: 72 additions & 0 deletions strategy/trend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>)
Expand Down Expand Up @@ -218,6 +224,15 @@ const (
)
```

<a name="DefaultHmaStrategyPeriod"></a>

```go
const (
// DefaultHmaStrategyPeriod is the default period for the HMA strategy.
DefaultHmaStrategyPeriod = 9
)
```

<a name="DefaultTsiStrategySignalPeriod"></a>

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

<a name="HmaStrategy"></a>
## type [HmaStrategy](<https://github.com/cinar/indicator/blob/master/strategy/trend/hma_strategy.go#L21-L24>)

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

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

```go
func NewHmaStrategy() *HmaStrategy
```

NewHmaStrategy function initializes a new HMA strategy instance with the default parameters.

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

```go
func NewHmaStrategyWith(period int) *HmaStrategy
```

NewHmaStrategyWith function initializes a new HMA strategy instance with the given period.

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

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

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

```go
func (h *HmaStrategy) Name() string
```

Name returns the name of the strategy.

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

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

<a name="KamaStrategy"></a>
## type [KamaStrategy](<https://github.com/cinar/indicator/blob/master/strategy/trend/kama_strategy.go#L16-L19>)

Expand Down
99 changes: 99 additions & 0 deletions strategy/trend/hma_strategy.go
Original file line number Diff line number Diff line change
@@ -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
}
73 changes: 73 additions & 0 deletions strategy/trend/hma_strategy_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}

Loading
Loading