forked from c9s/bbgo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ewma.go
136 lines (108 loc) · 2.93 KB
/
ewma.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
package indicator
import (
"time"
"github.com/c9s/bbgo/pkg/types"
)
// These numbers should be aligned with bbgo MaxNumOfKLines and MaxNumOfKLinesTruncate
const MaxNumOfEWMA = 5_000
const MaxNumOfEWMATruncateSize = 100
//go:generate callbackgen -type EWMA
type EWMA struct {
types.IntervalWindow
types.SeriesBase
Values types.Float64Slice
EndTime time.Time
updateCallbacks []func(value float64)
}
var _ types.SeriesExtend = &EWMA{}
func (inc *EWMA) Clone() *EWMA {
out := &EWMA{
IntervalWindow: inc.IntervalWindow,
Values: inc.Values[:],
LastOpenTime: inc.LastOpenTime,
}
out.SeriesBase.Series = out
return out
}
func (inc *EWMA) TestUpdate(value float64) *EWMA {
out := inc.Clone()
out.Update(value)
return out
}
func (inc *EWMA) Update(value float64) {
var multiplier = 2.0 / float64(1+inc.Window)
if len(inc.Values) == 0 {
inc.SeriesBase.Series = inc
inc.Values.Push(value)
return
} else if len(inc.Values) > MaxNumOfEWMA {
inc.Values = inc.Values[MaxNumOfEWMATruncateSize-1:]
}
ema := (1-multiplier)*inc.Last() + multiplier*value
inc.Values.Push(ema)
}
func (inc *EWMA) Last() float64 {
if len(inc.Values) == 0 {
return 0
}
return inc.Values[len(inc.Values)-1]
}
func (inc *EWMA) Index(i int) float64 {
if i >= len(inc.Values) {
return 0
}
return inc.Values[len(inc.Values)-1-i]
}
func (inc *EWMA) Length() int {
return len(inc.Values)
}
func (inc *EWMA) CalculateAndUpdate(allKLines []types.KLine) {
if len(inc.Values) == 0 {
for _, k := range allKLines {
inc.PushK(k)
}
inc.EmitUpdate(inc.Last())
} else {
k := allKLines[len(allKLines)-1]
inc.PushK(k)
inc.EmitUpdate(inc.Last())
}
}
func (inc *EWMA) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
if inc.Interval != interval {
return
}
inc.CalculateAndUpdate(window)
}
func (inc *EWMA) Bind(updater KLineWindowUpdater) {
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
}
func (inc *EWMA) BindK(target KLineClosedEmitter, symbol string, interval types.Interval) {
target.OnKLineClosed(types.KLineWith(symbol, interval, inc.PushK))
}
func (inc *EWMA) PushK(k types.KLine) {
if inc.EndTime != zeroTime && k.EndTime.Before(inc.EndTime) {
return
}
inc.Update(k.Close.Float64())
inc.EndTime = k.EndTime.Time()
inc.EmitUpdate(inc.Last())
}
func (inc *EWMA) LoadK(allKLines []types.KLine) {
for _, k := range allKLines {
inc.PushK(k)
}
inc.EmitUpdate(inc.Last())
}
func CalculateKLinesEMA(allKLines []types.KLine, priceF KLinePriceMapper, window int) float64 {
var multiplier = 2.0 / (float64(window) + 1)
return ewma(MapKLinePrice(allKLines, priceF), multiplier)
}
// see https://www.investopedia.com/ask/answers/122314/what-exponential-moving-average-ema-formula-and-how-ema-calculated.asp
func ewma(prices []float64, multiplier float64) float64 {
var end = len(prices) - 1
if end == 0 {
return prices[0]
}
return prices[end]*multiplier + (1-multiplier)*ewma(prices[:end], multiplier)
}