-
Notifications
You must be signed in to change notification settings - Fork 47
/
ema.go
160 lines (127 loc) · 5.35 KB
/
ema.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package indicators
import (
"errors"
"github.com/thetruetrade/gotrade"
)
// An Exponential Moving Average Indicator (Ema), no storage, for use in other indicators
type EmaWithoutStorage struct {
*baseIndicatorWithFloatBounds
// private variables
periodTotal float64
periodCounter int
multiplier float64
previousEma float64
timePeriod int
}
// NewEmaWithoutStorage creates an Exponential Moving Average Indicator (Ema) without storage
func NewEmaWithoutStorage(timePeriod int, valueAvailableAction ValueAvailableActionFloat) (indicator *EmaWithoutStorage, err error) {
// an indicator without storage MUST have a value available action
if valueAvailableAction == nil {
return nil, ErrValueAvailableActionIsNil
}
// the minimum timeperiod for this indicator is 2
if timePeriod < 2 {
return nil, errors.New("timePeriod is less than the minimum (2)")
}
// check the maximum timeperiod
if timePeriod > MaximumLookbackPeriod {
return nil, errors.New("timePeriod is greater than the maximum (100000)")
}
lookback := timePeriod - 1
ind := EmaWithoutStorage{
baseIndicatorWithFloatBounds: newBaseIndicatorWithFloatBounds(lookback, valueAvailableAction),
periodCounter: timePeriod * -1,
multiplier: float64(2.0 / float64(timePeriod+1.0)),
timePeriod: timePeriod,
}
return &ind, err
}
// An Exponential Moving Average Indicator (Ema)
type Ema struct {
*EmaWithoutStorage
selectData gotrade.DOHLCVDataSelectionFunc
// public variables
Data []float64
}
// NewEma creates an Exponential Moving Average Indicator (Ema) for online usage
func NewEma(timePeriod int, selectData gotrade.DOHLCVDataSelectionFunc) (indicator *Ema, err error) {
if selectData == nil {
return nil, ErrDOHLCVDataSelectFuncIsNil
}
ind := Ema{
selectData: selectData,
}
ind.EmaWithoutStorage, err = NewEmaWithoutStorage(timePeriod,
func(dataItem float64, streamBarIndex int) {
ind.Data = append(ind.Data, dataItem)
})
return &ind, err
}
// NewDefaultEma creates an Exponential Moving Average (Ema) for online usage with default parameters
// - timePeriod: 25
func NewDefaultEma() (indicator *Ema, err error) {
timePeriod := 25
return NewEma(timePeriod, gotrade.UseClosePrice)
}
// NewEmaWithSrcLen creates an Exponential Moving Average (Ema) for offline usage
func NewEmaWithSrcLen(sourceLength uint, timePeriod int, selectData gotrade.DOHLCVDataSelectionFunc) (indicator *Ema, err error) {
ind, err := NewEma(timePeriod, selectData)
// only initialise the storage if there is enough source data to require it
if sourceLength-uint(ind.GetLookbackPeriod()) > 1 {
ind.Data = make([]float64, 0, sourceLength-uint(ind.GetLookbackPeriod()))
}
return ind, err
}
// NewDefaultEmaWithSrcLen creates an Exponential Moving Average (Ema) for offline usage with default parameters
func NewDefaultEmaWithSrcLen(sourceLength uint) (indicator *Ema, err error) {
ind, err := NewDefaultEma()
// only initialise the storage if there is enough source data to require it
if sourceLength-uint(ind.GetLookbackPeriod()) > 1 {
ind.Data = make([]float64, 0, sourceLength-uint(ind.GetLookbackPeriod()))
}
return ind, err
}
// NewEmaForStream creates an Exponential Moving Average (Ema) for online usage with a source data stream
func NewEmaForStream(priceStream gotrade.DOHLCVStreamSubscriber, timePeriod int, selectData gotrade.DOHLCVDataSelectionFunc) (indicator *Ema, err error) {
ind, err := NewEma(timePeriod, selectData)
priceStream.AddTickSubscription(ind)
return ind, err
}
// NewDefaultEmaForStream creates an Exponential Moving Average (Ema) for online usage with a source data stream
func NewDefaultEmaForStream(priceStream gotrade.DOHLCVStreamSubscriber) (indicator *Ema, err error) {
ind, err := NewDefaultEma()
priceStream.AddTickSubscription(ind)
return ind, err
}
// NewEmaForStreamWithSrcLen creates an Exponential Moving Average (Ema) for offline usage with a source data stream
func NewEmaForStreamWithSrcLen(sourceLength uint, priceStream gotrade.DOHLCVStreamSubscriber, timePeriod int, selectData gotrade.DOHLCVDataSelectionFunc) (indicator *Ema, err error) {
ind, err := NewEmaWithSrcLen(sourceLength, timePeriod, selectData)
priceStream.AddTickSubscription(ind)
return ind, err
}
// NewDefaultEmaForStreamWithSrcLen creates an Exponential Moving Average (Ema) for offline usage with a source data stream
func NewDefaultEmaForStreamWithSrcLen(sourceLength uint, priceStream gotrade.DOHLCVStreamSubscriber) (indicator *Ema, err error) {
ind, err := NewDefaultEmaWithSrcLen(sourceLength)
priceStream.AddTickSubscription(ind)
return ind, err
}
// ReceiveDOHLCVTick consumes a source data DOHLCV price tick
func (ind *Ema) ReceiveDOHLCVTick(tickData gotrade.DOHLCV, streamBarIndex int) {
var selectedData = ind.selectData(tickData)
ind.ReceiveTick(selectedData, streamBarIndex)
}
func (ind *EmaWithoutStorage) ReceiveTick(tickData float64, streamBarIndex int) {
ind.periodCounter += 1
if ind.periodCounter < 0 {
ind.periodTotal += tickData
} else if ind.periodCounter == 0 {
ind.periodTotal += tickData
result := ind.periodTotal / float64(ind.timePeriod)
ind.previousEma = result
ind.UpdateIndicatorWithNewValue(result, streamBarIndex)
} else if ind.periodCounter > 0 {
result := (tickData-ind.previousEma)*ind.multiplier + ind.previousEma
ind.previousEma = result
ind.UpdateIndicatorWithNewValue(result, streamBarIndex)
}
}