Merge pull request #359 from austin362667/submit-order

binance: support submit futures order
This commit is contained in:
Yo-An Lin 2021-12-13 23:30:30 +08:00 committed by GitHub
commit 7fcbc1edcf
4 changed files with 170 additions and 3 deletions

View File

@ -161,6 +161,10 @@ type ExchangeSession struct {
IsolatedMargin bool `json:"isolatedMargin,omitempty" yaml:"isolatedMargin,omitempty"`
IsolatedMarginSymbol string `json:"isolatedMarginSymbol,omitempty" yaml:"isolatedMarginSymbol,omitempty"`
Futures bool `json:"futures,omitempty" yaml:"futures"`
IsolatedFutures bool `json:"isolatedFutures,omitempty" yaml:"isolatedFutures,omitempty"`
IsolatedFuturesSymbol string `json:"isolatedFuturesSymbol,omitempty" yaml:"isolatedFuturesSymbol,omitempty"`
// ---------------------------
// Runtime fields
// ---------------------------
@ -691,6 +695,19 @@ func InitExchangeSession(name string, session *ExchangeSession) error {
}
}
if session.Futures {
futuresExchange, ok := exchange.(types.FuturesExchange)
if !ok {
return fmt.Errorf("exchange %s does not support futures", exchangeName)
}
if session.IsolatedFutures {
futuresExchange.UseIsolatedFutures(session.IsolatedFuturesSymbol)
} else {
futuresExchange.UseFutures()
}
}
session.Name = name
session.Notifiability = Notifiability{
SymbolChannelRouter: NewPatternChannelRouter(nil),

View File

@ -161,6 +161,28 @@ func toLocalOrderType(orderType types.OrderType) (binance.OrderType, error) {
return "", fmt.Errorf("can not convert to local order, order type %s not supported", orderType)
}
func toLocalFuturesOrderType(orderType types.OrderType) (futures.OrderType, error) {
switch orderType {
// case types.OrderTypeLimitMaker:
// return futures.OrderTypeLimitMaker, nil //TODO
case types.OrderTypeLimit:
return futures.OrderTypeLimit, nil
// case types.OrderTypeStopLimit:
// return futures.OrderTypeStopLossLimit, nil //TODO
// case types.OrderTypeStopMarket:
// return futures.OrderTypeStopLoss, nil //TODO
case types.OrderTypeMarket:
return futures.OrderTypeMarket, nil
}
return "", fmt.Errorf("can not convert to local order, order type %s not supported", orderType)
}
func toGlobalOrders(binanceOrders []*binance.Order) (orders []types.Order, err error) {
for _, binanceOrder := range binanceOrders {
order, err := toGlobalOrder(binanceOrder, false)
@ -174,6 +196,19 @@ func toGlobalOrders(binanceOrders []*binance.Order) (orders []types.Order, err e
return orders, err
}
func toGlobalFuturesOrders(futuresOrders []*futures.Order) (orders []types.Order, err error) {
for _, futuresOrder := range futuresOrders {
order, err := toGlobalFuturesOrder(futuresOrder, false)
if err != nil {
return orders, err
}
orders = append(orders, *order)
}
return orders, err
}
func toGlobalOrder(binanceOrder *binance.Order, isMargin bool) (*types.Order, error) {
return &types.Order{
SubmitOrder: types.SubmitOrder{
@ -197,6 +232,31 @@ func toGlobalOrder(binanceOrder *binance.Order, isMargin bool) (*types.Order, er
}, nil
}
func toGlobalFuturesOrder(futuresOrder *futures.Order, isMargin bool) (*types.Order, error) {
return &types.Order{
SubmitOrder: types.SubmitOrder{
ClientOrderID: futuresOrder.ClientOrderID,
Symbol: futuresOrder.Symbol,
Side: toGlobalFuturesSideType(futuresOrder.Side),
Type: toGlobalFuturesOrderType(futuresOrder.Type),
ReduceOnly: futuresOrder.ReduceOnly,
ClosePosition: futuresOrder.ClosePosition,
Quantity: util.MustParseFloat(futuresOrder.OrigQuantity),
Price: util.MustParseFloat(futuresOrder.Price),
TimeInForce: string(futuresOrder.TimeInForce),
},
Exchange: types.ExchangeBinance,
// IsWorking: futuresOrder.IsWorking,
OrderID: uint64(futuresOrder.OrderID),
Status: toGlobalFuturesOrderStatus(futuresOrder.Status),
ExecutedQuantity: util.MustParseFloat(futuresOrder.ExecutedQuantity),
CreationTime: types.Time(millisecondTime(futuresOrder.Time)),
UpdateTime: types.Time(millisecondTime(futuresOrder.UpdateTime)),
IsMargin: isMargin,
// IsIsolated: futuresOrder.IsIsolated,
}, nil
}
func millisecondTime(t int64) time.Time {
return time.Unix(0, t*int64(time.Millisecond))
}

View File

@ -600,6 +600,95 @@ func (e *Exchange) submitMarginOrder(ctx context.Context, order types.SubmitOrde
return createdOrder, err
}
func (e *Exchange) submitFuturesOrder(ctx context.Context, order types.SubmitOrder) (*types.Order, error) {
orderType, err := toLocalFuturesOrderType(order.Type)
if err != nil {
return nil, err
}
req := e.futuresClient.NewCreateOrderService().
Symbol(order.Symbol).
Type(orderType).
Side(futures.SideType(order.Side))
clientOrderID := newSpotClientOrderID(order.ClientOrderID)
if len(clientOrderID) > 0 {
req.NewClientOrderID(clientOrderID)
}
// use response result format
req.NewOrderResponseType(futures.NewOrderRespTypeRESULT)
// if e.IsIsolatedFutures {
// req.IsIsolated(e.IsIsolatedFutures)
// }
if len(order.QuantityString) > 0 {
req.Quantity(order.QuantityString)
} else if order.Market.Symbol != "" {
req.Quantity(order.Market.FormatQuantity(order.Quantity))
} else {
req.Quantity(strconv.FormatFloat(order.Quantity, 'f', 8, 64))
}
// set price field for limit orders
switch order.Type {
case types.OrderTypeStopLimit, types.OrderTypeLimit, types.OrderTypeLimitMaker:
if len(order.PriceString) > 0 {
req.Price(order.PriceString)
} else if order.Market.Symbol != "" {
req.Price(order.Market.FormatPrice(order.Price))
}
}
// set stop price
switch order.Type {
case types.OrderTypeStopLimit, types.OrderTypeStopMarket:
if len(order.StopPriceString) == 0 {
return nil, fmt.Errorf("stop price string can not be empty")
}
req.StopPrice(order.StopPriceString)
}
// could be IOC or FOK
if len(order.TimeInForce) > 0 {
// TODO: check the TimeInForce value
req.TimeInForce(futures.TimeInForceType(order.TimeInForce))
} else {
switch order.Type {
case types.OrderTypeLimit, types.OrderTypeStopLimit:
req.TimeInForce(futures.TimeInForceTypeGTC)
}
}
response, err := req.Do(ctx)
if err != nil {
return nil, err
}
log.Infof("futures order creation response: %+v", response)
createdOrder, err := toGlobalFuturesOrder(&futures.Order{
Symbol: response.Symbol,
OrderID: response.OrderID,
ClientOrderID: response.ClientOrderID,
Price: response.Price,
OrigQuantity: response.OrigQuantity,
ExecutedQuantity: response.ExecutedQuantity,
// CummulativeQuoteQuantity: response.CummulativeQuoteQuantity,
Status: response.Status,
TimeInForce: response.TimeInForce,
Type: response.Type,
Side: response.Side,
// UpdateTime: response.TransactTime,
// Time: response.TransactTime,
// IsIsolated: response.IsIsolated,
}, true)
return createdOrder, err
}
// BBGO is a broker on Binance
const spotBrokerID = "NSUYEBKM"
@ -725,6 +814,8 @@ func (e *Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder
var createdOrder *types.Order
if e.IsMargin {
createdOrder, err = e.submitMarginOrder(ctx, order)
} else if e.IsFutures {
createdOrder, err = e.submitFuturesOrder(ctx, order)
} else {
createdOrder, err = e.submitSpotOrder(ctx, order)
}

View File

@ -4,11 +4,12 @@ import "github.com/c9s/bbgo/pkg/fixedpoint"
type FuturesExchange interface {
UseFutures()
UseIsolatedFutures(symbol string)
GetFuturesSettings() FuturesSettings
}
type FuturesSettings struct {
IsFutures bool
IsFutures bool
IsIsolatedFutures bool
IsolatedFuturesSymbol string
}
@ -25,10 +26,8 @@ func (s *FuturesSettings) UseIsolatedFutures(symbol string) {
s.IsFutures = true
s.IsIsolatedFutures = true
s.IsolatedFuturesSymbol = symbol
}
type MarginExchange interface {
UseMargin()
UseIsolatedMargin(symbol string)