mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-30 23:16:52 +00:00
Binance/FTX: spot websocket fixes (#753)
* exchange/binance/websocket: 1. resolve order type by reading the dedicated executionReport field (X) 2. populate average price (computed as advised in docs: Z/z) 3. fee taken is now assigned to order.Detail.Fee (not to order.Detail.Cost) 4. proper order.Detail.Cost and order.Detail.CostAsset * order.Detail: add a new field: FeeAsset * exchange/binance/websocket: wsOrderUpdate: assign FeeAsset * exchange/binance/websocket: wsOrderUpdate: use TransactionTime instead of EventTime (more precise) and populate order.Detail.TargetAmount * exchange/binance/websocket: wsOrderUpdate: use full name for variables (isn't obvious otherwise), do not mix var and :=, order temporary variable by their order in order.Detail * exchange/binance/websocket: order fields of WsOrderUpdateData by the documented/received-over-the-websocket order of fields * exchange/binance/websocket: add explicitly "M" as WsOrderUpdateData.Ignored2, which would otherwise override small "m" * exchange/binance/websocket: check for zero when computing averagePrice * exchange/binance/test: update TestExecutionTypeToOrderStatus and TestWsOrderExecutionReport * exchange/binance/websocket: wsOrderUpdate now reports cost thus far, not the total order cost as that cannot be predicted in the case of partially filled orders * exchange/ftx/websocket: WsOrderDataStore now reports cost thus far, not the total order cost as that cannot be predicted in the case of partially filled orders * exchange/binance/websocket: executionReport now does not set TargetAmount as it is not applicable * exchanges: binance/websocket and ftx/websocket now properly set Date and LastUpdated time fields when reporting a trade * exchange/binance/websocket: check if WsOrderUpdateData.CommissionAsset is populated when assigning feeAsset * exchange/binance/test: fix TestWsOrderExecutionReport
This commit is contained in:
@@ -2266,9 +2266,11 @@ func TestExecutionTypeToOrderStatus(t *testing.T) {
|
||||
}
|
||||
testCases := []TestCases{
|
||||
{Case: "NEW", Result: order.New},
|
||||
{Case: "PARTIALLY_FILLED", Result: order.PartiallyFilled},
|
||||
{Case: "FILLED", Result: order.Filled},
|
||||
{Case: "CANCELED", Result: order.Cancelled},
|
||||
{Case: "PENDING_CANCEL", Result: order.PendingCancel},
|
||||
{Case: "REJECTED", Result: order.Rejected},
|
||||
{Case: "TRADE", Result: order.PartiallyFilled},
|
||||
{Case: "EXPIRED", Result: order.Expired},
|
||||
{Case: "LOL", Result: order.UnknownStatus},
|
||||
}
|
||||
@@ -2491,20 +2493,26 @@ func TestWsOrderExecutionReport(t *testing.T) {
|
||||
payload := []byte(`{"stream":"jTfvpakT2yT0hVIo5gYWVihZhdM2PrBgJUZ5PyfZ4EVpCkx4Uoxk5timcrQc","data":{"e":"executionReport","E":1616627567900,"s":"BTCUSDT","c":"c4wyKsIhoAaittTYlIVLqk","S":"BUY","o":"LIMIT","f":"GTC","q":"0.00028400","p":"52789.10000000","P":"0.00000000","F":"0.00000000","g":-1,"C":"","x":"NEW","X":"NEW","r":"NONE","i":5340845958,"l":"0.00000000","z":"0.00000000","L":"0.00000000","n":"0","N":"BTC","T":1616627567900,"t":-1,"I":11388173160,"w":true,"m":false,"M":false,"O":1616627567900,"Z":"0.00000000","Y":"0.00000000","Q":"0.00000000"}}`)
|
||||
// this is a buy BTC order, normally commission is charged in BTC, vice versa.
|
||||
expRes := order.Detail{
|
||||
Price: 52789.1,
|
||||
Amount: 0.00028400,
|
||||
Exchange: "Binance",
|
||||
ID: "5340845958",
|
||||
ClientOrderID: "c4wyKsIhoAaittTYlIVLqk",
|
||||
Side: order.Buy,
|
||||
Type: order.Limit,
|
||||
Status: order.New,
|
||||
AssetType: asset.Spot,
|
||||
Pair: currency.NewPair(currency.BTC, currency.USDT),
|
||||
RemainingAmount: 0.000284,
|
||||
Date: time.Unix(0, 1616627567900*int64(time.Millisecond)),
|
||||
Cost: 0,
|
||||
CostAsset: currency.BTC,
|
||||
Price: 52789.1,
|
||||
Amount: 0.00028400,
|
||||
AverageExecutedPrice: 0,
|
||||
TargetAmount: 0,
|
||||
ExecutedAmount: 0,
|
||||
RemainingAmount: 0.00028400,
|
||||
Cost: 0,
|
||||
CostAsset: currency.USDT,
|
||||
Fee: 0,
|
||||
FeeAsset: currency.BTC,
|
||||
Exchange: "Binance",
|
||||
ID: "5340845958",
|
||||
ClientOrderID: "c4wyKsIhoAaittTYlIVLqk",
|
||||
Type: order.Limit,
|
||||
Side: order.Buy,
|
||||
Status: order.New,
|
||||
AssetType: asset.Spot,
|
||||
Date: time.Unix(0, 1616627567900*int64(time.Millisecond)),
|
||||
LastUpdated: time.Unix(0, 1616627567900*int64(time.Millisecond)),
|
||||
Pair: currency.NewPair(currency.BTC, currency.USDT),
|
||||
}
|
||||
// empty the channel. otherwise mock_test will fail
|
||||
for len(b.Websocket.DataHandler) > 0 {
|
||||
|
||||
@@ -736,37 +736,38 @@ type wsOrderUpdate struct {
|
||||
|
||||
// WsOrderUpdateData defines websocket account order update data
|
||||
type WsOrderUpdateData struct {
|
||||
ClientOrderID string `json:"c"`
|
||||
EventTime time.Time `json:"E"`
|
||||
IcebergQuantity float64 `json:"F,string"`
|
||||
LastExecutedPrice float64 `json:"L,string"`
|
||||
CommissionAsset string `json:"N"`
|
||||
OrderCreationTime time.Time `json:"O"`
|
||||
StopPrice float64 `json:"P,string"`
|
||||
QuoteOrderQuantity float64 `json:"Q,string"`
|
||||
Side string `json:"S"`
|
||||
TransactionTime time.Time `json:"T"`
|
||||
OrderStatus string `json:"X"`
|
||||
LastQuoteAssetTransactedQuantity float64 `json:"Y,string"`
|
||||
CumulativeQuoteTransactedQuantity float64 `json:"Z,string"`
|
||||
CancelledClientOrderID string `json:"C"`
|
||||
EventType string `json:"e"`
|
||||
EventTime time.Time `json:"E"`
|
||||
Symbol string `json:"s"`
|
||||
ClientOrderID string `json:"c"`
|
||||
Side string `json:"S"`
|
||||
OrderType string `json:"o"`
|
||||
TimeInForce string `json:"f"`
|
||||
Quantity float64 `json:"q,string"`
|
||||
Price float64 `json:"p,string"`
|
||||
StopPrice float64 `json:"P,string"`
|
||||
IcebergQuantity float64 `json:"F,string"`
|
||||
OrderListID int64 `json:"g"`
|
||||
CancelledClientOrderID string `json:"C"`
|
||||
CurrentExecutionType string `json:"x"`
|
||||
OrderStatus string `json:"X"`
|
||||
RejectionReason string `json:"r"`
|
||||
OrderID int64 `json:"i"`
|
||||
LastExecutedQuantity float64 `json:"l,string"`
|
||||
IsMaker bool `json:"m"`
|
||||
Commission float64 `json:"n,string"`
|
||||
OrderType string `json:"o"`
|
||||
Price float64 `json:"p,string"`
|
||||
Quantity float64 `json:"q,string"`
|
||||
RejectionReason string `json:"r"`
|
||||
Symbol string `json:"s"`
|
||||
TradeID int64 `json:"t"`
|
||||
Ignored int64 `json:"I"` // must be ignored explicitly, otherwise it overwrites 'i'
|
||||
IsOnOrderBook bool `json:"w"`
|
||||
CurrentExecutionType string `json:"x"`
|
||||
CumulativeFilledQuantity float64 `json:"z,string"`
|
||||
LastExecutedPrice float64 `json:"L,string"`
|
||||
Commission float64 `json:"n,string"`
|
||||
CommissionAsset string `json:"N"`
|
||||
TransactionTime time.Time `json:"T"`
|
||||
TradeID int64 `json:"t"`
|
||||
Ignored int64 `json:"I"` // Must be ignored explicitly, otherwise it overwrites 'i'.
|
||||
IsOnOrderBook bool `json:"w"`
|
||||
IsMaker bool `json:"m"`
|
||||
Ignored2 bool `json:"M"` // See the comment for "I".
|
||||
OrderCreationTime time.Time `json:"O"`
|
||||
CumulativeQuoteTransactedQuantity float64 `json:"Z,string"`
|
||||
LastQuoteAssetTransactedQuantity float64 `json:"Y,string"`
|
||||
QuoteOrderQuantity float64 `json:"Q,string"`
|
||||
}
|
||||
|
||||
type wsListStatus struct {
|
||||
|
||||
@@ -207,66 +207,68 @@ func (b *Binance) wsHandleData(respRaw []byte) error {
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
var orderID = strconv.FormatInt(data.Data.OrderID, 10)
|
||||
oType, err := order.StringToOrderType(data.Data.OrderType)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
averagePrice := 0.0
|
||||
if data.Data.CumulativeFilledQuantity != 0 {
|
||||
averagePrice = data.Data.CumulativeQuoteTransactedQuantity / data.Data.CumulativeFilledQuantity
|
||||
}
|
||||
var oSide order.Side
|
||||
oSide, err = order.StringToOrderSide(data.Data.Side)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
var oStatus order.Status
|
||||
oStatus, err = stringToOrderStatus(data.Data.CurrentExecutionType)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
if oStatus == order.PartiallyFilled && data.Data.CumulativeFilledQuantity == data.Data.Quantity {
|
||||
oStatus = order.Filled
|
||||
}
|
||||
var p currency.Pair
|
||||
var a asset.Item
|
||||
p, a, err = b.GetRequestFormattedPairAndAssetType(data.Data.Symbol)
|
||||
remainingAmount := data.Data.Quantity - data.Data.CumulativeFilledQuantity
|
||||
pair, assetType, err := b.GetRequestFormattedPairAndAssetType(data.Data.Symbol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var feeAsset currency.Code
|
||||
if data.Data.CommissionAsset != "" {
|
||||
feeAsset = currency.NewCode(data.Data.CommissionAsset)
|
||||
}
|
||||
orderID := strconv.FormatInt(data.Data.OrderID, 10)
|
||||
orderStatus, err := stringToOrderStatus(data.Data.OrderStatus)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
clientOrderID := data.Data.ClientOrderID
|
||||
if oStatus == order.Cancelled {
|
||||
if orderStatus == order.Cancelled {
|
||||
clientOrderID = data.Data.CancelledClientOrderID
|
||||
}
|
||||
var costAsset currency.Code
|
||||
if data.Data.CommissionAsset != "" {
|
||||
costAsset = currency.NewCode(data.Data.CommissionAsset)
|
||||
orderType, err := order.StringToOrderType(data.Data.OrderType)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
orderSide, err := order.StringToOrderSide(data.Data.Side)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- order.ClassificationError{
|
||||
Exchange: b.Name,
|
||||
OrderID: orderID,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
b.Websocket.DataHandler <- &order.Detail{
|
||||
Price: data.Data.Price,
|
||||
Amount: data.Data.Quantity,
|
||||
ExecutedAmount: data.Data.CumulativeFilledQuantity,
|
||||
RemainingAmount: data.Data.Quantity - data.Data.CumulativeFilledQuantity,
|
||||
Exchange: b.Name,
|
||||
ID: orderID,
|
||||
Type: oType,
|
||||
Side: oSide,
|
||||
Status: oStatus,
|
||||
AssetType: a,
|
||||
Date: data.Data.OrderCreationTime,
|
||||
Pair: p,
|
||||
ClientOrderID: clientOrderID,
|
||||
Cost: data.Data.Commission,
|
||||
CostAsset: costAsset,
|
||||
Price: data.Data.Price,
|
||||
Amount: data.Data.Quantity,
|
||||
AverageExecutedPrice: averagePrice,
|
||||
ExecutedAmount: data.Data.CumulativeFilledQuantity,
|
||||
RemainingAmount: remainingAmount,
|
||||
Cost: data.Data.CumulativeQuoteTransactedQuantity,
|
||||
CostAsset: pair.Quote,
|
||||
Fee: data.Data.Commission,
|
||||
FeeAsset: feeAsset,
|
||||
Exchange: b.Name,
|
||||
ID: orderID,
|
||||
ClientOrderID: clientOrderID,
|
||||
Type: orderType,
|
||||
Side: orderSide,
|
||||
Status: orderStatus,
|
||||
AssetType: assetType,
|
||||
Date: data.Data.OrderCreationTime,
|
||||
LastUpdated: data.Data.TransactionTime,
|
||||
Pair: pair,
|
||||
}
|
||||
return nil
|
||||
case "listStatus":
|
||||
@@ -435,12 +437,16 @@ func stringToOrderStatus(status string) (order.Status, error) {
|
||||
switch status {
|
||||
case "NEW":
|
||||
return order.New, nil
|
||||
case "PARTIALLY_FILLED":
|
||||
return order.PartiallyFilled, nil
|
||||
case "FILLED":
|
||||
return order.Filled, nil
|
||||
case "CANCELED":
|
||||
return order.Cancelled, nil
|
||||
case "PENDING_CANCEL":
|
||||
return order.PendingCancel, nil
|
||||
case "REJECTED":
|
||||
return order.Rejected, nil
|
||||
case "TRADE":
|
||||
return order.PartiallyFilled, nil
|
||||
case "EXPIRED":
|
||||
return order.Expired, nil
|
||||
default:
|
||||
|
||||
@@ -353,7 +353,7 @@ func (f *FTX) wsHandleData(respRaw []byte) error {
|
||||
resp.AverageExecutedPrice = resultData.OrderData.AvgFillPrice
|
||||
resp.ExecutedAmount = resultData.OrderData.FilledSize
|
||||
resp.RemainingAmount = resultData.OrderData.Size - resultData.OrderData.FilledSize
|
||||
resp.Cost = resp.AverageExecutedPrice * resp.Amount
|
||||
resp.Cost = resp.AverageExecutedPrice * resultData.OrderData.FilledSize
|
||||
// Fee: orderVars.Fee is incorrect.
|
||||
resp.Exchange = f.Name
|
||||
resp.ID = strconv.FormatInt(resultData.OrderData.ID, 10)
|
||||
@@ -363,6 +363,8 @@ func (f *FTX) wsHandleData(respRaw []byte) error {
|
||||
resp.Status = orderVars.Status
|
||||
resp.AssetType = assetType
|
||||
resp.Date = resultData.OrderData.CreatedAt
|
||||
// There's no current timestamp, so this is the best we can get.
|
||||
resp.LastUpdated = resultData.OrderData.CreatedAt
|
||||
resp.Pair = pair
|
||||
f.Websocket.DataHandler <- &resp
|
||||
case wsFills:
|
||||
|
||||
@@ -135,6 +135,7 @@ type Detail struct {
|
||||
Cost float64
|
||||
CostAsset currency.Code
|
||||
Fee float64
|
||||
FeeAsset currency.Code
|
||||
Exchange string
|
||||
InternalOrderID string
|
||||
ID string
|
||||
|
||||
Reference in New Issue
Block a user