mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-06-07 23:16:53 +00:00
Merge branch 'master' into engine
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
@@ -13,6 +14,7 @@ import (
|
||||
"github.com/thrasher-/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-/gocryptotrader/exchanges/asset"
|
||||
log "github.com/thrasher-/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -53,7 +55,7 @@ type COINUT struct {
|
||||
func (c *COINUT) GetInstruments() (Instruments, error) {
|
||||
var result Instruments
|
||||
params := make(map[string]interface{})
|
||||
params["sec_type"] = "SPOT"
|
||||
params["sec_type"] = strings.ToUpper(asset.Spot.String())
|
||||
|
||||
return result, c.SendHTTPRequest(coinutInstruments, params, false, &result)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
package coinut
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-/gocryptotrader/common"
|
||||
"github.com/thrasher-/gocryptotrader/config"
|
||||
"github.com/thrasher-/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-/gocryptotrader/exchanges/sharedtestvalues"
|
||||
)
|
||||
|
||||
var c COINUT
|
||||
var wsSetupRan bool
|
||||
|
||||
// Please supply your own keys here to do better tests
|
||||
const (
|
||||
@@ -30,6 +35,7 @@ func TestSetup(t *testing.T) {
|
||||
t.Error("Test Failed - Coinut Setup() init error")
|
||||
}
|
||||
bConfig.API.AuthenticatedSupport = true
|
||||
bConfig.API.AuthenticatedWebsocketSupport = true
|
||||
bConfig.API.Credentials.Key = apiKey
|
||||
bConfig.API.Credentials.ClientID = clientID
|
||||
bConfig.Verbose = true
|
||||
@@ -41,6 +47,46 @@ func TestSetup(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func setupWSTestAuth(t *testing.T) {
|
||||
if wsSetupRan {
|
||||
return
|
||||
}
|
||||
c.SetDefaults()
|
||||
TestSetup(t)
|
||||
if !c.Websocket.IsEnabled() && !c.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() {
|
||||
t.Skip(exchange.WebsocketNotEnabled)
|
||||
}
|
||||
var err error
|
||||
var dialer websocket.Dialer
|
||||
c.WebsocketConn, _, err = dialer.Dial(c.Websocket.GetWebsocketURL(),
|
||||
http.Header{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
c.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
go c.WsHandleData()
|
||||
err = c.wsAuthenticate()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
timer := time.NewTimer(5 * time.Second)
|
||||
select {
|
||||
case resp := <-c.Websocket.DataHandler:
|
||||
if resp.(WsLoginResponse).Username != clientID {
|
||||
t.Fatal("Unsuccessful login")
|
||||
}
|
||||
case <-timer.C:
|
||||
t.Fatal("Expected response")
|
||||
}
|
||||
timer.Stop()
|
||||
time.Sleep(2 * time.Second)
|
||||
instrumentListByString = make(map[string]int64)
|
||||
instrumentListByString[currency.NewPair(currency.LTC, currency.BTC).String()] = 1
|
||||
wsSetupRan = true
|
||||
}
|
||||
|
||||
func TestGetInstruments(t *testing.T) {
|
||||
_, err := c.GetInstruments()
|
||||
if err != nil {
|
||||
@@ -403,3 +449,101 @@ func TestGetDepositAddress(t *testing.T) {
|
||||
t.Error("Test Failed - GetDepositAddress() function unsupported cannot be nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestWsAuthGetAccountBalance dials websocket, sends login request.
|
||||
func TestWsAuthGetAccountBalance(t *testing.T) {
|
||||
setupWSTestAuth(t)
|
||||
err := c.wsGetAccountBalance()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
timer := time.NewTimer(sharedtestvalues.WebsocketResponseExtendedTimeout)
|
||||
select {
|
||||
case resp := <-c.Websocket.DataHandler:
|
||||
if resp.(WsUserBalanceResponse).Status[0] != "OK" {
|
||||
t.Error("Expected successful response")
|
||||
}
|
||||
case <-timer.C:
|
||||
t.Error("Expected response")
|
||||
}
|
||||
timer.Stop()
|
||||
}
|
||||
|
||||
// TestWsAuthSubmitOrders dials websocket, sends login request.
|
||||
func TestWsAuthSubmitOrders(t *testing.T) {
|
||||
setupWSTestAuth(t)
|
||||
order := WsSubmitOrderParameters{
|
||||
Amount: 1,
|
||||
Currency: currency.NewPair(currency.LTC, currency.BTC),
|
||||
OrderID: 1,
|
||||
Price: 1,
|
||||
Side: exchange.BuyOrderSide,
|
||||
}
|
||||
err := c.wsSubmitOrders([]WsSubmitOrderParameters{order, order})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
timer := time.NewTimer(sharedtestvalues.WebsocketResponseExtendedTimeout)
|
||||
select {
|
||||
case <-c.Websocket.DataHandler:
|
||||
case <-timer.C:
|
||||
t.Error("Expected response")
|
||||
}
|
||||
timer.Stop()
|
||||
}
|
||||
|
||||
// TestWsAuthCancelOrders dials websocket, sends login request.
|
||||
func TestWsAuthCancelOrders(t *testing.T) {
|
||||
setupWSTestAuth(t)
|
||||
order := WsCancelOrderParameters{
|
||||
Currency: currency.NewPair(currency.LTC, currency.BTC),
|
||||
OrderID: 1,
|
||||
}
|
||||
err := c.wsCancelOrders([]WsCancelOrderParameters{order, order})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
timer := time.NewTimer(sharedtestvalues.WebsocketResponseExtendedTimeout)
|
||||
select {
|
||||
case <-c.Websocket.DataHandler:
|
||||
case <-timer.C:
|
||||
t.Error("Expected response")
|
||||
}
|
||||
timer.Stop()
|
||||
}
|
||||
|
||||
// TestWsAuthCancelOrder dials websocket, sends login request.
|
||||
func TestWsAuthCancelOrder(t *testing.T) {
|
||||
setupWSTestAuth(t)
|
||||
order := WsCancelOrderParameters{
|
||||
Currency: currency.NewPair(currency.LTC, currency.BTC),
|
||||
OrderID: 1,
|
||||
}
|
||||
err := c.wsCancelOrder(order)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
timer := time.NewTimer(sharedtestvalues.WebsocketResponseExtendedTimeout)
|
||||
select {
|
||||
case <-c.Websocket.DataHandler:
|
||||
case <-timer.C:
|
||||
t.Error("Expected response")
|
||||
}
|
||||
timer.Stop()
|
||||
}
|
||||
|
||||
// TestWsAuthGetOpenOrders dials websocket, sends login request.
|
||||
func TestWsAuthGetOpenOrders(t *testing.T) {
|
||||
setupWSTestAuth(t)
|
||||
err := c.wsGetOpenOrders(currency.NewPair(currency.LTC, currency.BTC))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
timer := time.NewTimer(sharedtestvalues.WebsocketResponseExtendedTimeout)
|
||||
select {
|
||||
case <-c.Websocket.DataHandler:
|
||||
case <-timer.C:
|
||||
t.Error("Expected response")
|
||||
}
|
||||
timer.Stop()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package coinut
|
||||
|
||||
import (
|
||||
"github.com/thrasher-/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-/gocryptotrader/exchanges"
|
||||
)
|
||||
|
||||
// GenericResponse is the generic response you will get from coinut
|
||||
type GenericResponse struct {
|
||||
Nonce int64 `json:"nonce"`
|
||||
@@ -111,8 +116,8 @@ type OrderResponse struct {
|
||||
|
||||
// Commission holds trade commission structure
|
||||
type Commission struct {
|
||||
Currency string `json:"currency"`
|
||||
Amount float64 `json:"amount,string"`
|
||||
Currency currency.Pair `json:"currency"`
|
||||
Amount float64 `json:"amount,string"`
|
||||
}
|
||||
|
||||
// OrderFilledResponse contains order filled response
|
||||
@@ -362,3 +367,248 @@ type WsSupportedCurrency struct {
|
||||
DecimalPlaces int64 `json:"decimal_places"`
|
||||
Quote string `json:"quote"`
|
||||
}
|
||||
|
||||
// WsRequest base request
|
||||
type WsRequest struct {
|
||||
Request string `json:"request"`
|
||||
Nonce int64 `json:"nonce"`
|
||||
}
|
||||
|
||||
// WsTradeHistoryRequest ws request
|
||||
type WsTradeHistoryRequest struct {
|
||||
InstID int64 `json:"inst_id"`
|
||||
Start int64 `json:"start,omitempty"`
|
||||
Limit int64 `json:"limit,omitempty"`
|
||||
WsRequest
|
||||
}
|
||||
|
||||
// WsCancelOrdersRequest ws request
|
||||
type WsCancelOrdersRequest struct {
|
||||
Entries []WsCancelOrdersRequestEntry `json:"entries"`
|
||||
WsRequest
|
||||
}
|
||||
|
||||
// WsCancelOrdersRequestEntry ws request entry
|
||||
type WsCancelOrdersRequestEntry struct {
|
||||
InstID int64 `json:"inst_id"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
}
|
||||
|
||||
// WsCancelOrderParameters ws request parameters
|
||||
type WsCancelOrderParameters struct {
|
||||
Currency currency.Pair
|
||||
OrderID int64
|
||||
}
|
||||
|
||||
// WsCancelOrderRequest ws request
|
||||
type WsCancelOrderRequest struct {
|
||||
InstID int64 `json:"inst_id"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
WsRequest
|
||||
}
|
||||
|
||||
// WsCancelOrderResponse ws response
|
||||
type WsCancelOrderResponse struct {
|
||||
Nonce int64 `json:"nonce"`
|
||||
Reply string `json:"reply"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
ClientOrdID int64 `json:"client_ord_id"`
|
||||
Status []string `json:"status"`
|
||||
}
|
||||
|
||||
// WsCancelOrdersResponse ws response
|
||||
type WsCancelOrdersResponse struct {
|
||||
WsRequest
|
||||
Entries []WsCancelOrdersResponseEntry `json:"entries"`
|
||||
}
|
||||
|
||||
// WsCancelOrdersResponseEntry ws response entry
|
||||
type WsCancelOrdersResponseEntry struct {
|
||||
InstID int64 `json:"inst_id"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
}
|
||||
|
||||
// WsGetOpenOrdersRequest ws request
|
||||
type WsGetOpenOrdersRequest struct {
|
||||
InstID int64 `json:"inst_id"`
|
||||
WsRequest
|
||||
}
|
||||
|
||||
// WsSubmitOrdersRequest ws request
|
||||
type WsSubmitOrdersRequest struct {
|
||||
Orders []WsSubmitOrdersRequestData `json:"orders"`
|
||||
WsRequest
|
||||
}
|
||||
|
||||
// WsSubmitOrdersRequestData ws request data
|
||||
type WsSubmitOrdersRequestData struct {
|
||||
InstID int64 `json:"inst_id"`
|
||||
Price float64 `json:"price,string"`
|
||||
Qty float64 `json:"qty,string"`
|
||||
ClientOrdID int `json:"client_ord_id"`
|
||||
Side string `json:"side"`
|
||||
}
|
||||
|
||||
// WsSubmitOrderRequest ws request
|
||||
type WsSubmitOrderRequest struct {
|
||||
InstID int64 `json:"inst_id"`
|
||||
Price float64 `json:"price,string"`
|
||||
Qty float64 `json:"qty,string"`
|
||||
OrderID int64 `json:"client_ord_id"`
|
||||
Side string `json:"side"`
|
||||
WsRequest
|
||||
}
|
||||
|
||||
// WsSubmitOrderParameters ws request parameters
|
||||
type WsSubmitOrderParameters struct {
|
||||
Currency currency.Pair
|
||||
Side exchange.OrderSide
|
||||
Amount, Price float64
|
||||
OrderID int64
|
||||
}
|
||||
|
||||
// WsUserBalanceResponse ws response
|
||||
type WsUserBalanceResponse struct {
|
||||
Nonce int64 `json:"nonce"`
|
||||
Status []string `json:"status"`
|
||||
Btc float64 `json:"BTC,string"`
|
||||
Ltc float64 `json:"LTC,string"`
|
||||
Etc float64 `json:"ETC,string"`
|
||||
Eth float64 `json:"ETH,string"`
|
||||
FloatingPl float64 `json:"floating_pl,string"`
|
||||
InitialMargin float64 `json:"initial_margin,string"`
|
||||
RealizedPl float64 `json:"realized_pl,string"`
|
||||
MaintenanceMargin float64 `json:"maintenance_margin,string"`
|
||||
Equity float64 `json:"equity,string"`
|
||||
Reply string `json:"reply"`
|
||||
TransID int64 `json:"trans_id"`
|
||||
}
|
||||
|
||||
// WsOrderAcceptedResponse ws response
|
||||
type WsOrderAcceptedResponse struct {
|
||||
Nonce int64 `json:"nonce"`
|
||||
Status []string `json:"status"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
OpenQty float64 `json:"open_qty,string"`
|
||||
InstID int64 `json:"inst_id"`
|
||||
Qty float64 `json:"qty,string"`
|
||||
ClientOrdID int64 `json:"client_ord_id"`
|
||||
OrderPrice float64 `json:"order_price,string"`
|
||||
Reply string `json:"reply"`
|
||||
Side string `json:"side"`
|
||||
TransID int64 `json:"trans_id"`
|
||||
}
|
||||
|
||||
// WsOrderFilledResponse ws response
|
||||
type WsOrderFilledResponse struct {
|
||||
Commission WsOrderFilledCommissionData `json:"commission"`
|
||||
FillPrice float64 `json:"fill_price,string"`
|
||||
FillQty float64 `json:"fill_qty,string"`
|
||||
Nonce int64 `json:"nonce"`
|
||||
Order WsOrderData `json:"order"`
|
||||
Reply string `json:"reply"`
|
||||
Status []string `json:"status"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
TransID int64 `json:"trans_id"`
|
||||
}
|
||||
|
||||
// WsOrderData ws response data
|
||||
type WsOrderData struct {
|
||||
ClientOrdID int64 `json:"client_ord_id"`
|
||||
InstID int64 `json:"inst_id"`
|
||||
OpenQty float64 `json:"open_qty,string"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
Price float64 `json:"price,string"`
|
||||
Qty float64 `json:"qty,string"`
|
||||
Side string `json:"side"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
// WsOrderFilledCommissionData ws response data
|
||||
type WsOrderFilledCommissionData struct {
|
||||
Amount float64 `json:"amount,string"`
|
||||
Currency currency.Pair `json:"currency"`
|
||||
}
|
||||
|
||||
// WsOrderRejectedResponse ws response
|
||||
type WsOrderRejectedResponse struct {
|
||||
Nonce int64 `json:"nonce"`
|
||||
Status []string `json:"status"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
OpenQty float64 `json:"open_qty,string"`
|
||||
Price float64 `json:"price,string"`
|
||||
InstID int64 `json:"inst_id"`
|
||||
Reasons []string `json:"reasons"`
|
||||
ClientOrdID int64 `json:"client_ord_id"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Reply string `json:"reply"`
|
||||
Qty float64 `json:"qty,string"`
|
||||
Side string `json:"side"`
|
||||
TransID int64 `json:"trans_id"`
|
||||
}
|
||||
|
||||
// WsUserOpenOrdersResponse ws response
|
||||
type WsUserOpenOrdersResponse struct {
|
||||
Nonce int64 `json:"nonce"`
|
||||
Reply string `json:"reply"`
|
||||
Status []string `json:"status"`
|
||||
Orders []WsOrderData `json:"orders"`
|
||||
}
|
||||
|
||||
// WsTradeHistoryResponse ws response
|
||||
type WsTradeHistoryResponse struct {
|
||||
Nonce int64 `json:"nonce"`
|
||||
Reply string `json:"reply"`
|
||||
Status []string `json:"status"`
|
||||
TotalNumber int64 `json:"total_number"`
|
||||
Trades []WsOrderData `json:"trades"`
|
||||
}
|
||||
|
||||
// WsTradeHistoryCommissionData ws response data
|
||||
type WsTradeHistoryCommissionData struct {
|
||||
Amount float64 `json:"amount,string"`
|
||||
Currency currency.Pair `json:"currency"`
|
||||
}
|
||||
|
||||
// WsTradeHistoryTradeData ws response data
|
||||
type WsTradeHistoryTradeData struct {
|
||||
Commission WsTradeHistoryCommissionData `json:"commission"`
|
||||
Order WsOrderData `json:"order"`
|
||||
FillPrice float64 `json:"fill_price,string"`
|
||||
FillQty float64 `json:"fill_qty,string"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
TransID int64 `json:"trans_id"`
|
||||
}
|
||||
|
||||
// WsLoginResponse ws response data
|
||||
type WsLoginResponse struct {
|
||||
APIKey string `json:"api_key"`
|
||||
Country string `json:"country"`
|
||||
DepositEnabled bool `json:"deposit_enabled"`
|
||||
Deposited bool `json:"deposited"`
|
||||
Email string `json:"email"`
|
||||
FailedTimes int64 `json:"failed_times"`
|
||||
KycPassed bool `json:"kyc_passed"`
|
||||
Lang string `json:"lang"`
|
||||
Nonce int64 `json:"nonce"`
|
||||
OtpEnabled bool `json:"otp_enabled"`
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
ProductsEnabled []string `json:"products_enabled"`
|
||||
Referred bool `json:"referred"`
|
||||
Reply string `json:"reply"`
|
||||
SessionID string `json:"session_id"`
|
||||
Status []string `json:"status"`
|
||||
Timezone string `json:"timezone"`
|
||||
Traded bool `json:"traded"`
|
||||
UnverifiedEmail string `json:"unverified_email"`
|
||||
Username string `json:"username"`
|
||||
WithdrawEnabled bool `json:"withdraw_enabled"`
|
||||
}
|
||||
|
||||
// WsNewOrderResponse returns if new_order response failes
|
||||
type WsNewOrderResponse struct {
|
||||
Msg string `json:"msg"`
|
||||
Nonce int64 `json:"nonce"`
|
||||
Reply string `json:"reply"`
|
||||
Status []string `json:"status"`
|
||||
}
|
||||
|
||||
@@ -2,12 +2,15 @@ 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"
|
||||
@@ -29,142 +32,6 @@ var populatedList bool
|
||||
// wss://wsapi-na.coinut.com
|
||||
// wss://wsapi-eu.coinut.com
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
var incoming wsResponse
|
||||
err = common.JSONDecode(resp.Raw, &incoming)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
switch incoming.Reply {
|
||||
case "hb":
|
||||
channels["hb"] <- resp.Raw
|
||||
|
||||
case "inst_tick":
|
||||
var ticker WsTicker
|
||||
err := common.JSONDecode(resp.Raw, &ticker)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
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.Raw, &orderbooksnapshot)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
err = c.WsProcessOrderbookSnapshot(&orderbooksnapshot)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
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.Raw, &orderbookUpdate)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
err = c.WsProcessOrderbookUpdate(&orderbookUpdate)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
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.Raw, &tradeSnap)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
case "inst_trade_update":
|
||||
var tradeUpdate WsTradeUpdate
|
||||
err := common.JSONDecode(resp.Raw, &tradeUpdate)
|
||||
if err != nil {
|
||||
c.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WsConnect intiates a websocket connection
|
||||
func (c *COINUT) WsConnect() error {
|
||||
if !c.Websocket.IsEnabled() || !c.IsEnabled() {
|
||||
@@ -200,7 +67,7 @@ func (c *COINUT) WsConnect() error {
|
||||
}
|
||||
populatedList = true
|
||||
}
|
||||
|
||||
c.wsAuthenticate()
|
||||
c.GenerateDefaultSubscriptions()
|
||||
|
||||
// define bi-directional communication
|
||||
@@ -208,10 +75,244 @@ func (c *COINUT) WsConnect() error {
|
||||
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 {
|
||||
@@ -227,7 +328,7 @@ func (c *COINUT) GetNonce() int64 {
|
||||
func (c *COINUT) WsSetInstrumentList() error {
|
||||
err := c.wsSend(wsRequest{
|
||||
Request: "inst_list",
|
||||
SecType: "SPOT",
|
||||
SecType: strings.ToUpper(asset.Spot.String()),
|
||||
Nonce: c.GetNonce(),
|
||||
})
|
||||
if err != nil {
|
||||
@@ -313,7 +414,7 @@ func (c *COINUT) WsProcessOrderbookUpdate(ob *WsOrderbookUpdate) error {
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
func (c *COINUT) GenerateDefaultSubscriptions() {
|
||||
var channels = []string{"inst_tick", "inst_order_book"}
|
||||
subscriptions := []exchange.WebsocketChannelSubscription{}
|
||||
var subscriptions []exchange.WebsocketChannelSubscription
|
||||
enabledCurrencies := c.GetEnabledPairs(asset.Spot)
|
||||
for i := range channels {
|
||||
for j := range enabledCurrencies {
|
||||
@@ -364,3 +465,146 @@ func (c *COINUT) wsSend(data interface{}) error {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -631,3 +631,13 @@ func (c *COINUT) UnsubscribeToWebsocketChannels(channels []exchange.WebsocketCha
|
||||
c.Websocket.UnsubscribeToChannels(channels)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSubscriptions returns a copied list of subscriptions
|
||||
func (c *COINUT) GetSubscriptions() ([]exchange.WebsocketChannelSubscription, error) {
|
||||
return c.Websocket.GetSubscriptions(), nil
|
||||
}
|
||||
|
||||
// AuthenticateWebsocket sends an authentication message to the websocket
|
||||
func (c *COINUT) AuthenticateWebsocket() error {
|
||||
return c.wsAuthenticate()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user