Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
74 changes: 74 additions & 0 deletions indicator_money_flow.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package techan

import (
"math"

"github.com/sdcoffey/big"
)

type moneyFlowIndexIndicator struct {
mfIndicator Indicator
oneHundred big.Decimal
}

// NewMoneyFlowIndexIndicator returns a derivative Indicator which returns the money flow index of the base indicator
// in a given time frame. A more in-depth explanation of money flow index can be found here:
// https://www.investopedia.com/terms/m/mfi.asp
func NewMoneyFlowIndexIndicator(series *TimeSeries, timeframe int) Indicator {
return moneyFlowIndexIndicator{
mfIndicator: NewMoneyFlowRatioIndicator(series, timeframe),
oneHundred: big.NewFromString("100"),
}
}

func (mfi moneyFlowIndexIndicator) Calculate(index int) big.Decimal {
moneyFlowRatio := mfi.mfIndicator.Calculate(index)

return mfi.oneHundred.Sub(mfi.oneHundred.Div(big.ONE.Add(moneyFlowRatio)))
}

type moneyFlowRatioIndicator struct {
typicalPrice Indicator
volume Indicator
window int
}

// NewMoneyFlowRatioIndicator returns a derivative Indicator which returns the money flow ratio of the base indicator
// in a given time frame. Money flow ratio is the positive money flow divided by the negative money flow during the
// same time frame
func NewMoneyFlowRatioIndicator(series *TimeSeries, timeframe int) Indicator {
return moneyFlowRatioIndicator{
typicalPrice: NewTypicalPriceIndicator(series),
volume: NewVolumeIndicator(series),
window: timeframe,
}
}

func (mfr moneyFlowRatioIndicator) Calculate(index int) big.Decimal {
if index < mfr.window-1 {
return big.ZERO
}

positiveMoneyFlow := big.ZERO
negativeMoneyFlow := big.ZERO

typicalPrice := mfr.typicalPrice.Calculate(index)

for i := index; i > index-mfr.window && i > 0; i-- {
prevTypicalPrice := mfr.typicalPrice.Calculate(i - 1)

if typicalPrice.GT(prevTypicalPrice) {
positiveMoneyFlow = positiveMoneyFlow.Add(typicalPrice.Mul(mfr.volume.Calculate(i)))
} else if typicalPrice.LT(prevTypicalPrice) {
negativeMoneyFlow = negativeMoneyFlow.Add(typicalPrice.Mul(mfr.volume.Calculate(i)))
}

typicalPrice = prevTypicalPrice
}

if negativeMoneyFlow.EQ(big.ZERO) {
return big.NewDecimal(math.Inf(1))
}

return positiveMoneyFlow.Div(negativeMoneyFlow)
}
45 changes: 45 additions & 0 deletions indicator_money_flow_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package techan

import (
"math"
"testing"
)

var series = mockTimeSeriesOCHLV(
[]float64{10, 12, 12, 8, 1000},
[]float64{11, 14, 14, 9, 1500},
[]float64{10, 20, 24, 10, 1200},
[]float64{9, 10, 11, 9, 1800},
[]float64{11, 14, 14, 9, 2000},
[]float64{9, 10, 11, 9, 1300},
)

func TestMoneyFlowIndexIndicator(t *testing.T) {
indicator := NewMoneyFlowIndexIndicator(series, 3)

expectedValues := []float64{
0,
0,
100,
69.0189,
71.9917,
44.3114,
}

indicatorEquals(t, expectedValues, indicator)
}

func TestMoneyFlowRatioIndicator(t *testing.T) {
indicator := NewMoneyFlowRatioIndicator(series, 3)

expectedValues := []float64{
0,
0,
math.Inf(1),
2.2278,
2.5704,
0.7957,
}

indicatorEquals(t, expectedValues, indicator)
}
33 changes: 25 additions & 8 deletions testutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,22 @@ import (
"fmt"
"math"
"math/rand"
"strconv"
"testing"
"time"

"strconv"

"github.com/sdcoffey/big"
"github.com/stretchr/testify/assert"
)

var candleIndex int
var mockedTimeSeries = mockTimeSeriesFl(
64.75, 63.79, 63.73,
63.73, 63.55, 63.19,
63.91, 63.85, 62.95,
63.37, 61.33, 61.51)
var (
candleIndex int
mockedTimeSeries = mockTimeSeriesFl(
64.75, 63.79, 63.73,
63.73, 63.55, 63.19,
63.91, 63.85, 62.95,
63.37, 61.33, 61.51)
)

func randomTimeSeries(size int) *TimeSeries {
vals := make([]string, size)
Expand Down Expand Up @@ -56,6 +57,22 @@ func mockTimeSeriesOCHL(values ...[]float64) *TimeSeries {
return ts
}

func mockTimeSeriesOCHLV(values ...[]float64) *TimeSeries {
ts := NewTimeSeries()
for i, ochlv := range values {
candle := NewCandle(NewTimePeriod(time.Unix(int64(i), 0), time.Second))
candle.OpenPrice = big.NewDecimal(ochlv[0])
candle.ClosePrice = big.NewDecimal(ochlv[1])
candle.MaxPrice = big.NewDecimal(ochlv[2])
candle.MinPrice = big.NewDecimal(ochlv[3])
candle.Volume = big.NewDecimal(ochlv[4])

ts.AddCandle(candle)
}

return ts
}

func mockTimeSeries(values ...string) *TimeSeries {
ts := NewTimeSeries()
for _, val := range values {
Expand Down