mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-31 23:16:54 +00:00
* Add exchange multichain support * Start tidying up * Add multichain transfer support for Bitfinex and fix poloniex bug * Add Coinbene multichain support * Start adjusting the deposit address manager * Fix deposit tests and further enhancements * Cleanup * Add bypass flag, expand tests plus error coverage for Huobi Adjust helpers * Address nitterinos * BFX wd changes * Address nitterinos * Minor fixes rebasing on master * Fix BFX acceptableMethods test * Add some TO-DOs for 2 tests WRT races * Fix acceptableMethods test round 2 * Address nitterinos
368 lines
9.3 KiB
Go
368 lines
9.3 KiB
Go
package btcmarkets
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/gorilla/websocket"
|
|
"github.com/thrasher-corp/gocryptotrader/common"
|
|
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
|
"github.com/thrasher-corp/gocryptotrader/currency"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
|
|
"github.com/thrasher-corp/gocryptotrader/log"
|
|
)
|
|
|
|
const (
|
|
btcMarketsWSURL = "wss://socket.btcmarkets.net/v2"
|
|
)
|
|
|
|
// WsConnect connects to a websocket feed
|
|
func (b *BTCMarkets) WsConnect() error {
|
|
if !b.Websocket.IsEnabled() || !b.IsEnabled() {
|
|
return errors.New(stream.WebsocketNotEnabled)
|
|
}
|
|
var dialer websocket.Dialer
|
|
err := b.Websocket.Conn.Dial(&dialer, http.Header{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if b.Verbose {
|
|
log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", b.Name)
|
|
}
|
|
|
|
b.Websocket.Wg.Add(1)
|
|
go b.wsReadData()
|
|
return nil
|
|
}
|
|
|
|
// wsReadData receives and passes on websocket messages for processing
|
|
func (b *BTCMarkets) wsReadData() {
|
|
defer b.Websocket.Wg.Done()
|
|
|
|
for {
|
|
resp := b.Websocket.Conn.ReadMessage()
|
|
if resp.Raw == nil {
|
|
return
|
|
}
|
|
err := b.wsHandleData(resp.Raw)
|
|
if err != nil {
|
|
b.Websocket.DataHandler <- err
|
|
}
|
|
}
|
|
}
|
|
|
|
func (b *BTCMarkets) wsHandleData(respRaw []byte) error {
|
|
var wsResponse WsMessageType
|
|
err := json.Unmarshal(respRaw, &wsResponse)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
switch wsResponse.MessageType {
|
|
case heartbeat:
|
|
if b.Verbose {
|
|
log.Debugf(log.ExchangeSys, "%v - Websocket heartbeat received %s", b.Name, respRaw)
|
|
}
|
|
case wsOB:
|
|
var ob WsOrderbook
|
|
err := json.Unmarshal(respRaw, &ob)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
p, err := currency.NewPairFromString(ob.Currency)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var bids, asks orderbook.Items
|
|
for x := range ob.Bids {
|
|
var price, amount float64
|
|
price, err = strconv.ParseFloat(ob.Bids[x][0].(string), 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
amount, err = strconv.ParseFloat(ob.Bids[x][1].(string), 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bids = append(bids, orderbook.Item{
|
|
Amount: amount,
|
|
Price: price,
|
|
OrderCount: int64(ob.Bids[x][2].(float64)),
|
|
})
|
|
}
|
|
for x := range ob.Asks {
|
|
var price, amount float64
|
|
price, err = strconv.ParseFloat(ob.Asks[x][0].(string), 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
amount, err = strconv.ParseFloat(ob.Asks[x][1].(string), 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
asks = append(asks, orderbook.Item{
|
|
Amount: amount,
|
|
Price: price,
|
|
OrderCount: int64(ob.Asks[x][2].(float64)),
|
|
})
|
|
}
|
|
if ob.Snapshot {
|
|
bids.SortBids() // Alignment completely out, sort is needed.
|
|
err = b.Websocket.Orderbook.LoadSnapshot(&orderbook.Base{
|
|
Pair: p,
|
|
Bids: bids,
|
|
Asks: asks,
|
|
LastUpdated: ob.Timestamp,
|
|
Asset: asset.Spot,
|
|
Exchange: b.Name,
|
|
VerifyOrderbook: b.CanVerifyOrderbook,
|
|
})
|
|
} else {
|
|
err = b.Websocket.Orderbook.Update(&buffer.Update{
|
|
UpdateTime: ob.Timestamp,
|
|
Asset: asset.Spot,
|
|
Bids: bids,
|
|
Asks: asks,
|
|
Pair: p,
|
|
})
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case tradeEndPoint:
|
|
if !b.IsSaveTradeDataEnabled() {
|
|
return nil
|
|
}
|
|
var t WsTrade
|
|
err := json.Unmarshal(respRaw, &t)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
p, err := currency.NewPairFromString(t.Currency)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
side := order.Buy
|
|
if t.Side == "Ask" {
|
|
side = order.Sell
|
|
}
|
|
|
|
return trade.AddTradesToBuffer(b.Name, trade.Data{
|
|
Timestamp: t.Timestamp,
|
|
CurrencyPair: p,
|
|
AssetType: asset.Spot,
|
|
Exchange: b.Name,
|
|
Price: t.Price,
|
|
Amount: t.Volume,
|
|
Side: side,
|
|
TID: strconv.FormatInt(t.TradeID, 10),
|
|
})
|
|
case tick:
|
|
var tick WsTick
|
|
err := json.Unmarshal(respRaw, &tick)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
p, err := currency.NewPairFromString(tick.Currency)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
b.Websocket.DataHandler <- &ticker.Price{
|
|
ExchangeName: b.Name,
|
|
Volume: tick.Volume,
|
|
High: tick.High24,
|
|
Low: tick.Low24h,
|
|
Bid: tick.Bid,
|
|
Ask: tick.Ask,
|
|
Last: tick.Last,
|
|
LastUpdated: tick.Timestamp,
|
|
AssetType: asset.Spot,
|
|
Pair: p,
|
|
}
|
|
case fundChange:
|
|
var transferData WsFundTransfer
|
|
err := json.Unmarshal(respRaw, &transferData)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
b.Websocket.DataHandler <- transferData
|
|
case orderChange:
|
|
var orderData WsOrderChange
|
|
err := json.Unmarshal(respRaw, &orderData)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
originalAmount := orderData.OpenVolume
|
|
var price float64
|
|
var trades []order.TradeHistory
|
|
var orderID = strconv.FormatInt(orderData.OrderID, 10)
|
|
for x := range orderData.Trades {
|
|
var isMaker bool
|
|
if orderData.Trades[x].LiquidityType == "Maker" {
|
|
isMaker = true
|
|
}
|
|
trades = append(trades, order.TradeHistory{
|
|
Price: orderData.Trades[x].Price,
|
|
Amount: orderData.Trades[x].Volume,
|
|
Fee: orderData.Trades[x].Fee,
|
|
Exchange: b.Name,
|
|
TID: strconv.FormatInt(orderData.Trades[x].TradeID, 10),
|
|
IsMaker: isMaker,
|
|
})
|
|
price = orderData.Trades[x].Price
|
|
originalAmount += orderData.Trades[x].Volume
|
|
}
|
|
oType, err := order.StringToOrderType(orderData.OrderType)
|
|
if err != nil {
|
|
b.Websocket.DataHandler <- order.ClassificationError{
|
|
Exchange: b.Name,
|
|
OrderID: orderID,
|
|
Err: err,
|
|
}
|
|
}
|
|
oSide, err := order.StringToOrderSide(orderData.Side)
|
|
if err != nil {
|
|
b.Websocket.DataHandler <- order.ClassificationError{
|
|
Exchange: b.Name,
|
|
OrderID: orderID,
|
|
Err: err,
|
|
}
|
|
}
|
|
oStatus, err := order.StringToOrderStatus(orderData.Status)
|
|
if err != nil {
|
|
b.Websocket.DataHandler <- order.ClassificationError{
|
|
Exchange: b.Name,
|
|
OrderID: orderID,
|
|
Err: err,
|
|
}
|
|
}
|
|
|
|
p, err := currency.NewPairFromString(orderData.MarketID)
|
|
if err != nil {
|
|
b.Websocket.DataHandler <- order.ClassificationError{
|
|
Exchange: b.Name,
|
|
OrderID: orderID,
|
|
Err: err,
|
|
}
|
|
}
|
|
|
|
b.Websocket.DataHandler <- &order.Detail{
|
|
Price: price,
|
|
Amount: originalAmount,
|
|
RemainingAmount: orderData.OpenVolume,
|
|
Exchange: b.Name,
|
|
ID: orderID,
|
|
ClientID: b.API.Credentials.ClientID,
|
|
Type: oType,
|
|
Side: oSide,
|
|
Status: oStatus,
|
|
AssetType: asset.Spot,
|
|
Date: orderData.Timestamp,
|
|
Trades: trades,
|
|
Pair: p,
|
|
}
|
|
case "error":
|
|
var wsErr WsError
|
|
err := json.Unmarshal(respRaw, &wsErr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return fmt.Errorf("%v websocket error. Code: %v Message: %v", b.Name, wsErr.Code, wsErr.Message)
|
|
default:
|
|
b.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: b.Name + stream.UnhandledMessage + string(respRaw)}
|
|
return nil
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (b *BTCMarkets) generateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
|
|
var channels = []string{wsOB, tick, tradeEndPoint}
|
|
enabledCurrencies, err := b.GetEnabledPairs(asset.Spot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var subscriptions []stream.ChannelSubscription
|
|
for i := range channels {
|
|
for j := range enabledCurrencies {
|
|
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
|
Channel: channels[i],
|
|
Currency: enabledCurrencies[j],
|
|
Asset: asset.Spot,
|
|
})
|
|
}
|
|
}
|
|
|
|
var authChannels = []string{fundChange, heartbeat, orderChange}
|
|
if b.Websocket.CanUseAuthenticatedEndpoints() {
|
|
for i := range authChannels {
|
|
subscriptions = append(subscriptions, stream.ChannelSubscription{
|
|
Channel: authChannels[i],
|
|
})
|
|
}
|
|
}
|
|
return subscriptions, nil
|
|
}
|
|
|
|
// Subscribe sends a websocket message to receive data from the channel
|
|
func (b *BTCMarkets) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
|
|
var authChannels = []string{fundChange, heartbeat, orderChange}
|
|
|
|
var payload WsSubscribe
|
|
payload.MessageType = subscribe
|
|
|
|
for i := range channelsToSubscribe {
|
|
payload.Channels = append(payload.Channels,
|
|
channelsToSubscribe[i].Channel)
|
|
|
|
if channelsToSubscribe[i].Currency.String() != "" {
|
|
if !common.StringDataCompare(payload.MarketIDs,
|
|
channelsToSubscribe[i].Currency.String()) {
|
|
payload.MarketIDs = append(payload.MarketIDs,
|
|
channelsToSubscribe[i].Currency.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
for i := range authChannels {
|
|
if !common.StringDataCompare(payload.Channels, authChannels[i]) {
|
|
continue
|
|
}
|
|
signTime := strconv.FormatInt(time.Now().UnixMilli(), 10)
|
|
strToSign := "/users/self/subscribe" + "\n" + signTime
|
|
tempSign, err := crypto.GetHMAC(crypto.HashSHA512,
|
|
[]byte(strToSign),
|
|
[]byte(b.API.Credentials.Secret))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sign := crypto.Base64Encode(tempSign)
|
|
payload.Key = b.API.Credentials.Key
|
|
payload.Signature = sign
|
|
payload.Timestamp = signTime
|
|
break
|
|
}
|
|
|
|
err := b.Websocket.Conn.SendJSONMessage(payload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
b.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe...)
|
|
return nil
|
|
}
|