Files
gocryptotrader/exchanges/hitbtc/hitbtc_websocket.go
Ryan O'Hara-Reid d2561402c4 common: update Errors type (#1129)
* common: adjust common error slice to allow multi errors.Is matching and conform to interface better

* zb: forgot to save?

* linties: fixies

* linties: word change as well.

* nitters: glorious

* buts

* nitters: fix glorious bug

* Update common/common.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* nitters: shifty

---------

Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
Co-authored-by: Scott <gloriousCode@users.noreply.github.com>
2023-02-20 10:48:24 +11:00

833 lines
22 KiB
Go

package hitbtc
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"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"
"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 (
hitbtcWebsocketAddress = "wss://api.hitbtc.com/api/2/ws"
rpcVersion = "2.0"
rateLimit = 20
errAuthFailed = 1002
)
// WsConnect starts a new connection with the websocket API
func (h *HitBTC) WsConnect() error {
if !h.Websocket.IsEnabled() || !h.IsEnabled() {
return errors.New(stream.WebsocketNotEnabled)
}
var dialer websocket.Dialer
err := h.Websocket.Conn.Dial(&dialer, http.Header{})
if err != nil {
return err
}
h.Websocket.Wg.Add(1)
go h.wsReadData()
if h.Websocket.CanUseAuthenticatedEndpoints() {
err = h.wsLogin(context.TODO())
if err != nil {
log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", h.Name, err)
}
}
return nil
}
// wsReadData receives and passes on websocket messages for processing
func (h *HitBTC) wsReadData() {
defer h.Websocket.Wg.Done()
for {
resp := h.Websocket.Conn.ReadMessage()
if resp.Raw == nil {
return
}
err := h.wsHandleData(resp.Raw)
if err != nil {
h.Websocket.DataHandler <- err
}
}
}
func (h *HitBTC) wsGetTableName(respRaw []byte) (string, error) {
var init capture
err := json.Unmarshal(respRaw, &init)
if err != nil {
return "", err
}
if init.Error.Code == errAuthFailed {
h.Websocket.SetCanUseAuthenticatedEndpoints(false)
}
if init.ID > 0 {
if h.Websocket.Match.IncomingWithData(init.ID, respRaw) {
return "", nil
}
}
if init.Error.Message != "" || init.Error.Code != 0 {
return "", fmt.Errorf("code: %d, Message: %s",
init.Error.Code,
init.Error.Message)
}
if _, ok := init.Result.(bool); ok {
return "", nil
}
if init.Method != "" {
return init.Method, nil
}
switch resultType := init.Result.(type) {
case map[string]interface{}:
if reportType, ok := resultType["reportType"].(string); ok {
return reportType, nil
}
// check for ids - means it was a specific request
// and can't go through normal processing
if responseID, ok := resultType["id"].(string); ok {
if responseID != "" {
return "", nil
}
}
case []interface{}:
if len(resultType) == 0 {
h.Websocket.DataHandler <- fmt.Sprintf("No data returned. ID: %v", init.ID)
return "", nil
}
data, ok := resultType[0].(map[string]interface{})
if !ok {
return "", errors.New("unable to type assert data")
}
if _, ok := data["clientOrderId"]; ok {
return "order", nil
} else if _, ok := data["available"]; ok {
return "trading", nil
}
}
h.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: h.Name + stream.UnhandledMessage + string(respRaw)}
return "", nil
}
func (h *HitBTC) wsHandleData(respRaw []byte) error {
name, err := h.wsGetTableName(respRaw)
if err != nil {
return err
}
switch name {
case "":
return nil
case "ticker":
var wsTicker WsTicker
err := json.Unmarshal(respRaw, &wsTicker)
if err != nil {
return err
}
pairs, err := h.GetEnabledPairs(asset.Spot)
if err != nil {
return err
}
format, err := h.GetPairFormat(asset.Spot, true)
if err != nil {
return err
}
p, err := currency.NewPairFromFormattedPairs(wsTicker.Params.Symbol,
pairs,
format)
if err != nil {
return err
}
h.Websocket.DataHandler <- &ticker.Price{
ExchangeName: h.Name,
Open: wsTicker.Params.Open,
Volume: wsTicker.Params.Volume,
QuoteVolume: wsTicker.Params.VolumeQuote,
High: wsTicker.Params.High,
Low: wsTicker.Params.Low,
Bid: wsTicker.Params.Bid,
Ask: wsTicker.Params.Ask,
Last: wsTicker.Params.Last,
LastUpdated: wsTicker.Params.Timestamp,
AssetType: asset.Spot,
Pair: p,
}
case "snapshotOrderbook":
var obSnapshot WsOrderbook
err := json.Unmarshal(respRaw, &obSnapshot)
if err != nil {
return err
}
err = h.WsProcessOrderbookSnapshot(&obSnapshot)
if err != nil {
return err
}
case "updateOrderbook":
var obUpdate WsOrderbook
err := json.Unmarshal(respRaw, &obUpdate)
if err != nil {
return err
}
err = h.WsProcessOrderbookUpdate(&obUpdate)
if err != nil {
return err
}
case "snapshotTrades", "updateTrades":
if !h.IsSaveTradeDataEnabled() {
return nil
}
var tradeSnapshot WsTrade
err := json.Unmarshal(respRaw, &tradeSnapshot)
if err != nil {
return err
}
var trades []trade.Data
p, err := currency.NewPairFromString(tradeSnapshot.Params.Symbol)
if err != nil {
return &order.ClassificationError{
Exchange: h.Name,
Err: err,
}
}
for i := range tradeSnapshot.Params.Data {
side, err := order.StringToOrderSide(tradeSnapshot.Params.Data[i].Side)
if err != nil {
return &order.ClassificationError{
Exchange: h.Name,
Err: err,
}
}
trades = append(trades, trade.Data{
Timestamp: tradeSnapshot.Params.Data[i].Timestamp,
Exchange: h.Name,
CurrencyPair: p,
AssetType: asset.Spot,
Price: tradeSnapshot.Params.Data[i].Price,
Amount: tradeSnapshot.Params.Data[i].Quantity,
Side: side,
TID: strconv.FormatInt(tradeSnapshot.Params.Data[i].ID, 10),
})
}
return trade.AddTradesToBuffer(h.Name, trades...)
case "activeOrders":
var o wsActiveOrdersResponse
err := json.Unmarshal(respRaw, &o)
if err != nil {
return err
}
for i := range o.Params {
err = h.wsHandleOrderData(&o.Params[i])
if err != nil {
return err
}
}
case "trading":
var trades WsGetTradingBalanceResponse
err := json.Unmarshal(respRaw, &trades)
if err != nil {
return err
}
h.Websocket.DataHandler <- trades
case "report":
var o wsReportResponse
err := json.Unmarshal(respRaw, &o)
if err != nil {
return err
}
err = h.wsHandleOrderData(&o.OrderData)
if err != nil {
return err
}
case "order":
var o wsActiveOrderRequestResponse
err := json.Unmarshal(respRaw, &o)
if err != nil {
return err
}
for i := range o.OrderData {
err = h.wsHandleOrderData(&o.OrderData[i])
if err != nil {
return err
}
}
case
"replaced",
"canceled",
"new":
var o wsOrderResponse
err := json.Unmarshal(respRaw, &o)
if err != nil {
return err
}
err = h.wsHandleOrderData(&o.OrderData)
if err != nil {
return err
}
default:
h.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: h.Name + stream.UnhandledMessage + string(respRaw)}
return nil
}
return nil
}
// WsProcessOrderbookSnapshot processes a full orderbook snapshot to a local cache
func (h *HitBTC) WsProcessOrderbookSnapshot(ob *WsOrderbook) error {
if len(ob.Params.Bid) == 0 || len(ob.Params.Ask) == 0 {
return errors.New("no orderbooks to process")
}
newOrderBook := orderbook.Base{
Bids: make(orderbook.Items, len(ob.Params.Bid)),
Asks: make(orderbook.Items, len(ob.Params.Ask)),
}
for i := range ob.Params.Bid {
newOrderBook.Bids[i] = orderbook.Item{
Amount: ob.Params.Bid[i].Size,
Price: ob.Params.Bid[i].Price,
}
}
for i := range ob.Params.Ask {
newOrderBook.Asks[i] = orderbook.Item{
Amount: ob.Params.Ask[i].Size,
Price: ob.Params.Ask[i].Price,
}
}
pairs, err := h.GetEnabledPairs(asset.Spot)
if err != nil {
return err
}
format, err := h.GetPairFormat(asset.Spot, true)
if err != nil {
return err
}
p, err := currency.NewPairFromFormattedPairs(ob.Params.Symbol,
pairs,
format)
if err != nil {
h.Websocket.DataHandler <- err
return err
}
newOrderBook.Asset = asset.Spot
newOrderBook.Pair = p
newOrderBook.Exchange = h.Name
newOrderBook.VerifyOrderbook = h.CanVerifyOrderbook
return h.Websocket.Orderbook.LoadSnapshot(&newOrderBook)
}
func (h *HitBTC) wsHandleOrderData(o *wsOrderData) error {
var trades []order.TradeHistory
if o.TradeID > 0 {
trades = append(trades, order.TradeHistory{
Price: o.TradePrice,
Amount: o.TradeQuantity,
Fee: o.TradeFee,
Exchange: h.Name,
TID: strconv.FormatFloat(o.TradeID, 'f', -1, 64),
Timestamp: o.UpdatedAt,
})
}
oType, err := order.StringToOrderType(o.Type)
if err != nil {
h.Websocket.DataHandler <- order.ClassificationError{
Exchange: h.Name,
OrderID: o.ID,
Err: err,
}
}
o.Status = strings.Replace(o.Status, "canceled", "cancelled", 1)
oStatus, err := order.StringToOrderStatus(o.Status)
if err != nil {
h.Websocket.DataHandler <- order.ClassificationError{
Exchange: h.Name,
OrderID: o.ID,
Err: err,
}
}
oSide, err := order.StringToOrderSide(o.Side)
if err != nil {
h.Websocket.DataHandler <- order.ClassificationError{
Exchange: h.Name,
OrderID: o.ID,
Err: err,
}
}
p, err := currency.NewPairFromString(o.Symbol)
if err != nil {
h.Websocket.DataHandler <- order.ClassificationError{
Exchange: h.Name,
OrderID: o.ID,
Err: err,
}
}
var a asset.Item
a, err = h.GetPairAssetType(p)
if err != nil {
return err
}
h.Websocket.DataHandler <- &order.Detail{
Price: o.Price,
Amount: o.Quantity,
ExecutedAmount: o.CumQuantity,
RemainingAmount: o.Quantity - o.CumQuantity,
Exchange: h.Name,
OrderID: o.ID,
Type: oType,
Side: oSide,
Status: oStatus,
AssetType: a,
Date: o.CreatedAt,
LastUpdated: o.UpdatedAt,
Pair: p,
Trades: trades,
}
return nil
}
// WsProcessOrderbookUpdate updates a local cache
func (h *HitBTC) WsProcessOrderbookUpdate(update *WsOrderbook) error {
if len(update.Params.Bid) == 0 && len(update.Params.Ask) == 0 {
// Periodically HitBTC sends empty updates which includes a sequence
// can return this as nil.
return nil
}
bids := make(orderbook.Items, len(update.Params.Bid))
for i := range update.Params.Bid {
bids[i] = orderbook.Item{
Price: update.Params.Bid[i].Price,
Amount: update.Params.Bid[i].Size,
}
}
asks := make(orderbook.Items, len(update.Params.Ask))
for i := range update.Params.Ask {
asks[i] = orderbook.Item{
Price: update.Params.Ask[i].Price,
Amount: update.Params.Ask[i].Size,
}
}
pairs, err := h.GetEnabledPairs(asset.Spot)
if err != nil {
return err
}
format, err := h.GetPairFormat(asset.Spot, true)
if err != nil {
return err
}
p, err := currency.NewPairFromFormattedPairs(update.Params.Symbol,
pairs,
format)
if err != nil {
return err
}
return h.Websocket.Orderbook.Update(&orderbook.Update{
Asks: asks,
Bids: bids,
Pair: p,
UpdateID: update.Params.Sequence,
Asset: asset.Spot,
})
}
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
func (h *HitBTC) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
var channels = []string{"subscribeTicker",
"subscribeOrderbook",
"subscribeTrades",
"subscribeCandles"}
var subscriptions []stream.ChannelSubscription
if h.Websocket.CanUseAuthenticatedEndpoints() {
subscriptions = append(subscriptions, stream.ChannelSubscription{
Channel: "subscribeReports",
})
}
enabledCurrencies, err := h.GetEnabledPairs(asset.Spot)
if err != nil {
return nil, err
}
for i := range channels {
for j := range enabledCurrencies {
fpair, err := h.FormatExchangeCurrency(enabledCurrencies[j], asset.Spot)
if err != nil {
return nil, err
}
enabledCurrencies[j].Delimiter = ""
subscriptions = append(subscriptions, stream.ChannelSubscription{
Channel: channels[i],
Currency: fpair,
Asset: asset.Spot,
})
}
}
return subscriptions, nil
}
// Subscribe sends a websocket message to receive data from the channel
func (h *HitBTC) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
var errs error
for i := range channelsToSubscribe {
subscribe := WsRequest{
Method: channelsToSubscribe[i].Channel,
ID: h.Websocket.Conn.GenerateMessageID(false),
}
if channelsToSubscribe[i].Currency.String() != "" {
subscribe.Params.Symbol = channelsToSubscribe[i].Currency.String()
}
if strings.EqualFold(channelsToSubscribe[i].Channel, "subscribeTrades") {
subscribe.Params.Limit = 100
} else if strings.EqualFold(channelsToSubscribe[i].Channel, "subscribeCandles") {
subscribe.Params.Period = "M30"
subscribe.Params.Limit = 100
}
err := h.Websocket.Conn.SendJSONMessage(subscribe)
if err != nil {
errs = common.AppendError(errs, err)
continue
}
h.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i])
}
if errs != nil {
return errs
}
return nil
}
// Unsubscribe sends a websocket message to stop receiving data from the channel
func (h *HitBTC) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
var errs error
for i := range channelsToUnsubscribe {
unsubscribeChannel := strings.Replace(channelsToUnsubscribe[i].Channel,
"subscribe",
"unsubscribe",
1)
unsubscribe := WsNotification{
JSONRPCVersion: rpcVersion,
Method: unsubscribeChannel,
}
unsubscribe.Params.Symbol = channelsToUnsubscribe[i].Currency.String()
if strings.EqualFold(unsubscribeChannel, "unsubscribeTrades") {
unsubscribe.Params.Limit = 100
} else if strings.EqualFold(unsubscribeChannel, "unsubscribeCandles") {
unsubscribe.Params.Period = "M30"
unsubscribe.Params.Limit = 100
}
err := h.Websocket.Conn.SendJSONMessage(unsubscribe)
if err != nil {
errs = common.AppendError(errs, err)
continue
}
h.Websocket.RemoveSuccessfulUnsubscriptions(channelsToUnsubscribe[i])
}
if errs != nil {
return errs
}
return nil
}
// Unsubscribe sends a websocket message to stop receiving data from the channel
func (h *HitBTC) wsLogin(ctx context.Context) error {
if !h.IsWebsocketAuthenticationSupported() {
return fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", h.Name)
}
creds, err := h.GetCredentials(ctx)
if err != nil {
return err
}
h.Websocket.SetCanUseAuthenticatedEndpoints(true)
n := strconv.FormatInt(time.Now().Unix(), 10)
hmac, err := crypto.GetHMAC(crypto.HashSHA256,
[]byte(n),
[]byte(creds.Secret))
if err != nil {
return err
}
request := WsLoginRequest{
Method: "login",
Params: WsLoginData{
Algo: "HS256",
PKey: creds.Key,
Nonce: n,
Signature: crypto.HexEncodeToString(hmac),
},
ID: h.Websocket.Conn.GenerateMessageID(false),
}
err = h.Websocket.Conn.SendJSONMessage(request)
if err != nil {
h.Websocket.SetCanUseAuthenticatedEndpoints(false)
return err
}
return nil
}
// wsPlaceOrder sends a websocket message to submit an order
func (h *HitBTC) wsPlaceOrder(pair currency.Pair, side string, price, quantity float64) (*WsSubmitOrderSuccessResponse, error) {
if !h.Websocket.CanUseAuthenticatedEndpoints() {
return nil, fmt.Errorf("%v not authenticated, cannot place order", h.Name)
}
id := h.Websocket.Conn.GenerateMessageID(false)
fpair, err := h.FormatExchangeCurrency(pair, asset.Spot)
if err != nil {
return nil, err
}
request := WsSubmitOrderRequest{
Method: "newOrder",
Params: WsSubmitOrderRequestData{
ClientOrderID: id,
Symbol: fpair.String(),
Side: strings.ToLower(side),
Price: price,
Quantity: quantity,
},
ID: id,
}
resp, err := h.Websocket.Conn.SendMessageReturnResponse(id, request)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
var response WsSubmitOrderSuccessResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
if response.Error.Code > 0 || response.Error.Message != "" {
return &response, fmt.Errorf("%v Error:%v Message:%v", h.Name, response.Error.Code, response.Error.Message)
}
return &response, nil
}
// wsCancelOrder sends a websocket message to cancel an order
func (h *HitBTC) wsCancelOrder(clientOrderID string) (*WsCancelOrderResponse, error) {
if !h.Websocket.CanUseAuthenticatedEndpoints() {
return nil, fmt.Errorf("%v not authenticated, cannot place order", h.Name)
}
request := WsCancelOrderRequest{
Method: "cancelOrder",
Params: WsCancelOrderRequestData{
ClientOrderID: clientOrderID,
},
ID: h.Websocket.Conn.GenerateMessageID(false),
}
resp, err := h.Websocket.Conn.SendMessageReturnResponse(request.ID, request)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
var response WsCancelOrderResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
if response.Error.Code > 0 || response.Error.Message != "" {
return &response, fmt.Errorf("%v Error:%v Message:%v", h.Name, response.Error.Code, response.Error.Message)
}
return &response, nil
}
// wsReplaceOrder sends a websocket message to replace an order
func (h *HitBTC) wsReplaceOrder(clientOrderID string, quantity, price float64) (*WsReplaceOrderResponse, error) {
if !h.Websocket.CanUseAuthenticatedEndpoints() {
return nil, fmt.Errorf("%v not authenticated, cannot place order", h.Name)
}
request := WsReplaceOrderRequest{
Method: "cancelReplaceOrder",
Params: WsReplaceOrderRequestData{
ClientOrderID: clientOrderID,
RequestClientID: strconv.FormatInt(time.Now().Unix(), 10),
Quantity: quantity,
Price: price,
},
ID: h.Websocket.Conn.GenerateMessageID(false),
}
resp, err := h.Websocket.Conn.SendMessageReturnResponse(request.ID, request)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
var response WsReplaceOrderResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
if response.Error.Code > 0 || response.Error.Message != "" {
return &response, fmt.Errorf("%v Error:%v Message:%v", h.Name, response.Error.Code, response.Error.Message)
}
return &response, nil
}
// wsGetActiveOrders sends a websocket message to get all active orders
func (h *HitBTC) wsGetActiveOrders() (*wsActiveOrdersResponse, error) {
if !h.Websocket.CanUseAuthenticatedEndpoints() {
return nil, fmt.Errorf("%v not authenticated, cannot get active orders", h.Name)
}
request := WsReplaceOrderRequest{
Method: "getOrders",
Params: WsReplaceOrderRequestData{},
ID: h.Websocket.Conn.GenerateMessageID(false),
}
resp, err := h.Websocket.Conn.SendMessageReturnResponse(request.ID, request)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
var response wsActiveOrdersResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
if response.Error.Code > 0 || response.Error.Message != "" {
return &response, fmt.Errorf("%v Error:%v Message:%v", h.Name, response.Error.Code, response.Error.Message)
}
return &response, nil
}
// wsGetTradingBalance sends a websocket message to get trading balance
func (h *HitBTC) wsGetTradingBalance() (*WsGetTradingBalanceResponse, error) {
if !h.Websocket.CanUseAuthenticatedEndpoints() {
return nil, fmt.Errorf("%v not authenticated, cannot place order", h.Name)
}
request := WsReplaceOrderRequest{
Method: "getTradingBalance",
Params: WsReplaceOrderRequestData{},
ID: h.Websocket.Conn.GenerateMessageID(false),
}
resp, err := h.Websocket.Conn.SendMessageReturnResponse(request.ID, request)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
var response WsGetTradingBalanceResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
if response.Error.Code > 0 || response.Error.Message != "" {
return &response, fmt.Errorf("%v Error:%v Message:%v", h.Name, response.Error.Code, response.Error.Message)
}
return &response, nil
}
// wsGetCurrencies sends a websocket message to get trading balance
func (h *HitBTC) wsGetCurrencies(currencyItem currency.Code) (*WsGetCurrenciesResponse, error) {
request := WsGetCurrenciesRequest{
Method: "getCurrency",
Params: WsGetCurrenciesRequestParameters{
Currency: currencyItem,
},
ID: h.Websocket.Conn.GenerateMessageID(false),
}
resp, err := h.Websocket.Conn.SendMessageReturnResponse(request.ID, request)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
var response WsGetCurrenciesResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
if response.Error.Code > 0 || response.Error.Message != "" {
return &response, fmt.Errorf("%v Error:%v Message:%v", h.Name, response.Error.Code, response.Error.Message)
}
return &response, nil
}
// wsGetSymbols sends a websocket message to get trading balance
func (h *HitBTC) wsGetSymbols(c currency.Pair) (*WsGetSymbolsResponse, error) {
fpair, err := h.FormatExchangeCurrency(c, asset.Spot)
if err != nil {
return nil, err
}
request := WsGetSymbolsRequest{
Method: "getSymbol",
Params: WsGetSymbolsRequestParameters{
Symbol: fpair.String(),
},
ID: h.Websocket.Conn.GenerateMessageID(false),
}
resp, err := h.Websocket.Conn.SendMessageReturnResponse(request.ID, request)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
var response WsGetSymbolsResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
if response.Error.Code > 0 || response.Error.Message != "" {
return &response, fmt.Errorf("%v Error:%v Message:%v", h.Name, response.Error.Code, response.Error.Message)
}
return &response, nil
}
// wsGetSymbols sends a websocket message to get trading balance
func (h *HitBTC) wsGetTrades(c currency.Pair, limit int64, sort, by string) (*WsGetTradesResponse, error) {
fpair, err := h.FormatExchangeCurrency(c, asset.Spot)
if err != nil {
return nil, err
}
request := WsGetTradesRequest{
Method: "getTrades",
Params: WsGetTradesRequestParameters{
Symbol: fpair.String(),
Limit: limit,
Sort: sort,
By: by,
},
ID: h.Websocket.Conn.GenerateMessageID(false),
}
resp, err := h.Websocket.Conn.SendMessageReturnResponse(request.ID, request)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
var response WsGetTradesResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, fmt.Errorf("%v %v", h.Name, err)
}
if response.Error.Code > 0 || response.Error.Message != "" {
return &response, fmt.Errorf("%v Error:%v Message:%v", h.Name, response.Error.Code, response.Error.Message)
}
return &response, nil
}