Files
gocryptotrader/exchanges/zb/zb_websocket.go
Ryan O'Hara-Reid eb0571cc9b exchange: binance orderbook fix (#599)
* port orderbook binance management from draft singular asset (spot) processing add additional updates to buffer management

* integrate port

* shifted burden of proof to exchange and remove repairing techniques that obfuscate issues and could caause artifacts

* WIP

* Update exchanges, update tests, update configuration so we can default off on buffer util.

* Add buffer enabled switching to all exchanges and some that are missing, default to off.

* lbtc set not aggregate books

* Addr linter issues

* EOD wip

* optimization and bug fix pass

* clean before test and benchmarking

* add testing/benchmarks to sorting/reversing functions, dropped pointer to slice as we aren't changing slice len or cap

* Add tests and removed ptr for main book as we just ammend amount

* addr exchange test issues

* ci issues

* addr glorious issues

* Addr MCB nits, fixed funding rate book for bitfinex and fixed potential panic on nil book return

* addr linter issues

* updated mistakes

* Fix more tests

* revert bypass

* Addr mcb nits

* fix zero price bug caused by exchange. Filted out bid result rather then unsubscribing. Updated orderbook to L2 so there is no aggregation.

* Allow for zero bid and ask books to be loaded and warn if found.

* remove authentication subscription conflicts as they do not have a channel ID return

* WIP - Batching outbound requests for kraken as they do not give you the partial if you subscribe to do many things.

* finalised outbound request for kraken

* filter zero value due to invalid returned data from exchange, add in max subscription amount and increased outbound batch limit

* expand to max allowed book length & fix issue where they were sending a zero length ask side when we sent a depth of zero

* Updated function comments and added in more realistic book sizing for sort cases

* change map ordering

* amalgamate maps in buffer

* Rm ln

* fix kraken linter issues

* add in buffer initialisation

* increase timout by 30seconds

* Coinbene: Add websocket orderbook length check.

* Engine: Improve switch statement for orderbook summary dissplay.

* Binance: Added tests, remove deadlock

* Exchanges: Change orderbook field -> IsFundingRate

* Orderbook Buffer: Added method to orderbookHolder

* Kraken: removed superfluous integer for sleep

* Bitmex: fixed error return

* cmd/gctcli: force 8 decimal place usage for orderbook streaming

* Kraken: Add checksum and fix bug where we were dropping returned data which was causing artifacts

* Kraken: As per orderbook documentation added in maxdepth field to update to filter depth that goes beyond current scope

* Bitfinex: Tracking down bug on margin-funding, added sequence and checksum validation websocket config on connect (WIP)

* Bitfinex: Complete implementation of checksum

* Bitfinex: Fix funding book insertion and checksum - Dropped updates and deleting items not on book are continuously occuring from stream

* Bitfinex: Fix linter issues

* Bitfinex: Fix even more linter issues.

* Bitmex: Populate orderbook base identification fields to be passed back when error occurrs

* OkGroup: Populate orderbook base identification fields to be passed back when error occurrs

* BTSE: Change string check to 'connect success' to capture multiple user successful strings

* Bitfinex: Updated handling of funding tickers

* Bitfinex: Fix undocumented alignment bug for funding rates

* Bitfinex: Updated error return with more information

* Bitfinex: Change REST fetching to Raw book to keep it in line with websocket implementation. Fix woopsy.

* Localbitcoins: Had to impose a rate limiter to stop errors, fixed return for easier error identification.

* Exchanges: Update failing tests

* LocalBitcoins: Addr nit and bumped time by 1 second for fetching books

* Kraken: Dynamically scale precision based on str return for checksum calculations

* Kraken: Add pair and asset type to validateCRC32 error reponse

* BTSE: Filter out zero amount orderbook price levels in websocket return

* Exchanges: Update orderbook functions to return orderbook base to differentiate errors.

* BTSE: Fix spelling

* Bitmex: Fix error return string

* BTSE: Add orderbook filtering function

* Coinbene: Change wording

* BTSE: Add test for filtering

* Binance: Addr nits, added in variables for buffers and worker amounts and fixed error log messages

* GolangCI: Remove excess 0

* Binance: Reduces double ups on asset and pair in errors

* Binance: Fix error checking
2021-01-04 17:19:55 +11:00

671 lines
19 KiB
Go

