exp moving average, renaming moving average to simple moving average.

This commit is contained in:
Will Charczuk 2016-07-16 13:10:44 -07:00
parent 9ad15b3288
commit 8bd5cdfe17
5 changed files with 158 additions and 19 deletions

View File

@ -0,0 +1,88 @@
package chart
const (
// DefaultExponentialMovingAverageSigma is the default exponential smoothing factor.
DefaultExponentialMovingAverageSigma = 0.25
)
// ExponentialMovingAverageSeries is a computed series.
type ExponentialMovingAverageSeries struct {
Name string
Style Style
YAxis YAxisType
// Sigma is the 'smoothing factor' parameter.
Sigma float64
InnerSeries ValueProvider
valueBuffer []float64
}
// GetName returns the name of the time series.
func (mas ExponentialMovingAverageSeries) GetName() string {
return mas.Name
}
// GetStyle returns the line style.
func (mas ExponentialMovingAverageSeries) GetStyle() Style {
return mas.Style
}
// GetYAxis returns which YAxis the series draws on.
func (mas ExponentialMovingAverageSeries) GetYAxis() YAxisType {
return mas.YAxis
}
// Len returns the number of elements in the series.
func (mas *ExponentialMovingAverageSeries) Len() int {
return mas.InnerSeries.Len()
}
// GetSigma returns the smoothing factor for the serise.
func (mas ExponentialMovingAverageSeries) GetSigma(defaults ...float64) float64 {
if mas.Sigma == 0 {
if len(defaults) > 0 {
return defaults[0]
}
return DefaultExponentialMovingAverageSigma
}
return mas.Sigma
}
// GetValue gets a value at a given index.
func (mas *ExponentialMovingAverageSeries) GetValue(index int) (x float64, y float64) {
if mas.InnerSeries == nil {
return
}
if mas.valueBuffer == nil || index == 0 {
mas.valueBuffer = make([]float64, mas.InnerSeries.Len())
}
vx, vy := mas.InnerSeries.GetValue(index)
x = vx
if index == 0 {
mas.valueBuffer[0] = vy
y = vy
return
}
sig := mas.GetSigma()
mas.valueBuffer[index] = sig*vy + ((1.0 - sig) * mas.valueBuffer[index-1])
y = mas.valueBuffer[index]
return
}
// GetLastValue computes the last moving average value but walking back window size samples,
// and recomputing the last moving average chunk.
func (mas ExponentialMovingAverageSeries) GetLastValue() (x float64, y float64) {
if mas.InnerSeries == nil {
return
}
return 0.0, 0.0 //this one is going to be a bitch.
}
// Render renders the series.
func (mas *ExponentialMovingAverageSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := mas.Style.WithDefaultsFrom(defaults)
DrawLineSeries(r, canvasBox, xrange, yrange, style, mas)
}

View File

@ -0,0 +1,31 @@
package chart
import (
"math"
"testing"
"github.com/blendlabs/go-assert"
)
func TestExponentialMovingAverageSeries(t *testing.T) {
assert := assert.New(t)
mockSeries := mockValueProvider{
Seq(1.0, 10.0),
Seq(10, 1.0),
}
assert.Equal(10, mockSeries.Len())
mas := &ExponentialMovingAverageSeries{
InnerSeries: mockSeries,
}
var yvalues []float64
for x := 0; x < mas.Len(); x++ {
_, y := mas.GetValue(x)
yvalues = append(yvalues, y)
}
assert.Equal(10.0, yvalues[0])
assert.True(math.Abs(yvalues[9]-3.77) < 0.01)
}

View File

