github.com/influxdata/influxdb/v2@v2.7.6/influxql/query/internal/gota/cmo.go (about)

     1  package gota
     2  
     3  // CMO - Chande Momentum Oscillator (https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/cmo)
     4  type CMO struct {
     5  	points  []cmoPoint
     6  	sumUp   float64
     7  	sumDown float64
     8  	count   int
     9  	idx     int // index of newest point
    10  }
    11  
    12  type cmoPoint struct {
    13  	price float64
    14  	diff  float64
    15  }
    16  
    17  // NewCMO constructs a new CMO.
    18  func NewCMO(inTimePeriod int) *CMO {
    19  	return &CMO{
    20  		points: make([]cmoPoint, inTimePeriod-1),
    21  	}
    22  }
    23  
    24  // WarmCount returns the number of samples that must be provided for the algorithm to be fully "warmed".
    25  func (cmo *CMO) WarmCount() int {
    26  	return len(cmo.points)
    27  }
    28  
    29  // Add adds a new sample value to the algorithm and returns the computed value.
    30  func (cmo *CMO) Add(v float64) float64 {
    31  	idxOldest := cmo.idx + 1
    32  	if idxOldest == len(cmo.points) {
    33  		idxOldest = 0
    34  	}
    35  
    36  	var diff float64
    37  	if cmo.count != 0 {
    38  		prev := cmo.points[cmo.idx]
    39  		diff = v - prev.price
    40  		if diff > 0 {
    41  			cmo.sumUp += diff
    42  		} else if diff < 0 {
    43  			cmo.sumDown -= diff
    44  		}
    45  	}
    46  
    47  	var outV float64
    48  	if cmo.sumUp != 0 || cmo.sumDown != 0 {
    49  		outV = 100.0 * ((cmo.sumUp - cmo.sumDown) / (cmo.sumUp + cmo.sumDown))
    50  	}
    51  
    52  	oldest := cmo.points[idxOldest]
    53  	//NOTE: because we're just adding and subtracting the difference, and not recalculating sumUp/sumDown using cmo.points[].price, it's possible for imprecision to creep in over time. Not sure how significant this is going to be, but if we want to fix it, we could recalculate it from scratch every N points.
    54  	if oldest.diff > 0 {
    55  		cmo.sumUp -= oldest.diff
    56  	} else if oldest.diff < 0 {
    57  		cmo.sumDown += oldest.diff
    58  	}
    59  
    60  	p := cmoPoint{
    61  		price: v,
    62  		diff:  diff,
    63  	}
    64  	cmo.points[idxOldest] = p
    65  	cmo.idx = idxOldest
    66  
    67  	if !cmo.Warmed() {
    68  		cmo.count++
    69  	}
    70  
    71  	return outV
    72  }
    73  
    74  // Warmed indicates whether the algorithm has enough data to generate accurate results.
    75  func (cmo *CMO) Warmed() bool {
    76  	return cmo.count == len(cmo.points)+2
    77  }
    78  
    79  // CMOS is a smoothed version of the Chande Momentum Oscillator.
    80  // This is the version of CMO utilized by ta-lib.
    81  type CMOS struct {
    82  	emaUp   EMA
    83  	emaDown EMA
    84  	lastV   float64
    85  }
    86  
    87  // NewCMOS constructs a new CMOS.
    88  func NewCMOS(inTimePeriod int, warmType WarmupType) *CMOS {
    89  	ema := NewEMA(inTimePeriod+1, warmType)
    90  	ema.alpha = float64(1) / float64(inTimePeriod)
    91  	return &CMOS{
    92  		emaUp:   *ema,
    93  		emaDown: *ema,
    94  	}
    95  }
    96  
    97  // WarmCount returns the number of samples that must be provided for the algorithm to be fully "warmed".
    98  func (cmos CMOS) WarmCount() int {
    99  	return cmos.emaUp.WarmCount()
   100  }
   101  
   102  // Warmed indicates whether the algorithm has enough data to generate accurate results.
   103  func (cmos CMOS) Warmed() bool {
   104  	return cmos.emaUp.Warmed()
   105  }
   106  
   107  // Last returns the last output value.
   108  func (cmos CMOS) Last() float64 {
   109  	up := cmos.emaUp.Last()
   110  	down := cmos.emaDown.Last()
   111  	return 100.0 * ((up - down) / (up + down))
   112  }
   113  
   114  // Add adds a new sample value to the algorithm and returns the computed value.
   115  func (cmos *CMOS) Add(v float64) float64 {
   116  	var up float64
   117  	var down float64
   118  	if v > cmos.lastV {
   119  		up = v - cmos.lastV
   120  	} else if v < cmos.lastV {
   121  		down = cmos.lastV - v
   122  	}
   123  	cmos.emaUp.Add(up)
   124  	cmos.emaDown.Add(down)
   125  	cmos.lastV = v
   126  	return cmos.Last()
   127  }