Skip to content

Commit

Permalink
Money Flow Index (MFI) is added.
Browse files Browse the repository at this point in the history
  • Loading branch information
cinar committed Jan 13, 2022
1 parent 85f62b6 commit 8837894
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 1 deletion.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
67 changes: 67 additions & 0 deletions helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
86 changes: 86 additions & 0 deletions helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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])
}
}
}
33 changes: 33 additions & 0 deletions volume_indicators.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
18 changes: 18 additions & 0 deletions volume_indicators.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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.
Expand Down
30 changes: 30 additions & 0 deletions volume_indicators_test.go
Original file line number Diff line number Diff line change
@@ -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])
}
}
}
26 changes: 26 additions & 0 deletions volume_strategies.go
Original file line number Diff line number Diff line change
@@ -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
}
10 changes: 10 additions & 0 deletions volume_strategies.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 8837894

Please sign in to comment.