-
-
Notifications
You must be signed in to change notification settings - Fork 296
/
dmi.go
129 lines (107 loc) · 3.62 KB
/
dmi.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
package indicator
import (
"math"
"github.com/c9s/bbgo/pkg/types"
)
// Refer: https://www.investopedia.com/terms/d/dmi.asp
// Refer: https://github.com/twopirllc/pandas-ta/blob/main/pandas_ta/trend/adx.py
//
// Directional Movement Index
//
// The Directional Movement Index (DMI) is a technical analysis indicator that is used to identify the direction and strength of a trend
// in a security's price. It was developed by J. Welles Wilder and is based on the concept of the +DI and -DI lines, which measure the strength
// of upward and downward price movements, respectively. The DMI is calculated by taking the difference between the +DI and -DI lines, and then
// smoothing the result using a moving average. This resulting line is called the Average Directional Index (ADX), and is used to identify whether
// a security is trending or not. If the ADX is above a certain threshold, typically 20, it indicates that the security is in a strong trend,
// and if it is below that threshold it indicates that the security is in a sideways or choppy market. The DMI can be used by traders to confirm
// the direction and strength of a trend, or to identify potential entry and exit points for trades.
//go:generate callbackgen -type DMI
type DMI struct {
types.IntervalWindow
ADXSmoothing int
atr *ATR
DMP types.UpdatableSeriesExtend
DMN types.UpdatableSeriesExtend
DIPlus *types.Queue
DIMinus *types.Queue
ADX types.UpdatableSeriesExtend
PrevHigh, PrevLow float64
updateCallbacks []func(diplus, diminus, adx float64)
}
func (inc *DMI) Update(high, low, cloze float64) {
if inc.DMP == nil || inc.DMN == nil {
inc.DMP = &RMA{IntervalWindow: inc.IntervalWindow, Adjust: true}
inc.DMN = &RMA{IntervalWindow: inc.IntervalWindow, Adjust: true}
inc.ADX = &RMA{IntervalWindow: types.IntervalWindow{Window: inc.ADXSmoothing}, Adjust: true}
}
if inc.atr == nil {
inc.atr = &ATR{IntervalWindow: inc.IntervalWindow}
inc.atr.Update(high, low, cloze)
inc.PrevHigh = high
inc.PrevLow = low
inc.DIPlus = types.NewQueue(500)
inc.DIMinus = types.NewQueue(500)
return
}
inc.atr.Update(high, low, cloze)
up := high - inc.PrevHigh
dn := inc.PrevLow - low
inc.PrevHigh = high
inc.PrevLow = low
pos := 0.0
if up > dn && up > 0. {
pos = up
}
neg := 0.0
if dn > up && dn > 0. {
neg = dn
}
inc.DMP.Update(pos)
inc.DMN.Update(neg)
if inc.atr.Length() < inc.Window {
return
}
k := 100. / inc.atr.Last()
dmp := inc.DMP.Last()
dmn := inc.DMN.Last()
inc.DIPlus.Update(k * dmp)
inc.DIMinus.Update(k * dmn)
dx := 100. * math.Abs(dmp-dmn) / (dmp + dmn)
inc.ADX.Update(dx)
}
func (inc *DMI) GetDIPlus() types.SeriesExtend {
return inc.DIPlus
}
func (inc *DMI) GetDIMinus() types.SeriesExtend {
return inc.DIMinus
}
func (inc *DMI) GetADX() types.SeriesExtend {
return inc.ADX
}
func (inc *DMI) Length() int {
return inc.ADX.Length()
}
func (inc *DMI) PushK(k types.KLine) {
inc.Update(k.High.Float64(), k.Low.Float64(), k.Close.Float64())
}
func (inc *DMI) CalculateAndUpdate(allKLines []types.KLine) {
last := allKLines[len(allKLines)-1]
if inc.ADX == nil {
for _, k := range allKLines {
inc.PushK(k)
inc.EmitUpdate(inc.DIPlus.Last(), inc.DIMinus.Last(), inc.ADX.Last())
}
} else {
inc.PushK(last)
inc.EmitUpdate(inc.DIPlus.Last(), inc.DIMinus.Last(), inc.ADX.Last())
}
}
func (inc *DMI) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
if inc.Interval != interval {
return
}
inc.CalculateAndUpdate(window)
}
func (inc *DMI) Bind(updater KLineWindowUpdater) {
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
}