Skip to content

Commit c81cf33

Browse files
committed
Implement Money Flow Index
1 parent a2f4ef8 commit c81cf33

4 files changed

Lines changed: 144 additions & 9 deletions

File tree

go.sum

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
1616
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
1717
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1818
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
19-
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
19+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

indicator_money_flow.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package techan
2+
3+
import (
4+
"math"
5+
6+
"github.com/sdcoffey/big"
7+
)
8+
9+
type moneyFlowIndexIndicator struct {
10+
mfIndicator Indicator
11+
oneHundred big.Decimal
12+
}
13+
14+
// NewMoneyFlowIndexIndicator returns a derivative Indicator which returns the money flow index of the base indicator
15+
// in a given time frame. A more in-depth explanation of money flow index can be found here:
16+
// https://www.investopedia.com/terms/m/mfi.asp
17+
func NewMoneyFlowIndexIndicator(series *TimeSeries, timeframe int) Indicator {
18+
return moneyFlowIndexIndicator{
19+
mfIndicator: NewMoneyFlowRatioIndicator(series, timeframe),
20+
oneHundred: big.NewFromString("100"),
21+
}
22+
}
23+
24+
func (mfi moneyFlowIndexIndicator) Calculate(index int) big.Decimal {
25+
moneyFlowRatio := mfi.mfIndicator.Calculate(index)
26+
27+
return mfi.oneHundred.Sub(mfi.oneHundred.Div(big.ONE.Add(moneyFlowRatio)))
28+
}
29+
30+
type moneyFlowRatioIndicator struct {
31+
typicalPrice Indicator
32+
volume Indicator
33+
window int
34+
}
35+
36+
// NewMoneyFlowRatioIndicator returns a derivative Indicator which returns the money flow ratio of the base indicator
37+
// in a given time frame. Money flow ratio is the positive money flow divided by the negative money flow during the
38+
// same time frame
39+
func NewMoneyFlowRatioIndicator(series *TimeSeries, timeframe int) Indicator {
40+
return moneyFlowRatioIndicator{
41+
typicalPrice: NewTypicalPriceIndicator(series),
42+
volume: NewVolumeIndicator(series),
43+
window: timeframe,
44+
}
45+
}
46+
47+
func (mfr moneyFlowRatioIndicator) Calculate(index int) big.Decimal {
48+
if index < mfr.window-1 {
49+
return big.ZERO
50+
}
51+
52+
positiveMoneyFlow := big.ZERO
53+
negativeMoneyFlow := big.ZERO
54+
55+
rawMoneyFlow := mfr.typicalPrice.Calculate(index).Mul(mfr.volume.Calculate(index))
56+
for i := index; i > index-mfr.window+1; i-- {
57+
prevRawMoneyFlow := mfr.typicalPrice.Calculate(i - 1).Mul(mfr.volume.Calculate(i - 1))
58+
59+
if rawMoneyFlow.GT(prevRawMoneyFlow) {
60+
positiveMoneyFlow = positiveMoneyFlow.Add(rawMoneyFlow)
61+
} else if rawMoneyFlow.LT(prevRawMoneyFlow) {
62+
negativeMoneyFlow = negativeMoneyFlow.Add(rawMoneyFlow)
63+
}
64+
65+
rawMoneyFlow = prevRawMoneyFlow
66+
}
67+
68+
if negativeMoneyFlow.EQ(big.ZERO) {
69+
return big.NewDecimal(math.Inf(1))
70+
}
71+
72+
return positiveMoneyFlow.Div(negativeMoneyFlow)
73+
}

indicator_money_flow_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package techan
2+
3+
import (
4+
"math"
5+
"testing"
6+
)
7+
8+
var series = mockTimeSeriesOCHLV(
9+
[]float64{10, 12, 12, 8, 1000},
10+
[]float64{11, 14, 14, 9, 1500},
11+
[]float64{10, 20, 24, 10, 1200},
12+
[]float64{9, 10, 11, 9, 1800},
13+
[]float64{11, 14, 14, 9, 2000},
14+
[]float64{9, 10, 11, 9, 1300},
15+
)
16+
17+
func TestMoneyFlowIndexIndicator(t *testing.T) {
18+
indicator := NewMoneyFlowIndexIndicator(series, 3)
19+
20+
expectedValues := []float64{
21+
0,
22+
0,
23+
100,
24+
54.5455,
25+
57.8125,
26+
65.4867,
27+
}
28+
29+
indicatorEquals(t, expectedValues, indicator)
30+
}
31+
32+
func TestMoneyFlowRatioIndicator(t *testing.T) {
33+
indicator := NewMoneyFlowRatioIndicator(series, 3)
34+
35+
expectedValues := []float64{
36+
0,
37+
0,
38+
math.Inf(1),
39+
1.2,
40+
1.3704,
41+
1.8974,
42+
}
43+
44+
indicatorEquals(t, expectedValues, indicator)
45+
}

testutils.go

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,22 @@ import (
44
"fmt"
55
"math"
66
"math/rand"
7+
"strconv"
78
"testing"
89
"time"
910

10-
"strconv"
11-
1211
"github.com/sdcoffey/big"
1312
"github.com/stretchr/testify/assert"
1413
)
1514

16-
var candleIndex int
17-
var mockedTimeSeries = mockTimeSeriesFl(
18-
64.75, 63.79, 63.73,
19-
63.73, 63.55, 63.19,
20-
63.91, 63.85, 62.95,
21-
63.37, 61.33, 61.51)
15+
var (
16+
candleIndex int
17+
mockedTimeSeries = mockTimeSeriesFl(
18+
64.75, 63.79, 63.73,
19+
63.73, 63.55, 63.19,
20+
63.91, 63.85, 62.95,
21+
63.37, 61.33, 61.51)
22+
)
2223

2324
func randomTimeSeries(size int) *TimeSeries {
2425
vals := make([]string, size)
@@ -56,6 +57,22 @@ func mockTimeSeriesOCHL(values ...[]float64) *TimeSeries {
5657
return ts
5758
}
5859

60+
func mockTimeSeriesOCHLV(values ...[]float64) *TimeSeries {
61+
ts := NewTimeSeries()
62+
for i, ochlv := range values {
63+
candle := NewCandle(NewTimePeriod(time.Unix(int64(i), 0), time.Second))
64+
candle.OpenPrice = big.NewDecimal(ochlv[0])
65+
candle.ClosePrice = big.NewDecimal(ochlv[1])
66+
candle.MaxPrice = big.NewDecimal(ochlv[2])
67+
candle.MinPrice = big.NewDecimal(ochlv[3])
68+
candle.Volume = big.NewDecimal(ochlv[4])
69+
70+
ts.AddCandle(candle)
71+
}
72+
73+
return ts
74+
}
75+
5976
func mockTimeSeries(values ...string) *TimeSeries {
6077
ts := NewTimeSeries()
6178
for _, val := range values {

0 commit comments

Comments
 (0)