github.com/influxdata/influxdb/v2@v2.7.6/influxql/query/internal/gota/ema.go (about) 1 package gota 2 3 import ( 4 "fmt" 5 ) 6 7 type AlgSimple interface { 8 Add(float64) float64 9 Warmed() bool 10 WarmCount() int 11 } 12 13 type WarmupType int8 14 15 const ( 16 WarmEMA WarmupType = iota // Exponential Moving Average 17 WarmSMA // Simple Moving Average 18 ) 19 20 func ParseWarmupType(wt string) (WarmupType, error) { 21 switch wt { 22 case "exponential": 23 return WarmEMA, nil 24 case "simple": 25 return WarmSMA, nil 26 default: 27 return 0, fmt.Errorf("invalid warmup type '%s'", wt) 28 } 29 } 30 31 // EMA - Exponential Moving Average (http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_averages#exponential_moving_average_calculation) 32 type EMA struct { 33 inTimePeriod int 34 last float64 35 count int 36 alpha float64 37 warmType WarmupType 38 } 39 40 // NewEMA constructs a new EMA. 41 // 42 // When warmed with WarmSMA the first inTimePeriod samples will result in a simple average, switching to exponential moving average after warmup is complete. 43 // 44 // When warmed with WarmEMA the algorithm immediately starts using an exponential moving average for the output values. During the warmup period the alpha value is scaled to prevent unbalanced weighting on initial values. 45 func NewEMA(inTimePeriod int, warmType WarmupType) *EMA { 46 return &EMA{ 47 inTimePeriod: inTimePeriod, 48 alpha: 2 / float64(inTimePeriod+1), 49 warmType: warmType, 50 } 51 } 52 53 // WarmCount returns the number of samples that must be provided for the algorithm to be fully "warmed". 54 func (ema *EMA) WarmCount() int { 55 return ema.inTimePeriod - 1 56 } 57 58 // Warmed indicates whether the algorithm has enough data to generate accurate results. 59 func (ema *EMA) Warmed() bool { 60 return ema.count == ema.inTimePeriod 61 } 62 63 // Last returns the last output value. 64 func (ema *EMA) Last() float64 { 65 return ema.last 66 } 67 68 // Add adds a new sample value to the algorithm and returns the computed value. 69 func (ema *EMA) Add(v float64) float64 { 70 var avg float64 71 if ema.count == 0 { 72 avg = v 73 } else { 74 lastAvg := ema.Last() 75 if !ema.Warmed() { 76 if ema.warmType == WarmSMA { 77 avg = (lastAvg*float64(ema.count) + v) / float64(ema.count+1) 78 } else { // ema.warmType == WarmEMA 79 // scale the alpha so that we don't excessively weight the result towards the first value 80 alpha := 2 / float64(ema.count+2) 81 avg = (v-lastAvg)*alpha + lastAvg 82 } 83 } else { 84 avg = (v-lastAvg)*ema.alpha + lastAvg 85 } 86 } 87 88 ema.last = avg 89 if ema.count < ema.inTimePeriod { 90 // don't just keep incrementing to prevent potential overflow 91 ema.count++ 92 } 93 return avg 94 } 95 96 // DEMA - Double Exponential Moving Average (https://en.wikipedia.org/wiki/Double_exponential_moving_average) 97 type DEMA struct { 98 ema1 EMA 99 ema2 EMA 100 } 101 102 // NewDEMA constructs a new DEMA. 103 // 104 // When warmed with WarmSMA the first inTimePeriod samples will result in a simple average, switching to exponential moving average after warmup is complete. 105 // 106 // When warmed with WarmEMA the algorithm immediately starts using an exponential moving average for the output values. During the warmup period the alpha value is scaled to prevent unbalanced weighting on initial values. 107 func NewDEMA(inTimePeriod int, warmType WarmupType) *DEMA { 108 return &DEMA{ 109 ema1: *NewEMA(inTimePeriod, warmType), 110 ema2: *NewEMA(inTimePeriod, warmType), 111 } 112 } 113 114 // WarmCount returns the number of samples that must be provided for the algorithm to be fully "warmed". 115 func (dema *DEMA) WarmCount() int { 116 if dema.ema1.warmType == WarmEMA { 117 return dema.ema1.WarmCount() 118 } 119 return dema.ema1.WarmCount() + dema.ema2.WarmCount() 120 } 121 122 // Add adds a new sample value to the algorithm and returns the computed value. 123 func (dema *DEMA) Add(v float64) float64 { 124 avg1 := dema.ema1.Add(v) 125 var avg2 float64 126 if dema.ema1.Warmed() || dema.ema1.warmType == WarmEMA { 127 avg2 = dema.ema2.Add(avg1) 128 } else { 129 avg2 = avg1 130 } 131 return 2*avg1 - avg2 132 } 133 134 // Warmed indicates whether the algorithm has enough data to generate accurate results. 135 func (dema *DEMA) Warmed() bool { 136 return dema.ema2.Warmed() 137 } 138 139 // TEMA - Triple Exponential Moving Average (https://en.wikipedia.org/wiki/Triple_exponential_moving_average) 140 type TEMA struct { 141 ema1 EMA 142 ema2 EMA 143 ema3 EMA 144 } 145 146 // NewTEMA constructs a new TEMA. 147 // 148 // When warmed with WarmSMA the first inTimePeriod samples will result in a simple average, switching to exponential moving average after warmup is complete. 149 // 150 // When warmed with WarmEMA the algorithm immediately starts using an exponential moving average for the output values. During the warmup period the alpha value is scaled to prevent unbalanced weighting on initial values. 151 func NewTEMA(inTimePeriod int, warmType WarmupType) *TEMA { 152 return &TEMA{ 153 ema1: *NewEMA(inTimePeriod, warmType), 154 ema2: *NewEMA(inTimePeriod, warmType), 155 ema3: *NewEMA(inTimePeriod, warmType), 156 } 157 } 158 159 // WarmCount returns the number of samples that must be provided for the algorithm to be fully "warmed". 160 func (tema *TEMA) WarmCount() int { 161 if tema.ema1.warmType == WarmEMA { 162 return tema.ema1.WarmCount() 163 } 164 return tema.ema1.WarmCount() + tema.ema2.WarmCount() + tema.ema3.WarmCount() 165 } 166 167 // Add adds a new sample value to the algorithm and returns the computed value. 168 func (tema *TEMA) Add(v float64) float64 { 169 avg1 := tema.ema1.Add(v) 170 var avg2 float64 171 if tema.ema1.Warmed() || tema.ema1.warmType == WarmEMA { 172 avg2 = tema.ema2.Add(avg1) 173 } else { 174 avg2 = avg1 175 } 176 var avg3 float64 177 if tema.ema2.Warmed() || tema.ema2.warmType == WarmEMA { 178 avg3 = tema.ema3.Add(avg2) 179 } else { 180 avg3 = avg2 181 } 182 return 3*avg1 - 3*avg2 + avg3 183 } 184 185 // Warmed indicates whether the algorithm has enough data to generate accurate results. 186 func (tema *TEMA) Warmed() bool { 187 return tema.ema3.Warmed() 188 }