diff --git a/ema_series.go b/ema_series.go index 5a610e3..d0c58f8 100644 --- a/ema_series.go +++ b/ema_series.go @@ -11,8 +11,6 @@ type EMASeries struct { Style Style YAxis YAxisType - // Sigma is the 'smoothing factor' parameter. - Sigma float64 Period int InnerSeries ValueProvider } @@ -49,14 +47,8 @@ func (ema EMASeries) Len() int { } // GetSigma returns the smoothing factor for the serise. -func (ema EMASeries) GetSigma(defaults ...float64) float64 { - if ema.Sigma == 0 { - if len(defaults) > 0 { - return defaults[0] - } - return DefaultEMASigma - } - return ema.Sigma +func (ema EMASeries) GetSigma() float64 { + return 2.0 / (float64(ema.Period) + 1) } // GetValue gets a value at a given index. @@ -84,11 +76,11 @@ func (ema EMASeries) GetLastValue() (x float64, y float64) { func (ema EMASeries) compute(period, index int) float64 { _, v := ema.InnerSeries.GetValue(index) - if period == 1 || index == 0 { + if index == 0 { return v } - sig := ema.GetSigma() - return sig*v + ((1.0 - sig) * ema.compute(period-1, index-1)) + previousEMA := ema.compute(period-1, index-1) + return ((v - previousEMA) * ema.GetSigma()) + previousEMA } // Render renders the series. diff --git a/ema_series_test.go b/ema_series_test.go index 1778960..42025da 100644 --- a/ema_series_test.go +++ b/ema_series_test.go @@ -1,35 +1,105 @@ package chart import ( - "math" "testing" "github.com/blendlabs/go-assert" ) +var ( + emaXValues = Seq(1.0, 50.0) + emaYValues = []float64{ + 1, 2, 3, 4, 5, 4, 3, 2, + 1, 2, 3, 4, 5, 4, 3, 2, + 1, 2, 3, 4, 5, 4, 3, 2, + 1, 2, 3, 4, 5, 4, 3, 2, + 1, 2, 3, 4, 5, 4, 3, 2, + 1, 2, 3, 4, 5, 4, 3, 2, + 1, 2, + } + emaExpected = []float64{ + 1, + 1.074074074, + 1.216735254, + 1.422903013, + 1.68787316, + 1.859141815, + 1.943649828, + 1.947823915, + 1.877614736, + 1.886680311, + 1.969148437, + 2.119581886, + 2.33294619, + 2.456431658, + 2.496695979, + 2.459903685, + 2.351762671, + 2.325706177, + 2.375653867, + 2.495975803, + 2.681459077, + 2.779128775, + 2.795489607, + 2.73656445, + 2.607930047, + 2.562898191, + 2.595276103, + 2.699329725, + 2.869749746, + 2.953471987, + 2.956918506, + 2.886035654, + 2.746329309, + 2.691045657, + 2.713931163, + 2.809195522, + 2.971477335, + 3.047664199, + 3.044133518, + 2.966790294, + 2.821102124, + 2.760279745, + 2.778036801, + 2.868552593, + 3.026437586, + 3.098553321, + 3.091253075, + 3.010419514, + 2.86149955, + 2.797684768, + } + emaDelta = 0.0001 +) + func TestEMASeries(t *testing.T) { assert := assert.New(t) mockSeries := mockValueProvider{ - Seq(1.0, 10.0), - Seq(10, 1.0), + emaXValues, + emaYValues, } - assert.Equal(10, mockSeries.Len()) + assert.Equal(50, mockSeries.Len()) - mas := &EMASeries{ + ema := &EMASeries{ InnerSeries: mockSeries, + Period: 26, } + sig := ema.GetSigma() + assert.Equal(2.0/(26.0+1), sig) + var yvalues []float64 - for x := 0; x < mas.Len(); x++ { - _, y := mas.GetValue(x) + for x := 0; x < ema.Len(); x++ { + _, y := ema.GetValue(x) yvalues = append(yvalues, y) } - assert.Equal(10.0, yvalues[0]) - assert.True(math.Abs(yvalues[9]-3.77) < 0.01) + for index, yv := range yvalues { + assert.InDelta(yv, emaExpected[index], emaDelta) + } - lvx, lvy := mas.GetLastValue() - assert.Equal(10.0, lvx) - assert.True(math.Abs(lvy-3.77) < 0.01) + lvx, lvy := ema.GetLastValue() + assert.Equal(50.0, lvx) + assert.InDelta(lvy, emaExpected[49], emaDelta) } diff --git a/macd_series.go b/macd_series.go index fd10a67..746bd41 100644 --- a/macd_series.go +++ b/macd_series.go @@ -20,8 +20,6 @@ type MACDSeries struct { PrimaryPeriod int SecondaryPeriod int SignalPeriod int - - Sigma float64 } // GetPeriods returns the primary and secondary periods. @@ -44,17 +42,6 @@ func (macd MACDSeries) GetPeriods() (w1, w2, sig int) { return } -// GetSigma returns the smoothing factor for the serise. -func (macd MACDSeries) GetSigma(defaults ...float64) float64 { - if macd.Sigma == 0 { - if len(defaults) > 0 { - return defaults[0] - } - return DefaultEMASigma - } - return macd.Sigma -} - // GetName returns the name of the time series. func (macd MACDSeries) GetName() string { return macd.Name @@ -76,12 +63,7 @@ func (macd MACDSeries) Len() int { return 0 } - w1, _, _ := macd.GetPeriods() - innerLen := macd.InnerSeries.Len() - if innerLen > w1 { - return innerLen - w1 - } - return 0 + return macd.InnerSeries.Len() } // GetValue gets a value at a given index. For MACD it is the signal value. @@ -91,19 +73,15 @@ func (macd MACDSeries) GetValue(index int) (x float64, y float64) { } w1, w2, sig := macd.GetPeriods() - sigma := macd.GetSigma() - effectiveIndex := index + w1 - x, _ = macd.InnerSeries.GetValue(effectiveIndex) + x, _ = macd.InnerSeries.GetValue(index) signal := EMASeries{ InnerSeries: MACDLineSeries{ InnerSeries: macd.InnerSeries, PrimaryPeriod: w1, SecondaryPeriod: w2, - Sigma: sigma, }, - Sigma: sigma, Period: sig, } @@ -111,11 +89,10 @@ func (macd MACDSeries) GetValue(index int) (x float64, y float64) { InnerSeries: macd.InnerSeries, PrimaryPeriod: w1, SecondaryPeriod: w2, - Sigma: sigma, } - _, sv := signal.GetValue(index) _, lv := macdl.GetValue(index) + _, sv := signal.GetValue(index) y = lv - sv return @@ -131,8 +108,6 @@ type MACDSignalSeries struct { PrimaryPeriod int SecondaryPeriod int SignalPeriod int - - Sigma float64 } // GetPeriods returns the primary and secondary periods. @@ -155,17 +130,6 @@ func (macds MACDSignalSeries) GetPeriods() (w1, w2, sig int) { return } -// GetSigma returns the smoothing factor for the serise. -func (macds MACDSignalSeries) GetSigma(defaults ...float64) float64 { - if macds.Sigma == 0 { - if len(defaults) > 0 { - return defaults[0] - } - return DefaultEMASigma - } - return macds.Sigma -} - // GetName returns the name of the time series. func (macds MACDSignalSeries) GetName() string { return macds.Name @@ -187,12 +151,7 @@ func (macds MACDSignalSeries) Len() int { return 0 } - w1, _, _ := macds.GetPeriods() - innerLen := macds.InnerSeries.Len() - if innerLen > w1 { - return innerLen - w1 - } - return 0 + return macds.InnerSeries.Len() } // GetValue gets a value at a given index. For MACD it is the signal value. @@ -202,19 +161,15 @@ func (macds MACDSignalSeries) GetValue(index int) (x float64, y float64) { } w1, w2, sig := macds.GetPeriods() - sigma := macds.GetSigma() - effectiveIndex := index + w1 - x, _ = macds.InnerSeries.GetValue(effectiveIndex) + x, _ = macds.InnerSeries.GetValue(index) signal := EMASeries{ InnerSeries: MACDLineSeries{ InnerSeries: macds.InnerSeries, PrimaryPeriod: w1, SecondaryPeriod: w2, - Sigma: sigma, }, - Sigma: sigma, Period: sig, } @@ -277,12 +232,7 @@ func (macdl MACDLineSeries) Len() int { return 0 } - w1, _ := macdl.GetPeriods() - innerLen := macdl.InnerSeries.Len() - if innerLen > w1 { - return innerLen - w1 - } - return 0 + return macdl.InnerSeries.Len() } // GetSigma returns the smoothing factor for the serise. @@ -304,19 +254,16 @@ func (macdl MACDLineSeries) GetValue(index int) (x float64, y float64) { w1, w2 := macdl.GetPeriods() - effectiveIndex := index + w1 - x, _ = macdl.InnerSeries.GetValue(effectiveIndex) + x, _ = macdl.InnerSeries.GetValue(index) ema1 := EMASeries{ InnerSeries: macdl.InnerSeries, Period: w1, - Sigma: macdl.GetSigma(), } ema2 := EMASeries{ InnerSeries: macdl.InnerSeries, Period: w2, - Sigma: macdl.GetSigma(), } _, emav1 := ema1.GetValue(index) diff --git a/macd_series_test.go b/macd_series_test.go index 6d78ee1..057cddd 100644 --- a/macd_series_test.go +++ b/macd_series_test.go @@ -1,19 +1,75 @@ package chart import ( + "fmt" "testing" "github.com/blendlabs/go-assert" ) +var ( + macdExpected = []float64{ + 0, + -0.06381766382, + -0.1641441222, + -0.2817201894, + -0.4033023481, + -0.3924673744, + -0.2983093823, + -0.1561821464, + 0.008916708129, + 0.05210332292, + 0.01649503993, + -0.06667130899, + -0.1751344574, + -0.1657328378, + -0.08257097469, + 0.04265109369, + 0.1875741257, + 0.2091853882, + 0.1518975486, + 0.04781419838, + -0.08025242841, + -0.08881960494, + -0.02183529775, + 0.08904155476, + 0.2214141128, + 0.2321805992, + 0.1656331722, + 0.05373789678, + -0.08083727586, + -0.09475354363, + -0.03209767112, + 0.07534076818, + 0.2050442354, + 0.2138010557, + 0.1458045181, + 0.03293263556, + -0.1022243734, + -0.1163957964, + -0.05372761902, + 0.05393941791, + 0.1840438454, + 0.1933365048, + 0.1259788988, + 0.01382225715, + -0.1205656194, + -0.1339326478, + -0.07044017167, + 0.03805851969, + 0.1689918111, + 0.1791024416, + } +) + func TestMACDSeries(t *testing.T) { assert := assert.New(t) mockSeries := mockValueProvider{ - Seq(1.0, 100.0), - SeqRand(100.0, 256), + emaXValues, + emaYValues, } - assert.Equal(100, mockSeries.Len()) + assert.Equal(50, mockSeries.Len()) mas := &MACDSeries{ InnerSeries: mockSeries, @@ -26,4 +82,8 @@ func TestMACDSeries(t *testing.T) { } assert.NotEmpty(yvalues) + for index, vy := range yvalues { + fmt.Printf("delta @ %d actual: %0.9f expected: %0.9f\n", index, vy, macdExpected[index]) + assert.InDelta(vy, macdExpected[index], emaDelta, fmt.Sprintf("delta @ %d actual: %0.9f expected: %0.9f", index, vy, macdExpected[index])) + } }