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  }