Merge pull request #169 from ermalguni/master

OKEX websocket
resolves #158
This commit is contained in:
Ryan O'Hara-Reid
2018-08-27 10:11:19 +10:00
committed by GitHub
7 changed files with 239 additions and 54 deletions

View File

@@ -5,8 +5,8 @@ COPY Gopkg.* ./
RUN dep ensure -vendor-only
COPY . .
RUN mv -vn config_example.json config.json \
&& GOARCH=386 GOOS=linux CGO_ENABLED=0 go install -v \
&& mv /go/bin/linux_386 /go/bin/gocryptotrader
&& GOARCH=386 GOOS=linux CGO_ENABLED=0 go build . \
&& mv gocryptotrader /go/bin/gocryptotrader
FROM alpine:latest
RUN apk update && apk add --no-cache ca-certificates

View File

@@ -1036,32 +1036,31 @@
]
},
{
"name": "OKEX",
"enabled": true,
"verbose": false,
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"authenticatedApiSupport": false,
"apiKey": "Key",
"apiSecret": "Secret",
"availablePairs": "ltc_btc,eth_btc,etc_btc,bch_btc,btc_usdt,eth_usdt,ltc_usdt,etc_usdt,bch_usdt,etc_eth,bt1_btc,bt2_btc,btg_btc,qtum_btc,hsr_btc,neo_btc,gas_btc,qtum_usdt,hsr_usdt,neo_usdt,gas_usdt,btc_usd,ltc_usd,eth_usd,etc_usd,bch_usd",
"enabledPairs": "btc_usd,ltc_usd",
"baseCurrencies": "USD",
"assetTypes": "SPOT,this_week,next_week,quarter",
"supportsAutoPairUpdates": false,
"pairsLastUpdated": 1522111402,
"configCurrencyPairFormat": {
"uppercase": false,
"delimiter": "_"
"Name": "OKEX",
"Enabled": true,
"Verbose": false,
"Websocket": false,
"UseSandbox": false,
"RESTPollingDelay": 10,
"HTTPTimeout": 15000000000,
"AuthenticatedAPISupport": false,
"APIKey": "Key",
"APISecret": "Secret",
"AvailablePairs": "ltc_btc,eth_btc,etc_btc,bch_btc,btc_usdt,eth_usdt,ltc_usdt,etc_usdt,bch_usdt,etc_eth,bt1_btc,bt2_btc,btg_btc,qtum_btc,hsr_btc,neo_btc,gas_btc,qtum_usdt,hsr_usdt,neo_usdt,gas_usdt,btc_usdt,ltc_usdt,eth_usdt,etc_usdt,bch_usdt",
"EnabledPairs": "btc_usdt,ltc_usdt",
"BaseCurrencies": "USD",
"AssetTypes": "SPOT,this_week,next_week,quarter",
"SupportsAutoPairUpdates": false,
"PairsLastUpdated": 1522111402,
"ConfigCurrencyPairFormat": {
"Uppercase": false,
"Delimiter": "_"
},
"requestCurrencyPairFormat": {
"uppercase": false,
"delimiter": "_"
"RequestCurrencyPairFormat": {
"Uppercase": false,
"Delimiter": "_"
},
"bankAccounts": [
"BankAccounts": [
{
"bankName": "",
"bankAddress": "",

View File

@@ -8,8 +8,10 @@ import (
"reflect"
"strconv"
"strings"
"sync"
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
@@ -78,6 +80,8 @@ var errMissValue = errors.New("warning - resp value is missing from exchange")
// OKEX is the overaching type across the OKEX methods
type OKEX struct {
exchange.Base
WebsocketConn *websocket.Conn
mu sync.Mutex
// Spot and contract market error codes as per https://www.okex.com/rest_request.html
ErrorCodes map[string]error

View File

@@ -1,11 +1,13 @@
package okex
import "encoding/json"
// ContractPrice holds date and ticker price price for contracts.
type ContractPrice struct {
Date string `json:"date"`
Ticker struct {
Buy float64 `json:"buy"`
ContractID int `json:"contract_id"`
ContractID float64 `json:"contract_id"`
High float64 `json:"high"`
Low float64 `json:"low"`
Last float64 `json:"last"`
@@ -17,6 +19,33 @@ type ContractPrice struct {
Error interface{} `json:"error_code"`
}
type MultiStreamData struct {
Channel string `json:"channel"`
Data json.RawMessage `json:"data"`
}
type TickerStreamData struct {
Buy string `json:"buy"`
Change string `json:"change"`
High string `json:"high"`
Low string `json:"low"`
Last string `json:"last"`
Sell string `json:"sell"`
DayLow string `json:"dayLow"`
DayHigh string `json:"dayHigh"`
Timestamp float64 `json:"timestamp"`
Vol string `json:"vol"`
}
type DealsStreamData = [][]string
type KlineStreamData = [][]string
type DepthStreamData struct {
Asks [][]string `json:"asks"`
Bids [][]string `json:"bids"`
Timestamp float64 `json:"timestamp"`
}
// ContractDepth response depth
type ContractDepth struct {
Asks []interface{} `json:"asks"`
@@ -83,7 +112,7 @@ type HoldData struct {
BuyPriceAvg float64 `json:"buy_price_avg"`
BuyPriceCost float64 `json:"buy_price_cost"`
BuyProfitReal float64 `json:"buy_profit_real"`
ContractID int `json:"contract_id"`
ContractID float64 `json:"contract_id"`
ContractType string `json:"contract_type"`
CreateDate int `json:"create_date"`
LeverRate float64 `json:"lever_rate"`
@@ -115,7 +144,7 @@ type SpotPrice struct {
Date string `json:"date"`
Ticker struct {
Buy float64 `json:"buy,string"`
ContractID int `json:"contract_id"`
ContractID float64 `json:"contract_id"`
High float64 `json:"high,string"`
Low float64 `json:"low,string"`
Last float64 `json:"last,string"`

View File

@@ -0,0 +1,150 @@
package okex
import (
"fmt"
"log"
"net/http"
"strings"
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-/gocryptotrader/common"
)
const (
okexDefaultWebsocketURL = "wss://real.okex.com:10440/websocket/okexapi"
)
func (o *OKEX) writeToWebsocket(message string) error {
o.mu.Lock()
defer o.mu.Unlock()
return o.WebsocketConn.WriteMessage(websocket.TextMessage, []byte(message))
}
func (o *OKEX) websocketConnect() {
var Dialer websocket.Dialer
var err error
myEnabledSubscriptionChannels := []string{}
for _, pair := range o.EnabledPairs {
myEnabledSubscriptionChannels = append(myEnabledSubscriptionChannels, fmt.Sprintf("{'event':'addChannel','channel':'ok_sub_spot_%s_ticker'}", pair))
myEnabledSubscriptionChannels = append(myEnabledSubscriptionChannels, fmt.Sprintf("{'event':'addChannel','channel':'ok_sub_spot_%s_depth'}", pair))
myEnabledSubscriptionChannels = append(myEnabledSubscriptionChannels, fmt.Sprintf("{'event':'addChannel','channel':'ok_sub_spot_%s_deals'}", pair))
myEnabledSubscriptionChannels = append(myEnabledSubscriptionChannels, fmt.Sprintf("{'event':'addChannel','channel':'ok_sub_spot_%s_kline_1min'}", pair))
}
mySubscriptionString := "[" + strings.Join(myEnabledSubscriptionChannels, ",") + "]"
o.WebsocketConn, _, err = Dialer.Dial(okexDefaultWebsocketURL, http.Header{})
if err != nil {
log.Printf("%s Unable to connect to Websocket. Error: %s\n", o.Name, err)
return
}
if o.Verbose {
log.Printf("%s Connected to Websocket.\n", o.Name)
log.Printf("Subscription String is %s\n", mySubscriptionString)
}
log.Printf("Subscription String is %s\n", mySubscriptionString)
// subscribe to all the desired subscriptions
err = o.writeToWebsocket(mySubscriptionString)
if err != nil {
log.Printf("Error: Could not subscribe to the OKEX websocket %s", err)
return
}
}
// WebsocketClient the main function handling the OKEX websocket
// Documentation URL: https://github.com/okcoin-okex/API-docs-OKEx.com/blob/master/API-For-Spot-EN/WEBSOCKET%20API%20for%20SPOT.md
func (o *OKEX) WebsocketClient() {
for o.Enabled && o.Websocket {
o.websocketConnect()
go func() {
for {
time.Sleep(time.Second * 27)
o.writeToWebsocket("{'event':'ping'}")
log.Printf("%s sent Ping message\n", o.GetName())
}
}()
for o.Enabled && o.Websocket {
msgType, resp, err := o.WebsocketConn.ReadMessage()
if err != nil {
log.Printf("Error: Could not read from the OKEX websocket %s", err)
o.websocketConnect()
continue
}
switch msgType {
case websocket.TextMessage:
multiStreamDataArr := []MultiStreamData{}
err = common.JSONDecode(resp, &multiStreamDataArr)
if err != nil {
if strings.Contains(string(resp), "pong") {
log.Printf("%s received Pong message\n", o.GetName())
} else {
log.Printf("%s some other error happened: %s", o.GetName(), err)
continue
}
}
for _, multiStreamData := range multiStreamDataArr {
if strings.Contains(multiStreamData.Channel, "ticker") {
// ticker data
ticker := TickerStreamData{}
tickerDecodeError := common.JSONDecode(multiStreamData.Data, &ticker)
if tickerDecodeError != nil {
log.Printf("OKEX Ticker Decode Error: %s", tickerDecodeError)
continue
}
log.Printf("OKEX Channel: %s\tData: %s\n", multiStreamData.Channel, multiStreamData.Data)
} else if strings.Contains(multiStreamData.Channel, "deals") {
// orderbook data
deals := DealsStreamData{}
decodeError := common.JSONDecode(multiStreamData.Data, &deals)
if decodeError != nil {
log.Printf("OKEX Deals Decode Error: %s", decodeError)
continue
}
log.Printf("OKEX Channel: %s\tData: %s\n", multiStreamData.Channel, multiStreamData.Data)
} else if strings.Contains(multiStreamData.Channel, "kline") {
// 1 min kline data
klines := KlineStreamData{}
decodeError := common.JSONDecode(multiStreamData.Data, &klines)
if decodeError != nil {
log.Printf("OKEX Klines Decode Error: %s", decodeError)
continue
}
log.Printf("OKEX Channel: %s\tData: %s\n", multiStreamData.Channel, multiStreamData.Data)
} else if strings.Contains(multiStreamData.Channel, "depth") {
// market depth data
depth := DepthStreamData{}
decodeError := common.JSONDecode(multiStreamData.Data, &depth)
if decodeError != nil {
log.Printf("OKEX Depth Decode Error: %s", decodeError)
continue
}
log.Printf("OKEX Channel: %s\tData: %s\n", multiStreamData.Channel, multiStreamData.Data)
}
}
}
}
}
}

View File

@@ -7,7 +7,7 @@ import (
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
@@ -28,6 +28,10 @@ func (o *OKEX) Run() {
log.Printf("%s polling delay: %ds.\n", o.GetName(), o.RESTPollingDelay)
log.Printf("%s %d currencies enabled: %s.\n", o.GetName(), len(o.EnabledPairs), o.EnabledPairs)
}
if o.Websocket {
go o.WebsocketClient()
}
}
// UpdateTicker updates and returns the ticker for a currency pair

View File

@@ -1048,32 +1048,31 @@
]
},
{
"name": "OKEX",
"enabled": true,
"verbose": false,
"websocket": false,
"useSandbox": false,
"restPollingDelay": 10,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"authenticatedApiSupport": false,
"apiKey": "Key",
"apiSecret": "Secret",
"availablePairs": "ltc_btc,eth_btc,etc_btc,bch_btc,btc_usdt,eth_usdt,ltc_usdt,etc_usdt,bch_usdt,etc_eth,bt1_btc,bt2_btc,btg_btc,qtum_btc,hsr_btc,neo_btc,gas_btc,qtum_usdt,hsr_usdt,neo_usdt,gas_usdt,btc_usd,ltc_usd,eth_usd,etc_usd,bch_usd",
"enabledPairs": "btc_usd,ltc_usd",
"baseCurrencies": "USD",
"assetTypes": "SPOT,this_week,next_week,quarter",
"supportsAutoPairUpdates": false,
"pairsLastUpdated": 1522112372,
"configCurrencyPairFormat": {
"uppercase": false,
"delimiter": "_"
"Name": "OKEX",
"Enabled": true,
"Verbose": false,
"Websocket": false,
"UseSandbox": false,
"RESTPollingDelay": 10,
"HTTPTimeout": 15000000000,
"AuthenticatedAPISupport": false,
"APIKey": "Key",
"APISecret": "Secret",
"AvailablePairs": "ltc_btc,eth_btc,etc_btc,bch_btc,btc_usdt,eth_usdt,ltc_usdt,etc_usdt,bch_usdt,etc_eth,bt1_btc,bt2_btc,btg_btc,qtum_btc,hsr_btc,neo_btc,gas_btc,qtum_usdt,hsr_usdt,neo_usdt,gas_usdt,btc_usdt,ltc_usdt,eth_usdt,etc_usdt,bch_usdt",
"EnabledPairs": "btc_usdt,ltc_usdt",
"BaseCurrencies": "USD",
"AssetTypes": "SPOT,this_week,next_week,quarter",
"SupportsAutoPairUpdates": false,
"PairsLastUpdated": 1522112372,
"ConfigCurrencyPairFormat": {
"Uppercase": false,
"Delimiter": "_"
},
"requestCurrencyPairFormat": {
"uppercase": false,
"delimiter": "_"
"RequestCurrencyPairFormat": {
"Uppercase": false,
"Delimiter": "_"
},
"bankAccounts": [
"BankAccounts": [
{
"bankName": "",
"bankAddress": "",