forked from c9s/bbgo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
supertrend.go
177 lines (138 loc) · 4.51 KB
/
supertrend.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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package indicator
import (
"math"
"time"
"github.com/sirupsen/logrus"
"github.com/c9s/bbgo/pkg/datatype/floats"
"github.com/c9s/bbgo/pkg/types"
)
var logst = logrus.WithField("indicator", "supertrend")
//go:generate callbackgen -type Supertrend
type Supertrend struct {
types.SeriesBase
types.IntervalWindow
ATRMultiplier float64 `json:"atrMultiplier"`
AverageTrueRange *ATR
trendPrices floats.Slice
closePrice float64
previousClosePrice float64
uptrendPrice float64
previousUptrendPrice float64
downtrendPrice float64
previousDowntrendPrice float64
trend types.Direction
previousTrend types.Direction
tradeSignal types.Direction
EndTime time.Time
UpdateCallbacks []func(value float64)
}
func (inc *Supertrend) Last() float64 {
return inc.trendPrices.Last()
}
func (inc *Supertrend) Index(i int) float64 {
length := inc.Length()
if length == 0 || length-i-1 < 0 {
return 0
}
return inc.trendPrices[length-i-1]
}
func (inc *Supertrend) Length() int {
return len(inc.trendPrices)
}
func (inc *Supertrend) Update(highPrice, lowPrice, closePrice float64) {
if inc.Window <= 0 {
panic("window must be greater than 0")
}
if inc.AverageTrueRange == nil {
inc.SeriesBase.Series = inc
}
// Start with DirectionUp
if inc.trend != types.DirectionUp && inc.trend != types.DirectionDown {
inc.trend = types.DirectionUp
}
// Update ATR
inc.AverageTrueRange.Update(highPrice, lowPrice, closePrice)
// Update last prices
inc.previousUptrendPrice = inc.uptrendPrice
inc.previousDowntrendPrice = inc.downtrendPrice
inc.previousClosePrice = inc.closePrice
inc.previousTrend = inc.trend
inc.closePrice = closePrice
src := (highPrice + lowPrice) / 2
// Update uptrend
inc.uptrendPrice = src - inc.AverageTrueRange.Last()*inc.ATRMultiplier
if inc.previousClosePrice > inc.previousUptrendPrice {
inc.uptrendPrice = math.Max(inc.uptrendPrice, inc.previousUptrendPrice)
}
// Update downtrend
inc.downtrendPrice = src + inc.AverageTrueRange.Last()*inc.ATRMultiplier
if inc.previousClosePrice < inc.previousDowntrendPrice {
inc.downtrendPrice = math.Min(inc.downtrendPrice, inc.previousDowntrendPrice)
}
// Update trend
if inc.previousTrend == types.DirectionUp && inc.closePrice < inc.previousUptrendPrice {
inc.trend = types.DirectionDown
} else if inc.previousTrend == types.DirectionDown && inc.closePrice > inc.previousDowntrendPrice {
inc.trend = types.DirectionUp
} else {
inc.trend = inc.previousTrend
}
// Update signal
if inc.AverageTrueRange.Last() <= 0 {
inc.tradeSignal = types.DirectionNone
} else if inc.trend == types.DirectionUp && inc.previousTrend == types.DirectionDown {
inc.tradeSignal = types.DirectionUp
} else if inc.trend == types.DirectionDown && inc.previousTrend == types.DirectionUp {
inc.tradeSignal = types.DirectionDown
} else {
inc.tradeSignal = types.DirectionNone
}
// Update trend price
if inc.trend == types.DirectionDown {
inc.trendPrices.Push(inc.downtrendPrice)
} else {
inc.trendPrices.Push(inc.uptrendPrice)
}
logst.Debugf("Update supertrend result: closePrice: %v, uptrendPrice: %v, downtrendPrice: %v, trend: %v,"+
" tradeSignal: %v, AverageTrueRange.Last(): %v", inc.closePrice, inc.uptrendPrice, inc.downtrendPrice,
inc.trend, inc.tradeSignal, inc.AverageTrueRange.Last())
}
func (inc *Supertrend) GetSignal() types.Direction {
return inc.tradeSignal
}
var _ types.SeriesExtend = &Supertrend{}
func (inc *Supertrend) PushK(k types.KLine) {
if inc.EndTime != zeroTime && k.EndTime.Before(inc.EndTime) {
return
}
inc.Update(k.GetHigh().Float64(), k.GetLow().Float64(), k.GetClose().Float64())
inc.EndTime = k.EndTime.Time()
inc.EmitUpdate(inc.Last())
}
func (inc *Supertrend) BindK(target KLineClosedEmitter, symbol string, interval types.Interval) {
target.OnKLineClosed(types.KLineWith(symbol, interval, inc.PushK))
}
func (inc *Supertrend) LoadK(allKLines []types.KLine) {
for _, k := range allKLines {
inc.PushK(k)
}
}
func (inc *Supertrend) CalculateAndUpdate(kLines []types.KLine) {
for _, k := range kLines {
if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) {
continue
}
inc.PushK(k)
}
inc.EmitUpdate(inc.Last())
inc.EndTime = kLines[len(kLines)-1].EndTime.Time()
}
func (inc *Supertrend) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
if inc.Interval != interval {
return
}
inc.CalculateAndUpdate(window)
}
func (inc *Supertrend) Bind(updater KLineWindowUpdater) {
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
}