From 43a8044b44844d3527b91568f94f73b24be40545 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Fri, 4 Aug 2023 17:39:38 +1000 Subject: [PATCH] gateio: websocket updates (#1282) * gateio: websocket updates * Update exchanges/gateio/gateio_websocket.go Co-authored-by: Scott * glorious: nits * revert that trick * glorious:nits * Update exchanges/gateio/gateio_ws_futures.go Co-authored-by: Adrian Gallagher * Update exchanges/gateio/gateio_ws_futures.go Co-authored-by: Adrian Gallagher * Update exchanges/gateio/gateio_websocket.go Co-authored-by: Adrian Gallagher * Update exchanges/gateio/gateio_websocket.go Co-authored-by: Adrian Gallagher * Update exchanges/gateio/gateio_ws_option.go Co-authored-by: Adrian Gallagher * Update exchanges/gateio/gateio_ws_option.go Co-authored-by: Adrian Gallagher * Update exchanges/gateio/gateio_websocket.go Co-authored-by: Adrian Gallagher * Update exchanges/gateio/gateio_websocket.go Co-authored-by: Adrian Gallagher * thrasher-nits * Update exchanges/gateio/gateio_ws_futures.go Co-authored-by: Adrian Gallagher * Update exchanges/gateio/gateio_ws_futures.go Co-authored-by: Adrian Gallagher * thrasher: nits rides again chapter 2 volume 1 * rm unmarshaljson method for orderbook * use gateio time where it can and update tests * math.trunc and lower time frame on big books * :eagle --------- Co-authored-by: Ryan O'Hara-Reid Co-authored-by: Scott Co-authored-by: Adrian Gallagher --- currency/pair_methods.go | 9 +- exchanges/bithumb/bithumb_wrapper.go | 2 +- exchanges/bitmex/bitmex_wrapper.go | 4 +- exchanges/gateio/gateio_convert.go | 74 +---- exchanges/gateio/gateio_test.go | 31 ++- exchanges/gateio/gateio_types.go | 377 +++++++++++++------------- exchanges/gateio/gateio_websocket.go | 312 ++++++++++----------- exchanges/gateio/gateio_wrapper.go | 24 +- exchanges/gateio/gateio_ws_futures.go | 176 +++++------- exchanges/gateio/gateio_ws_option.go | 202 ++++++-------- exchanges/okx/okx_wrapper.go | 2 +- 11 files changed, 516 insertions(+), 697 deletions(-) diff --git a/currency/pair_methods.go b/currency/pair_methods.go index 504a3be5..7add2bec 100644 --- a/currency/pair_methods.go +++ b/currency/pair_methods.go @@ -45,13 +45,8 @@ func (p *Pair) UnmarshalJSON(d []byte) error { return nil } - newPair, err := NewPairFromString(pair) - if err != nil { - return err - } - - *p = newPair - return nil + *p, err = NewPairFromString(pair) + return err } // MarshalJSON conforms type to the marshaler interface diff --git a/exchanges/bithumb/bithumb_wrapper.go b/exchanges/bithumb/bithumb_wrapper.go index 83e160f2..b6fcf6e4 100644 --- a/exchanges/bithumb/bithumb_wrapper.go +++ b/exchanges/bithumb/bithumb_wrapper.go @@ -669,7 +669,7 @@ func (b *Bithumb) WithdrawFiatFunds(ctx context.Context, withdrawRequest *withdr if err := withdrawRequest.Validate(); err != nil { return nil, err } - if math.Mod(withdrawRequest.Amount, 1) != 0 { + if math.Trunc(withdrawRequest.Amount) != withdrawRequest.Amount { return nil, errors.New("currency KRW does not support decimal places") } if !withdrawRequest.Currency.Equal(currency.KRW) { diff --git a/exchanges/bitmex/bitmex_wrapper.go b/exchanges/bitmex/bitmex_wrapper.go index 77b9a369..9dddb13f 100644 --- a/exchanges/bitmex/bitmex_wrapper.go +++ b/exchanges/bitmex/bitmex_wrapper.go @@ -728,7 +728,7 @@ func (b *Bitmex) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Submi return nil, err } - if math.Mod(s.Amount, 1) != 0 { + if math.Trunc(s.Amount) != s.Amount { return nil, errors.New("order contract amount can not have decimals") } @@ -763,7 +763,7 @@ func (b *Bitmex) ModifyOrder(ctx context.Context, action *order.Modify) (*order. return nil, err } - if math.Mod(action.Amount, 1) != 0 { + if math.Trunc(action.Amount) != action.Amount { return nil, errors.New("contract amount can not have decimals") } diff --git a/exchanges/gateio/gateio_convert.go b/exchanges/gateio/gateio_convert.go index 5fc504db..0bf3475c 100644 --- a/exchanges/gateio/gateio_convert.go +++ b/exchanges/gateio/gateio_convert.go @@ -3,6 +3,7 @@ package gateio import ( "encoding/json" "fmt" + "math" "strconv" "time" ) @@ -19,11 +20,11 @@ func (a *gateioTime) UnmarshalJSON(data []byte) error { var standard int64 switch val := value.(type) { case float64: - standard = int64(val) - case int64: - standard = val - case int32: - standard = int64(val) + if math.Trunc(val) != val { + standard = int64(val * 1e3) // Account for 1684981731.098 + } else { + standard = int64(val) + } case string: if val == "" { return nil @@ -32,6 +33,10 @@ func (a *gateioTime) UnmarshalJSON(data []byte) error { if err != nil { return err } + if math.Trunc(parsedValue) != parsedValue { + *a = gateioTime(time.UnixMicro(int64(parsedValue * 1e3))) // Account for "1691122380942.173000" microseconds + return nil + } standard = int64(parsedValue) default: return fmt.Errorf("cannot unmarshal %T into gateioTime", val) @@ -45,7 +50,7 @@ func (a *gateioTime) UnmarshalJSON(data []byte) error { } // Time represents a time instance. -func (a *gateioTime) Time() time.Time { return time.Time(*a) } +func (a gateioTime) Time() time.Time { return time.Time(a) } type gateioNumericalValue float64 @@ -75,59 +80,4 @@ func (a *gateioNumericalValue) UnmarshalJSON(data []byte) error { } // Float64 returns float64 value from gateioNumericalValue instance. -func (a *gateioNumericalValue) Float64() float64 { return float64(*a) } - -// UnmarshalJSON to deserialize timestamp information and create OrderbookItem instance from the list of asks and bids data. -func (a *Orderbook) UnmarshalJSON(data []byte) error { - type Alias Orderbook - type askorbid struct { - Price gateioNumericalValue `json:"p"` - Size float64 `json:"s"` - } - chil := &struct { - *Alias - Current float64 `json:"current"` - Update float64 `json:"update"` - Asks []askorbid `json:"asks"` - Bids []askorbid `json:"bids"` - }{ - Alias: (*Alias)(a), - } - err := json.Unmarshal(data, &chil) - if err != nil { - return err - } - a.Current = time.UnixMilli(int64(chil.Current * 1000)) - a.Update = time.UnixMilli(int64(chil.Update * 1000)) - a.Asks = make([]OrderbookItem, len(chil.Asks)) - a.Bids = make([]OrderbookItem, len(chil.Bids)) - for x := range chil.Asks { - a.Asks[x] = OrderbookItem{ - Amount: chil.Asks[x].Size, - Price: chil.Asks[x].Price.Float64(), - } - } - for x := range chil.Bids { - a.Bids[x] = OrderbookItem{ - Amount: chil.Bids[x].Size, - Price: chil.Bids[x].Price.Float64(), - } - } - return nil -} - -// UnmarshalJSON deserialises the JSON info, including the timestamp -func (a *WsUserPersonalTrade) UnmarshalJSON(data []byte) error { - type Alias WsUserPersonalTrade - chil := &struct { - *Alias - CreateTimeMicroS float64 `json:"create_time_ms,string"` - }{ - Alias: (*Alias)(a), - } - if err := json.Unmarshal(data, chil); err != nil { - return err - } - a.CreateTimeMicroS = time.UnixMicro(int64(chil.CreateTimeMicroS * 1000)) - return nil -} +func (a gateioNumericalValue) Float64() float64 { return float64(a) } diff --git a/exchanges/gateio/gateio_test.go b/exchanges/gateio/gateio_test.go index 7a655eee..9e17116f 100644 --- a/exchanges/gateio/gateio_test.go +++ b/exchanges/gateio/gateio_test.go @@ -3206,9 +3206,9 @@ func TestSettlement(t *testing.T) { func TestParseGateioMilliSecTimeUnmarshal(t *testing.T) { t.Parallel() var timeWhenTesting int64 = 1684981731098 - timeWhenTestingString := "1684981731098" + timeWhenTestingString := `"1684981731098"` // Normal string integerJSON := `{"number": 1684981731098}` - float64JSON := `{"number": 1684981731098.234}` + float64JSON := `{"number": 1684981731.098}` time := time.UnixMilli(timeWhenTesting) var in gateioTime @@ -3245,18 +3245,19 @@ func TestParseGateioMilliSecTimeUnmarshal(t *testing.T) { func TestParseGateioTimeUnmarshal(t *testing.T) { t.Parallel() var timeWhenTesting int64 = 1684981731 - timeWhenTestingString := "1684981731" + timeWhenTestingString := `"1684981731"` integerJSON := `{"number": 1684981731}` float64JSON := `{"number": 1684981731.234}` + timeWhenTestingStringMicroSecond := `"1691122380942.173000"` - time := time.Unix(timeWhenTesting, 0) + whenTime := time.Unix(timeWhenTesting, 0) var in gateioTime err := json.Unmarshal([]byte(timeWhenTestingString), &in) if err != nil { t.Fatal(err) } - if !in.Time().Equal(time) { - t.Fatalf("found %v, but expected %v", in.Time(), time) + if !in.Time().Equal(whenTime) { + t.Fatalf("found %v, but expected %v", in.Time(), whenTime) } inInteger := struct { Number gateioTime `json:"number"` @@ -3265,8 +3266,8 @@ func TestParseGateioTimeUnmarshal(t *testing.T) { if err != nil { t.Fatal(err) } - if !inInteger.Number.Time().Equal(time) { - t.Fatalf("found %v, but expected %v", inInteger.Number.Time(), time) + if !inInteger.Number.Time().Equal(whenTime) { + t.Fatalf("found %v, but expected %v", inInteger.Number.Time(), whenTime) } inFloat64 := struct { @@ -3276,8 +3277,18 @@ func TestParseGateioTimeUnmarshal(t *testing.T) { if err != nil { t.Fatal(err) } - if !inFloat64.Number.Time().Equal(time) { - t.Fatalf("found %v, but expected %v", inFloat64.Number.Time(), time) + msTime := time.UnixMilli(1684981731234) + if !inFloat64.Number.Time().Equal(time.UnixMilli(1684981731234)) { + t.Fatalf("found %v, but expected %v", inFloat64.Number.Time(), msTime) + } + + var microSeconds gateioTime + err = json.Unmarshal([]byte(timeWhenTestingStringMicroSecond), µSeconds) + if err != nil { + t.Fatal(err) + } + if !microSeconds.Time().Equal(time.UnixMicro(1691122380942173)) { + t.Fatalf("found %v, but expected %v", microSeconds.Time(), time.UnixMicro(1691122380942173)) } } diff --git a/exchanges/gateio/gateio_types.go b/exchanges/gateio/gateio_types.go index 966c20b1..b07bff72 100644 --- a/exchanges/gateio/gateio_types.go +++ b/exchanges/gateio/gateio_types.go @@ -1,6 +1,7 @@ package gateio import ( + "encoding/json" "strconv" "time" @@ -535,8 +536,8 @@ type OrderbookData struct { func (a *OrderbookData) MakeOrderbook() (*Orderbook, error) { ob := &Orderbook{ ID: a.ID, - Current: a.Current.Time(), - Update: a.Update.Time(), + Current: a.Current, + Update: a.Update, } ob.Asks = make([]OrderbookItem, len(a.Asks)) ob.Bids = make([]OrderbookItem, len(a.Bids)) @@ -550,7 +551,7 @@ func (a *OrderbookData) MakeOrderbook() (*Orderbook, error) { return nil, err } ob.Asks[x] = OrderbookItem{ - Price: price, + Price: gateioNumericalValue(price), Amount: amount, } } @@ -564,7 +565,7 @@ func (a *OrderbookData) MakeOrderbook() (*Orderbook, error) { return nil, err } ob.Bids[x] = OrderbookItem{ - Price: price, + Price: gateioNumericalValue(price), Amount: amount, } } @@ -573,15 +574,15 @@ func (a *OrderbookData) MakeOrderbook() (*Orderbook, error) { // OrderbookItem stores an orderbook item type OrderbookItem struct { - Price float64 `json:"p"` - Amount float64 `json:"s"` + Price gateioNumericalValue `json:"p"` + Amount float64 `json:"s"` } // Orderbook stores the orderbook data type Orderbook struct { ID int64 `json:"id"` - Current time.Time `json:"current"` // The timestamp of the response data being generated (in milliseconds) - Update time.Time `json:"update"` // The timestamp of when the orderbook last changed (in milliseconds) + Current gateioTime `json:"current"` // The timestamp of the response data being generated (in milliseconds) + Update gateioTime `json:"update"` // The timestamp of when the orderbook last changed (in milliseconds) Bids []OrderbookItem `json:"bids"` Asks []OrderbookItem `json:"asks"` } @@ -904,20 +905,20 @@ type SwapCurrencies struct { // MyOptionSettlement represents option private settlement type MyOptionSettlement struct { - Size float64 `json:"size"` - SettleProfit float64 `json:"settle_profit,string"` - Contract string `json:"contract"` - StrikePrice float64 `json:"strike_price,string"` - Time time.Time `json:"time"` - SettlePrice float64 `json:"settle_price,string"` - Underlying string `json:"underlying"` - RealisedPnl string `json:"realised_pnl"` - Fee float64 `json:"fee,string"` + Size float64 `json:"size"` + SettleProfit float64 `json:"settle_profit,string"` + Contract string `json:"contract"` + StrikePrice float64 `json:"strike_price,string"` + Time gateioTime `json:"time"` + SettlePrice float64 `json:"settle_price,string"` + Underlying string `json:"underlying"` + RealisedPnl string `json:"realised_pnl"` + Fee float64 `json:"fee,string"` } // OptionsTicker represents tickers of options contracts type OptionsTicker struct { - Name string `json:"name"` + Name currency.Pair `json:"name"` LastPrice gateioNumericalValue `json:"last_price"` MarkPrice gateioNumericalValue `json:"mark_price"` PositionSize float64 `json:"position_size"` @@ -1088,16 +1089,16 @@ type MultiChainAddressItem struct { // DepositRecord represents deposit record item type DepositRecord struct { - ID string `json:"id"` - Timestamp time.Time `json:"timestamp"` - Currency string `json:"currency"` - Address string `json:"address"` - TransactionID string `json:"txid"` - Amount float64 `json:"amount,string"` - Memo string `json:"memo"` - Status string `json:"status"` - Chain string `json:"chain"` - Fee float64 `json:"fee,string"` + ID string `json:"id"` + Timestamp gateioTime `json:"timestamp"` + Currency string `json:"currency"` + Address string `json:"address"` + TransactionID string `json:"txid"` + Amount float64 `json:"amount,string"` + Memo string `json:"memo"` + Status string `json:"status"` + Chain string `json:"chain"` + Fee float64 `json:"fee,string"` } // TransferCurrencyParam represents currency transfer. @@ -1989,35 +1990,35 @@ type WsEventResponse struct { // WsResponse represents generalized websocket push data from the server. type WsResponse struct { - ID int64 `json:"id"` - Time int64 `json:"time"` - Channel string `json:"channel"` - Event string `json:"event"` - Result interface{} `json:"result"` + ID int64 `json:"id"` + Time int64 `json:"time"` + Channel string `json:"channel"` + Event string `json:"event"` + Result json.RawMessage `json:"result"` } // WsTicker websocket ticker information. type WsTicker struct { - CurrencyPair string `json:"currency_pair"` - Last float64 `json:"last,string"` - LowestAsk float64 `json:"lowest_ask,string"` - HighestBid float64 `json:"highest_bid,string"` - ChangePercentage float64 `json:"change_percentage,string"` - BaseVolume float64 `json:"base_volume,string"` - QuoteVolume float64 `json:"quote_volume,string"` - High24H float64 `json:"high_24h,string"` - Low24H float64 `json:"low_24h,string"` + CurrencyPair currency.Pair `json:"currency_pair"` + Last float64 `json:"last,string"` + LowestAsk float64 `json:"lowest_ask,string"` + HighestBid float64 `json:"highest_bid,string"` + ChangePercentage float64 `json:"change_percentage,string"` + BaseVolume float64 `json:"base_volume,string"` + QuoteVolume float64 `json:"quote_volume,string"` + High24H float64 `json:"high_24h,string"` + Low24H float64 `json:"low_24h,string"` } // WsTrade represents a websocket push data response for a trade type WsTrade struct { - ID int64 `json:"id"` - CreateTime int64 `json:"create_time"` - CreateTimeMs float64 `json:"create_time_ms,string"` - Side string `json:"side"` - CurrencyPair string `json:"currency_pair"` - Amount float64 `json:"amount,string"` - Price float64 `json:"price,string"` + ID int64 `json:"id"` + CreateTime gateioTime `json:"create_time"` + CreateTimeMs gateioTime `json:"create_time_ms"` + Side string `json:"side"` + CurrencyPair currency.Pair `json:"currency_pair"` + Amount float64 `json:"amount,string"` + Price float64 `json:"price,string"` } // WsCandlesticks represents the candlestick data for spot, margin and cross margin trades pushed through the websocket channel. @@ -2033,34 +2034,34 @@ type WsCandlesticks struct { // WsOrderbookTickerData represents the websocket orderbook best bid or best ask push data type WsOrderbookTickerData struct { - UpdateTimeMS int64 `json:"t"` - UpdateOrderID int64 `json:"u"` - CurrencyPair string `json:"s"` - BestBidPrice float64 `json:"b,string"` - BestBidAmount float64 `json:"B,string"` - BestAskPrice float64 `json:"a,string"` - BestAskAmount float64 `json:"A,string"` + UpdateTimeMS int64 `json:"t"` + UpdateOrderID int64 `json:"u"` + CurrencyPair currency.Pair `json:"s"` + BestBidPrice float64 `json:"b,string"` + BestBidAmount float64 `json:"B,string"` + BestAskPrice float64 `json:"a,string"` + BestAskAmount float64 `json:"A,string"` } // WsOrderbookUpdate represents websocket orderbook update push data type WsOrderbookUpdate struct { - UpdateTimeMs gateioTime `json:"t"` - IgnoreField string `json:"e"` - UpdateTime gateioTime `json:"E"` - CurrencyPair string `json:"s"` - FirstOrderbookUpdatedID int64 `json:"U"` // First update order book id in this event since last update - LastOrderbookUpdatedID int64 `json:"u"` - Bids [][2]string `json:"b"` - Asks [][2]string `json:"a"` + UpdateTimeMs gateioTime `json:"t"` + IgnoreField string `json:"e"` + UpdateTime gateioTime `json:"E"` + CurrencyPair currency.Pair `json:"s"` + FirstOrderbookUpdatedID int64 `json:"U"` // First update order book id in this event since last update + LastOrderbookUpdatedID int64 `json:"u"` + Bids [][2]string `json:"b"` + Asks [][2]string `json:"a"` } // WsOrderbookSnapshot represents a websocket orderbook snapshot push data type WsOrderbookSnapshot struct { - UpdateTimeMs gateioTime `json:"t"` - LastUpdateID int64 `json:"lastUpdateId"` - CurrencyPair string `json:"s"` - Bids [][2]string `json:"bids"` - Asks [][2]string `json:"asks"` + UpdateTimeMs gateioTime `json:"t"` + LastUpdateID int64 `json:"lastUpdateId"` + CurrencyPair currency.Pair `json:"s"` + Bids [][2]string `json:"bids"` + Asks [][2]string `json:"asks"` } // WsSpotOrder represents an order push data through the websocket channel. @@ -2071,7 +2072,7 @@ type WsSpotOrder struct { Succeeded bool `json:"succeeded,omitempty"` Label string `json:"label,omitempty"` Message string `json:"message,omitempty"` - CurrencyPair string `json:"currency_pair,omitempty"` + CurrencyPair currency.Pair `json:"currency_pair,omitempty"` Type string `json:"type,omitempty"` Account string `json:"account,omitempty"` Side string `json:"side,omitempty"` @@ -2097,20 +2098,20 @@ type WsSpotOrder struct { // WsUserPersonalTrade represents a user's personal trade pushed through the websocket connection. type WsUserPersonalTrade struct { - ID int64 `json:"id"` - UserID int64 `json:"user_id"` - OrderID string `json:"order_id"` - CurrencyPair string `json:"currency_pair"` - CreateTime int64 `json:"create_time"` - CreateTimeMicroS time.Time `json:"create_time_ms"` - Side string `json:"side"` - Amount float64 `json:"amount,string"` - Role string `json:"role"` - Price float64 `json:"price,string"` - Fee float64 `json:"fee,string"` - PointFee float64 `json:"point_fee,string"` - GtFee string `json:"gt_fee"` - Text string `json:"text"` + ID int64 `json:"id"` + UserID int64 `json:"user_id"` + OrderID string `json:"order_id"` + CurrencyPair currency.Pair `json:"currency_pair"` + CreateTime int64 `json:"create_time"` + CreateTimeMs gateioTime `json:"create_time_ms"` + Side string `json:"side"` + Amount float64 `json:"amount,string"` + Role string `json:"role"` + Price float64 `json:"price,string"` + Fee float64 `json:"fee,string"` + PointFee float64 `json:"point_fee,string"` + GtFee string `json:"gt_fee"` + Text string `json:"text"` } // WsSpotBalance represents a spot balance. @@ -2174,33 +2175,33 @@ type WsCrossMarginLoan struct { // WsFutureTicker represents a futures push data. type WsFutureTicker struct { - Contract string `json:"contract"` - Last float64 `json:"last,string"` - ChangePercentage string `json:"change_percentage"` - FundingRate string `json:"funding_rate"` - FundingRateIndicative string `json:"funding_rate_indicative"` - MarkPrice float64 `json:"mark_price,string"` - IndexPrice float64 `json:"index_price,string"` - TotalSize float64 `json:"total_size,string"` - Volume24H float64 `json:"volume_24h,string"` - Volume24HBtc float64 `json:"volume_24h_btc,string"` - Volume24HUsd float64 `json:"volume_24h_usd,string"` - QuantoBaseRate string `json:"quanto_base_rate"` - Volume24HQuote float64 `json:"volume_24h_quote,string"` - Volume24HSettle string `json:"volume_24h_settle"` - Volume24HBase float64 `json:"volume_24h_base,string"` - Low24H float64 `json:"low_24h,string"` - High24H float64 `json:"high_24h,string"` + Contract currency.Pair `json:"contract"` + Last float64 `json:"last,string"` + ChangePercentage string `json:"change_percentage"` + FundingRate string `json:"funding_rate"` + FundingRateIndicative string `json:"funding_rate_indicative"` + MarkPrice float64 `json:"mark_price,string"` + IndexPrice float64 `json:"index_price,string"` + TotalSize float64 `json:"total_size,string"` + Volume24H float64 `json:"volume_24h,string"` + Volume24HBtc float64 `json:"volume_24h_btc,string"` + Volume24HUsd float64 `json:"volume_24h_usd,string"` + QuantoBaseRate string `json:"quanto_base_rate"` + Volume24HQuote float64 `json:"volume_24h_quote,string"` + Volume24HSettle string `json:"volume_24h_settle"` + Volume24HBase float64 `json:"volume_24h_base,string"` + Low24H float64 `json:"low_24h,string"` + High24H float64 `json:"high_24h,string"` } // WsFuturesTrades represents a list of trades push data type WsFuturesTrades struct { - Size float64 `json:"size"` - ID int64 `json:"id"` - CreateTime gateioTime `json:"create_time"` - CreateTimeMs gateioTime `json:"create_time_ms"` - Price float64 `json:"price,string"` - Contract string `json:"contract"` + Size float64 `json:"size"` + ID int64 `json:"id"` + CreateTime gateioTime `json:"create_time"` + CreateTimeMs gateioTime `json:"create_time_ms"` + Price float64 `json:"price,string"` + Contract currency.Pair `json:"contract"` } // WsFuturesOrderbookTicker represents the orderbook ticker push data @@ -2216,10 +2217,10 @@ type WsFuturesOrderbookTicker struct { // WsFuturesAndOptionsOrderbookUpdate represents futures and options account orderbook update push data type WsFuturesAndOptionsOrderbookUpdate struct { - TimestampInMs int64 `json:"t"` - ContractName string `json:"s"` - FirstUpdatedID int64 `json:"U"` - LastUpdatedID int64 `json:"u"` + TimestampInMs int64 `json:"t"` + ContractName currency.Pair `json:"s"` + FirstUpdatedID int64 `json:"U"` + LastUpdatedID int64 `json:"u"` Bids []struct { Price float64 `json:"p,string"` Size float64 `json:"s"` @@ -2232,9 +2233,9 @@ type WsFuturesAndOptionsOrderbookUpdate struct { // WsFuturesOrderbookSnapshot represents a futures orderbook snapshot push data type WsFuturesOrderbookSnapshot struct { - TimestampInMs gateioTime `json:"t"` - Contract string `json:"contract"` - OrderbookID int64 `json:"id"` + TimestampInMs gateioTime `json:"t"` + Contract currency.Pair `json:"contract"` + OrderbookID int64 `json:"id"` Asks []struct { Price float64 `json:"p,string"` Size float64 `json:"s"` @@ -2255,44 +2256,44 @@ type WsFuturesOrderbookUpdateEvent struct { // WsFuturesOrder represents futures order type WsFuturesOrder struct { - Contract string `json:"contract"` - CreateTime gateioTime `json:"create_time"` - CreateTimeMs gateioTime `json:"create_time_ms"` - FillPrice float64 `json:"fill_price"` - FinishAs string `json:"finish_as"` - FinishTime int64 `json:"finish_time"` - FinishTimeMs gateioTime `json:"finish_time_ms"` - Iceberg int64 `json:"iceberg"` - ID int64 `json:"id"` - IsClose bool `json:"is_close"` - IsLiq bool `json:"is_liq"` - IsReduceOnly bool `json:"is_reduce_only"` - Left float64 `json:"left"` - Mkfr float64 `json:"mkfr"` - Price float64 `json:"price"` - Refr int64 `json:"refr"` - Refu int64 `json:"refu"` - Size float64 `json:"size"` - Status string `json:"status"` - Text string `json:"text"` - TimeInForce string `json:"tif"` - Tkfr float64 `json:"tkfr"` - User string `json:"user"` + Contract currency.Pair `json:"contract"` + CreateTime gateioTime `json:"create_time"` + CreateTimeMs gateioTime `json:"create_time_ms"` + FillPrice float64 `json:"fill_price"` + FinishAs string `json:"finish_as"` + FinishTime int64 `json:"finish_time"` + FinishTimeMs gateioTime `json:"finish_time_ms"` + Iceberg int64 `json:"iceberg"` + ID int64 `json:"id"` + IsClose bool `json:"is_close"` + IsLiq bool `json:"is_liq"` + IsReduceOnly bool `json:"is_reduce_only"` + Left float64 `json:"left"` + Mkfr float64 `json:"mkfr"` + Price float64 `json:"price"` + Refr int64 `json:"refr"` + Refu int64 `json:"refu"` + Size float64 `json:"size"` + Status string `json:"status"` + Text string `json:"text"` + TimeInForce string `json:"tif"` + Tkfr float64 `json:"tkfr"` + User string `json:"user"` } // WsFuturesUserTrade represents a futures account user trade push data type WsFuturesUserTrade struct { - ID string `json:"id"` - CreateTime gateioTime `json:"create_time"` - CreateTimeMs gateioTime `json:"create_time_ms"` - Contract string `json:"contract"` - OrderID string `json:"order_id"` - Size float64 `json:"size"` - Price float64 `json:"price,string"` - Role string `json:"role"` - Text string `json:"text"` - Fee float64 `json:"fee"` - PointFee int64 `json:"point_fee"` + ID string `json:"id"` + CreateTime gateioTime `json:"create_time"` + CreateTimeMs gateioTime `json:"create_time_ms"` + Contract currency.Pair `json:"contract"` + OrderID string `json:"order_id"` + Size float64 `json:"size"` + Price float64 `json:"price,string"` + Role string `json:"role"` + Text string `json:"text"` + Fee float64 `json:"fee"` + PointFee int64 `json:"point_fee"` } // WsFuturesLiquidationNotification represents a liquidation notification push data @@ -2431,11 +2432,11 @@ type WsOptionUnderlyingTicker struct { // WsOptionsTrades represents options trades for websocket push data. type WsOptionsTrades struct { - ID int64 `json:"id"` - CreateTime gateioTime `json:"create_time"` - Contract string `json:"contract"` - Size float64 `json:"size"` - Price float64 `json:"price"` + ID int64 `json:"id"` + CreateTime gateioTime `json:"create_time"` + Contract currency.Pair `json:"contract"` + Size float64 `json:"size"` + Price float64 `json:"price"` // Added in options websocket push data CreateTimeMs gateioTime `json:"create_time_ms"` @@ -2529,9 +2530,9 @@ type WsOptionsOrderbookTicker struct { // WsOptionsOrderbookSnapshot represents the options orderbook snapshot push data. type WsOptionsOrderbookSnapshot struct { - Timestamp gateioTime `json:"t"` - Contract string `json:"contract"` - ID int64 `json:"id"` + Timestamp gateioTime `json:"t"` + Contract currency.Pair `json:"contract"` + ID int64 `json:"id"` Asks []struct { Price float64 `json:"p,string"` Size float64 `json:"s"` @@ -2544,42 +2545,42 @@ type WsOptionsOrderbookSnapshot struct { // WsOptionsOrder represents options order push data. type WsOptionsOrder struct { - ID int64 `json:"id"` - Contract string `json:"contract"` - CreateTime int64 `json:"create_time"` - FillPrice float64 `json:"fill_price"` - FinishAs string `json:"finish_as"` - Iceberg float64 `json:"iceberg"` - IsClose bool `json:"is_close"` - IsLiq bool `json:"is_liq"` - IsReduceOnly bool `json:"is_reduce_only"` - Left float64 `json:"left"` - Mkfr float64 `json:"mkfr"` - Price float64 `json:"price"` - Refr float64 `json:"refr"` - Refu float64 `json:"refu"` - Size float64 `json:"size"` - Status string `json:"status"` - Text string `json:"text"` - Tif string `json:"tif"` - Tkfr float64 `json:"tkfr"` - Underlying string `json:"underlying"` - User string `json:"user"` - CreationTime gateioTime `json:"time"` - CreationTimeMs gateioTime `json:"time_ms"` + ID int64 `json:"id"` + Contract currency.Pair `json:"contract"` + CreateTime int64 `json:"create_time"` + FillPrice float64 `json:"fill_price"` + FinishAs string `json:"finish_as"` + Iceberg float64 `json:"iceberg"` + IsClose bool `json:"is_close"` + IsLiq bool `json:"is_liq"` + IsReduceOnly bool `json:"is_reduce_only"` + Left float64 `json:"left"` + Mkfr float64 `json:"mkfr"` + Price float64 `json:"price"` + Refr float64 `json:"refr"` + Refu float64 `json:"refu"` + Size float64 `json:"size"` + Status string `json:"status"` + Text string `json:"text"` + Tif string `json:"tif"` + Tkfr float64 `json:"tkfr"` + Underlying string `json:"underlying"` + User string `json:"user"` + CreationTime gateioTime `json:"time"` + CreationTimeMs gateioTime `json:"time_ms"` } // WsOptionsUserTrade represents user's personal trades of option account. type WsOptionsUserTrade struct { - ID string `json:"id"` - Underlying string `json:"underlying"` - OrderID string `json:"order"` - Contract string `json:"contract"` - CreateTime gateioTime `json:"create_time"` - CreateTimeMs gateioTime `json:"create_time_ms"` - Price float64 `json:"price,string"` - Role string `json:"role"` - Size float64 `json:"size"` + ID string `json:"id"` + Underlying string `json:"underlying"` + OrderID string `json:"order"` + Contract currency.Pair `json:"contract"` + CreateTime gateioTime `json:"create_time"` + CreateTimeMs gateioTime `json:"create_time_ms"` + Price float64 `json:"price,string"` + Role string `json:"role"` + Size float64 `json:"size"` } // WsOptionsLiquidates represents the liquidates push data of option account. diff --git a/exchanges/gateio/gateio_websocket.go b/exchanges/gateio/gateio_websocket.go index 7831d7a4..2d1e26d0 100644 --- a/exchanges/gateio/gateio_websocket.go +++ b/exchanges/gateio/gateio_websocket.go @@ -52,7 +52,7 @@ var defaultSubscriptions = []string{ spotTickerChannel, spotCandlesticksChannel, spotTradesChannel, - spotOrderbookChannel, + spotOrderbookTickerChannel, } var fetchedCurrencyPairSnapshotOrderbook = make(map[string]bool) @@ -66,14 +66,11 @@ func (g *Gateio) WsConnect() error { if err != nil { return err } - var dialer websocket.Dialer - err = g.Websocket.Conn.Dial(&dialer, http.Header{}) + err = g.Websocket.Conn.Dial(&websocket.Dialer{}, http.Header{}) if err != nil { return err } - pingMessage, err := json.Marshal(WsInput{ - Channel: spotPingChannel, - }) + pingMessage, err := json.Marshal(WsInput{Channel: spotPingChannel}) if err != nil { return err } @@ -113,34 +110,32 @@ func (g *Gateio) wsReadConnData() { } func (g *Gateio) wsHandleData(respRaw []byte) error { - var result WsResponse - var eventResponse WsEventResponse - err := json.Unmarshal(respRaw, &eventResponse) - if err == nil && - (eventResponse.Result != nil || eventResponse.Error != nil) && - (eventResponse.Event == "subscribe" || eventResponse.Event == "unsubscribe") { - if !g.Websocket.Match.IncomingWithData(eventResponse.ID, respRaw) { - return fmt.Errorf("couldn't match subscription message with ID: %d", eventResponse.ID) - } - return nil - } - err = json.Unmarshal(respRaw, &result) + var push WsResponse + err := json.Unmarshal(respRaw, &push) if err != nil { return err } - switch result.Channel { + + if push.Event == "subscribe" || push.Event == "unsubscribe" { + if !g.Websocket.Match.IncomingWithData(push.ID, respRaw) { + return fmt.Errorf("couldn't match subscription message with ID: %d", push.ID) + } + return nil + } + + switch push.Channel { // TODO: Convert function params below to only use push.Result case spotTickerChannel: - return g.processTicker(respRaw) + return g.processTicker(push.Result, push.Time) case spotTradesChannel: - return g.processTrades(respRaw) + return g.processTrades(push.Result) case spotCandlesticksChannel: - return g.processCandlestick(respRaw) + return g.processCandlestick(push.Result) case spotOrderbookTickerChannel: - return g.processOrderbookTicker(respRaw) + return g.processOrderbookTicker(push.Result) case spotOrderbookUpdateChannel: - return g.processOrderbookUpdate(respRaw) + return g.processOrderbookUpdate(push.Result) case spotOrderbookChannel: - return g.processOrderbookSnapshot(respRaw) + return g.processOrderbookSnapshot(push.Result) case spotOrdersChannel: return g.processSpotOrders(respRaw) case spotUserTradesChannel: @@ -165,32 +160,26 @@ func (g *Gateio) wsHandleData(respRaw []byte) error { return nil } -func (g *Gateio) processTicker(data []byte) error { - var response WsResponse - tickerData := &WsTicker{} - response.Result = tickerData - err := json.Unmarshal(data, &response) - if err != nil { - return err - } - currencyPair, err := currency.NewPairFromString(tickerData.CurrencyPair) +func (g *Gateio) processTicker(incoming []byte, pushTime int64) error { + var data WsTicker + err := json.Unmarshal(incoming, &data) if err != nil { return err } tickerPrice := ticker.Price{ ExchangeName: g.Name, - Volume: tickerData.BaseVolume, - QuoteVolume: tickerData.QuoteVolume, - High: tickerData.High24H, - Low: tickerData.Low24H, - Last: tickerData.Last, - Bid: tickerData.HighestBid, - Ask: tickerData.LowestAsk, + Volume: data.BaseVolume, + QuoteVolume: data.QuoteVolume, + High: data.High24H, + Low: data.Low24H, + Last: data.Last, + Bid: data.HighestBid, + Ask: data.LowestAsk, AssetType: asset.Spot, - Pair: currencyPair, - LastUpdated: time.Unix(response.Time, 0), + Pair: data.CurrencyPair, + LastUpdated: time.Unix(pushTime, 0), } - assetPairEnabled := g.listOfAssetsCurrencyPairEnabledFor(currencyPair) + assetPairEnabled := g.listOfAssetsCurrencyPairEnabledFor(data.CurrencyPair) if assetPairEnabled[asset.Spot] { g.Websocket.DataHandler <- &tickerPrice } @@ -207,33 +196,28 @@ func (g *Gateio) processTicker(data []byte) error { return nil } -func (g *Gateio) processTrades(data []byte) error { - var response WsResponse - tradeData := &WsTrade{} - response.Result = tradeData - err := json.Unmarshal(data, &response) +func (g *Gateio) processTrades(incoming []byte) error { + var data WsTrade + err := json.Unmarshal(incoming, &data) if err != nil { return err } - currencyPair, err := currency.NewPairFromString(tradeData.CurrencyPair) - if err != nil { - return err - } - side, err := order.StringToOrderSide(tradeData.Side) + + side, err := order.StringToOrderSide(data.Side) if err != nil { return err } spotTradeData := trade.Data{ - Timestamp: time.UnixMicro(int64(tradeData.CreateTimeMs * 1e3)), // the timestamp data is coming as a floating number. - CurrencyPair: currencyPair, + Timestamp: data.CreateTimeMs.Time(), + CurrencyPair: data.CurrencyPair, AssetType: asset.Spot, Exchange: g.Name, - Price: tradeData.Price, - Amount: tradeData.Amount, + Price: data.Price, + Amount: data.Amount, Side: side, - TID: strconv.FormatInt(tradeData.ID, 10), + TID: strconv.FormatInt(data.ID, 10), } - assetPairEnabled := g.listOfAssetsCurrencyPairEnabledFor(currencyPair) + assetPairEnabled := g.listOfAssetsCurrencyPairEnabledFor(data.CurrencyPair) if assetPairEnabled[asset.Spot] { err = trade.AddTradesToBuffer(g.Name, spotTradeData) if err != nil { @@ -259,15 +243,13 @@ func (g *Gateio) processTrades(data []byte) error { return nil } -func (g *Gateio) processCandlestick(data []byte) error { - var response WsResponse - candleData := &WsCandlesticks{} - response.Result = candleData - err := json.Unmarshal(data, &response) +func (g *Gateio) processCandlestick(incoming []byte) error { + var data WsCandlesticks + err := json.Unmarshal(incoming, &data) if err != nil { return err } - icp := strings.Split(candleData.NameOfSubscription, currency.UnderscoreDelimiter) + icp := strings.Split(data.NameOfSubscription, currency.UnderscoreDelimiter) if len(icp) < 3 { return errors.New("malformed candlestick websocket push data") } @@ -279,13 +261,13 @@ func (g *Gateio) processCandlestick(data []byte) error { Pair: currencyPair, AssetType: asset.Spot, Exchange: g.Name, - StartTime: time.Unix(candleData.Timestamp, 0), + StartTime: time.Unix(data.Timestamp, 0), Interval: icp[0], - OpenPrice: candleData.OpenPrice, - ClosePrice: candleData.ClosePrice, - HighPrice: candleData.HighestPrice, - LowPrice: candleData.LowestPrice, - Volume: candleData.TotalVolume, + OpenPrice: data.OpenPrice, + ClosePrice: data.ClosePrice, + HighPrice: data.HighestPrice, + LowPrice: data.LowestPrice, + Volume: data.TotalVolume, } assetPairEnabled := g.listOfAssetsCurrencyPairEnabledFor(currencyPair) if assetPairEnabled[asset.Spot] { @@ -304,34 +286,33 @@ func (g *Gateio) processCandlestick(data []byte) error { return nil } -func (g *Gateio) processOrderbookTicker(data []byte) error { - var response WsResponse - tickerData := &WsOrderbookTickerData{} - response.Result = tickerData - err := json.Unmarshal(data, &response) +func (g *Gateio) processOrderbookTicker(incoming []byte) error { + var data WsOrderbookTickerData + err := json.Unmarshal(incoming, &data) if err != nil { return err } - g.Websocket.DataHandler <- tickerData - return nil + + return g.Websocket.Orderbook.LoadSnapshot(&orderbook.Base{ + Exchange: g.Name, + Pair: data.CurrencyPair, + Asset: asset.Spot, + LastUpdated: time.UnixMilli(data.UpdateTimeMS), + Bids: []orderbook.Item{{Price: data.BestBidPrice, Amount: data.BestBidAmount}}, + Asks: []orderbook.Item{{Price: data.BestAskPrice, Amount: data.BestAskAmount}}, + }) } -func (g *Gateio) processOrderbookUpdate(data []byte) error { - var response WsResponse - update := new(WsOrderbookUpdate) - response.Result = update - err := json.Unmarshal(data, &response) +func (g *Gateio) processOrderbookUpdate(incoming []byte) error { + var data WsOrderbookUpdate + err := json.Unmarshal(incoming, &data) if err != nil { return err } - pair, err := currency.NewPairFromString(update.CurrencyPair) - if err != nil { - return err - } - assetPairEnabled := g.listOfAssetsCurrencyPairEnabledFor(pair) - if !fetchedCurrencyPairSnapshotOrderbook[update.CurrencyPair] { + assetPairEnabled := g.listOfAssetsCurrencyPairEnabledFor(data.CurrencyPair) + if !fetchedCurrencyPairSnapshotOrderbook[data.CurrencyPair.String()] { var orderbooks *orderbook.Base - orderbooks, err = g.FetchOrderbook(context.Background(), pair, asset.Spot) // currency pair orderbook data for Spot, Margin, and Cross Margin is same + orderbooks, err = g.FetchOrderbook(context.Background(), data.CurrencyPair, asset.Spot) // currency pair orderbook data for Spot, Margin, and Cross Margin is same if err != nil { return err } @@ -347,43 +328,33 @@ func (g *Gateio) processOrderbookUpdate(data []byte) error { return err } } - fetchedCurrencyPairSnapshotOrderbook[update.CurrencyPair] = true + fetchedCurrencyPairSnapshotOrderbook[data.CurrencyPair.String()] = true } updates := orderbook.Update{ - UpdateTime: update.UpdateTimeMs.Time(), - Pair: pair, + UpdateTime: data.UpdateTimeMs.Time(), + Pair: data.CurrencyPair, } - updates.Bids = make([]orderbook.Item, len(update.Bids)) - updates.Asks = make([]orderbook.Item, len(update.Asks)) - var price float64 - var amount float64 - for x := range updates.Asks { - price, err = strconv.ParseFloat(update.Asks[x][0], 64) + updates.Asks = make([]orderbook.Item, len(data.Asks)) + for x := range data.Asks { + updates.Asks[x].Price, err = strconv.ParseFloat(data.Asks[x][0], 64) if err != nil { return err } - amount, err = strconv.ParseFloat(update.Asks[x][1], 64) + updates.Asks[x].Amount, err = strconv.ParseFloat(data.Asks[x][1], 64) if err != nil { return err } - updates.Asks[x] = orderbook.Item{ - Amount: amount, - Price: price, - } } - for x := range updates.Bids { - price, err = strconv.ParseFloat(update.Bids[x][0], 64) + updates.Bids = make([]orderbook.Item, len(data.Bids)) + for x := range data.Bids { + updates.Bids[x].Price, err = strconv.ParseFloat(data.Bids[x][0], 64) if err != nil { return err } - amount, err = strconv.ParseFloat(update.Bids[x][1], 64) + updates.Bids[x].Amount, err = strconv.ParseFloat(data.Bids[x][1], 64) if err != nil { return err } - updates.Bids[x] = orderbook.Item{ - Amount: amount, - Price: price, - } } if len(updates.Asks) == 0 && len(updates.Bids) == 0 { return nil @@ -414,58 +385,42 @@ func (g *Gateio) processOrderbookUpdate(data []byte) error { return nil } -func (g *Gateio) processOrderbookSnapshot(data []byte) error { - var response WsResponse - snapshot := &WsOrderbookSnapshot{} - response.Result = snapshot - err := json.Unmarshal(data, &response) +func (g *Gateio) processOrderbookSnapshot(incoming []byte) error { + var data WsOrderbookSnapshot + err := json.Unmarshal(incoming, &data) if err != nil { return err } - pair, err := currency.NewPairFromString(snapshot.CurrencyPair) - if err != nil { - return err - } - assetPairEnabled := g.listOfAssetsCurrencyPairEnabledFor(pair) + assetPairEnabled := g.listOfAssetsCurrencyPairEnabledFor(data.CurrencyPair) bases := orderbook.Base{ Exchange: g.Name, - Pair: pair, + Pair: data.CurrencyPair, Asset: asset.Spot, - LastUpdated: snapshot.UpdateTimeMs.Time(), - LastUpdateID: snapshot.LastUpdateID, + LastUpdated: data.UpdateTimeMs.Time(), + LastUpdateID: data.LastUpdateID, VerifyOrderbook: g.CanVerifyOrderbook, } - bases.Bids = make([]orderbook.Item, len(snapshot.Bids)) - bases.Asks = make([]orderbook.Item, len(snapshot.Asks)) - var price float64 - var amount float64 - for x := range bases.Asks { - price, err = strconv.ParseFloat(snapshot.Asks[x][0], 64) + bases.Asks = make([]orderbook.Item, len(data.Asks)) + for x := range data.Asks { + bases.Asks[x].Price, err = strconv.ParseFloat(data.Asks[x][0], 64) if err != nil { return err } - amount, err = strconv.ParseFloat(snapshot.Asks[x][1], 64) + bases.Asks[x].Amount, err = strconv.ParseFloat(data.Asks[x][1], 64) if err != nil { return err } - bases.Asks[x] = orderbook.Item{ - Amount: amount, - Price: price, - } } - for x := range bases.Bids { - price, err = strconv.ParseFloat(snapshot.Bids[x][0], 64) + bases.Bids = make([]orderbook.Item, len(data.Bids)) + for x := range data.Bids { + bases.Bids[x].Price, err = strconv.ParseFloat(data.Bids[x][0], 64) if err != nil { return err } - amount, err = strconv.ParseFloat(snapshot.Bids[x][1], 64) + bases.Bids[x].Amount, err = strconv.ParseFloat(data.Bids[x][1], 64) if err != nil { return err } - bases.Bids[x] = orderbook.Item{ - Amount: amount, - Price: price, - } } if assetPairEnabled[asset.Spot] { err = g.Websocket.Orderbook.LoadSnapshot(&bases) @@ -505,10 +460,6 @@ func (g *Gateio) processSpotOrders(data []byte) error { } details := make([]order.Detail, len(resp.Result)) for x := range resp.Result { - pair, err := currency.NewPairFromString(resp.Result[x].CurrencyPair) - if err != nil { - return err - } side, err := order.StringToOrderSide(resp.Result[x].Side) if err != nil { return err @@ -527,7 +478,7 @@ func (g *Gateio) processSpotOrders(data []byte) error { OrderID: resp.Result[x].ID, Side: side, Type: orderType, - Pair: pair, + Pair: resp.Result[x].CurrencyPair, Cost: resp.Result[x].Fee, AssetType: a, Price: resp.Result[x].Price, @@ -553,18 +504,14 @@ func (g *Gateio) processUserPersonalTrades(data []byte) error { } fills := make([]fill.Data, len(resp.Result)) for x := range fills { - currencyPair, err := currency.NewPairFromString(resp.Result[x].CurrencyPair) - if err != nil { - return err - } side, err := order.StringToOrderSide(resp.Result[x].Side) if err != nil { return err } fills[x] = fill.Data{ - Timestamp: resp.Result[x].CreateTimeMicroS, + Timestamp: resp.Result[x].CreateTimeMs.Time(), Exchange: g.Name, - CurrencyPair: currencyPair, + CurrencyPair: resp.Result[x].CurrencyPair, Side: side, OrderID: resp.Result[x].OrderID, TradeID: strconv.FormatInt(resp.Result[x].ID, 10), @@ -691,29 +638,28 @@ func (g *Gateio) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, e spotBalancesChannel}...) } var subscriptions []stream.ChannelSubscription - var pairs []currency.Pair - var crossMarginPairs, spotPairs currency.Pairs - marginPairs, err := g.GetEnabledPairs(asset.Margin) - if err != nil { - return nil, err - } - crossMarginPairs, err = g.GetEnabledPairs(asset.CrossMargin) - if err != nil { - return nil, err - } - spotPairs, err = g.GetEnabledPairs(asset.Spot) - if err != nil { - return nil, err - } + var err error for i := range channelsToSubscribe { + var pairs []currency.Pair + var assetType asset.Item switch channelsToSubscribe[i] { case marginBalancesChannel: - pairs = marginPairs + assetType = asset.Margin + pairs, err = g.GetEnabledPairs(asset.Margin) case crossMarginBalanceChannel: - pairs = crossMarginPairs + assetType = asset.CrossMargin + pairs, err = g.GetEnabledPairs(asset.CrossMargin) default: - pairs = spotPairs + assetType = asset.Spot + pairs, err = g.GetEnabledPairs(asset.Spot) } + if err != nil { + if errors.Is(err, asset.ErrNotEnabled) { + continue // Skip if asset is not enabled. + } + return nil, err + } + for j := range pairs { params := make(map[string]interface{}) switch channelsToSubscribe[i] { @@ -723,7 +669,7 @@ func (g *Gateio) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, e case spotCandlesticksChannel: params["interval"] = kline.FiveMin case spotOrderbookUpdateChannel: - params["interval"] = kline.ThousandMilliseconds + params["interval"] = kline.HundredMilliseconds } if spotTradesChannel == channelsToSubscribe[i] { if !g.IsSaveTradeDataEnabled() { @@ -738,6 +684,7 @@ func (g *Gateio) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, e subscriptions = append(subscriptions, stream.ChannelSubscription{ Channel: channelsToSubscribe[i], Currency: fpair.Upper(), + Asset: assetType, Params: params, }) } @@ -788,8 +735,9 @@ func (g *Gateio) generatePayload(event string, channelsToSubscribe []stream.Chan return nil, err } } + var batch *[]string var intervalString string - payloads := make([]WsInput, len(channelsToSubscribe)) + payloads := make([]WsInput, 0, len(channelsToSubscribe)) for i := range channelsToSubscribe { var auth *WsAuthInput timestamp := time.Now() @@ -863,7 +811,8 @@ func (g *Gateio) generatePayload(event string, channelsToSubscribe []stream.Chan } params = append(params, intervalString) } - payloads[i] = WsInput{ + + payload := WsInput{ ID: g.Websocket.Conn.GenerateMessageID(false), Event: event, Channel: channelsToSubscribe[i].Channel, @@ -871,6 +820,21 @@ func (g *Gateio) generatePayload(event string, channelsToSubscribe []stream.Chan Auth: auth, Time: timestamp.Unix(), } + + if channelsToSubscribe[i].Channel == "spot.book_ticker" { + // To get all orderbook assets subscribed it needs to be batched and + // only spot.book_ticker can be batched, if not it will take about + // half an hour for initital sync. + if batch != nil { + *batch = append(*batch, params...) + } else { + // Sets up pointer to the field for the outbound payload. + payloads = append(payloads, payload) + batch = &payloads[len(payloads)-1].Payload + } + continue + } + payloads = append(payloads, payload) } return payloads, nil } diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index 4d488568..ef147818 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -331,20 +331,16 @@ func (g *Gateio) UpdateTicker(ctx context.Context, p currency.Pair, a asset.Item return nil, err } for x := range tickers { - if tickers[x].Name != fPair.String() { + if !tickers[x].Name.Equal(fPair) { continue } - var cp currency.Pair - cp, err = currency.NewPairFromString(strings.ReplaceAll(tickers[x].Name, currency.DashDelimiter, currency.UnderscoreDelimiter)) - if err != nil { - return nil, err - } - cp.Quote = currency.NewCode(strings.ReplaceAll(cp.Quote.String(), currency.UnderscoreDelimiter, currency.DashDelimiter)) + cleanQuote := strings.ReplaceAll(tickers[x].Name.Quote.String(), currency.UnderscoreDelimiter, currency.DashDelimiter) + tickers[x].Name.Quote = currency.NewCode(cleanQuote) if err != nil { return nil, err } tickerData = &ticker.Price{ - Pair: cp, + Pair: tickers[x].Name, Last: tickers[x].LastPrice.Float64(), Bid: tickers[x].Bid1Price, Ask: tickers[x].Ask1Price, @@ -644,17 +640,13 @@ func (g *Gateio) UpdateTickers(ctx context.Context, a asset.Item) error { return err } for x := range tickers { - currencyPair, err := currency.NewPairFromString(tickers[x].Name) - if err != nil { - return err - } err = ticker.ProcessTicker(&ticker.Price{ Last: tickers[x].LastPrice.Float64(), Ask: tickers[x].Ask1Price, AskSize: tickers[x].Ask1Size, Bid: tickers[x].Bid1Price, BidSize: tickers[x].Bid1Size, - Pair: currencyPair, + Pair: tickers[x].Name, ExchangeName: g.Name, AssetType: a, }) @@ -724,20 +716,20 @@ func (g *Gateio) UpdateOrderbook(ctx context.Context, p currency.Pair, a asset.I VerifyOrderbook: g.CanVerifyOrderbook, Pair: p.Upper(), LastUpdateID: orderbookNew.ID, - LastUpdated: orderbookNew.Update, + LastUpdated: orderbookNew.Update.Time(), } book.Bids = make(orderbook.Items, len(orderbookNew.Bids)) for x := range orderbookNew.Bids { book.Bids[x] = orderbook.Item{ Amount: orderbookNew.Bids[x].Amount, - Price: orderbookNew.Bids[x].Price, + Price: orderbookNew.Bids[x].Price.Float64(), } } book.Asks = make(orderbook.Items, len(orderbookNew.Asks)) for x := range orderbookNew.Asks { book.Asks[x] = orderbook.Item{ Amount: orderbookNew.Asks[x].Amount, - Price: orderbookNew.Asks[x].Price, + Price: orderbookNew.Asks[x].Price.Float64(), } } err = book.Process() diff --git a/exchanges/gateio/gateio_ws_futures.go b/exchanges/gateio/gateio_ws_futures.go index 15e45f78..115708c7 100644 --- a/exchanges/gateio/gateio_ws_futures.go +++ b/exchanges/gateio/gateio_ws_futures.go @@ -216,33 +216,30 @@ func (g *Gateio) wsFunnelFuturesConnectionData(ws stream.Connection) { } func (g *Gateio) wsHandleFuturesData(respRaw []byte, assetType asset.Item) error { - var result WsResponse - var eventResponse WsEventResponse - err := json.Unmarshal(respRaw, &eventResponse) - if err == nil && - (eventResponse.Result != nil || eventResponse.Error != nil) && - (eventResponse.Event == "subscribe" || eventResponse.Event == "unsubscribe") { - if !g.Websocket.Match.IncomingWithData(eventResponse.ID, respRaw) { - return fmt.Errorf("couldn't match subscription message with ID: %d", eventResponse.ID) - } - return nil - } - err = json.Unmarshal(respRaw, &result) + var push WsResponse + err := json.Unmarshal(respRaw, &push) if err != nil { return err } - switch result.Channel { - // Futures push datas. + + if push.Event == "subscribe" || push.Event == "unsubscribe" { + if !g.Websocket.Match.IncomingWithData(push.ID, respRaw) { + return fmt.Errorf("couldn't match subscription message with ID: %d", push.ID) + } + return nil + } + + switch push.Channel { case futuresTickersChannel: return g.processFuturesTickers(respRaw, assetType) case futuresTradesChannel: return g.processFuturesTrades(respRaw, assetType) case futuresOrderbookChannel: - return g.processFuturesOrderbookSnapshot(result.Event, respRaw, assetType) + return g.processFuturesOrderbookSnapshot(push.Event, push.Result, assetType, push.Time) case futuresOrderbookTickerChannel: - return g.processFuturesOrderbookTicker(respRaw) + return g.processFuturesOrderbookTicker(push.Result) case futuresOrderbookUpdateChannel: - return g.processFuturesAndOptionsOrderbookUpdate(respRaw, assetType) + return g.processFuturesAndOptionsOrderbookUpdate(push.Result, assetType) case futuresCandlesticksChannel: return g.processFuturesCandlesticks(respRaw, assetType) case futuresOrdersChannel: @@ -426,10 +423,6 @@ func (g *Gateio) processFuturesTickers(data []byte, assetType asset.Item) error } tickerPriceDatas := make([]ticker.Price, len(resp.Result)) for x := range resp.Result { - currencyPair, err := currency.NewPairFromString(resp.Result[x].Contract) - if err != nil { - return err - } tickerPriceDatas[x] = ticker.Price{ ExchangeName: g.Name, Volume: resp.Result[x].Volume24HBase, @@ -438,7 +431,7 @@ func (g *Gateio) processFuturesTickers(data []byte, assetType asset.Item) error Low: resp.Result[x].Low24H, Last: resp.Result[x].Last, AssetType: assetType, - Pair: currencyPair, + Pair: resp.Result[x].Contract, LastUpdated: time.Unix(resp.Time, 0), } } @@ -459,13 +452,9 @@ func (g *Gateio) processFuturesTrades(data []byte, assetType asset.Item) error { } trades := make([]trade.Data, len(resp.Result)) for x := range resp.Result { - currencyPair, err := currency.NewPairFromString(resp.Result[x].Contract) - if err != nil { - return err - } trades[x] = trade.Data{ Timestamp: resp.Result[x].CreateTimeMs.Time(), - CurrencyPair: currencyPair, + CurrencyPair: resp.Result[x].Contract, AssetType: assetType, Exchange: g.Name, Price: resp.Result[x].Price, @@ -514,37 +503,29 @@ func (g *Gateio) processFuturesCandlesticks(data []byte, assetType asset.Item) e return nil } -func (g *Gateio) processFuturesOrderbookTicker(data []byte) error { - var response WsResponse - orderbookTicker := &WsFuturesOrderbookTicker{} - response.Result = orderbookTicker - err := json.Unmarshal(data, &response) +func (g *Gateio) processFuturesOrderbookTicker(incoming []byte) error { + var data WsFuturesOrderbookTicker + err := json.Unmarshal(incoming, &data) if err != nil { return err } - g.Websocket.DataHandler <- response + g.Websocket.DataHandler <- data return nil } -func (g *Gateio) processFuturesAndOptionsOrderbookUpdate(data []byte, assetType asset.Item) error { - var response WsResponse - update := &WsFuturesAndOptionsOrderbookUpdate{} - response.Result = update - err := json.Unmarshal(data, &response) +func (g *Gateio) processFuturesAndOptionsOrderbookUpdate(incoming []byte, assetType asset.Item) error { + var data WsFuturesAndOptionsOrderbookUpdate + err := json.Unmarshal(incoming, &data) if err != nil { return err } - pair, err := currency.NewPairFromString(update.ContractName) - if err != nil { - return err - } - if (assetType == asset.Options && !fetchedOptionsCurrencyPairSnapshotOrderbook[update.ContractName]) || - (assetType != asset.Options && !fetchedFuturesCurrencyPairSnapshotOrderbook[update.ContractName]) { - orderbooks, err := g.FetchOrderbook(context.Background(), pair, assetType) + if (assetType == asset.Options && !fetchedOptionsCurrencyPairSnapshotOrderbook[data.ContractName.String()]) || + (assetType != asset.Options && !fetchedFuturesCurrencyPairSnapshotOrderbook[data.ContractName.String()]) { + orderbooks, err := g.FetchOrderbook(context.Background(), data.ContractName, assetType) if err != nil { return err } - if orderbooks.LastUpdateID < update.FirstUpdatedID || orderbooks.LastUpdateID > update.LastUpdatedID { + if orderbooks.LastUpdateID < data.FirstUpdatedID || orderbooks.LastUpdateID > data.LastUpdatedID { return nil } err = g.Websocket.Orderbook.LoadSnapshot(orderbooks) @@ -552,29 +533,25 @@ func (g *Gateio) processFuturesAndOptionsOrderbookUpdate(data []byte, assetType return err } if assetType == asset.Options { - fetchedOptionsCurrencyPairSnapshotOrderbook[update.ContractName] = true + fetchedOptionsCurrencyPairSnapshotOrderbook[data.ContractName.String()] = true } else { - fetchedFuturesCurrencyPairSnapshotOrderbook[update.ContractName] = true + fetchedFuturesCurrencyPairSnapshotOrderbook[data.ContractName.String()] = true } } updates := orderbook.Update{ - UpdateTime: time.UnixMilli(update.TimestampInMs), - Pair: pair, + UpdateTime: time.UnixMilli(data.TimestampInMs), + Pair: data.ContractName, Asset: assetType, } - updates.Bids = make([]orderbook.Item, len(update.Bids)) - updates.Asks = make([]orderbook.Item, len(update.Asks)) - for x := range updates.Asks { - updates.Asks[x] = orderbook.Item{ - Amount: update.Asks[x].Size, - Price: update.Asks[x].Price, - } + updates.Asks = make([]orderbook.Item, len(data.Asks)) + for x := range data.Asks { + updates.Asks[x].Amount = data.Asks[x].Size + updates.Asks[x].Price = data.Asks[x].Price } - for x := range updates.Bids { - updates.Bids[x] = orderbook.Item{ - Amount: update.Bids[x].Size, - Price: update.Bids[x].Price, - } + updates.Bids = make([]orderbook.Item, len(data.Bids)) + for x := range data.Bids { + updates.Bids[x].Amount = data.Bids[x].Size + updates.Bids[x].Price = data.Bids[x].Price } if len(updates.Asks) == 0 && len(updates.Bids) == 0 { return errors.New("malformed orderbook data") @@ -582,71 +559,56 @@ func (g *Gateio) processFuturesAndOptionsOrderbookUpdate(data []byte, assetType return g.Websocket.Orderbook.Update(&updates) } -func (g *Gateio) processFuturesOrderbookSnapshot(event string, data []byte, assetType asset.Item) error { +func (g *Gateio) processFuturesOrderbookSnapshot(event string, incoming []byte, assetType asset.Item, pushTime int64) error { if event == "all" { - var response WsResponse - snapshot := &WsFuturesOrderbookSnapshot{} - response.Result = snapshot - err := json.Unmarshal(data, &response) - if err != nil { - return err - } - pair, err := currency.NewPairFromString(snapshot.Contract) + var data WsFuturesOrderbookSnapshot + err := json.Unmarshal(incoming, &data) if err != nil { return err } base := orderbook.Base{ Asset: assetType, Exchange: g.Name, - Pair: pair, - LastUpdated: snapshot.TimestampInMs.Time(), + Pair: data.Contract, + LastUpdated: data.TimestampInMs.Time(), VerifyOrderbook: g.CanVerifyOrderbook, } - base.Bids = make([]orderbook.Item, len(snapshot.Bids)) - base.Asks = make([]orderbook.Item, len(snapshot.Asks)) - for x := range base.Asks { - base.Asks[x] = orderbook.Item{ - Amount: snapshot.Asks[x].Size, - Price: snapshot.Asks[x].Price, - } + base.Asks = make([]orderbook.Item, len(data.Asks)) + for x := range data.Asks { + base.Asks[x].Amount = data.Asks[x].Size + base.Asks[x].Price = data.Asks[x].Price } - for x := range base.Bids { - base.Bids[x] = orderbook.Item{ - Amount: snapshot.Bids[x].Size, - Price: snapshot.Bids[x].Price, - } + base.Bids = make([]orderbook.Item, len(data.Bids)) + for x := range data.Bids { + base.Bids[x].Amount = data.Bids[x].Size + base.Bids[x].Price = data.Bids[x].Price } return g.Websocket.Orderbook.LoadSnapshot(&base) } - resp := struct { - Time int64 `json:"time"` - Channel string `json:"channel"` - Event string `json:"event"` - Result []WsFuturesOrderbookUpdateEvent `json:"result"` - }{} - err := json.Unmarshal(data, &resp) + var data []WsFuturesOrderbookUpdateEvent + err := json.Unmarshal(incoming, &data) if err != nil { return err } dataMap := map[string][2][]orderbook.Item{} - for x := range resp.Result { - ab, ok := dataMap[resp.Result[x].CurrencyPair] + for x := range data { + ab, ok := dataMap[data[x].CurrencyPair] if !ok { ab = [2][]orderbook.Item{} } - if resp.Result[x].Amount > 0 { + if data[x].Amount > 0 { ab[1] = append(ab[1], orderbook.Item{ - Price: resp.Result[x].Price, - Amount: resp.Result[x].Amount, + Price: data[x].Price, + Amount: data[x].Amount, }) } else { ab[0] = append(ab[0], orderbook.Item{ - Price: resp.Result[x].Price, - Amount: -resp.Result[x].Amount, + Price: data[x].Price, + Amount: -data[x].Amount, }) } if !ok { - dataMap[resp.Result[x].CurrencyPair] = ab + dataMap[data[x].CurrencyPair] = ab } } if len(dataMap) == 0 { @@ -663,7 +625,7 @@ func (g *Gateio) processFuturesOrderbookSnapshot(event string, data []byte, asse Asset: assetType, Exchange: g.Name, Pair: currencyPair, - LastUpdated: time.Unix(resp.Time, 0), + LastUpdated: time.Unix(pushTime, 0), VerifyOrderbook: g.CanVerifyOrderbook, }) if err != nil { @@ -686,10 +648,6 @@ func (g *Gateio) processFuturesOrdersPushData(data []byte, assetType asset.Item) } orderDetails := make([]order.Detail, len(resp.Result)) for x := range resp.Result { - currencyPair, err := currency.NewPairFromString(resp.Result[x].Contract) - if err != nil { - return err - } status, err := order.StringToOrderStatus(func() string { if resp.Result[x].Status == "finished" { return "cancelled" @@ -704,7 +662,7 @@ func (g *Gateio) processFuturesOrdersPushData(data []byte, assetType asset.Item) Exchange: g.Name, OrderID: strconv.FormatInt(resp.Result[x].ID, 10), Status: status, - Pair: currencyPair, + Pair: resp.Result[x].Contract, LastUpdated: resp.Result[x].FinishTimeMs.Time(), Date: resp.Result[x].CreateTimeMs.Time(), ExecutedAmount: resp.Result[x].Size - resp.Result[x].Left, @@ -731,14 +689,10 @@ func (g *Gateio) procesFuturesUserTrades(data []byte, assetType asset.Item) erro } fills := make([]fill.Data, len(resp.Result)) for x := range resp.Result { - currencyPair, err := currency.NewPairFromString(resp.Result[x].Contract) - if err != nil { - return err - } fills[x] = fill.Data{ Timestamp: resp.Result[x].CreateTimeMs.Time(), Exchange: g.Name, - CurrencyPair: currencyPair, + CurrencyPair: resp.Result[x].Contract, OrderID: resp.Result[x].OrderID, TradeID: resp.Result[x].ID, Price: resp.Result[x].Price, diff --git a/exchanges/gateio/gateio_ws_option.go b/exchanges/gateio/gateio_ws_option.go index 527046a8..a111e1fb 100644 --- a/exchanges/gateio/gateio_ws_option.go +++ b/exchanges/gateio/gateio_ws_option.go @@ -339,46 +339,44 @@ func (g *Gateio) handleOptionsSubscription(event string, channelsToSubscribe []s } func (g *Gateio) wsHandleOptionsData(respRaw []byte) error { - var result WsResponse - var eventResponse WsEventResponse - err := json.Unmarshal(respRaw, &eventResponse) - if err == nil && - (eventResponse.Result != nil || eventResponse.Error != nil) && - (eventResponse.Event == "subscribe" || eventResponse.Event == "unsubscribe") { - if !g.Websocket.Match.IncomingWithData(eventResponse.ID, respRaw) { - return fmt.Errorf("couldn't match subscription message with ID: %d", eventResponse.ID) - } - return nil - } - err = json.Unmarshal(respRaw, &result) + var push WsResponse + err := json.Unmarshal(respRaw, &push) if err != nil { return err } - switch result.Channel { + + if push.Event == "subscribe" || push.Event == "unsubscribe" { + if !g.Websocket.Match.IncomingWithData(push.ID, respRaw) { + return fmt.Errorf("couldn't match subscription message with ID: %d", push.ID) + } + return nil + } + + switch push.Channel { case optionsContractTickersChannel: - return g.processOptionsContractTickers(respRaw) + return g.processOptionsContractTickers(push.Result) case optionsUnderlyingTickersChannel: - return g.processOptionsUnderlyingTicker(respRaw) + return g.processOptionsUnderlyingTicker(push.Result) case optionsTradesChannel, optionsUnderlyingTradesChannel: return g.processOptionsTradesPushData(respRaw) case optionsUnderlyingPriceChannel: - return g.processOptionsUnderlyingPricePushData(respRaw) + return g.processOptionsUnderlyingPricePushData(push.Result) case optionsMarkPriceChannel: - return g.processOptionsMarkPrice(respRaw) + return g.processOptionsMarkPrice(push.Result) case optionsSettlementChannel: - return g.processOptionsSettlementPushData(respRaw) + return g.processOptionsSettlementPushData(push.Result) case optionsContractsChannel: - return g.processOptionsContractPushData(respRaw) + return g.processOptionsContractPushData(push.Result) case optionsContractCandlesticksChannel, optionsUnderlyingCandlesticksChannel: return g.processOptionsCandlestickPushData(respRaw) case optionsOrderbookChannel: - return g.processOptionsOrderbookSnapshotPushData(result.Event, respRaw) + return g.processOptionsOrderbookSnapshotPushData(push.Event, push.Result, push.Time) case optionsOrderbookTickerChannel: return g.processOrderbookTickerPushData(respRaw) case optionsOrderbookUpdateChannel: - return g.processFuturesAndOptionsOrderbookUpdate(respRaw, asset.Options) + return g.processFuturesAndOptionsOrderbookUpdate(push.Result, asset.Options) case optionsOrdersChannel: return g.processOptionsOrderPushData(respRaw) case optionsUserTradesChannel: @@ -401,39 +399,32 @@ func (g *Gateio) wsHandleOptionsData(respRaw []byte) error { } } -func (g *Gateio) processOptionsContractTickers(data []byte) error { - var response WsResponse - tickerData := OptionsTicker{} - response.Result = &tickerData - err := json.Unmarshal(data, &response) - if err != nil { - return err - } - currencyPair, err := currency.NewPairFromString(tickerData.Name) +func (g *Gateio) processOptionsContractTickers(incoming []byte) error { + var data OptionsTicker + err := json.Unmarshal(incoming, &data) if err != nil { return err } g.Websocket.DataHandler <- &ticker.Price{ - Pair: currencyPair, - Last: tickerData.LastPrice.Float64(), - Bid: tickerData.Bid1Price, - Ask: tickerData.Ask1Price, - AskSize: tickerData.Ask1Size, - BidSize: tickerData.Bid1Size, + Pair: data.Name, + Last: data.LastPrice.Float64(), + Bid: data.Bid1Price, + Ask: data.Ask1Price, + AskSize: data.Ask1Size, + BidSize: data.Bid1Size, ExchangeName: g.Name, AssetType: asset.Options, } return nil } -func (g *Gateio) processOptionsUnderlyingTicker(data []byte) error { - var response WsResponse - response.Result = &WsOptionUnderlyingTicker{} - err := json.Unmarshal(data, &response) +func (g *Gateio) processOptionsUnderlyingTicker(incoming []byte) error { + var data WsOptionUnderlyingTicker + err := json.Unmarshal(incoming, &data) if err != nil { return err } - g.Websocket.DataHandler <- &response + g.Websocket.DataHandler <- &data return nil } @@ -455,13 +446,9 @@ func (g *Gateio) processOptionsTradesPushData(data []byte) error { } trades := make([]trade.Data, len(resp.Result)) for x := range resp.Result { - currencyPair, err := currency.NewPairFromString(resp.Result[x].Contract) - if err != nil { - return err - } trades[x] = trade.Data{ Timestamp: resp.Result[x].CreateTimeMs.Time(), - CurrencyPair: currencyPair, + CurrencyPair: resp.Result[x].Contract, AssetType: asset.Options, Exchange: g.Name, Price: resp.Result[x].Price, @@ -472,51 +459,43 @@ func (g *Gateio) processOptionsTradesPushData(data []byte) error { return g.Websocket.Trade.Update(saveTradeData, trades...) } -func (g *Gateio) processOptionsUnderlyingPricePushData(data []byte) error { - var response WsResponse - priceD := WsOptionsUnderlyingPrice{} - response.Result = &priceD - err := json.Unmarshal(data, &response) +func (g *Gateio) processOptionsUnderlyingPricePushData(incoming []byte) error { + var data WsOptionsUnderlyingPrice + err := json.Unmarshal(incoming, &data) if err != nil { return err } - g.Websocket.DataHandler <- &response + g.Websocket.DataHandler <- &data return nil } -func (g *Gateio) processOptionsMarkPrice(data []byte) error { - var response WsResponse - markPrice := WsOptionsMarkPrice{} - response.Result = &markPrice - err := json.Unmarshal(data, &response) +func (g *Gateio) processOptionsMarkPrice(incoming []byte) error { + var data WsOptionsMarkPrice + err := json.Unmarshal(incoming, &data) if err != nil { return err } - g.Websocket.DataHandler <- &response + g.Websocket.DataHandler <- &data return nil } -func (g *Gateio) processOptionsSettlementPushData(data []byte) error { - var response WsResponse - settlementData := WsOptionsSettlement{} - response.Result = &settlementData - err := json.Unmarshal(data, &response) +func (g *Gateio) processOptionsSettlementPushData(incoming []byte) error { + var data WsOptionsSettlement + err := json.Unmarshal(incoming, &data) if err != nil { return err } - g.Websocket.DataHandler <- &response + g.Websocket.DataHandler <- &data return nil } -func (g *Gateio) processOptionsContractPushData(data []byte) error { - var response WsResponse - contractData := WsOptionsContract{} - response.Result = &contractData - err := json.Unmarshal(data, &response) +func (g *Gateio) processOptionsContractPushData(incoming []byte) error { + var data WsOptionsContract + err := json.Unmarshal(incoming, &data) if err != nil { return err } - g.Websocket.DataHandler <- &response + g.Websocket.DataHandler <- &data return nil } @@ -558,83 +537,64 @@ func (g *Gateio) processOptionsCandlestickPushData(data []byte) error { return nil } -func (g *Gateio) processOrderbookTickerPushData(data []byte) error { - var response WsResponse - orderbookTicker := WsOptionsOrderbookTicker{} - response.Result = &orderbookTicker - err := json.Unmarshal(data, &orderbookTicker) +func (g *Gateio) processOrderbookTickerPushData(incoming []byte) error { + var data WsOptionsOrderbookTicker + err := json.Unmarshal(incoming, &data) if err != nil { return err } - g.Websocket.DataHandler <- &response + g.Websocket.DataHandler <- &data return nil } -func (g *Gateio) processOptionsOrderbookSnapshotPushData(event string, data []byte) error { +func (g *Gateio) processOptionsOrderbookSnapshotPushData(event string, incoming []byte, pushTime int64) error { if event == "all" { - var response WsResponse - snapshot := WsOptionsOrderbookSnapshot{} - response.Result = &snapshot - err := json.Unmarshal(data, &response) - if err != nil { - return err - } - pair, err := currency.NewPairFromString(snapshot.Contract) + var data WsOptionsOrderbookSnapshot + err := json.Unmarshal(incoming, &data) if err != nil { return err } base := orderbook.Base{ Asset: asset.Options, Exchange: g.Name, - Pair: pair, - LastUpdated: snapshot.Timestamp.Time(), + Pair: data.Contract, + LastUpdated: data.Timestamp.Time(), VerifyOrderbook: g.CanVerifyOrderbook, } - base.Asks = make([]orderbook.Item, len(snapshot.Asks)) - base.Bids = make([]orderbook.Item, len(snapshot.Bids)) - for x := range base.Asks { - base.Asks[x] = orderbook.Item{ - Amount: snapshot.Asks[x].Size, - Price: snapshot.Asks[x].Price, - } + base.Asks = make([]orderbook.Item, len(data.Asks)) + for x := range data.Asks { + base.Asks[x].Amount = data.Asks[x].Size + base.Asks[x].Price = data.Asks[x].Price } - for x := range base.Bids { - base.Bids[x] = orderbook.Item{ - Amount: snapshot.Bids[x].Size, - Price: snapshot.Bids[x].Price, - } + base.Bids = make([]orderbook.Item, len(data.Bids)) + for x := range data.Bids { + base.Bids[x].Amount = data.Bids[x].Size + base.Bids[x].Price = data.Bids[x].Price } return g.Websocket.Orderbook.LoadSnapshot(&base) } - resp := struct { - Time int64 `json:"time"` - Channel string `json:"channel"` - Event string `json:"event"` - Result []WsFuturesOrderbookUpdateEvent `json:"result"` - }{} - err := json.Unmarshal(data, &resp) + var data []WsFuturesOrderbookUpdateEvent + err := json.Unmarshal(incoming, &data) if err != nil { return err } dataMap := map[string][2][]orderbook.Item{} - for x := range resp.Result { - ab, ok := dataMap[resp.Result[x].CurrencyPair] + for x := range data { + ab, ok := dataMap[data[x].CurrencyPair] if !ok { ab = [2][]orderbook.Item{} } - if resp.Result[x].Amount > 0 { + if data[x].Amount > 0 { ab[1] = append(ab[1], orderbook.Item{ - Price: resp.Result[x].Price, - Amount: resp.Result[x].Amount, + Price: data[x].Price, Amount: data[x].Amount, }) } else { ab[0] = append(ab[0], orderbook.Item{ - Price: resp.Result[x].Price, - Amount: -resp.Result[x].Amount, + Price: data[x].Price, Amount: -data[x].Amount, }) } if !ok { - dataMap[resp.Result[x].CurrencyPair] = ab + dataMap[data[x].CurrencyPair] = ab } } if len(dataMap) == 0 { @@ -651,7 +611,7 @@ func (g *Gateio) processOptionsOrderbookSnapshotPushData(event string, data []by Asset: asset.Options, Exchange: g.Name, Pair: currencyPair, - LastUpdated: time.Unix(resp.Time, 0), + LastUpdated: time.Unix(pushTime, 0), VerifyOrderbook: g.CanVerifyOrderbook, }) if err != nil { @@ -674,10 +634,6 @@ func (g *Gateio) processOptionsOrderPushData(data []byte) error { } orderDetails := make([]order.Detail, len(resp.Result)) for x := range resp.Result { - currencyPair, err := currency.NewPairFromString(resp.Result[x].Contract) - if err != nil { - return err - } status, err := order.StringToOrderStatus(func() string { if resp.Result[x].Status == "finished" { return "cancelled" @@ -692,7 +648,7 @@ func (g *Gateio) processOptionsOrderPushData(data []byte) error { Exchange: g.Name, OrderID: strconv.FormatInt(resp.Result[x].ID, 10), Status: status, - Pair: currencyPair, + Pair: resp.Result[x].Contract, Date: resp.Result[x].CreationTimeMs.Time(), ExecutedAmount: resp.Result[x].Size - resp.Result[x].Left, Price: resp.Result[x].Price, @@ -717,14 +673,10 @@ func (g *Gateio) processOptionsUserTradesPushData(data []byte) error { } fills := make([]fill.Data, len(resp.Result)) for x := range resp.Result { - currencyPair, err := currency.NewPairFromString(resp.Result[x].Contract) - if err != nil { - return err - } fills[x] = fill.Data{ Timestamp: resp.Result[x].CreateTimeMs.Time(), Exchange: g.Name, - CurrencyPair: currencyPair, + CurrencyPair: resp.Result[x].Contract, OrderID: resp.Result[x].OrderID, TradeID: resp.Result[x].ID, Price: resp.Result[x].Price, diff --git a/exchanges/okx/okx_wrapper.go b/exchanges/okx/okx_wrapper.go index d69d25a8..b1caeacb 100644 --- a/exchanges/okx/okx_wrapper.go +++ b/exchanges/okx/okx_wrapper.go @@ -853,7 +853,7 @@ func (ok *Okx) ModifyOrder(ctx context.Context, action *order.Modify) (*order.Mo return nil, err } var err error - if math.Mod(action.Amount, 1) != 0 { + if math.Trunc(action.Amount) != action.Amount { return nil, errors.New("okx contract amount can not be decimal") } format, err := ok.GetPairFormat(action.AssetType, false)