1package gota 2 3// CMO - Chande Momentum Oscillator (https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/cmo) 4type CMO struct { 5 points []cmoPoint 6 sumUp float64 7 sumDown float64 8 count int 9 idx int // index of newest point 10} 11 12type cmoPoint struct { 13 price float64 14 diff float64 15} 16 17// NewCMO constructs a new CMO. 18func 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". 25func (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. 30func (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. 75func (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. 81type CMOS struct { 82 emaUp EMA 83 emaDown EMA 84 lastV float64 85} 86 87// NewCMOS constructs a new CMOS. 88func 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". 98func (cmos CMOS) WarmCount() int { 99 return cmos.emaUp.WarmCount() 100} 101 102// Warmed indicates whether the algorithm has enough data to generate accurate results. 103func (cmos CMOS) Warmed() bool { 104 return cmos.emaUp.Warmed() 105} 106 107// Last returns the last output value. 108func (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. 115func (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} 128