From 36c6d396129a96394846c57409ae04c32dcbcd5b Mon Sep 17 00:00:00 2001 From: austin362667 Date: Mon, 13 Dec 2021 23:16:58 +0800 Subject: [PATCH 1/2] bbgo: add session Futures & types: add FuturesExchange --- pkg/bbgo/session.go | 17 +++++++++++++++++ pkg/types/margin.go | 5 ++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/pkg/bbgo/session.go b/pkg/bbgo/session.go index 2d9138bf7..8060906b1 100644 --- a/pkg/bbgo/session.go +++ b/pkg/bbgo/session.go @@ -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), diff --git a/pkg/types/margin.go b/pkg/types/margin.go index 09e30d97e..ede54b700 100644 --- a/pkg/types/margin.go +++ b/pkg/types/margin.go @@ -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) From d3526b2c7127cbec6e875861241af3b0934c6c4d Mon Sep 17 00:00:00 2001 From: austin362667 Date: Mon, 13 Dec 2021 23:19:14 +0800 Subject: [PATCH 2/2] binance: add SubmitFuturesOrder and related conversions --- pkg/exchange/binance/convert.go | 60 +++++++++++++++++++++ pkg/exchange/binance/exchange.go | 91 ++++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+) diff --git a/pkg/exchange/binance/convert.go b/pkg/exchange/binance/convert.go index 7cb1c26e6..eeee9a1ed 100644 --- a/pkg/exchange/binance/convert.go +++ b/pkg/exchange/binance/convert.go @@ -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)) } diff --git a/pkg/exchange/binance/exchange.go b/pkg/exchange/binance/exchange.go index 1864a1020..a115e0e80 100644 --- a/pkg/exchange/binance/exchange.go +++ b/pkg/exchange/binance/exchange.go @@ -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) }