├── go.mod ├── .github └── FUNDING.yml ├── ema_test.go ├── ema_float64.go ├── sma_test.go ├── sma_float64.go ├── LICENSE ├── ema_big.go ├── sma_big.go ├── macd_big.go ├── macd_float64.go ├── examples ├── sma │ └── main.go ├── ema │ └── main.go ├── default_signal │ └── main.go ├── macd │ └── main.go └── custom_signal │ └── main.go ├── macd_test.go ├── README.md ├── macd_signal_big.go ├── macd_signal_float64.go ├── macd_signal_test.go └── data_test.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/MicahParks/go-ma 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [MicahParks] 4 | -------------------------------------------------------------------------------- /ema_test.go: -------------------------------------------------------------------------------- 1 | package ma_test 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | "github.com/MicahParks/go-ma" 8 | ) 9 | 10 | var ( 11 | bigTestSmoothing = big.NewFloat(ma.DefaultEMASmoothing) 12 | ) 13 | 14 | func BenchmarkEMABig_Calculate(b *testing.B) { 15 | _, sma := ma.NewBigSMA(bigPrices[:testPeriod]) 16 | 17 | ema := ma.NewBigEMA(testPeriod, sma, bigTestSmoothing) 18 | 19 | for _, p := range bigPrices[testPeriod:] { 20 | ema.Calculate(p) 21 | } 22 | } 23 | 24 | func BenchmarkEMAFloat_Calculate(b *testing.B) { 25 | _, sma := ma.NewSMA(prices[:testPeriod]) 26 | 27 | ema := ma.NewEMA(testPeriod, sma, ma.DefaultEMASmoothing) 28 | 29 | for _, p := range prices[testPeriod:] { 30 | ema.Calculate(p) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ema_float64.go: -------------------------------------------------------------------------------- 1 | package ma 2 | 3 | const ( 4 | 5 | // DefaultEMASmoothing is a common smoothing constant for the EMA algorithm. 6 | DefaultEMASmoothing = 2 7 | ) 8 | 9 | // EMA represents the state of an Exponential Moving Average (EMA). 10 | type EMA struct { 11 | constant float64 12 | prev float64 13 | oneMinusConstant float64 14 | } 15 | 16 | // NewEMA creates a new EMA data structure. 17 | func NewEMA(periods uint, sma, smoothing float64) *EMA { 18 | if smoothing == 0 { 19 | smoothing = DefaultEMASmoothing 20 | } 21 | 22 | ema := &EMA{ 23 | constant: smoothing / (1 + float64(periods)), 24 | prev: sma, 25 | } 26 | ema.oneMinusConstant = 1 - ema.constant 27 | 28 | return ema 29 | } 30 | 31 | // Calculate produces the next EMA result given the next input. 32 | func (ema *EMA) Calculate(next float64) (result float64) { 33 | ema.prev = next*ema.constant + ema.prev*ema.oneMinusConstant 34 | 35 | return ema.prev 36 | } 37 | -------------------------------------------------------------------------------- /sma_test.go: -------------------------------------------------------------------------------- 1 | package ma_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/MicahParks/go-ma" 7 | ) 8 | 9 | const ( 10 | testPeriod = 10 11 | ) 12 | 13 | func BenchmarkSMABig_Calculate(b *testing.B) { 14 | sma, _ := ma.NewBigSMA(bigPrices[:testPeriod]) 15 | 16 | for _, p := range bigPrices[testPeriod:] { 17 | sma.Calculate(p) 18 | } 19 | } 20 | 21 | func BenchmarkSMAFloat_Calculate(b *testing.B) { 22 | sma, _ := ma.NewSMA(prices[:testPeriod]) 23 | 24 | for i, p := range prices[testPeriod:] { 25 | if smaResults[i] != sma.Calculate(p) { 26 | b.FailNow() 27 | } 28 | } 29 | } 30 | 31 | func TestSMABig_Calculate(t *testing.T) { 32 | sma, _ := ma.NewBigSMA(bigPrices[:testPeriod]) 33 | 34 | for _, p := range bigPrices[testPeriod:] { 35 | sma.Calculate(p) 36 | } 37 | } 38 | 39 | func TestSMA_Calculate(t *testing.T) { 40 | sma, _ := ma.NewSMA(prices[:testPeriod]) 41 | 42 | for i, p := range prices[testPeriod:] { 43 | if smaResults[i] != sma.Calculate(p) { 44 | t.FailNow() 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /sma_float64.go: -------------------------------------------------------------------------------- 1 | package ma 2 | 3 | // SMA represents the state of a Simple Moving Average (SMA) algorithm. 4 | type SMA struct { 5 | cache []float64 6 | cacheLen uint 7 | index uint 8 | periods float64 9 | } 10 | 11 | // NewSMA creates a new SMA data structure and the initial result. The period used will be the length of the 12 | // initial input slice. 13 | func NewSMA(initial []float64) (sma *SMA, result float64) { 14 | 15 | sma = &SMA{ 16 | cacheLen: uint(len(initial)), 17 | } 18 | sma.cache = make([]float64, sma.cacheLen) 19 | sma.periods = float64(sma.cacheLen) 20 | 21 | for i, p := range initial { 22 | if i != 0 { 23 | sma.cache[i] = p 24 | } 25 | result += p 26 | } 27 | result /= sma.periods 28 | 29 | return sma, result 30 | } 31 | 32 | // Calculate produces the next SMA result given the next input. 33 | func (sma *SMA) Calculate(next float64) (result float64) { 34 | sma.cache[sma.index] = next 35 | sma.index++ 36 | if sma.index == sma.cacheLen { 37 | sma.index = 0 38 | } 39 | 40 | for _, p := range sma.cache { 41 | result += p 42 | } 43 | result /= sma.periods 44 | 45 | return result 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Micah Parks 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ema_big.go: -------------------------------------------------------------------------------- 1 | package ma 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | var ( 8 | bigOne = big.NewFloat(1) 9 | ) 10 | 11 | // BigEMA represents the state of an Exponential Moving Average (EMA) algorithm. 12 | type BigEMA struct { 13 | constant *big.Float 14 | prev *big.Float 15 | oneMinusConstant *big.Float 16 | } 17 | 18 | // NewBigEMA creates a new EMA data structure. 19 | func NewBigEMA(periods uint, sma, smoothing *big.Float) *BigEMA { 20 | if smoothing == nil || smoothing.Cmp(big.NewFloat(0)) == 0 { 21 | smoothing = big.NewFloat(DefaultEMASmoothing) 22 | } 23 | 24 | ema := &BigEMA{ 25 | constant: new(big.Float).Quo(smoothing, new(big.Float).Add(bigOne, big.NewFloat(float64(periods)))), 26 | prev: sma, 27 | } 28 | ema.oneMinusConstant = new(big.Float).Sub(bigOne, ema.constant) 29 | 30 | return ema 31 | } 32 | 33 | // Calculate produces the next EMA result given the next input. 34 | func (ema *BigEMA) Calculate(next *big.Float) (result *big.Float) { 35 | ema.prev = new(big.Float).Add(new(big.Float).Mul(next, ema.constant), new(big.Float).Mul(ema.prev, ema.oneMinusConstant)) 36 | 37 | return new(big.Float).Copy(ema.prev) 38 | } 39 | -------------------------------------------------------------------------------- /sma_big.go: -------------------------------------------------------------------------------- 1 | package ma 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | // SMABig represents the state of a Simple Moving Average (SMA) algorithm. 8 | type SMABig struct { 9 | cache []*big.Float 10 | cacheLen uint 11 | index uint 12 | periods *big.Float 13 | } 14 | 15 | // NewBigSMA creates a new SMA data structure and the initial result. The period used will be the length of the initial 16 | // input slice. 17 | func NewBigSMA(initial []*big.Float) (sma *SMABig, result *big.Float) { 18 | sma = &SMABig{ 19 | cacheLen: uint(len(initial)), 20 | } 21 | sma.cache = make([]*big.Float, sma.cacheLen) 22 | sma.periods = big.NewFloat(float64(sma.cacheLen)) 23 | 24 | result = big.NewFloat(0) 25 | for i, p := range initial { 26 | if i != 0 { 27 | sma.cache[i] = p 28 | } 29 | result = result.Add(result, p) 30 | } 31 | result = result.Quo(result, sma.periods) 32 | 33 | return sma, result 34 | } 35 | 36 | // Calculate produces the next SMA result given the next input. 37 | func (sma *SMABig) Calculate(next *big.Float) (result *big.Float) { 38 | sma.cache[sma.index] = next 39 | sma.index++ 40 | if sma.index == sma.cacheLen { 41 | sma.index = 0 42 | } 43 | 44 | result = big.NewFloat(0) 45 | for _, p := range sma.cache { 46 | result = result.Add(result, p) 47 | } 48 | result = result.Quo(result, sma.periods) 49 | 50 | return result 51 | } 52 | -------------------------------------------------------------------------------- /macd_big.go: -------------------------------------------------------------------------------- 1 | package ma 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | // BigMACD represents the state of a Moving Average Convergence Divergence (MACD) algorithm. 8 | type BigMACD struct { 9 | Long *BigEMA 10 | Short *BigEMA 11 | } 12 | 13 | // BigMACDResults holds the results fo an MACD calculation. 14 | type BigMACDResults struct { 15 | Long *big.Float 16 | Result *big.Float 17 | Short *big.Float 18 | } 19 | 20 | // NewBigMACD creates a new MACD data structure and returns the initial result. 21 | func NewBigMACD(long, short *BigEMA) BigMACD { 22 | return BigMACD{ 23 | Long: long, 24 | Short: short, 25 | } 26 | } 27 | 28 | // Calculate produces the next MACD result given the next input. 29 | func (macd BigMACD) Calculate(next *big.Float) BigMACDResults { 30 | short := macd.Short.Calculate(next) 31 | long := macd.Long.Calculate(next) 32 | return BigMACDResults{ 33 | Long: long, 34 | Result: new(big.Float).Sub(short, long), 35 | Short: short, 36 | } 37 | } 38 | 39 | // SignalEMA creates a signal EMA for the current MACD. 40 | // 41 | // The first MACD result *must* be saved in order to create the signal EMA. Then, the next period samples required for 42 | // the creation of the signal EMA must be given. The period length of the EMA is `1 + len(next)`. 43 | func (macd BigMACD) SignalEMA(firstMACDResult *big.Float, next []*big.Float, smoothing *big.Float) (signalEMA *BigEMA, signalResult *big.Float, macdResults []BigMACDResults) { 44 | macdBigs := make([]*big.Float, len(next)) 45 | macdResults = make([]BigMACDResults, len(next)) 46 | 47 | for i, p := range next { 48 | result := macd.Calculate(p) 49 | macdBigs[i] = result.Result 50 | macdResults[i] = result 51 | } 52 | 53 | _, sma := NewBigSMA(append([]*big.Float{firstMACDResult}, macdBigs...)) 54 | 55 | return NewBigEMA(uint(len(next)+1), sma, smoothing), sma, macdResults 56 | } 57 | -------------------------------------------------------------------------------- /macd_float64.go: -------------------------------------------------------------------------------- 1 | package ma 2 | 3 | const ( 4 | 5 | // DefaultLongMACDPeriod is a common MACD period for the long input EMA algorithm. 6 | DefaultLongMACDPeriod = 26 7 | 8 | // DefaultShortMACDPeriod is a common MACD period for the short input EMA algorithm. 9 | DefaultShortMACDPeriod = 12 10 | 11 | // DefaultSignalEMAPeriod is a common MACD period for the signal EMA that will crossover the MACD line. 12 | DefaultSignalEMAPeriod = 9 13 | ) 14 | 15 | // MACD represents the state of a Moving Average Convergence Divergence (MACD) algorithm. 16 | type MACD struct { 17 | Long *EMA 18 | Short *EMA 19 | } 20 | 21 | // MACDResults holds the results fo an MACD calculation. 22 | type MACDResults struct { 23 | Long float64 24 | Result float64 25 | Short float64 26 | } 27 | 28 | // NewMACD creates a new MACD data structure and returns the initial result. 29 | func NewMACD(long, short *EMA) MACD { 30 | return MACD{ 31 | Long: long, 32 | Short: short, 33 | } 34 | } 35 | 36 | // Calculate produces the next MACD result given the next input. 37 | func (macd MACD) Calculate(next float64) MACDResults { 38 | short := macd.Short.Calculate(next) 39 | long := macd.Long.Calculate(next) 40 | return MACDResults{ 41 | Long: long, 42 | Result: short - long, 43 | Short: short, 44 | } 45 | } 46 | 47 | // SignalEMA creates a signal EMA for the current MACD. 48 | // 49 | // The first MACD result *must* be saved in order to create the signal EMA. Then, the next period samples required for 50 | // the creation of the signal EMA must be given. The period length of the EMA is `1 + len(next)`. 51 | func (macd MACD) SignalEMA(firstMACDResult float64, next []float64, smoothing float64) (signalEMA *EMA, signalResult float64, macdResults []MACDResults) { 52 | macdFloats := make([]float64, len(next)) 53 | macdResults = make([]MACDResults, len(next)) 54 | 55 | for i, p := range next { 56 | result := macd.Calculate(p) 57 | macdFloats[i] = result.Result 58 | macdResults[i] = result 59 | } 60 | 61 | _, sma := NewSMA(append([]float64{firstMACDResult}, macdFloats...)) 62 | 63 | return NewEMA(uint(len(next)+1), sma, smoothing), sma, macdResults 64 | } 65 | -------------------------------------------------------------------------------- /examples/sma/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/MicahParks/go-ma" 8 | ) 9 | 10 | func main() { 11 | // Create a logger. 12 | logger := log.New(os.Stdout, "", 0) 13 | 14 | // Gather some data. 15 | // 16 | // For production systems, it'd be best to gather data asynchronously. 17 | prices := testData() 18 | 19 | // Determine the number of periods for the calculating the SMA. 20 | const periods = 10 21 | 22 | // Create the SMA data structure and get the first result. 23 | // 24 | // The length of the initial slice is the number of periods used for calculating the SMA. 25 | sma, result := ma.NewSMA(prices[:periods]) 26 | logger.Printf("Period index: %d\nPrice: %.2f\nSMA: %.2f", periods-1, prices[periods-1], result) 27 | 28 | // Use the remaining data to generate the SMA for each period. 29 | for i := periods; i < len(prices); i++ { 30 | result = sma.Calculate(prices[i]) 31 | logger.Printf("Period index: %d\nPrice: %.2f\nSMA: %.2f", i, prices[i], result) 32 | } 33 | } 34 | 35 | func testData() (prices []float64) { 36 | return []float64{ 37 | 459.99, 38 | 448.85, 39 | 446.06, 40 | 450.81, 41 | 442.8, 42 | 448.97, 43 | 444.57, 44 | 441.4, 45 | 430.47, 46 | 420.05, 47 | 431.14, 48 | 425.66, 49 | 430.58, 50 | 431.72, 51 | 437.87, 52 | 428.43, 53 | 428.35, 54 | 432.5, 55 | 443.66, 56 | 455.72, 57 | 454.49, 58 | 452.08, 59 | 452.73, 60 | 461.91, 61 | 463.58, 62 | 461.14, 63 | 452.08, 64 | 442.66, 65 | 428.91, 66 | 429.79, 67 | 431.99, 68 | 427.72, 69 | 423.2, 70 | 426.21, 71 | 426.98, 72 | 435.69, 73 | 434.33, 74 | 429.8, 75 | 419.85, 76 | 426.24, 77 | 402.8, 78 | 392.05, 79 | 390.53, 80 | 398.67, 81 | 406.13, 82 | 405.46, 83 | 408.38, 84 | 417.2, 85 | 430.12, 86 | 442.78, 87 | 439.29, 88 | 445.52, 89 | 449.98, 90 | 460.71, 91 | 458.66, 92 | 463.84, 93 | 456.77, 94 | 452.97, 95 | 454.74, 96 | 443.86, 97 | 428.85, 98 | 434.58, 99 | 433.26, 100 | 442.93, 101 | 439.66, 102 | 441.35, 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /examples/ema/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/MicahParks/go-ma" 8 | ) 9 | 10 | func main() { 11 | // Create a logger. 12 | logger := log.New(os.Stdout, "", 0) 13 | 14 | // Gather some data. 15 | // 16 | // For production systems, it'd be best to gather data asynchronously. 17 | prices := testData() 18 | 19 | // Determine the number of periods for the calculating the EMA. 20 | const periods = 10 21 | 22 | // Get the first result of the SMA. The first result of the SMA is the same as the first result of the EMA. 23 | // 24 | // The length of the initial slice is the number of periods used for calculating the SMA. 25 | _, sma := ma.NewSMA(prices[:periods]) 26 | logger.Printf("Period index: %d\nPrice: %.2f\nEMA: %.2f", periods-1, prices[periods-1], sma) 27 | 28 | // Create the EMA data structure. 29 | ema := ma.NewEMA(periods, sma, 0) 30 | 31 | // Use the remaining data to generate the SMA for each period. 32 | var result float64 33 | for i := periods; i < len(prices); i++ { 34 | result = ema.Calculate(prices[i]) 35 | logger.Printf("Period index: %d\nPrice: %.2f\nEMA: %.2f", i, prices[i], result) 36 | } 37 | } 38 | 39 | func testData() (prices []float64) { 40 | return []float64{ 41 | 459.99, 42 | 448.85, 43 | 446.06, 44 | 450.81, 45 | 442.8, 46 | 448.97, 47 | 444.57, 48 | 441.4, 49 | 430.47, 50 | 420.05, 51 | 431.14, 52 | 425.66, 53 | 430.58, 54 | 431.72, 55 | 437.87, 56 | 428.43, 57 | 428.35, 58 | 432.5, 59 | 443.66, 60 | 455.72, 61 | 454.49, 62 | 452.08, 63 | 452.73, 64 | 461.91, 65 | 463.58, 66 | 461.14, 67 | 452.08, 68 | 442.66, 69 | 428.91, 70 | 429.79, 71 | 431.99, 72 | 427.72, 73 | 423.2, 74 | 426.21, 75 | 426.98, 76 | 435.69, 77 | 434.33, 78 | 429.8, 79 | 419.85, 80 | 426.24, 81 | 402.8, 82 | 392.05, 83 | 390.53, 84 | 398.67, 85 | 406.13, 86 | 405.46, 87 | 408.38, 88 | 417.2, 89 | 430.12, 90 | 442.78, 91 | 439.29, 92 | 445.52, 93 | 449.98, 94 | 460.71, 95 | 458.66, 96 | 463.84, 97 | 456.77, 98 | 452.97, 99 | 454.74, 100 | 443.86, 101 | 428.85, 102 | 434.58, 103 | 433.26, 104 | 442.93, 105 | 439.66, 106 | 441.35, 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /examples/default_signal/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/MicahParks/go-ma" 8 | ) 9 | 10 | func main() { 11 | // Create a logger. 12 | logger := log.New(os.Stdout, "", 0) 13 | 14 | // Gather some data. 15 | // 16 | // For production systems, it'd be best to gather data asynchronously. 17 | prices := testData() 18 | 19 | // Create the MACD and signal EMA pair. 20 | signal := ma.DefaultMACDSignal(prices[:ma.RequiredSamplesForDefaultMACDSignal]) 21 | 22 | // Iterate through the rest of the data and print the results. 23 | var results ma.MACDSignalResults 24 | for i, p := range prices[ma.RequiredSamplesForDefaultMACDSignal:] { 25 | results = signal.Calculate(p) 26 | 27 | // Interpret the buy signal. 28 | var buySignal string 29 | if results.BuySignal != nil { 30 | if *results.BuySignal { 31 | buySignal = "Buy, buy, buy!" 32 | } else { 33 | buySignal = "Sell, sell, sell!" 34 | } 35 | } else { 36 | buySignal = "Do nothing." 37 | } 38 | 39 | logger.Printf("Price index: %d\n MACD: %.5f\n Signal EMA: %.5f\n Buy signal: %s", i+ma.RequiredSamplesForDefaultMACDSignal, results.MACD.Result, results.SignalEMA, buySignal) 40 | } 41 | } 42 | 43 | func testData() (prices []float64) { 44 | return []float64{ 45 | 459.99, 46 | 448.85, 47 | 446.06, 48 | 450.81, 49 | 442.8, 50 | 448.97, 51 | 444.57, 52 | 441.4, 53 | 430.47, 54 | 420.05, 55 | 431.14, 56 | 425.66, 57 | 430.58, 58 | 431.72, 59 | 437.87, 60 | 428.43, 61 | 428.35, 62 | 432.5, 63 | 443.66, 64 | 455.72, 65 | 454.49, 66 | 452.08, 67 | 452.73, 68 | 461.91, 69 | 463.58, 70 | 461.14, 71 | 452.08, 72 | 442.66, 73 | 428.91, 74 | 429.79, 75 | 431.99, 76 | 427.72, 77 | 423.2, 78 | 426.21, 79 | 426.98, 80 | 435.69, 81 | 434.33, 82 | 429.8, 83 | 419.85, 84 | 426.24, 85 | 402.8, 86 | 392.05, 87 | 390.53, 88 | 398.67, 89 | 406.13, 90 | 405.46, 91 | 408.38, 92 | 417.2, 93 | 430.12, 94 | 442.78, 95 | 439.29, 96 | 445.52, 97 | 449.98, 98 | 460.71, 99 | 458.66, 100 | 463.84, 101 | 456.77, 102 | 452.97, 103 | 454.74, 104 | 443.86, 105 | 428.85, 106 | 434.58, 107 | 433.26, 108 | 442.93, 109 | 439.66, 110 | 441.35, 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /macd_test.go: -------------------------------------------------------------------------------- 1 | package ma_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/MicahParks/go-ma" 7 | ) 8 | 9 | func BenchmarkMACDBig_Calculate(b *testing.B) { 10 | _, shortSMA := ma.NewBigSMA(bigPrices[:ma.DefaultShortMACDPeriod]) 11 | shortEMA := ma.NewBigEMA(ma.DefaultShortMACDPeriod, shortSMA, nil) 12 | for _, p := range bigPrices[ma.DefaultShortMACDPeriod:ma.DefaultLongMACDPeriod] { 13 | shortEMA.Calculate(p) 14 | } 15 | 16 | _, longSMA := ma.NewBigSMA(bigPrices[:ma.DefaultLongMACDPeriod]) 17 | longEMA := ma.NewBigEMA(ma.DefaultLongMACDPeriod, longSMA, nil) 18 | 19 | macd := ma.NewBigMACD(longEMA, shortEMA) 20 | 21 | for _, p := range bigPrices[ma.DefaultLongMACDPeriod:] { 22 | macd.Calculate(p) 23 | } 24 | } 25 | 26 | func BenchmarkMACDFloat_Calculate(b *testing.B) { 27 | _, shortSMA := ma.NewSMA(prices[:ma.DefaultShortMACDPeriod]) 28 | shortEMA := ma.NewEMA(ma.DefaultShortMACDPeriod, shortSMA, 0) 29 | for _, p := range prices[ma.DefaultShortMACDPeriod:ma.DefaultLongMACDPeriod] { 30 | shortEMA.Calculate(p) 31 | } 32 | 33 | _, longSMA := ma.NewSMA(prices[:ma.DefaultLongMACDPeriod]) 34 | longEMA := ma.NewEMA(ma.DefaultLongMACDPeriod, longSMA, 0) 35 | 36 | macd := ma.NewMACD(longEMA, shortEMA) 37 | 38 | for _, p := range prices[ma.DefaultLongMACDPeriod:] { 39 | macd.Calculate(p) 40 | } 41 | } 42 | 43 | func TestMACDBig_Calculate(t *testing.T) { 44 | _, shortSMA := ma.NewBigSMA(bigPrices[:ma.DefaultShortMACDPeriod]) 45 | shortEMA := ma.NewBigEMA(ma.DefaultShortMACDPeriod, shortSMA, nil) 46 | for _, p := range bigPrices[ma.DefaultShortMACDPeriod:ma.DefaultLongMACDPeriod] { 47 | shortEMA.Calculate(p) 48 | } 49 | 50 | _, longSMA := ma.NewBigSMA(bigPrices[:ma.DefaultLongMACDPeriod]) 51 | longEMA := ma.NewBigEMA(ma.DefaultLongMACDPeriod, longSMA, nil) 52 | 53 | macd := ma.NewBigMACD(longEMA, shortEMA) 54 | 55 | var res float64 56 | for i, p := range bigPrices[ma.DefaultLongMACDPeriod:] { 57 | res, _ = macd.Calculate(p).Result.Float64() 58 | if res != results[i] { 59 | t.FailNow() 60 | } 61 | } 62 | } 63 | 64 | func TestMACD_Calculate(t *testing.T) { 65 | _, shortSMA := ma.NewSMA(prices[:ma.DefaultShortMACDPeriod]) 66 | shortEMA := ma.NewEMA(ma.DefaultShortMACDPeriod, shortSMA, 0) 67 | for _, p := range prices[ma.DefaultShortMACDPeriod:ma.DefaultLongMACDPeriod] { 68 | shortEMA.Calculate(p) 69 | } 70 | 71 | _, longSMA := ma.NewSMA(prices[:ma.DefaultLongMACDPeriod]) 72 | longEMA := ma.NewEMA(ma.DefaultLongMACDPeriod, longSMA, 0) 73 | 74 | macd := ma.NewMACD(longEMA, shortEMA) 75 | 76 | for i, p := range prices[ma.DefaultLongMACDPeriod:] { 77 | if macd.Calculate(p).Result != results[i] { 78 | t.FailNow() 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /examples/macd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/MicahParks/go-ma" 8 | ) 9 | 10 | func main() { 11 | // Create a logger. 12 | logger := log.New(os.Stdout, "", 0) 13 | 14 | // Gather some data. 15 | // 16 | // For production systems, it'd be best to gather data asynchronously. 17 | prices := testData() 18 | 19 | // Get the first result of the short SMA. The first result of the SMA is the same as the first result of the EMA. 20 | // 21 | // The length of the initial slice is the number of periods used for calculating the SMA. 22 | _, shortSMA := ma.NewSMA(prices[:ma.DefaultShortMACDPeriod]) 23 | 24 | // Create the short EMA data structure. 25 | shortEMA := ma.NewEMA(ma.DefaultShortMACDPeriod, shortSMA, 0) 26 | 27 | // Save the last value of the short EMA for the first MACD calculation. 28 | // 29 | // This is optional and should only be done if the first result is relevant. 30 | var latestShortEMA float64 31 | 32 | // Catch up the short EMA to the period where the long EMA will be at. 33 | for _, p := range prices[ma.DefaultShortMACDPeriod:ma.DefaultLongMACDPeriod] { 34 | latestShortEMA = shortEMA.Calculate(p) 35 | } 36 | 37 | // Create the long EMA. 38 | _, longSMA := ma.NewSMA(prices[:ma.DefaultLongMACDPeriod]) 39 | longEMA := ma.NewEMA(ma.DefaultLongMACDPeriod, longSMA, 0) 40 | 41 | // The first result returned from calculating the MACD will be the second possible MACD result. To get the first 42 | // possible MACD result, use the most recent short and long EMA values. For the long EMA value, this will be 43 | // equivalent to the most recent long SMA value. 44 | // 45 | // This is optional and should only be done if the first result is relevant. 46 | firstResult := latestShortEMA - longSMA 47 | logger.Printf("Period index: %d\n MACD: %.5f\n Short: %.5f\n Long: %.5f", ma.DefaultLongMACDPeriod-1, firstResult, latestShortEMA, longSMA) 48 | 49 | // Create the MACD from the short and long EMAs. 50 | macd := ma.NewMACD(longEMA, shortEMA) 51 | 52 | // Use the remaining data to generate the MACD results for each period. 53 | var results ma.MACDResults 54 | for i := ma.DefaultLongMACDPeriod; i < len(prices); i++ { 55 | results = macd.Calculate(prices[i]) 56 | logger.Printf("Period index: %d\n MACD: %.5f\n Short: %.5f\n Long: %.5f", i, results.Result, results.Short, results.Long) 57 | } 58 | } 59 | 60 | func testData() (prices []float64) { 61 | return []float64{ 62 | 459.99, 63 | 448.85, 64 | 446.06, 65 | 450.81, 66 | 442.8, 67 | 448.97, 68 | 444.57, 69 | 441.4, 70 | 430.47, 71 | 420.05, 72 | 431.14, 73 | 425.66, 74 | 430.58, 75 | 431.72, 76 | 437.87, 77 | 428.43, 78 | 428.35, 79 | 432.5, 80 | 443.66, 81 | 455.72, 82 | 454.49, 83 | 452.08, 84 | 452.73, 85 | 461.91, 86 | 463.58, 87 | 461.14, 88 | 452.08, 89 | 442.66, 90 | 428.91, 91 | 429.79, 92 | 431.99, 93 | 427.72, 94 | 423.2, 95 | 426.21, 96 | 426.98, 97 | 435.69, 98 | 434.33, 99 | 429.8, 100 | 419.85, 101 | 426.24, 102 | 402.8, 103 | 392.05, 104 | 390.53, 105 | 398.67, 106 | 406.13, 107 | 405.46, 108 | 408.38, 109 | 417.2, 110 | 430.12, 111 | 442.78, 112 | 439.29, 113 | 445.52, 114 | 449.98, 115 | 460.71, 116 | 458.66, 117 | 463.84, 118 | 456.77, 119 | 452.97, 120 | 454.74, 121 | 443.86, 122 | 428.85, 123 | 434.58, 124 | 433.26, 125 | 442.93, 126 | 439.66, 127 | 441.35, 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Go Reference](https://pkg.go.dev/badge/github.com/MicahParks/go-ma.svg)](https://pkg.go.dev/github.com/MicahParks/go-ma) [![Go Report Card](https://goreportcard.com/badge/github.com/MicahParks/go-ma)](https://goreportcard.com/report/github.com/MicahParks/go-ma) 2 | # go-ma 3 | The Simple Moving Average (SMA), Exponential Moving (EMA), and Moving Average Convergence Divergence (MACD) technical 4 | analysis algorithms implemented in Golang. 5 | 6 | If there are any other moving average algorithms you'd like implemented, please feel free to open an issue or a PR. 7 | Example: Weighted Moving Average (WMA) 8 | 9 | ```go 10 | import "github.com/MicahParks/go-ma" 11 | ``` 12 | 13 | # Usage 14 | Please see the `examples` directory for full customizable examples. 15 | 16 | ## Create an MACD and signal EMA data structure 17 | This will produce points on the MACD and signal EMA lines, as well as buy/sell signals. 18 | ```go 19 | // Create a logger. 20 | logger := log.New(os.Stdout, "", 0) 21 | 22 | // Gather some data. 23 | // 24 | // For production systems, it'd be best to gather data asynchronously. 25 | prices := testData() 26 | 27 | // Create the MACD and signal EMA pair. 28 | signal := ma.DefaultMACDSignal(prices[:ma.RequiredSamplesForDefaultMACDSignal]) 29 | 30 | // Iterate through the rest of the data and print the results. 31 | var results ma.MACDSignalResults 32 | for i, p := range prices[ma.RequiredSamplesForDefaultMACDSignal:] { 33 | results = signal.Calculate(p) 34 | 35 | // Interpret the buy signal. 36 | var buySignal string 37 | if results.BuySignal != nil { 38 | if *results.BuySignal { 39 | buySignal = "Buy, buy, buy!" 40 | } else { 41 | buySignal = "Sell, sell, sell!" 42 | } 43 | } else { 44 | buySignal = "Do nothing." 45 | } 46 | 47 | logger.Printf("Price index: %d\n MACD: %.5f\n Signal EMA: %.5f\n Buy signal: %s", i+ma.RequiredSamplesForDefaultMACDSignal, results.MACD.Result, results.SignalEMA, buySignal) 48 | } 49 | ``` 50 | 51 | # Testing 52 | There is 100% test coverage and benchmarks for this project. Here is an example benchmark result: 53 | ``` 54 | $ go test -bench . 55 | goos: linux 56 | goarch: amd64 57 | pkg: github.com/MicahParks/go-ma 58 | cpu: Intel(R) Core(TM) i5-9600K CPU @ 3.70GHz 59 | BenchmarkEMABig_Calculate-6 1000000000 0.0000272 ns/op 60 | BenchmarkEMA_Calculate-6 1000000000 0.0000009 ns/op 61 | BenchmarkMACDSignalBig_Calculate-6 1000000000 0.0004847 ns/op 62 | BenchmarkMACDSignal_Calculate-6 1000000000 0.0000064 ns/op 63 | BenchmarkMACDBig_Calculate-6 1000000000 0.0000389 ns/op 64 | BenchmarkMACD_Calculate-6 1000000000 0.0000019 ns/op 65 | BenchmarkSMABig_Calculate-6 1000000000 0.0000476 ns/op 66 | BenchmarkSMA_Calculate-6 1000000000 0.0000009 ns/op 67 | PASS 68 | ok github.com/MicahParks/go-ma 0.014s 69 | ``` 70 | 71 | # Other Technical Algorithms 72 | Looking for some other technical analysis algorithms? Here are some other ones I've implemented: 73 | * Accumulation/Distribution (A/D): [go-ad](https://github.com/MicahParks/go-ad) 74 | * Chaikin: [go-chaikin](https://github.com/MicahParks/go-chaikin) 75 | * Moving Average Convergence Divergence (MACD), Exponential Moving Average (EMA), Simple Moving Average (SMA): 76 | [go-ma](https://github.com/MicahParks/go-ma) 77 | * Relative Strength Index (RSI): [go-rsi](https://github.com/MicahParks/go-rsi) 78 | 79 | # Resources 80 | I built and tested this package based on the resources here: 81 | * [Investopedia](https://www.investopedia.com/terms/m/macd.asp) 82 | * [Invest Excel](https://investexcel.net/how-to-calculate-macd-in-excel/) 83 | -------------------------------------------------------------------------------- /macd_signal_big.go: -------------------------------------------------------------------------------- 1 | package ma 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | // BigMACDSignal represents an MACD and signal EMA pair. 8 | type BigMACDSignal struct { 9 | macd BigMACD 10 | signalEMA *BigEMA 11 | prevBuy bool 12 | } 13 | 14 | // BigMACDSignalResults holds the results of an MACD and signal EMA pair's calculation. 15 | // 16 | // If the calculation triggered a buy signal BuySignal will not be `nil`. It will be a pointer to `true`, if the signal 17 | // indicates a buy and a pointer to `false` if the signal indicates a sell. 18 | type BigMACDSignalResults struct { 19 | BuySignal *bool 20 | MACD BigMACDResults 21 | SignalEMA *big.Float 22 | } 23 | 24 | // NewBigMACDSignal creates a new MACD and signal EMA pair. It's calculations are done in tandem to produced buy/sell 25 | // signals. 26 | func NewBigMACDSignal(macd BigMACD, signalEMA *BigEMA, next *big.Float) (*BigMACDSignal, BigMACDSignalResults) { 27 | macdSignal := &BigMACDSignal{ 28 | macd: macd, 29 | signalEMA: signalEMA, 30 | } 31 | 32 | macdResult := macd.Calculate(next) 33 | results := BigMACDSignalResults{ 34 | MACD: macdResult, 35 | SignalEMA: signalEMA.Calculate(macdResult.Result), 36 | } 37 | 38 | macdSignal.prevBuy = results.MACD.Result.Cmp(results.SignalEMA) == 1 39 | 40 | return macdSignal, results 41 | } 42 | 43 | // Calculate computes the next MACD and signal EMA pair's results. It may also trigger a buy/sell signal. 44 | func (m *BigMACDSignal) Calculate(next *big.Float) BigMACDSignalResults { 45 | macd := m.macd.Calculate(next) 46 | signalEMA := m.signalEMA.Calculate(macd.Result) 47 | 48 | results := BigMACDSignalResults{ 49 | MACD: macd, 50 | SignalEMA: signalEMA, 51 | } 52 | 53 | targetCmp := 1 54 | if m.prevBuy { 55 | targetCmp = -1 56 | } 57 | if results.MACD.Result.Cmp(results.SignalEMA) == targetCmp { 58 | buy := !m.prevBuy 59 | m.prevBuy = buy 60 | results.BuySignal = &buy 61 | } 62 | 63 | return results 64 | } 65 | 66 | // DefaultBigMACDSignal is a helper function to create an MACD and signal EMA pair from the default parameters given the 67 | // initial input data points. 68 | // 69 | // There must be at least 35 data points in the initial slice. This accounts for the number of data points required to 70 | // make the MACD (26) and the number of data points to make the signal EMA from the MACD (9). 71 | // 72 | // If the initial input slice does not have enough data points, the function will return `nil`. 73 | // 74 | // If the initial input slice does has too many data points, the MACD and signal EMA pair will be "caught" up to the 75 | // last data point given, but no results will be accessible. 76 | func DefaultBigMACDSignal(initial []*big.Float) *BigMACDSignal { 77 | if RequiredSamplesForDefaultMACDSignal > len(initial) { 78 | return nil 79 | } 80 | 81 | _, shortSMA := NewBigSMA(initial[:DefaultShortMACDPeriod]) 82 | shortEMA := NewBigEMA(DefaultShortMACDPeriod, shortSMA, nil) 83 | 84 | var latestShortEMA *big.Float 85 | for _, p := range initial[DefaultShortMACDPeriod:DefaultLongMACDPeriod] { 86 | latestShortEMA = shortEMA.Calculate(p) 87 | } 88 | 89 | _, longSMA := NewBigSMA(initial[:DefaultLongMACDPeriod]) 90 | longEMA := NewBigEMA(DefaultLongMACDPeriod, longSMA, nil) 91 | 92 | firstMACDResult := new(big.Float).Sub(latestShortEMA, longSMA) 93 | 94 | macd := NewBigMACD(longEMA, shortEMA) 95 | 96 | signalEMA, _, _ := macd.SignalEMA(firstMACDResult, initial[DefaultLongMACDPeriod:DefaultLongMACDPeriod+DefaultSignalEMAPeriod-1], nil) 97 | 98 | signal, _ := NewBigMACDSignal(macd, signalEMA, initial[DefaultLongMACDPeriod+DefaultSignalEMAPeriod-1]) 99 | 100 | for i := DefaultLongMACDPeriod + DefaultSignalEMAPeriod; i < len(initial); i++ { 101 | signal.Calculate(initial[i]) 102 | } 103 | 104 | return signal 105 | } 106 | -------------------------------------------------------------------------------- /macd_signal_float64.go: -------------------------------------------------------------------------------- 1 | package ma 2 | 3 | const ( 4 | 5 | // RequiredSamplesForDefaultMACDSignal is the required number of period samples for an MACD signal using the default 6 | // arguments. 7 | RequiredSamplesForDefaultMACDSignal = DefaultLongMACDPeriod + DefaultSignalEMAPeriod 8 | ) 9 | 10 | // MACDSignal represents an MACD and signal EMA pair. 11 | type MACDSignal struct { 12 | macd MACD 13 | signalEMA *EMA 14 | prevBuy bool 15 | } 16 | 17 | // MACDSignalResults holds the results of an MACD and signal EMA pair's calculation. 18 | // 19 | // If the calculation triggered a buy signal BuySignal will not be `nil`. It will be a pointer to `true`, if the signal 20 | // indicates a buy and a pointer to `false` if the signal indicates a sell. 21 | type MACDSignalResults struct { 22 | BuySignal *bool 23 | MACD MACDResults 24 | SignalEMA float64 25 | } 26 | 27 | // NewMACDSignal creates a new MACD and signal EMA pair. It's calculations are done in tandem to produced buy/sell 28 | // signals. 29 | func NewMACDSignal(macd MACD, signalEMA *EMA, next float64) (*MACDSignal, MACDSignalResults) { 30 | macdSignal := &MACDSignal{ 31 | macd: macd, 32 | signalEMA: signalEMA, 33 | } 34 | 35 | macdResult := macd.Calculate(next) 36 | results := MACDSignalResults{ 37 | MACD: macdResult, 38 | SignalEMA: signalEMA.Calculate(macdResult.Result), 39 | } 40 | 41 | macdSignal.prevBuy = results.MACD.Result > results.SignalEMA 42 | 43 | return macdSignal, results 44 | } 45 | 46 | // Calculate computes the next MACD and signal EMA pair's results. It may also trigger a buy/sell signal. 47 | func (m *MACDSignal) Calculate(next float64) MACDSignalResults { 48 | macd := m.macd.Calculate(next) 49 | signalEMA := m.signalEMA.Calculate(macd.Result) 50 | 51 | results := MACDSignalResults{ 52 | MACD: macd, 53 | SignalEMA: signalEMA, 54 | } 55 | 56 | if results.MACD.Result > results.SignalEMA != m.prevBuy { 57 | buy := !m.prevBuy 58 | m.prevBuy = buy 59 | results.BuySignal = &buy 60 | } 61 | 62 | return results 63 | } 64 | 65 | // DefaultMACDSignal is a helper function to create an MACD and signal EMA pair from the default parameters given the 66 | // initial input data points. 67 | // 68 | // There must be at least 35 data points in the initial slice. This accounts for the number of data points required to 69 | // make the MACD (26) and the number of data points to make the signal EMA from the MACD (9). 70 | // 71 | // If the initial input slice does not have enough data points, the function will return `nil`. 72 | // 73 | // If the initial input slice does has too many data points, the MACD and signal EMA pair will be "caught" up to the 74 | // last data point given, but no results will be accessible. 75 | func DefaultMACDSignal(initial []float64) *MACDSignal { 76 | if RequiredSamplesForDefaultMACDSignal > len(initial) { 77 | return nil 78 | } 79 | 80 | _, shortSMA := NewSMA(initial[:DefaultShortMACDPeriod]) 81 | shortEMA := NewEMA(DefaultShortMACDPeriod, shortSMA, 0) 82 | 83 | var latestShortEMA float64 84 | for _, p := range initial[DefaultShortMACDPeriod:DefaultLongMACDPeriod] { 85 | latestShortEMA = shortEMA.Calculate(p) 86 | } 87 | 88 | _, longSMA := NewSMA(initial[:DefaultLongMACDPeriod]) 89 | longEMA := NewEMA(DefaultLongMACDPeriod, longSMA, 0) 90 | 91 | firstMACDResult := latestShortEMA - longSMA 92 | 93 | macd := NewMACD(longEMA, shortEMA) 94 | 95 | signalEMA, _, _ := macd.SignalEMA(firstMACDResult, initial[DefaultLongMACDPeriod:DefaultLongMACDPeriod+DefaultSignalEMAPeriod-1], 0) 96 | 97 | signal, _ := NewMACDSignal(macd, signalEMA, initial[DefaultLongMACDPeriod+DefaultSignalEMAPeriod-1]) 98 | 99 | for i := DefaultLongMACDPeriod + DefaultSignalEMAPeriod; i < len(initial); i++ { 100 | signal.Calculate(initial[i]) 101 | } 102 | 103 | return signal 104 | } 105 | -------------------------------------------------------------------------------- /examples/custom_signal/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/MicahParks/go-ma" 8 | ) 9 | 10 | // This example is functionally equivalent to the default signal, but it can be modified for customization. 11 | // These customizations can include period lengths and EMA smoothing constants. 12 | // Be careful to correctly index any slices during creations and read all function/method documentation. 13 | func main() { 14 | // Create a logger. 15 | logger := log.New(os.Stdout, "", 0) 16 | 17 | // Gather some data. 18 | // 19 | // For production systems, it'd be best to gather data asynchronously. 20 | prices := testData() 21 | 22 | // Get the first result of the short SMA. The first result of the SMA is the same as the first result of the EMA. 23 | // 24 | // The length of the initial slice is the number of periods used for calculating the SMA. 25 | _, shortSMA := ma.NewSMA(prices[:ma.DefaultShortMACDPeriod]) 26 | 27 | // Create the short EMA data structure. 28 | shortEMA := ma.NewEMA(ma.DefaultShortMACDPeriod, shortSMA, 0) 29 | 30 | // Save the last value of the short EMA for the first MACD calculation. 31 | var latestShortEMA float64 32 | 33 | // Catch up the short EMA to the period where the long EMA plus the signal EMA will be at. 34 | for _, p := range prices[ma.DefaultShortMACDPeriod:ma.DefaultLongMACDPeriod] { 35 | latestShortEMA = shortEMA.Calculate(p) 36 | } 37 | 38 | // Create the long EMA. 39 | _, longSMA := ma.NewSMA(prices[:ma.DefaultLongMACDPeriod]) 40 | longEMA := ma.NewEMA(ma.DefaultLongMACDPeriod, longSMA, 0) 41 | 42 | // The first result returned from calculating the MACD will be the second possible MACD result. To get the first 43 | // possible MACD result, use the most recent short and long EMA values. For the long EMA value, this will be 44 | // equivalent to the most recent long SMA value. 45 | firstMACDResult := latestShortEMA - longSMA 46 | 47 | // Create the MACD from the short and long EMAs. 48 | macd := ma.NewMACD(longEMA, shortEMA) 49 | 50 | // Create the signal EMA. 51 | signalEMA, _, _ := macd.SignalEMA(firstMACDResult, prices[ma.DefaultLongMACDPeriod:ma.RequiredSamplesForDefaultMACDSignal-1], 0) 52 | 53 | // Create the signal from the MACD and signal EMA. 54 | signal, results := ma.NewMACDSignal(macd, signalEMA, prices[ma.RequiredSamplesForDefaultMACDSignal-1]) 55 | buySignal := "Do nothing." // The first result's buy signal will never buy a buy/sell. 56 | logger.Printf("Period index: %d\n Buy Signal: %s\n MACD: %.5f\n Signal EMA: %.5f", ma.RequiredSamplesForDefaultMACDSignal-1, buySignal, results.MACD.Result, results.SignalEMA) 57 | 58 | // Use the remaining data to generate the signal results for each period. 59 | for i := ma.RequiredSamplesForDefaultMACDSignal; i < len(prices); i++ { 60 | results = signal.Calculate(prices[i]) 61 | 62 | // Interpret the buy signal. 63 | if results.BuySignal != nil { 64 | if *results.BuySignal { 65 | buySignal = "Buy, buy, buy!" 66 | } else { 67 | buySignal = "Sell, sell, sell!" 68 | } 69 | } else { 70 | buySignal = "Do nothing." 71 | } 72 | 73 | logger.Printf("Period index: %d\n Buy Signal: %s\n MACD: %.5f\n Signal EMA: %.5f", i, buySignal, results.MACD.Result, results.SignalEMA) 74 | } 75 | } 76 | 77 | func testData() (prices []float64) { 78 | return []float64{ 79 | 459.99, 80 | 448.85, 81 | 446.06, 82 | 450.81, 83 | 442.8, 84 | 448.97, 85 | 444.57, 86 | 441.4, 87 | 430.47, 88 | 420.05, 89 | 431.14, 90 | 425.66, 91 | 430.58, 92 | 431.72, 93 | 437.87, 94 | 428.43, 95 | 428.35, 96 | 432.5, 97 | 443.66, 98 | 455.72, 99 | 454.49, 100 | 452.08, 101 | 452.73, 102 | 461.91, 103 | 463.58, 104 | 461.14, 105 | 452.08, 106 | 442.66, 107 | 428.91, 108 | 429.79, 109 | 431.99, 110 | 427.72, 111 | 423.2, 112 | 426.21, 113 | 426.98, 114 | 435.69, 115 | 434.33, 116 | 429.8, 117 | 419.85, 118 | 426.24, 119 | 402.8, 120 | 392.05, 121 | 390.53, 122 | 398.67, 123 | 406.13, 124 | 405.46, 125 | 408.38, 126 | 417.2, 127 | 430.12, 128 | 442.78, 129 | 439.29, 130 | 445.52, 131 | 449.98, 132 | 460.71, 133 | 458.66, 134 | 463.84, 135 | 456.77, 136 | 452.97, 137 | 454.74, 138 | 443.86, 139 | 428.85, 140 | 434.58, 141 | 433.26, 142 | 442.93, 143 | 439.66, 144 | 441.35, 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /macd_signal_test.go: -------------------------------------------------------------------------------- 1 | package ma_test 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | "github.com/MicahParks/go-ma" 8 | ) 9 | 10 | func BenchmarkMACDSignalBig_Calculate(b *testing.B) { 11 | _, shortSMA := ma.NewBigSMA(bigPrices[:ma.DefaultShortMACDPeriod]) 12 | shortEMA := ma.NewBigEMA(ma.DefaultShortMACDPeriod, shortSMA, nil) 13 | for _, p := range bigPrices[ma.DefaultShortMACDPeriod:ma.DefaultLongMACDPeriod] { 14 | shortEMA.Calculate(p) 15 | } 16 | 17 | _, signalSMA := ma.NewBigSMA(bigPrices[:ma.DefaultSignalEMAPeriod]) 18 | signalEMA := ma.NewBigEMA(ma.DefaultSignalEMAPeriod, signalSMA, nil) 19 | for _, p := range bigPrices[ma.DefaultSignalEMAPeriod:ma.DefaultLongMACDPeriod] { 20 | signalEMA.Calculate(p) 21 | } 22 | 23 | _, longSMA := ma.NewBigSMA(bigPrices[:ma.DefaultLongMACDPeriod]) 24 | longEMA := ma.NewBigEMA(ma.DefaultLongMACDPeriod, longSMA, nil) 25 | 26 | macd := ma.NewBigMACD(longEMA, shortEMA) 27 | 28 | signal, _ := ma.NewBigMACDSignal(macd, signalEMA, bigPrices[ma.DefaultLongMACDPeriod+1]) 29 | 30 | for _, p := range bigPrices[ma.DefaultLongMACDPeriod+2:] { 31 | signal.Calculate(p) 32 | } 33 | } 34 | 35 | func BenchmarkMACDSignalFloat_Calculate(b *testing.B) { 36 | _, shortSMA := ma.NewSMA(prices[:ma.DefaultShortMACDPeriod]) 37 | shortEMA := ma.NewEMA(ma.DefaultShortMACDPeriod, shortSMA, 0) 38 | for _, p := range prices[ma.DefaultShortMACDPeriod:ma.DefaultLongMACDPeriod] { 39 | shortEMA.Calculate(p) 40 | } 41 | 42 | _, signalSMA := ma.NewSMA(prices[:ma.DefaultSignalEMAPeriod]) 43 | signalEMA := ma.NewEMA(ma.DefaultSignalEMAPeriod, signalSMA, 0) 44 | for _, p := range prices[ma.DefaultSignalEMAPeriod:ma.DefaultLongMACDPeriod] { 45 | signalEMA.Calculate(p) 46 | } 47 | 48 | _, longSMA := ma.NewSMA(prices[:ma.DefaultLongMACDPeriod]) 49 | longEMA := ma.NewEMA(ma.DefaultLongMACDPeriod, longSMA, 0) 50 | 51 | macd := ma.NewMACD(longEMA, shortEMA) 52 | 53 | signal, _ := ma.NewMACDSignal(macd, signalEMA, prices[ma.DefaultLongMACDPeriod+1]) 54 | 55 | for _, p := range prices[ma.DefaultLongMACDPeriod+2:] { 56 | signal.Calculate(p) 57 | } 58 | } 59 | 60 | func TestMACDSignalBig_Calculate(t *testing.T) { 61 | var latestShortEMA *big.Float 62 | _, shortSMA := ma.NewBigSMA(bigPrices[:ma.DefaultShortMACDPeriod]) 63 | shortEMA := ma.NewBigEMA(ma.DefaultShortMACDPeriod, shortSMA, nil) 64 | for _, p := range bigPrices[ma.DefaultShortMACDPeriod:ma.DefaultLongMACDPeriod] { 65 | latestShortEMA = shortEMA.Calculate(p) 66 | } 67 | 68 | _, longSMA := ma.NewBigSMA(bigPrices[:ma.DefaultLongMACDPeriod]) 69 | longEMA := ma.NewBigEMA(ma.DefaultLongMACDPeriod, longSMA, nil) 70 | 71 | firstMACDResult := new(big.Float).Sub(latestShortEMA, longSMA) 72 | 73 | macd := ma.NewBigMACD(longEMA, shortEMA) 74 | 75 | signalEMA, _, _ := macd.SignalEMA(firstMACDResult, bigPrices[ma.DefaultLongMACDPeriod:ma.RequiredSamplesForDefaultMACDSignal-1], nil) 76 | 77 | signal, _ := ma.NewBigMACDSignal(macd, signalEMA, bigPrices[ma.RequiredSamplesForDefaultMACDSignal-1]) 78 | 79 | for i, p := range bigPrices[ma.RequiredSamplesForDefaultMACDSignal:] { 80 | actual := signal.Calculate(p).BuySignal 81 | expected := signalResults[i] 82 | if actual != expected { 83 | if actual == nil || expected == nil || *actual != *expected { 84 | t.FailNow() 85 | } 86 | } 87 | } 88 | } 89 | 90 | func TestMACDSignal_Calculate(t *testing.T) { 91 | var latestShortEMA float64 92 | _, shortSMA := ma.NewSMA(prices[:ma.DefaultShortMACDPeriod]) 93 | shortEMA := ma.NewEMA(ma.DefaultShortMACDPeriod, shortSMA, 0) 94 | for _, p := range prices[ma.DefaultShortMACDPeriod:ma.DefaultLongMACDPeriod] { 95 | latestShortEMA = shortEMA.Calculate(p) 96 | } 97 | 98 | _, longSMA := ma.NewSMA(prices[:ma.DefaultLongMACDPeriod]) 99 | longEMA := ma.NewEMA(ma.DefaultLongMACDPeriod, longSMA, 0) 100 | 101 | firstMACDResult := latestShortEMA - longSMA 102 | 103 | macd := ma.NewMACD(longEMA, shortEMA) 104 | 105 | signalEMA, _, _ := macd.SignalEMA(firstMACDResult, prices[ma.DefaultLongMACDPeriod:ma.RequiredSamplesForDefaultMACDSignal-1], 0) 106 | 107 | signal, _ := ma.NewMACDSignal(macd, signalEMA, prices[ma.RequiredSamplesForDefaultMACDSignal-1]) 108 | 109 | for i, p := range prices[ma.RequiredSamplesForDefaultMACDSignal:] { 110 | actual := signal.Calculate(p).BuySignal 111 | expected := signalResults[i] 112 | if actual != expected { 113 | if actual == nil || expected == nil || *actual != *expected { 114 | t.FailNow() 115 | } 116 | } 117 | } 118 | } 119 | 120 | func TestDefaultMACDSignal(t *testing.T) { 121 | signal := ma.DefaultMACDSignal(prices[:ma.RequiredSamplesForDefaultMACDSignal]) 122 | 123 | for i, p := range prices[ma.RequiredSamplesForDefaultMACDSignal:] { 124 | actual := signal.Calculate(p).BuySignal 125 | expected := signalResults[i] 126 | if actual != expected { 127 | if actual == nil || expected == nil || *actual != *expected { 128 | t.FailNow() 129 | } 130 | } 131 | } 132 | } 133 | 134 | func TestDefaultMACDSignalBig(t *testing.T) { 135 | signal := ma.DefaultBigMACDSignal(bigPrices[:ma.RequiredSamplesForDefaultMACDSignal]) 136 | 137 | for i, p := range bigPrices[ma.RequiredSamplesForDefaultMACDSignal:] { 138 | actual := signal.Calculate(p).BuySignal 139 | expected := signalResults[i] 140 | if actual != expected { 141 | if actual == nil || expected == nil || *actual != *expected { 142 | t.FailNow() 143 | } 144 | } 145 | } 146 | } 147 | 148 | func TestDefaultMACDSignalNil(t *testing.T) { 149 | signal := ma.DefaultMACDSignal(nil) 150 | if signal != nil { 151 | t.FailNow() 152 | } 153 | } 154 | 155 | func TestDefaultMACDSignalBigNil(t *testing.T) { 156 | signal := ma.DefaultBigMACDSignal(nil) 157 | if signal != nil { 158 | t.FailNow() 159 | } 160 | } 161 | 162 | func TestDefaultMACDSignalCatchUp(t *testing.T) { 163 | const catchUp = 5 164 | 165 | signal := ma.DefaultMACDSignal(prices[:ma.RequiredSamplesForDefaultMACDSignal+catchUp]) 166 | 167 | for i, p := range prices[ma.RequiredSamplesForDefaultMACDSignal+catchUp:] { 168 | actual := signal.Calculate(p).BuySignal 169 | expected := signalResults[i+catchUp] 170 | if actual != expected { 171 | if actual == nil || expected == nil || *actual != *expected { 172 | t.FailNow() 173 | } 174 | } 175 | } 176 | } 177 | 178 | func TestDefaultMACDSignalCatchUpBig(t *testing.T) { 179 | const catchUp = 5 180 | 181 | signal := ma.DefaultBigMACDSignal(bigPrices[:ma.RequiredSamplesForDefaultMACDSignal+catchUp]) 182 | 183 | for i, p := range bigPrices[ma.RequiredSamplesForDefaultMACDSignal+catchUp:] { 184 | actual := signal.Calculate(p).BuySignal 185 | expected := signalResults[i+catchUp] 186 | if actual != expected { 187 | if actual == nil || expected == nil || *actual != *expected { 188 | t.FailNow() 189 | } 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /data_test.go: -------------------------------------------------------------------------------- 1 | package ma_test 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | var ( 8 | bigPrices = floatToBig(prices) 9 | prices = []float64{ 10 | 459.99, 11 | 448.85, 12 | 446.06, 13 | 450.81, 14 | 442.8, 15 | 448.97, 16 | 444.57, 17 | 441.4, 18 | 430.47, 19 | 420.05, 20 | 431.14, 21 | 425.66, 22 | 430.58, 23 | 431.72, 24 | 437.87, 25 | 428.43, 26 | 428.35, 27 | 432.5, 28 | 443.66, 29 | 455.72, 30 | 454.49, 31 | 452.08, 32 | 452.73, 33 | 461.91, 34 | 463.58, 35 | 461.14, 36 | 452.08, 37 | 442.66, 38 | 428.91, 39 | 429.79, 40 | 431.99, 41 | 427.72, 42 | 423.2, 43 | 426.21, 44 | 426.98, 45 | 435.69, 46 | 434.33, 47 | 429.8, 48 | 419.85, 49 | 426.24, 50 | 402.8, 51 | 392.05, 52 | 390.53, 53 | 398.67, 54 | 406.13, 55 | 405.46, 56 | 408.38, 57 | 417.2, 58 | 430.12, 59 | 442.78, 60 | 439.29, 61 | 445.52, 62 | 449.98, 63 | 460.71, 64 | 458.66, 65 | 463.84, 66 | 456.77, 67 | 452.97, 68 | 454.74, 69 | 443.86, 70 | 428.85, 71 | 434.58, 72 | 433.26, 73 | 442.93, 74 | 439.66, 75 | 441.35, 76 | } 77 | results = []float64{ 78 | 7.70337838145673003964475356042385101318359375, 79 | 6.41607475695587936570518650114536285400390625, 80 | 4.23751978326481548720039427280426025390625000, 81 | 2.55258332486573635833337903022766113281250000, 82 | 1.37888571985365615546470507979393005371093750, 83 | 0.10298149119910249282838776707649230957031250, 84 | -1.25840195280312627801322378218173980712890625, 85 | -2.07055819009491415272350423038005828857421875, 86 | -2.62184232825688923185225576162338256835937500, 87 | -2.32906674045494810343370772898197174072265625, 88 | -2.18163211479298979611485265195369720458984375, 89 | -2.40262627286642782564740628004074096679687500, 90 | -3.34212168135286447068210691213607788085937500, 91 | -3.53036313607753982068970799446105957031250000, 92 | -5.50747124862732562178280204534530639648437500, 93 | -7.85127422856083967417362146079540252685546875, 94 | -9.71936745524880052471417002379894256591796875, 95 | -10.42286650800718916798359714448451995849609375, 96 | -10.26016215893747585141682066023349761962890625, 97 | -10.06920961013429405284114181995391845703125000, 98 | -9.57191961195360363490181043744087219238281250, 99 | -8.36963349244507526236702688038349151611328125, 100 | -6.30163572368542190815787762403488159179687500, 101 | -3.59968150914517082128440961241722106933593750, 102 | -1.72014836089687150888494215905666351318359375, 103 | 0.26900323229938294389285147190093994140625000, 104 | 2.18017324711348692289902828633785247802734375, 105 | 4.50863780861044460834818892180919647216796875, 106 | 6.11802015384472497316892258822917938232421875, 107 | 7.72243059351438887460972182452678680419921875, 108 | 8.32745380871403995115542784333229064941406250, 109 | 8.40344118462587630347115918993949890136718750, 110 | 8.50840632319352607737528160214424133300781250, 111 | 7.62576184402922763183596543967723846435546875, 112 | 5.64994908292868558419286273419857025146484375, 113 | 4.49465476488205695204669609665870666503906250, 114 | 3.43298936168440604888019151985645294189453125, 115 | 3.33347385363293824411812238395214080810546875, 116 | 2.95666285611525836429791525006294250488281250, 117 | 2.76256121582525793201057240366935729980468750, 118 | } 119 | smaResults = []float64{ 120 | 440.51200000000005729816621169447898864746093750, 121 | 438.19300000000004047251422889530658721923828125, 122 | 436.64500000000009549694368615746498107910156250, 123 | 434.73600000000004683897714130580425262451171875, 124 | 434.24300000000005184119800105690956115722656250, 125 | 432.18900000000002137312549166381359100341796875, 126 | 430.56700000000000727595761418342590332031250000, 127 | 429.67699999999996407495927996933460235595703125, 128 | 430.99599999999992405719240196049213409423828125, 129 | 434.56299999999993133315001614391803741455078125, 130 | 436.89799999999996771293808706104755401611328125, 131 | 439.53999999999996362021192908287048339843750000, 132 | 441.75499999999993860910763032734394073486328125, 133 | 444.77400000000000090949470177292823791503906250, 134 | 447.34499999999997044142219237983226776123046875, 135 | 450.61599999999998544808477163314819335937500000, 136 | 452.98899999999991905497154220938682556152343750, 137 | 454.00499999999993860910763032734394073486328125, 138 | 452.52999999999991587174008600413799285888671875, 139 | 449.93700000000001182343112304806709289550781250, 140 | 447.68700000000001182343112304806709289550781250, 141 | 445.25100000000003319655661471188068389892578125, 142 | 442.29799999999994497557054273784160614013671875, 143 | 438.72799999999995179678080603480339050292968750, 144 | 435.06800000000004047251422889530658721923828125, 145 | 432.52300000000002455635694786906242370605468750, 146 | 430.74800000000004729372449219226837158203125000, 147 | 429.46200000000010277290130034089088439941406250, 148 | 428.55600000000004001776687800884246826171875000, 149 | 428.20100000000002182787284255027770996093750000, 150 | 425.28199999999998226485331542789936065673828125, 151 | 421.71499999999997498889570124447345733642578125, 152 | 418.44800000000003592504072003066539764404296875, 153 | 415.69400000000007366907084360718727111816406250, 154 | 413.60900000000003728928277269005775451660156250, 155 | 410.58600000000006957634468562901020050048828125, 156 | 407.99100000000009913492249324917793273925781250, 157 | 406.73100000000005138645065017044544219970703125, 158 | 407.75799999999998135535861365497112274169921875, 159 | 409.41199999999997771737980656325817108154296875, 160 | 413.06099999999997862687450833618640899658203125, 161 | 418.40800000000001546140993013978004455566406250, 162 | 424.35299999999995179678080603480339050292968750, 163 | 430.55699999999995952748577110469341278076171875, 164 | 435.80999999999994543031789362430572509765625000, 165 | 441.64799999999996771293808706104755401611328125, 166 | 446.48699999999996634869603440165519714355468750, 167 | 450.06399999999996452970663085579872131347656250, 168 | 452.52599999999995361577020958065986633300781250, 169 | 452.63399999999990086507750675082206726074218750, 170 | 451.58999999999997498889570124447345733642578125, 171 | 450.49599999999998090061126276850700378417968750, 172 | 448.82399999999995543475961312651634216308593750, 173 | 447.04599999999999226929503493010997772216796875, 174 | 445.14600000000001500666257925331592559814453125, 175 | 442.89699999999993451638147234916687011718750000, 176 | } 177 | signalResults = []*bool{ 178 | nil, 179 | nil, 180 | nil, 181 | nil, 182 | nil, 183 | nil, 184 | nil, 185 | nil, 186 | nil, 187 | nil, 188 | nil, 189 | nil, 190 | nil, 191 | &[]bool{true}[0], 192 | nil, 193 | nil, 194 | nil, 195 | nil, 196 | nil, 197 | nil, 198 | nil, 199 | nil, 200 | nil, 201 | nil, 202 | nil, 203 | nil, 204 | &[]bool{false}[0], 205 | nil, 206 | nil, 207 | nil, 208 | nil, 209 | } 210 | ) 211 | 212 | func floatToBig(s []float64) (b []*big.Float) { 213 | l := len(s) 214 | b = make([]*big.Float, l) 215 | for i := 0; i < l; i++ { 216 | b[i] = big.NewFloat(s[i]) 217 | } 218 | return b 219 | } 220 | --------------------------------------------------------------------------------