mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
Gateio expand wrappers, websocket and bug fix (#291)
* Added fix for balances as GateIO returns object or array depending on if you have any balances * Adds GetOrderInfo function for REST API * Correctly assign WebsocketURL * Adds websocket auth support to GateIO * reverts Host changes * unexported getbalance and signin * Add WsGetOrderInfo to retreive information on a set order * unexport wsGetOrderInfo * renamed freeze to locked to match rest interface * Removed old logging * Added detailed error messages and testing disabling auth api support if WS auth fails * Removed old code for nonce * reworked GetOrder to return an error if no order is found * gofmt tests * removed unneeded event from websocketresponse * fixed casing on websocket * Fixed condition
This commit is contained in:
@@ -97,6 +97,7 @@ func (g *Gateio) Setup(exch *config.ExchangeConfig) {
|
||||
g.BaseCurrencies = exch.BaseCurrencies
|
||||
g.AvailablePairs = exch.AvailablePairs
|
||||
g.EnabledPairs = exch.EnabledPairs
|
||||
g.WebsocketURL = gateioWebsocketEndpoint
|
||||
err := g.SetCurrencyPairFormat()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@@ -459,6 +460,10 @@ func (g *Gateio) GetTradeHistory(symbol string) (TradHistoryResponse, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (g *Gateio) GenerateSignature(message string) []byte {
|
||||
return common.GetHMAC(common.HashSHA512, []byte(message), []byte(g.APISecret))
|
||||
}
|
||||
|
||||
// SendAuthenticatedHTTPRequest sends authenticated requests to the Gateio API
|
||||
// To use this you must setup an APIKey and APISecret from the exchange
|
||||
func (g *Gateio) SendAuthenticatedHTTPRequest(method, endpoint, param string, result interface{}) error {
|
||||
@@ -471,7 +476,7 @@ func (g *Gateio) SendAuthenticatedHTTPRequest(method, endpoint, param string, re
|
||||
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
headers["key"] = g.APIKey
|
||||
|
||||
hmac := common.GetHMAC(common.HashSHA512, []byte(param), []byte(g.APISecret))
|
||||
hmac := g.GenerateSignature(param)
|
||||
headers["sign"] = common.HexEncodeToString(hmac)
|
||||
|
||||
urlPath := fmt.Sprintf("%s/%s/%s", g.APIUrl, gateioAPIVersion, endpoint)
|
||||
|
||||
@@ -56,7 +56,7 @@ func TestGetMarketInfo(t *testing.T) {
|
||||
func TestSpotNewOrder(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if apiKey == "" || apiSecret == "" {
|
||||
if !areTestAPIKeysSet() && !canManipulateRealOrders {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ func TestSpotNewOrder(t *testing.T) {
|
||||
func TestCancelExistingOrder(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if apiKey == "" || apiSecret == "" {
|
||||
if !areTestAPIKeysSet() && !canManipulateRealOrders {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
@@ -475,3 +475,18 @@ func TestGetDepositAddress(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
func TestGetOrderInfo(t *testing.T) {
|
||||
g.SetDefaults()
|
||||
TestSetup(t)
|
||||
|
||||
if !areTestAPIKeysSet() {
|
||||
t.Skip("no API keys set skipping test")
|
||||
}
|
||||
|
||||
_, err := g.GetOrderInfo("917591554")
|
||||
if err != nil {
|
||||
if err.Error() != "no order found with id 917591554" && err.Error() != "failed to get open orders" {
|
||||
t.Fatalf("GetOrderInfo() returned an error skipping test: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,13 @@ var (
|
||||
TimeIntervalDay = TimeInterval(60 * 60 * 24)
|
||||
)
|
||||
|
||||
const (
|
||||
IDGeneric = 0000
|
||||
IDSignIn = 1010
|
||||
IDBalance = 2000
|
||||
IDOrderQuery = 3001
|
||||
)
|
||||
|
||||
// MarketInfoResponse holds the market info data
|
||||
type MarketInfoResponse struct {
|
||||
Result string `json:"result"`
|
||||
@@ -54,9 +61,9 @@ type MarketInfoPairsResponse struct {
|
||||
|
||||
// BalancesResponse holds the user balances
|
||||
type BalancesResponse struct {
|
||||
Result string `json:"result"`
|
||||
Available map[string]string `json:"available"`
|
||||
Locked map[string]string `json:"locked"`
|
||||
Result string `json:"result"`
|
||||
Available interface{} `json:"available"`
|
||||
Locked interface{} `json:"locked"`
|
||||
}
|
||||
|
||||
// KlinesRequestParams represents Klines request data.
|
||||
@@ -397,15 +404,13 @@ type WebsocketRequest struct {
|
||||
|
||||
// WebsocketResponse defines a websocket response from gateio
|
||||
type WebsocketResponse struct {
|
||||
Time int64 `json:"time"`
|
||||
Channel string `json:"channel"`
|
||||
Event string `json:""`
|
||||
Error WebsocketError `json:"error"`
|
||||
Result struct {
|
||||
Status string `json:"status"`
|
||||
} `json:"result"`
|
||||
Method string `json:"method"`
|
||||
Params []json.RawMessage `json:"params"`
|
||||
Time int64 `json:"time"`
|
||||
Channel string `json:"channel"`
|
||||
Error WebsocketError `json:"error"`
|
||||
Result json.RawMessage `json:"result"`
|
||||
ID int64 `json:"id"`
|
||||
Method string `json:"method"`
|
||||
Params []json.RawMessage `json:"params"`
|
||||
}
|
||||
|
||||
// WebsocketError defines a websocket error type
|
||||
@@ -435,3 +440,40 @@ type WebsocketTrade struct {
|
||||
Amount float64 `json:"amount,string"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// WebsocketBalance holds a slice of WebsocketBalanceCurrency
|
||||
type WebsocketBalance struct {
|
||||
Currency []WebsocketBalanceCurrency
|
||||
}
|
||||
|
||||
// WebsocketBalanceCurrency contains currency name funds available and frozen
|
||||
type WebsocketBalanceCurrency struct {
|
||||
Currency string
|
||||
Available string `json:"available"`
|
||||
Locked string `json:"freeze"`
|
||||
}
|
||||
|
||||
// WebSocketOrderQueryResult data returned from a websocket ordre query holds slice of WebSocketOrderQueryRecords
|
||||
type WebSocketOrderQueryResult struct {
|
||||
Limit int `json:"limit"`
|
||||
Offset int `json:"offset"`
|
||||
Total int `json:"total"`
|
||||
WebSocketOrderQueryRecords []WebSocketOrderQueryRecords `json:"records"`
|
||||
}
|
||||
|
||||
// WebSocketOrderQueryRecords contains order information from a order.query websocket request
|
||||
type WebSocketOrderQueryRecords struct {
|
||||
ID int `json:"id"`
|
||||
Market string `json:"market"`
|
||||
User int `json:"user"`
|
||||
Ctime float64 `json:"ctime"`
|
||||
Mtime float64 `json:"mtime"`
|
||||
Price string `json:"price"`
|
||||
Amount string `json:"amount"`
|
||||
Left string `json:"left"`
|
||||
DealFee string `json:"dealFee"`
|
||||
OrderType int `json:"orderType"`
|
||||
Type int `json:"type"`
|
||||
FilledAmount string `json:"filledAmount"`
|
||||
FilledTotal string `json:"filledTotal"`
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package gateio
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -13,6 +14,7 @@ import (
|
||||
"github.com/thrasher-/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
|
||||
log "github.com/thrasher-/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -43,11 +45,31 @@ func (g *Gateio) WsConnect() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if g.AuthenticatedAPISupport {
|
||||
err = g.wsServerSignIn()
|
||||
if err != nil {
|
||||
log.Errorf("%v - wsServerSignin() failed: %v", g.GetName(), err)
|
||||
}
|
||||
time.Sleep(time.Second * 2) // sleep to allow server to complete sign-on if further authenticated requests are sent piror to this they will fail
|
||||
}
|
||||
|
||||
go g.WsHandleData()
|
||||
|
||||
return g.WsSubscribe()
|
||||
}
|
||||
|
||||
func (g *Gateio) wsServerSignIn() error {
|
||||
nonce := int(time.Now().Unix() * 1000)
|
||||
sigTemp := g.GenerateSignature(strconv.Itoa(nonce))
|
||||
signature := common.Base64Encode(sigTemp)
|
||||
signinWsRequest := WebsocketRequest{
|
||||
ID: IDSignIn,
|
||||
Method: "server.sign",
|
||||
Params: []interface{}{g.APIKey, signature, nonce},
|
||||
}
|
||||
return g.WebsocketConn.WriteJSON(signinWsRequest)
|
||||
}
|
||||
|
||||
// WsSubscribe subscribes to the full websocket suite on ZB exchange
|
||||
func (g *Gateio) WsSubscribe() error {
|
||||
enabled := g.GetEnabledCurrencies()
|
||||
@@ -98,6 +120,30 @@ func (g *Gateio) WsSubscribe() error {
|
||||
}
|
||||
}
|
||||
|
||||
if g.AuthenticatedAPISupport {
|
||||
balance := WebsocketRequest{
|
||||
ID: IDBalance,
|
||||
Method: "balance.subscribe",
|
||||
Params: []interface{}{},
|
||||
}
|
||||
|
||||
err := g.WebsocketConn.WriteJSON(balance)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, c := range enabled {
|
||||
orderNotification := WebsocketRequest{
|
||||
ID: IDGeneric,
|
||||
Method: "order.subscribe",
|
||||
Params: []interface{}{c.String()},
|
||||
}
|
||||
err := g.WebsocketConn.WriteJSON(orderNotification)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -147,11 +193,57 @@ func (g *Gateio) WsHandleData() {
|
||||
}
|
||||
|
||||
if result.Error.Code != 0 {
|
||||
if common.StringContains(result.Error.Message, "authentication") {
|
||||
g.Websocket.DataHandler <- fmt.Errorf("%v - WebSocket authentication failed ",
|
||||
g.GetName())
|
||||
g.AuthenticatedAPISupport = false
|
||||
continue
|
||||
}
|
||||
g.Websocket.DataHandler <- fmt.Errorf("gateio_websocket.go error %s",
|
||||
result.Error.Message)
|
||||
continue
|
||||
}
|
||||
|
||||
switch result.ID {
|
||||
case IDBalance:
|
||||
var balance WebsocketBalance
|
||||
var balanceInterface interface{}
|
||||
err = json.Unmarshal(result.Result, &balanceInterface)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
}
|
||||
var p WebsocketBalanceCurrency
|
||||
switch x := balanceInterface.(type) {
|
||||
case map[string]interface{}:
|
||||
for xx := range x {
|
||||
switch kk := x[xx].(type) {
|
||||
case map[string]interface{}:
|
||||
p = WebsocketBalanceCurrency{
|
||||
Currency: xx,
|
||||
Available: kk["available"].(string),
|
||||
Locked: kk["freeze"].(string),
|
||||
}
|
||||
balance.Currency = append(balance.Currency, p)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
g.Websocket.DataHandler <- balance
|
||||
case IDOrderQuery:
|
||||
var orderQuery WebSocketOrderQueryResult
|
||||
err = common.JSONDecode(result.Result, &orderQuery)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
g.Websocket.DataHandler <- orderQuery
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
switch {
|
||||
case common.StringContains(result.Method, "ticker"):
|
||||
var ticker WebsocketTicker
|
||||
@@ -323,3 +415,25 @@ func (g *Gateio) WsHandleData() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Gateio) wsGetBalance() error {
|
||||
balanceWsRequest := WebsocketRequest{
|
||||
ID: IDBalance,
|
||||
Method: "balance.query",
|
||||
Params: []interface{}{},
|
||||
}
|
||||
return g.WebsocketConn.WriteJSON(balanceWsRequest)
|
||||
}
|
||||
|
||||
func (g *Gateio) wsGetOrderInfo(market string, offset, limit int) error {
|
||||
order := WebsocketRequest{
|
||||
ID: IDOrderQuery,
|
||||
Method: "order.query",
|
||||
Params: []interface{}{
|
||||
market,
|
||||
offset,
|
||||
limit,
|
||||
},
|
||||
}
|
||||
return g.WebsocketConn.WriteJSON(order)
|
||||
}
|
||||
|
||||
@@ -137,46 +137,50 @@ func (g *Gateio) GetAccountInfo() (exchange.AccountInfo, error) {
|
||||
return info, err
|
||||
}
|
||||
|
||||
if len(balance.Available) == 0 && len(balance.Locked) == 0 {
|
||||
return info, nil
|
||||
}
|
||||
|
||||
var balances []exchange.AccountCurrencyInfo
|
||||
|
||||
for key, amountStr := range balance.Locked {
|
||||
|
||||
lockedF, err := strconv.ParseFloat(amountStr, 64)
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
|
||||
balances = append(balances, exchange.AccountCurrencyInfo{
|
||||
CurrencyName: currency.NewCode(key),
|
||||
Hold: lockedF,
|
||||
})
|
||||
}
|
||||
|
||||
for key, amountStr := range balance.Available {
|
||||
availAmount, err := strconv.ParseFloat(amountStr, 64)
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
|
||||
var updated bool
|
||||
for i := range balances {
|
||||
if balances[i].CurrencyName == currency.NewCode(key) {
|
||||
balances[i].TotalValue = balances[i].Hold + availAmount
|
||||
updated = true
|
||||
break
|
||||
switch l := balance.Locked.(type) {
|
||||
case map[string]interface{}:
|
||||
for x := range l {
|
||||
lockedF, err := strconv.ParseFloat(l[x].(string), 64)
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
}
|
||||
|
||||
if !updated {
|
||||
balances = append(balances, exchange.AccountCurrencyInfo{
|
||||
CurrencyName: currency.NewCode(key),
|
||||
TotalValue: availAmount,
|
||||
CurrencyName: currency.NewCode(x),
|
||||
Hold: lockedF,
|
||||
})
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
switch v := balance.Available.(type) {
|
||||
case map[string]interface{}:
|
||||
for x := range v {
|
||||
availAmount, err := strconv.ParseFloat(v[x].(string), 64)
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
|
||||
var updated bool
|
||||
for i := range balances {
|
||||
if balances[i].CurrencyName == currency.NewCode(x) {
|
||||
balances[i].TotalValue = balances[i].Hold + availAmount
|
||||
updated = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !updated {
|
||||
balances = append(balances, exchange.AccountCurrencyInfo{
|
||||
CurrencyName: currency.NewCode(x),
|
||||
TotalValue: availAmount,
|
||||
})
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
info.Accounts = append(info.Accounts, exchange.Account{
|
||||
@@ -280,7 +284,32 @@ func (g *Gateio) CancelAllOrders(_ *exchange.OrderCancellation) (exchange.Cancel
|
||||
// GetOrderInfo returns information on a current open order
|
||||
func (g *Gateio) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
|
||||
var orderDetail exchange.OrderDetail
|
||||
return orderDetail, common.ErrNotYetImplemented
|
||||
|
||||
orders, err := g.GetOpenOrders("")
|
||||
if err != nil {
|
||||
return orderDetail, errors.New("failed to get open orders")
|
||||
}
|
||||
for x := range orders.Orders {
|
||||
if orders.Orders[x].OrderNumber != orderID {
|
||||
continue
|
||||
}
|
||||
orderDetail.Exchange = g.GetName()
|
||||
orderDetail.ID = orders.Orders[x].OrderNumber
|
||||
orderDetail.RemainingAmount = orders.Orders[x].InitialAmount - orders.Orders[x].FilledAmount
|
||||
orderDetail.ExecutedAmount = orders.Orders[x].FilledAmount
|
||||
orderDetail.Amount = orders.Orders[x].InitialAmount
|
||||
orderDetail.OrderDate = time.Unix(orders.Orders[x].Timestamp, 0)
|
||||
orderDetail.Status = orders.Orders[x].Status
|
||||
orderDetail.Price = orders.Orders[x].Rate
|
||||
orderDetail.CurrencyPair = currency.NewPairDelimiter(orders.Orders[x].CurrencyPair, g.ConfigCurrencyPairFormat.Delimiter)
|
||||
if strings.EqualFold(orders.Orders[x].Type, exchange.AskOrderSide.ToString()) {
|
||||
orderDetail.OrderSide = exchange.AskOrderSide
|
||||
} else if strings.EqualFold(orders.Orders[x].Type, exchange.BidOrderSide.ToString()) {
|
||||
orderDetail.OrderSide = exchange.BuyOrderSide
|
||||
}
|
||||
return orderDetail, nil
|
||||
}
|
||||
return orderDetail, fmt.Errorf("no order found with id %v", orderID)
|
||||
}
|
||||
|
||||
// GetDepositAddress returns a deposit address for a specified currency
|
||||
|
||||
Reference in New Issue
Block a user