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(), } }