Skip to content

Commit

Permalink
Ease of Movement Strategy is added.
Browse files Browse the repository at this point in the history
  • Loading branch information
cinar committed Oct 20, 2024
1 parent 4e64211 commit df96b66
Show file tree
Hide file tree
Showing 6 changed files with 491 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ The following list of strategies are currently supported by this package:
### 📢 Volume Strategies

- [Chaikin Money Flow Strategy](strategy/volume/README.md#type-chaikinmoneyflowstrategy)
- Ease of Movement Strategy
- [Ease of Movement Strategy](strategy/volume/README.md#type-easeofmovementstrategy)
- [Force Index Strategy](strategy/volume/README.md#type-forceindexstrategy)
- [Money Flow Index Strategy](strategy/volume/README.md#type-moneyflowindexstrategy)
- [Negative Volume Index Strategy](strategy/volume/README.md#type-negativevolumeindexstrategy)
Expand Down
63 changes: 63 additions & 0 deletions strategy/volume/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ The information provided on this project is strictly for informational purposes
- [func \(c \*ChaikinMoneyFlowStrategy\) Compute\(snapshots \<\-chan \*asset.Snapshot\) \<\-chan strategy.Action](<#ChaikinMoneyFlowStrategy.Compute>)
- [func \(c \*ChaikinMoneyFlowStrategy\) Name\(\) string](<#ChaikinMoneyFlowStrategy.Name>)
- [func \(c \*ChaikinMoneyFlowStrategy\) Report\(snapshots \<\-chan \*asset.Snapshot\) \*helper.Report](<#ChaikinMoneyFlowStrategy.Report>)
- [type EaseOfMovementStrategy](<#EaseOfMovementStrategy>)
- [func NewEaseOfMovementStrategy\(\) \*EaseOfMovementStrategy](<#NewEaseOfMovementStrategy>)
- [func NewEaseOfMovementStrategyWith\(period int\) \*EaseOfMovementStrategy](<#NewEaseOfMovementStrategyWith>)
- [func \(e \*EaseOfMovementStrategy\) Compute\(snapshots \<\-chan \*asset.Snapshot\) \<\-chan strategy.Action](<#EaseOfMovementStrategy.Compute>)
- [func \(e \*EaseOfMovementStrategy\) Name\(\) string](<#EaseOfMovementStrategy.Name>)
- [func \(e \*EaseOfMovementStrategy\) Report\(snapshots \<\-chan \*asset.Snapshot\) \*helper.Report](<#EaseOfMovementStrategy.Report>)
- [type ForceIndexStrategy](<#ForceIndexStrategy>)
- [func NewForceIndexStrategy\(\) \*ForceIndexStrategy](<#NewForceIndexStrategy>)
- [func NewForceIndexStrategyWith\(period int\) \*ForceIndexStrategy](<#NewForceIndexStrategyWith>)
Expand Down Expand Up @@ -147,6 +153,63 @@ func (c *ChaikinMoneyFlowStrategy) Report(snapshots <-chan *asset.Snapshot) *hel

Report function processes the provided asset snapshots and generates a report annotated with the recommended actions.

<a name="EaseOfMovementStrategy"></a>
## type [EaseOfMovementStrategy](<https://github.com/cinar/indicator/blob/master/strategy/volume/ease_of_movement_strategy.go#L18-L21>)

EaseOfMovementStrategy represents the configuration parameters for calculating the Ease of Movement strategy. Recommends a Buy action when it crosses above 0, and recommends a Sell action when it crosses below 0.

```go
type EaseOfMovementStrategy struct {
// EaseOfMovement is the Ease of Movement indicator instance.
EaseOfMovement *volume.Emv[float64]
}
```

<a name="NewEaseOfMovementStrategy"></a>
### func [NewEaseOfMovementStrategy](<https://github.com/cinar/indicator/blob/master/strategy/volume/ease_of_movement_strategy.go#L25>)

```go
func NewEaseOfMovementStrategy() *EaseOfMovementStrategy
```

NewEaseOfMovementStrategy function initializes a new Ease of Movement strategy instance with the default parameters.

<a name="NewEaseOfMovementStrategyWith"></a>
### func [NewEaseOfMovementStrategyWith](<https://github.com/cinar/indicator/blob/master/strategy/volume/ease_of_movement_strategy.go#L33>)

```go
func NewEaseOfMovementStrategyWith(period int) *EaseOfMovementStrategy
```

NewEaseOfMovementStrategyWith function initializes a new Ease of Movement strategy instance with the given parameters.

<a name="EaseOfMovementStrategy.Compute"></a>
### func \(\*EaseOfMovementStrategy\) [Compute](<https://github.com/cinar/indicator/blob/master/strategy/volume/ease_of_movement_strategy.go#L45>)

```go
func (e *EaseOfMovementStrategy) Compute(snapshots <-chan *asset.Snapshot) <-chan strategy.Action
```

Compute function processes the provided asset snapshots and generates a stream of actionable recommendations.

<a name="EaseOfMovementStrategy.Name"></a>
### func \(\*EaseOfMovementStrategy\) [Name](<https://github.com/cinar/indicator/blob/master/strategy/volume/ease_of_movement_strategy.go#L40>)

```go
func (e *EaseOfMovementStrategy) Name() string
```

Name function returns the name of the strategy.

<a name="EaseOfMovementStrategy.Report"></a>
### func \(\*EaseOfMovementStrategy\) [Report](<https://github.com/cinar/indicator/blob/master/strategy/volume/ease_of_movement_strategy.go#L73>)

```go
func (e *EaseOfMovementStrategy) Report(snapshots <-chan *asset.Snapshot) *helper.Report
```

Report function processes the provided asset snapshots and generates a report annotated with the recommended actions.

<a name="ForceIndexStrategy"></a>
## type [ForceIndexStrategy](<https://github.com/cinar/indicator/blob/master/strategy/volume/force_index_strategy.go#L18-L21>)

Expand Down
119 changes: 119 additions & 0 deletions strategy/volume/ease_of_movement_strategy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright (c) 2021-2024 Onur Cinar.
// The source code is provided under GNU AGPLv3 License.
// https://github.com/cinar/indicator

package volume

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/volume"
)

// EaseOfMovementStrategy represents the configuration parameters for calculating the Ease of Movement strategy.
// Recommends a Buy action when it crosses above 0, and recommends a Sell action when it crosses below 0.
type EaseOfMovementStrategy struct {
// EaseOfMovement is the Ease of Movement indicator instance.
EaseOfMovement *volume.Emv[float64]
}

// NewEaseOfMovementStrategy function initializes a new Ease of Movement strategy instance with the
// default parameters.
func NewEaseOfMovementStrategy() *EaseOfMovementStrategy {
return NewEaseOfMovementStrategyWith(
volume.DefaultEmvPeriod,
)
}

// NewEaseOfMovementStrategyWith function initializes a new Ease of Movement strategy instance with the
// given parameters.
func NewEaseOfMovementStrategyWith(period int) *EaseOfMovementStrategy {
return &EaseOfMovementStrategy{
EaseOfMovement: volume.NewEmvWithPeriod[float64](period),
}
}

// Name function returns the name of the strategy.
func (e *EaseOfMovementStrategy) Name() string {
return fmt.Sprintf("Ease of Movement Strategy (%d)", e.EaseOfMovement.IdlePeriod()+1)
}

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

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

emvs := e.EaseOfMovement.Compute(highs, lows, volumes)

actions := helper.Map(emvs, func(emv float64) strategy.Action {
if emv > 0 {
return strategy.Buy
}

if emv < 0 {
return strategy.Sell
}

return strategy.Hold
})

// Ease of Movement starts only after a full period.
actions = helper.Shift(actions, e.EaseOfMovement.IdlePeriod(), strategy.Hold)

return actions
}

// Report function processes the provided asset snapshots and generates a report annotated with the recommended actions.
func (e *EaseOfMovementStrategy) Report(snapshots <-chan *asset.Snapshot) *helper.Report {
//
// snapshots[0] -> dates
// snapshots[1] -> highs |
// snapshots[2] -> lows |
// snapshots[3] -> volumes -> emv
// snapshots[4] -> closings
// snapshots[5] -> actions -> annotations
// -> outcomes
//
snapshotsSplice := helper.Duplicate(snapshots, 6)

dates := helper.Skip(
asset.SnapshotsAsDates(snapshotsSplice[0]),
e.EaseOfMovement.IdlePeriod(),
)

highs := asset.SnapshotsAsHighs(snapshotsSplice[1])
lows := asset.SnapshotsAsLows(snapshotsSplice[2])
volumes := asset.SnapshotsAsVolumes(snapshotsSplice[3])

closings := helper.Skip(
asset.SnapshotsAsClosings(snapshotsSplice[4]),
e.EaseOfMovement.IdlePeriod(),
)

emvs := e.EaseOfMovement.Compute(highs, lows, volumes)

actions, outcomes := strategy.ComputeWithOutcome(e, snapshotsSplice[5])
actions = helper.Skip(actions, e.EaseOfMovement.IdlePeriod())
outcomes = helper.Skip(outcomes, e.EaseOfMovement.IdlePeriod())

annotations := strategy.ActionsToAnnotations(actions)
outcomes = helper.MultiplyBy(outcomes, 100)

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

report.AddColumn(helper.NewNumericReportColumn("Close", closings))
report.AddColumn(helper.NewNumericReportColumn("Ease of Movement", emvs), 1)
report.AddColumn(helper.NewAnnotationReportColumn(annotations), 0, 1)

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

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

package volume_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/volume"
)

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

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

emvs := volume.NewEaseOfMovementStrategy()
actual := emvs.Compute(snapshots)

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

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

emvs := volume.NewEaseOfMovementStrategy()
report := emvs.Report(snapshots)

fileName := "ease_of_movement_strategy.html"
defer os.Remove(fileName)

err = report.WriteToFile(fileName)
if err != nil {
t.Fatal(err)
}
}
Loading

0 comments on commit df96b66

Please sign in to comment.