@ -5,8 +5,8 @@ const (
DefaultMovingAverageWindowSize = 5 DefaultMovingAverageWindowSize = 5
) )
// MovingAverageSeries is a computed series. // SimpleMovingAverageSeries is a computed series.
type MovingAverageSeries struct { type SimpleMovingAverageSeries struct {
Name string Name string
Style Style Style Style
YAxis YAxisType YAxis YAxisType
@ -18,27 +18,27 @@ type MovingAverageSeries struct {
} }
// GetName returns the name of the time series. // GetName returns the name of the time series.
func (mas MovingAverageSeries) GetName() string { func (mas SimpleMovingAverageSeries) GetName() string {
return mas.Name return mas.Name
} }
// GetStyle returns the line style. // GetStyle returns the line style.
func (mas MovingAverageSeries) GetStyle() Style { func (mas SimpleMovingAverageSeries) GetStyle() Style {
return mas.Style return mas.Style
} }
// GetYAxis returns which YAxis the series draws on. // GetYAxis returns which YAxis the series draws on.
func (mas MovingAverageSeries) GetYAxis() YAxisType { func (mas SimpleMovingAverageSeries) GetYAxis() YAxisType {
return mas.YAxis return mas.YAxis
} }
// Len returns the number of elements in the series. // Len returns the number of elements in the series.
func (mas *MovingAverageSeries) Len() int { func (mas *SimpleMovingAverageSeries) Len() int {
return mas.InnerSeries.Len() return mas.InnerSeries.Len()
} }
// GetValue gets a value at a given index. // GetValue gets a value at a given index.
func (mas *MovingAverageSeries) GetValue(index int) (x float64, y float64) { func (mas *SimpleMovingAverageSeries) GetValue(index int) (x float64, y float64) {
if mas.InnerSeries == nil { if mas.InnerSeries == nil {
return return
} }
@ -57,7 +57,7 @@ func (mas *MovingAverageSeries) GetValue(index int) (x float64, y float64) {
// GetLastValue computes the last moving average value but walking back window size samples, // GetLastValue computes the last moving average value but walking back window size samples,
// and recomputing the last moving average chunk. // and recomputing the last moving average chunk.
func (mas MovingAverageSeries) GetLastValue() (x float64, y float64) { func (mas SimpleMovingAverageSeries) GetLastValue() (x float64, y float64) {
if mas.InnerSeries == nil { if mas.InnerSeries == nil {
return return
} }
@ -78,7 +78,7 @@ func (mas MovingAverageSeries) GetLastValue() (x float64, y float64) {
} }
// GetWindowSize returns the window size. // GetWindowSize returns the window size.
func (mas MovingAverageSeries) GetWindowSize(defaults ...int) int { func (mas SimpleMovingAverageSeries) GetWindowSize(defaults ...int) int {
if mas.WindowSize == 0 { if mas.WindowSize == 0 {
if len(defaults) > 0 { if len(defaults) > 0 {
return defaults[0] return defaults[0]
@ -88,7 +88,7 @@ func (mas MovingAverageSeries) GetWindowSize(defaults ...int) int {
return mas.WindowSize return mas.WindowSize
} }
func (mas MovingAverageSeries) getAverage(valueBuffer *RingBuffer) float64 { func (mas SimpleMovingAverageSeries) getAverage(valueBuffer *RingBuffer) float64 {
var accum float64 var accum float64
valueBuffer.Each(func(v interface{}) { valueBuffer.Each(func(v interface{}) {
if typed, isTyped := v.(float64); isTyped { if typed, isTyped := v.(float64); isTyped {
@ -99,7 +99,7 @@ func (mas MovingAverageSeries) getAverage(valueBuffer *RingBuffer) float64 {
} }
// Render renders the series. // Render renders the series.
func (mas *MovingAverageSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { func (mas *SimpleMovingAverageSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := mas.Style.WithDefaultsFrom(defaults) style := mas.Style.WithDefaultsFrom(defaults)
DrawLineSeries(r, canvasBox, xrange, yrange, style, mas) DrawLineSeries(r, canvasBox, xrange, yrange, style, mas)
} }

View File

@ -21,7 +21,7 @@ func (m mockValueProvider) GetValue(index int) (x, y float64) {
return return
} }
func TestMovingAverageSeriesGetValue(t *testing.T) { func TestSimpleMovingAverageSeriesGetValue(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
mockSeries := mockValueProvider{ mockSeries := mockValueProvider{
@ -30,7 +30,7 @@ func TestMovingAverageSeriesGetValue(t *testing.T) {
} }
assert.Equal(10, mockSeries.Len()) assert.Equal(10, mockSeries.Len())
mas := &MovingAverageSeries{ mas := &SimpleMovingAverageSeries{
InnerSeries: mockSeries, InnerSeries: mockSeries,
WindowSize: 10, WindowSize: 10,
} }
@ -52,7 +52,7 @@ func TestMovingAverageSeriesGetValue(t *testing.T) {
assert.Equal(6.0, yvalues[8]) assert.Equal(6.0, yvalues[8])
} }
func TestMovingAverageSeriesGetLastValueWindowOverlap(t *testing.T) { func TestSimpleMovingAverageSeriesGetLastValueWindowOverlap(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
mockSeries := mockValueProvider{ mockSeries := mockValueProvider{
@ -61,7 +61,7 @@ func TestMovingAverageSeriesGetLastValueWindowOverlap(t *testing.T) {
} }
assert.Equal(10, mockSeries.Len()) assert.Equal(10, mockSeries.Len())
mas := &MovingAverageSeries{ mas := &SimpleMovingAverageSeries{
InnerSeries: mockSeries, InnerSeries: mockSeries,
WindowSize: 15, WindowSize: 15,
} }
@ -78,7 +78,7 @@ func TestMovingAverageSeriesGetLastValueWindowOverlap(t *testing.T) {
assert.Equal(yvalues[len(yvalues)-1], ly) assert.Equal(yvalues[len(yvalues)-1], ly)
} }
func TestMovingAverageSeriesGetLastValue(t *testing.T) { func TestSimpleMovingAverageSeriesGetLastValue(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
mockSeries := mockValueProvider{ mockSeries := mockValueProvider{
@ -87,7 +87,7 @@ func TestMovingAverageSeriesGetLastValue(t *testing.T) {
} }
assert.Equal(100, mockSeries.Len()) assert.Equal(100, mockSeries.Len())
mas := &MovingAverageSeries{ mas := &SimpleMovingAverageSeries{
InnerSeries: mockSeries, InnerSeries: mockSeries,
WindowSize: 10, WindowSize: 10,
} }

View File

@ -60,7 +60,7 @@ func chartHandler(rc *web.RequestContext) web.ControllerResult {
}, },
} }
s1ma := &chart.BollingerBandsSeries{ s1bb := &chart.BollingerBandsSeries{
Name: "BBS", Name: "BBS",
Style: chart.Style{ Style: chart.Style{
Show: true, Show: true,
@ -72,6 +72,24 @@ func chartHandler(rc *web.RequestContext) web.ControllerResult {
InnerSeries: s1, InnerSeries: s1,
} }
s1sma := &chart.SimpleMovingAverageSeries{
Style: chart.Style{
Show: true,
StrokeColor: drawing.ColorRed,
StrokeDashArray: []float64{5.0, 5.0},
},
InnerSeries: s1,
}
s1ema := &chart.ExponentialMovingAverageSeries{
Style: chart.Style{
Show: true,
StrokeColor: drawing.ColorBlue,
StrokeDashArray: []float64{5.0, 5.0},
},
InnerSeries: s1,
}
c := chart.Chart{ c := chart.Chart{
Title: "A Test Chart", Title: "A Test Chart",
TitleStyle: chart.Style{ TitleStyle: chart.Style{
@ -102,9 +120,11 @@ func chartHandler(rc *web.RequestContext) web.ControllerResult {
}, },
}, },
Series: []chart.Series{ Series: []chart.Series{
s1bb,
s1, s1,
s1ma,
s1lv, s1lv,
s1sma,
s1ema,
}, },
} }