mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-06-01 23:16:51 +00:00
* First pass at adding new logging system * NewLogger * NewLogger * WIP * silly bug fix * :D removed files * removed old logging interface * added tests * added tests * Started to add new lines to all f calls * Added subsystem log types * Logger improvements * Further performance improvements * changes to logger and sublogger creation * Renamed Logging types * removed old print statement * changes based on feedback * moved sublogger types to own file * :) * added console as output type * added get level command * added get/set log level via grpc command * added check for output being empty for migration support * first pass at log rotation * added log rotation * :D derp fixed * added tests * changes based on feedback * changed log type * comments * renamed file -> fileSettings * typo fix * changes based on feedback * gofmt ran on additional files * gofmt ran on additional files
611 lines
17 KiB
Go
611 lines
17 KiB
Go
package coinut
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gorilla/websocket"
|
|
"github.com/thrasher-/gocryptotrader/common"
|
|
"github.com/thrasher-/gocryptotrader/common/crypto"
|
|
"github.com/thrasher-/gocryptotrader/currency"
|
|
exchange "github.com/thrasher-/gocryptotrader/exchanges"
|
|
"github.com/thrasher-/gocryptotrader/exchanges/asset"
|
|
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
|
|
log "github.com/thrasher-/gocryptotrader/logger"
|
|
)
|
|
|
|
const coinutWebsocketURL = "wss://wsapi.coinut.com"
|
|
const coinutWebsocketRateLimit = 30 * time.Millisecond
|
|
|
|
var nNonce map[int64]string
|
|
var channels map[string]chan []byte
|
|
var instrumentListByString map[string]int64
|
|
var instrumentListByCode map[int64]string
|
|
var populatedList bool
|
|
|
|
// NOTE for speed considerations
|
|
// wss://wsapi-as.coinut.com
|
|
// wss://wsapi-na.coinut.com
|
|
// wss://wsapi-eu.coinut.com
|
|
|
|
// WsConnect intiates a websocket connection
|
|
func (c *COINUT) WsConnect() error {
|
|
if !c.Websocket.IsEnabled() || !c.IsEnabled() {
|
|
return errors.New(exchange.WebsocketNotEnabled)
|
|
}
|
|
|
|
var Dialer websocket.Dialer
|
|
|
|
if c.Websocket.GetProxyAddress() != "" {
|
|
proxy, err := url.Parse(c.Websocket.GetProxyAddress())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
Dialer.Proxy = http.ProxyURL(proxy)
|
|
}
|
|
|
|
var err error
|
|
c.WebsocketConn, _, err = Dialer.Dial(c.Websocket.GetWebsocketURL(),
|
|
http.Header{})
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !populatedList {
|
|
instrumentListByString = make(map[string]int64)
|
|
instrumentListByCode = make(map[int64]string)
|
|
|
|
err = c.WsSetInstrumentList()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
populatedList = true
|
|
}
|
|
c.wsAuthenticate()
|
|
c.GenerateDefaultSubscriptions()
|
|
|
|
// define bi-directional communication
|
|
channels = make(map[string]chan []byte)
|
|
channels["hb"] = make(chan []byte, 1)
|
|
|
|
go c.WsHandleData()
|
|
return nil
|
|
}
|
|
|
|
// WsReadData reads data from the websocket connection
|
|
func (c *COINUT) WsReadData() (exchange.WebsocketResponse, error) {
|
|
_, resp, err := c.WebsocketConn.ReadMessage()
|
|
if err != nil {
|
|
return exchange.WebsocketResponse{}, err
|
|
}
|
|
|
|
c.Websocket.TrafficAlert <- struct{}{}
|
|
return exchange.WebsocketResponse{Raw: resp}, nil
|
|
}
|
|
|
|
// WsHandleData handles read data
|
|
func (c *COINUT) WsHandleData() {
|
|
c.Websocket.Wg.Add(1)
|
|
|
|
defer func() {
|
|
c.Websocket.Wg.Done()
|
|
}()
|
|
|
|
for {
|
|
select {
|
|
case <-c.Websocket.ShutdownC:
|
|
return
|
|
|
|
default:
|
|
resp, err := c.WsReadData()
|
|
if err != nil {
|
|
c.Websocket.DataHandler <- err
|
|
return
|
|
}
|
|
|
|
if strings.HasPrefix(string(resp.Raw), "[") {
|
|
var incoming []wsResponse
|
|
err = common.JSONDecode(resp.Raw, &incoming)
|
|
if err != nil {
|
|
c.Websocket.DataHandler <- err
|
|
continue
|
|
}
|
|
for i := range incoming {
|
|
var individualJSON []byte
|
|
individualJSON, err = common.JSONEncode(incoming[i])
|
|
if err != nil {
|
|
c.Websocket.DataHandler <- err
|
|
continue
|
|
}
|
|
c.wsProcessResponse(individualJSON)
|
|
}
|
|
|
|
} else {
|
|
var incoming wsResponse
|
|
err = common.JSONDecode(resp.Raw, &incoming)
|
|
if err != nil {
|
|
c.Websocket.DataHandler <- err
|
|
continue
|
|
}
|
|
c.wsProcessResponse(resp.Raw)
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *COINUT) wsProcessResponse(resp []byte) {
|
|
var incoming wsResponse
|
|
err := common.JSONDecode(resp, &incoming)
|
|
if err != nil {
|
|
c.Websocket.DataHandler <- err
|
|
return
|
|
}
|
|
switch incoming.Reply {
|
|
case "login":
|
|
var login WsLoginResponse
|
|
err := common.JSONDecode(resp, &login)
|
|
if err != nil {
|
|
c.Websocket.DataHandler <- err
|
|
return
|
|
}
|
|
c.Websocket.SetCanUseAuthenticatedEndpoints(login.Username == c.API.Credentials.ClientID)
|
|
c.Websocket.DataHandler <- login
|
|
case "hb":
|
|
channels["hb"] <- resp
|
|
case "inst_tick":
|
|
var ticker WsTicker
|
|
err := common.JSONDecode(resp, &ticker)
|
|
if err != nil {
|
|
c.Websocket.DataHandler <- err
|
|
return
|
|
}
|
|
currencyPair := instrumentListByCode[ticker.InstID]
|
|
c.Websocket.DataHandler <- exchange.TickerData{
|
|
Timestamp: time.Unix(0, ticker.Timestamp),
|
|
Pair: currency.NewPairFromString(currencyPair),
|
|
Exchange: c.GetName(),
|
|
AssetType: asset.Spot,
|
|
HighPrice: ticker.HighestBuy,
|
|
LowPrice: ticker.LowestSell,
|
|
ClosePrice: ticker.Last,
|
|
Quantity: ticker.Volume,
|
|
}
|
|
|
|
case "inst_order_book":
|
|
var orderbooksnapshot WsOrderbookSnapshot
|
|
err := common.JSONDecode(resp, &orderbooksnapshot)
|
|
if err != nil {
|
|
c.Websocket.DataHandler <- err
|
|
return
|
|
}
|
|
err = c.WsProcessOrderbookSnapshot(&orderbooksnapshot)
|
|
if err != nil {
|
|
c.Websocket.DataHandler <- err
|
|
return
|
|
}
|
|
currencyPair := instrumentListByCode[orderbooksnapshot.InstID]
|
|
c.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
|
|
Exchange: c.GetName(),
|
|
Asset: asset.Spot,
|
|
Pair: currency.NewPairFromString(currencyPair),
|
|
}
|
|
case "inst_order_book_update":
|
|
var orderbookUpdate WsOrderbookUpdate
|
|
err := common.JSONDecode(resp, &orderbookUpdate)
|
|
if err != nil {
|
|
c.Websocket.DataHandler <- err
|
|
return
|
|
}
|
|
err = c.WsProcessOrderbookUpdate(&orderbookUpdate)
|
|
if err != nil {
|
|
c.Websocket.DataHandler <- err
|
|
return
|
|
}
|
|
currencyPair := instrumentListByCode[orderbookUpdate.InstID]
|
|
c.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
|
|
Exchange: c.GetName(),
|
|
Asset: asset.Spot,
|
|
Pair: currency.NewPairFromString(currencyPair),
|
|
}
|
|
case "inst_trade":
|
|
var tradeSnap WsTradeSnapshot
|
|
err := common.JSONDecode(resp, &tradeSnap)
|
|
if err != nil {
|
|
c.Websocket.DataHandler <- err
|
|
return
|
|
}
|
|
|
|
case "inst_trade_update":
|
|
var tradeUpdate WsTradeUpdate
|
|
err := common.JSONDecode(resp, &tradeUpdate)
|
|
if err != nil {
|
|
c.Websocket.DataHandler <- err
|
|
return
|
|
}
|
|
currencyPair := instrumentListByCode[tradeUpdate.InstID]
|
|
c.Websocket.DataHandler <- exchange.TradeData{
|
|
Timestamp: time.Unix(tradeUpdate.Timestamp, 0),
|
|
CurrencyPair: currency.NewPairFromString(currencyPair),
|
|
AssetType: asset.Spot,
|
|
Exchange: c.GetName(),
|
|
Price: tradeUpdate.Price,
|
|
Side: tradeUpdate.Side,
|
|
}
|
|
case "user_balance":
|
|
var userBalance WsUserBalanceResponse
|
|
err := common.JSONDecode(resp, &userBalance)
|
|
if err != nil {
|
|
c.Websocket.DataHandler <- err
|
|
return
|
|
}
|
|
c.Websocket.DataHandler <- userBalance
|
|
case "new_order":
|
|
var newOrder WsNewOrderResponse
|
|
err := common.JSONDecode(resp, &newOrder)
|
|
if err != nil {
|
|
c.Websocket.DataHandler <- err
|
|
return
|
|
}
|
|
c.Websocket.DataHandler <- newOrder
|
|
case "order_accepted":
|
|
var orderAccepted WsOrderAcceptedResponse
|
|
err := common.JSONDecode(resp, &orderAccepted)
|
|
if err != nil {
|
|
c.Websocket.DataHandler <- err
|
|
return
|
|
}
|
|
c.Websocket.DataHandler <- orderAccepted
|
|
case "order_filled":
|
|
var orderFilled WsOrderFilledResponse
|
|
err := common.JSONDecode(resp, &orderFilled)
|
|
if err != nil {
|
|
c.Websocket.DataHandler <- err
|
|
return
|
|
}
|
|
c.Websocket.DataHandler <- orderFilled
|
|
case "order_rejected":
|
|
var orderRejected WsOrderRejectedResponse
|
|
err := common.JSONDecode(resp, &orderRejected)
|
|
if err != nil {
|
|
c.Websocket.DataHandler <- err
|
|
return
|
|
}
|
|
c.Websocket.DataHandler <- orderRejected
|
|
case "user_open_orders":
|
|
var openOrders WsUserOpenOrdersResponse
|
|
err := common.JSONDecode(resp, &openOrders)
|
|
if err != nil {
|
|
c.Websocket.DataHandler <- err
|
|
return
|
|
}
|
|
c.Websocket.DataHandler <- openOrders
|
|
case "trade_history":
|
|
var tradeHistory WsTradeHistoryResponse
|
|
err := common.JSONDecode(resp, &tradeHistory)
|
|
if err != nil {
|
|
c.Websocket.DataHandler <- err
|
|
return
|
|
}
|
|
c.Websocket.DataHandler <- tradeHistory
|
|
case "cancel_orders":
|
|
var cancelOrders WsCancelOrdersResponse
|
|
err := common.JSONDecode(resp, &cancelOrders)
|
|
if err != nil {
|
|
c.Websocket.DataHandler <- err
|
|
return
|
|
}
|
|
c.Websocket.DataHandler <- cancelOrders
|
|
case "cancel_order":
|
|
var cancelOrder WsCancelOrderResponse
|
|
err := common.JSONDecode(resp, &cancelOrder)
|
|
if err != nil {
|
|
c.Websocket.DataHandler <- err
|
|
return
|
|
}
|
|
c.Websocket.DataHandler <- cancelOrder
|
|
}
|
|
}
|
|
|
|
// GetNonce returns a nonce for a required request
|
|
func (c *COINUT) GetNonce() int64 {
|
|
if c.Nonce.Get() == 0 {
|
|
c.Nonce.Set(time.Now().Unix())
|
|
} else {
|
|
c.Nonce.Inc()
|
|
}
|
|
|
|
return int64(c.Nonce.Get())
|
|
}
|
|
|
|
// WsSetInstrumentList fetches instrument list and propagates a local cache
|
|
func (c *COINUT) WsSetInstrumentList() error {
|
|
err := c.wsSend(wsRequest{
|
|
Request: "inst_list",
|
|
SecType: strings.ToUpper(asset.Spot.String()),
|
|
Nonce: c.GetNonce(),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, resp, err := c.WebsocketConn.ReadMessage()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.Websocket.TrafficAlert <- struct{}{}
|
|
|
|
var list WsInstrumentList
|
|
err = common.JSONDecode(resp, &list)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for currency, data := range list.Spot {
|
|
instrumentListByString[currency] = data[0].InstID
|
|
instrumentListByCode[data[0].InstID] = currency
|
|
}
|
|
|
|
if len(instrumentListByString) == 0 || len(instrumentListByCode) == 0 {
|
|
return errors.New("instrument lists failed to populate")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// WsProcessOrderbookSnapshot processes the orderbook snapshot
|
|
func (c *COINUT) WsProcessOrderbookSnapshot(ob *WsOrderbookSnapshot) error {
|
|
var bids []orderbook.Item
|
|
for _, bid := range ob.Buy {
|
|
bids = append(bids, orderbook.Item{
|
|
Amount: bid.Volume,
|
|
Price: bid.Price,
|
|
})
|
|
}
|
|
|
|
var asks []orderbook.Item
|
|
for _, ask := range ob.Sell {
|
|
asks = append(asks, orderbook.Item{
|
|
Amount: ask.Volume,
|
|
Price: ask.Price,
|
|
})
|
|
}
|
|
|
|
var newOrderBook orderbook.Base
|
|
newOrderBook.Asks = asks
|
|
newOrderBook.Bids = bids
|
|
newOrderBook.Pair = currency.NewPairFromString(instrumentListByCode[ob.InstID])
|
|
newOrderBook.AssetType = asset.Spot
|
|
newOrderBook.LastUpdated = time.Now()
|
|
|
|
return c.Websocket.Orderbook.LoadSnapshot(&newOrderBook, c.GetName(), false)
|
|
}
|
|
|
|
// WsProcessOrderbookUpdate process an orderbook update
|
|
func (c *COINUT) WsProcessOrderbookUpdate(ob *WsOrderbookUpdate) error {
|
|
p := currency.NewPairFromString(instrumentListByCode[ob.InstID])
|
|
|
|
if ob.Side == exchange.BuyOrderSide.ToLower().ToString() {
|
|
return c.Websocket.Orderbook.Update([]orderbook.Item{
|
|
{Price: ob.Price, Amount: ob.Volume}},
|
|
nil,
|
|
p,
|
|
time.Now(),
|
|
c.GetName(),
|
|
asset.Spot)
|
|
}
|
|
|
|
return c.Websocket.Orderbook.Update([]orderbook.Item{
|
|
{Price: ob.Price, Amount: ob.Volume}},
|
|
nil,
|
|
p,
|
|
time.Now(),
|
|
c.GetName(),
|
|
asset.Spot)
|
|
}
|
|
|
|
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
|
|
func (c *COINUT) GenerateDefaultSubscriptions() {
|
|
var channels = []string{"inst_tick", "inst_order_book"}
|
|
var subscriptions []exchange.WebsocketChannelSubscription
|
|
enabledCurrencies := c.GetEnabledPairs(asset.Spot)
|
|
for i := range channels {
|
|
for j := range enabledCurrencies {
|
|
subscriptions = append(subscriptions, exchange.WebsocketChannelSubscription{
|
|
Channel: channels[i],
|
|
Currency: enabledCurrencies[j],
|
|
})
|
|
}
|
|
}
|
|
c.Websocket.SubscribeToChannels(subscriptions)
|
|
}
|
|
|
|
// Subscribe sends a websocket message to receive data from the channel
|
|
func (c *COINUT) Subscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
|
|
subscribe := wsRequest{
|
|
Request: channelToSubscribe.Channel,
|
|
InstID: instrumentListByString[channelToSubscribe.Currency.String()],
|
|
Subscribe: true,
|
|
Nonce: c.GetNonce(),
|
|
}
|
|
return c.wsSend(subscribe)
|
|
}
|
|
|
|
// Unsubscribe sends a websocket message to stop receiving data from the channel
|
|
func (c *COINUT) Unsubscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
|
|
subscribe := wsRequest{
|
|
Request: channelToSubscribe.Channel,
|
|
InstID: instrumentListByString[channelToSubscribe.Currency.String()],
|
|
Subscribe: false,
|
|
Nonce: c.GetNonce(),
|
|
}
|
|
return c.wsSend(subscribe)
|
|
}
|
|
|
|
// WsSend sends data to the websocket server
|
|
func (c *COINUT) wsSend(data interface{}) error {
|
|
c.wsRequestMtx.Lock()
|
|
defer c.wsRequestMtx.Unlock()
|
|
|
|
json, err := common.JSONEncode(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if c.Verbose {
|
|
log.Debugf(log.ExchangeSys, "%v sending message to websocket %v", c.Name, string(json))
|
|
}
|
|
// Basic rate limiter
|
|
time.Sleep(coinutWebsocketRateLimit)
|
|
return c.WebsocketConn.WriteMessage(websocket.TextMessage, json)
|
|
}
|
|
|
|
func (c *COINUT) wsAuthenticate() error {
|
|
if !c.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
|
|
return fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", c.Name)
|
|
}
|
|
timestamp := time.Now().Unix()
|
|
nonce := c.GetNonce()
|
|
payload := fmt.Sprintf("%v|%v|%v", c.API.Credentials.ClientID, timestamp, nonce)
|
|
hmac := crypto.GetHMAC(crypto.HashSHA256, []byte(payload), []byte(c.API.Credentials.Key))
|
|
loginRequest := struct {
|
|
Request string `json:"request"`
|
|
Username string `json:"username"`
|
|
Nonce int64 `json:"nonce"`
|
|
Hmac string `json:"hmac_sha256"`
|
|
Timestamp int64 `json:"timestamp"`
|
|
}{
|
|
Request: "login",
|
|
Username: c.API.Credentials.ClientID,
|
|
Nonce: nonce,
|
|
Hmac: crypto.HexEncodeToString(hmac),
|
|
Timestamp: timestamp,
|
|
}
|
|
|
|
err := c.wsSend(loginRequest)
|
|
if err != nil {
|
|
c.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
|
return err
|
|
}
|
|
return nil
|
|
|
|
}
|
|
|
|
func (c *COINUT) wsGetAccountBalance() error {
|
|
if !c.Websocket.CanUseAuthenticatedEndpoints() {
|
|
return fmt.Errorf("%v not authorised to submit order", c.Name)
|
|
}
|
|
accBalance := wsRequest{
|
|
Request: "user_balance",
|
|
Nonce: c.GetNonce(),
|
|
}
|
|
return c.wsSend(accBalance)
|
|
}
|
|
|
|
func (c *COINUT) wsSubmitOrder(order *WsSubmitOrderParameters) error {
|
|
if !c.Websocket.CanUseAuthenticatedEndpoints() {
|
|
return fmt.Errorf("%v not authorised to submit order", c.Name)
|
|
}
|
|
currency := c.FormatExchangeCurrency(order.Currency, asset.Spot).String()
|
|
var orderSubmissionRequest WsSubmitOrderRequest
|
|
orderSubmissionRequest.Request = "new_order"
|
|
orderSubmissionRequest.Nonce = c.GetNonce()
|
|
orderSubmissionRequest.InstID = instrumentListByString[currency]
|
|
orderSubmissionRequest.Qty = order.Amount
|
|
orderSubmissionRequest.Price = order.Price
|
|
orderSubmissionRequest.Side = string(order.Side)
|
|
|
|
if order.OrderID > 0 {
|
|
orderSubmissionRequest.OrderID = order.OrderID
|
|
}
|
|
return c.wsSend(orderSubmissionRequest)
|
|
}
|
|
|
|
func (c *COINUT) wsSubmitOrders(orders []WsSubmitOrderParameters) error {
|
|
if !c.Websocket.CanUseAuthenticatedEndpoints() {
|
|
return fmt.Errorf("%v not authorised to submit orders", c.Name)
|
|
}
|
|
orderRequest := WsSubmitOrdersRequest{}
|
|
for i := range orders {
|
|
currency := c.FormatExchangeCurrency(orders[i].Currency, asset.Spot).String()
|
|
orderRequest.Orders = append(orderRequest.Orders,
|
|
WsSubmitOrdersRequestData{
|
|
Qty: orders[i].Amount,
|
|
Price: orders[i].Price,
|
|
Side: string(orders[i].Side),
|
|
InstID: instrumentListByString[currency],
|
|
ClientOrdID: i + 1,
|
|
})
|
|
}
|
|
|
|
orderRequest.Nonce = c.GetNonce()
|
|
orderRequest.Request = "new_orders"
|
|
return c.wsSend(orderRequest)
|
|
}
|
|
|
|
func (c *COINUT) wsGetOpenOrders(p currency.Pair) error {
|
|
if !c.Websocket.CanUseAuthenticatedEndpoints() {
|
|
return fmt.Errorf("%v not authorised to get open orders", c.Name)
|
|
}
|
|
currency := c.FormatExchangeCurrency(p, asset.Spot).String()
|
|
var openOrdersRequest WsGetOpenOrdersRequest
|
|
openOrdersRequest.Request = "user_open_orders"
|
|
openOrdersRequest.Nonce = c.GetNonce()
|
|
openOrdersRequest.InstID = instrumentListByString[currency]
|
|
|
|
return c.wsSend(openOrdersRequest)
|
|
}
|
|
|
|
func (c *COINUT) wsCancelOrder(cancellation WsCancelOrderParameters) error {
|
|
if !c.Websocket.CanUseAuthenticatedEndpoints() {
|
|
return fmt.Errorf("%v not authorised to cancel order", c.Name)
|
|
}
|
|
currency := c.FormatExchangeCurrency(cancellation.Currency, asset.Spot).String()
|
|
var cancellationRequest WsCancelOrderRequest
|
|
cancellationRequest.Request = "cancel_order"
|
|
cancellationRequest.InstID = instrumentListByString[currency]
|
|
cancellationRequest.OrderID = cancellation.OrderID
|
|
cancellationRequest.Nonce = c.GetNonce()
|
|
|
|
return c.wsSend(cancellationRequest)
|
|
}
|
|
|
|
func (c *COINUT) wsCancelOrders(cancellations []WsCancelOrderParameters) error {
|
|
if !c.Websocket.CanUseAuthenticatedEndpoints() {
|
|
return fmt.Errorf("%v not authorised to cancel orders", c.Name)
|
|
}
|
|
cancelOrderRequest := WsCancelOrdersRequest{}
|
|
for i := range cancellations {
|
|
currency := c.FormatExchangeCurrency(cancellations[i].Currency, asset.Spot).String()
|
|
cancelOrderRequest.Entries = append(cancelOrderRequest.Entries, WsCancelOrdersRequestEntry{
|
|
InstID: instrumentListByString[currency],
|
|
OrderID: cancellations[i].OrderID,
|
|
})
|
|
}
|
|
|
|
cancelOrderRequest.Request = "cancel_orders"
|
|
cancelOrderRequest.Nonce = c.GetNonce()
|
|
return c.wsSend(cancelOrderRequest)
|
|
}
|
|
|
|
func (c *COINUT) wsGetTradeHistory(p currency.Pair, start, limit int64) error {
|
|
if !c.Websocket.CanUseAuthenticatedEndpoints() {
|
|
return fmt.Errorf("%v not authorised to get trade history", c.Name)
|
|
}
|
|
currency := c.FormatExchangeCurrency(p, asset.Spot).String()
|
|
var request WsTradeHistoryRequest
|
|
request.Request = "trade_history"
|
|
request.InstID = instrumentListByString[currency]
|
|
request.Nonce = c.GetNonce()
|
|
request.Start = start
|
|
request.Limit = limit
|
|
|
|
return c.wsSend(request)
|
|
}
|