exchanges/binance/coinm: fix order submission (#819)

* exchanges/binance: proper arguments order when calling Binance.FuturesNewOrder

* exchanges/binance: adapt FuturesOrderPlaceData (+ unmarshaling) to latest coin/delivery futures API

* exchanges/binance: introduce futuresNewOrderRequest and use it to pass order parameters for coin margined futures

* exchanges/binance: test json unmarshaling of FuturesOrderPlaceData

* exchanges/binance: reorder fields as per docs, include missing fields (also in tests)

* exchanges/binance/coinm: use constants instead of hard coded strings (also fixes linting)

* exchanges/binance/coinm: pass futuresNewOrderRequest by reference

* exchanges/binance/coinm: expose FuturesNewOrderRequest

* exchanges/binance/coinm: do not explicitly assign default values fields of FuturesNewOrderRequest

* exchanges/binance/coinm: document FuturesNewOrderRequest

* exchanges/binance/coinm: expose all fields of FuturesNewOrderRequest

* exchanges/binance/coinm: expose fields of FuturesNewOrderRequest

* exchange/binance/coin: order submission: add support for priceProtect

Co-authored-by: Yordan Miladinov <jordanmiladinov@gmail.bg>
This commit is contained in:
Yordan Miladinov
2021-11-10 01:24:30 +02:00
committed by GitHub
parent da8a9f8372
commit 417b2a77ef
5 changed files with 169 additions and 48 deletions

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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"`

View File

@@ -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)
}
}