2016-07-17 04:42:31 -04:00
|
|
|
package chart
|
|
|
|
|
2017-02-03 14:26:53 -05:00
|
|
|
import "fmt"
|
|
|
|
|
2016-07-17 04:42:31 -04:00
|
|
|
const (
|
2016-07-17 14:10:04 -04:00
|
|
|
// DefaultMACDPeriodPrimary is the long window.
|
|
|
|
DefaultMACDPeriodPrimary = 26
|
|
|
|
// DefaultMACDPeriodSecondary is the short window.
|
|
|
|
DefaultMACDPeriodSecondary = 12
|
|
|
|
// DefaultMACDSignalPeriod is the signal period to compute for the MACD.
|
|
|
|
DefaultMACDSignalPeriod = 9
|
2016-07-17 04:42:31 -04:00
|
|
|
)
|
|
|
|
|
2016-07-17 14:10:04 -04:00
|
|
|
// MACDSeries computes the difference between the MACD line and the MACD Signal line.
|
|
|
|
// It is used in technical analysis and gives a lagging indicator of momentum.
|
2016-07-17 04:42:31 -04:00
|
|
|
type MACDSeries struct {
|
|
|
|
Name string
|
|
|
|
Style Style
|
2016-07-31 19:54:09 -04:00
|
|
|
YAxis YAxisType
|
2016-07-17 04:42:31 -04:00
|
|
|
InnerSeries ValueProvider
|
|
|
|
|
2016-07-17 14:10:04 -04:00
|
|
|
PrimaryPeriod int
|
|
|
|
SecondaryPeriod int
|
|
|
|
SignalPeriod int
|
2016-07-18 21:29:08 -04:00
|
|
|
|
|
|
|
signal *MACDSignalSeries
|
|
|
|
macdl *MACDLineSeries
|
2016-07-17 04:42:31 -04:00
|
|
|
}
|
|
|
|
|
2017-03-05 17:03:11 -05:00
|
|
|
// Validate validates the series.
|
|
|
|
func (macd MACDSeries) Validate() error {
|
|
|
|
var err error
|
|
|
|
if macd.signal != nil {
|
|
|
|
err = macd.signal.Validate()
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if macd.macdl != nil {
|
|
|
|
err = macd.macdl.Validate()
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-07-17 14:10:04 -04:00
|
|
|
// GetPeriods returns the primary and secondary periods.
|
|
|
|
func (macd MACDSeries) GetPeriods() (w1, w2, sig int) {
|
|
|
|
if macd.PrimaryPeriod == 0 {
|
|
|
|
w1 = DefaultMACDPeriodPrimary
|
2016-07-17 04:42:31 -04:00
|
|
|
} else {
|
2016-07-17 14:10:04 -04:00
|
|
|
w1 = macd.PrimaryPeriod
|
2016-07-17 04:42:31 -04:00
|
|
|
}
|
2016-07-17 14:10:04 -04:00
|
|
|
if macd.SecondaryPeriod == 0 {
|
|
|
|
w2 = DefaultMACDPeriodSecondary
|
2016-07-17 04:42:31 -04:00
|
|
|
} else {
|
2016-07-17 14:10:04 -04:00
|
|
|
w2 = macd.SecondaryPeriod
|
|
|
|
}
|
|
|
|
if macd.SignalPeriod == 0 {
|
|
|
|
sig = DefaultMACDSignalPeriod
|
|
|
|
} else {
|
|
|
|
sig = macd.SignalPeriod
|
2016-07-17 04:42:31 -04:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetName returns the name of the time series.
|
|
|
|
func (macd MACDSeries) GetName() string {
|
|
|
|
return macd.Name
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetStyle returns the line style.
|
|
|
|
func (macd MACDSeries) GetStyle() Style {
|
|
|
|
return macd.Style
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetYAxis returns which YAxis the series draws on.
|
2016-07-31 19:54:09 -04:00
|
|
|
func (macd MACDSeries) GetYAxis() YAxisType {
|
2016-07-17 04:42:31 -04:00
|
|
|
return macd.YAxis
|
|
|
|
}
|
|
|
|
|
|
|
|
// Len returns the number of elements in the series.
|
|
|
|
func (macd MACDSeries) Len() int {
|
|
|
|
if macd.InnerSeries == nil {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2016-07-17 21:54:50 -04:00
|
|
|
return macd.InnerSeries.Len()
|
2016-07-17 04:42:31 -04:00
|
|
|
}
|
|
|
|
|
2016-07-17 14:10:04 -04:00
|
|
|
// GetValue gets a value at a given index. For MACD it is the signal value.
|
2016-07-18 21:29:08 -04:00
|
|
|
func (macd *MACDSeries) GetValue(index int) (x float64, y float64) {
|
2016-07-17 04:42:31 -04:00
|
|
|
if macd.InnerSeries == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-07-18 21:29:08 -04:00
|
|
|
if macd.signal == nil || macd.macdl == nil {
|
|
|
|
macd.ensureChildSeries()
|
|
|
|
}
|
|
|
|
|
|
|
|
_, lv := macd.macdl.GetValue(index)
|
|
|
|
_, sv := macd.signal.GetValue(index)
|
2016-07-17 04:42:31 -04:00
|
|
|
|
2016-07-17 21:54:50 -04:00
|
|
|
x, _ = macd.InnerSeries.GetValue(index)
|
2016-07-18 21:29:08 -04:00
|
|
|
y = lv - sv
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (macd *MACDSeries) ensureChildSeries() {
|
|
|
|
w1, w2, sig := macd.GetPeriods()
|
2016-07-17 04:42:31 -04:00
|
|
|
|
2016-07-18 21:29:08 -04:00
|
|
|
macd.signal = &MACDSignalSeries{
|
2016-07-18 18:13:39 -04:00
|
|
|
InnerSeries: macd.InnerSeries,
|
|
|
|
PrimaryPeriod: w1,
|
|
|
|
SecondaryPeriod: w2,
|
|
|
|
SignalPeriod: sig,
|
2016-07-17 14:10:04 -04:00
|
|
|
}
|
|
|
|
|
2016-07-18 21:29:08 -04:00
|
|
|
macd.macdl = &MACDLineSeries{
|
2016-07-17 14:10:04 -04:00
|
|
|
InnerSeries: macd.InnerSeries,
|
|
|
|
PrimaryPeriod: w1,
|
|
|
|
SecondaryPeriod: w2,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// MACDSignalSeries computes the EMA of the MACDLineSeries.
|
|
|
|
type MACDSignalSeries struct {
|
|
|
|
Name string
|
|
|
|
Style Style
|
2016-07-31 19:54:09 -04:00
|
|
|
YAxis YAxisType
|
2016-07-17 14:10:04 -04:00
|
|
|
InnerSeries ValueProvider
|
|
|
|
|
|
|
|
PrimaryPeriod int
|
|
|
|
SecondaryPeriod int
|
|
|
|
SignalPeriod int
|
2016-07-18 21:29:08 -04:00
|
|
|
|
|
|
|
signal *EMASeries
|
2016-07-17 14:10:04 -04:00
|
|
|
}
|
|
|
|
|
2017-03-05 17:03:11 -05:00
|
|
|
// Validate validates the series.
|
|
|
|
func (macds MACDSignalSeries) Validate() error {
|
|
|
|
if macds.signal != nil {
|
|
|
|
return macds.signal.Validate()
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-07-17 14:10:04 -04:00
|
|
|
// 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
|
|
|
|
}
|
2016-07-17 04:42:31 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-07-17 14:10:04 -04:00
|
|
|
// 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.
|
2016-07-31 19:54:09 -04:00
|
|
|
func (macds MACDSignalSeries) GetYAxis() YAxisType {
|
2016-07-17 14:10:04 -04:00
|
|
|
return macds.YAxis
|
|
|
|
}
|
|
|
|
|
|
|
|
// Len returns the number of elements in the series.
|
2016-07-18 21:29:08 -04:00
|
|
|
func (macds *MACDSignalSeries) Len() int {
|
2016-07-17 14:10:04 -04:00
|
|
|
if macds.InnerSeries == nil {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2016-07-17 21:54:50 -04:00
|
|
|
return macds.InnerSeries.Len()
|
2016-07-17 14:10:04 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetValue gets a value at a given index. For MACD it is the signal value.
|
2016-07-18 21:29:08 -04:00
|
|
|
func (macds *MACDSignalSeries) GetValue(index int) (x float64, y float64) {
|
2016-07-17 14:10:04 -04:00
|
|
|
if macds.InnerSeries == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-07-18 21:29:08 -04:00
|
|
|
if macds.signal == nil {
|
|
|
|
macds.ensureSignal()
|
|
|
|
}
|
2016-07-17 21:54:50 -04:00
|
|
|
x, _ = macds.InnerSeries.GetValue(index)
|
2016-07-18 21:29:08 -04:00
|
|
|
_, y = macds.signal.GetValue(index)
|
|
|
|
return
|
|
|
|
}
|
2016-07-17 14:10:04 -04:00
|
|
|
|
2016-07-18 21:29:08 -04:00
|
|
|
func (macds *MACDSignalSeries) ensureSignal() {
|
|
|
|
w1, w2, sig := macds.GetPeriods()
|
|
|
|
|
|
|
|
macds.signal = &EMASeries{
|
|
|
|
InnerSeries: &MACDLineSeries{
|
2016-07-17 14:10:04 -04:00
|
|
|
InnerSeries: macds.InnerSeries,
|
|
|
|
PrimaryPeriod: w1,
|
|
|
|
SecondaryPeriod: w2,
|
|
|
|
},
|
|
|
|
Period: sig,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Render renders the series.
|
2016-07-18 21:29:08 -04:00
|
|
|
func (macds *MACDSignalSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
|
2016-07-21 17:11:27 -04:00
|
|
|
style := macds.Style.InheritFrom(defaults)
|
2016-07-29 19:36:29 -04:00
|
|
|
Draw.LineSeries(r, canvasBox, xrange, yrange, style, macds)
|
2016-07-17 14:10:04 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// MACDLineSeries is a series that computes the inner ema1-ema2 value as a series.
|
|
|
|
type MACDLineSeries struct {
|
|
|
|
Name string
|
|
|
|
Style Style
|
2016-07-31 19:54:09 -04:00
|
|
|
YAxis YAxisType
|
2016-07-17 14:10:04 -04:00
|
|
|
InnerSeries ValueProvider
|
|
|
|
|
|
|
|
PrimaryPeriod int
|
|
|
|
SecondaryPeriod int
|
|
|
|
|
2016-07-18 21:29:08 -04:00
|
|
|
ema1 *EMASeries
|
|
|
|
ema2 *EMASeries
|
|
|
|
|
2016-07-17 14:10:04 -04:00
|
|
|
Sigma float64
|
|
|
|
}
|
|
|
|
|
2017-03-05 17:03:11 -05:00
|
|
|
// Validate validates the series.
|
|
|
|
func (macdl MACDLineSeries) Validate() error {
|
|
|
|
var err error
|
|
|
|
if macdl.ema1 != nil {
|
|
|
|
err = macdl.ema1.Validate()
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if macdl.ema2 != nil {
|
|
|
|
err = macdl.ema2.Validate()
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if macdl.InnerSeries == nil {
|
|
|
|
return fmt.Errorf("MACDLineSeries: must provide an inner series")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-07-17 14:10:04 -04:00
|
|
|
// 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.
|
2016-07-31 19:54:09 -04:00
|
|
|
func (macdl MACDLineSeries) GetYAxis() YAxisType {
|
2016-07-17 14:10:04 -04:00
|
|
|
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.
|
2016-07-18 21:29:08 -04:00
|
|
|
func (macdl *MACDLineSeries) Len() int {
|
2016-07-17 14:10:04 -04:00
|
|
|
if macdl.InnerSeries == nil {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2016-07-17 21:54:50 -04:00
|
|
|
return macdl.InnerSeries.Len()
|
2016-07-17 14:10:04 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetValue gets a value at a given index. For MACD it is the signal value.
|
2016-07-18 21:29:08 -04:00
|
|
|
func (macdl *MACDLineSeries) GetValue(index int) (x float64, y float64) {
|
2016-07-17 14:10:04 -04:00
|
|
|
if macdl.InnerSeries == nil {
|
|
|
|
return
|
|
|
|
}
|
2016-07-18 21:29:08 -04:00
|
|
|
if macdl.ema1 == nil && macdl.ema2 == nil {
|
|
|
|
macdl.ensureEMASeries()
|
|
|
|
}
|
2016-07-17 14:10:04 -04:00
|
|
|
|
2016-07-17 21:54:50 -04:00
|
|
|
x, _ = macdl.InnerSeries.GetValue(index)
|
2016-07-17 14:10:04 -04:00
|
|
|
|
2016-07-18 21:29:08 -04:00
|
|
|
_, emav1 := macdl.ema1.GetValue(index)
|
|
|
|
_, emav2 := macdl.ema2.GetValue(index)
|
|
|
|
|
|
|
|
y = emav2 - emav1
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (macdl *MACDLineSeries) ensureEMASeries() {
|
|
|
|
w1, w2 := macdl.GetPeriods()
|
|
|
|
|
|
|
|
macdl.ema1 = &EMASeries{
|
2016-07-17 14:10:04 -04:00
|
|
|
InnerSeries: macdl.InnerSeries,
|
|
|
|
Period: w1,
|
|
|
|
}
|
2016-07-18 21:29:08 -04:00
|
|
|
macdl.ema2 = &EMASeries{
|
2016-07-17 14:10:04 -04:00
|
|
|
InnerSeries: macdl.InnerSeries,
|
|
|
|
Period: w2,
|
|
|
|
}
|
2016-07-17 04:42:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Render renders the series.
|
2016-07-18 21:29:08 -04:00
|
|
|
func (macdl *MACDLineSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
|
2016-07-21 17:11:27 -04:00
|
|
|
style := macdl.Style.InheritFrom(defaults)
|
2016-07-29 19:36:29 -04:00
|
|
|
Draw.LineSeries(r, canvasBox, xrange, yrange, style, macdl)
|
2016-07-17 04:42:31 -04:00
|
|
|
}
|