diff --git a/indicator.go b/indicator.go index 73e0cba..6abfc64 100644 --- a/indicator.go +++ b/indicator.go @@ -7,4 +7,5 @@ import "github.com/sdcoffey/big" // returns the current moving average of the prices in that series. type Indicator interface { Calculate(int) big.Decimal + RemoveCachedEntry(int) } diff --git a/indicator_aroon.go b/indicator_aroon.go index b89953a..73c8cad 100644 --- a/indicator_aroon.go +++ b/indicator_aroon.go @@ -26,6 +26,10 @@ func (ai *aroonIndicator) Calculate(index int) big.Decimal { return windowAsDecimal.Sub(pSince).Div(windowAsDecimal).Mul(oneHundred) } +func (ai *aroonIndicator) RemoveCachedEntry(index int) { + ai.indicator.RemoveCachedEntry(index) +} + func (ai aroonIndicator) findLowIndex(index int) int { if ai.lowIndex < 1 || ai.lowIndex < index-ai.window { lv := big.NewDecimal(math.MaxFloat64) diff --git a/indicator_aroon_test.go b/indicator_aroon_test.go index 665c1ea..e838dda 100644 --- a/indicator_aroon_test.go +++ b/indicator_aroon_test.go @@ -21,6 +21,23 @@ func TestAroonUpIndicator(t *testing.T) { decimalEquals(t, 75, aroonUpIndicator.Calculate(4)) decimalEquals(t, 50, aroonUpIndicator.Calculate(5)) }) + + t.Run("works after calling RemoveCacheEntry ", func(t *testing.T) { + ts := mockTimeSeriesFl(1, 2, 3, 4, 3, 2, 1) + indicator := NewHighPriceIndicator(ts) + + aroonUpIndicator := NewAroonUpIndicator(indicator, 4) + + decimalEquals(t, 100, aroonUpIndicator.Calculate(3)) + decimalEquals(t, 75, aroonUpIndicator.Calculate(4)) + decimalEquals(t, 50, aroonUpIndicator.Calculate(5)) + + aroonUpIndicator.RemoveCachedEntry(4) + + decimalEquals(t, 100, aroonUpIndicator.Calculate(3)) + decimalEquals(t, 75, aroonUpIndicator.Calculate(4)) + decimalEquals(t, 50, aroonUpIndicator.Calculate(5)) + }) } func TestAroonDownIndicator(t *testing.T) { @@ -42,4 +59,21 @@ func TestAroonDownIndicator(t *testing.T) { decimalEquals(t, 75, aroonUpIndicator.Calculate(4)) decimalEquals(t, 50, aroonUpIndicator.Calculate(5)) }) + + t.Run("works after calling RemoveCacheEntry ", func(t *testing.T) { + ts := mockTimeSeriesFl(5, 4, 3, 2, 3, 4, 5) + indicator := NewLowPriceIndicator(ts) + + aroonUpIndicator := NewAroonDownIndicator(indicator, 4) + + decimalEquals(t, 100, aroonUpIndicator.Calculate(3)) + decimalEquals(t, 75, aroonUpIndicator.Calculate(4)) + decimalEquals(t, 50, aroonUpIndicator.Calculate(5)) + + aroonUpIndicator.RemoveCachedEntry(4) + + decimalEquals(t, 100, aroonUpIndicator.Calculate(3)) + decimalEquals(t, 75, aroonUpIndicator.Calculate(4)) + decimalEquals(t, 50, aroonUpIndicator.Calculate(5)) + }) } diff --git a/indicator_average_test.go b/indicator_average_test.go index 1674892..8706e0b 100644 --- a/indicator_average_test.go +++ b/indicator_average_test.go @@ -18,6 +18,28 @@ func TestAverageGainsIndicator(t *testing.T) { decimalEquals(t, 12.0/6.0, avgGains.Calculate(5)) }) + t.Run("Works after RemoveCachedEntry", func(t *testing.T) { + ts := mockTimeSeriesFl(1, 2, 3, 5, 8, 13) + + avgGains := NewAverageGainsIndicator(NewClosePriceIndicator(ts), 6) + + decimalEquals(t, 0, avgGains.Calculate(0)) + decimalEquals(t, 1.0/2.0, avgGains.Calculate(1)) + decimalEquals(t, 2.0/3.0, avgGains.Calculate(2)) + decimalEquals(t, 1.0, avgGains.Calculate(3)) + decimalEquals(t, 7.0/5.0, avgGains.Calculate(4)) + decimalEquals(t, 12.0/6.0, avgGains.Calculate(5)) + + avgGains.RemoveCachedEntry(3) + + decimalEquals(t, 0, avgGains.Calculate(0)) + decimalEquals(t, 1.0/2.0, avgGains.Calculate(1)) + decimalEquals(t, 2.0/3.0, avgGains.Calculate(2)) + decimalEquals(t, 1.0, avgGains.Calculate(3)) + decimalEquals(t, 7.0/5.0, avgGains.Calculate(4)) + decimalEquals(t, 12.0/6.0, avgGains.Calculate(5)) + }) + t.Run("Oscillating indicator", func(t *testing.T) { ts := mockTimeSeriesFl(0, 5, 2, 10, 12, 11) @@ -59,6 +81,28 @@ func TestNewAverageLossesIndicator(t *testing.T) { decimalEquals(t, 12.0/6.0, cumLosses.Calculate(5)) }) + t.Run("Works after RemoveCachedEntry", func(t *testing.T) { + ts := mockTimeSeriesFl(13, 8, 5, 3, 2, 1) + + cumLosses := NewAverageLossesIndicator(NewClosePriceIndicator(ts), 6) + + decimalEquals(t, 0, cumLosses.Calculate(0)) + decimalEquals(t, 5.0/2.0, cumLosses.Calculate(1)) + decimalEquals(t, 8.0/3.0, cumLosses.Calculate(2)) + decimalEquals(t, 10.0/4.0, cumLosses.Calculate(3)) + decimalEquals(t, 11.0/5.0, cumLosses.Calculate(4)) + decimalEquals(t, 12.0/6.0, cumLosses.Calculate(5)) + + cumLosses.RemoveCachedEntry(3) + + decimalEquals(t, 0, cumLosses.Calculate(0)) + decimalEquals(t, 5.0/2.0, cumLosses.Calculate(1)) + decimalEquals(t, 8.0/3.0, cumLosses.Calculate(2)) + decimalEquals(t, 10.0/4.0, cumLosses.Calculate(3)) + decimalEquals(t, 11.0/5.0, cumLosses.Calculate(4)) + decimalEquals(t, 12.0/6.0, cumLosses.Calculate(5)) + }) + t.Run("Oscillating indicator", func(t *testing.T) { ts := mockTimeSeriesFl(13, 16, 10, 8, 9, 8) diff --git a/indicator_basic.go b/indicator_basic.go index e1ebfab..e686229 100644 --- a/indicator_basic.go +++ b/indicator_basic.go @@ -15,6 +15,10 @@ func (vi volumeIndicator) Calculate(index int) big.Decimal { return vi.Candles[index].Volume } +func (vi volumeIndicator) RemoveCachedEntry(index int) { + //No-Op +} + type closePriceIndicator struct { *TimeSeries } @@ -28,6 +32,10 @@ func (cpi closePriceIndicator) Calculate(index int) big.Decimal { return cpi.Candles[index].ClosePrice } +func (cpi closePriceIndicator) RemoveCachedEntry(index int) { + //No-Op +} + type highPriceIndicator struct { *TimeSeries } @@ -43,6 +51,10 @@ func (hpi highPriceIndicator) Calculate(index int) big.Decimal { return hpi.Candles[index].MaxPrice } +func (hpi highPriceIndicator) RemoveCachedEntry(index int) { + //No-Op +} + type lowPriceIndicator struct { *TimeSeries } @@ -58,6 +70,10 @@ func (lpi lowPriceIndicator) Calculate(index int) big.Decimal { return lpi.Candles[index].MinPrice } +func (lpi lowPriceIndicator) RemoveCachedEntry(index int) { + //No-Op +} + type openPriceIndicator struct { *TimeSeries } @@ -73,6 +89,10 @@ func (opi openPriceIndicator) Calculate(index int) big.Decimal { return opi.Candles[index].OpenPrice } +func (opi openPriceIndicator) RemoveCachedEntry(index int) { + //No-Op +} + type typicalPriceIndicator struct { *TimeSeries } @@ -87,3 +107,7 @@ func (tpi typicalPriceIndicator) Calculate(index int) big.Decimal { numerator := tpi.Candles[index].MaxPrice.Add(tpi.Candles[index].MinPrice).Add(tpi.Candles[index].ClosePrice) return numerator.Div(big.NewFromString("3")) } + +func (tpi typicalPriceIndicator) RemoveCachedEntry(index int) { + //No-Op +} diff --git a/indicator_basic_test.go b/indicator_basic_test.go index 2f1b447..3250350 100644 --- a/indicator_basic_test.go +++ b/indicator_basic_test.go @@ -25,6 +25,10 @@ func TestVolumeIndicator_Calculate(t *testing.T) { indicator := NewVolumeIndicator(series) assert.EqualValues(t, "1.208", indicator.Calculate(0).FormattedString(3)) + + indicator.RemoveCachedEntry(0) + + assert.EqualValues(t, "1.208", indicator.Calculate(0).FormattedString(3)) } func TestTypicalPriceIndicator_Calculate(t *testing.T) { @@ -40,7 +44,10 @@ func TestTypicalPriceIndicator_Calculate(t *testing.T) { series.AddCandle(candle) - typicalPrice := NewTypicalPriceIndicator(series).Calculate(0) + indicator := NewTypicalPriceIndicator(series) + assert.EqualValues(t, "1.2143", indicator.Calculate(0).FormattedString(4)) + + indicator.RemoveCachedEntry(0) - assert.EqualValues(t, "1.2143", typicalPrice.FormattedString(4)) + assert.EqualValues(t, "1.2143", indicator.Calculate(0).FormattedString(4)) } diff --git a/indicator_bollinger_band.go b/indicator_bollinger_band.go index 1290e62..a908335 100644 --- a/indicator_bollinger_band.go +++ b/indicator_bollinger_band.go @@ -31,3 +31,8 @@ func NewBollingerLowerBandIndicator(indicator Indicator, window int, sigma float func (bbi bbandIndicator) Calculate(index int) big.Decimal { return bbi.ma.Calculate(index).Add(bbi.stdev.Calculate(index).Mul(bbi.muladd)) } + +func (bbi bbandIndicator) RemoveCachedEntry(index int) { + bbi.ma.RemoveCachedEntry(index) + bbi.stdev.RemoveCachedEntry(index) +} diff --git a/indicator_bollinger_band_test.go b/indicator_bollinger_band_test.go index 8ee158f..be8a9e7 100644 --- a/indicator_bollinger_band_test.go +++ b/indicator_bollinger_band_test.go @@ -118,4 +118,19 @@ func TestBollingerBandIndicator(t *testing.T) { decimalAlmostEquals(t, big.NewFromString(BBLOs[j]), bbLO.Calculate(i), 0.01) decimalAlmostEquals(t, big.NewFromString(BBWs[j]), bbUP.Calculate(i).Sub(bbLO.Calculate((i))), 0.01) } + + src.RemoveCachedEntry(0) + sma.RemoveCachedEntry(0) + wstd.RemoveCachedEntry(0) + bbUP.RemoveCachedEntry(0) + bbLO.RemoveCachedEntry(0) + + for i := window - 1; i < len(ts.Candles); i++ { + j := i - (window - 1) + decimalAlmostEquals(t, big.NewFromString(SMAs[j]), sma.Calculate(i), 0.01) + decimalAlmostEquals(t, big.NewFromString(STDEVs[j]), wstd.Calculate(i), 0.01) + decimalAlmostEquals(t, big.NewFromString(BBUPs[j]), bbUP.Calculate(i), 0.01) + decimalAlmostEquals(t, big.NewFromString(BBLOs[j]), bbLO.Calculate(i), 0.01) + decimalAlmostEquals(t, big.NewFromString(BBWs[j]), bbUP.Calculate(i).Sub(bbLO.Calculate((i))), 0.01) + } } diff --git a/indicator_cci.go b/indicator_cci.go index ac40fda..a5fde32 100644 --- a/indicator_cci.go +++ b/indicator_cci.go @@ -23,3 +23,7 @@ func (ccii commidityChannelIndexIndicator) Calculate(index int) big.Decimal { return typicalPrice.Calculate(index).Sub(typicalPriceSma.Calculate(index)).Div(meanDeviation.Calculate(index).Mul(big.NewFromString("0.015"))) } + +func (ccii commidityChannelIndexIndicator) RemoveCachedEntry(index int) { + //No-Op - since indicators are created directly within the Calculate function +} diff --git a/indicator_cci_test.go b/indicator_cci_test.go index f549e2a..1517713 100644 --- a/indicator_cci_test.go +++ b/indicator_cci_test.go @@ -26,4 +26,10 @@ func TestCommidityChannelIndexIndicator_Calculate(t *testing.T) { for i, result := range results { assert.EqualValues(t, result, cci.Calculate(i+19).FormattedString(4)) } + + cci.RemoveCachedEntry(19) + + for i, result := range results { + assert.EqualValues(t, result, cci.Calculate(i+19).FormattedString(4)) + } } diff --git a/indicator_constant.go b/indicator_constant.go index 14e2d16..8d614db 100644 --- a/indicator_constant.go +++ b/indicator_constant.go @@ -13,3 +13,7 @@ func NewConstantIndicator(constant float64) Indicator { func (ci constantIndicator) Calculate(index int) big.Decimal { return big.NewDecimal(float64(ci)) } + +func (ci constantIndicator) RemoveCachedEntry(index int) { + //No-Op +} diff --git a/indicator_constant_test.go b/indicator_constant_test.go index 05117b4..b3bf218 100644 --- a/indicator_constant_test.go +++ b/indicator_constant_test.go @@ -11,4 +11,8 @@ func TestConstantIndicator_Calculate(t *testing.T) { decimalEquals(t, 4.56, ci.Calculate(0)) decimalEquals(t, 4.56, ci.Calculate(-math.MaxInt64)) decimalEquals(t, 4.56, ci.Calculate(math.MaxInt64)) + + ci.RemoveCachedEntry(0) + + decimalEquals(t, 4.56, ci.Calculate(0)) } diff --git a/indicator_derivative.go b/indicator_derivative.go index 87ebbdd..c2c662b 100644 --- a/indicator_derivative.go +++ b/indicator_derivative.go @@ -6,7 +6,7 @@ import "github.com/sdcoffey/big" // The derivative is defined as the difference between the value at the previous index and the value at the current index. // Eg series [1, 1, 2, 3, 5, 8] -> [0, 0, 1, 1, 2, 3] type DerivativeIndicator struct { - Indicator Indicator + indicator Indicator } // Calculate returns the derivative of the underlying indicator. At index 0, it will always return 0. @@ -15,5 +15,10 @@ func (di DerivativeIndicator) Calculate(index int) big.Decimal { return big.ZERO } - return di.Indicator.Calculate(index).Sub(di.Indicator.Calculate(index - 1)) + return di.indicator.Calculate(index).Sub(di.indicator.Calculate(index - 1)) +} + +// RemoveCachedEntry removes the cached entry at the given index of the underlying indicator +func (di DerivativeIndicator) RemoveCachedEntry(index int) { + di.indicator.RemoveCachedEntry(index) } diff --git a/indicator_derivative_test.go b/indicator_derivative_test.go index 5001716..7aef59c 100644 --- a/indicator_derivative_test.go +++ b/indicator_derivative_test.go @@ -9,7 +9,7 @@ import ( func TestDerivativeIndicator(t *testing.T) { series := mockTimeSeries("1", "1", "2", "3", "5", "8", "13") indicator := DerivativeIndicator{ - Indicator: NewClosePriceIndicator(series), + indicator: NewClosePriceIndicator(series), } t.Run("returns zero at index zero", func(t *testing.T) { @@ -25,4 +25,22 @@ func TestDerivativeIndicator(t *testing.T) { assert.EqualValues(t, expected.String(), indicator.Calculate(i).String()) } }) + + t.Run("works after calling RemoveCacheEntry ", func(t *testing.T) { + assert.EqualValues(t, "0", indicator.Calculate(1).String()) + + for i := 2; i < len(series.Candles); i++ { + expected := series.Candles[i-2].ClosePrice + + assert.EqualValues(t, expected.String(), indicator.Calculate(i).String()) + } + + indicator.RemoveCachedEntry(2) + + for i := 2; i < len(series.Candles); i++ { + expected := series.Candles[i-2].ClosePrice + + assert.EqualValues(t, expected.String(), indicator.Calculate(i).String()) + } + }) } diff --git a/indicator_difference.go b/indicator_difference.go index bbd41a3..698c55f 100644 --- a/indicator_difference.go +++ b/indicator_difference.go @@ -19,3 +19,8 @@ func NewDifferenceIndicator(minuend, subtrahend Indicator) Indicator { func (di differenceIndicator) Calculate(index int) big.Decimal { return di.minuend.Calculate(index).Sub(di.subtrahend.Calculate(index)) } + +func (di differenceIndicator) RemoveCachedEntry(index int) { + di.minuend.RemoveCachedEntry(index) + di.subtrahend.RemoveCachedEntry(index) +} diff --git a/indicator_difference_test.go b/indicator_difference_test.go index 8783f54..f6c5fa7 100644 --- a/indicator_difference_test.go +++ b/indicator_difference_test.go @@ -10,4 +10,10 @@ func TestDifferenceIndicator_Calculate(t *testing.T) { decimalEquals(t, 2, di.Calculate(0)) decimalEquals(t, 0, di.Calculate(1)) decimalEquals(t, -2, di.Calculate(2)) + + di.RemoveCachedEntry(1) + + decimalEquals(t, 2, di.Calculate(0)) + decimalEquals(t, 0, di.Calculate(1)) + decimalEquals(t, -2, di.Calculate(2)) } diff --git a/indicator_fixed.go b/indicator_fixed.go index cadc9c8..85725e6 100644 --- a/indicator_fixed.go +++ b/indicator_fixed.go @@ -12,3 +12,7 @@ func NewFixedIndicator(vals ...float64) Indicator { func (fi fixedIndicator) Calculate(index int) big.Decimal { return big.NewDecimal(fi[index]) } + +func (fi fixedIndicator) RemoveCachedEntry(index int) { + //No-Op +} diff --git a/indicator_fixed_test.go b/indicator_fixed_test.go index e5bd01a..3fabc0a 100644 --- a/indicator_fixed_test.go +++ b/indicator_fixed_test.go @@ -13,4 +13,12 @@ func TestFixedIndicator_Calculate(t *testing.T) { decimalEquals(t, 2, fi.Calculate(2)) decimalEquals(t, -100, fi.Calculate(3)) decimalEquals(t, math.MaxInt64, fi.Calculate(4)) + + fi.RemoveCachedEntry(2) + + decimalEquals(t, 0, fi.Calculate(0)) + decimalEquals(t, 1, fi.Calculate(1)) + decimalEquals(t, 2, fi.Calculate(2)) + decimalEquals(t, -100, fi.Calculate(3)) + decimalEquals(t, math.MaxInt64, fi.Calculate(4)) } diff --git a/indicator_gains_test.go b/indicator_gains_test.go index 73a9807..d6d38f2 100644 --- a/indicator_gains_test.go +++ b/indicator_gains_test.go @@ -15,19 +15,37 @@ func TestGainIndicator(t *testing.T) { decimalEquals(t, 0, gains.Calculate(3)) decimalEquals(t, 0, gains.Calculate(4)) decimalEquals(t, 0, gains.Calculate(5)) + + gains.RemoveCachedEntry(2) + + decimalEquals(t, 0, gains.Calculate(0)) + decimalEquals(t, 1, gains.Calculate(1)) + decimalEquals(t, 1, gains.Calculate(2)) + decimalEquals(t, 0, gains.Calculate(3)) + decimalEquals(t, 0, gains.Calculate(4)) + decimalEquals(t, 0, gains.Calculate(5)) } func TestLossIndicator(t *testing.T) { ts := mockTimeSeriesFl(1, 2, 3, 3, 2, 0) - gains := NewLossIndicator(NewClosePriceIndicator(ts)) + losses := NewLossIndicator(NewClosePriceIndicator(ts)) - decimalEquals(t, 0, gains.Calculate(0)) - decimalEquals(t, 0, gains.Calculate(1)) - decimalEquals(t, 0, gains.Calculate(2)) - decimalEquals(t, 0, gains.Calculate(3)) - decimalEquals(t, 1, gains.Calculate(4)) - decimalEquals(t, 2, gains.Calculate(5)) + decimalEquals(t, 0, losses.Calculate(0)) + decimalEquals(t, 0, losses.Calculate(1)) + decimalEquals(t, 0, losses.Calculate(2)) + decimalEquals(t, 0, losses.Calculate(3)) + decimalEquals(t, 1, losses.Calculate(4)) + decimalEquals(t, 2, losses.Calculate(5)) + + losses.RemoveCachedEntry(2) + + decimalEquals(t, 0, losses.Calculate(0)) + decimalEquals(t, 0, losses.Calculate(1)) + decimalEquals(t, 0, losses.Calculate(2)) + decimalEquals(t, 0, losses.Calculate(3)) + decimalEquals(t, 1, losses.Calculate(4)) + decimalEquals(t, 2, losses.Calculate(5)) } func TestCumulativeGainsIndicator(t *testing.T) { @@ -42,6 +60,15 @@ func TestCumulativeGainsIndicator(t *testing.T) { decimalEquals(t, 4, cumGains.Calculate(3)) decimalEquals(t, 7, cumGains.Calculate(4)) decimalEquals(t, 12, cumGains.Calculate(5)) + + cumGains.RemoveCachedEntry(2) + + decimalEquals(t, 0, cumGains.Calculate(0)) + decimalEquals(t, 1, cumGains.Calculate(1)) + decimalEquals(t, 2, cumGains.Calculate(2)) + decimalEquals(t, 4, cumGains.Calculate(3)) + decimalEquals(t, 7, cumGains.Calculate(4)) + decimalEquals(t, 12, cumGains.Calculate(5)) }) t.Run("Oscillating scale", func(t *testing.T) { @@ -83,6 +110,15 @@ func TestCumulativeLossesIndicator(t *testing.T) { decimalEquals(t, 10, cumLosses.Calculate(3)) decimalEquals(t, 11, cumLosses.Calculate(4)) decimalEquals(t, 12, cumLosses.Calculate(5)) + + cumLosses.RemoveCachedEntry(2) + + decimalEquals(t, 0, cumLosses.Calculate(0)) + decimalEquals(t, 5, cumLosses.Calculate(1)) + decimalEquals(t, 8, cumLosses.Calculate(2)) + decimalEquals(t, 10, cumLosses.Calculate(3)) + decimalEquals(t, 11, cumLosses.Calculate(4)) + decimalEquals(t, 12, cumLosses.Calculate(5)) }) t.Run("Oscillating indicator", func(t *testing.T) { @@ -134,4 +170,22 @@ func TestPercentGainIndicator(t *testing.T) { decimalEquals(t, -.5, pgi.Calculate(2)) decimalEquals(t, 0, pgi.Calculate(3)) }) + + t.Run("Works after RemoveCachedEntry", func(t *testing.T) { + ts := mockTimeSeriesFl(1, 1.5, 2.25, 2.25) + + pgi := NewPercentChangeIndicator(NewClosePriceIndicator(ts)) + + decimalEquals(t, 0, pgi.Calculate(0)) + decimalEquals(t, .5, pgi.Calculate(1)) + decimalEquals(t, .5, pgi.Calculate(2)) + decimalEquals(t, 0, pgi.Calculate(3)) + + pgi.RemoveCachedEntry(2) + + decimalEquals(t, 0, pgi.Calculate(0)) + decimalEquals(t, .5, pgi.Calculate(1)) + decimalEquals(t, .5, pgi.Calculate(2)) + decimalEquals(t, 0, pgi.Calculate(3)) + }) } diff --git a/indicator_maximum_drawdown.go b/indicator_maximum_drawdown.go index bae38d9..e5a6970 100644 --- a/indicator_maximum_drawdown.go +++ b/indicator_maximum_drawdown.go @@ -26,3 +26,7 @@ func (mdi maximumDrawdownIndicator) Calculate(index int) big.Decimal { return (minVal.Sub(maxVal)).Div(maxVal) } + +func (mdi maximumDrawdownIndicator) RemoveCachedEntry(index int) { + mdi.indicator.RemoveCachedEntry(index) +} diff --git a/indicator_maximum_drawdown_test.go b/indicator_maximum_drawdown_test.go index 81a815c..c549742 100644 --- a/indicator_maximum_drawdown_test.go +++ b/indicator_maximum_drawdown_test.go @@ -8,6 +8,10 @@ func TestMaximumDrawdownIndicator(t *testing.T) { mvi := NewMaximumDrawdownIndicator(NewClosePriceIndicator(ts), 3) decimalEquals(t, -0.95, mvi.Calculate(ts.LastIndex())) + + mvi.RemoveCachedEntry(ts.LastIndex()) + + decimalEquals(t, -0.95, mvi.Calculate(ts.LastIndex())) }) t.Run("without window", func(t *testing.T) { @@ -15,5 +19,9 @@ func TestMaximumDrawdownIndicator(t *testing.T) { mvi := NewMaximumDrawdownIndicator(NewClosePriceIndicator(ts), -1) decimalEquals(t, -1.05, mvi.Calculate(ts.LastIndex())) + + mvi.RemoveCachedEntry(ts.LastIndex()) + + decimalEquals(t, -1.05, mvi.Calculate(ts.LastIndex())) }) } diff --git a/indicator_maximum_value.go b/indicator_maximum_value.go index cecbffd..9fb6d8d 100644 --- a/indicator_maximum_value.go +++ b/indicator_maximum_value.go @@ -34,3 +34,7 @@ func (mvi maximumValueIndicator) Calculate(index int) big.Decimal { return maxValue } + +func (mvi maximumValueIndicator) RemoveCachedEntry(index int) { + mvi.indicator.RemoveCachedEntry(index) +} diff --git a/indicator_maximum_value_test.go b/indicator_maximum_value_test.go index e9d455e..7905309 100644 --- a/indicator_maximum_value_test.go +++ b/indicator_maximum_value_test.go @@ -9,6 +9,10 @@ func TestMaximumValueIndicator(t *testing.T) { mvi := NewMaximumValueIndicator(NewClosePriceIndicator(ts), 3) decimalEquals(t, 20, mvi.Calculate(ts.LastIndex())) decimalEquals(t, 21, mvi.Calculate(ts.LastIndex()-1)) + + mvi.RemoveCachedEntry(ts.LastIndex()) + + decimalEquals(t, 20, mvi.Calculate(ts.LastIndex())) }) t.Run("without window", func(t *testing.T) { @@ -16,5 +20,9 @@ func TestMaximumValueIndicator(t *testing.T) { mvi := NewMaximumValueIndicator(NewClosePriceIndicator(ts), -1) decimalEquals(t, 20, mvi.Calculate(ts.LastIndex())) + + mvi.RemoveCachedEntry(ts.LastIndex()) + + decimalEquals(t, 20, mvi.Calculate(ts.LastIndex())) }) } diff --git a/indicator_mean_deviation_test.go b/indicator_mean_deviation_test.go index 033f70e..ac1e0ea 100644 --- a/indicator_mean_deviation_test.go +++ b/indicator_mean_deviation_test.go @@ -18,4 +18,16 @@ func TestMeanDeviationIndicator(t *testing.T) { decimalEquals(t, 2.32, meanDeviation.Calculate(8)) decimalEquals(t, 2.72, meanDeviation.Calculate(9)) decimalEquals(t, 3.52, meanDeviation.Calculate(10)) + + meanDeviation.RemoveCachedEntry(6) + + decimalEquals(t, 2.4444, meanDeviation.Calculate(2)) + decimalEquals(t, 2.5, meanDeviation.Calculate(3)) + decimalEquals(t, 2.16, meanDeviation.Calculate(4)) + decimalEquals(t, 1.68, meanDeviation.Calculate(5)) + decimalEquals(t, 1.2, meanDeviation.Calculate(6)) + decimalEquals(t, 2.16, meanDeviation.Calculate(7)) + decimalEquals(t, 2.32, meanDeviation.Calculate(8)) + decimalEquals(t, 2.72, meanDeviation.Calculate(9)) + decimalEquals(t, 3.52, meanDeviation.Calculate(10)) } diff --git a/indicator_minimum_value.go b/indicator_minimum_value.go index 7282548..d47955d 100644 --- a/indicator_minimum_value.go +++ b/indicator_minimum_value.go @@ -34,3 +34,7 @@ func (mvi minimumValueIndicator) Calculate(index int) big.Decimal { return minValue } + +func (mvi minimumValueIndicator) RemoveCachedEntry(index int) { + mvi.indicator.RemoveCachedEntry(index) +} diff --git a/indicator_minimum_value_test.go b/indicator_minimum_value_test.go index 76a6ebf..89d8e31 100644 --- a/indicator_minimum_value_test.go +++ b/indicator_minimum_value_test.go @@ -9,6 +9,10 @@ func TestMinimumValueIndicator(t *testing.T) { mvi := NewMinimumValueIndicator(NewClosePriceIndicator(ts), 3) decimalEquals(t, 1, mvi.Calculate(ts.LastIndex())) decimalEquals(t, 0, mvi.Calculate(ts.LastIndex()-1)) + + mvi.RemoveCachedEntry(ts.LastIndex()) + + decimalEquals(t, 1, mvi.Calculate(ts.LastIndex())) }) t.Run("without window", func(t *testing.T) { @@ -16,5 +20,9 @@ func TestMinimumValueIndicator(t *testing.T) { mvi := NewMinimumValueIndicator(NewClosePriceIndicator(ts), -1) decimalEquals(t, -1, mvi.Calculate(ts.LastIndex())) + + mvi.RemoveCachedEntry(ts.LastIndex()) + + decimalEquals(t, -1, mvi.Calculate(ts.LastIndex())) }) } diff --git a/indicator_moving_average.go b/indicator_moving_average.go index 017390a..a2fad11 100644 --- a/indicator_moving_average.go +++ b/indicator_moving_average.go @@ -23,6 +23,10 @@ func (sma smaIndicator) Calculate(index int) big.Decimal { return sum.Div(big.NewDecimal(float64(realwindow))) } +func (sma smaIndicator) RemoveCachedEntry(index int) { + sma.indicator.RemoveCachedEntry(index) +} + type emaIndicator struct { Indicator window int @@ -51,16 +55,20 @@ func (ema *emaIndicator) Calculate(index int) big.Decimal { emaPrev := ema.Calculate(index - 1) result := ema.Indicator.Calculate(index).Sub(emaPrev).Mul(ema.alpha).Add(emaPrev) - ema.cacheResult(index, result) + ema.cacheResult(index, &result) return result } -func (ema *emaIndicator) cacheResult(index int, val big.Decimal) { +func (ema *emaIndicator) RemoveCachedEntry(index int) { + ema.cacheResult(index, nil) +} + +func (ema *emaIndicator) cacheResult(index int, val *big.Decimal) { if index < len(ema.resultCache) { - ema.resultCache[index] = &val + ema.resultCache[index] = val } else { - ema.resultCache = append(ema.resultCache, &val) + ema.resultCache = append(ema.resultCache, val) } } diff --git a/indicator_moving_average_test.go b/indicator_moving_average_test.go index 5a5a6b3..f5cc8ac 100644 --- a/indicator_moving_average_test.go +++ b/indicator_moving_average_test.go @@ -13,7 +13,22 @@ func TestSimpleMovingAverage(t *testing.T) { decimalEquals(t, 1, sma.Calculate(0)) decimalEquals(t, 1.5, sma.Calculate(1)) + decimalEquals(t, 2, sma.Calculate(2)) + decimalEquals(t, 3, sma.Calculate(3)) + decimalEquals(t, 10.0/3.0, sma.Calculate(4)) + decimalEquals(t, 11.0/3.0, sma.Calculate(5)) + decimalEquals(t, 4, sma.Calculate(6)) + decimalEquals(t, 13.0/3.0, sma.Calculate(7)) + decimalEquals(t, 4, sma.Calculate(8)) + decimalEquals(t, 10.0/3.0, sma.Calculate(9)) + decimalEquals(t, 10.0/3.0, sma.Calculate(10)) + decimalEquals(t, 10.0/3.0, sma.Calculate(11)) + decimalEquals(t, 3, sma.Calculate(12)) + + sma.RemoveCachedEntry(6) + decimalEquals(t, 1, sma.Calculate(0)) + decimalEquals(t, 1.5, sma.Calculate(1)) decimalEquals(t, 2, sma.Calculate(2)) decimalEquals(t, 3, sma.Calculate(3)) decimalEquals(t, 10.0/3.0, sma.Calculate(4)) @@ -62,6 +77,26 @@ func TestExponentialMovingAverage(t *testing.T) { assert.EqualValues(t, 10001, len(ema.(*emaIndicator).resultCache)) }) + + t.Run("works after calling RemoveCacheEntry ", func(t *testing.T) { + ts := 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) + + ema := NewEMAIndicator(NewClosePriceIndicator(ts), 10) + + decimalEquals(t, 63.6948, ema.Calculate(9)) + decimalEquals(t, 63.2649, ema.Calculate(10)) + decimalEquals(t, 62.9458, ema.Calculate(11)) + + ema.RemoveCachedEntry(10) + + decimalEquals(t, 63.6948, ema.Calculate(9)) + decimalEquals(t, 63.2649, ema.Calculate(10)) + decimalEquals(t, 62.9458, ema.Calculate(11)) + }) } func TestNewMMAIndicator(t *testing.T) { @@ -76,6 +111,12 @@ func TestNewMMAIndicator(t *testing.T) { decimalEquals(t, 63.9983, mma.Calculate(9)) decimalEquals(t, 63.7315, mma.Calculate(10)) decimalEquals(t, 63.5094, mma.Calculate(11)) + + mma.RemoveCachedEntry(10) + + decimalEquals(t, 63.9983, mma.Calculate(9)) + decimalEquals(t, 63.7315, mma.Calculate(10)) + decimalEquals(t, 63.5094, mma.Calculate(11)) } func TestNewMACDIndicator(t *testing.T) { @@ -95,7 +136,7 @@ func TestNewMACDHistogramIndicator(t *testing.T) { assert.NotNil(t, macdHistogram) } -func BenchmarkExponetialMovingAverage(b *testing.B) { +func BenchmarkExponentialMovingAverage(b *testing.B) { size := 10000 ts := randomTimeSeries(size) diff --git a/indicator_relative_strength.go b/indicator_relative_strength.go index 0b6b83e..90127f6 100644 --- a/indicator_relative_strength.go +++ b/indicator_relative_strength.go @@ -30,6 +30,10 @@ func (rsi relativeStrengthIndexIndicator) Calculate(index int) big.Decimal { return oneHundred.Sub(oneHundred.Div(big.ONE.Add(relativeStrength))) } +func (rsi relativeStrengthIndexIndicator) RemoveCachedEntry(index int) { + rsi.rsIndicator.RemoveCachedEntry(index) +} + type relativeStrengthIndicator struct { avgGain Indicator avgLoss Indicator @@ -55,3 +59,8 @@ func (rs relativeStrengthIndicator) Calculate(index int) big.Decimal { return avgGain.Div(avgLoss) } + +func (rs relativeStrengthIndicator) RemoveCachedEntry(index int) { + rs.avgGain.RemoveCachedEntry(index) + rs.avgLoss.RemoveCachedEntry(index) +} diff --git a/indicator_relative_strength_test.go b/indicator_relative_strength_test.go index 1cb11cc..c71f42d 100644 --- a/indicator_relative_strength_test.go +++ b/indicator_relative_strength_test.go @@ -38,6 +38,20 @@ func TestRelativeStrengthIndexIndicator(t *testing.T) { decimalEquals(t, 76.2770, indicator.Calculate(23)) decimalEquals(t, 77.9908, indicator.Calculate(24)) decimalEquals(t, 67.4895, indicator.Calculate(25)) + + indicator.RemoveCachedEntry(20) + + decimalEquals(t, 68.4747, indicator.Calculate(15)) + decimalEquals(t, 64.7836, indicator.Calculate(16)) + decimalEquals(t, 72.0777, indicator.Calculate(17)) + decimalEquals(t, 60.7800, indicator.Calculate(18)) + decimalEquals(t, 63.6439, indicator.Calculate(19)) + decimalEquals(t, 72.3434, indicator.Calculate(20)) + decimalEquals(t, 67.3823, indicator.Calculate(21)) + decimalEquals(t, 68.5438, indicator.Calculate(22)) + decimalEquals(t, 76.2770, indicator.Calculate(23)) + decimalEquals(t, 77.9908, indicator.Calculate(24)) + decimalEquals(t, 67.4895, indicator.Calculate(25)) }) } @@ -55,10 +69,28 @@ func TestRelativeStrengthIndicator(t *testing.T) { decimalEquals(t, 3.2153, indicator.Calculate(23)) decimalEquals(t, 3.5436, indicator.Calculate(24)) decimalEquals(t, 2.0759, indicator.Calculate(25)) + + indicator.RemoveCachedEntry(20) + + decimalEquals(t, 2.1721, indicator.Calculate(15)) + decimalEquals(t, 1.8396, indicator.Calculate(16)) + decimalEquals(t, 2.5814, indicator.Calculate(17)) + decimalEquals(t, 1.5497, indicator.Calculate(18)) + decimalEquals(t, 1.7506, indicator.Calculate(19)) + decimalEquals(t, 2.6158, indicator.Calculate(20)) + decimalEquals(t, 2.0658, indicator.Calculate(21)) + decimalEquals(t, 2.1790, indicator.Calculate(22)) + decimalEquals(t, 3.2153, indicator.Calculate(23)) + decimalEquals(t, 3.5436, indicator.Calculate(24)) + decimalEquals(t, 2.0759, indicator.Calculate(25)) } func TestRelativeStrengthIndicatorNoPriceChange(t *testing.T) { close := NewClosePriceIndicator(mockTimeSeries("42.0", "42.0")) rsInd := NewRelativeStrengthIndicator(close, 2) assert.Equal(t, big.NewDecimal(math.MaxFloat64).FormattedString(2), rsInd.Calculate(1).FormattedString(2)) + + rsInd.RemoveCachedEntry(1) + + assert.Equal(t, big.NewDecimal(math.MaxFloat64).FormattedString(2), rsInd.Calculate(1).FormattedString(2)) } diff --git a/indicator_relative_vigor_index.go b/indicator_relative_vigor_index.go index 932ecc5..53ee4b5 100644 --- a/indicator_relative_vigor_index.go +++ b/indicator_relative_vigor_index.go @@ -42,6 +42,11 @@ func (rvii relativeVigorIndexIndicator) Calculate(index int) big.Decimal { return num.Div(denom) } +func (rvii relativeVigorIndexIndicator) RemoveCachedEntry(index int) { + rvii.numerator.RemoveCachedEntry(index) + rvii.denominator.RemoveCachedEntry(index) +} + type relativeVigorIndexSignalLine struct { relativeVigorIndex Indicator } @@ -66,3 +71,7 @@ func (rvsn relativeVigorIndexSignalLine) Calculate(index int) big.Decimal { return (rvi.Add(i).Add(j).Add(k)).Div(big.NewFromString("6")) } + +func (rvsn relativeVigorIndexSignalLine) RemoveCachedEntry(index int) { + rvsn.relativeVigorIndex.RemoveCachedEntry(index) +} diff --git a/indicator_relative_vigor_index_test.go b/indicator_relative_vigor_index_test.go index 8549149..97723b2 100644 --- a/indicator_relative_vigor_index_test.go +++ b/indicator_relative_vigor_index_test.go @@ -25,6 +25,11 @@ func TestRelativeVigorIndexIndicator_Calculate(t *testing.T) { t.Run("Calculates rvii", func(t *testing.T) { assert.EqualValues(t, "0.756", rvii.Calculate(3).FormattedString(3)) }) + + t.Run("Works after RemoveCachedEntry", func(t *testing.T) { + rvii.RemoveCachedEntry(3) + assert.EqualValues(t, "0.756", rvii.Calculate(3).FormattedString(3)) + }) } func TestRelativeVigorIndexSignalLine_Calculate(t *testing.T) { @@ -50,4 +55,9 @@ func TestRelativeVigorIndexSignalLine_Calculate(t *testing.T) { t.Run("Calculates rvii signal line", func(t *testing.T) { assert.EqualValues(t, "0.5752", signalLine.Calculate(7).FormattedString(4)) }) + + t.Run("Works after RemoveCachedEntry", func(t *testing.T) { + signalLine.RemoveCachedEntry(7) + assert.EqualValues(t, "0.5752", signalLine.Calculate(7).FormattedString(4)) + }) } diff --git a/indicator_standard_deviation.go b/indicator_standard_deviation.go index ac6009c..349b1a5 100644 --- a/indicator_standard_deviation.go +++ b/indicator_standard_deviation.go @@ -20,3 +20,7 @@ type standardDeviationIndicator struct { func (sdi standardDeviationIndicator) Calculate(index int) big.Decimal { return sdi.indicator.Calculate(index).Sqrt() } + +func (sdi standardDeviationIndicator) RemoveCachedEntry(index int) { + sdi.indicator.RemoveCachedEntry(index) +} diff --git a/indicator_standard_deviation_test.go b/indicator_standard_deviation_test.go index 0c7153b..84306a0 100644 --- a/indicator_standard_deviation_test.go +++ b/indicator_standard_deviation_test.go @@ -32,5 +32,14 @@ func TestStandardDeviationIndicator(t *testing.T) { assert.EqualValues(t, "14.54", stdDev.Calculate(4).FormattedString(2)) assert.EqualValues(t, "13.27", stdDev.Calculate(5).FormattedString(2)) assert.EqualValues(t, "12.30", stdDev.Calculate(6).FormattedString(2)) + + stdDev.RemoveCachedEntry(3) + + assert.EqualValues(t, "4.00", stdDev.Calculate(1).FormattedString(2)) + assert.EqualValues(t, "15.43", stdDev.Calculate(2).FormattedString(2)) + assert.EqualValues(t, "13.65", stdDev.Calculate(3).FormattedString(2)) + assert.EqualValues(t, "14.54", stdDev.Calculate(4).FormattedString(2)) + assert.EqualValues(t, "13.27", stdDev.Calculate(5).FormattedString(2)) + assert.EqualValues(t, "12.30", stdDev.Calculate(6).FormattedString(2)) }) } diff --git a/indicator_trend.go b/indicator_trend.go index a4c9888..a5d76ca 100644 --- a/indicator_trend.go +++ b/indicator_trend.go @@ -32,6 +32,10 @@ func (tli trendLineIndicator) Calculate(index int) big.Decimal { return ab.Div(cd) } +func (tli trendLineIndicator) RemoveCachedEntry(index int) { + tli.indicator.RemoveCachedEntry(index) +} + func sumX(decimals []big.Decimal) (s big.Decimal) { s = big.ZERO diff --git a/indicator_trend_test.go b/indicator_trend_test.go index b932845..bdde8e8 100644 --- a/indicator_trend_test.go +++ b/indicator_trend_test.go @@ -31,6 +31,10 @@ func TestTrendIndicator(t *testing.T) { indicator := NewTrendlineIndicator(NewClosePriceIndicator(series), 4) assert.EqualValues(t, test.expectedResult, indicator.Calculate(3).String()) + + indicator.RemoveCachedEntry(3) + + assert.EqualValues(t, test.expectedResult, indicator.Calculate(3).String()) } }) diff --git a/indicator_variance.go b/indicator_variance.go index acf5e48..53801ec 100644 --- a/indicator_variance.go +++ b/indicator_variance.go @@ -6,12 +6,12 @@ import "github.com/sdcoffey/big" // deviations from the mean at any given index in the time series. func NewVarianceIndicator(ind Indicator) Indicator { return varianceIndicator{ - Indicator: ind, + indicator: ind, } } type varianceIndicator struct { - Indicator Indicator + indicator Indicator } // Calculate returns the Variance for this indicator at the given index @@ -20,14 +20,18 @@ func (vi varianceIndicator) Calculate(index int) big.Decimal { return big.ZERO } - avgIndicator := NewSimpleMovingAverage(vi.Indicator, index+1) + avgIndicator := NewSimpleMovingAverage(vi.indicator, index+1) avg := avgIndicator.Calculate(index) variance := big.ZERO for i := 0; i <= index; i++ { - pow := vi.Indicator.Calculate(i).Sub(avg).Pow(2) + pow := vi.indicator.Calculate(i).Sub(avg).Pow(2) variance = variance.Add(pow) } return variance.Div(big.NewDecimal(float64(index + 1))) } + +func (vi varianceIndicator) RemoveCachedEntry(index int) { + vi.indicator.RemoveCachedEntry(index) +} diff --git a/indicator_variance_test.go b/indicator_variance_test.go index 709e91b..3cb8779 100644 --- a/indicator_variance_test.go +++ b/indicator_variance_test.go @@ -32,5 +32,14 @@ func TestVarianceIndicator(t *testing.T) { assert.EqualValues(t, "211.36", varInd.Calculate(4).FormattedString(2)) assert.EqualValues(t, "176.22", varInd.Calculate(5).FormattedString(2)) assert.EqualValues(t, "151.27", varInd.Calculate(6).FormattedString(2)) + + varInd.RemoveCachedEntry(3) + + assert.EqualValues(t, "16.00", varInd.Calculate(1).FormattedString(2)) + assert.EqualValues(t, "238.22", varInd.Calculate(2).FormattedString(2)) + assert.EqualValues(t, "186.19", varInd.Calculate(3).FormattedString(2)) + assert.EqualValues(t, "211.36", varInd.Calculate(4).FormattedString(2)) + assert.EqualValues(t, "176.22", varInd.Calculate(5).FormattedString(2)) + assert.EqualValues(t, "151.27", varInd.Calculate(6).FormattedString(2)) }) } diff --git a/indicator_windowed_standard_deviation.go b/indicator_windowed_standard_deviation.go index a62bf5e..188c72d 100644 --- a/indicator_windowed_standard_deviation.go +++ b/indicator_windowed_standard_deviation.go @@ -29,3 +29,7 @@ func (sdi windowedStandardDeviationIndicator) Calculate(index int) big.Decimal { return variance.Div(big.NewDecimal(float64(realwindow))).Sqrt() } + +func (sdi windowedStandardDeviationIndicator) RemoveCachedEntry(index int) { + sdi.movingAverage.RemoveCachedEntry(index) +}