diff --git a/pkg/indicator/ad.go b/pkg/indicator/ad.go index dd2f4bf5c..d7263a5ab 100644 --- a/pkg/indicator/ad.go +++ b/pkg/indicator/ad.go @@ -22,12 +22,7 @@ type AD struct { UpdateCallbacks []func(value float64) } -func (inc *AD) Update(kLine types.KLine) { - cloze := kLine.Close.Float64() - high := kLine.High.Float64() - low := kLine.Low.Float64() - volume := kLine.Volume.Float64() - +func (inc *AD) Update(high, low, cloze, volume float64) { var moneyFlowVolume float64 if high == low { moneyFlowVolume = 0 @@ -65,7 +60,7 @@ func (inc *AD) calculateAndUpdate(kLines []types.KLine) { if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) { continue } - inc.Update(k) + inc.Update(k.High.Float64(), k.Low.Float64(), k.Close.Float64(), k.Volume.Float64()) } inc.EmitUpdate(inc.Last()) diff --git a/pkg/indicator/atr.go b/pkg/indicator/atr.go index 573052a8b..5b1e384cf 100644 --- a/pkg/indicator/atr.go +++ b/pkg/indicator/atr.go @@ -11,25 +11,26 @@ import ( type ATR struct { types.IntervalWindow Values types.Float64Slice - TrueRanges types.Float64Slice PercentageVolatility types.Float64Slice - PriviousClose float64 + + PriviousClose float64 + RMA *RMA EndTime time.Time UpdateCallbacks []func(value float64) } -func (inc *ATR) Update(kLine types.KLine) { +func (inc *ATR) Update(high, low, cloze float64) { if inc.Window <= 0 { panic("window must be greater than 0") } - cloze := kLine.Close.Float64() - high := kLine.High.Float64() - low := kLine.Low.Float64() + if len(inc.Values) == 0 { + inc.RMA = &RMA{IntervalWindow: types.IntervalWindow{Window: inc.Window}} + } if inc.PriviousClose == 0 { - inc.PriviousClose = kLine.Close.Float64() + inc.PriviousClose = cloze return } @@ -39,24 +40,12 @@ func (inc *ATR) Update(kLine types.KLine) { math.Abs(high - inc.PriviousClose), math.Abs(low - inc.PriviousClose), }.Max() - inc.TrueRanges.Push(trueRange) inc.PriviousClose = cloze // apply rolling moving average - if len(inc.TrueRanges) < inc.Window { - return - } - - if len(inc.TrueRanges) == inc.Window { - atr := inc.TrueRanges.Mean() - inc.Values.Push(atr) - inc.PercentageVolatility.Push(atr / cloze) - return - } - - lambda := 1 / float64(inc.Window) - atr := inc.Values.Last()*(1-lambda) + inc.TrueRanges.Last()*lambda + inc.RMA.Update(trueRange) + atr := inc.RMA.Last() inc.Values.Push(atr) inc.PercentageVolatility.Push(atr / cloze) } @@ -87,7 +76,7 @@ func (inc *ATR) calculateAndUpdate(kLines []types.KLine) { if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) { continue } - inc.Update(k) + inc.Update(k.High.Float64(), k.Low.Float64(), k.Close.Float64()) } inc.EmitUpdate(inc.Last()) diff --git a/pkg/indicator/macd.go b/pkg/indicator/macd.go index ed3e3ba76..3dfbd6d45 100644 --- a/pkg/indicator/macd.go +++ b/pkg/indicator/macd.go @@ -29,25 +29,16 @@ type MACD struct { UpdateCallbacks []func(value float64) } -func (inc *MACD) calculateMACD(kLines []types.KLine, priceF KLinePriceMapper) float64 { - for _, kline := range kLines { - inc.Update(kline, priceF) - } - return inc.Values[len(inc.Values)-1] -} - -func (inc *MACD) Update(kLine types.KLine, priceF KLinePriceMapper) { +func (inc *MACD) Update(x float64) { if len(inc.Values) == 0 { inc.FastEWMA = EWMA{IntervalWindow: types.IntervalWindow{Window: inc.ShortPeriod}} inc.SlowEWMA = EWMA{IntervalWindow: types.IntervalWindow{Window: inc.LongPeriod}} inc.SignalLine = EWMA{IntervalWindow: types.IntervalWindow{Window: inc.Window}} } - price := priceF(kLine) - // update fast and slow ema - inc.FastEWMA.Update(price) - inc.SlowEWMA.Update(price) + inc.FastEWMA.Update(x) + inc.SlowEWMA.Update(x) // update macd macd := inc.FastEWMA.Last() - inc.SlowEWMA.Last() @@ -60,18 +51,23 @@ func (inc *MACD) Update(kLine types.KLine, priceF KLinePriceMapper) { inc.Histogram.Push(macd - inc.SignalLine.Last()) } +func (inc *MACD) calculateMACD(kLines []types.KLine, priceF KLinePriceMapper) float64 { + for _, kline := range kLines { + inc.Update(kline.Close.Float64()) + } + return inc.Values[len(inc.Values)-1] +} + func (inc *MACD) calculateAndUpdate(kLines []types.KLine) { if len(kLines) == 0 { return } - var priceF = KLineClosePriceMapper - for _, k := range kLines { if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) { continue } - inc.Update(k, priceF) + inc.Update(k.Close.Float64()) } inc.EmitUpdate(inc.Values[len(inc.Values)-1]) diff --git a/pkg/indicator/obv.go b/pkg/indicator/obv.go index 7f9a28723..3ea11772d 100644 --- a/pkg/indicator/obv.go +++ b/pkg/indicator/obv.go @@ -22,10 +22,7 @@ type OBV struct { UpdateCallbacks []func(value float64) } -func (inc *OBV) Update(kLine types.KLine, priceF KLinePriceMapper) { - price := priceF(kLine) - volume := kLine.Volume.Float64() - +func (inc *OBV) Update(price, volume float64) { if len(inc.Values) == 0 { inc.PrePrice = price inc.Values.Push(volume) @@ -47,17 +44,16 @@ func (inc *OBV) Last() float64 { } func (inc *OBV) calculateAndUpdate(kLines []types.KLine) { - var priceF = KLineClosePriceMapper - for _, k := range kLines { if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) { continue } - inc.Update(k, priceF) + inc.Update(k.Close.Float64(), k.Volume.Float64()) } inc.EmitUpdate(inc.Last()) inc.EndTime = kLines[len(kLines)-1].EndTime.Time() } + func (inc *OBV) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { if inc.Interval != interval { return diff --git a/pkg/indicator/rsi.go b/pkg/indicator/rsi.go index 3531e2ee9..b9eabd6f4 100644 --- a/pkg/indicator/rsi.go +++ b/pkg/indicator/rsi.go @@ -24,8 +24,7 @@ type RSI struct { UpdateCallbacks []func(value float64) } -func (inc *RSI) Update(kline types.KLine, priceF KLinePriceMapper) { - price := priceF(kline) +func (inc *RSI) Update(price float64) { inc.Prices.Push(price) if len(inc.Prices) < inc.Window+1 { @@ -78,13 +77,11 @@ func (inc *RSI) Length() int { var _ types.Series = &RSI{} func (inc *RSI) calculateAndUpdate(kLines []types.KLine) { - var priceF = KLineClosePriceMapper - for _, k := range kLines { if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) { continue } - inc.Update(k, priceF) + inc.Update(k.Close.Float64()) } inc.EmitUpdate(inc.Last()) diff --git a/pkg/indicator/stoch.go b/pkg/indicator/stoch.go index c739f2b46..a5581fc40 100644 --- a/pkg/indicator/stoch.go +++ b/pkg/indicator/stoch.go @@ -20,21 +20,21 @@ type STOCH struct { K types.Float64Slice D types.Float64Slice - KLineWindow types.KLineWindow + HighValues types.Float64Slice + LowValues types.Float64Slice EndTime time.Time UpdateCallbacks []func(k float64, d float64) } -func (inc *STOCH) update(kLine types.KLine) { - inc.KLineWindow.Add(kLine) - inc.KLineWindow.Truncate(inc.Window) +func (inc *STOCH) Update(high, low, cloze float64) { + inc.HighValues.Push(high) + inc.LowValues.Push(low) - lowest := inc.KLineWindow.GetLow().Float64() - highest := inc.KLineWindow.GetHigh().Float64() - clos := kLine.Close.Float64() + lowest := inc.LowValues.Tail(inc.Window).Min() + highest := inc.HighValues.Tail(inc.Window).Max() - k := 100.0 * (clos - lowest) / (highest - lowest) + k := 100.0 * (cloze - lowest) / (highest - lowest) inc.K.Push(k) d := inc.K.Tail(DPeriod).Mean() @@ -64,7 +64,7 @@ func (inc *STOCH) calculateAndUpdate(kLines []types.KLine) { if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) { continue } - inc.update(k) + inc.Update(k.High.Float64(), k.Low.Float64(), k.Close.Float64()) } inc.EmitUpdate(inc.LastK(), inc.LastD()) diff --git a/pkg/indicator/vwap.go b/pkg/indicator/vwap.go index 2ebae069a..7fcac717a 100644 --- a/pkg/indicator/vwap.go +++ b/pkg/indicator/vwap.go @@ -28,6 +28,23 @@ type VWAP struct { UpdateCallbacks []func(value float64) } +func (inc *VWAP) Update(price, volume float64) { + inc.Prices.Push(price) + inc.Volumes.Push(volume) + + if inc.Window != 0 && len(inc.Prices) > inc.Window { + popIndex := len(inc.Prices) - inc.Window - 1 + inc.WeightedSum -= inc.Prices[popIndex] * inc.Volumes[popIndex] + inc.VolumeSum -= inc.Volumes[popIndex] + } + + inc.WeightedSum += price * volume + inc.VolumeSum += volume + + vwap := inc.WeightedSum / inc.VolumeSum + inc.Values.Push(vwap) +} + func (inc *VWAP) Last() float64 { if len(inc.Values) == 0 { return 0.0 @@ -50,26 +67,6 @@ func (inc *VWAP) Length() int { var _ types.Series = &VWAP{} -func (inc *VWAP) Update(kLine types.KLine, priceF KLinePriceMapper) { - price := priceF(kLine) - volume := kLine.Volume.Float64() - - inc.Prices.Push(price) - inc.Volumes.Push(volume) - - if inc.Window != 0 && len(inc.Prices) > inc.Window { - popIndex := len(inc.Prices) - inc.Window - 1 - inc.WeightedSum -= inc.Prices[popIndex] * inc.Volumes[popIndex] - inc.VolumeSum -= inc.Volumes[popIndex] - } - - inc.WeightedSum += price * volume - inc.VolumeSum += volume - - vwap := inc.WeightedSum / inc.VolumeSum - inc.Values.Push(vwap) -} - func (inc *VWAP) calculateAndUpdate(kLines []types.KLine) { var priceF = KLineTypicalPriceMapper @@ -77,7 +74,7 @@ func (inc *VWAP) calculateAndUpdate(kLines []types.KLine) { if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) { continue } - inc.Update(k, priceF) + inc.Update(priceF(k), k.Volume.Float64()) } inc.EmitUpdate(inc.Last()) @@ -99,7 +96,7 @@ func (inc *VWAP) Bind(updater KLineWindowUpdater) { func CalculateVWAP(klines []types.KLine, priceF KLinePriceMapper, window int) float64 { vwap := VWAP{IntervalWindow: types.IntervalWindow{Window: window}} for _, k := range klines { - vwap.Update(k, priceF) + vwap.Update(priceF(k), k.Volume.Float64()) } return vwap.Last() }