From 1555902fc46a6084e841a53194f0d80f945459b9 Mon Sep 17 00:00:00 2001 From: Will Charczuk Date: Fri, 7 Sep 2018 12:52:30 -0700 Subject: [PATCH] updates + tests --- annotation_series.go | 5 +++ bollinger_band_series.go | 5 +++ continuous_series.go | 12 ++++++ ema_series.go | 20 ++++++++++ first_value_annotation.go | 37 +++++++++++++++++++ first_value_annotation_test.go | 22 +++++++++++ ...tion_series.go => last_value_annotation.go | 0 last_value_annotation_test.go | 22 +++++++++++ linear_regression_series.go | 20 ++++++++++ polynomial_regression_series.go | 24 ++++++++++++ sma_series.go | 18 +++++++++ time_series.go | 19 +++++++++- value_provider.go | 5 +++ 13 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 first_value_annotation.go create mode 100644 first_value_annotation_test.go rename last_value_annotation_series.go => last_value_annotation.go (100%) create mode 100644 last_value_annotation_test.go diff --git a/annotation_series.go b/annotation_series.go index 9c383c9..c99779b 100644 --- a/annotation_series.go +++ b/annotation_series.go @@ -7,6 +7,11 @@ import ( util "github.com/wcharczuk/go-chart/util" ) +// Interface Assertions. +var ( + _ Series = (*AnnotationSeries)(nil) +) + // AnnotationSeries is a series of labels on the chart. type AnnotationSeries struct { Name string diff --git a/bollinger_band_series.go b/bollinger_band_series.go index 9dbd3b8..0a2b447 100644 --- a/bollinger_band_series.go +++ b/bollinger_band_series.go @@ -6,6 +6,11 @@ import ( "github.com/wcharczuk/go-chart/seq" ) +// Interface Assertions. +var ( + _ Series = (*BollingerBandsSeries)(nil) +) + // BollingerBandsSeries draws bollinger bands for an inner series. // Bollinger bands are defined by two lines, one at SMA+k*stddev, one at SMA-k*stdev. type BollingerBandsSeries struct { diff --git a/continuous_series.go b/continuous_series.go index bca80de..7e5ed2a 100644 --- a/continuous_series.go +++ b/continuous_series.go @@ -2,6 +2,13 @@ package chart import "fmt" +// Interface Assertions. +var ( + _ Series = (*ContinuousSeries)(nil) + _ FirstValuesProvider = (*ContinuousSeries)(nil) + _ LastValuesProvider = (*ContinuousSeries)(nil) +) + // ContinuousSeries represents a line on a chart. type ContinuousSeries struct { Name string @@ -36,6 +43,11 @@ func (cs ContinuousSeries) GetValues(index int) (float64, float64) { return cs.XValues[index], cs.YValues[index] } +// GetFirstValues gets the first x,y values. +func (cs ContinuousSeries) GetFirstValues() (float64, float64) { + return cs.XValues[0], cs.YValues[0] +} + // GetLastValues gets the last x,y values. func (cs ContinuousSeries) GetLastValues() (float64, float64) { return cs.XValues[len(cs.XValues)-1], cs.YValues[len(cs.YValues)-1] diff --git a/ema_series.go b/ema_series.go index ceaec39..44415b5 100644 --- a/ema_series.go +++ b/ema_series.go @@ -7,6 +7,13 @@ const ( DefaultEMAPeriod = 12 ) +// Interface Assertions. +var ( + _ Series = (*EMASeries)(nil) + _ FirstValuesProvider = (*EMASeries)(nil) + _ LastValuesProvider = (*EMASeries)(nil) +) + // EMASeries is a computed series. type EMASeries struct { Name string @@ -66,6 +73,19 @@ func (ema *EMASeries) GetValues(index int) (x, y float64) { return } +// GetFirstValues computes the first moving average value. +func (ema *EMASeries) GetFirstValues() (x, y float64) { + if ema.InnerSeries == nil { + return + } + if len(ema.cache) == 0 { + ema.ensureCachedValues() + } + x, _ = ema.InnerSeries.GetValues(0) + y = ema.cache[0] + return +} + // GetLastValues computes the last moving average value but walking back window size samples, // and recomputing the last moving average chunk. func (ema *EMASeries) GetLastValues() (x, y float64) { diff --git a/first_value_annotation.go b/first_value_annotation.go new file mode 100644 index 0000000..2b214b3 --- /dev/null +++ b/first_value_annotation.go @@ -0,0 +1,37 @@ +package chart + +import "fmt" + +// FirstValueAnnotation returns an annotation series of just the first value of a value provider as an annotation. +func FirstValueAnnotation(innerSeries ValuesProvider, vfs ...ValueFormatter) AnnotationSeries { + var vf ValueFormatter + if len(vfs) > 0 { + vf = vfs[0] + } else if typed, isTyped := innerSeries.(ValueFormatterProvider); isTyped { + _, vf = typed.GetValueFormatters() + } else { + vf = FloatValueFormatter + } + + var firstValue Value2 + if typed, isTyped := innerSeries.(FirstValuesProvider); isTyped { + firstValue.XValue, firstValue.YValue = typed.GetFirstValues() + firstValue.Label = vf(firstValue.YValue) + } else { + firstValue.XValue, firstValue.YValue = innerSeries.GetValues(0) + firstValue.Label = vf(firstValue.YValue) + } + + var seriesName string + var seriesStyle Style + if typed, isTyped := innerSeries.(Series); isTyped { + seriesName = fmt.Sprintf("%s - First Value", typed.GetName()) + seriesStyle = typed.GetStyle() + } + + return AnnotationSeries{ + Name: seriesName, + Style: seriesStyle, + Annotations: []Value2{firstValue}, + } +} diff --git a/first_value_annotation_test.go b/first_value_annotation_test.go new file mode 100644 index 0000000..1f1d4cf --- /dev/null +++ b/first_value_annotation_test.go @@ -0,0 +1,22 @@ +package chart + +import ( + "testing" + + "github.com/blend/go-sdk/assert" +) + +func TestFirstValueAnnotation(t *testing.T) { + assert := assert.New(t) + + series := ContinuousSeries{ + XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0}, + YValues: []float64{5.0, 3.0, 3.0, 2.0, 1.0}, + } + + fva := FirstValueAnnotation(series) + assert.NotEmpty(fva.Annotations) + fvaa := fva.Annotations[0] + assert.Equal(1, fvaa.XValue) + assert.Equal(5, fvaa.YValue) +} diff --git a/last_value_annotation_series.go b/last_value_annotation.go similarity index 100% rename from last_value_annotation_series.go rename to last_value_annotation.go diff --git a/last_value_annotation_test.go b/last_value_annotation_test.go new file mode 100644 index 0000000..3d1931f --- /dev/null +++ b/last_value_annotation_test.go @@ -0,0 +1,22 @@ +package chart + +import ( + "testing" + + "github.com/blend/go-sdk/assert" +) + +func TestLastValueAnnotation(t *testing.T) { + assert := assert.New(t) + + series := ContinuousSeries{ + XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0}, + YValues: []float64{5.0, 3.0, 3.0, 2.0, 1.0}, + } + + lva := LastValueAnnotation(series) + assert.NotEmpty(lva.Annotations) + lvaa := lva.Annotations[0] + assert.Equal(5, lvaa.XValue) + assert.Equal(1, lvaa.YValue) +} diff --git a/linear_regression_series.go b/linear_regression_series.go index 13c3cb0..ebeb98b 100644 --- a/linear_regression_series.go +++ b/linear_regression_series.go @@ -7,6 +7,13 @@ import ( util "github.com/wcharczuk/go-chart/util" ) +// Interface Assertions. +var ( + _ Series = (*LinearRegressionSeries)(nil) + _ FirstValuesProvider = (*LinearRegressionSeries)(nil) + _ LastValuesProvider = (*LinearRegressionSeries)(nil) +) + // LinearRegressionSeries is a series that plots the n-nearest neighbors // linear regression for the values. type LinearRegressionSeries struct { @@ -82,6 +89,19 @@ func (lrs *LinearRegressionSeries) GetValues(index int) (x, y float64) { return } +// GetFirstValues computes the first linear regression value. +func (lrs *LinearRegressionSeries) GetFirstValues() (x, y float64) { + if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 { + return + } + if lrs.m == 0 && lrs.b == 0 { + lrs.computeCoefficients() + } + x, y = lrs.InnerSeries.GetValues(0) + y = (lrs.m * lrs.normalize(x)) + lrs.b + return +} + // GetLastValues computes the last linear regression value. func (lrs *LinearRegressionSeries) GetLastValues() (x, y float64) { if lrs.InnerSeries == nil || lrs.InnerSeries.Len() == 0 { diff --git a/polynomial_regression_series.go b/polynomial_regression_series.go index 506a4cb..bf39af0 100644 --- a/polynomial_regression_series.go +++ b/polynomial_regression_series.go @@ -8,6 +8,13 @@ import ( util "github.com/wcharczuk/go-chart/util" ) +// Interface Assertions. +var ( + _ Series = (*PolynomialRegressionSeries)(nil) + _ FirstValuesProvider = (*PolynomialRegressionSeries)(nil) + _ LastValuesProvider = (*PolynomialRegressionSeries)(nil) +) + // PolynomialRegressionSeries implements a polynomial regression over a given // inner series. type PolynomialRegressionSeries struct { @@ -101,6 +108,23 @@ func (prs *PolynomialRegressionSeries) GetValues(index int) (x, y float64) { return } +// GetFirstValues computes the first poly regression value. +func (prs *PolynomialRegressionSeries) GetFirstValues() (x, y float64) { + if prs.InnerSeries == nil || prs.InnerSeries.Len() == 0 { + return + } + if prs.coeffs == nil { + coeffs, err := prs.computeCoefficients() + if err != nil { + panic(err) + } + prs.coeffs = coeffs + } + x, y = prs.InnerSeries.GetValues(0) + y = prs.apply(x) + return +} + // GetLastValues computes the last poly regression value. func (prs *PolynomialRegressionSeries) GetLastValues() (x, y float64) { if prs.InnerSeries == nil || prs.InnerSeries.Len() == 0 { diff --git a/sma_series.go b/sma_series.go index 396ecaa..7ed75e1 100644 --- a/sma_series.go +++ b/sma_series.go @@ -11,6 +11,13 @@ const ( DefaultSimpleMovingAveragePeriod = 16 ) +// Interface Assertions. +var ( + _ Series = (*SMASeries)(nil) + _ FirstValuesProvider = (*SMASeries)(nil) + _ LastValuesProvider = (*SMASeries)(nil) +) + // SMASeries is a computed series. type SMASeries struct { Name string @@ -63,6 +70,17 @@ func (sma SMASeries) GetValues(index int) (x, y float64) { return } +// GetFirstValues computes the first moving average value. +func (sma SMASeries) GetFirstValues() (x, y float64) { + if sma.InnerSeries == nil || sma.InnerSeries.Len() == 0 { + return + } + px, _ := sma.InnerSeries.GetValues(0) + x = px + y = sma.getAverage(0) + return +} + // GetLastValues computes the last moving average value but walking back window size samples, // and recomputing the last moving average chunk. func (sma SMASeries) GetLastValues() (x, y float64) { diff --git a/time_series.go b/time_series.go index d2636a1..21d39ee 100644 --- a/time_series.go +++ b/time_series.go @@ -7,6 +7,14 @@ import ( util "github.com/wcharczuk/go-chart/util" ) +// Interface Assertions. +var ( + _ Series = (*TimeSeries)(nil) + _ FirstValuesProvider = (*TimeSeries)(nil) + _ LastValuesProvider = (*TimeSeries)(nil) + _ ValueFormatterProvider = (*TimeSeries)(nil) +) + // TimeSeries is a line on a chart. type TimeSeries struct { Name string @@ -33,14 +41,21 @@ func (ts TimeSeries) Len() int { return len(ts.XValues) } -// GetValues gets a value at a given index. +// GetValues gets x, y values at a given index. func (ts TimeSeries) GetValues(index int) (x, y float64) { x = util.Time.ToFloat64(ts.XValues[index]) y = ts.YValues[index] return } -// GetLastValues gets the last value. +// GetFirstValues gets the first values. +func (ts TimeSeries) GetFirstValues() (x, y float64) { + x = util.Time.ToFloat64(ts.XValues[0]) + y = ts.YValues[0] + return +} + +// GetLastValues gets the last values. func (ts TimeSeries) GetLastValues() (x, y float64) { x = util.Time.ToFloat64(ts.XValues[len(ts.XValues)-1]) y = ts.YValues[len(ts.YValues)-1] diff --git a/value_provider.go b/value_provider.go index e93c30d..e3a1a02 100644 --- a/value_provider.go +++ b/value_provider.go @@ -14,6 +14,11 @@ type BoundedValuesProvider interface { GetBoundedValues(index int) (x, y1, y2 float64) } +// FirstValuesProvider is a special type of value provider that can return it's (potentially computed) first value. +type FirstValuesProvider interface { + GetFirstValues() (x, y float64) +} + // LastValuesProvider is a special type of value provider that can return it's (potentially computed) last value. type LastValuesProvider interface { GetLastValues() (x, y float64)