Files
gocryptotrader/exchanges/okcoin/okcoin_websocket.go
Adrian Gallagher 7dcb1ab553 Migrate from gometalinter.v2 to golangci-lint (#249)
* Migrate from gometalinter.v2 to golangci-lint
2019-03-01 16:10:29 +11:00

349 lines
9.9 KiB
Go

package okcoin
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/pair"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
log "github.com/thrasher-/gocryptotrader/logger"
)
const (
wsSubTicker = "ok_sub_spot_%s_ticker"
wsSubDepthIncrement = "ok_sub_spot_%s_depth"
wsSubDepthFull = "ok_sub_spot_%s_depth_%s"
wsSubTrades = "ok_sub_spot_%s_deals"
wsSubKline = "ok_sub_spot_%s_kline_%s"
)
// PingHandler handles the keep alive
func (o *OKCoin) PingHandler(_ string) error {
return o.WebsocketConn.WriteControl(websocket.PingMessage,
[]byte("{'event':'ping'}"),
time.Now().Add(time.Second))
}
// AddChannel adds a new channel on the websocket client
func (o *OKCoin) AddChannel(channel string) error {
event := WebsocketEvent{"addChannel", channel}
data, err := common.JSONEncode(event)
if err != nil {
return err
}
return o.WebsocketConn.WriteMessage(websocket.TextMessage, data)
}
// WsConnect initiates a websocket connection
func (o *OKCoin) WsConnect() error {
if !o.Websocket.IsEnabled() || !o.IsEnabled() {
return errors.New(exchange.WebsocketNotEnabled)
}
klineValues := []string{"1min", "3min", "5min", "15min", "30min", "1hour",
"2hour", "4hour", "6hour", "12hour", "day", "3day", "week"}
var dialer websocket.Dialer
if o.Websocket.GetProxyAddress() != "" {
proxy, err := url.Parse(o.Websocket.GetProxyAddress())
if err != nil {
return err
}
dialer.Proxy = http.ProxyURL(proxy)
}
var err error
o.WebsocketConn, _, err = dialer.Dial(o.Websocket.GetWebsocketURL(),
http.Header{})
if err != nil {
return err
}
o.WebsocketConn.SetPingHandler(o.PingHandler)
go o.WsHandleData()
for _, p := range o.GetEnabledCurrencies() {
fPair := exchange.FormatExchangeCurrency(o.GetName(), p)
o.AddChannel(fmt.Sprintf(wsSubDepthFull, fPair.String(), "20"))
o.AddChannel(fmt.Sprintf(wsSubKline, fPair.String(), klineValues[0]))
o.AddChannel(fmt.Sprintf(wsSubTicker, fPair.String()))
o.AddChannel(fmt.Sprintf(wsSubTrades, fPair.String()))
}
return nil
}
// WsReadData reads from the websocket connection
func (o *OKCoin) WsReadData() (exchange.WebsocketResponse, error) {
_, resp, err := o.WebsocketConn.ReadMessage()
if err != nil {
return exchange.WebsocketResponse{}, err
}
o.Websocket.TrafficAlert <- struct{}{}
return exchange.WebsocketResponse{Raw: resp}, nil
}
// WsHandleData handles stream data from the websocket connection
func (o *OKCoin) WsHandleData() {
o.Websocket.Wg.Add(1)
defer func() {
err := o.WebsocketConn.Close()
if err != nil {
o.Websocket.DataHandler <- fmt.Errorf("okcoin_websocket.go - Unable to to close Websocket connection. Error: %s",
err)
}
o.Websocket.Wg.Done()
}()
for {
select {
case <-o.Websocket.ShutdownC:
return
default:
resp, err := o.WsReadData()
if err != nil {
o.Websocket.DataHandler <- err
}
var init []WsResponse
err = common.JSONDecode(resp.Raw, &init)
if err != nil {
o.Websocket.DataHandler <- err
continue
}
if init[0].ErrorCode != "" {
log.Error(o.WebsocketErrors[init[0].ErrorCode])
}
if init[0].Success {
if init[0].Data == nil {
continue
}
}
if init[0].Channel == "addChannel" {
continue
}
var currencyPairSlice []string
splitChar := common.SplitStrings(init[0].Channel, "_")
currencyPairSlice = append(currencyPairSlice,
common.StringToUpper(splitChar[3]),
common.StringToUpper(splitChar[4]))
currencyPair := common.JoinStrings(currencyPairSlice, "-")
assetType := common.StringToUpper(splitChar[2])
switch {
case common.StringContains(init[0].Channel, "ticker") &&
common.StringContains(init[0].Channel, "spot"):
var ticker WsTicker
err = common.JSONDecode(init[0].Data, &ticker)
if err != nil {
o.Websocket.DataHandler <- err
continue
}
o.Websocket.DataHandler <- exchange.TickerData{
Timestamp: time.Unix(0, ticker.Timestamp),
Pair: pair.NewCurrencyPairFromString(currencyPair),
AssetType: assetType,
Exchange: o.GetName(),
ClosePrice: ticker.Close,
OpenPrice: ticker.Open,
HighPrice: ticker.Last,
LowPrice: ticker.Low,
Quantity: ticker.Volume,
}
case common.StringContains(init[0].Channel, "depth"):
var orderbook WsOrderbook
err = common.JSONDecode(init[0].Data, &orderbook)
if err != nil {
o.Websocket.DataHandler <- err
continue
}
o.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
Pair: pair.NewCurrencyPairFromString(currencyPair),
Exchange: o.GetName(),
Asset: assetType,
}
case common.StringContains(init[0].Channel, "kline"):
var klineData [][]interface{}
err = common.JSONDecode(init[0].Data, &klineData)
if err != nil {
o.Websocket.DataHandler <- err
continue
}
var klines []WsKlines
for _, data := range klineData {
var newKline WsKlines
newKline.Timestamp, _ = strconv.ParseInt(data[0].(string), 10, 64)
newKline.Open, _ = strconv.ParseFloat(data[1].(string), 64)
newKline.High, _ = strconv.ParseFloat(data[1].(string), 64)
newKline.Low, _ = strconv.ParseFloat(data[1].(string), 64)
newKline.Close, _ = strconv.ParseFloat(data[1].(string), 64)
newKline.Volume, _ = strconv.ParseFloat(data[1].(string), 64)
klines = append(klines, newKline)
}
for _, data := range klines {
o.Websocket.DataHandler <- exchange.KlineData{
Timestamp: time.Unix(0, data.Timestamp),
Pair: pair.NewCurrencyPairFromString(currencyPair),
AssetType: assetType,
Exchange: o.GetName(),
OpenPrice: data.Open,
ClosePrice: data.Close,
HighPrice: data.High,
LowPrice: data.Low,
Volume: data.Volume,
}
}
case common.StringContains(init[0].Channel, "spot") &&
common.StringContains(init[0].Channel, "deals"):
var dealsData [][]interface{}
err = common.JSONDecode(init[0].Data, &dealsData)
if err != nil {
o.Websocket.DataHandler <- err
continue
}
var deals []WsDeals
for _, data := range dealsData {
var newDeal WsDeals
newDeal.TID, _ = strconv.ParseInt(data[0].(string), 10, 64)
newDeal.Price, _ = strconv.ParseFloat(data[1].(string), 64)
newDeal.Amount, _ = strconv.ParseFloat(data[2].(string), 64)
newDeal.Timestamp, _ = data[3].(string)
newDeal.Type, _ = data[4].(string)
deals = append(deals, newDeal) // nolint: staticcheck
// TODO: will need to link this up
}
}
}
}
}
// SetWebsocketErrorDefaults sets default errors for websocket
func (o *OKCoin) SetWebsocketErrorDefaults() {
o.WebsocketErrors = map[string]string{
"10001": "Illegal parameters",
"10002": "Authentication failure",
"10003": "This connection has requested other user data",
"10004": "This connection did not request this user data",
"10005": "System error",
"10009": "Order does not exist",
"10010": "Insufficient funds",
"10011": "Order quantity too low",
"10012": "Only support btc_usd/btc_cny ltc_usd/ltc_cny",
"10014": "Order price must be between 0 - 1,000,000",
"10015": "Channel subscription temporally not available",
"10016": "Insufficient coins",
"10017": "WebSocket authorization error",
"10100": "User frozen",
"10216": "Non-public API",
"20001": "User does not exist",
"20002": "User frozen",
"20003": "Frozen due to force liquidation",
"20004": "Future account frozen",
"20005": "User future account does not exist",
"20006": "Required field can not be null",
"20007": "Illegal parameter",
"20008": "Future account fund balance is zero",
"20009": "Future contract status error",
"20010": "Risk rate information does not exist",
"20011": `Risk rate bigger than 90% before opening position`,
"20012": `Risk rate bigger than 90% after opening position`,
"20013": "Temporally no counter party price",
"20014": "System error",
"20015": "Order does not exist",
"20016": "Liquidation quantity bigger than holding",
"20017": "Not authorized/illegal order ID",
"20018": `Order price higher than 105% or lower than 95% of the price of last minute`,
"20019": "IP restrained to access the resource",
"20020": "Secret key does not exist",
"20021": "Index information does not exist",
"20022": "Wrong API interface",
"20023": "Fixed margin user",
"20024": "Signature does not match",
"20025": "Leverage rate error",
}
}
// WsOrderbook defines orderbook data from websocket connection
type WsOrderbook struct {
Asks [][]string `json:"asks"`
Bids [][]string `json:"bids"`
Timestamp int64 `json:"timestamp"`
}
// WsResponse defines initial response stream
type WsResponse struct {
Channel string `json:"channel"`
Result bool `json:"result"`
Success bool `json:"success"`
ErrorCode string `json:"errorcode"`
Data json.RawMessage `json:"data"`
}
// WsKlines defines a Kline response data from the websocket connection
type WsKlines struct {
Timestamp int64
Open float64
High float64
Low float64
Close float64
Volume float64
}
// WsTicker holds ticker data for websocket
type WsTicker struct {
High float64 `json:"high,string"`
Volume float64 `json:"vol,string"`
Last float64 `json:"last,string"`
Low float64 `json:"low,string"`
Buy float64 `json:"buy,string"`
Change float64 `json:"change,string"`
Sell float64 `json:"sell,string"`
DayLow float64 `json:"dayLow,string"`
Close float64 `json:"close,string"`
DayHigh float64 `json:"dayHigh,string"`
Open float64 `json:"open,string"`
Timestamp int64 `json:"timestamp"`
}
// WsDeals defines a deal response from the websocket connection
type WsDeals struct {
TID int64
Price float64
Amount float64
Timestamp string
Type string
}