From 88378943d7d99489122a1731bd3e8d55686e77f4 Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Wed, 12 Jan 2022 21:17:48 -0800 Subject: [PATCH] Money Flow Index (MFI) is added. --- README.md | 3 +- helper.go | 67 ++++++++++++++++++++++++++++++ helper_test.go | 86 +++++++++++++++++++++++++++++++++++++++ volume_indicators.go | 33 +++++++++++++++ volume_indicators.md | 18 ++++++++ volume_indicators_test.go | 30 ++++++++++++++ volume_strategies.go | 26 ++++++++++++ volume_strategies.md | 10 +++++ 8 files changed, 272 insertions(+), 1 deletion(-) create mode 100644 volume_indicators_test.go create mode 100644 volume_strategies.go diff --git a/README.md b/README.md index d2ae59b..fedc1ef 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ The following list of indicators are currently supported by this package: - [Accumulation/Distribution (A/D)](volume_indicators.md#accumulationdistribution-ad) - [On-Balance Volume (OBV)](volume_indicators.md#on-balance-volume-obv) +- [Money Flow Index (MFI)](volume_indicators.md#money-flow-index-mfi) ## Strategies Provided @@ -87,7 +88,7 @@ The following list of strategies are currently supported by this package: ### Volume Strategies -- No Strategies. +- [Money Flow Index Strategy](volume_strategies.md#money-flow-index-strategy) ### Compound Strategies diff --git a/helper.go b/helper.go index 104a65f..763c8d0 100644 --- a/helper.go +++ b/helper.go @@ -151,3 +151,70 @@ func generateNumbers(begin, end, step float64) []float64 { return numbers } + +// Convets the []int64 to []float64. +func asFloat64(values []int64) []float64 { + result := make([]float64, len(values)) + + for i := 0; i < len(values); i++ { + result[i] = float64(values[i]) + } + + return result +} + +// Calculate power of base with exponent. +func pow(base []float64, exponent float64) []float64 { + result := make([]float64, len(base)) + + for i := 0; i < len(result); i++ { + result[i] = math.Pow(base[i], exponent) + } + + return result +} + +// Extact sign. +func extractSign(values []float64) []float64 { + result := make([]float64, len(values)) + + for i := 0; i < len(result); i++ { + if values[i] >= 0 { + result[i] = 1 + } else { + result[i] = -1 + } + } + + return result +} + +// Keep positives. +func keepPositives(values []float64) []float64 { + result := make([]float64, len(values)) + + for i := 0; i < len(values); i++ { + if values[i] > 0 { + result[i] = values[i] + } else { + result[i] = 0 + } + } + + return result +} + +// Keep negatives. +func keepNegatives(values []float64) []float64 { + result := make([]float64, len(values)) + + for i := 0; i < len(values); i++ { + if values[i] < 0 { + result[i] = values[i] + } else { + result[i] = 0 + } + } + + return result +} diff --git a/helper_test.go b/helper_test.go index e5725b2..b974e2e 100644 --- a/helper_test.go +++ b/helper_test.go @@ -221,3 +221,89 @@ func TestGenerateNunbers(t *testing.T) { } } } + +func TestAsFloat64(t *testing.T) { + values := []int64{1, 2, 3, 4} + expected := []float64{1, 2, 3, 4} + + actual := asFloat64(values) + + if len(actual) != len(expected) { + t.Fatal("not the same size") + } + + for i := 0; i < len(expected); i++ { + if actual[i] != expected[i] { + t.Fatalf("at %d actual %f expected %f", i, actual[i], expected[i]) + } + } +} + +func TestPow(t *testing.T) { + values := []float64{1, 2, 3, 4} + expected := []float64{1, 4, 9, 16} + exponent := 2.0 + + actual := pow(values, exponent) + + if len(actual) != len(expected) { + t.Fatal("not the same size") + } + + for i := 0; i < len(expected); i++ { + if actual[i] != expected[i] { + t.Fatalf("at %d actual %f expected %f", i, actual[i], expected[i]) + } + } +} + +func TestExtractSign(t *testing.T) { + values := []float64{1, -2, 3, 4, -5, 0, 6, -8, -9, 10} + expected := []float64{1, -1, 1, 1, -1, 1, 1, -1, -1, 1} + + actual := extractSign(values) + + if len(actual) != len(expected) { + t.Fatal("not the same size") + } + + for i := 0; i < len(expected); i++ { + if actual[i] != expected[i] { + t.Fatalf("at %d actual %f expected %f", i, actual[i], expected[i]) + } + } +} + +func TestKeepPositives(t *testing.T) { + values := []float64{1, -2, -3, 4} + expected := []float64{1, 0, 0, 4} + + actual := keepPositives(values) + + if len(actual) != len(expected) { + t.Fatal("not the same size") + } + + for i := 0; i < len(expected); i++ { + if actual[i] != expected[i] { + t.Fatalf("at %d actual %f expected %f", i, actual[i], expected[i]) + } + } +} + +func TestKeepNegatives(t *testing.T) { + values := []float64{1, -2, -3, 4} + expected := []float64{0, -2, -3, 0} + + actual := keepNegatives(values) + + if len(actual) != len(expected) { + t.Fatal("not the same size") + } + + for i := 0; i < len(expected); i++ { + if actual[i] != expected[i] { + t.Fatalf("at %d actual %f expected %f", i, actual[i], expected[i]) + } + } +} diff --git a/volume_indicators.go b/volume_indicators.go index 943ae25..0dce37e 100644 --- a/volume_indicators.go +++ b/volume_indicators.go @@ -57,3 +57,36 @@ func Obv(closing []float64, volume []int64) []int64 { return obv } + +// The Money Flow Index (MFI) analyzes both the closing price and the volume +// to measure to identify overbought and oversold states. It is similar to +// the Relative Strength Index (RSI), but it also uses the volume. +// +// Raw Money Flow = Typical Price * Volume +// Money Ratio = Positive Money Flow / Negative Money Flow +// Money Flow Index = 100 - (100 / (1 + Money Ratio)) +// +// Retruns money flow index values. +func MoneyFlowIndex(period int, high, low, closing []float64, volume []int64) []float64 { + typicalPrice, _ := TypicalPrice(low, high, closing) + rawMoneyFlow := multiply(typicalPrice, asFloat64(volume)) + + signs := extractSign(diff(rawMoneyFlow, 1)) + moneyFlow := multiply(signs, rawMoneyFlow) + + positiveMoneyFlow := keepPositives(moneyFlow) + negativeMoneyFlow := keepNegatives(moneyFlow) + + moneyRatio := divide( + Sum(period, positiveMoneyFlow), + Sum(period, negativeMoneyFlow)) + + moneyFlowIndex := addBy(multiplyBy(pow(addBy(moneyRatio, 1), -1), -100), 100) + + return moneyFlowIndex +} + +// Default money flow index with period 14. +func DefaultMoneyFlowIndex(high, low, closing []float64, volume []int64) []float64 { + return MoneyFlowIndex(14, high, low, closing, volume) +} diff --git a/volume_indicators.md b/volume_indicators.md index 26bbad9..9687250 100644 --- a/volume_indicators.md +++ b/volume_indicators.md @@ -4,6 +4,7 @@ Volumne indicators measure the strength of a trend based the volume. - [Accumulation/Distribution (A/D)](#accumulationdistribution-ad) - [On-Balance Volume (OBV)](#on-balance-volume-obv) +- [Money Flow Index (MFI)](#money-flow-index-mfi) #### Accumulation/Distribution (A/D) @@ -37,6 +38,23 @@ OBV = OBV-Prev + 0, if Closing = Closing-Prev result := indicator.Obv(closing, volume) ``` +#### Money Flow Index (MFI) + +The [MoneyFlowIndex](https://pkg.go.dev/github.com/cinar/indicator#MoneyFlowIndex) function analyzes both the closing price and the volume to measure to identify overbought and oversold states. It is similar to the Relative Strength Index (RSI), but it also uses the volume. + +``` +Raw Money Flow = Typical Price * Volume +Money Ratio = Positive Money Flow / Negative Money Flow +Money Flow Index = 100 - (100 / (1 + Money Ratio)) +``` + +```Golang +result := indicator.MoneyFlowIndex(period, high, low, closing, volume) +``` + +The [DefaultMoneyFlowIndex](https://pkg.go.dev/github.com/cinar/indicator#DefaultMoneyFlowIndex) function uses the default period of 14. + + ## 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. diff --git a/volume_indicators_test.go b/volume_indicators_test.go new file mode 100644 index 0000000..64bc1dc --- /dev/null +++ b/volume_indicators_test.go @@ -0,0 +1,30 @@ +// Copyright (c) 2021 Onur Cinar. All Rights Reserved. +// The source code is provided under MIT License. +// +// https://github.com/cinar/indicator + +package indicator + +import "testing" + +func TestMoneyFlowIndex(t *testing.T) { + high := []float64{10, 9, 12, 14, 12} + low := []float64{6, 7, 9, 12, 10} + closing := []float64{9, 11, 7, 10, 8} + volume := []int64{100, 110, 80, 120, 90} + expected := []float64{100, 100, 406.85, 207.69, 266.67} + period := 2 + + result := MoneyFlowIndex(period, high, low, closing, volume) + if len(result) != len(expected) { + t.Fatal("result not same size") + } + + for i := 0; i < len(result); i++ { + actual := roundDigits(result[i], 2) + + if actual != expected[i] { + t.Fatalf("result %d actual %f expected %f", i, actual, expected[i]) + } + } +} diff --git a/volume_strategies.go b/volume_strategies.go new file mode 100644 index 0000000..154b918 --- /dev/null +++ b/volume_strategies.go @@ -0,0 +1,26 @@ +// Copyright (c) 2021 Onur Cinar. All Rights Reserved. +// The source code is provided under MIT License. +// +// https://github.com/cinar/indicator + +package indicator + +func MoneyFlowIndexStrategy(asset Asset) []Action { + actions := make([]Action, len(asset.Date)) + + moneyFlowIndex := DefaultMoneyFlowIndex( + asset.High, + asset.Low, + asset.Closing, + asset.Volume) + + for i := 0; i < len(actions); i++ { + if moneyFlowIndex[i] >= 80 { + actions[i] = SELL + } else { + actions[i] = BUY + } + } + + return actions +} diff --git a/volume_strategies.md b/volume_strategies.md index 01fdb2f..13abff3 100644 --- a/volume_strategies.md +++ b/volume_strategies.md @@ -2,6 +2,16 @@ Volume strategies generate signals based on a volume indicator. +- [Money Flow Index Strategy](#money-flow-index-strategy) + +#### Money Flow Index Strategy + +The [MoneyFlowIndexStrategy](https://pkg.go.dev/github.com/cinar/indicator#MoneyFlowIndexStrategy) uses the _mfi_ values that are generated by the [Money Flow Index (MFI)](volume_indicators.md#money-flow-index-mfi) indicator function to provide a _SELL_ action when _mfi_ is greather than or equal to 80, and a _BUY_ action when _mfi_ is less than or equal to 20. + +```Golang +actions := MoneyFlowIndexStrategy(asset) +``` + ## 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.