From e387f4d75dad75e51bbcdd1322cb51d4dc72df11 Mon Sep 17 00:00:00 2001 From: xandershum Date: Sun, 3 Mar 2024 00:13:31 +0800 Subject: [PATCH] update WsUserDataEvent struct --- v2/websocket_service.go | 56 +++-- v2/websocket_service_test.go | 393 +++++++++++++++++++++++++++-------- 2 files changed, 340 insertions(+), 109 deletions(-) diff --git a/v2/websocket_service.go b/v2/websocket_service.go index 3f86e3ad..2b2e0ba1 100644 --- a/v2/websocket_service.go +++ b/v2/websocket_service.go @@ -474,18 +474,17 @@ type WsCombinedTradeEvent struct { // WsUserDataEvent define user data event type WsUserDataEvent struct { - Event UserDataEventType `json:"e"` - Time int64 `json:"E"` - TransactionTime int64 `json:"T"` - AccountUpdateTime int64 `json:"u"` - AccountUpdate WsAccountUpdateList - BalanceUpdate WsBalanceUpdate - OrderUpdate WsOrderUpdate - OCOUpdate WsOCOUpdate + Event UserDataEventType `json:"e"` + Time int64 `json:"E"` + AccountUpdate WsAccountUpdateList + BalanceUpdate WsBalanceUpdate + OrderUpdate WsOrderUpdate + OCOUpdate WsOCOUpdate } type WsAccountUpdateList struct { - WsAccountUpdates []WsAccountUpdate `json:"B"` + AccountUpdateTime int64 `json:"u"` + WsAccountUpdates []WsAccountUpdate `json:"B"` } // WsAccountUpdate define account update @@ -496,8 +495,9 @@ type WsAccountUpdate struct { } type WsBalanceUpdate struct { - Asset string `json:"a"` - Change string `json:"d"` + Asset string `json:"a"` + Change string `json:"d"` + TransactionTime int64 `json:"T"` } type WsOrderUpdate struct { @@ -509,7 +509,6 @@ type WsOrderUpdate struct { Volume string `json:"q"` Price string `json:"p"` StopPrice string `json:"P"` - TrailingDelta int64 `json:"d"` // Trailing Delta IceBergVolume string `json:"F"` OrderListId int64 `json:"g"` // for OCO OrigCustomOrderId string `json:"C"` // customized order ID for the original order @@ -524,17 +523,35 @@ type WsOrderUpdate struct { FeeCost string `json:"n"` TransactionTime int64 `json:"T"` TradeId int64 `json:"t"` + IgnoreI int64 `json:"I"` // ignore IsInOrderBook bool `json:"w"` // is the order in the order book? IsMaker bool `json:"m"` // is this order maker? + IgnoreM bool `json:"M"` // ignore CreateTime int64 `json:"O"` FilledQuoteVolume string `json:"Z"` // the quote volume that already filled LatestQuoteVolume string `json:"Y"` // the quote volume for the latest trade QuoteVolume string `json:"Q"` - TrailingTime int64 `json:"D"` // Trailing Time - StrategyId int64 `json:"j"` // Strategy ID - StrategyType int64 `json:"J"` // Strategy Type - WorkingTime int64 `json:"W"` // Working Time SelfTradePreventionMode string `json:"V"` + + //These are fields that appear in the payload only if certain conditions are met. + TrailingDelta int64 `json:"d"` // Appears only for trailing stop orders. + TrailingTime int64 `json:"D"` + StrategyId int64 `json:"j"` // Appears only if the strategyId parameter was provided upon order placement. + StrategyType int64 `json:"J"` // Appears only if the strategyType parameter was provided upon order placement. + PreventedMatchId int64 `json:"v"` // Appears only for orders that expired due to STP. + PreventedQuantity string `json:"A"` + LastPreventedQuantity string `json:"B"` + TradeGroupId int64 `json:"u"` + CounterOrderId int64 `json:"U"` + CounterSymbol string `json:"Cs"` + PreventedExecutionQuantity string `json:"pl"` + PreventedExecutionPrice string `json:"pL"` + PreventedExecutionQuoteQty string `json:"pY"` + WorkingTime int64 `json:"W"` // Appears when the order is working on the book + MatchType string `json:"b"` + AllocationId int64 `json:"a"` + WorkingFloor string `json:"k"` // Appears for orders that could potentially have allocations + UsedSor bool `json:"uS"` // Appears for orders that used SOR } type WsOCOUpdate struct { @@ -545,6 +562,7 @@ type WsOCOUpdate struct { ListOrderStatus string `json:"L"` RejectReason string `json:"r"` ClientOrderId string `json:"C"` // List Client Order ID + TransactionTime int64 `json:"T"` Orders WsOCOOrderList } @@ -599,12 +617,6 @@ func WsUserDataServe(listenKey string, handler WsUserDataHandler, errHandler Err errHandler(err) return } - // Unmarshal has case sensitive problem - event.TransactionTime = j.Get("T").MustInt64() - event.OrderUpdate.TransactionTime = j.Get("T").MustInt64() - event.OrderUpdate.Id = j.Get("i").MustInt64() - event.OrderUpdate.TradeId = j.Get("t").MustInt64() - event.OrderUpdate.FeeAsset = j.Get("N").MustString() case UserDataEventTypeListStatus: err = json.Unmarshal(message, &event.OCOUpdate) if err != nil { diff --git a/v2/websocket_service_test.go b/v2/websocket_service_test.go index 73abf51b..7379bb17 100644 --- a/v2/websocket_service_test.go +++ b/v2/websocket_service_test.go @@ -746,47 +746,69 @@ func (s *websocketServiceTestSuite) assertAccountUpdate(e, a *WsAccountUpdate) { func (s *websocketServiceTestSuite) assertOrderUpdate(e, a *WsOrderUpdate) { r := s.r() - r.Equal(e.TransactionTime, a.TransactionTime, "TransactionTime") r.Equal(e.Symbol, a.Symbol, "Symbol") - r.Equal(e.Volume, a.Volume, "Volume") - r.Equal(e.QuoteVolume, a.QuoteVolume, "QuoteVolume") - r.Equal(e.Price, a.Price, "Price") + r.Equal(e.ClientOrderId, a.ClientOrderId, "ClientOrderId") r.Equal(e.Side, a.Side, "Side") - r.Equal(e.IsMaker, a.IsMaker, "IsMaker") - r.Equal(e.Status, a.Status, "Status") - r.Equal(e.TimeInForce, a.TimeInForce, "TimeInForce") r.Equal(e.Type, a.Type, "Type") - r.Equal(e.CreateTime, a.CreateTime, "CreateTime") - r.Equal(e.Id, a.Id, "Id") + r.Equal(e.TimeInForce, a.TimeInForce, "TimeInForce") + r.Equal(e.Volume, a.Volume, "Volume") + r.Equal(e.Price, a.Price, "Price") r.Equal(e.StopPrice, a.StopPrice, "StopPrice") - r.Equal(e.TradeId, a.TradeId, "TradeId") + r.Equal(e.IceBergVolume, a.IceBergVolume, "IceBergVolume") + r.Equal(e.OrderListId, a.OrderListId, "OrderListId") + r.Equal(e.OrigCustomOrderId, a.OrigCustomOrderId, "OrigCustomOrderId") r.Equal(e.ExecutionType, a.ExecutionType, "ExecutionType") + r.Equal(e.Status, a.Status, "Status") + r.Equal(e.RejectReason, a.RejectReason, "RejectReason") + r.Equal(e.Id, a.Id, "Id") + r.Equal(e.LatestVolume, a.LatestVolume, "LatestVolume") + r.Equal(e.FilledVolume, a.FilledVolume, "FilledVolume") + r.Equal(e.LatestPrice, a.LatestPrice, "LatestPrice") r.Equal(e.FeeAsset, a.FeeAsset, "FeeAsset") r.Equal(e.FeeCost, a.FeeCost, "FeeCost") - r.Equal(e.FilledQuoteVolume, a.FilledQuoteVolume, "FilledQuoteVolume") - r.Equal(e.FilledVolume, a.FilledVolume, "FilledVolume") - r.Equal(e.IceBergVolume, a.IceBergVolume, "IceBergVolume") + r.Equal(e.TransactionTime, a.TransactionTime, "TransactionTime") + r.Equal(e.TradeId, a.TradeId, "TradeId") + r.Equal(e.IgnoreI, a.IgnoreI, "IgnoreI") r.Equal(e.IsInOrderBook, a.IsInOrderBook, "IsInOrderBook") - r.Equal(e.LatestPrice, a.LatestPrice, "LatestPrice") - r.Equal(e.OrderListId, a.OrderListId, "LatestQuoteVolume") + r.Equal(e.IsMaker, a.IsMaker, "IsMaker") + r.Equal(e.IgnoreM, a.IgnoreM, "IgnoreM") + r.Equal(e.CreateTime, a.CreateTime, "CreateTime") + r.Equal(e.FilledQuoteVolume, a.FilledQuoteVolume, "FilledQuoteVolume") r.Equal(e.LatestQuoteVolume, a.LatestQuoteVolume, "LatestQuoteVolume") - r.Equal(e.LatestVolume, a.LatestVolume, "OrigCustomOrderId") - r.Equal(e.OrigCustomOrderId, a.OrigCustomOrderId, "OrigCustomOrderId") - r.Equal(e.RejectReason, a.RejectReason, "RejectReason") + r.Equal(e.QuoteVolume, a.QuoteVolume, "QuoteVolume") + r.Equal(e.SelfTradePreventionMode, a.SelfTradePreventionMode, "SelfTradePreventionMode") + + r.Equal(e.TrailingDelta, a.TrailingDelta, "TrailingDelta") + r.Equal(e.TrailingTime, a.TrailingTime, "TrailingTime") + r.Equal(e.StrategyId, a.StrategyId, "StrategyId") + r.Equal(e.StrategyType, a.StrategyType, "StrategyType") + r.Equal(e.PreventedMatchId, a.PreventedMatchId, "PreventedMatchId") + r.Equal(e.PreventedQuantity, a.PreventedQuantity, "PreventedQuantity") + r.Equal(e.LastPreventedQuantity, a.LastPreventedQuantity, "LastPreventedQuantity") + r.Equal(e.TradeGroupId, a.TradeGroupId, "TradeGroupId") + r.Equal(e.CounterOrderId, a.CounterOrderId, "CounterOrderId") + r.Equal(e.CounterSymbol, a.CounterSymbol, "CounterSymbol") + r.Equal(e.PreventedExecutionQuantity, a.PreventedExecutionQuantity, "PreventedExecutionQuantity") + r.Equal(e.PreventedExecutionPrice, a.PreventedExecutionPrice, "PreventedExecutionPrice") + r.Equal(e.PreventedExecutionQuoteQty, a.PreventedExecutionQuoteQty, "PreventedExecutionQuoteQty") + r.Equal(e.WorkingTime, a.WorkingTime, "WorkingTime") + r.Equal(e.MatchType, a.MatchType, "MatchType") + r.Equal(e.AllocationId, a.AllocationId, "AllocationId") + r.Equal(e.WorkingFloor, a.WorkingFloor, "WorkingFloor") + r.Equal(e.UsedSor, a.UsedSor, "UsedSor") } func (s *websocketServiceTestSuite) assertBalanceUpdate(e, a *WsBalanceUpdate) { r := s.r() r.Equal(e.Asset, a.Asset) r.Equal(e.Change, a.Change) + r.Equal(e.TransactionTime, a.TransactionTime) } func (s *websocketServiceTestSuite) assertUserDataEvent(e, a *WsUserDataEvent) { r := s.r() r.Equal(e.Event, a.Event, "Event") r.Equal(e.Time, a.Time, "Time") - r.Equal(e.TransactionTime, a.TransactionTime, "TransactionTime") - r.Equal(e.AccountUpdateTime, a.AccountUpdateTime, "AccountUpdateTime") for i, e := range e.AccountUpdate.WsAccountUpdates { a := a.AccountUpdate.WsAccountUpdates[i] s.assertAccountUpdate(&e, &a) @@ -825,10 +847,10 @@ func (s *websocketServiceTestSuite) TestWsUserDataServeAccountUpdate() { ] }`) expectedEvent := &WsUserDataEvent{ - Event: "outboundAccountPosition", - Time: 1629771130464, - AccountUpdateTime: 1629771130463, + Event: "outboundAccountPosition", + Time: 1629771130464, AccountUpdate: WsAccountUpdateList{ + 1629771130463, []WsAccountUpdate{ { "LTC", @@ -841,74 +863,271 @@ func (s *websocketServiceTestSuite) TestWsUserDataServeAccountUpdate() { s.testWsUserDataServe(data, expectedEvent) } -func (s *websocketServiceTestSuite) TestWsUserDataServeOrderUpdate() { +func (s *websocketServiceTestSuite) TestWsUserDataServeOrderUpdateWithExample() { + //Using example data from the API documentation data := []byte(`{ - "e":"executionReport", - "E":1629771130464, - "s":"LTCUSDT", - "c":"MRx05dQCeTigiV1u1rfhUs", - "S":"BUY", - "o":"MARKET", - "f":"GTC", - "q":"0.10000000", - "p":"0.00000000", - "P":"0.00000000", - "F":"0.00000000", - "g":-1, - "C":"", - "x":"TRADE", - "X":"FILLED", - "r":"NONE", - "i":18997, - "l":"0.10000000", - "z":"0.10000000", - "L":"175.37000000", - "n":"0.00000000", - "N":"LTC", - "T":1629771130463, - "t":1473, - "I":314739191, - "w":false, - "m":false, - "M":true, - "O":1629771130463, - "Z":"17.53700000", - "Y":"17.53700000", - "Q":"0.00000000" + "e": "executionReport", + "E": 1499405658658, + "s": "ETHBTC", + "c": "mUvoqJxFIILMdfAW5iGSOW", + "S": "BUY", + "o": "LIMIT", + "f": "GTC", + "q": "1.00000000", + "p": "0.10264410", + "P": "0.00000000", + "F": "0.00000000", + "g": -1, + "C": "", + "x": "NEW", + "X": "NEW", + "r": "NONE", + "i": 4293153, + "l": "0.00000000", + "z": "0.00000000", + "L": "0.00000000", + "n": "0", + "N": null, + "T": 1499405658657, + "t": -1, + "I": 8641984, + "w": true, + "m": false, + "M": false, + "O": 1499405658657, + "Z": "0.00000000", + "Y": "0.00000000", + "Q": "0.00000000", + "V": "NONE", + "d": 4, + "D": 1668680518494, + "j": 1, + "J": 1000000, + "v": 3, + "A": "3.000000", + "B": "3.000000", + "u": 1, + "U": 37, + "Cs": "BTCUSDT", + "pl": "2.123456", + "pL": "0.10000001", + "pY": "0.21234562", + "W": 1668683798379, + "b": "ONE_PARTY_TRADE_REPORT", + "a": 1234, + "k": "SOR", + "uS": true + }`) + expectedEvent := &WsUserDataEvent{ + Event: "executionReport", + Time: 1499405658658, + OrderUpdate: WsOrderUpdate{ + Symbol: "ETHBTC", + ClientOrderId: "mUvoqJxFIILMdfAW5iGSOW", + Side: "BUY", + Type: "LIMIT", + TimeInForce: "GTC", + Volume: "1.00000000", + Price: "0.10264410", + StopPrice: "0.00000000", + IceBergVolume: "0.00000000", + OrderListId: -1, + OrigCustomOrderId: "", + ExecutionType: "NEW", + Status: "NEW", + RejectReason: "NONE", + Id: 4293153, + LatestVolume: "0.00000000", + FilledVolume: "0.00000000", + LatestPrice: "0.00000000", + FeeAsset: "", + FeeCost: "0", + TransactionTime: 1499405658657, + TradeId: -1, + IgnoreI: 8641984, + IsInOrderBook: true, + IsMaker: false, + IgnoreM: false, + CreateTime: 1499405658657, + FilledQuoteVolume: "0.00000000", + LatestQuoteVolume: "0.00000000", + QuoteVolume: "0.00000000", + SelfTradePreventionMode: "NONE", + TrailingDelta: 4, + TrailingTime: 1668680518494, + StrategyId: 1, + StrategyType: 1000000, + PreventedMatchId: 3, + PreventedQuantity: "3.000000", + LastPreventedQuantity: "3.000000", + TradeGroupId: 1, + CounterOrderId: 37, + CounterSymbol: "BTCUSDT", + PreventedExecutionQuantity: "2.123456", + PreventedExecutionPrice: "0.10000001", + PreventedExecutionQuoteQty: "0.21234562", + WorkingTime: 1668683798379, + MatchType: "ONE_PARTY_TRADE_REPORT", + AllocationId: 1234, + WorkingFloor: "SOR", + UsedSor: true, + }, + } + s.testWsUserDataServe(data, expectedEvent) +} +func (s *websocketServiceTestSuite) TestWsUserDataServeOrderUpdateWithPendingOrder() { + //Use pending order data + data := []byte(`{ + "e": "executionReport", + "E": 1709393629467, + "s": "FDUSDUSDT", + "c": "ios_2249e2aa2d394cada23b8e2e5b8ecdb9", + "S": "BUY", + "o": "LIMIT", + "f": "GTC", + "q": "1000.00000000", + "p": "0.99700000", + "P": "0.00000000", + "F": "0.00000000", + "g": -1, + "C": "", + "x": "NEW", + "X": "NEW", + "r": "NONE", + "i": 98534826, + "l": "0.00000000", + "z": "0.00000000", + "L": "0.00000000", + "n": "0", + "N": null, + "T": 1709393629467, + "t": -1, + "I": 270913746, + "w": true, + "m": false, + "M": false, + "O": 1709393629467, + "Z": "0.00000000", + "Y": "0.00000000", + "Q": "0.00000000", + "W": 1709393629467, + "V": "EXPIRE_MAKER" + }`) + expectedEvent := &WsUserDataEvent{ + Event: "executionReport", + Time: 1709393629467, + OrderUpdate: WsOrderUpdate{ + Symbol: "FDUSDUSDT", + ClientOrderId: "ios_2249e2aa2d394cada23b8e2e5b8ecdb9", + Side: "BUY", + Type: "LIMIT", + TimeInForce: "GTC", + Volume: "1000.00000000", + Price: "0.99700000", + StopPrice: "0.00000000", + IceBergVolume: "0.00000000", + OrderListId: -1, + OrigCustomOrderId: "", + ExecutionType: "NEW", + Status: "NEW", + RejectReason: "NONE", + Id: 98534826, + LatestVolume: "0.00000000", + FilledVolume: "0.00000000", + LatestPrice: "0.00000000", + FeeAsset: "", + FeeCost: "0", + TransactionTime: 1709393629467, + TradeId: -1, + IgnoreI: 270913746, + IsInOrderBook: true, + IsMaker: false, + IgnoreM: false, + CreateTime: 1709393629467, + FilledQuoteVolume: "0.00000000", + LatestQuoteVolume: "0.00000000", + QuoteVolume: "0.00000000", + WorkingTime: 1709393629467, + SelfTradePreventionMode: "EXPIRE_MAKER", + }, + } + s.testWsUserDataServe(data, expectedEvent) +} + +func (s *websocketServiceTestSuite) TestWsUserDataServeOrderUpdateWithTradeOrder() { + //Use trade order data + data := []byte(`{ + "e": "executionReport", + "E": 1709393640518, + "s": "FDUSDUSDT", + "c": "ios_54d9b18d8e7a4caf9d149573e16480ba", + "S": "BUY", + "o": "LIMIT", + "f": "GTC", + "q": "100.00000000", + "p": "0.99750000", + "P": "0.00000000", + "F": "0.00000000", + "g": -1, + "C": "", + "x": "TRADE", + "X": "FILLED", + "r": "NONE", + "i": 98534864, + "l": "100.00000000", + "z": "100.00000000", + "L": "0.99750000", + "n": "0.00000000", + "N": "BNB", + "T": 1709393640518, + "t": 73839999, + "I": 270913853, + "w": false, + "m": false, + "M": true, + "O": 1709393640518, + "Z": "99.75000000", + "Y": "99.75000000", + "Q": "0.00000000", + "W": 1709393640518, + "V": "EXPIRE_MAKER" }`) expectedEvent := &WsUserDataEvent{ - Event: "executionReport", - Time: 1629771130464, - TransactionTime: 1629771130463, + Event: "executionReport", + Time: 1709393640518, OrderUpdate: WsOrderUpdate{ - Symbol: "LTCUSDT", - ClientOrderId: "MRx05dQCeTigiV1u1rfhUs", - Side: "BUY", - Type: "MARKET", - TimeInForce: "GTC", - Volume: "0.10000000", - Price: "0.00000000", - StopPrice: "0.00000000", - IceBergVolume: "0.00000000", - OrderListId: -1, - OrigCustomOrderId: "", - ExecutionType: "TRADE", - Status: "FILLED", - RejectReason: "NONE", - Id: 18997, - LatestVolume: "0.10000000", - FilledVolume: "0.10000000", - LatestPrice: "175.37000000", - FeeAsset: "LTC", - FeeCost: "0.00000000", - TransactionTime: 1629771130463, - TradeId: 1473, - IsInOrderBook: false, - IsMaker: true, - CreateTime: 1629771130463, - FilledQuoteVolume: "17.53700000", - LatestQuoteVolume: "17.53700000", - QuoteVolume: "0.00000000", + Symbol: "FDUSDUSDT", + ClientOrderId: "ios_54d9b18d8e7a4caf9d149573e16480ba", + Side: "BUY", + Type: "LIMIT", + TimeInForce: "GTC", + Volume: "100.00000000", + Price: "0.99750000", + StopPrice: "0.00000000", + IceBergVolume: "0.00000000", + OrderListId: -1, + OrigCustomOrderId: "", + ExecutionType: "TRADE", + Status: "FILLED", + RejectReason: "NONE", + Id: 98534864, + LatestVolume: "100.00000000", + FilledVolume: "100.00000000", + LatestPrice: "0.99750000", + FeeAsset: "BNB", + FeeCost: "0.00000000", + TransactionTime: 1709393640518, + TradeId: 73839999, + IgnoreI: 270913853, + IsInOrderBook: false, + IsMaker: false, + IgnoreM: true, + CreateTime: 1709393640518, + FilledQuoteVolume: "99.75000000", + LatestQuoteVolume: "99.75000000", + QuoteVolume: "0.00000000", + WorkingTime: 1709393640518, + SelfTradePreventionMode: "EXPIRE_MAKER", }, } s.testWsUserDataServe(data, expectedEvent)