gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/expmovingavg.go (about)

     1  package renter
     2  
     3  // expMovingAvg is a function to track the exponential moving average of a
     4  // series of datapoints.
     5  type expMovingAvg struct {
     6  	decay float64
     7  
     8  	// weightedSpan tracks the total decayed amount of data we've received, and
     9  	// the weighted sum tracks the total decayed value of all the data  we've
    10  	// received.
    11  	weightedSpan float64
    12  	weightedSum  float64
    13  }
    14  
    15  // newExpMovingAvg returns an expMovingAvg that's ready to receive data and
    16  // compute averages.
    17  func newExpMovingAvg(decay float64) *expMovingAvg {
    18  	return &expMovingAvg{
    19  		decay: decay,
    20  	}
    21  }
    22  
    23  // addDataPoint adds a single data point to the average, decaying the existing
    24  // data by the chosen decay of the average.
    25  func (ema *expMovingAvg) addDataPoint(data float64) {
    26  	ema.weightedSpan *= ema.decay
    27  	ema.weightedSpan += 1
    28  	ema.weightedSum *= ema.decay
    29  	ema.weightedSum += data
    30  }
    31  
    32  // average returns the average of all the data that's been added to the
    33  // expMovingAvg.
    34  func (ema *expMovingAvg) average() float64 {
    35  	return ema.weightedSum / ema.weightedSpan
    36  }
    37  
    38  // expMovingAvgHotStart is a helper to compute the next exponential moving
    39  // average given the last value and a new point of measurement. The function is
    40  // fully stateless, but also has a 'hot start', which means that the first data
    41  // point sets the average at full strength. If the first datapoint is an outlier
    42  // datapoint, it can significantly disrupt the average until many more
    43  // datapoints have come in.
    44  //
    45  // https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
    46  func expMovingAvgHotStart(oldEMA, newValue, decay float64) float64 {
    47  	if decay < 0 || decay > 1 {
    48  		panic("decay has to be a value in range 0 <= x <= 1")
    49  	}
    50  	if oldEMA == 0 {
    51  		// This is the hot start. If there is no EMA yet, we take the first
    52  		// datapoint as the true average. If you don't do this, when you start
    53  		// off at first you will have an average that is completely incorrect,
    54  		// because your first datapoint might be '80' but at a decay of 0.9 your
    55  		// average after that datapoint will be 8.
    56  		return newValue
    57  	}
    58  	return newValue*(1-decay) + decay*oldEMA
    59  }