more iterating on macd, need to get a better handle on how to test.
This commit is contained in:
parent
7858457772
commit
32dd907bf8
9 changed files with 464 additions and 246 deletions
|
@ -37,7 +37,7 @@ func (bbs BollingerBandsSeries) GetWindowSize(defaults ...int) int {
|
||||||
if len(defaults) > 0 {
|
if len(defaults) > 0 {
|
||||||
return defaults[0]
|
return defaults[0]
|
||||||
}
|
}
|
||||||
return DefaultMovingAverageWindowSize
|
return DefaultSimpleMovingAveragePeriod
|
||||||
}
|
}
|
||||||
return bbs.WindowSize
|
return bbs.WindowSize
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,6 @@ func TestBollingerBandLastValue(t *testing.T) {
|
||||||
|
|
||||||
x, y1, y2 := bbs.GetLastBoundedValue()
|
x, y1, y2 := bbs.GetLastBoundedValue()
|
||||||
assert.Equal(100.0, x)
|
assert.Equal(100.0, x)
|
||||||
assert.Equal(100, math.Floor(y1))
|
assert.Equal(101, math.Floor(y1))
|
||||||
assert.Equal(95, math.Floor(y2))
|
assert.Equal(83, math.Floor(y2))
|
||||||
}
|
}
|
||||||
|
|
98
ema_series.go
Normal file
98
ema_series.go
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultEMASigma is the default exponential smoothing factor.
|
||||||
|
DefaultEMASigma = 0.25
|
||||||
|
)
|
||||||
|
|
||||||
|
// EMASeries is a computed series.
|
||||||
|
type EMASeries struct {
|
||||||
|
Name string
|
||||||
|
Style Style
|
||||||
|
YAxis YAxisType
|
||||||
|
|
||||||
|
// Sigma is the 'smoothing factor' parameter.
|
||||||
|
Sigma float64
|
||||||
|
Period int
|
||||||
|
InnerSeries ValueProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetName returns the name of the time series.
|
||||||
|
func (ema EMASeries) GetName() string {
|
||||||
|
return ema.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStyle returns the line style.
|
||||||
|
func (ema EMASeries) GetStyle() Style {
|
||||||
|
return ema.Style
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetYAxis returns which YAxis the series draws on.
|
||||||
|
func (ema EMASeries) GetYAxis() YAxisType {
|
||||||
|
return ema.YAxis
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPeriod returns the window size.
|
||||||
|
func (ema EMASeries) GetPeriod(defaults ...int) int {
|
||||||
|
if ema.Period == 0 {
|
||||||
|
if len(defaults) > 0 {
|
||||||
|
return defaults[0]
|
||||||
|
}
|
||||||
|
return ema.InnerSeries.Len()
|
||||||
|
}
|
||||||
|
return ema.Period
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of elements in the series.
|
||||||
|
func (ema EMASeries) Len() int {
|
||||||
|
return ema.InnerSeries.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValue gets a value at a given index.
|
||||||
|
func (ema EMASeries) GetValue(index int) (x float64, y float64) {
|
||||||
|
if ema.InnerSeries == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vx, _ := ema.InnerSeries.GetValue(index)
|
||||||
|
x = vx
|
||||||
|
y = ema.compute(ema.GetPeriod(), index)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLastValue computes the last moving average value but walking back window size samples,
|
||||||
|
// and recomputing the last moving average chunk.
|
||||||
|
func (ema EMASeries) GetLastValue() (x float64, y float64) {
|
||||||
|
if ema.InnerSeries == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lastIndex := ema.InnerSeries.Len() - 1
|
||||||
|
x, _ = ema.InnerSeries.GetValue(lastIndex)
|
||||||
|
y = ema.compute(ema.GetPeriod(), lastIndex)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ema EMASeries) compute(period, index int) float64 {
|
||||||
|
_, v := ema.InnerSeries.GetValue(index)
|
||||||
|
if period == 1 || index == 0 {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
sig := ema.GetSigma()
|
||||||
|
return sig*v + ((1.0 - sig) * ema.compute(period-1, index-1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render renders the series.
|
||||||
|
func (ema EMASeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
|
||||||
|
style := ema.Style.WithDefaultsFrom(defaults)
|
||||||
|
DrawLineSeries(r, canvasBox, xrange, yrange, style, ema)
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"github.com/blendlabs/go-assert"
|
"github.com/blendlabs/go-assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestExponentialMovingAverageSeries(t *testing.T) {
|
func TestEMASeries(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
mockSeries := mockValueProvider{
|
mockSeries := mockValueProvider{
|
||||||
|
@ -16,7 +16,7 @@ func TestExponentialMovingAverageSeries(t *testing.T) {
|
||||||
}
|
}
|
||||||
assert.Equal(10, mockSeries.Len())
|
assert.Equal(10, mockSeries.Len())
|
||||||
|
|
||||||
mas := &ExponentialMovingAverageSeries{
|
mas := &EMASeries{
|
||||||
InnerSeries: mockSeries,
|
InnerSeries: mockSeries,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,94 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
seriesLength := mas.InnerSeries.Len()
|
|
||||||
for index := 0; index < seriesLength; index++ {
|
|
||||||
x, _ = mas.GetValue(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
y = mas.valueBuffer[seriesLength-1]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
287
macd_series.go
287
macd_series.go
|
@ -1,37 +1,45 @@
|
||||||
package chart
|
package chart
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// DefaultMACDWindowPrimary is the long window.
|
// DefaultMACDPeriodPrimary is the long window.
|
||||||
DefaultMACDWindowPrimary = 26
|
DefaultMACDPeriodPrimary = 26
|
||||||
// DefaultMACDWindowSecondary is the short window.
|
// DefaultMACDPeriodSecondary is the short window.
|
||||||
DefaultMACDWindowSecondary = 12
|
DefaultMACDPeriodSecondary = 12
|
||||||
|
// DefaultMACDSignalPeriod is the signal period to compute for the MACD.
|
||||||
|
DefaultMACDSignalPeriod = 9
|
||||||
)
|
)
|
||||||
|
|
||||||
// MACDSeries (or Moving Average Convergence Divergence) is a special type of series that
|
// MACDSeries computes the difference between the MACD line and the MACD Signal line.
|
||||||
// computes the difference between two different EMA values for a given index, as denoted by WindowPrimary(26) and WindowSecondary(12).
|
// It is used in technical analysis and gives a lagging indicator of momentum.
|
||||||
type MACDSeries struct {
|
type MACDSeries struct {
|
||||||
Name string
|
Name string
|
||||||
Style Style
|
Style Style
|
||||||
YAxis YAxisType
|
YAxis YAxisType
|
||||||
InnerSeries ValueProvider
|
InnerSeries ValueProvider
|
||||||
|
|
||||||
WindowPrimary int
|
PrimaryPeriod int
|
||||||
WindowSecondary int
|
SecondaryPeriod int
|
||||||
|
SignalPeriod int
|
||||||
|
|
||||||
Sigma float64
|
Sigma float64
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetWindows returns the primary and secondary window sizes.
|
// GetPeriods returns the primary and secondary periods.
|
||||||
func (macd MACDSeries) GetWindows() (w1, w2 int) {
|
func (macd MACDSeries) GetPeriods() (w1, w2, sig int) {
|
||||||
if macd.WindowPrimary == 0 {
|
if macd.PrimaryPeriod == 0 {
|
||||||
w1 = DefaultMACDWindowPrimary
|
w1 = DefaultMACDPeriodPrimary
|
||||||
} else {
|
} else {
|
||||||
w1 = macd.WindowPrimary
|
w1 = macd.PrimaryPeriod
|
||||||
}
|
}
|
||||||
if macd.WindowSecondary == 0 {
|
if macd.SecondaryPeriod == 0 {
|
||||||
w2 = DefaultMACDWindowSecondary
|
w2 = DefaultMACDPeriodSecondary
|
||||||
} else {
|
} else {
|
||||||
w2 = macd.WindowSecondary
|
w2 = macd.SecondaryPeriod
|
||||||
|
}
|
||||||
|
if macd.SignalPeriod == 0 {
|
||||||
|
sig = DefaultMACDSignalPeriod
|
||||||
|
} else {
|
||||||
|
sig = macd.SignalPeriod
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -42,7 +50,7 @@ func (macd MACDSeries) GetSigma(defaults ...float64) float64 {
|
||||||
if len(defaults) > 0 {
|
if len(defaults) > 0 {
|
||||||
return defaults[0]
|
return defaults[0]
|
||||||
}
|
}
|
||||||
return DefaultExponentialMovingAverageSigma
|
return DefaultEMASigma
|
||||||
}
|
}
|
||||||
return macd.Sigma
|
return macd.Sigma
|
||||||
}
|
}
|
||||||
|
@ -68,7 +76,7 @@ func (macd MACDSeries) Len() int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
w1, _ := macd.GetWindows()
|
w1, _, _ := macd.GetPeriods()
|
||||||
innerLen := macd.InnerSeries.Len()
|
innerLen := macd.InnerSeries.Len()
|
||||||
if innerLen > w1 {
|
if innerLen > w1 {
|
||||||
return innerLen - w1
|
return innerLen - w1
|
||||||
|
@ -76,35 +84,250 @@ func (macd MACDSeries) Len() int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetValue gets a value at a given index.
|
// GetValue gets a value at a given index. For MACD it is the signal value.
|
||||||
func (macd MACDSeries) GetValue(index int) (x float64, y float64) {
|
func (macd MACDSeries) GetValue(index int) (x float64, y float64) {
|
||||||
if macd.InnerSeries == nil {
|
if macd.InnerSeries == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w1, w2 := macd.GetWindows()
|
w1, w2, sig := macd.GetPeriods()
|
||||||
|
sigma := macd.GetSigma()
|
||||||
|
|
||||||
effectiveIndex := index + w1
|
effectiveIndex := index + w1
|
||||||
x, _ = macd.InnerSeries.GetValue(effectiveIndex)
|
x, _ = macd.InnerSeries.GetValue(effectiveIndex)
|
||||||
|
|
||||||
ema1 := macd.computeEMA(w1, effectiveIndex)
|
signal := EMASeries{
|
||||||
ema2 := macd.computeEMA(w2, effectiveIndex)
|
InnerSeries: MACDLineSeries{
|
||||||
|
InnerSeries: macd.InnerSeries,
|
||||||
|
PrimaryPeriod: w1,
|
||||||
|
SecondaryPeriod: w2,
|
||||||
|
Sigma: sigma,
|
||||||
|
},
|
||||||
|
Sigma: sigma,
|
||||||
|
Period: sig,
|
||||||
|
}
|
||||||
|
|
||||||
|
macdl := MACDLineSeries{
|
||||||
|
InnerSeries: macd.InnerSeries,
|
||||||
|
PrimaryPeriod: w1,
|
||||||
|
SecondaryPeriod: w2,
|
||||||
|
Sigma: sigma,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, sv := signal.GetValue(index)
|
||||||
|
_, lv := macdl.GetValue(index)
|
||||||
|
y = lv - sv
|
||||||
|
|
||||||
y = ema1 - ema2
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (macd MACDSeries) computeEMA(windowSize int, index int) float64 {
|
// MACDSignalSeries computes the EMA of the MACDLineSeries.
|
||||||
_, v := macd.InnerSeries.GetValue(index)
|
type MACDSignalSeries struct {
|
||||||
if windowSize == 1 {
|
Name string
|
||||||
return v
|
Style Style
|
||||||
|
YAxis YAxisType
|
||||||
|
InnerSeries ValueProvider
|
||||||
|
|
||||||
|
PrimaryPeriod int
|
||||||
|
SecondaryPeriod int
|
||||||
|
SignalPeriod int
|
||||||
|
|
||||||
|
Sigma float64
|
||||||
}
|
}
|
||||||
sig := macd.GetSigma()
|
|
||||||
return sig*v + ((1.0 - sig) * macd.computeEMA(windowSize-1, index-1))
|
// GetPeriods returns the primary and secondary periods.
|
||||||
|
func (macds MACDSignalSeries) GetPeriods() (w1, w2, sig int) {
|
||||||
|
if macds.PrimaryPeriod == 0 {
|
||||||
|
w1 = DefaultMACDPeriodPrimary
|
||||||
|
} else {
|
||||||
|
w1 = macds.PrimaryPeriod
|
||||||
|
}
|
||||||
|
if macds.SecondaryPeriod == 0 {
|
||||||
|
w2 = DefaultMACDPeriodSecondary
|
||||||
|
} else {
|
||||||
|
w2 = macds.SecondaryPeriod
|
||||||
|
}
|
||||||
|
if macds.SignalPeriod == 0 {
|
||||||
|
sig = DefaultMACDSignalPeriod
|
||||||
|
} else {
|
||||||
|
sig = macds.SignalPeriod
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStyle returns the line style.
|
||||||
|
func (macds MACDSignalSeries) GetStyle() Style {
|
||||||
|
return macds.Style
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetYAxis returns which YAxis the series draws on.
|
||||||
|
func (macds MACDSignalSeries) GetYAxis() YAxisType {
|
||||||
|
return macds.YAxis
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of elements in the series.
|
||||||
|
func (macds MACDSignalSeries) Len() int {
|
||||||
|
if macds.InnerSeries == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
w1, _, _ := macds.GetPeriods()
|
||||||
|
innerLen := macds.InnerSeries.Len()
|
||||||
|
if innerLen > w1 {
|
||||||
|
return innerLen - w1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValue gets a value at a given index. For MACD it is the signal value.
|
||||||
|
func (macds MACDSignalSeries) GetValue(index int) (x float64, y float64) {
|
||||||
|
if macds.InnerSeries == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w1, w2, sig := macds.GetPeriods()
|
||||||
|
sigma := macds.GetSigma()
|
||||||
|
|
||||||
|
effectiveIndex := index + w1
|
||||||
|
x, _ = macds.InnerSeries.GetValue(effectiveIndex)
|
||||||
|
|
||||||
|
signal := EMASeries{
|
||||||
|
InnerSeries: MACDLineSeries{
|
||||||
|
InnerSeries: macds.InnerSeries,
|
||||||
|
PrimaryPeriod: w1,
|
||||||
|
SecondaryPeriod: w2,
|
||||||
|
Sigma: sigma,
|
||||||
|
},
|
||||||
|
Sigma: sigma,
|
||||||
|
Period: sig,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, y = signal.GetValue(index)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render renders the series.
|
// Render renders the series.
|
||||||
func (macd MACDSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
|
func (macds MACDSignalSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
|
||||||
style := macd.Style.WithDefaultsFrom(defaults)
|
style := macds.Style.WithDefaultsFrom(defaults)
|
||||||
DrawLineSeries(r, canvasBox, xrange, yrange, style, macd)
|
DrawLineSeries(r, canvasBox, xrange, yrange, style, macds)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MACDLineSeries is a series that computes the inner ema1-ema2 value as a series.
|
||||||
|
type MACDLineSeries struct {
|
||||||
|
Name string
|
||||||
|
Style Style
|
||||||
|
YAxis YAxisType
|
||||||
|
InnerSeries ValueProvider
|
||||||
|
|
||||||
|
PrimaryPeriod int
|
||||||
|
SecondaryPeriod int
|
||||||
|
|
||||||
|
Sigma float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetName returns the name of the time series.
|
||||||
|
func (macdl MACDLineSeries) GetName() string {
|
||||||
|
return macdl.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStyle returns the line style.
|
||||||
|
func (macdl MACDLineSeries) GetStyle() Style {
|
||||||
|
return macdl.Style
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetYAxis returns which YAxis the series draws on.
|
||||||
|
func (macdl MACDLineSeries) GetYAxis() YAxisType {
|
||||||
|
return macdl.YAxis
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPeriods returns the primary and secondary periods.
|
||||||
|
func (macdl MACDLineSeries) GetPeriods() (w1, w2 int) {
|
||||||
|
if macdl.PrimaryPeriod == 0 {
|
||||||
|
w1 = DefaultMACDPeriodPrimary
|
||||||
|
} else {
|
||||||
|
w1 = macdl.PrimaryPeriod
|
||||||
|
}
|
||||||
|
if macdl.SecondaryPeriod == 0 {
|
||||||
|
w2 = DefaultMACDPeriodSecondary
|
||||||
|
} else {
|
||||||
|
w2 = macdl.SecondaryPeriod
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of elements in the series.
|
||||||
|
func (macdl MACDLineSeries) Len() int {
|
||||||
|
if macdl.InnerSeries == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
w1, _ := macdl.GetPeriods()
|
||||||
|
innerLen := macdl.InnerSeries.Len()
|
||||||
|
if innerLen > w1 {
|
||||||
|
return innerLen - w1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSigma returns the smoothing factor for the serise.
|
||||||
|
func (macdl MACDLineSeries) GetSigma(defaults ...float64) float64 {
|
||||||
|
if macdl.Sigma == 0 {
|
||||||
|
if len(defaults) > 0 {
|
||||||
|
return defaults[0]
|
||||||
|
}
|
||||||
|
return DefaultEMASigma
|
||||||
|
}
|
||||||
|
return macdl.Sigma
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValue gets a value at a given index. For MACD it is the signal value.
|
||||||
|
func (macdl MACDLineSeries) GetValue(index int) (x float64, y float64) {
|
||||||
|
if macdl.InnerSeries == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w1, w2 := macdl.GetPeriods()
|
||||||
|
|
||||||
|
effectiveIndex := index + w1
|
||||||
|
x, _ = macdl.InnerSeries.GetValue(effectiveIndex)
|
||||||
|
|
||||||
|
ema1 := EMASeries{
|
||||||
|
InnerSeries: macdl.InnerSeries,
|
||||||
|
Period: w1,
|
||||||
|
Sigma: macdl.GetSigma(),
|
||||||
|
}
|
||||||
|
|
||||||
|
ema2 := EMASeries{
|
||||||
|
InnerSeries: macdl.InnerSeries,
|
||||||
|
Period: w2,
|
||||||
|
Sigma: macdl.GetSigma(),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, emav1 := ema1.GetValue(index)
|
||||||
|
_, emav2 := ema2.GetValue(index)
|
||||||
|
|
||||||
|
y = emav1 - emav2
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render renders the series.
|
||||||
|
func (macdl MACDLineSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
|
||||||
|
style := macdl.Style.WithDefaultsFrom(defaults)
|
||||||
|
DrawLineSeries(r, canvasBox, xrange, yrange, style, macdl)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,105 +0,0 @@
|
||||||
package chart
|
|
||||||
|
|
||||||
const (
|
|
||||||
// DefaultMovingAverageWindowSize is the default number of values to average.
|
|
||||||
DefaultMovingAverageWindowSize = 5
|
|
||||||
)
|
|
||||||
|
|
||||||
// SimpleMovingAverageSeries is a computed series.
|
|
||||||
type SimpleMovingAverageSeries struct {
|
|
||||||
Name string
|
|
||||||
Style Style
|
|
||||||
YAxis YAxisType
|
|
||||||
|
|
||||||
WindowSize int
|
|
||||||
InnerSeries ValueProvider
|
|
||||||
|
|
||||||
valueBuffer *RingBuffer
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name of the time series.
|
|
||||||
func (mas SimpleMovingAverageSeries) GetName() string {
|
|
||||||
return mas.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetStyle returns the line style.
|
|
||||||
func (mas SimpleMovingAverageSeries) GetStyle() Style {
|
|
||||||
return mas.Style
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetYAxis returns which YAxis the series draws on.
|
|
||||||
func (mas SimpleMovingAverageSeries) GetYAxis() YAxisType {
|
|
||||||
return mas.YAxis
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the number of elements in the series.
|
|
||||||
func (mas *SimpleMovingAverageSeries) Len() int {
|
|
||||||
return mas.InnerSeries.Len()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetValue gets a value at a given index.
|
|
||||||
func (mas *SimpleMovingAverageSeries) GetValue(index int) (x float64, y float64) {
|
|
||||||
if mas.InnerSeries == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if mas.valueBuffer == nil || index == 0 {
|
|
||||||
mas.valueBuffer = NewRingBufferWithCapacity(mas.GetWindowSize())
|
|
||||||
}
|
|
||||||
if mas.valueBuffer.Len() >= mas.GetWindowSize() {
|
|
||||||
mas.valueBuffer.Dequeue()
|
|
||||||
}
|
|
||||||
px, py := mas.InnerSeries.GetValue(index)
|
|
||||||
mas.valueBuffer.Enqueue(py)
|
|
||||||
x = px
|
|
||||||
y = mas.getAverage(mas.valueBuffer)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLastValue computes the last moving average value but walking back window size samples,
|
|
||||||
// and recomputing the last moving average chunk.
|
|
||||||
func (mas SimpleMovingAverageSeries) GetLastValue() (x float64, y float64) {
|
|
||||||
if mas.InnerSeries == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
windowSize := mas.GetWindowSize()
|
|
||||||
seriesLength := mas.InnerSeries.Len()
|
|
||||||
startAt := seriesLength - windowSize
|
|
||||||
if startAt < 0 {
|
|
||||||
startAt = 0
|
|
||||||
}
|
|
||||||
vb := NewRingBufferWithCapacity(windowSize)
|
|
||||||
for index := startAt; index < seriesLength; index++ {
|
|
||||||
xn, yn := mas.InnerSeries.GetValue(index)
|
|
||||||
vb.Enqueue(yn)
|
|
||||||
x = xn
|
|
||||||
}
|
|
||||||
y = mas.getAverage(vb)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetWindowSize returns the window size.
|
|
||||||
func (mas SimpleMovingAverageSeries) GetWindowSize(defaults ...int) int {
|
|
||||||
if mas.WindowSize == 0 {
|
|
||||||
if len(defaults) > 0 {
|
|
||||||
return defaults[0]
|
|
||||||
}
|
|
||||||
return DefaultMovingAverageWindowSize
|
|
||||||
}
|
|
||||||
return mas.WindowSize
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mas SimpleMovingAverageSeries) getAverage(valueBuffer *RingBuffer) float64 {
|
|
||||||
var accum float64
|
|
||||||
valueBuffer.Each(func(v interface{}) {
|
|
||||||
if typed, isTyped := v.(float64); isTyped {
|
|
||||||
accum += typed
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return accum / float64(valueBuffer.Len())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render renders the series.
|
|
||||||
func (mas *SimpleMovingAverageSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
|
|
||||||
style := mas.Style.WithDefaultsFrom(defaults)
|
|
||||||
DrawLineSeries(r, canvasBox, xrange, yrange, style, mas)
|
|
||||||
}
|
|
90
sma_series.go
Normal file
90
sma_series.go
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultSimpleMovingAveragePeriod is the default number of values to average.
|
||||||
|
DefaultSimpleMovingAveragePeriod = 16
|
||||||
|
)
|
||||||
|
|
||||||
|
// SMASeries is a computed series.
|
||||||
|
type SMASeries struct {
|
||||||
|
Name string
|
||||||
|
Style Style
|
||||||
|
YAxis YAxisType
|
||||||
|
|
||||||
|
Period int
|
||||||
|
InnerSeries ValueProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetName returns the name of the time series.
|
||||||
|
func (sma SMASeries) GetName() string {
|
||||||
|
return sma.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStyle returns the line style.
|
||||||
|
func (sma SMASeries) GetStyle() Style {
|
||||||
|
return sma.Style
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetYAxis returns which YAxis the series draws on.
|
||||||
|
func (sma SMASeries) GetYAxis() YAxisType {
|
||||||
|
return sma.YAxis
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of elements in the series.
|
||||||
|
func (sma SMASeries) Len() int {
|
||||||
|
return sma.InnerSeries.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValue gets a value at a given index.
|
||||||
|
func (sma SMASeries) GetValue(index int) (x float64, y float64) {
|
||||||
|
if sma.InnerSeries == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
px, _ := sma.InnerSeries.GetValue(index)
|
||||||
|
x = px
|
||||||
|
y = sma.getAverage(index)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLastValue computes the last moving average value but walking back window size samples,
|
||||||
|
// and recomputing the last moving average chunk.
|
||||||
|
func (sma SMASeries) GetLastValue() (x float64, y float64) {
|
||||||
|
if sma.InnerSeries == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
seriesLen := sma.InnerSeries.Len()
|
||||||
|
px, _ := sma.InnerSeries.GetValue(seriesLen - 1)
|
||||||
|
x = px
|
||||||
|
y = sma.getAverage(seriesLen - 1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPeriod returns the window size.
|
||||||
|
func (sma SMASeries) GetPeriod(defaults ...int) int {
|
||||||
|
if sma.Period == 0 {
|
||||||
|
if len(defaults) > 0 {
|
||||||
|
return defaults[0]
|
||||||
|
}
|
||||||
|
return DefaultSimpleMovingAveragePeriod
|
||||||
|
}
|
||||||
|
return sma.Period
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sma SMASeries) getAverage(index int) float64 {
|
||||||
|
period := sma.GetPeriod()
|
||||||
|
floor := MaxInt(0, index-period)
|
||||||
|
var accum float64
|
||||||
|
var count float64
|
||||||
|
for x := index; x >= floor; x-- {
|
||||||
|
_, vy := sma.InnerSeries.GetValue(x)
|
||||||
|
accum += vy
|
||||||
|
count += 1.0
|
||||||
|
}
|
||||||
|
return accum / count
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render renders the series.
|
||||||
|
func (sma *SMASeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
|
||||||
|
style := sma.Style.WithDefaultsFrom(defaults)
|
||||||
|
DrawLineSeries(r, canvasBox, xrange, yrange, style, sma)
|
||||||
|
}
|
|
@ -16,12 +16,18 @@ func (m mockValueProvider) Len() int {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m mockValueProvider) GetValue(index int) (x, y float64) {
|
func (m mockValueProvider) GetValue(index int) (x, y float64) {
|
||||||
|
if index < 0 {
|
||||||
|
panic("negative index at GetValue()")
|
||||||
|
}
|
||||||
|
if index > MinInt(len(m.X), len(m.Y)) {
|
||||||
|
panic("index is outside the length of m.X or m.Y")
|
||||||
|
}
|
||||||
x = m.X[index]
|
x = m.X[index]
|
||||||
y = m.Y[index]
|
y = m.Y[index]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSimpleMovingAverageSeriesGetValue(t *testing.T) {
|
func TestSMASeriesGetValue(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
mockSeries := mockValueProvider{
|
mockSeries := mockValueProvider{
|
||||||
|
@ -30,9 +36,9 @@ func TestSimpleMovingAverageSeriesGetValue(t *testing.T) {
|
||||||
}
|
}
|
||||||
assert.Equal(10, mockSeries.Len())
|
assert.Equal(10, mockSeries.Len())
|
||||||
|
|
||||||
mas := &SimpleMovingAverageSeries{
|
mas := &SMASeries{
|
||||||
InnerSeries: mockSeries,
|
InnerSeries: mockSeries,
|
||||||
WindowSize: 10,
|
Period: 10,
|
||||||
}
|
}
|
||||||
|
|
||||||
var yvalues []float64
|
var yvalues []float64
|
||||||
|
@ -52,7 +58,7 @@ func TestSimpleMovingAverageSeriesGetValue(t *testing.T) {
|
||||||
assert.Equal(6.0, yvalues[8])
|
assert.Equal(6.0, yvalues[8])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSimpleMovingAverageSeriesGetLastValueWindowOverlap(t *testing.T) {
|
func TestSMASeriesGetLastValueWindowOverlap(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
mockSeries := mockValueProvider{
|
mockSeries := mockValueProvider{
|
||||||
|
@ -61,9 +67,9 @@ func TestSimpleMovingAverageSeriesGetLastValueWindowOverlap(t *testing.T) {
|
||||||
}
|
}
|
||||||
assert.Equal(10, mockSeries.Len())
|
assert.Equal(10, mockSeries.Len())
|
||||||
|
|
||||||
mas := &SimpleMovingAverageSeries{
|
mas := &SMASeries{
|
||||||
InnerSeries: mockSeries,
|
InnerSeries: mockSeries,
|
||||||
WindowSize: 15,
|
Period: 15,
|
||||||
}
|
}
|
||||||
|
|
||||||
var yvalues []float64
|
var yvalues []float64
|
||||||
|
@ -78,7 +84,7 @@ func TestSimpleMovingAverageSeriesGetLastValueWindowOverlap(t *testing.T) {
|
||||||
assert.Equal(yvalues[len(yvalues)-1], ly)
|
assert.Equal(yvalues[len(yvalues)-1], ly)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSimpleMovingAverageSeriesGetLastValue(t *testing.T) {
|
func TestSMASeriesGetLastValue(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
mockSeries := mockValueProvider{
|
mockSeries := mockValueProvider{
|
||||||
|
@ -87,9 +93,9 @@ func TestSimpleMovingAverageSeriesGetLastValue(t *testing.T) {
|
||||||
}
|
}
|
||||||
assert.Equal(100, mockSeries.Len())
|
assert.Equal(100, mockSeries.Len())
|
||||||
|
|
||||||
mas := &SimpleMovingAverageSeries{
|
mas := &SMASeries{
|
||||||
InnerSeries: mockSeries,
|
InnerSeries: mockSeries,
|
||||||
WindowSize: 10,
|
Period: 10,
|
||||||
}
|
}
|
||||||
|
|
||||||
var yvalues []float64
|
var yvalues []float64
|
||||||
|
@ -100,6 +106,6 @@ func TestSimpleMovingAverageSeriesGetLastValue(t *testing.T) {
|
||||||
|
|
||||||
lx, ly := mas.GetLastValue()
|
lx, ly := mas.GetLastValue()
|
||||||
assert.Equal(100.0, lx)
|
assert.Equal(100.0, lx)
|
||||||
assert.Equal(5.5, ly)
|
assert.Equal(6, ly)
|
||||||
assert.Equal(yvalues[len(yvalues)-1], ly)
|
assert.Equal(yvalues[len(yvalues)-1], ly)
|
||||||
}
|
}
|
Loading…
Reference in a new issue