Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Money Flow Index Strategy #234

Merged
merged 7 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,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 (CCI)](trend/README.md#type-cci)
- [Envelope](trend/README.md#type-envelope)
- [Hull Moving Average (HMA)](trend/README.md#type-hma)
- [Double Exponential Moving Average (DEMA)](trend/README.md#type-dema)
- [Exponential Moving Average (EMA)](trend/README.md#type-ema)
Expand Down Expand Up @@ -110,6 +111,7 @@ The following list of strategies are currently supported by this package:
- Chande Forecast Oscillator Strategy
- [Community Channel Index (CCI) Strategy](strategy/trend/README.md#type-ccistrategy)
- [Double Exponential Moving Average (DEMA) Strategy](strategy/trend/README.md#type-demastrategy)
- [Envelope Strategy](strategy/trend/README.md#type-envelope)
- [Golden Cross Strategy](strategy/trend/README.md#type-goldencrossstrategy)
- [Kaufman's Adaptive Moving Average (KAMA) Strategy](strategy/trend/README.md#type-kamastrategy)
- [Moving Average Convergence Divergence (MACD) Strategy](strategy/trend/README.md#type-macdstrategy)
Expand Down Expand Up @@ -138,7 +140,7 @@ The following list of strategies are currently supported by this package:
- Chaikin Money Flow Strategy
- Ease of Movement Strategy
- Force Index Strategy
- Money Flow Index Strategy
- [Money Flow Index Strategy](strategy/volume/README.md#type-moneyflowindexstrategy)
- Negative Volume Index Strategy
- Volume Weighted Average Price Strategy

Expand Down
8 changes: 4 additions & 4 deletions strategy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ The information provided on this project is strictly for informational purposes
- [type Action](<#Action>)
- [func \(a Action\) Annotation\(\) string](<#Action.Annotation>)
- [type AndStrategy](<#AndStrategy>)
- [func NewAndStrategy\(name string\) \*AndStrategy](<#NewAndStrategy>)
- [func NewAndStrategy\(name string, strategies ...Strategy\) \*AndStrategy](<#NewAndStrategy>)
- [func \(a \*AndStrategy\) Compute\(snapshots \<\-chan \*asset.Snapshot\) \<\-chan Action](<#AndStrategy.Compute>)
- [func \(a \*AndStrategy\) Name\(\) string](<#AndStrategy.Name>)
- [func \(a \*AndStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#AndStrategy.Report>)
Expand All @@ -51,7 +51,7 @@ The information provided on this project is strictly for informational purposes
- [func \(a \*MajorityStrategy\) Name\(\) string](<#MajorityStrategy.Name>)
- [func \(a \*MajorityStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#MajorityStrategy.Report>)
- [type OrStrategy](<#OrStrategy>)
- [func NewOrStrategy\(name string\) \*OrStrategy](<#NewOrStrategy>)
- [func NewOrStrategy\(name string, strategies ...Strategy\) \*OrStrategy](<#NewOrStrategy>)
- [func \(a \*OrStrategy\) Compute\(snapshots \<\-chan \*asset.Snapshot\) \<\-chan Action](<#OrStrategy.Compute>)
- [func \(a \*OrStrategy\) Name\(\) string](<#OrStrategy.Name>)
- [func \(a \*OrStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#OrStrategy.Report>)
Expand Down Expand Up @@ -194,7 +194,7 @@ type AndStrategy struct {
### func [NewAndStrategy](<https://github.com/cinar/indicator/blob/master/strategy/and_strategy.go#L26>)

```go
func NewAndStrategy(name string) *AndStrategy
func NewAndStrategy(name string, strategies ...Strategy) *AndStrategy
```

NewAndStrategy function initializes an empty and strategies group with the given name.
Expand Down Expand Up @@ -347,7 +347,7 @@ type OrStrategy struct {
### func [NewOrStrategy](<https://github.com/cinar/indicator/blob/master/strategy/or_strategy.go#L23>)

```go
func NewOrStrategy(name string) *OrStrategy
func NewOrStrategy(name string, strategies ...Strategy) *OrStrategy
```

NewOrStrategy function initializes an empty or strategies group with the given name.
Expand Down
1 change: 1 addition & 0 deletions strategy/testdata/x
Original file line number Diff line number Diff line change
@@ -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 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 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
63 changes: 63 additions & 0 deletions strategy/trend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ The information provided on this project is strictly for informational purposes
- [func \(d \*DemaStrategy\) Compute\(c \<\-chan \*asset.Snapshot\) \<\-chan strategy.Action](<#DemaStrategy.Compute>)
- [func \(\*DemaStrategy\) Name\(\) string](<#DemaStrategy.Name>)
- [func \(d \*DemaStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#DemaStrategy.Report>)
- [type EnvelopeStrategy](<#EnvelopeStrategy>)
- [func NewEnvelopeStrategy\(\) \*EnvelopeStrategy](<#NewEnvelopeStrategy>)
- [func NewEnvelopeStrategyWith\(envelope \*trend.Envelope\[float64\]\) \*EnvelopeStrategy](<#NewEnvelopeStrategyWith>)
- [func \(e \*EnvelopeStrategy\) Compute\(snapshots \<\-chan \*asset.Snapshot\) \<\-chan strategy.Action](<#EnvelopeStrategy.Compute>)
- [func \(e \*EnvelopeStrategy\) Name\(\) string](<#EnvelopeStrategy.Name>)
- [func \(e \*EnvelopeStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#EnvelopeStrategy.Report>)
- [type GoldenCrossStrategy](<#GoldenCrossStrategy>)
- [func NewGoldenCrossStrategy\(\) \*GoldenCrossStrategy](<#NewGoldenCrossStrategy>)
- [func NewGoldenCrossStrategyWith\(fastPeriod, slowPeriod int\) \*GoldenCrossStrategy](<#NewGoldenCrossStrategyWith>)
Expand Down Expand Up @@ -436,6 +442,63 @@ 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.

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

EnvelopeStrategy represents the configuration parameters for calculating the Envelope strategy. When the closing is above the upper band suggests a Sell recommendation, and when the closing is below the lower band suggests a buy recommendation.

```go
type EnvelopeStrategy struct {
// Envelope is the envelope indicator instance.
Envelope *trend.Envelope[float64]
}
```

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

```go
func NewEnvelopeStrategy() *EnvelopeStrategy
```

NewEnvelopeStrategy function initializes a new Envelope strategy with the default parameters.

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

```go
func NewEnvelopeStrategyWith(envelope *trend.Envelope[float64]) *EnvelopeStrategy
```

NewEnvelopeStrategyWith function initializes a new Envelope strategy with the given Envelope instance.

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

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

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

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

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

Name returns the name of the strategy.

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

```go
func (e *EnvelopeStrategy) Report(c <-chan *asset.Snapshot) *helper.Report
```

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

<a name="GoldenCrossStrategy"></a>
## type [GoldenCrossStrategy](<https://github.com/cinar/indicator/blob/master/strategy/trend/golden_cross_strategy.go#L27-L33>)

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

package trend

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

// EnvelopeStrategy represents the configuration parameters for calculating the Envelope strategy. When the closing
// is above the upper band suggests a Sell recommendation, and when the closing is below the lower band suggests a
// buy recommendation.
type EnvelopeStrategy struct {
// Envelope is the envelope indicator instance.
Envelope *trend.Envelope[float64]
}

// NewEnvelopeStrategy function initializes a new Envelope strategy with the default parameters.
func NewEnvelopeStrategy() *EnvelopeStrategy {
return NewEnvelopeStrategyWith(
trend.NewEnvelopeWithSma[float64](),
)
}

// NewEnvelopeStrategyWith function initializes a new Envelope strategy with the given Envelope instance.
func NewEnvelopeStrategyWith(envelope *trend.Envelope[float64]) *EnvelopeStrategy {
return &EnvelopeStrategy{
Envelope: envelope,
}
}

// Name returns the name of the strategy.
func (e *EnvelopeStrategy) Name() string {
return fmt.Sprintf("Envelope Strategy (%s)", e.Envelope.String())
}

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

closingsSplice[1] = helper.Skip(closingsSplice[1], e.Envelope.IdlePeriod())

uppers, middles, lowers := e.Envelope.Compute(closingsSplice[0])
go helper.Drain(middles)

actions := helper.Operate3(uppers, lowers, closingsSplice[1], func(upper, lower, closing float64) strategy.Action {
// When the closing is below the lower band suggests a buy recommendation.
if closing < lower {
return strategy.Buy
}

// When the closing is above the upper band suggests a Sell recommendation.
if closing > upper {
return strategy.Sell
}

return strategy.Hold
})

// Envelope start only after a full period.
actions = helper.Shift(actions, e.Envelope.IdlePeriod(), strategy.Hold)

return actions
}

// Report processes the provided asset snapshots and generates a report annotated with the recommended actions.
func (e *EnvelopeStrategy) Report(c <-chan *asset.Snapshot) *helper.Report {
//
// snapshots[0] -> dates
// snapshots[1] -> closings[0] -> closings
// closings[1] -> envelope -> upper
// -> middle
// -> lower
// snapshots[2] -> actions -> annotations
// -> outcomes
//
snapshotsSplice := helper.Duplicate(c, 3)

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

closingsSplice := helper.Duplicate(asset.SnapshotsAsClosings(snapshotsSplice[1]), 2)
closingsSplice[0] = helper.Skip(closingsSplice[0], e.Envelope.IdlePeriod())

uppers, middles, lowers := e.Envelope.Compute(closingsSplice[1])

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

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

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

report.AddColumn(helper.NewNumericReportColumn("Close", closingsSplice[0]))
report.AddColumn(helper.NewNumericReportColumn("Upper", uppers))
report.AddColumn(helper.NewNumericReportColumn("Middle", middles))
report.AddColumn(helper.NewNumericReportColumn("Lower", lowers))
report.AddColumn(helper.NewAnnotationReportColumn(annotations))

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

return report
}
55 changes: 55 additions & 0 deletions strategy/trend/envelope_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 trend_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/trend"
)

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

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

envelope := trend.NewEnvelopeStrategy()
actual := envelope.Compute(snapshots)

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

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

envelope := trend.NewEnvelopeStrategy()
report := envelope.Report(snapshots)

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

err = report.WriteToFile(fileName)
if err != nil {
t.Fatal(err)
}
}
cinar marked this conversation as resolved.
Show resolved Hide resolved
Loading
Loading