Merge branch 'master' into engine

This commit is contained in:
Adrian Gallagher
2019-08-09 15:46:24 +10:00
353 changed files with 4966 additions and 4196 deletions

View File

@@ -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">
[![Build Status](https://travis-ci.org/thrasher-/gocryptotrader.svg?branch=master)](https://travis-ci.org/thrasher-/gocryptotrader)
[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-/gocryptotrader/blob/master/LICENSE)
[![GoDoc](https://godoc.org/github.com/thrasher-/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-/gocryptotrader/exchanges/huobi)
[![Coverage Status](http://codecov.io/github/thrasher-/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-/gocryptotrader?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-/gocryptotrader)
[![Build Status](https://travis-ci.org/thrasher-corp/gocryptotrader.svg?branch=master)](https://travis-ci.org/thrasher-corp/gocryptotrader)
[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/exchanges/huobi)
[![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](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:

View File

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

View File

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

View File

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

View File

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

View File

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