diff --git a/README.md b/README.md
index 72cc8e8..addf1c3 100644
--- a/README.md
+++ b/README.md
@@ -111,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)
@@ -139,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
diff --git a/strategy/volume/README.md b/strategy/volume/README.md
new file mode 100644
index 0000000..b010cc0
--- /dev/null
+++ b/strategy/volume/README.md
@@ -0,0 +1,123 @@
+
+
+# volume
+
+```go
+import "github.com/cinar/indicator/v2/strategy/volume"
+```
+
+Package volume contains the volume strategy functions.
+
+This package belongs to the Indicator project. Indicator is a Golang module that supplies a variety of technical indicators, strategies, and a backtesting framework for analysis.
+
+### License
+
+```
+Copyright (c) 2021-2024 Onur Cinar.
+The source code is provided under GNU AGPLv3 License.
+https://github.com/cinar/indicator
+```
+
+### Disclaimer
+
+The information provided on this project is strictly for informational purposes and is not to be construed as advice or solicitation to buy or sell any security.
+
+## Index
+
+- [Constants](<#constants>)
+- [func AllStrategies\(\) \[\]strategy.Strategy](<#AllStrategies>)
+- [type MoneyFlowIndexStrategy](<#MoneyFlowIndexStrategy>)
+ - [func NewMoneyFlowIndexStrategy\(\) \*MoneyFlowIndexStrategy](<#NewMoneyFlowIndexStrategy>)
+ - [func NewMoneyFlowIndexStrategyWith\(sellAt, buyAt float64\) \*MoneyFlowIndexStrategy](<#NewMoneyFlowIndexStrategyWith>)
+ - [func \(m \*MoneyFlowIndexStrategy\) Compute\(snapshots \<\-chan \*asset.Snapshot\) \<\-chan strategy.Action](<#MoneyFlowIndexStrategy.Compute>)
+ - [func \(m \*MoneyFlowIndexStrategy\) Name\(\) string](<#MoneyFlowIndexStrategy.Name>)
+ - [func \(m \*MoneyFlowIndexStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#MoneyFlowIndexStrategy.Report>)
+
+
+## Constants
+
+
+
+```go
+const (
+ // DefaultMoneyFlowIndexStrategySellAt is the default sell at of 80.
+ DefaultMoneyFlowIndexStrategySellAt = 80
+
+ // DefaultMoneyFlowIndexStrategyBuyAt is the default buy at of 20.
+ DefaultMoneyFlowIndexStrategyBuyAt = 20
+)
+```
+
+
+## func [AllStrategies]()
+
+```go
+func AllStrategies() []strategy.Strategy
+```
+
+AllStrategies returns a slice containing references to all available volume strategies.
+
+
+## type [MoneyFlowIndexStrategy]()
+
+MoneyFlowIndexStrategy represents the configuration parameters for calculating the Money Flow Index strategy. Recommends a Sell action when it crosses over 80, and recommends a Buy action when it crosses below 20.
+
+```go
+type MoneyFlowIndexStrategy struct {
+ // MoneyFlowIndex is the Money Flow Index indicator instance.
+ MoneyFlowIndex *volume.Mfi[float64]
+
+ // SellAt is the sell at value.
+ SellAt float64
+
+ // BuyAt is the buy at value.
+ BuyAt float64
+}
+```
+
+
+### func [NewMoneyFlowIndexStrategy]()
+
+```go
+func NewMoneyFlowIndexStrategy() *MoneyFlowIndexStrategy
+```
+
+NewMoneyFlowIndexStrategy function initializes a new Money Flow Index strategy instance with the default parameters.
+
+
+### func [NewMoneyFlowIndexStrategyWith]()
+
+```go
+func NewMoneyFlowIndexStrategyWith(sellAt, buyAt float64) *MoneyFlowIndexStrategy
+```
+
+NewMoneyFlowIndexStrategyWith function initializes a new Money Flow Index strategy instance with the given parameters.
+
+
+### func \(\*MoneyFlowIndexStrategy\) [Compute]()
+
+```go
+func (m *MoneyFlowIndexStrategy) Compute(snapshots <-chan *asset.Snapshot) <-chan strategy.Action
+```
+
+Compute processes the provided asset snapshots and generates a stream of actionable recommendations.
+
+
+### func \(\*MoneyFlowIndexStrategy\) [Name]()
+
+```go
+func (m *MoneyFlowIndexStrategy) Name() string
+```
+
+Name returns the name of the strategy.
+
+
+### func \(\*MoneyFlowIndexStrategy\) [Report]()
+
+```go
+func (m *MoneyFlowIndexStrategy) Report(c <-chan *asset.Snapshot) *helper.Report
+```
+
+Report processes the provided asset snapshots and generates a report annotated with the recommended actions.
+
+Generated by [gomarkdoc]()
diff --git a/strategy/volume/money_flow_index_strategy.go b/strategy/volume/money_flow_index_strategy.go
new file mode 100644
index 0000000..9e292d1
--- /dev/null
+++ b/strategy/volume/money_flow_index_strategy.go
@@ -0,0 +1,137 @@
+// 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"
+)
+
+const (
+ // DefaultMoneyFlowIndexStrategySellAt is the default sell at of 80.
+ DefaultMoneyFlowIndexStrategySellAt = 80
+
+ // DefaultMoneyFlowIndexStrategyBuyAt is the default buy at of 20.
+ DefaultMoneyFlowIndexStrategyBuyAt = 20
+)
+
+// MoneyFlowIndexStrategy represents the configuration parameters for calculating the Money Flow Index strategy.
+// Recommends a Sell action when it crosses over 80, and recommends a Buy action when it crosses below 20.
+type MoneyFlowIndexStrategy struct {
+ // MoneyFlowIndex is the Money Flow Index indicator instance.
+ MoneyFlowIndex *volume.Mfi[float64]
+
+ // SellAt is the sell at value.
+ SellAt float64
+
+ // BuyAt is the buy at value.
+ BuyAt float64
+}
+
+// NewMoneyFlowIndexStrategy function initializes a new Money Flow Index strategy instance with the default parameters.
+func NewMoneyFlowIndexStrategy() *MoneyFlowIndexStrategy {
+ return NewMoneyFlowIndexStrategyWith(
+ DefaultMoneyFlowIndexStrategySellAt,
+ DefaultMoneyFlowIndexStrategyBuyAt,
+ )
+}
+
+// NewMoneyFlowIndexStrategyWith function initializes a new Money Flow Index strategy instance with the
+// given parameters.
+func NewMoneyFlowIndexStrategyWith(sellAt, buyAt float64) *MoneyFlowIndexStrategy {
+ return &MoneyFlowIndexStrategy{
+ MoneyFlowIndex: volume.NewMfi[float64](),
+ SellAt: sellAt,
+ BuyAt: buyAt,
+ }
+}
+
+// Name returns the name of the strategy.
+func (m *MoneyFlowIndexStrategy) Name() string {
+ return fmt.Sprintf("Money Flow Index Strategy (%f)", m.SellAt)
+}
+
+// Compute processes the provided asset snapshots and generates a stream of actionable recommendations.
+func (m *MoneyFlowIndexStrategy) Compute(snapshots <-chan *asset.Snapshot) <-chan strategy.Action {
+ snapshotsSplice := helper.Duplicate(snapshots, 4)
+
+ highs := asset.SnapshotsAsHighs(snapshotsSplice[0])
+ lows := asset.SnapshotsAsLows(snapshotsSplice[1])
+ closings := asset.SnapshotsAsClosings(snapshotsSplice[2])
+ volumes := asset.SnapshotsAsVolumes(snapshotsSplice[3])
+
+ mfis := m.MoneyFlowIndex.Compute(highs, lows, closings, volumes)
+
+ actions := helper.Map(mfis, func(mfi float64) strategy.Action {
+ if mfi >= m.SellAt {
+ return strategy.Sell
+ }
+
+ if mfi <= m.BuyAt {
+ return strategy.Buy
+ }
+
+ return strategy.Hold
+ })
+
+ // Money Flow Index starts only after a full period.
+ actions = helper.Shift(actions, m.MoneyFlowIndex.IdlePeriod(), strategy.Hold)
+
+ return actions
+}
+
+// Report processes the provided asset snapshots and generates a report annotated with the recommended actions.
+func (m *MoneyFlowIndexStrategy) Report(c <-chan *asset.Snapshot) *helper.Report {
+ //
+ // snapshots[0] -> dates
+ // snapshots[1] -> highs |
+ // snapshots[2] -> lows |
+ // snapshots[3] -> closings[0] -> closings
+ // closings[1] -> superTrend
+ // snapshots[4] -> volumes
+ // snapshots[5] -> actions -> annotations
+ // -> outcomes
+ //
+ snapshots := helper.Duplicate(c, 6)
+
+ dates := helper.Skip(
+ asset.SnapshotsAsDates(snapshots[0]),
+ m.MoneyFlowIndex.IdlePeriod(),
+ )
+
+ highs := asset.SnapshotsAsHighs(snapshots[1])
+ lows := asset.SnapshotsAsLows(snapshots[2])
+ closingsSplice := helper.Duplicate(
+ asset.SnapshotsAsClosings(snapshots[3]),
+ 2,
+ )
+ volumes := asset.SnapshotsAsVolumes(snapshots[4])
+
+ mfis := m.MoneyFlowIndex.Compute(highs, lows, closingsSplice[0], volumes)
+ closingsSplice[1] = helper.Skip(closingsSplice[1], m.MoneyFlowIndex.IdlePeriod())
+
+ actions, outcomes := strategy.ComputeWithOutcome(m, snapshots[5])
+ actions = helper.Skip(actions, m.MoneyFlowIndex.IdlePeriod())
+ outcomes = helper.Skip(outcomes, m.MoneyFlowIndex.IdlePeriod())
+
+ annotations := strategy.ActionsToAnnotations(actions)
+ outcomes = helper.MultiplyBy(outcomes, 100)
+
+ report := helper.NewReport(m.Name(), dates)
+ report.AddChart()
+ report.AddChart()
+
+ report.AddColumn(helper.NewNumericReportColumn("Close", closingsSplice[1]))
+ report.AddColumn(helper.NewNumericReportColumn("Money Flow Index", mfis), 1)
+ report.AddColumn(helper.NewAnnotationReportColumn(annotations), 0, 1)
+
+ report.AddColumn(helper.NewNumericReportColumn("Outcome", outcomes), 2)
+
+ return report
+}
diff --git a/strategy/volume/money_flow_index_strategy_test.go b/strategy/volume/money_flow_index_strategy_test.go
new file mode 100644
index 0000000..598a8c5
--- /dev/null
+++ b/strategy/volume/money_flow_index_strategy_test.go
@@ -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 TestMoneyFlowIndexStrategy(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/money_flow_index_strategy.csv", true)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ expected := helper.Map(results, func(r *strategy.Result) strategy.Action { return r.Action })
+
+ mfis := volume.NewMoneyFlowIndexStrategy()
+ actual := mfis.Compute(snapshots)
+
+ err = helper.CheckEquals(actual, expected)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestMoneyFlowIndexStrategyReport(t *testing.T) {
+ snapshots, err := helper.ReadFromCsvFile[asset.Snapshot]("testdata/brk-b.csv", true)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ mfis := volume.NewMoneyFlowIndexStrategy()
+ report := mfis.Report(snapshots)
+
+ fileName := "money_flow_index_strategy.html"
+ defer os.Remove(fileName)
+
+ err = report.WriteToFile(fileName)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
diff --git a/strategy/volume/testdata/brk-b.csv b/strategy/volume/testdata/brk-b.csv
new file mode 120000
index 0000000..53db519
--- /dev/null
+++ b/strategy/volume/testdata/brk-b.csv
@@ -0,0 +1 @@
+../../../asset/testdata/repository/brk-b.csv
\ No newline at end of file
diff --git a/strategy/volume/testdata/money_flow_index_strategy.csv b/strategy/volume/testdata/money_flow_index_strategy.csv
new file mode 100644
index 0000000..792409e
--- /dev/null
+++ b/strategy/volume/testdata/money_flow_index_strategy.csv
@@ -0,0 +1,252 @@
+Action
+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
diff --git a/strategy/volume/volume.go b/strategy/volume/volume.go
new file mode 100644
index 0000000..e0fa6ee
--- /dev/null
+++ b/strategy/volume/volume.go
@@ -0,0 +1,28 @@
+// Package volume contains the volume strategy functions.
+//
+// This package belongs to the Indicator project. Indicator is
+// a Golang module that supplies a variety of technical
+// indicators, strategies, and a backtesting framework
+// for analysis.
+//
+// # License
+//
+// Copyright (c) 2021-2024 Onur Cinar.
+// The source code is provided under GNU AGPLv3 License.
+// https://github.com/cinar/indicator
+//
+// # Disclaimer
+//
+// The information provided on this project is strictly for
+// informational purposes and is not to be construed as
+// advice or solicitation to buy or sell any security.
+package volume
+
+import (
+ "github.com/cinar/indicator/v2/strategy"
+)
+
+// AllStrategies returns a slice containing references to all available volume strategies.
+func AllStrategies() []strategy.Strategy {
+ return []strategy.Strategy{}
+}
diff --git a/trend/envelope.go b/trend/envelope.go
index 97ce533..b3f6ffe 100644
--- a/trend/envelope.go
+++ b/trend/envelope.go
@@ -39,7 +39,7 @@ func NewEnvelope[T helper.Number](ma Ma[T], percentage T) *Envelope[T] {
func NewEnvelopeWithSma[T helper.Number]() *Envelope[T] {
return NewEnvelope(
NewSmaWithPeriod[T](DefaultEnvelopePeriod),
- DefaultEnvelopePercentage,
+ T(DefaultEnvelopePercentage),
)
}
@@ -47,7 +47,7 @@ func NewEnvelopeWithSma[T helper.Number]() *Envelope[T] {
func NewEnvelopeWithEma[T helper.Number]() *Envelope[T] {
return NewEnvelope(
NewEmaWithPeriod[T](DefaultEnvelopePeriod),
- DefaultEnvelopePercentage,
+ T(DefaultEnvelopePercentage),
)
}
@@ -60,12 +60,12 @@ func (e *Envelope[T]) Compute(closings <-chan T) (<-chan T, <-chan T, <-chan T)
upper := helper.MultiplyBy(
middleSplice[0],
- 1+(e.Percentage/100),
+ 1+(e.Percentage/100.0),
)
lower := helper.MultiplyBy(
middleSplice[2],
- 1-(e.Percentage/100),
+ 1-(e.Percentage/100.0),
)
return upper, middleSplice[1], lower