mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-20 23:16:49 +00:00
* Added new base logger * updated example and test configs * updated exchange helpers restful router & server * logPath is now passed to the logger to remove dependency on common package * updated everything besides exchanges to use new logger * alphapoint to bitmex done * updated bitmex bitstamp bittrex btcc and also performance changes to logger * btcmarkets coinbase coinut exmo gateio wrappers updated * gateio and gemini logger updated * hitbtc huobi itbit & kraken updated * All exchanges updatd * return correct error for disabled websocket * don't disconnect client on invalid json * updated router internal logging * log.Fatal to t.Error for tests * Changed from fatal to error failure to set maxprocs * output ANSI codes for everything but windows for now due to lack of windows support * added error handling to logger and unit tests * clear wording on print -> log.print * added benchmark test * cleaned up import sections * Updated logger based on PR requests (added default config options on failure/setting errors) * ah this should fix travici enc config issue * Load entire config and clear out logging to hopefully fix travisci issue * wording & test error handling * fixed formatting issues based on feedback * fixed formatting issues based on feedback * changed CheckDir to use mkdirall instead of mkdir and other changes based on feedback
366 lines
8.1 KiB
Go
366 lines
8.1 KiB
Go
package coinut
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"time"
|
|
|
|
"github.com/gorilla/websocket"
|
|
"github.com/thrasher-/gocryptotrader/common"
|
|
"github.com/thrasher-/gocryptotrader/currency/pair"
|
|
exchange "github.com/thrasher-/gocryptotrader/exchanges"
|
|
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
|
|
log "github.com/thrasher-/gocryptotrader/logger"
|
|
)
|
|
|
|
const coinutWebsocketURL = "wss://wsapi.coinut.com"
|
|
|
|
var nNonce map[int64]string
|
|
var channels map[string]chan []byte
|
|
var instrumentListByString map[string]int64
|
|
var instrumentListByCode map[int64]string
|
|
var populatedList bool
|
|
|
|
// NOTE for speed considerations
|
|
// wss://wsapi-as.coinut.com
|
|
// wss://wsapi-na.coinut.com
|
|
// wss://wsapi-eu.coinut.com
|
|
|
|
// WsReadData reads data from the websocket connection
|
|
func (c *COINUT) WsReadData() {
|
|
c.Websocket.Wg.Add(1)
|
|
|
|
defer func() {
|
|
err := c.WebsocketConn.Close()
|
|
if err != nil {
|
|
c.Websocket.DataHandler <- fmt.Errorf("coinut_websocket.go - Unable to to close Websocket connection. Error: %s",
|
|
err)
|
|
}
|
|
c.Websocket.Wg.Done()
|
|
}()
|
|
|
|
for {
|
|
select {
|
|
case <-c.Websocket.ShutdownC:
|
|
return
|
|
|
|
default:
|
|
_, resp, err := c.WebsocketConn.ReadMessage()
|
|
if err != nil {
|
|
c.Websocket.DataHandler <- err
|
|
return
|
|
}
|
|
|
|
c.Websocket.TrafficAlert <- struct{}{}
|
|
c.Websocket.Intercomm <- exchange.WebsocketResponse{Raw: resp}
|
|
}
|
|
}
|
|
}
|
|
|
|
// WsHandleData handles read data
|
|
func (c *COINUT) WsHandleData() {
|
|
c.Websocket.Wg.Add(1)
|
|
defer c.Websocket.Wg.Done()
|
|
|
|
for {
|
|
select {
|
|
case <-c.Websocket.ShutdownC:
|
|
return
|
|
|
|
case resp := <-c.Websocket.Intercomm:
|
|
var incoming wsResponse
|
|
err := common.JSONDecode(resp.Raw, &incoming)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
switch incoming.Reply {
|
|
case "hb":
|
|
channels["hb"] <- resp.Raw
|
|
|
|
case "inst_tick":
|
|
var ticker WsTicker
|
|
err := common.JSONDecode(resp.Raw, &ticker)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
c.Websocket.DataHandler <- exchange.TickerData{
|
|
Timestamp: time.Unix(0, ticker.Timestamp),
|
|
Exchange: c.GetName(),
|
|
AssetType: "SPOT",
|
|
HighPrice: ticker.HighestBuy,
|
|
LowPrice: ticker.LowestSell,
|
|
ClosePrice: ticker.Last,
|
|
Quantity: ticker.Volume,
|
|
}
|
|
|
|
case "inst_order_book":
|
|
var orderbooksnapshot WsOrderbookSnapshot
|
|
err := common.JSONDecode(resp.Raw, &orderbooksnapshot)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
err = c.WsProcessOrderbookSnapshot(orderbooksnapshot)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
currencyPair := instrumentListByCode[orderbooksnapshot.InstID]
|
|
|
|
c.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
|
|
Exchange: c.GetName(),
|
|
Asset: "SPOT",
|
|
Pair: pair.NewCurrencyPairFromString(currencyPair),
|
|
}
|
|
|
|
case "inst_order_book_update":
|
|
var orderbookUpdate WsOrderbookUpdate
|
|
err := common.JSONDecode(resp.Raw, &orderbookUpdate)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
err = c.WsProcessOrderbookUpdate(orderbookUpdate)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
currencyPair := instrumentListByCode[orderbookUpdate.InstID]
|
|
|
|
c.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
|
|
Exchange: c.GetName(),
|
|
Asset: "SPOT",
|
|
Pair: pair.NewCurrencyPairFromString(currencyPair),
|
|
}
|
|
|
|
case "inst_trade":
|
|
var tradeSnap WsTradeSnapshot
|
|
err := common.JSONDecode(resp.Raw, &tradeSnap)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
case "inst_trade_update":
|
|
var tradeUpdate WsTradeUpdate
|
|
err := common.JSONDecode(resp.Raw, &tradeUpdate)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
currencyPair := instrumentListByCode[tradeUpdate.InstID]
|
|
|
|
c.Websocket.DataHandler <- exchange.TradeData{
|
|
Timestamp: time.Unix(tradeUpdate.Timestamp, 0),
|
|
CurrencyPair: pair.NewCurrencyPairFromString(currencyPair),
|
|
AssetType: "SPOT",
|
|
Exchange: c.GetName(),
|
|
Price: tradeUpdate.Price,
|
|
Side: tradeUpdate.Side,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// WsConnect intiates a websocket connection
|
|
func (c *COINUT) WsConnect() error {
|
|
if !c.Websocket.IsEnabled() || !c.IsEnabled() {
|
|
return errors.New(exchange.WebsocketNotEnabled)
|
|
}
|
|
|
|
var Dialer websocket.Dialer
|
|
|
|
if c.Websocket.GetProxyAddress() != "" {
|
|
proxy, err := url.Parse(c.Websocket.GetProxyAddress())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
Dialer.Proxy = http.ProxyURL(proxy)
|
|
}
|
|
|
|
var err error
|
|
c.WebsocketConn, _, err = Dialer.Dial(c.Websocket.GetWebsocketURL(),
|
|
http.Header{})
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !populatedList {
|
|
instrumentListByString = make(map[string]int64)
|
|
instrumentListByCode = make(map[int64]string)
|
|
|
|
err = c.WsSetInstrumentList()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
populatedList = true
|
|
}
|
|
|
|
err = c.WsSubscribe()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// define bi-directional communication
|
|
channels = make(map[string]chan []byte)
|
|
channels["hb"] = make(chan []byte, 1)
|
|
|
|
go c.WsReadData()
|
|
go c.WsHandleData()
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetNonce returns a nonce for a required request
|
|
func (c *COINUT) GetNonce() int64 {
|
|
if c.Nonce.Get() == 0 {
|
|
c.Nonce.Set(time.Now().Unix())
|
|
} else {
|
|
c.Nonce.Inc()
|
|
}
|
|
|
|
return c.Nonce.Get()
|
|
}
|
|
|
|
// WsSetInstrumentList fetches instrument list and propagates a local cache
|
|
func (c *COINUT) WsSetInstrumentList() error {
|
|
request, err := common.JSONEncode(wsRequest{
|
|
Request: "inst_list",
|
|
SecType: "SPOT",
|
|
Nonce: c.GetNonce(),
|
|
})
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = c.WebsocketConn.WriteMessage(websocket.TextMessage, request)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, resp, err := c.WebsocketConn.ReadMessage()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.Websocket.TrafficAlert <- struct{}{}
|
|
|
|
var list WsInstrumentList
|
|
err = common.JSONDecode(resp, &list)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for currency, data := range list.Spot {
|
|
instrumentListByString[currency] = data[0].InstID
|
|
instrumentListByCode[data[0].InstID] = currency
|
|
}
|
|
|
|
if len(instrumentListByString) == 0 || len(instrumentListByCode) == 0 {
|
|
return errors.New("instrument lists failed to populate")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// WsSubscribe subscribes to websocket streams
|
|
func (c *COINUT) WsSubscribe() error {
|
|
pairs := c.GetEnabledCurrencies()
|
|
|
|
for _, p := range pairs {
|
|
ticker := wsRequest{
|
|
Request: "inst_tick",
|
|
InstID: instrumentListByString[p.Pair().String()],
|
|
Subscribe: true,
|
|
Nonce: c.GetNonce(),
|
|
}
|
|
|
|
tickjson, err := common.JSONEncode(ticker)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = c.WebsocketConn.WriteMessage(websocket.TextMessage, tickjson)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
orderbook := wsRequest{
|
|
Request: "inst_order_book",
|
|
InstID: instrumentListByString[p.Pair().String()],
|
|
Subscribe: true,
|
|
Nonce: c.GetNonce(),
|
|
}
|
|
|
|
objson, err := common.JSONEncode(orderbook)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = c.WebsocketConn.WriteMessage(websocket.TextMessage, objson)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// WsProcessOrderbookSnapshot processes the orderbook snapshot
|
|
func (c *COINUT) WsProcessOrderbookSnapshot(ob WsOrderbookSnapshot) error {
|
|
var bids []orderbook.Item
|
|
for _, bid := range ob.Buy {
|
|
bids = append(bids, orderbook.Item{
|
|
Amount: bid.Volume,
|
|
Price: bid.Price,
|
|
})
|
|
}
|
|
|
|
var asks []orderbook.Item
|
|
for _, ask := range ob.Sell {
|
|
asks = append(asks, orderbook.Item{
|
|
Amount: ask.Volume,
|
|
Price: ask.Price,
|
|
})
|
|
}
|
|
|
|
var newOrderbook orderbook.Base
|
|
newOrderbook.Asks = asks
|
|
newOrderbook.Bids = bids
|
|
newOrderbook.CurrencyPair = instrumentListByCode[ob.InstID]
|
|
newOrderbook.Pair = pair.NewCurrencyPairFromString(instrumentListByCode[ob.InstID])
|
|
newOrderbook.AssetType = "SPOT"
|
|
newOrderbook.LastUpdated = time.Now()
|
|
|
|
return c.Websocket.Orderbook.LoadSnapshot(newOrderbook, c.GetName())
|
|
}
|
|
|
|
// WsProcessOrderbookUpdate process an orderbook update
|
|
func (c *COINUT) WsProcessOrderbookUpdate(ob WsOrderbookUpdate) error {
|
|
p := pair.NewCurrencyPairFromString(instrumentListByCode[ob.InstID])
|
|
|
|
if ob.Side == "buy" {
|
|
return c.Websocket.Orderbook.Update([]orderbook.Item{
|
|
{Price: ob.Price, Amount: ob.Volume}},
|
|
nil,
|
|
p,
|
|
time.Now(),
|
|
c.GetName(),
|
|
"SPOT")
|
|
}
|
|
|
|
return c.Websocket.Orderbook.Update([]orderbook.Item{
|
|
{Price: ob.Price, Amount: ob.Volume}},
|
|
nil,
|
|
p,
|
|
time.Now(),
|
|
c.GetName(),
|
|
"SPOT")
|
|
}
|