@@ -34,6 +34,7 @@ type Strategy struct {
34
34
MaxAmount fixedpoint.Value `json:"maxAmount"` // max amount to buy or sell per order
35
35
OrderType types.OrderType `json:"orderType"`
36
36
DryRun bool `json:"dryRun"`
37
+ OnStart bool `json:"onStart"` // rebalance on start
37
38
38
39
PositionMap PositionMap `persistence:"positionMap"`
39
40
ProfitStatsMap ProfitStatsMap `persistence:"profitStatsMap"`
@@ -114,6 +115,12 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
114
115
s .activeOrderBook = bbgo .NewActiveOrderBook ("" )
115
116
s .activeOrderBook .BindStream (s .session .UserDataStream )
116
117
118
+ session .UserDataStream .OnStart (func () {
119
+ if s .OnStart {
120
+ s .rebalance (ctx )
121
+ }
122
+ })
123
+
117
124
s .session .MarketDataStream .OnKLineClosed (func (kline types.KLine ) {
118
125
s .rebalance (ctx )
119
126
})
@@ -133,12 +140,17 @@ func (s *Strategy) rebalance(ctx context.Context) {
133
140
log .WithError (err ).Errorf ("failed to cancel orders" )
134
141
}
135
142
136
- submitOrders := s .generateSubmitOrders (ctx )
143
+ submitOrders , err := s .generateSubmitOrders (ctx )
144
+ if err != nil {
145
+ log .WithError (err ).Error ("failed to generate submit orders" )
146
+ return
147
+ }
137
148
for _ , order := range submitOrders {
138
149
log .Infof ("generated submit order: %s" , order .String ())
139
150
}
140
151
141
152
if s .DryRun {
153
+ log .Infof ("dry run, not submitting orders" )
142
154
return
143
155
}
144
156
@@ -150,7 +162,7 @@ func (s *Strategy) rebalance(ctx context.Context) {
150
162
s .activeOrderBook .Add (createdOrders ... )
151
163
}
152
164
153
- func (s * Strategy ) prices (ctx context.Context ) types.ValueMap {
165
+ func (s * Strategy ) prices (ctx context.Context ) ( types.ValueMap , error ) {
154
166
m := make (types.ValueMap )
155
167
for currency := range s .TargetWeights {
156
168
if currency == s .QuoteCurrency {
@@ -160,29 +172,37 @@ func (s *Strategy) prices(ctx context.Context) types.ValueMap {
160
172
161
173
ticker , err := s .session .Exchange .QueryTicker (ctx , currency + s .QuoteCurrency )
162
174
if err != nil {
163
- log .WithError (err ).Error ("failed to query tickers" )
164
- return nil
175
+ return nil , err
165
176
}
166
177
167
- m [currency ] = ticker .Last
178
+ m [currency ] = ticker .Buy . Add ( ticker . Sell ). Div ( fixedpoint . NewFromFloat ( 2.0 ))
168
179
}
169
- return m
180
+ return m , nil
170
181
}
171
182
172
- func (s * Strategy ) quantities () types.ValueMap {
173
- m := make (types.ValueMap )
174
-
183
+ func (s * Strategy ) balances () (types.BalanceMap , error ) {
184
+ m := make (types.BalanceMap )
175
185
balances := s .session .GetAccount ().Balances ()
176
186
for currency := range s .TargetWeights {
177
- m [currency ] = balances [currency ].Total ()
187
+ balance , ok := balances [currency ]
188
+ if ! ok {
189
+ return nil , fmt .Errorf ("no balance for %s" , currency )
190
+ }
191
+ m [currency ] = balance
178
192
}
179
-
180
- return m
193
+ return m , nil
181
194
}
182
195
183
- func (s * Strategy ) generateSubmitOrders (ctx context.Context ) (submitOrders []types.SubmitOrder ) {
184
- prices := s .prices (ctx )
185
- marketValues := prices .Mul (s .quantities ())
196
+ func (s * Strategy ) generateSubmitOrders (ctx context.Context ) (submitOrders []types.SubmitOrder , err error ) {
197
+ prices , err := s .prices (ctx )
198
+ if err != nil {
199
+ return nil , err
200
+ }
201
+ balances , err := s .balances ()
202
+ if err != nil {
203
+ return nil , err
204
+ }
205
+ marketValues := prices .Mul (balanceToTotal (balances ))
186
206
currentWeights := marketValues .Normalize ()
187
207
188
208
for currency , targetWeight := range s .TargetWeights {
@@ -221,8 +241,9 @@ func (s *Strategy) generateSubmitOrders(ctx context.Context) (submitOrders []typ
221
241
quantity = quantity .Abs ()
222
242
}
223
243
224
- if s .MaxAmount .Sign () > 0 {
225
- quantity = bbgo .AdjustQuantityByMaxAmount (quantity , currentPrice , s .MaxAmount )
244
+ maxAmount := s .adjustMaxAmountByBalance (side , currency , currentPrice , balances )
245
+ if maxAmount .Sign () > 0 {
246
+ quantity = bbgo .AdjustQuantityByMaxAmount (quantity , currentPrice , maxAmount )
226
247
log .Infof ("adjust the quantity %v (%s %s @ %v) by max amount %v" ,
227
248
quantity ,
228
249
symbol ,
@@ -241,10 +262,12 @@ func (s *Strategy) generateSubmitOrders(ctx context.Context) (submitOrders []typ
241
262
Price : currentPrice ,
242
263
}
243
264
244
- submitOrders = append (submitOrders , order )
265
+ if ok := s .checkMinimalOrderQuantity (order ); ok {
266
+ submitOrders = append (submitOrders , order )
267
+ }
245
268
}
246
269
247
- return submitOrders
270
+ return submitOrders , err
248
271
}
249
272
250
273
func (s * Strategy ) symbols () (symbols []string ) {
@@ -268,3 +291,44 @@ func (s *Strategy) markets() ([]types.Market, error) {
268
291
}
269
292
return markets , nil
270
293
}
294
+
295
+ func (s * Strategy ) adjustMaxAmountByBalance (side types.SideType , currency string , currentPrice fixedpoint.Value , balances types.BalanceMap ) fixedpoint.Value {
296
+ var maxAmount fixedpoint.Value
297
+
298
+ switch side {
299
+ case types .SideTypeBuy :
300
+ maxAmount = balances [s .QuoteCurrency ].Available
301
+ case types .SideTypeSell :
302
+ maxAmount = balances [currency ].Available .Mul (currentPrice )
303
+ default :
304
+ log .Errorf ("unknown side type: %s" , side )
305
+ return fixedpoint .Zero
306
+ }
307
+
308
+ if s .MaxAmount .Sign () > 0 {
309
+ maxAmount = fixedpoint .Min (s .MaxAmount , maxAmount )
310
+ }
311
+
312
+ return maxAmount
313
+ }
314
+
315
+ func (s * Strategy ) checkMinimalOrderQuantity (order types.SubmitOrder ) bool {
316
+ if order .Quantity .Compare (order .Market .MinQuantity ) < 0 {
317
+ log .Infof ("order quantity is too small: %f < %f" , order .Quantity .Float64 (), order .Market .MinQuantity .Float64 ())
318
+ return false
319
+ }
320
+
321
+ if order .Quantity .Mul (order .Price ).Compare (order .Market .MinNotional ) < 0 {
322
+ log .Infof ("order min notional is too small: %f < %f" , order .Quantity .Mul (order .Price ).Float64 (), order .Market .MinNotional .Float64 ())
323
+ return false
324
+ }
325
+ return true
326
+ }
327
+
328
+ func balanceToTotal (balances types.BalanceMap ) types.ValueMap {
329
+ m := make (types.ValueMap )
330
+ for _ , b := range balances {
331
+ m [b .Currency ] = b .Total ()
332
+ }
333
+ return m
334
+ }
0 commit comments