diff --git a/exchanges/binance/binance_cfutures.go b/exchanges/binance/binance_cfutures.go index f42f3111..32c720b9 100644 --- a/exchanges/binance/binance_cfutures.go +++ b/exchanges/binance/binance_cfutures.go @@ -65,6 +65,14 @@ const ( cfuturesNotionalBracket = "/dapi/v1/leverageBracket" cfuturesUsersForceOrders = "/dapi/v1/forceOrders" cfuturesADLQuantile = "/dapi/v1/adlQuantile" + + cfuturesLimit = "LIMIT" + cfuturesMarket = "MARKET" + cfuturesStop = "STOP" + cfuturesTakeProfit = "TAKE_PROFIT" + cfuturesStopMarket = "STOP_MARKET" + cfuturesTakeProfitMarket = "TAKE_PROFIT_MARKET" + cfuturesTrailingStopMarket = "TRAILING_STOP_MARKET" ) // FuturesExchangeInfo stores CoinMarginedFutures, data @@ -973,60 +981,64 @@ func (b *Binance) GetFuturesBasisData(ctx context.Context, pair, contractType, p } // FuturesNewOrder sends a new futures order to the exchange -func (b *Binance) FuturesNewOrder(ctx context.Context, symbol currency.Pair, side, positionSide, orderType, timeInForce, - newClientOrderID, closePosition, workingType, newOrderRespType string, - quantity, price, stopPrice, activationPrice, callbackRate float64, reduceOnly bool) (FuturesOrderPlaceData, error) { +func (b *Binance) FuturesNewOrder(ctx context.Context, x *FuturesNewOrderRequest) ( + FuturesOrderPlaceData, + error, +) { var resp FuturesOrderPlaceData params := url.Values{} - symbolValue, err := b.FormatSymbol(symbol, asset.CoinMarginedFutures) + symbolValue, err := b.FormatSymbol(x.Symbol, asset.CoinMarginedFutures) if err != nil { return resp, err } params.Set("symbol", symbolValue) - params.Set("side", side) - if positionSide != "" { - if !common.StringDataCompare(validPositionSide, positionSide) { + params.Set("side", x.Side) + if x.PositionSide != "" { + if !common.StringDataCompare(validPositionSide, x.PositionSide) { return resp, errors.New("invalid positionSide") } - params.Set("positionSide", positionSide) + params.Set("positionSide", x.PositionSide) } - params.Set("type", orderType) - params.Set("timeInForce", timeInForce) - if reduceOnly { + params.Set("type", x.OrderType) + params.Set("timeInForce", x.TimeInForce) + if x.ReduceOnly { params.Set("reduceOnly", "true") } - if newClientOrderID != "" { - params.Set("newClientOrderID", newClientOrderID) + if x.NewClientOrderID != "" { + params.Set("newClientOrderID", x.NewClientOrderID) } - if closePosition != "" { - params.Set("closePosition", closePosition) + if x.ClosePosition != "" { + params.Set("closePosition", x.ClosePosition) } - if workingType != "" { - if !common.StringDataCompare(validWorkingType, workingType) { + if x.WorkingType != "" { + if !common.StringDataCompare(validWorkingType, x.WorkingType) { return resp, errors.New("invalid workingType") } - params.Set("workingType", workingType) + params.Set("workingType", x.WorkingType) } - if newOrderRespType != "" { - if !common.StringDataCompare(validNewOrderRespType, newOrderRespType) { + if x.NewOrderRespType != "" { + if !common.StringDataCompare(validNewOrderRespType, x.NewOrderRespType) { return resp, errors.New("invalid newOrderRespType") } - params.Set("newOrderRespType", newOrderRespType) + params.Set("newOrderRespType", x.NewOrderRespType) } - if quantity != 0 { - params.Set("quantity", strconv.FormatFloat(quantity, 'f', -1, 64)) + if x.Quantity != 0 { + params.Set("quantity", strconv.FormatFloat(x.Quantity, 'f', -1, 64)) } - if price != 0 { - params.Set("price", strconv.FormatFloat(price, 'f', -1, 64)) + if x.Price != 0 { + params.Set("price", strconv.FormatFloat(x.Price, 'f', -1, 64)) } - if stopPrice != 0 { - params.Set("stopPrice", strconv.FormatFloat(stopPrice, 'f', -1, 64)) + if x.StopPrice != 0 { + params.Set("stopPrice", strconv.FormatFloat(x.StopPrice, 'f', -1, 64)) } - if activationPrice != 0 { - params.Set("activationPrice", strconv.FormatFloat(activationPrice, 'f', -1, 64)) + if x.ActivationPrice != 0 { + params.Set("activationPrice", strconv.FormatFloat(x.ActivationPrice, 'f', -1, 64)) } - if callbackRate != 0 { - params.Set("callbackRate", strconv.FormatFloat(callbackRate, 'f', -1, 64)) + if x.CallbackRate != 0 { + params.Set("callbackRate", strconv.FormatFloat(x.CallbackRate, 'f', -1, 64)) + } + if x.PriceProtect { + params.Set("priceProtect", "TRUE") } return resp, b.SendAuthHTTPRequest(ctx, exchange.RestCoinMargined, http.MethodPost, cfuturesOrder, params, cFuturesOrdersDefaultRate, &resp) } diff --git a/exchanges/binance/binance_test.go b/exchanges/binance/binance_test.go index 38c57884..2b28ff01 100644 --- a/exchanges/binance/binance_test.go +++ b/exchanges/binance/binance_test.go @@ -903,7 +903,17 @@ func TestFuturesNewOrder(t *testing.T) { if !areTestAPIKeysSet() || !canManipulateRealOrders { t.Skip("skipping test: api keys not set or canManipulateRealOrders set to false") } - _, err := b.FuturesNewOrder(context.Background(), currency.NewPairWithDelimiter("BTCUSD", "PERP", "_"), "BUY", "", "LIMIT", "GTC", "", "", "", "", 1, 1, 0, 0, 0, false) + _, err := b.FuturesNewOrder( + context.Background(), + &FuturesNewOrderRequest{ + Symbol: currency.NewPairWithDelimiter("BTCUSD", "PERP", "_"), + Side: "BUY", + OrderType: "LIMIT", + TimeInForce: "GTC", + Quantity: 1, + Price: 1, + }, + ) if err != nil { t.Error(err) } diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index 1089dc9d..0f815316 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -936,27 +936,35 @@ func (b *Binance) SubmitOrder(ctx context.Context, s *order.Submit) (order.Submi var oType string switch s.Type { case order.Limit: - oType = "LIMIT" + oType = cfuturesLimit case order.Market: - oType = "MARKET" + oType = cfuturesMarket case order.Stop: - oType = "STOP" + oType = cfuturesStop case order.TakeProfit: - oType = "TAKE_PROFIT" + oType = cfuturesTakeProfit case order.StopMarket: - oType = "STOP_MARKET" + oType = cfuturesStopMarket case order.TakeProfitMarket: - oType = "TAKE_PROFIT_MARKET" + oType = cfuturesTakeProfitMarket case order.TrailingStop: - oType = "TRAILING_STOP_MARKET" + oType = cfuturesTrailingStopMarket default: return submitOrderResponse, errors.New("invalid type, check api docs for updates") } - o, err := b.FuturesNewOrder(ctx, - s.Pair, reqSide, - "", oType, "GTC", "", - s.ClientOrderID, "", "", - s.Amount, s.Price, 0, 0, 0, s.ReduceOnly) + o, err := b.FuturesNewOrder( + ctx, + &FuturesNewOrderRequest{ + Symbol: s.Pair, + Side: reqSide, + OrderType: oType, + TimeInForce: "GTC", + NewClientOrderID: s.ClientOrderID, + Quantity: s.Amount, + Price: s.Price, + ReduceOnly: s.ReduceOnly, + }, + ) if err != nil { return submitOrderResponse, err } diff --git a/exchanges/binance/cfutures_types.go b/exchanges/binance/cfutures_types.go index 1467d7b4..d54b04b8 100644 --- a/exchanges/binance/cfutures_types.go +++ b/exchanges/binance/cfutures_types.go @@ -3,6 +3,7 @@ package binance import ( "time" + "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) @@ -204,13 +205,34 @@ type BatchCancelOrderData struct { Msg string `json:"msg"` } +// FuturesNewOrderRequest stores all the data needed to submit a +// delivery/coin-margined-futures order. +type FuturesNewOrderRequest struct { + Symbol currency.Pair + Side string + PositionSide string + OrderType string + TimeInForce string + NewClientOrderID string + ClosePosition string + WorkingType string + NewOrderRespType string + Quantity float64 + Price float64 + StopPrice float64 + ActivationPrice float64 + CallbackRate float64 + ReduceOnly bool + PriceProtect bool +} + // FuturesOrderPlaceData stores futures order data type FuturesOrderPlaceData struct { - ClientOrderID string `json:"clientOrderID"` + ClientOrderID string `json:"clientOrderId"` CumQty float64 `json:"cumQty,string"` CumBase float64 `json:"cumBase,string"` - ExecuteQty float64 `json:"executeQty,string"` - OrderID int64 `json:"orderID,string"` + ExecuteQty float64 `json:"executedQty,string"` + OrderID int64 `json:"orderId"` AvgPrice float64 `json:"avgPrice,string"` OrigQty float64 `json:"origQty,string"` Price float64 `json:"price,string"` @@ -218,7 +240,7 @@ type FuturesOrderPlaceData struct { Side string `json:"side"` PositionSide string `json:"positionSide"` Status string `json:"status"` - StopPrice int64 `json:"stopPrice"` + StopPrice float64 `json:"stopPrice,string"` ClosePosition bool `json:"closePosition"` Symbol string `json:"symbol"` Pair string `json:"pair"` diff --git a/exchanges/binance/cfutures_types_test.go b/exchanges/binance/cfutures_types_test.go new file mode 100644 index 00000000..35366a76 --- /dev/null +++ b/exchanges/binance/cfutures_types_test.go @@ -0,0 +1,69 @@ +package binance + +import ( + "encoding/json" + "testing" +) + +func TestFuturesNewOrderRequest_Unmarshal(t *testing.T) { + const inp = ` +{ + "orderId": 18662274680, + "symbol": "ETHUSD_PERP", + "pair": "ETHUSD", + "status": "NEW", + "clientOrderId": "customID", + "price": "4096", + "avgPrice": "2.00", + "origQty": "8", + "executedQty": "4", + "cumQty": "32", + "cumBase": "16", + "timeInForce": "GTC", + "type": "LIMIT", + "reduceOnly": true, + "closePosition": true, + "side": "BUY", + "positionSide": "BOTH", + "stopPrice": "2048", + "workingType": "CONTRACT_PRICE", + "priceProtect": true, + "origType": "MARKET", + "updateTime": 1635931801320, + "activatePrice": "64", + "priceRate": "32" +} +` + + var x FuturesOrderPlaceData + + if err := json.Unmarshal([]byte(inp), &x); err != nil { + t.Error(err) + } + + if x.OrderID != 18662274680 || + x.Symbol != "ETHUSD_PERP" || + x.Pair != "ETHUSD" || + x.Status != "NEW" || + x.ClientOrderID != "customID" || + x.Price != 4096 || + x.AvgPrice != 2 || + x.OrigQty != 8 || + x.ExecuteQty != 4 || + x.CumQty != 32 || + x.CumBase != 16 || + x.TimeInForce != "GTC" || + x.OrderType != cfuturesLimit || + !x.ReduceOnly || + !x.ClosePosition || + x.StopPrice != 2048 || + x.WorkingType != "CONTRACT_PRICE" || + !x.PriceProtect || + x.OrigType != cfuturesMarket || + x.UpdateTime != 1635931801320 || + x.ActivatePrice != 64 || + x.PriceRate != 32 { + // If any of these values isn't set as expected, mark test as failed. + t.Errorf("unmarshaling failed: %v", x) + } +}