exp moving average, renaming moving average to simple moving average.
This commit is contained in:
parent
9ad15b3288
commit
8bd5cdfe17
5 changed files with 158 additions and 19 deletions
88
exp_moving_average_series.go
Normal file
88
exp_moving_average_series.go
Normal 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)
|
||||||
|
}
|
31
exp_moving_average_series_test.go
Normal file
31
exp_moving_average_series_test.go
Normal 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)
|
||||||
|
}
|
|
@ -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)
|
||||||
}
|
}
|
|
@ -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,
|
||||||
}
|
}
|
|
@ -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,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue