Skip to content
Closed
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
100 changes: 100 additions & 0 deletions strategy/momentum/elder_ray_strategy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright (c) 2021-2026 Onur Cinar.
// The source code is provided under GNU AGPLv3 License.
// https://github.com/cinar/indicator

package momentum

import (
"github.com/cinar/indicator/v2/asset"
"github.com/cinar/indicator/v2/helper"
"github.com/cinar/indicator/v2/momentum"
"github.com/cinar/indicator/v2/strategy"
)

// ElderRayStrategy represents the configuration parameters for calculating the Elder Ray strategy.
// A positive Bull Power suggests a Buy signal, while a negative Bear Power suggests a Sell signal.
type ElderRayStrategy struct {
// ElderRay represents the configuration parameters for calculating the Elder-Ray Index.
ElderRay *momentum.ElderRay[float64]
}

// NewElderRayStrategy function initializes a new Elder Ray strategy instance with the default parameters.
func NewElderRayStrategy() *ElderRayStrategy {
return &ElderRayStrategy{
ElderRay: momentum.NewElderRay[float64](),
}
}

// Name returns the name of the strategy.
func (*ElderRayStrategy) Name() string {
return "Elder Ray Strategy"
}

// Compute processes the provided asset snapshots and generates a stream of actionable recommendations.
func (e *ElderRayStrategy) Compute(snapshots <-chan *asset.Snapshot) <-chan strategy.Action {
snapshotsSplice := helper.Duplicate(snapshots, 3)

highs := asset.SnapshotsAsHighs(snapshotsSplice[0])
lows := asset.SnapshotsAsLows(snapshotsSplice[1])
closings := asset.SnapshotsAsClosings(snapshotsSplice[2])

bullPower, bearPower := e.ElderRay.Compute(highs, lows, closings)

actions := helper.Operate(bullPower, bearPower, func(bull, bear float64) strategy.Action {
if bull > 0 {
return strategy.Buy
}

if bear < 0 {
return strategy.Sell
}

return strategy.Hold
})

// Elder Ray starts only after the idle period.
actions = helper.Shift(actions, e.ElderRay.IdlePeriod(), strategy.Hold)

return actions
}

// Report processes the provided asset snapshots and generates a report annotated with the recommended actions.
func (e *ElderRayStrategy) Report(c <-chan *asset.Snapshot) *helper.Report {
//
// snapshots[0] -> dates
// snapshots[1] -> highs -|
// snapshots[2] -> lows -+-> ElderRay.Compute -> bullPower, bearPower
// snapshots[3] -> closings-|
// snapshots[4] -> closings -> close
// snapshots[5] -> actions -> annotations
// -> outcomes
//
snapshots := helper.Duplicate(c, 6)

dates := asset.SnapshotsAsDates(snapshots[0])
highs := asset.SnapshotsAsHighs(snapshots[1])
lows := asset.SnapshotsAsLows(snapshots[2])
closings := asset.SnapshotsAsClosings(snapshots[3])
closings2 := asset.SnapshotsAsClosings(snapshots[4])

bullPower, bearPower := e.ElderRay.Compute(highs, lows, closings)
bullPower = helper.Shift(bullPower, e.ElderRay.IdlePeriod(), 0)
bearPower = helper.Shift(bearPower, e.ElderRay.IdlePeriod(), 0)

actions, outcomes := strategy.ComputeWithOutcome(e, snapshots[5])
annotations := strategy.ActionsToAnnotations(actions)
outcomes = helper.MultiplyBy(outcomes, 100)

report := helper.NewReport(e.Name(), dates)
report.AddChart()
report.AddChart()

report.AddColumn(helper.NewNumericReportColumn("Close", closings2))
report.AddColumn(helper.NewNumericReportColumn("Bull Power", bullPower), 1)
report.AddColumn(helper.NewNumericReportColumn("Bear Power", bearPower), 1)
report.AddColumn(helper.NewAnnotationReportColumn(annotations), 0, 1)

report.AddColumn(helper.NewNumericReportColumn("Outcome", outcomes), 2)

return report
}
55 changes: 55 additions & 0 deletions strategy/momentum/elder_ray_strategy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) 2021-2026 Onur Cinar.
// The source code is provided under GNU AGPLv3 License.
// https://github.com/cinar/indicator

package momentum_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/momentum"
)

func TestElderRayStrategy(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/elder_ray_strategy.csv")
if err != nil {
t.Fatal(err)
}

expected := helper.Map(results, func(r *strategy.Result) strategy.Action { return r.Action })

er := momentum.NewElderRayStrategy()
actual := er.Compute(snapshots)

err = helper.CheckEquals(actual, expected)
if err != nil {
t.Fatal(err)
}
}

func TestElderRayStrategyReport(t *testing.T) {
snapshots, err := helper.ReadFromCsvFile[asset.Snapshot]("testdata/brk-b.csv")
if err != nil {
t.Fatal(err)
}

er := momentum.NewElderRayStrategy()

report := er.Report(snapshots)

fileName := "elder_ray_strategy.html"
defer helper.Remove(t, fileName)

err = report.WriteToFile(fileName)
if err != nil {
t.Fatal(err)
}
}
1 change: 1 addition & 0 deletions strategy/momentum/momentum.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import "github.com/cinar/indicator/v2/strategy"
func AllStrategies() []strategy.Strategy {
return []strategy.Strategy{
NewAwesomeOscillatorStrategy(),
NewElderRayStrategy(),
NewIchimokuCloudStrategy(),
NewRsiStrategy(),
NewStochasticRsiStrategy(),
Expand Down
Loading
Loading