diff --git a/indicator_stochastic_rsi.go b/indicator_stochastic_rsi.go new file mode 100644 index 0000000..e578cd5 --- /dev/null +++ b/indicator_stochastic_rsi.go @@ -0,0 +1,67 @@ +package techan + +import ( + "math" + + "github.com/sdcoffey/big" +) + +type stochasticRSIIndicator struct { + curRSI Indicator + minRSI Indicator + maxRSI Indicator +} + +// NewStochasticRSIIndicator returns a derivative Indicator which returns the stochastic RSI indicator for the given +// RSI window. +// https://www.investopedia.com/terms/s/stochrsi.asp +func NewStochasticRSIIndicator(indicator Indicator, timeframe int) Indicator { + rsiIndicator := NewRelativeStrengthIndexIndicator(indicator, timeframe) + return stochasticRSIIndicator{ + curRSI: rsiIndicator, + minRSI: NewMinimumValueIndicator(rsiIndicator, timeframe), + maxRSI: NewMaximumValueIndicator(rsiIndicator, timeframe), + } +} + +func (sri stochasticRSIIndicator) Calculate(index int) big.Decimal { + curRSI := sri.curRSI.Calculate(index) + minRSI := sri.minRSI.Calculate(index) + maxRSI := sri.maxRSI.Calculate(index) + + if minRSI.EQ(maxRSI) { + return big.NewDecimal(math.Inf(1)) + } + + return curRSI.Sub(minRSI).Div(maxRSI.Sub(minRSI)).Mul(big.NewDecimal(100)) +} + +type rsiKIndicator struct { + stochasticRSI Indicator + window int +} + +// NewFastStochasticRSIIndicator returns a derivative Indicator which returns the fast stochastic RSI indicator (%K) +// for the given stochastic window. +func NewFastStochasticRSIIndicator(stochasticRSI Indicator, timeframe int) Indicator { + return rsiKIndicator{stochasticRSI, timeframe} +} + +func (k rsiKIndicator) Calculate(index int) big.Decimal { + return NewSimpleMovingAverage(k.stochasticRSI, k.window).Calculate(index) +} + +type rsiDIndicator struct { + k Indicator + window int +} + +// NewSlowStochasticRSIIndicator returns a derivative Indicator which returns the slow stochastic RSI indicator (%D) +// for the given stochastic window. +func NewSlowStochasticRSIIndicator(k Indicator, timeframe int) Indicator { + return rsiDIndicator{k, timeframe} +} + +func (d rsiDIndicator) Calculate(index int) big.Decimal { + return NewSimpleMovingAverage(d.k, d.window).Calculate(index) +} diff --git a/indicator_stochastic_rsi_test.go b/indicator_stochastic_rsi_test.go new file mode 100644 index 0000000..ba0faf7 --- /dev/null +++ b/indicator_stochastic_rsi_test.go @@ -0,0 +1,80 @@ +package techan + +import ( + "math" + "testing" + + "github.com/sdcoffey/big" + "github.com/stretchr/testify/assert" +) + +func TestStochasticRSIIndicator(t *testing.T) { + indicator := NewStochasticRSIIndicator(NewClosePriceIndicator(mockedTimeSeries), 5) + + expectedValues := []float64{ + math.Inf(1), + math.Inf(1), + math.Inf(1), + math.Inf(1), + math.Inf(1), + math.Inf(1), + 100, + 95.9481, + 54.5245, + 93.1791, + 0, + 21.6754, + } + + indicatorEquals(t, expectedValues, indicator) +} + +func TestFastStochasticRSIIndicator(t *testing.T) { + indicator := NewFastStochasticRSIIndicator(NewStochasticRSIIndicator(NewClosePriceIndicator(mockedTimeSeries), + 5), 3) + + expectedValues := []float64{ + 0, + 0, + math.Inf(1), + math.Inf(1), + math.Inf(1), + math.Inf(1), + math.Inf(1), + math.Inf(1), + 83.4909, + 81.2173, + 49.2346, + 38.2848, + } + + indicatorEquals(t, expectedValues, indicator) +} + +func TestSlowStochasticRSIIndicator(t *testing.T) { + indicator := NewSlowStochasticRSIIndicator(NewFastStochasticRSIIndicator(NewStochasticRSIIndicator( + NewClosePriceIndicator(mockedTimeSeries), 5), 3), 3) + + expectedValues := []float64{ + 0, + 0, + math.Inf(1), + math.Inf(1), + math.Inf(1), + math.Inf(1), + math.Inf(1), + math.Inf(1), + math.Inf(1), + math.Inf(1), + 71.3142, + 56.2456, + } + + indicatorEquals(t, expectedValues, indicator) +} + +func TestFastStochasticRSIIndicatorNoPriceChange(t *testing.T) { + close := NewClosePriceIndicator(mockTimeSeries("42.0", "42.0")) + rsInd := NewRelativeStrengthIndicator(close, 2) + assert.Equal(t, big.NewDecimal(math.Inf(1)).FormattedString(2), rsInd.Calculate(1).FormattedString(2)) +}