package zb
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"regexp"
"strconv"
"strings"
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/log"
)
const (
zbWebsocketAPI = "wss://api.zb.live/websocket"
zWebsocketAddChannel = "addChannel"
zbWebsocketRateLimit = 20
)
// WsConnect initiates a websocket connection
func (z *ZB) WsConnect() error {
if !z.Websocket.IsEnabled() || !z.IsEnabled() {
return errors.New(stream.WebsocketNotEnabled)
}
var dialer websocket.Dialer
err := z.Websocket.Conn.Dial(&dialer, http.Header{})
if err != nil {
return err
}
go z.wsReadData()
return nil
}
// wsReadData handles all the websocket data coming from the websocket
// connection
func (z *ZB) wsReadData() {
z.Websocket.Wg.Add(1)
defer z.Websocket.Wg.Done()
for {
resp := z.Websocket.Conn.ReadMessage()
if resp.Raw == nil {
return
}
err := z.wsHandleData(resp.Raw)
if err != nil {
z.Websocket.DataHandler <- err
}
}
}
func (z *ZB) wsHandleData(respRaw []byte) error {
fixedJSON := z.wsFixInvalidJSON(respRaw)
var result Generic
err := json.Unmarshal(fixedJSON, &result)
if err != nil {
return err
}
if result.No > 0 {
if z.Websocket.Match.IncomingWithData(result.No, fixedJSON) {
return nil
}
}
if result.Code > 0 && result.Code != 1000 {
return fmt.Errorf("%v request failed, message: %v, error code: %v",
z.Name,
result.Message,
wsErrCodes[result.Code])
}
switch {
case strings.Contains(result.Channel, "markets"):
var markets Markets
err := json.Unmarshal(result.Data, &markets)
if err != nil {
return err
}
case strings.Contains(result.Channel, "ticker"):
cPair := strings.Split(result.Channel, currency.UnderscoreDelimiter)
var wsTicker WsTicker
err := json.Unmarshal(fixedJSON, &wsTicker)
if err != nil {
return err
}
p, err := currency.NewPairFromString(cPair[0])
if err != nil {
return err
}
z.Websocket.DataHandler <- &ticker.Price{
ExchangeName: z.Name,
Close: wsTicker.Data.Last,
Volume: wsTicker.Data.Volume24Hr,
High: wsTicker.Data.High,
Low: wsTicker.Data.Low,
Last: wsTicker.Data.Last,
Bid: wsTicker.Data.Buy,
Ask: wsTicker.Data.Sell,
LastUpdated: time.Unix(0, wsTicker.Date*int64(time.Millisecond)),
AssetType: asset.Spot,
Pair: p,
}
case strings.Contains(result.Channel, "depth"):
var depth WsDepth
err := json.Unmarshal(fixedJSON, &depth)
if err != nil {
return err
}
var book orderbook.Base
for i := range depth.Asks {
book.Asks = append(book.Asks, orderbook.Item{
Amount: depth.Asks[i][1].(float64),
Price: depth.Asks[i][0].(float64),
})
}
for i := range depth.Bids {
book.Bids = append(book.Bids, orderbook.Item{
Amount: depth.Bids[i][1].(float64),
Price: depth.Bids[i][0].(float64),
})
}
channelInfo := strings.Split(result.Channel, currency.UnderscoreDelimiter)
cPair, err := currency.NewPairFromString(channelInfo[0])
if err != nil {
return err
}
orderbook.Reverse(book.Asks) // Reverse asks for correct alignment
book.AssetType = asset.Spot
book.Pair = cPair
book.ExchangeName = z.Name
err = z.Websocket.Orderbook.LoadSnapshot(&book)
if err != nil {
return err
}
case strings.Contains(result.Channel, "_order"):
cPair := strings.Split(result.Channel, currency.UnderscoreDelimiter)
var o WsSubmitOrderResponse
err := json.Unmarshal(fixedJSON, &o)
if err != nil {
return err
}
if !o.Success {
return fmt.Errorf("%s - Order %v failed to be placed. %s",
z.Name,
o.Data.EntrustID,
respRaw)
}
p, err := currency.NewPairFromString(cPair[0])
if err != nil {
return err
}
var a asset.Item
a, err = z.GetPairAssetType(p)
if err != nil {
return err
}
z.Websocket.DataHandler <- &order.Detail{
Exchange: z.Name,
ID: strconv.FormatInt(o.Data.EntrustID, 10),
Pair: p,
AssetType: a,
}
case strings.Contains(result.Channel, "_cancelorder"):
cPair := strings.Split(result.Channel, currency.UnderscoreDelimiter)
var o WsSubmitOrderResponse
err := json.Unmarshal(fixedJSON, &o)
if err != nil {
return err
}
if !o.Success {
return fmt.Errorf("%s - Order %v failed to be cancelled. %s",
z.Name,
o.Data.EntrustID,
respRaw)
}
p, err := currency.NewPairFromString(cPair[0])
if err != nil {
return err
}
z.Websocket.DataHandler <- &order.Modify{
Exchange: z.Name,
ID: strconv.FormatInt(o.Data.EntrustID, 10),
Pair: p,
Status: order.Cancelled,
}
case strings.Contains(result.Channel, "trades"):
if !z.IsSaveTradeDataEnabled() {
return nil
}
var tradeData WsTrades
err := json.Unmarshal(fixedJSON, &tradeData)
if err != nil {
return err
}
var trades []trade.Data
for i := range tradeData.Data {
channelInfo := strings.Split(result.Channel, currency.UnderscoreDelimiter)
cPair, err := currency.NewPairFromString(channelInfo[0])
if err != nil {
return err
}
var tSide order.Side
tSide, err = order.StringToOrderSide(tradeData.Data[i].Type)
if err != nil {
return &order.ClassificationError{
Exchange: z.Name,
Err: err,
}
}
trades = append(trades, trade.Data{
Timestamp: time.Unix(tradeData.Data[i].Date, 0),
CurrencyPair: cPair,
AssetType: asset.Spot,
Exchange: z.Name,
Price: tradeData.Data[i].Price,
Amount: tradeData.Data[i].Amount,
Side: tSide,
TID: strconv.FormatInt(tradeData.Data[i].TID, 10),
})
}
return trade.AddTradesToBuffer(z.Name, trades...)
default:
z.Websocket.DataHandler <- stream.UnhandledMessageWarning{
Message: z.Name +
stream.UnhandledMessage +
string(respRaw)}
}
return nil
}
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
func (z *ZB) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
var subscriptions []stream.ChannelSubscription
// market configuration is its own channel
subscriptions = append(subscriptions, stream.ChannelSubscription{
Channel: "markets",
})
channels := []string{"%s_ticker", "%s_depth", "%s_trades"}
enabledCurrencies, err := z.GetEnabledPairs(asset.Spot)
if err != nil {
return nil, err
}
for i := range channels {
for j := range enabledCurrencies {
enabledCurrencies[j].Delimiter = ""
subscriptions = append(subscriptions, stream.ChannelSubscription{
Channel: fmt.Sprintf(channels[i], enabledCurrencies[j].Lower().String()),
Currency: enabledCurrencies[j].Lower(),
Asset: asset.Spot,
})
}
}
return subscriptions, nil
}
// Subscribe sends a websocket message to receive data from the channel
func (z *ZB) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
var errs common.Errors
for i := range channelsToSubscribe {
subscriptionRequest := Subscription{
Event: zWebsocketAddChannel,
Channel: channelsToSubscribe[i].Channel,
}
err := z.Websocket.Conn.SendJSONMessage(subscriptionRequest)
if err != nil {
errs = append(errs, err)
continue
}
z.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i])
}
if errs != nil {
return errs
}
return nil
}
func (z *ZB) wsGenerateSignature(request interface{}) string {
jsonResponse, err := json.Marshal(request)
if err != nil {
log.Error(log.ExchangeSys, err)
return ""
}
hmac := crypto.GetHMAC(crypto.HashMD5,
jsonResponse,
[]byte(crypto.Sha1ToHex(z.API.Credentials.Secret)))
return fmt.Sprintf("%x", hmac)
}
func (z *ZB) wsFixInvalidJSON(json []byte) []byte {
invalidZbJSONRegex := `(\"\[|\"\{)(.*)(\]\"|\}\")`
regexChecker := regexp.MustCompile(invalidZbJSONRegex)
matchingResults := regexChecker.Find(json)
if matchingResults == nil {
return json
}
// Remove first quote character
capturedInvalidZBJSON := strings.Replace(string(matchingResults), "\"", "", 1)
// Remove last quote character
fixedJSON := capturedInvalidZBJSON[:len(capturedInvalidZBJSON)-1]
return []byte(strings.Replace(string(json), string(matchingResults), fixedJSON, 1))
}
func (z *ZB) wsAddSubUser(username, password string) (*WsGetSubUserListResponse, error) {
if !z.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
return nil, fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
}
request := WsAddSubUserRequest{
Memo: "memo",
Password: password,
SubUserName: username,
}
request.Channel = "addSubUser"
request.Event = zWebsocketAddChannel
request.Accesskey = z.API.Credentials.Key
request.No = z.Websocket.Conn.GenerateMessageID(true)
request.Sign = z.wsGenerateSignature(request)
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
if err != nil {
return nil, err
}
var genericResponse Generic
err = json.Unmarshal(resp, &genericResponse)
if err != nil {
return nil, err
}
if genericResponse.Code > 0 && genericResponse.Code != 1000 {
return nil,
fmt.Errorf("%v request failed, message: %v, error code: %v",
z.Name,
genericResponse.Message,
wsErrCodes[genericResponse.Code])
}
var response WsGetSubUserListResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, err
}
return &response, nil
}
func (z *ZB) wsGetSubUserList() (*WsGetSubUserListResponse, error) {
if !z.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
return nil,
fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
}
request := WsAuthenticatedRequest{}
request.Channel = "getSubUserList"
request.Event = zWebsocketAddChannel
request.Accesskey = z.API.Credentials.Key
request.No = z.Websocket.Conn.GenerateMessageID(true)
request.Sign = z.wsGenerateSignature(request)
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
if err != nil {
return nil, err
}
var response WsGetSubUserListResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, err
}
if response.Code > 0 && response.Code != 1000 {
return &response,
fmt.Errorf("%v request failed, message: %v, error code: %v",
z.Name,
response.Message,
wsErrCodes[response.Code])
}
return &response, nil
}
func (z *ZB) wsDoTransferFunds(pair currency.Code, amount float64, fromUserName, toUserName string) (*WsRequestResponse, error) {
if !z.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
return nil,
fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
}
request := WsDoTransferFundsRequest{
Amount: amount,
Currency: pair,
FromUserName: fromUserName,
ToUserName: toUserName,
No: z.Websocket.Conn.GenerateMessageID(true),
}
request.Channel = "doTransferFunds"
request.Event = zWebsocketAddChannel
request.Accesskey = z.API.Credentials.Key
request.Sign = z.wsGenerateSignature(request)
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
if err != nil {
return nil, err
}
var response WsRequestResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, err
}
if response.Code > 0 && response.Code != 1000 {
return &response,
fmt.Errorf("%v request failed, message: %v, error code: %v",
z.Name,
response.Message,
wsErrCodes[response.Code])
}
return &response, nil
}
func (z *ZB) wsCreateSubUserKey(assetPerm, entrustPerm, leverPerm, moneyPerm bool, keyName, toUserID string) (*WsRequestResponse, error) {
if !z.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
return nil,
fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
}
request := WsCreateSubUserKeyRequest{
AssetPerm: assetPerm,
EntrustPerm: entrustPerm,
KeyName: keyName,
LeverPerm: leverPerm,
MoneyPerm: moneyPerm,
No: z.Websocket.Conn.GenerateMessageID(true),
ToUserID: toUserID,
}
request.Channel = "createSubUserKey"
request.Event = zWebsocketAddChannel
request.Accesskey = z.API.Credentials.Key
request.Sign = z.wsGenerateSignature(request)
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
if err != nil {
return nil, err
}
var response WsRequestResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, err
}
if response.Code > 0 && response.Code != 1000 {
return &response,
fmt.Errorf("%v request failed, message: %v, error code: %v",
z.Name,
response.Message,
wsErrCodes[response.Code])
}
return &response, nil
}
func (z *ZB) wsSubmitOrder(pair currency.Pair, amount, price float64, tradeType int64) (*WsSubmitOrderResponse, error) {
if !z.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
return nil,
fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
}
request := WsSubmitOrderRequest{
Amount: amount,
Price: price,
TradeType: tradeType,
No: z.Websocket.Conn.GenerateMessageID(true),
}
request.Channel = pair.String() + "_order"
request.Event = zWebsocketAddChannel
request.Accesskey = z.API.Credentials.Key
request.Sign = z.wsGenerateSignature(request)
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
if err != nil {
return nil, err
}
var response WsSubmitOrderResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, err
}
if response.Code > 0 && response.Code != 1000 {
return &response,
fmt.Errorf("%v request failed, message: %v, error code: %v",
z.Name,
response.Message,
wsErrCodes[response.Code])
}
return &response, nil
}
func (z *ZB) wsCancelOrder(pair currency.Pair, orderID int64) (*WsCancelOrderResponse, error) {
if !z.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
return nil,
fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
}
request := WsCancelOrderRequest{
ID: orderID,
No: z.Websocket.Conn.GenerateMessageID(true),
}
request.Channel = pair.String() + "_cancelorder"
request.Event = zWebsocketAddChannel
request.Accesskey = z.API.Credentials.Key
request.Sign = z.wsGenerateSignature(request)
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
if err != nil {
return nil, err
}
var response WsCancelOrderResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, err
}
if response.Code > 0 && response.Code != 1000 {
return &response,
fmt.Errorf("%v request failed, message: %v, error code: %v",
z.Name,
response.Message,
wsErrCodes[response.Code])
}
return &response, nil
}
func (z *ZB) wsGetOrder(pair currency.Pair, orderID int64) (*WsGetOrderResponse, error) {
if !z.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
return nil,
fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
}
request := WsGetOrderRequest{
ID: orderID,
No: z.Websocket.Conn.GenerateMessageID(true),
}
request.Channel = pair.String() + "_getorder"
request.Event = zWebsocketAddChannel
request.Accesskey = z.API.Credentials.Key
request.Sign = z.wsGenerateSignature(request)
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
if err != nil {
return nil, err
}
var response WsGetOrderResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, err
}
if response.Code > 0 && response.Code != 1000 {
return &response,
fmt.Errorf("%v request failed, message: %v, error code: %v",
z.Name,
response.Message,
wsErrCodes[response.Code])
}
return &response, nil
}
func (z *ZB) wsGetOrders(pair currency.Pair, pageIndex, tradeType int64) (*WsGetOrdersResponse, error) {
if !z.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
return nil,
fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
}
request := WsGetOrdersRequest{
PageIndex: pageIndex,
TradeType: tradeType,
No: z.Websocket.Conn.GenerateMessageID(true),
}
request.Channel = pair.String() + "_getorders"
request.Event = zWebsocketAddChannel
request.Accesskey = z.API.Credentials.Key
request.Sign = z.wsGenerateSignature(request)
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
if err != nil {
return nil, err
}
var response WsGetOrdersResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, err
}
if response.Code > 0 && response.Code != 1000 {
return &response,
fmt.Errorf("%v request failed, message: %v, error code: %v",
z.Name,
response.Message,
wsErrCodes[response.Code])
}
return &response, nil
}
func (z *ZB) wsGetOrdersIgnoreTradeType(pair currency.Pair, pageIndex, pageSize int64) (*WsGetOrdersIgnoreTradeTypeResponse, error) {
if !z.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
return nil,
fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
}
request := WsGetOrdersIgnoreTradeTypeRequest{
PageIndex: pageIndex,
PageSize: pageSize,
No: z.Websocket.Conn.GenerateMessageID(true),
}
request.Channel = pair.String() + "_getordersignoretradetype"
request.Event = zWebsocketAddChannel
request.Accesskey = z.API.Credentials.Key
request.Sign = z.wsGenerateSignature(request)
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
if err != nil {
return nil, err
}
var response WsGetOrdersIgnoreTradeTypeResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, err
}
if response.Code > 0 && response.Code != 1000 {
return &response,
fmt.Errorf("%v request failed, message: %v, error code: %v",
z.Name,
response.Message,
wsErrCodes[response.Code])
}
return &response, nil
}
func (z *ZB) wsGetAccountInfoRequest() (*WsGetAccountInfoResponse, error) {
if !z.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
return nil,
fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
}
request := WsAuthenticatedRequest{
Channel: "getaccountinfo",
Event: zWebsocketAddChannel,
Accesskey: z.API.Credentials.Key,
No: z.Websocket.Conn.GenerateMessageID(true),
}
request.Sign = z.wsGenerateSignature(request)
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
if err != nil {
return nil, err
}
var response WsGetAccountInfoResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, err
}
if response.Code > 0 && response.Code != 1000 {
return &response,
fmt.Errorf("%v request failed, message: %v, error code: %v",
z.Name,
response.Message,
wsErrCodes[response.Code])
}
return &response, nil
}