mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-06-07 15:11:03 +00:00
Merge branch 'master' into engine
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
# GoCryptoTrader package Huobi
|
||||
|
||||
<img src="https://github.com/thrasher-/gocryptotrader/blob/master/web/src/assets/page-logo.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/page-logo.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
|
||||
|
||||
[](https://travis-ci.org/thrasher-/gocryptotrader)
|
||||
[](https://github.com/thrasher-/gocryptotrader/blob/master/LICENSE)
|
||||
[](https://godoc.org/github.com/thrasher-/gocryptotrader/exchanges/huobi)
|
||||
[](http://codecov.io/github/thrasher-/gocryptotrader?branch=master)
|
||||
[](https://goreportcard.com/report/github.com/thrasher-/gocryptotrader)
|
||||
[](https://travis-ci.org/thrasher-corp/gocryptotrader)
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
|
||||
[](https://godoc.org/github.com/thrasher-corp/gocryptotrader/exchanges/huobi)
|
||||
[](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
|
||||
[](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader)
|
||||
|
||||
|
||||
This huobi package is part of the GoCryptoTrader codebase.
|
||||
@@ -27,7 +27,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
|
||||
|
||||
### How to enable
|
||||
|
||||
+ [Enable via configuration](https://github.com/thrasher-/gocryptotrader/tree/master/config#enable-exchange-via-config-example)
|
||||
+ [Enable via configuration](https://github.com/thrasher-corp/gocryptotrader/tree/master/config#enable-exchange-via-config-example)
|
||||
|
||||
+ Individual package example below:
|
||||
|
||||
@@ -127,12 +127,12 @@ When submitting a PR, please abide by our coding guidelines:
|
||||
|
||||
+ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
|
||||
+ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
|
||||
+ Code must adhere to our [coding style](https://github.com/thrasher-/gocryptotrader/blob/master/doc/coding_style.md).
|
||||
+ Code must adhere to our [coding style](https://github.com/thrasher-corp/gocryptotrader/blob/master/doc/coding_style.md).
|
||||
+ Pull requests need to be based on and opened against the `master` branch.
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
|
||||
@@ -14,14 +14,13 @@ import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"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-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -66,9 +65,8 @@ const (
|
||||
type HUOBI struct {
|
||||
exchange.Base
|
||||
AccountID string
|
||||
WebsocketConn *websocket.Conn
|
||||
AuthenticatedWebsocketConn *websocket.Conn
|
||||
wsRequestMtx sync.Mutex
|
||||
WebsocketConn *wshandler.WebsocketConnection
|
||||
AuthenticatedWebsocketConn *wshandler.WebsocketConnection
|
||||
}
|
||||
|
||||
// GetSpotKline returns kline data
|
||||
|
||||
@@ -9,15 +9,15 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-/gocryptotrader/common"
|
||||
"github.com/thrasher-/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-/gocryptotrader/config"
|
||||
"github.com/thrasher-/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-/gocryptotrader/exchanges/sharedtestvalues"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
)
|
||||
|
||||
// Please supply you own test keys here for due diligence testing.
|
||||
@@ -57,37 +57,36 @@ func setupWsTests(t *testing.T) {
|
||||
TestSetDefaults(t)
|
||||
TestSetup(t)
|
||||
if !h.Websocket.IsEnabled() && !h.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() {
|
||||
t.Skip(exchange.WebsocketNotEnabled)
|
||||
t.Skip(wshandler.WebsocketNotEnabled)
|
||||
}
|
||||
var err error
|
||||
var dialer websocket.Dialer
|
||||
comms = make(chan WsMessage, sharedtestvalues.WebsocketChannelOverrideCapacity)
|
||||
h.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
h.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
go h.WsHandleData()
|
||||
err = h.wsAuthenticatedDial(&dialer)
|
||||
h.AuthenticatedWebsocketConn = &wshandler.WebsocketConnection{
|
||||
ExchangeName: h.Name,
|
||||
URL: wsAccountsOrdersURL,
|
||||
Verbose: h.Verbose,
|
||||
ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit,
|
||||
ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout,
|
||||
}
|
||||
h.WebsocketConn = &wshandler.WebsocketConnection{
|
||||
ExchangeName: h.Name,
|
||||
URL: wsMarketURL,
|
||||
Verbose: h.Verbose,
|
||||
ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit,
|
||||
ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout,
|
||||
}
|
||||
var dialer websocket.Dialer
|
||||
err := h.wsAuthenticatedDial(&dialer)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = h.wsLogin()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout)
|
||||
select {
|
||||
case response := <-h.Websocket.DataHandler:
|
||||
switch respType := response.(type) {
|
||||
case WsAuthenticatedDataResponse:
|
||||
if respType.ErrorCode > 0 {
|
||||
t.Error(respType)
|
||||
}
|
||||
case error:
|
||||
t.Error(respType)
|
||||
}
|
||||
case <-timer.C:
|
||||
t.Error("Websocket did not receive a response")
|
||||
}
|
||||
timer.Stop()
|
||||
|
||||
wsSetupRan = true
|
||||
}
|
||||
|
||||
@@ -655,46 +654,36 @@ func TestGetDepositAddress(t *testing.T) {
|
||||
// TestWsGetAccountsList connects to WS, logs in, gets account list
|
||||
func TestWsGetAccountsList(t *testing.T) {
|
||||
setupWsTests(t)
|
||||
h.wsGetAccountsList(currency.NewPairFromString("ethbtc"))
|
||||
timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout)
|
||||
select {
|
||||
case response := <-h.Websocket.DataHandler:
|
||||
switch respType := response.(type) {
|
||||
case WsAuthenticatedAccountsListResponse:
|
||||
if respType.ErrorCode > 0 {
|
||||
t.Error(respType)
|
||||
}
|
||||
case error:
|
||||
t.Error(respType)
|
||||
}
|
||||
case <-timer.C:
|
||||
t.Error("Websocket did not receive a response")
|
||||
resp, err := h.wsGetAccountsList(currency.NewPairFromString("ethbtc"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.ErrorCode > 0 {
|
||||
t.Error(resp.ErrorMessage)
|
||||
}
|
||||
timer.Stop()
|
||||
}
|
||||
|
||||
// TestWsGetOrderList connects to WS, logs in, gets order list
|
||||
func TestWsGetOrderList(t *testing.T) {
|
||||
setupWsTests(t)
|
||||
h.wsGetOrdersList(1, currency.NewPairFromString("ethbtc"))
|
||||
timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout)
|
||||
select {
|
||||
case <-h.Websocket.DataHandler:
|
||||
case <-timer.C:
|
||||
t.Error("Websocket did not receive a response")
|
||||
resp, err := h.wsGetOrdersList(1, currency.NewPairFromString("ethbtc"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.ErrorCode > 0 {
|
||||
t.Error(resp.ErrorMessage)
|
||||
}
|
||||
timer.Stop()
|
||||
}
|
||||
|
||||
// TestWsGetOrderDetails connects to WS, logs in, gets order details
|
||||
func TestWsGetOrderDetails(t *testing.T) {
|
||||
setupWsTests(t)
|
||||
h.wsGetOrderDetails("123")
|
||||
timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout)
|
||||
select {
|
||||
case <-h.Websocket.DataHandler:
|
||||
case <-timer.C:
|
||||
t.Error("Websocket did not receive a response")
|
||||
orderID := "123"
|
||||
resp, err := h.wsGetOrderDetails(orderID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.ErrorCode > 0 && (orderID == "123" && resp.ErrorCode != 10022) {
|
||||
t.Error(resp.ErrorMessage)
|
||||
}
|
||||
timer.Stop()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package huobi
|
||||
|
||||
import "github.com/thrasher-/gocryptotrader/currency"
|
||||
import "github.com/thrasher-corp/gocryptotrader/currency"
|
||||
|
||||
// Response stores the Huobi response information
|
||||
type Response struct {
|
||||
@@ -270,10 +270,10 @@ var (
|
||||
|
||||
// WsRequest defines a request data structure
|
||||
type WsRequest struct {
|
||||
Topic string `json:"req,omitempty"`
|
||||
Subscribe string `json:"sub,omitempty"`
|
||||
Unsubscribe string `json:"unsub,omitempty"`
|
||||
ClientGeneratedID string `json:"id,omitempty"`
|
||||
Topic string `json:"req,omitempty"`
|
||||
Subscribe string `json:"sub,omitempty"`
|
||||
Unsubscribe string `json:"unsub,omitempty"`
|
||||
ClientID int64 `json:"cid,string,omitempty"`
|
||||
}
|
||||
|
||||
// WsResponse defines a response from the websocket connection when there
|
||||
@@ -286,6 +286,7 @@ type WsResponse struct {
|
||||
Ping int64 `json:"ping"`
|
||||
Channel string `json:"ch"`
|
||||
Subscribed string `json:"subbed"`
|
||||
ClientID int64 `json:"cid,string,omitempty"`
|
||||
}
|
||||
|
||||
// WsHeartBeat defines a heartbeat request
|
||||
@@ -346,6 +347,7 @@ type WsAuthenticationRequest struct {
|
||||
SignatureVersion string `json:"SignatureVersion"`
|
||||
Timestamp string `json:"Timestamp"`
|
||||
Signature string `json:"Signature"`
|
||||
ClientID int64 `json:"cid,string,omitempty"`
|
||||
}
|
||||
|
||||
// WsMessage defines read data from the websocket connection
|
||||
@@ -363,6 +365,7 @@ type WsAuthenticatedSubscriptionRequest struct {
|
||||
Timestamp string `json:"Timestamp"`
|
||||
Signature string `json:"Signature"`
|
||||
Topic string `json:"topic"`
|
||||
ClientID int64 `json:"cid,string,omitempty"`
|
||||
}
|
||||
|
||||
// WsAuthenticatedAccountsListRequest request for account list authenticated connection
|
||||
@@ -375,6 +378,7 @@ type WsAuthenticatedAccountsListRequest struct {
|
||||
Signature string `json:"Signature"`
|
||||
Topic string `json:"topic"`
|
||||
Symbol currency.Pair `json:"symbol"`
|
||||
ClientID int64 `json:"cid,string,omitempty"`
|
||||
}
|
||||
|
||||
// WsAuthenticatedOrderDetailsRequest request for order details authenticated connection
|
||||
@@ -387,6 +391,7 @@ type WsAuthenticatedOrderDetailsRequest struct {
|
||||
Signature string `json:"Signature"`
|
||||
Topic string `json:"topic"`
|
||||
OrderID string `json:"order-id"`
|
||||
ClientID int64 `json:"cid,string,omitempty"`
|
||||
}
|
||||
|
||||
// WsAuthenticatedOrdersListRequest request for orderslist authenticated connection
|
||||
@@ -401,6 +406,7 @@ type WsAuthenticatedOrdersListRequest struct {
|
||||
States string `json:"states"`
|
||||
AccountID int64 `json:"account-id"`
|
||||
Symbol currency.Pair `json:"symbol"`
|
||||
ClientID int64 `json:"cid,string,omitempty"`
|
||||
}
|
||||
|
||||
// WsAuthenticatedDataResponse response from authenticated connection
|
||||
@@ -411,7 +417,7 @@ type WsAuthenticatedDataResponse struct {
|
||||
ErrorCode int64 `json:"err-code,omitempty"`
|
||||
ErrorMessage string `json:"err-msg,omitempty"`
|
||||
Ping int64 `json:"ping,omitempty"`
|
||||
CID string `json:"cid,omitempty"`
|
||||
ClientID int64 `json:"cid,string,omitempty"`
|
||||
}
|
||||
|
||||
// WsAuthenticatedAccountsResponse response from Accounts authenticated subscription
|
||||
@@ -529,3 +535,8 @@ type WsAuthenticatedOrderDetailResponse struct {
|
||||
WsAuthenticatedDataResponse
|
||||
Data WsAuthenticatedOrdersListResponseData `json:"data"`
|
||||
}
|
||||
|
||||
// WsPong sent for pong messages
|
||||
type WsPong struct {
|
||||
Pong int64 `json:"pong"`
|
||||
}
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
package huobi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
|
||||
"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"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -44,6 +43,9 @@ const (
|
||||
signatureVersion = "2"
|
||||
requestOp = "req"
|
||||
authOp = "auth"
|
||||
|
||||
loginDelay = 50 * time.Millisecond
|
||||
rateLimit = 20
|
||||
)
|
||||
|
||||
// Instantiates a communications channel between websocket connections
|
||||
@@ -52,20 +54,9 @@ var comms = make(chan WsMessage, 1)
|
||||
// WsConnect initiates a new websocket connection
|
||||
func (h *HUOBI) WsConnect() error {
|
||||
if !h.Websocket.IsEnabled() || !h.IsEnabled() {
|
||||
return errors.New(exchange.WebsocketNotEnabled)
|
||||
return errors.New(wshandler.WebsocketNotEnabled)
|
||||
}
|
||||
|
||||
var dialer websocket.Dialer
|
||||
|
||||
if h.Websocket.GetProxyAddress() != "" {
|
||||
proxy, err := url.Parse(h.Websocket.GetProxyAddress())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dialer.Proxy = http.ProxyURL(proxy)
|
||||
}
|
||||
|
||||
err := h.wsDial(&dialer)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -86,11 +77,9 @@ func (h *HUOBI) WsConnect() error {
|
||||
}
|
||||
|
||||
func (h *HUOBI) wsDial(dialer *websocket.Dialer) error {
|
||||
var err error
|
||||
var conStatus *http.Response
|
||||
h.WebsocketConn, conStatus, err = dialer.Dial(wsMarketURL, http.Header{})
|
||||
err := h.WebsocketConn.Dial(dialer, http.Header{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v %v %v Error: %v", wsMarketURL, conStatus, conStatus.StatusCode, err)
|
||||
return err
|
||||
}
|
||||
go h.wsMultiConnectionFunnel(h.WebsocketConn, wsMarketURL)
|
||||
return nil
|
||||
@@ -100,18 +89,16 @@ func (h *HUOBI) wsAuthenticatedDial(dialer *websocket.Dialer) error {
|
||||
if !h.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) {
|
||||
return fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", h.Name)
|
||||
}
|
||||
var err error
|
||||
var conStatus *http.Response
|
||||
h.AuthenticatedWebsocketConn, conStatus, err = dialer.Dial(wsAccountsOrdersURL, http.Header{})
|
||||
err := h.AuthenticatedWebsocketConn.Dial(dialer, http.Header{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v %v %v Error: %v", wsAccountsOrdersURL, conStatus, conStatus.StatusCode, err)
|
||||
return err
|
||||
}
|
||||
go h.wsMultiConnectionFunnel(h.AuthenticatedWebsocketConn, wsAccountsOrdersURL)
|
||||
return nil
|
||||
}
|
||||
|
||||
// wsMultiConnectionFunnel manages data from multiple endpoints and passes it to a channel
|
||||
func (h *HUOBI) wsMultiConnectionFunnel(ws *websocket.Conn, url string) {
|
||||
func (h *HUOBI) wsMultiConnectionFunnel(ws *wshandler.WebsocketConnection, url string) {
|
||||
h.Websocket.Wg.Add(1)
|
||||
defer h.Websocket.Wg.Done()
|
||||
for {
|
||||
@@ -119,29 +106,13 @@ func (h *HUOBI) wsMultiConnectionFunnel(ws *websocket.Conn, url string) {
|
||||
case <-h.Websocket.ShutdownC:
|
||||
return
|
||||
default:
|
||||
_, resp, err := ws.ReadMessage()
|
||||
resp, err := ws.ReadMessage()
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
h.Websocket.TrafficAlert <- struct{}{}
|
||||
b := bytes.NewReader(resp)
|
||||
gReader, err := gzip.NewReader(b)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
unzipped, err := ioutil.ReadAll(gReader)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
err = gReader.Close()
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
comms <- WsMessage{Raw: unzipped, URL: url}
|
||||
comms <- WsMessage{Raw: resp.Raw, URL: url}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,9 +126,6 @@ func (h *HUOBI) WsHandleData() {
|
||||
case <-h.Websocket.ShutdownC:
|
||||
return
|
||||
case resp := <-comms:
|
||||
if h.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v: %v: %v", h.Name, resp.URL, string(resp.Raw))
|
||||
}
|
||||
switch resp.URL {
|
||||
case wsMarketURL:
|
||||
h.wsHandleMarketData(resp)
|
||||
@@ -175,31 +143,26 @@ func (h *HUOBI) wsHandleAuthenticatedData(resp WsMessage) {
|
||||
h.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
if init.ErrorCode > 0 {
|
||||
if init.ErrorMessage == "api-signature-not-valid" {
|
||||
h.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
||||
}
|
||||
h.Websocket.DataHandler <- fmt.Errorf("%v %v Websocket error %v %s",
|
||||
h.Name,
|
||||
resp.URL,
|
||||
init.ErrorCode,
|
||||
init.ErrorMessage)
|
||||
return
|
||||
}
|
||||
if init.Ping != 0 {
|
||||
err = h.WebsocketConn.WriteJSON(`{"pong":1337}`)
|
||||
err = h.WebsocketConn.SendMessage(WsPong{Pong: init.Ping})
|
||||
if err != nil {
|
||||
log.Error(log.ExchangeSys, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if init.ErrorMessage == "api-signature-not-valid" {
|
||||
h.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
||||
}
|
||||
if init.Op == "sub" {
|
||||
if h.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v: %v: Successfully subscribed to %v", h.Name, resp.URL, init.Topic)
|
||||
}
|
||||
return
|
||||
}
|
||||
if init.ClientID > 0 {
|
||||
h.AuthenticatedWebsocketConn.AddResponseWithID(init.ClientID, resp.Raw)
|
||||
return
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.EqualFold(init.Op, authOp):
|
||||
@@ -232,27 +195,6 @@ func (h *HUOBI) wsHandleAuthenticatedData(resp WsMessage) {
|
||||
h.Websocket.DataHandler <- err
|
||||
}
|
||||
h.Websocket.DataHandler <- response
|
||||
case strings.EqualFold(init.Topic, wsAccountsList):
|
||||
var response WsAuthenticatedAccountsListResponse
|
||||
err := common.JSONDecode(resp.Raw, &response)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
}
|
||||
h.Websocket.DataHandler <- response
|
||||
case strings.EqualFold(init.Topic, wsOrdersList):
|
||||
var response WsAuthenticatedOrdersListResponse
|
||||
err := common.JSONDecode(resp.Raw, &response)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
}
|
||||
h.Websocket.DataHandler <- response
|
||||
case strings.EqualFold(init.Topic, wsOrdersDetail):
|
||||
var response WsAuthenticatedOrderDetailResponse
|
||||
err := common.JSONDecode(resp.Raw, &response)
|
||||
if err != nil {
|
||||
h.Websocket.DataHandler <- err
|
||||
}
|
||||
h.Websocket.DataHandler <- response
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,7 +217,7 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) {
|
||||
return
|
||||
}
|
||||
if init.Ping != 0 {
|
||||
err = h.WebsocketConn.WriteJSON(`{"pong":1337}`)
|
||||
err = h.WebsocketConn.SendMessage(WsPong{Pong: init.Ping})
|
||||
if err != nil {
|
||||
log.Error(log.ExchangeSys, err)
|
||||
}
|
||||
@@ -300,7 +242,7 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) {
|
||||
return
|
||||
}
|
||||
data := strings.Split(kline.Channel, ".")
|
||||
h.Websocket.DataHandler <- exchange.KlineData{
|
||||
h.Websocket.DataHandler <- wshandler.KlineData{
|
||||
Timestamp: time.Unix(0, kline.Timestamp),
|
||||
Exchange: h.GetName(),
|
||||
AssetType: "SPOT",
|
||||
@@ -319,9 +261,9 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) {
|
||||
return
|
||||
}
|
||||
data := strings.Split(trade.Channel, ".")
|
||||
h.Websocket.DataHandler <- exchange.TradeData{
|
||||
h.Websocket.DataHandler <- wshandler.TradeData{
|
||||
Exchange: h.GetName(),
|
||||
AssetType: "SPOT",
|
||||
AssetType: asset.Spot,
|
||||
CurrencyPair: currency.NewPairFromString(data[1]),
|
||||
Timestamp: time.Unix(0, trade.Tick.Timestamp),
|
||||
}
|
||||
@@ -356,7 +298,7 @@ func (h *HUOBI) WsProcessOrderbook(ob *WsDepth, symbol string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
h.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
|
||||
h.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: p,
|
||||
Exchange: h.GetName(),
|
||||
Asset: asset.Spot,
|
||||
@@ -368,10 +310,10 @@ func (h *HUOBI) WsProcessOrderbook(ob *WsDepth, symbol string) error {
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
func (h *HUOBI) GenerateDefaultSubscriptions() {
|
||||
var channels = []string{wsMarketKline, wsMarketDepth, wsMarketTrade}
|
||||
var subscriptions []exchange.WebsocketChannelSubscription
|
||||
var subscriptions []wshandler.WebsocketChannelSubscription
|
||||
if h.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
channels = append(channels, "orders.%v", "orders.%v.update")
|
||||
subscriptions = append(subscriptions, exchange.WebsocketChannelSubscription{
|
||||
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
|
||||
Channel: "accounts",
|
||||
})
|
||||
}
|
||||
@@ -380,7 +322,7 @@ func (h *HUOBI) GenerateDefaultSubscriptions() {
|
||||
for j := range enabledCurrencies {
|
||||
enabledCurrencies[j].Delimiter = ""
|
||||
channel := fmt.Sprintf(channels[i], enabledCurrencies[j].Lower().String())
|
||||
subscriptions = append(subscriptions, exchange.WebsocketChannelSubscription{
|
||||
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
|
||||
Channel: channel,
|
||||
Currency: enabledCurrencies[j],
|
||||
})
|
||||
@@ -390,39 +332,33 @@ func (h *HUOBI) GenerateDefaultSubscriptions() {
|
||||
}
|
||||
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (h *HUOBI) Subscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
|
||||
func (h *HUOBI) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
|
||||
if strings.Contains(channelToSubscribe.Channel, "orders.") ||
|
||||
strings.Contains(channelToSubscribe.Channel, "accounts") {
|
||||
return h.wsAuthenticatedSubscribe("sub", wsAccountsOrdersEndPoint+channelToSubscribe.Channel, channelToSubscribe.Channel)
|
||||
}
|
||||
subscription, err := common.JSONEncode(WsRequest{Subscribe: channelToSubscribe.Channel})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return h.wsSend(subscription)
|
||||
return h.WebsocketConn.SendMessage(WsRequest{Subscribe: channelToSubscribe.Channel})
|
||||
}
|
||||
|
||||
// Unsubscribe sends a websocket message to stop receiving data from the channel
|
||||
func (h *HUOBI) Unsubscribe(channelToSubscribe exchange.WebsocketChannelSubscription) error {
|
||||
func (h *HUOBI) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
|
||||
if strings.Contains(channelToSubscribe.Channel, "orders.") ||
|
||||
strings.Contains(channelToSubscribe.Channel, "accounts") {
|
||||
return h.wsAuthenticatedSubscribe("unsub", wsAccountsOrdersEndPoint+channelToSubscribe.Channel, channelToSubscribe.Channel)
|
||||
}
|
||||
subscription, err := common.JSONEncode(WsRequest{Unsubscribe: channelToSubscribe.Channel})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return h.wsSend(subscription)
|
||||
return h.WebsocketConn.SendMessage(WsRequest{Unsubscribe: channelToSubscribe.Channel})
|
||||
}
|
||||
|
||||
// WsSend sends data to the websocket server
|
||||
func (h *HUOBI) wsSend(data []byte) error {
|
||||
h.wsRequestMtx.Lock()
|
||||
defer h.wsRequestMtx.Unlock()
|
||||
if h.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v sending message to websocket %s", h.Name, string(data))
|
||||
}
|
||||
return h.WebsocketConn.WriteMessage(websocket.TextMessage, data)
|
||||
func (h *HUOBI) wsGenerateSignature(timestamp, endpoint string) []byte {
|
||||
values := url.Values{}
|
||||
values.Set("AccessKeyId", h.API.Credentials.Key)
|
||||
values.Set("SignatureMethod", signatureMethod)
|
||||
values.Set("SignatureVersion", signatureVersion)
|
||||
values.Set("Timestamp", timestamp)
|
||||
host := "api.huobi.pro"
|
||||
payload := fmt.Sprintf("%s\n%s\n%s\n%s",
|
||||
"GET", host, endpoint, values.Encode())
|
||||
return crypto.GetHMAC(crypto.HashSHA256, []byte(payload), []byte(h.API.Credentials.Secret))
|
||||
}
|
||||
|
||||
func (h *HUOBI) wsLogin() error {
|
||||
@@ -440,39 +376,16 @@ func (h *HUOBI) wsLogin() error {
|
||||
}
|
||||
hmac := h.wsGenerateSignature(timestamp, wsAccountsOrdersEndPoint)
|
||||
request.Signature = crypto.Base64Encode(hmac)
|
||||
err := h.wsAuthenticatedSend(request)
|
||||
err := h.AuthenticatedWebsocketConn.SendMessage(request)
|
||||
if err != nil {
|
||||
h.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
||||
return err
|
||||
}
|
||||
|
||||
time.Sleep(loginDelay)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HUOBI) wsAuthenticatedSend(request interface{}) error {
|
||||
h.wsRequestMtx.Lock()
|
||||
defer h.wsRequestMtx.Unlock()
|
||||
encodedRequest, err := common.JSONEncode(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if h.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v sending Authenticated message to websocket %s", h.Name, string(encodedRequest))
|
||||
}
|
||||
return h.AuthenticatedWebsocketConn.WriteMessage(websocket.TextMessage, encodedRequest)
|
||||
}
|
||||
|
||||
func (h *HUOBI) wsGenerateSignature(timestamp, endpoint string) []byte {
|
||||
values := url.Values{}
|
||||
values.Set("AccessKeyId", h.API.Credentials.Key)
|
||||
values.Set("SignatureMethod", signatureMethod)
|
||||
values.Set("SignatureVersion", signatureVersion)
|
||||
values.Set("Timestamp", timestamp)
|
||||
host := "api.huobi.pro"
|
||||
payload := fmt.Sprintf("%s\n%s\n%s\n%s",
|
||||
"GET", host, endpoint, values.Encode())
|
||||
return crypto.GetHMAC(crypto.HashSHA256, []byte(payload), []byte(h.API.Credentials.Secret))
|
||||
}
|
||||
|
||||
func (h *HUOBI) wsAuthenticatedSubscribe(operation, endpoint, topic string) error {
|
||||
timestamp := time.Now().UTC().Format(wsDateTimeFormatting)
|
||||
request := WsAuthenticatedSubscriptionRequest{
|
||||
@@ -485,12 +398,12 @@ func (h *HUOBI) wsAuthenticatedSubscribe(operation, endpoint, topic string) erro
|
||||
}
|
||||
hmac := h.wsGenerateSignature(timestamp, endpoint)
|
||||
request.Signature = crypto.Base64Encode(hmac)
|
||||
return h.wsAuthenticatedSend(request)
|
||||
return h.AuthenticatedWebsocketConn.SendMessage(request)
|
||||
}
|
||||
|
||||
func (h *HUOBI) wsGetAccountsList(pair currency.Pair) error {
|
||||
func (h *HUOBI) wsGetAccountsList(pair currency.Pair) (*WsAuthenticatedAccountsListResponse, error) {
|
||||
if !h.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
return fmt.Errorf("%v not authenticated cannot get accounts list", h.Name)
|
||||
return nil, fmt.Errorf("%v not authenticated cannot get accounts list", h.Name)
|
||||
}
|
||||
timestamp := time.Now().UTC().Format(wsDateTimeFormatting)
|
||||
request := WsAuthenticatedAccountsListRequest{
|
||||
@@ -504,12 +417,19 @@ func (h *HUOBI) wsGetAccountsList(pair currency.Pair) error {
|
||||
}
|
||||
hmac := h.wsGenerateSignature(timestamp, wsAccountListEndpoint)
|
||||
request.Signature = crypto.Base64Encode(hmac)
|
||||
return h.wsAuthenticatedSend(request)
|
||||
request.ClientID = h.AuthenticatedWebsocketConn.GenerateMessageID(true)
|
||||
resp, err := h.AuthenticatedWebsocketConn.SendMessageReturnResponse(request.ClientID, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var response WsAuthenticatedAccountsListResponse
|
||||
err = common.JSONDecode(resp, &response)
|
||||
return &response, err
|
||||
}
|
||||
|
||||
func (h *HUOBI) wsGetOrdersList(accountID int64, pair currency.Pair) error {
|
||||
func (h *HUOBI) wsGetOrdersList(accountID int64, pair currency.Pair) (*WsAuthenticatedOrdersResponse, error) {
|
||||
if !h.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
return fmt.Errorf("%v not authenticated cannot get orders list", h.Name)
|
||||
return nil, fmt.Errorf("%v not authenticated cannot get orders list", h.Name)
|
||||
}
|
||||
timestamp := time.Now().UTC().Format(wsDateTimeFormatting)
|
||||
request := WsAuthenticatedOrdersListRequest{
|
||||
@@ -525,12 +445,19 @@ func (h *HUOBI) wsGetOrdersList(accountID int64, pair currency.Pair) error {
|
||||
}
|
||||
hmac := h.wsGenerateSignature(timestamp, wsOrdersListEndpoint)
|
||||
request.Signature = crypto.Base64Encode(hmac)
|
||||
return h.wsAuthenticatedSend(request)
|
||||
request.ClientID = h.AuthenticatedWebsocketConn.GenerateMessageID(true)
|
||||
resp, err := h.AuthenticatedWebsocketConn.SendMessageReturnResponse(request.ClientID, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var response WsAuthenticatedOrdersResponse
|
||||
err = common.JSONDecode(resp, &response)
|
||||
return &response, err
|
||||
}
|
||||
|
||||
func (h *HUOBI) wsGetOrderDetails(orderID string) error {
|
||||
func (h *HUOBI) wsGetOrderDetails(orderID string) (*WsAuthenticatedOrderDetailResponse, error) {
|
||||
if !h.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
return fmt.Errorf("%v not authenticated cannot get order details", h.Name)
|
||||
return nil, fmt.Errorf("%v not authenticated cannot get order details", h.Name)
|
||||
}
|
||||
timestamp := time.Now().UTC().Format(wsDateTimeFormatting)
|
||||
request := WsAuthenticatedOrderDetailsRequest{
|
||||
@@ -544,5 +471,12 @@ func (h *HUOBI) wsGetOrderDetails(orderID string) error {
|
||||
}
|
||||
hmac := h.wsGenerateSignature(timestamp, wsOrdersDetailEndpoint)
|
||||
request.Signature = crypto.Base64Encode(hmac)
|
||||
return h.wsAuthenticatedSend(request)
|
||||
request.ClientID = h.AuthenticatedWebsocketConn.GenerateMessageID(true)
|
||||
resp, err := h.AuthenticatedWebsocketConn.SendMessageReturnResponse(request.ClientID, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var response WsAuthenticatedOrderDetailResponse
|
||||
err = common.JSONDecode(resp, &response)
|
||||
return &response, err
|
||||
}
|
||||
|
||||
@@ -8,15 +8,16 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"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/asset"
|
||||
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
|
||||
log "github.com/thrasher-/gocryptotrader/logger"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
// GetDefaultConfig returns a default exchange config
|
||||
@@ -89,12 +90,17 @@ func (h *HUOBI) SetDefaults() {
|
||||
h.API.Endpoints.URLDefault = huobiAPIURL
|
||||
h.API.Endpoints.URL = h.API.Endpoints.URLDefault
|
||||
h.API.Endpoints.WebsocketURL = wsMarketURL
|
||||
h.WebsocketInit()
|
||||
h.Websocket.Functionality = exchange.WebsocketKlineSupported |
|
||||
exchange.WebsocketOrderbookSupported |
|
||||
exchange.WebsocketTradeDataSupported |
|
||||
exchange.WebsocketSubscribeSupported |
|
||||
exchange.WebsocketUnsubscribeSupported
|
||||
h.Websocket = wshandler.New()
|
||||
h.Websocket.Functionality = wshandler.WebsocketKlineSupported |
|
||||
wshandler.WebsocketOrderbookSupported |
|
||||
wshandler.WebsocketTradeDataSupported |
|
||||
wshandler.WebsocketSubscribeSupported |
|
||||
wshandler.WebsocketUnsubscribeSupported |
|
||||
wshandler.WebsocketAuthenticatedEndpointsSupported |
|
||||
wshandler.WebsocketAccountDataSupported |
|
||||
wshandler.WebsocketMessageCorrelationSupported
|
||||
h.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
h.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
}
|
||||
|
||||
// Setup sets user configuration
|
||||
@@ -112,14 +118,38 @@ func (h *HUOBI) Setup(exch *config.ExchangeConfig) error {
|
||||
h.API.PEMKeySupport = exch.API.PEMKeySupport
|
||||
h.API.Credentials.PEMKey = exch.API.Credentials.PEMKey
|
||||
|
||||
return h.WebsocketSetup(h.WsConnect,
|
||||
err = h.Websocket.Setup(h.WsConnect,
|
||||
h.Subscribe,
|
||||
h.Unsubscribe,
|
||||
exch.Name,
|
||||
exch.Features.Enabled.Websocket,
|
||||
exch.Verbose,
|
||||
wsMarketURL,
|
||||
exch.API.Endpoints.WebsocketURL)
|
||||
exch.API.Endpoints.WebsocketURL,
|
||||
exch.API.AuthenticatedWebsocketSupport)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.WebsocketConn = &wshandler.WebsocketConnection{
|
||||
ExchangeName: h.Name,
|
||||
URL: wsMarketURL,
|
||||
ProxyURL: h.Websocket.GetProxyAddress(),
|
||||
Verbose: h.Verbose,
|
||||
RateLimit: rateLimit,
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
h.AuthenticatedWebsocketConn = &wshandler.WebsocketConnection{
|
||||
ExchangeName: h.Name,
|
||||
URL: wsAccountsOrdersURL,
|
||||
ProxyURL: h.Websocket.GetProxyAddress(),
|
||||
Verbose: h.Verbose,
|
||||
RateLimit: rateLimit,
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start starts the HUOBI go routine
|
||||
@@ -499,7 +529,7 @@ func (h *HUOBI) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange.F
|
||||
}
|
||||
|
||||
// GetWebsocket returns a pointer to the exchange websocket
|
||||
func (h *HUOBI) GetWebsocket() (*exchange.Websocket, error) {
|
||||
func (h *HUOBI) GetWebsocket() (*wshandler.Websocket, error) {
|
||||
return h.Websocket, nil
|
||||
}
|
||||
|
||||
@@ -625,20 +655,20 @@ func setOrderSideAndType(requestType string, orderDetail *exchange.OrderDetail)
|
||||
|
||||
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle subscribing
|
||||
func (h *HUOBI) SubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
|
||||
func (h *HUOBI) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
h.Websocket.SubscribeToChannels(channels)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle unsubscribing
|
||||
func (h *HUOBI) UnsubscribeToWebsocketChannels(channels []exchange.WebsocketChannelSubscription) error {
|
||||
h.Websocket.UnsubscribeToChannels(channels)
|
||||
func (h *HUOBI) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
h.Websocket.RemoveSubscribedChannels(channels)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSubscriptions returns a copied list of subscriptions
|
||||
func (h *HUOBI) GetSubscriptions() ([]exchange.WebsocketChannelSubscription, error) {
|
||||
func (h *HUOBI) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
|
||||
return h.Websocket.GetSubscriptions(), nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user