Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to remove (to re-calc) cached calculations #33

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
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
1 change: 1 addition & 0 deletions indicator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
4 changes: 4 additions & 0 deletions indicator_aroon.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
34 changes: 34 additions & 0 deletions indicator_aroon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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))
})
}
44 changes: 44 additions & 0 deletions indicator_average_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand Down
24 changes: 24 additions & 0 deletions indicator_basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand All @@ -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
}
Expand All @@ -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
}
Expand All @@ -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
}
Expand All @@ -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
}
11 changes: 9 additions & 2 deletions indicator_basic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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))
}
5 changes: 5 additions & 0 deletions indicator_bollinger_band.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
15 changes: 15 additions & 0 deletions indicator_bollinger_band_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
4 changes: 4 additions & 0 deletions indicator_cci.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
6 changes: 6 additions & 0 deletions indicator_cci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}
4 changes: 4 additions & 0 deletions indicator_constant.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
4 changes: 4 additions & 0 deletions indicator_constant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
9 changes: 7 additions & 2 deletions indicator_derivative.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
}
20 changes: 19 additions & 1 deletion indicator_derivative_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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())
}
})
}
5 changes: 5 additions & 0 deletions indicator_difference.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
6 changes: 6 additions & 0 deletions indicator_difference_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
4 changes: 4 additions & 0 deletions indicator_fixed.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
8 changes: 8 additions & 0 deletions indicator_fixed_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Loading