-
-
Notifications
You must be signed in to change notification settings - Fork 270
/
strategy.go
172 lines (137 loc) · 5.67 KB
/
strategy.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
package swing
import (
"context"
"fmt"
"math"
log "github.com/sirupsen/logrus"
"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/types"
)
const ID = "swing"
// Float64Indicator is the indicators (SMA and EWMA) that we want to use are returning float64 data.
type Float64Indicator interface {
Last() float64
}
func init() {
// Register the pointer of the strategy struct,
// so that bbgo knows what struct to be used to unmarshal the configs (YAML or JSON)
// Note: built-in strategies need to imported manually in the bbgo cmd package.
bbgo.RegisterStrategy(ID, &Strategy{})
}
type Strategy struct {
// The notification system will be injected into the strategy automatically.
// This field will be injected automatically since it's a single exchange strategy.
*bbgo.Notifiability
// OrderExecutor is an interface for submitting order.
// This field will be injected automatically since it's a single exchange strategy.
bbgo.OrderExecutor
// if Symbol string field is defined, bbgo will know it's a symbol-based strategy
// The following embedded fields will be injected with the corresponding instances.
// MarketDataStore is a pointer only injection field. public trades, k-lines (candlestick)
// and order book updates are maintained in the market data store.
// This field will be injected automatically since we defined the Symbol field.
*bbgo.MarketDataStore
// StandardIndicatorSet contains the standard indicators of a market (symbol)
// This field will be injected automatically since we defined the Symbol field.
*bbgo.StandardIndicatorSet
// Market stores the configuration of the market, for example, VolumePrecision, PricePrecision, MinLotSize... etc
// This field will be injected automatically since we defined the Symbol field.
types.Market
// These fields will be filled from the config file (it translates YAML to JSON)
Symbol string `json:"symbol"`
// Interval is the interval of the kline channel we want to subscribe,
// the kline event will trigger the strategy to check if we need to submit order.
Interval string `json:"interval"`
// MinChange filters out the k-lines with small changes. so that our strategy will only be triggered
// in specific events.
MinChange float64 `json:"minChange"`
// BaseQuantity is the base quantity of the submit order. for both BUY and SELL, market order will be used.
BaseQuantity float64 `json:"baseQuantity"`
// MovingAverageType is the moving average indicator type that we want to use,
// it could be SMA or EWMA
MovingAverageType string `json:"movingAverageType"`
// MovingAverageInterval is the interval of k-lines for the moving average indicator to calculate,
// it could be "1m", "5m", "1h" and so on. note that, the moving averages are calculated from
// the k-line data we subscribed
MovingAverageInterval types.Interval `json:"movingAverageInterval"`
// MovingAverageWindow is the number of the window size of the moving average indicator.
// The number of k-lines in the window. generally used window sizes are 7, 25 and 99 in the TradingView.
MovingAverageWindow int `json:"movingAverageWindow"`
}
func (s *Strategy) ID() string {
return ID
}
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval})
}
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
var inc Float64Indicator
var iw = types.IntervalWindow{Interval: s.MovingAverageInterval, Window: s.MovingAverageWindow}
switch s.MovingAverageType {
case "SMA":
inc = s.StandardIndicatorSet.SMA(iw)
case "EWMA", "EMA":
inc = s.StandardIndicatorSet.EWMA(iw)
default:
return fmt.Errorf("unsupported moving average type: %s", s.MovingAverageType)
}
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
// skip k-lines from other symbols
if kline.Symbol != s.Symbol {
return
}
movingAveragePrice := inc.Last()
// skip it if it's near zero
if movingAveragePrice < 0.0001 {
return
}
// skip if the change is not above the minChange
if math.Abs(kline.GetChange()) < s.MinChange {
return
}
closePrice := kline.Close
changePercentage := kline.GetChange() / kline.Open
quantity := s.BaseQuantity * (1.0 + math.Abs(changePercentage))
trend := kline.Direction()
switch trend {
case types.DirectionUp:
// if it goes up and it's above the moving average price, then we sell
if closePrice > movingAveragePrice {
s.notify(":chart_with_upwards_trend: closePrice %f is above movingAveragePrice %f, submitting SELL order", closePrice, movingAveragePrice)
_, err := orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
Symbol: s.Symbol,
Market: s.Market,
Side: types.SideTypeSell,
Type: types.OrderTypeMarket,
Quantity: quantity,
})
if err != nil {
log.WithError(err).Error("submit order error")
}
}
case types.DirectionDown:
// if it goes down and it's below the moving average price, then we buy
if closePrice < movingAveragePrice {
s.notify(":chart_with_downwards_trend: closePrice %f is below movingAveragePrice %f, submitting BUY order", closePrice, movingAveragePrice)
_, err := orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
Symbol: s.Symbol,
Market: s.Market,
Side: types.SideTypeBuy,
Type: types.OrderTypeMarket,
Quantity: quantity,
})
if err != nil {
log.WithError(err).Error("submit order error")
}
}
}
})
return nil
}
func (s *Strategy) notify(format string, args ...interface{}) {
if channel, ok := s.RouteSymbol(s.Symbol); ok {
s.NotifyTo(channel, format, args...)
} else {
s.Notify(format, args...)
}
}