Merge branch 'master' into engine

This commit is contained in:
Adrian Gallagher
2019-06-21 18:10:55 +10:00
87 changed files with 5669 additions and 992 deletions

View File

@@ -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)
}

View File

@@ -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()
}

View File

@@ -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"`
}

View File

@@ -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)
}

View File

@@ -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()
}