-
Notifications
You must be signed in to change notification settings - Fork 0
/
rma.go
132 lines (108 loc) · 3.19 KB
/
rma.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
package indicator
import (
"time"
"github.com/c9s/bbgo/pkg/datatype/floats"
"github.com/c9s/bbgo/pkg/types"
)
const MaxNumOfRMA = 1000
const MaxNumOfRMATruncateSize = 500
// Running Moving Average
// Refer: https://github.com/twopirllc/pandas-ta/blob/main/pandas_ta/overlap/rma.py#L5
// Refer: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.ewm.html#pandas-dataframe-ewm
//
// The Running Moving Average (RMA) is a technical analysis indicator that is used to smooth price data and reduce the lag associated
// with traditional moving averages. It is calculated by taking the weighted moving average of the input data, with the weighting factors
// determined by the length of the moving average. The resulting average is then plotted on the price chart as a line, which can be used to
// make predictions about future price movements. The RMA is typically more responsive to changes in the underlying data than a simple
// moving average, but may be less reliable in trending markets. It is often used in conjunction with other technical analysis indicators
// to confirm signals or provide additional information about the security's price.
//go:generate callbackgen -type RMA
type RMA struct {
types.SeriesBase
types.IntervalWindow
Values floats.Slice
EndTime time.Time
counter int
Adjust bool
tmp float64
sum float64
updateCallbacks []func(value float64)
}
func (inc *RMA) Clone() types.UpdatableSeriesExtend {
out := &RMA{
IntervalWindow: inc.IntervalWindow,
Values: inc.Values[:],
counter: inc.counter,
Adjust: inc.Adjust,
tmp: inc.tmp,
sum: inc.sum,
EndTime: inc.EndTime,
}
out.SeriesBase.Series = out
return out
}
func (inc *RMA) Update(x float64) {
lambda := 1 / float64(inc.Window)
if inc.counter == 0 {
inc.SeriesBase.Series = inc
inc.sum = 1
inc.tmp = x
} else {
if inc.Adjust {
inc.sum = inc.sum*(1-lambda) + 1
inc.tmp = inc.tmp + (x-inc.tmp)/inc.sum
} else {
inc.tmp = inc.tmp*(1-lambda) + x*lambda
}
}
inc.counter++
if inc.counter < inc.Window {
inc.Values.Push(0)
return
}
inc.Values.Push(inc.tmp)
if len(inc.Values) > MaxNumOfRMA {
inc.Values = inc.Values[MaxNumOfRMATruncateSize-1:]
}
}
func (inc *RMA) Last() float64 {
return inc.Values.Last()
}
func (inc *RMA) Index(i int) float64 {
length := len(inc.Values)
if length == 0 || length-i-1 < 0 {
return 0
}
return inc.Values[length-i-1]
}
func (inc *RMA) Length() int {
return len(inc.Values)
}
var _ types.SeriesExtend = &RMA{}
func (inc *RMA) PushK(k types.KLine) {
inc.Update(k.Close.Float64())
inc.EndTime = k.EndTime.Time()
}
func (inc *RMA) CalculateAndUpdate(kLines []types.KLine) {
last := kLines[len(kLines)-1]
if len(inc.Values) == 0 {
for _, k := range kLines {
if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) {
continue
}
inc.PushK(k)
}
} else {
inc.PushK(last)
}
inc.EmitUpdate(inc.Last())
}
func (inc *RMA) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
if inc.Interval != interval {
return
}
inc.CalculateAndUpdate(window)
}
func (inc *RMA) Bind(updater KLineWindowUpdater) {
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
}