Files
core/math/average/sma.go
2024-10-23 11:08:13 +02:00

119 lines
2.0 KiB
Go

package average
import (
"container/ring"
"errors"
gotime "time"
"github.com/datarhei/core/v16/time"
)
type SMA struct {
ts time.Source
window int64
granularity int64
size int
last int64
samples *ring.Ring
}
var ErrWindow = errors.New("window size must be positive")
var ErrGranularity = errors.New("granularity must be positive")
var ErrMultiplier = errors.New("window size has to be a multiplier of the granularity size")
func NewSMA(window, granularity gotime.Duration) (*SMA, error) {
if window <= 0 {
return nil, ErrWindow
}
if granularity <= 0 {
return nil, ErrGranularity
}
if window <= granularity || window%granularity != 0 {
return nil, ErrMultiplier
}
s := &SMA{
ts: &time.StdSource{},
window: window.Nanoseconds(),
granularity: granularity.Nanoseconds(),
}
s.init()
return s, nil
}
func (s *SMA) init() {
s.size = int(s.window / s.granularity)
s.samples = ring.New(s.size)
s.Reset()
now := s.ts.Now().UnixNano()
s.last = now - now%s.granularity
}
func (s *SMA) Add(v float64) {
now := s.ts.Now().UnixNano()
now -= now % s.granularity
n := (now - s.last) / s.granularity
if n >= int64(s.samples.Len()) {
// zero everything
s.Reset()
} else {
for i := n; i > 0; i-- {
s.samples = s.samples.Next()
s.samples.Value = float64(0)
}
}
s.samples.Value = s.samples.Value.(float64) + v
s.last = now
}
func (s *SMA) AddAndAverage(v float64) float64 {
s.Add(v)
total := float64(0)
s.samples.Do(func(v any) {
total += v.(float64)
})
return total / float64(s.samples.Len())
}
func (s *SMA) Average() float64 {
total, samplecount := s.Total()
return total / float64(samplecount)
}
func (s *SMA) Reset() {
n := s.samples.Len()
// Initialize the ring buffer with 0 values.
for i := 0; i < n; i++ {
s.samples.Value = float64(0)
s.samples = s.samples.Next()
}
}
func (s *SMA) Total() (float64, int) {
// Propagate the ringbuffer
s.Add(0)
total := float64(0)
s.samples.Do(func(v any) {
total += v.(float64)
})
return total, s.samples.Len()
}