diff --git a/README.md b/README.md
index addf1c3..a61d158 100644
--- a/README.md
+++ b/README.md
@@ -137,7 +137,7 @@ The following list of strategies are currently supported by this package:
### 📢 Volume Strategies
-- Chaikin Money Flow Strategy
+- [Chaikin Money Flow Strategy](strategy/volume/README.md#type-chaikinmoneyflowstrategy)
- Ease of Movement Strategy
- Force Index Strategy
- [Money Flow Index Strategy](strategy/volume/README.md#type-moneyflowindexstrategy)
diff --git a/strategy/volume/README.md b/strategy/volume/README.md
index b010cc0..6d4ea7a 100644
--- a/strategy/volume/README.md
+++ b/strategy/volume/README.md
@@ -26,6 +26,12 @@ The information provided on this project is strictly for informational purposes
- [Constants](<#constants>)
- [func AllStrategies\(\) \[\]strategy.Strategy](<#AllStrategies>)
+- [type ChaikinMoneyFlowStrategy](<#ChaikinMoneyFlowStrategy>)
+ - [func NewChaikinMoneyFlowStrategy\(\) \*ChaikinMoneyFlowStrategy](<#NewChaikinMoneyFlowStrategy>)
+ - [func NewChaikinMoneyFlowStrategyWith\(period int\) \*ChaikinMoneyFlowStrategy](<#NewChaikinMoneyFlowStrategyWith>)
+ - [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 MoneyFlowIndexStrategy](<#MoneyFlowIndexStrategy>)
- [func NewMoneyFlowIndexStrategy\(\) \*MoneyFlowIndexStrategy](<#NewMoneyFlowIndexStrategy>)
- [func NewMoneyFlowIndexStrategyWith\(sellAt, buyAt float64\) \*MoneyFlowIndexStrategy](<#NewMoneyFlowIndexStrategyWith>)
@@ -57,6 +63,63 @@ func AllStrategies() []strategy.Strategy
AllStrategies returns a slice containing references to all available volume strategies.
+
+## type [ChaikinMoneyFlowStrategy]()
+
+ChaikinMoneyFlowStrategy represents the configuration parameters for calculating the Chaikin Money Flow strategy. Recommends a Sell action when it crosses above 0, and recommends a Buy action when it crosses below 0.
+
+```go
+type ChaikinMoneyFlowStrategy struct {
+ // ChaikinMoneyFlow is the Chaikin Money Flow indicator instance.
+ ChaikinMoneyFlow *volume.Cmf[float64]
+}
+```
+
+
+### func [NewChaikinMoneyFlowStrategy]()
+
+```go
+func NewChaikinMoneyFlowStrategy() *ChaikinMoneyFlowStrategy
+```
+
+NewChaikinMoneyFlowStrategy function initializes a new Money Flow Index strategy instance with the default parameters.
+
+
+### func [NewChaikinMoneyFlowStrategyWith]()
+
+```go
+func NewChaikinMoneyFlowStrategyWith(period int) *ChaikinMoneyFlowStrategy
+```
+
+NewChaikinMoneyFlowStrategyWith function initializes a new Money Flow Index strategy instance with the given parameters.
+
+
+### func \(\*ChaikinMoneyFlowStrategy\) [Compute]()
+
+```go
+func (c *ChaikinMoneyFlowStrategy) Compute(snapshots <-chan *asset.Snapshot) <-chan strategy.Action
+```
+
+Compute processes the provided asset snapshots and generates a stream of actionable recommendations.
+
+
+### func \(\*ChaikinMoneyFlowStrategy\) [Name]()
+
+```go
+func (c *ChaikinMoneyFlowStrategy) Name() string
+```
+
+Name returns the name of the strategy.
+
+
+### func \(\*ChaikinMoneyFlowStrategy\) [Report]()
+
+```go
+func (c *ChaikinMoneyFlowStrategy) Report(snapshots <-chan *asset.Snapshot) *helper.Report
+```
+
+Report processes the provided asset snapshots and generates a report annotated with the recommended actions.
+
## type [MoneyFlowIndexStrategy]()
diff --git a/strategy/volume/chaikin_money_flow_strategy.go b/strategy/volume/chaikin_money_flow_strategy.go
new file mode 100644
index 0000000..18f308e
--- /dev/null
+++ b/strategy/volume/chaikin_money_flow_strategy.go
@@ -0,0 +1,121 @@
+// 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"
+)
+
+// ChaikinMoneyFlowStrategy represents the configuration parameters for calculating the Chaikin Money Flow strategy.
+// Recommends a Sell action when it crosses above 0, and recommends a Buy action when it crosses below 0.
+type ChaikinMoneyFlowStrategy struct {
+ // ChaikinMoneyFlow is the Chaikin Money Flow indicator instance.
+ ChaikinMoneyFlow *volume.Cmf[float64]
+}
+
+// NewChaikinMoneyFlowStrategy function initializes a new Money Flow Index strategy instance with the
+// default parameters.
+func NewChaikinMoneyFlowStrategy() *ChaikinMoneyFlowStrategy {
+ return NewChaikinMoneyFlowStrategyWith(
+ volume.DefaultCmfPeriod,
+ )
+}
+
+// NewChaikinMoneyFlowStrategyWith function initializes a new Money Flow Index strategy instance with the
+// given parameters.
+func NewChaikinMoneyFlowStrategyWith(period int) *ChaikinMoneyFlowStrategy {
+ return &ChaikinMoneyFlowStrategy{
+ ChaikinMoneyFlow: volume.NewCmfWithPeriod[float64](period),
+ }
+}
+
+// Name returns the name of the strategy.
+func (c *ChaikinMoneyFlowStrategy) Name() string {
+ return fmt.Sprintf("Chaikin Money Flow Strategy (%d)", c.ChaikinMoneyFlow.IdlePeriod()+1)
+}
+
+// Compute processes the provided asset snapshots and generates a stream of actionable recommendations.
+func (c *ChaikinMoneyFlowStrategy) 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])
+
+ cmfs := c.ChaikinMoneyFlow.Compute(highs, lows, closings, volumes)
+
+ actions := helper.Map(cmfs, func(cmf float64) strategy.Action {
+ if cmf < 0 {
+ return strategy.Buy
+ }
+
+ if cmf > 0 {
+ return strategy.Sell
+ }
+
+ return strategy.Hold
+ })
+
+ // Chaikin Money Flow starts only after a full period.
+ actions = helper.Shift(actions, c.ChaikinMoneyFlow.IdlePeriod(), strategy.Hold)
+
+ return actions
+}
+
+// Report processes the provided asset snapshots and generates a report annotated with the recommended actions.
+func (c *ChaikinMoneyFlowStrategy) Report(snapshots <-chan *asset.Snapshot) *helper.Report {
+ //
+ // snapshots[0] -> dates
+ // snapshots[1] -> highs |
+ // snapshots[2] -> lows |
+ // snapshots[3] -> closings[0] -> closings
+ // closings[1] -> chaikin money flow
+ // snapshots[4] -> volumes
+ // snapshots[5] -> actions -> annotations
+ // -> outcomes
+ //
+ snapshotsSplice := helper.Duplicate(snapshots, 6)
+
+ dates := helper.Skip(
+ asset.SnapshotsAsDates(snapshotsSplice[0]),
+ c.ChaikinMoneyFlow.IdlePeriod(),
+ )
+
+ highs := asset.SnapshotsAsHighs(snapshotsSplice[1])
+ lows := asset.SnapshotsAsLows(snapshotsSplice[2])
+ closingsSplice := helper.Duplicate(
+ asset.SnapshotsAsClosings(snapshotsSplice[3]),
+ 2,
+ )
+ volumes := asset.SnapshotsAsVolumes(snapshotsSplice[4])
+
+ cmfs := c.ChaikinMoneyFlow.Compute(highs, lows, closingsSplice[0], volumes)
+ closingsSplice[1] = helper.Skip(closingsSplice[1], c.ChaikinMoneyFlow.IdlePeriod())
+
+ actions, outcomes := strategy.ComputeWithOutcome(c, snapshotsSplice[5])
+ actions = helper.Skip(actions, c.ChaikinMoneyFlow.IdlePeriod())
+ outcomes = helper.Skip(outcomes, c.ChaikinMoneyFlow.IdlePeriod())
+
+ annotations := strategy.ActionsToAnnotations(actions)
+ outcomes = helper.MultiplyBy(outcomes, 100)
+
+ report := helper.NewReport(c.Name(), dates)
+ report.AddChart()
+ report.AddChart()
+
+ report.AddColumn(helper.NewNumericReportColumn("Close", closingsSplice[1]))
+ report.AddColumn(helper.NewNumericReportColumn("Chaikin Money Flow", cmfs), 1)
+ report.AddColumn(helper.NewAnnotationReportColumn(annotations), 0, 1)
+
+ report.AddColumn(helper.NewNumericReportColumn("Outcome", outcomes), 2)
+
+ return report
+}
diff --git a/strategy/volume/chaikin_money_flow_strategy_test.go b/strategy/volume/chaikin_money_flow_strategy_test.go
new file mode 100644
index 0000000..fb53139
--- /dev/null
+++ b/strategy/volume/chaikin_money_flow_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 TestChaikinMoneyFlowStrategy(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/chaikin_money_flow_strategy.csv", true)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ expected := helper.Map(results, func(r *strategy.Result) strategy.Action { return r.Action })
+
+ cmfs := volume.NewChaikinMoneyFlowStrategy()
+ actual := cmfs.Compute(snapshots)
+
+ err = helper.CheckEquals(actual, expected)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestChaikinMoneyFlowStrategyReport(t *testing.T) {
+ snapshots, err := helper.ReadFromCsvFile[asset.Snapshot]("testdata/brk-b.csv", true)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ cmfs := volume.NewChaikinMoneyFlowStrategy()
+ report := cmfs.Report(snapshots)
+
+ fileName := "chaikin_money_flow_strategy.html"
+ defer os.Remove(fileName)
+
+ err = report.WriteToFile(fileName)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
diff --git a/strategy/volume/money_flow_index_strategy.go b/strategy/volume/money_flow_index_strategy.go
index f51d4de..2fd33ea 100644
--- a/strategy/volume/money_flow_index_strategy.go
+++ b/strategy/volume/money_flow_index_strategy.go
@@ -54,7 +54,7 @@ func NewMoneyFlowIndexStrategyWith(sellAt, buyAt float64) *MoneyFlowIndexStrateg
// Name returns the name of the strategy.
func (m *MoneyFlowIndexStrategy) Name() string {
- return fmt.Sprintf("Money Flow Index Strategy (%f,%f)", m.SellAt, m.BuyAt)
+ return fmt.Sprintf("Money Flow Index Strategy (%.2f,%.2f)", m.SellAt, m.BuyAt)
}
// Compute processes the provided asset snapshots and generates a stream of actionable recommendations.
@@ -93,7 +93,7 @@ func (m *MoneyFlowIndexStrategy) Report(c <-chan *asset.Snapshot) *helper.Report
// snapshots[1] -> highs |
// snapshots[2] -> lows |
// snapshots[3] -> closings[0] -> closings
- // closings[1] -> superTrend
+ // closings[1] -> money flow index
// snapshots[4] -> volumes
// snapshots[5] -> actions -> annotations
// -> outcomes
diff --git a/strategy/volume/testdata/chaikin_money_flow_strategy.csv b/strategy/volume/testdata/chaikin_money_flow_strategy.csv
new file mode 100644
index 0000000..c2c15cd
--- /dev/null
+++ b/strategy/volume/testdata/chaikin_money_flow_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
+-1
+1
+-1
+1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+1
+1
+1
+1
+1
+1
+1
+1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+1
+-1
+-1
+1
+1
+1
+-1
+1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+1
+1
+1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+1
+-1
+1
+-1
+1
+1
+-1
+1
+-1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
+-1
diff --git a/strategy/volume/volume.go b/strategy/volume/volume.go
index 919be8d..48fcbd3 100644
--- a/strategy/volume/volume.go
+++ b/strategy/volume/volume.go
@@ -25,6 +25,7 @@ import (
// AllStrategies returns a slice containing references to all available volume strategies.
func AllStrategies() []strategy.Strategy {
return []strategy.Strategy{
+ NewChaikinMoneyFlowStrategy(),
NewMoneyFlowIndexStrategy(),
}
}