diff --git a/indicator_stochastic_rsi.go b/indicator_stochastic_rsi.go new file mode 100644 index 0000000..7723bda --- /dev/null +++ b/indicator_stochastic_rsi.go @@ -0,0 +1,65 @@ +package techan + +import ( + "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(100) + } + + return curRSI.Sub(minRSI).Div(maxRSI.Sub(minRSI)).Mul(big.NewDecimal(100)) +} + +type stochRSIKIndicator 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 stochRSIKIndicator{stochasticRSI, timeframe} +} + +func (k stochRSIKIndicator) Calculate(index int) big.Decimal { + return NewSimpleMovingAverage(k.stochasticRSI, k.window).Calculate(index) +} + +type stochRSIDIndicator struct { + fastStochasticRSI Indicator + window int +} + +// NewSlowStochasticRSIIndicator returns a derivative Indicator which returns the slow stochastic RSI indicator (%D) +// for the given stochastic window. +func NewSlowStochasticRSIIndicator(fastStochasticRSI Indicator, timeframe int) Indicator { + return stochRSIDIndicator{fastStochasticRSI, timeframe} +} + +func (d stochRSIDIndicator) Calculate(index int) big.Decimal { + return NewSimpleMovingAverage(d.fastStochasticRSI, d.window).Calculate(index) +} diff --git a/indicator_stochastic_rsi_test.go b/indicator_stochastic_rsi_test.go new file mode 100644 index 0000000..b66ac43 --- /dev/null +++ b/indicator_stochastic_rsi_test.go @@ -0,0 +1,79 @@ +package techan + +import ( + "testing" + + "github.com/sdcoffey/big" + "github.com/stretchr/testify/assert" +) + +func TestStochasticRSIIndicator(t *testing.T) { + indicator := NewStochasticRSIIndicator(NewClosePriceIndicator(mockedTimeSeries), 5) + + expectedValues := []float64{ + 100, + 100, + 100, + 100, + 100, + 100, + 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, + 100, + 100, + 100, + 100, + 100, + 98.6494, + 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, + 33.3333, + 66.6667, + 100, + 100, + 100, + 99.5498, + 94.0468, + 87.7858, + 71.3142, + 56.2456, + } + + indicatorEquals(t, expectedValues, indicator) +} + +func TestFastStochasticRSIIndicatorNoPriceChange(t *testing.T) { + close := NewClosePriceIndicator(mockTimeSeries("42.0", "42.0")) + rsInd := NewStochasticRSIIndicator(close, 2) + assert.Equal(t, big.NewDecimal(100).FormattedString(2), rsInd.Calculate(1).FormattedString(2)) +}