Merge pull request #1193 from c9s/fix/schedule-quantity-truncation

FIX: [schedule] fix quantity truncation add minBaseBalance config
This commit is contained in:
c9s 2023-06-07 17:30:56 +08:00 committed by GitHub
commit d7cafcb65a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -34,8 +34,11 @@ type Strategy struct {
// Side is the order side type, which can be buy or sell // Side is the order side type, which can be buy or sell
Side types.SideType `json:"side,omitempty"` Side types.SideType `json:"side,omitempty"`
UseLimitOrder bool `json:"useLimitOrder"`
bbgo.QuantityOrAmount bbgo.QuantityOrAmount
MinBaseBalance fixedpoint.Value `json:"minBaseBalance"`
MaxBaseBalance fixedpoint.Value `json:"maxBaseBalance"` MaxBaseBalance fixedpoint.Value `json:"maxBaseBalance"`
BelowMovingAverage *bbgo.MovingAverageSettings `json:"belowMovingAverage,omitempty"` BelowMovingAverage *bbgo.MovingAverageSettings `json:"belowMovingAverage,omitempty"`
@ -161,55 +164,82 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
// calculate quote quantity for balance checking // calculate quote quantity for balance checking
quoteQuantity := quantity.Mul(closePrice) quoteQuantity := quantity.Mul(closePrice)
quoteBalance, ok := session.GetAccount().Balance(s.Market.QuoteCurrency)
if !ok {
log.Errorf("can not place scheduled %s order, quote balance %s is empty", s.Symbol, s.Market.QuoteCurrency)
return
}
baseBalance, ok := session.GetAccount().Balance(s.Market.BaseCurrency)
if !ok {
log.Errorf("can not place scheduled %s order, base balance %s is empty", s.Symbol, s.Market.BaseCurrency)
return
}
totalBase := baseBalance.Total()
// execute orders // execute orders
switch side { switch side {
case types.SideTypeBuy: case types.SideTypeBuy:
if !s.MaxBaseBalance.IsZero() { if !s.MaxBaseBalance.IsZero() {
if baseBalance, ok := session.GetAccount().Balance(s.Market.BaseCurrency); ok { if totalBase.Add(quantity).Compare(s.MaxBaseBalance) >= 0 {
total := baseBalance.Total() quantity = s.MaxBaseBalance.Sub(totalBase)
if total.Add(quantity).Compare(s.MaxBaseBalance) >= 0 { quoteQuantity = quantity.Mul(closePrice)
quantity = s.MaxBaseBalance.Sub(total)
quoteQuantity = quantity.Mul(closePrice)
}
} }
} }
quoteBalance, ok := session.GetAccount().Balance(s.Market.QuoteCurrency) // if min base balance is defined
if !ok { if !s.MinBaseBalance.IsZero() && s.MinBaseBalance.Compare(totalBase) > 0 {
log.Errorf("can not place scheduled %s order, quote balance %s is empty", s.Symbol, s.Market.QuoteCurrency) quantity = fixedpoint.Max(quantity, s.MinBaseBalance.Sub(totalBase))
return quantity = fixedpoint.Max(quantity, s.Market.MinQuantity)
} }
if quoteBalance.Available.Compare(quoteQuantity) < 0 { if quoteBalance.Available.Compare(quoteQuantity) < 0 {
bbgo.Notify("Can not place scheduled %s order: quote balance %s is not enough: %v < %v", s.Symbol, s.Market.QuoteCurrency, quoteBalance.Available, quoteQuantity)
log.Errorf("can not place scheduled %s order: quote balance %s is not enough: %v < %v", s.Symbol, s.Market.QuoteCurrency, quoteBalance.Available, quoteQuantity) log.Errorf("can not place scheduled %s order: quote balance %s is not enough: %v < %v", s.Symbol, s.Market.QuoteCurrency, quoteBalance.Available, quoteQuantity)
return return
} }
case types.SideTypeSell: case types.SideTypeSell:
baseBalance, ok := session.GetAccount().Balance(s.Market.BaseCurrency) quantity = fixedpoint.Min(quantity, baseBalance.Available)
if !ok {
log.Errorf("can not place scheduled %s order, base balance %s is empty", s.Symbol, s.Market.BaseCurrency) // skip sell if we hit the minBaseBalance line
return if !s.MinBaseBalance.IsZero() {
if totalBase.Sub(quantity).Compare(s.MinBaseBalance) < 0 {
return
}
} }
quantity = fixedpoint.Min(quantity, baseBalance.Available)
quoteQuantity = quantity.Mul(closePrice) quoteQuantity = quantity.Mul(closePrice)
} }
// truncate quantity by its step size
quantity = s.Market.TruncateQuantity(quantity)
if s.Market.IsDustQuantity(quantity, closePrice) { if s.Market.IsDustQuantity(quantity, closePrice) {
log.Warnf("%s: quantity %f is too small", s.Symbol, quantity.Float64()) log.Warnf("%s: quantity %f is too small, skip order", s.Symbol, quantity.Float64())
return return
} }
bbgo.Notify("Submitting scheduled %s order with quantity %s at price %s", s.Symbol, quantity.String(), closePrice.String()) submitOrder := types.SubmitOrder{
_, err := s.orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
Symbol: s.Symbol, Symbol: s.Symbol,
Side: side, Side: side,
Type: types.OrderTypeMarket, Type: types.OrderTypeMarket,
Quantity: quantity, Quantity: quantity,
Market: s.Market, Market: s.Market,
}) }
if s.UseLimitOrder {
submitOrder.Type = types.OrderTypeLimit
submitOrder.Price = closePrice
}
if err := s.orderExecutor.GracefulCancel(ctx); err != nil {
log.WithError(err).Errorf("cancel order error")
}
bbgo.Notify("Submitting scheduled %s order with quantity %s at price %s", s.Symbol, quantity.String(), closePrice.String())
_, err := s.orderExecutor.SubmitOrders(ctx, submitOrder)
if err != nil { if err != nil {
bbgo.Notify("Can not place scheduled %s order: submit error %s", s.Symbol, err.Error()) bbgo.Notify("Can not place scheduled %s order: submit error %s", s.Symbol, err.Error())
log.WithError(err).Errorf("can not place scheduled %s order error", s.Symbol) log.WithError(err).Errorf("can not place scheduled %s order error", s.Symbol)