Skip to content

Commit 1977bcb

Browse files
committed
OKex changed their api
1 parent 097b221 commit 1977bcb

File tree

6 files changed

+110
-187
lines changed

6 files changed

+110
-187
lines changed

exchange/kraken.go

Lines changed: 27 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import (
99
"time"
1010

1111
"github.com/polyrabbit/my-token/exchange/model"
12-
13-
"github.com/buger/jsonparser"
1412
"github.com/polyrabbit/my-token/http"
13+
1514
"github.com/sirupsen/logrus"
15+
"github.com/tidwall/gjson"
1616
)
1717

1818
// https://www.kraken.com/help/api
@@ -27,20 +27,16 @@ func (client *krakenClient) GetName() string {
2727
return "Kraken"
2828
}
2929

30-
/**
31-
Read response and check any potential errors
32-
*/
33-
func (client *krakenClient) readResponse(respBytes []byte) ([]byte, error) {
34-
var errorMsg []string
35-
jsonparser.ArrayEach(respBytes, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
36-
if dataType == jsonparser.String {
37-
errorMsg = append(errorMsg, string(value))
30+
// Check to see if we have error in the response
31+
func (client *krakenClient) extractError(respByte []byte) error {
32+
errorArray := gjson.GetBytes(respByte, "error").Array()
33+
if len(errorArray) > 0 {
34+
errMsg := errorArray[0].Get("0").String()
35+
if len(errMsg) != 0 {
36+
return errors.New(errMsg)
3837
}
39-
}, "error")
40-
if len(errorMsg) != 0 {
41-
return nil, errors.New(strings.Join(errorMsg, ", "))
4238
}
43-
return respBytes, nil
39+
return nil
4440
}
4541

4642
func (client *krakenClient) GetKlinePrice(symbol string, since time.Time, interval int) (float64, error) {
@@ -50,55 +46,40 @@ func (client *krakenClient) GetKlinePrice(symbol string, since time.Time, interv
5046
"since": strconv.FormatInt(since.Unix(), 10),
5147
"interval": strconv.Itoa(interval),
5248
})
53-
if err != nil {
54-
return 0, err
55-
}
56-
57-
content, err := client.readResponse(respByte)
58-
if err != nil {
59-
return 0, err
49+
if err := client.extractError(respByte); err != nil {
50+
return 0, fmt.Errorf("kraken get kline: %w", err)
6051
}
61-
// jsonparser saved my life, no need to struggle with different/weird response types
62-
klineBytes, dataType, _, err := jsonparser.Get(content, "result", symbolUpperCase, "[0]")
6352
if err != nil {
6453
return 0, err
6554
}
66-
if dataType != jsonparser.Array {
67-
return 0, fmt.Errorf("kline should be an array, getting %s", dataType)
68-
}
6955

70-
timestamp, err := jsonparser.GetInt(klineBytes, "[0]")
71-
if err != nil {
72-
return 0, err
73-
}
74-
openPrice, err := jsonparser.GetString(klineBytes, "[1]")
75-
if err != nil {
76-
return 0, err
56+
// gjson saved my life, no need to struggle with different/weird response types
57+
candleV := gjson.GetBytes(respByte, fmt.Sprintf("result.%s.0", strings.ToUpper(symbol))).Array()
58+
if len(candleV) != 8 {
59+
return 0, fmt.Errorf("kraken malformed kline response, expecting 8 elements, got %d", len(candleV))
7760
}
61+
62+
timestamp := candleV[0].Int()
63+
openPrice := candleV[1].Float()
7864
logrus.Debugf("%s - Kline for %s uses open price at %s", client.GetName(), since.Local(),
7965
time.Unix(timestamp, 0).Local())
80-
return strconv.ParseFloat(openPrice, 64)
66+
return openPrice, nil
8167
}
8268

8369
func (client *krakenClient) GetSymbolPrice(symbol string) (*model.SymbolPrice, error) {
8470
respByte, err := http.Get(krakenBaseApi+"Ticker", map[string]string{"pair": strings.ToUpper(symbol)})
85-
if err != nil {
86-
return nil, err
71+
if err := client.extractError(respByte); err != nil {
72+
return nil, fmt.Errorf("kraken get ticker: %w", err)
8773
}
88-
89-
content, err := client.readResponse(respByte)
9074
if err != nil {
9175
return nil, err
9276
}
9377

94-
lastPriceString, err := jsonparser.GetString(content, "result", strings.ToUpper(symbol), "c", "[0]")
95-
if err != nil {
96-
return nil, err
97-
}
98-
lastPrice, err := strconv.ParseFloat(lastPriceString, 64)
99-
if err != nil {
100-
return nil, err
78+
lastPriceV := gjson.GetBytes(respByte, fmt.Sprintf("result.%s.c.0", strings.ToUpper(symbol)))
79+
if !lastPriceV.Exists() {
80+
return nil, fmt.Errorf("kraken malformed ticker response, missing key %s", fmt.Sprintf("result.%s.c.0", strings.ToUpper(symbol)))
10181
}
82+
lastPrice := lastPriceV.Float()
10283

10384
time.Sleep(time.Second) // API call rate limit
10485
var (
@@ -121,7 +102,7 @@ func (client *krakenClient) GetSymbolPrice(symbol string) (*model.SymbolPrice, e
121102

122103
return &model.SymbolPrice{
123104
Symbol: symbol,
124-
Price: lastPriceString,
105+
Price: lastPriceV.String(),
125106
UpdateAt: time.Now(),
126107
Source: client.GetName(),
127108
PercentChange1h: percentChange1h,

exchange/okex.go

Lines changed: 61 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,148 +1,120 @@
11
package exchange
22

33
import (
4-
"encoding/json"
4+
"errors"
55
"fmt"
66
"math"
77
"strconv"
8-
"strings"
98
"time"
109

1110
"github.com/polyrabbit/my-token/exchange/model"
12-
1311
"github.com/polyrabbit/my-token/http"
1412
"github.com/sirupsen/logrus"
13+
"github.com/tidwall/gjson"
1514
)
1615

17-
// https://github.com/okcoin-okex/API-docs-OKEx.com
18-
const okexBaseApi = "https://www.okex.com/api/v1"
16+
// https://www.okex.com/docs/zh/#spot-some
17+
const okexBaseApi = "https://www.okex.com/api/spot/v3/instruments/"
1918

2019
type okexClient struct {
2120
AccessKey string
2221
SecretKey string
2322
}
2423

25-
type okexErrorResponse struct {
26-
ErrorCode int `json:"error_code"`
27-
}
28-
29-
type okexTickerResponse struct {
30-
okexErrorResponse
31-
Date int64 `json:",string"`
32-
Ticker struct {
33-
Last float64 `json:",string"`
34-
}
35-
}
36-
37-
type okexKlineResponse struct {
38-
okexErrorResponse
39-
Data [][]interface{}
40-
}
41-
42-
func (resp *okexTickerResponse) getCommonResponse() okexErrorResponse {
43-
return resp.okexErrorResponse
44-
}
45-
46-
func (resp *okexTickerResponse) getInternalData() interface{} {
47-
return resp
48-
}
49-
50-
func (resp *okexKlineResponse) getCommonResponse() okexErrorResponse {
51-
return resp.okexErrorResponse
52-
}
53-
54-
func (resp *okexKlineResponse) getInternalData() interface{} {
55-
return &resp.Data
56-
}
57-
58-
// Any way to hold the common response, instead of adding an interface here?
59-
type okexCommonResponseProvider interface {
60-
getCommonResponse() okexErrorResponse
61-
getInternalData() interface{}
62-
}
63-
6424
func (client *okexClient) GetName() string {
6525
return "OKEx"
6626
}
6727

68-
func (client *okexClient) decodeResponse(respBytes []byte, respJSON okexCommonResponseProvider) error {
69-
// What a messy
70-
respBody := strings.TrimSpace(string(respBytes))
71-
if respBody[0] == '[' {
72-
return json.Unmarshal(respBytes, respJSON.getInternalData())
28+
func (client *okexClient) GetKlinePrice(symbol, granularity string, start, end time.Time) (float64, error) {
29+
respByte, err := http.Get(okexBaseApi+symbol+"/candles", map[string]string{
30+
"granularity": granularity,
31+
"start": start.UTC().Format(time.RFC3339),
32+
"end": end.UTC().Format(time.RFC3339),
33+
})
34+
if err := client.extractError(respByte); err != nil {
35+
return 0, fmt.Errorf("okex get candles: %w", err)
7336
}
74-
75-
if err := json.Unmarshal(respBytes, &respJSON); err != nil {
76-
return err
37+
if err != nil {
38+
return 0, fmt.Errorf("okex get candles: %w", err)
7739
}
7840

79-
// All I need is to get the common part, I don't like this
80-
commonResponse := respJSON.getCommonResponse()
81-
if commonResponse.ErrorCode != 0 {
82-
return fmt.Errorf("error_code: %v", commonResponse.ErrorCode)
41+
klines := gjson.ParseBytes(respByte).Array()
42+
if len(klines) == 0 {
43+
return 0, fmt.Errorf("okex got empty candles response")
8344
}
84-
return nil
85-
}
86-
87-
func (client *okexClient) GetKlinePrice(symbol, period string, size int) (float64, error) {
88-
symbol = strings.ToLower(symbol)
89-
respByte, err := http.Get(okexBaseApi+"/kline.do", map[string]string{
90-
"symbol": symbol,
91-
"type": period,
92-
"size": strconv.Itoa(size),
93-
})
94-
if err != nil {
95-
return 0, err
45+
lastKline := klines[len(klines)-1]
46+
if len(lastKline.Array()) != 6 {
47+
return 0, fmt.Errorf(`okex malformed kline response, got size %d`, len(lastKline.Array()))
9648
}
97-
98-
var respJSON okexKlineResponse
99-
err = client.decodeResponse(respByte, &respJSON)
100-
if err != nil {
101-
return 0, err
49+
updated := time.Now()
50+
if parsed, err := time.Parse(time.RFC3339, lastKline.Get("0").String()); err == nil {
51+
updated = parsed
10252
}
103-
logrus.Debugf("%s - Kline for %s*%v uses price at %s", client.GetName(), period, size,
104-
time.Unix(int64(respJSON.Data[0][0].(float64))/1000, 0))
105-
return strconv.ParseFloat(respJSON.Data[0][1].(string), 64)
53+
logrus.Debugf("%s - Kline for %s seconds uses price at %s",
54+
client.GetName(), granularity, updated.Local())
55+
return lastKline.Get("1").Float(), nil
10656
}
10757

10858
func (client *okexClient) GetSymbolPrice(symbol string) (*model.SymbolPrice, error) {
109-
respByte, err := http.Get(okexBaseApi+"/ticker.do", map[string]string{"symbol": strings.ToLower(symbol)})
59+
respByte, err := http.Get(okexBaseApi+symbol+"/ticker", nil)
60+
if err := client.extractError(respByte); err != nil {
61+
// Extract more readable first if have
62+
return nil, fmt.Errorf("okex get symbol price: %w", err)
63+
}
11064
if err != nil {
111-
return nil, err
65+
return nil, fmt.Errorf("okex get symbol price: %w", err)
11266
}
113-
114-
var respJSON okexTickerResponse
115-
err = client.decodeResponse(respByte, &respJSON)
67+
lastV := gjson.GetBytes(respByte, "last")
68+
if !lastV.Exists() {
69+
return nil, fmt.Errorf(`okex malformed get symbol price response, missing "last" key`)
70+
}
71+
lastPrice := lastV.Float()
72+
updateAtV := gjson.GetBytes(respByte, "timestamp")
73+
if !updateAtV.Exists() {
74+
return nil, fmt.Errorf(`okex malformed get symbol price response, missing "timestamp" key`)
75+
}
76+
updateAt, err := time.Parse(time.RFC3339, updateAtV.String())
11677
if err != nil {
117-
return nil, err
78+
return nil, fmt.Errorf("okex parse timestamp: %w", err)
11879
}
11980

12081
var percentChange1h, percentChange24h = math.MaxFloat64, math.MaxFloat64
121-
price1hAgo, err := client.GetKlinePrice(symbol, "1min", 60)
82+
price1hAgo, err := client.GetKlinePrice(symbol, "60", updateAt.Add(-time.Hour), updateAt)
12283
if err != nil {
12384
logrus.Warnf("%s - Failed to get price 1 hour ago, error: %v\n", client.GetName(), err)
12485
} else if price1hAgo != 0 {
125-
percentChange1h = (respJSON.Ticker.Last - price1hAgo) / price1hAgo * 100
86+
percentChange1h = (lastPrice - price1hAgo) / price1hAgo * 100
12687
}
12788

128-
time.Sleep(time.Second) // Limit 1 req/sec for Kline
129-
price24hAgo, err := client.GetKlinePrice(symbol, "3min", 492) // Why not 480?
89+
price24hAgo, err := client.GetKlinePrice(symbol, "900", updateAt.Add(-24*time.Hour), updateAt)
13090
if err != nil {
13191
logrus.Warnf("%s - Failed to get price 24 hours ago, error: %v\n", client.GetName(), err)
13292
} else if price24hAgo != 0 {
133-
percentChange24h = (respJSON.Ticker.Last - price24hAgo) / price24hAgo * 100
93+
percentChange24h = (lastPrice - price24hAgo) / price24hAgo * 100
13494
}
13595

13696
return &model.SymbolPrice{
13797
Symbol: symbol,
138-
Price: strconv.FormatFloat(respJSON.Ticker.Last, 'f', -1, 64),
139-
UpdateAt: time.Unix(respJSON.Date, 0),
98+
Price: strconv.FormatFloat(lastPrice, 'f', -1, 64),
99+
UpdateAt: updateAt,
140100
Source: client.GetName(),
141101
PercentChange1h: percentChange1h,
142102
PercentChange24h: percentChange24h,
143103
}, nil
144104
}
145105

106+
// Check to see if we have error in the response
107+
func (client *okexClient) extractError(respByte []byte) error {
108+
errorMsg := gjson.GetBytes(respByte, "error_message")
109+
if !errorMsg.Exists() {
110+
errorMsg = gjson.GetBytes(respByte, "message")
111+
}
112+
if len(errorMsg.String()) != 0 {
113+
return errors.New(errorMsg.String())
114+
}
115+
return nil
116+
}
117+
146118
func init() {
147119
model.Register(new(okexClient))
148120
}

exchange/okex_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,23 @@ package exchange
22

33
import (
44
"testing"
5+
"time"
56
)
67

78
func TestOKExClient(t *testing.T) {
89

910
var client = new(okexClient)
1011

1112
t.Run("GetKlinePrice", func(t *testing.T) {
12-
_, err := client.GetKlinePrice("bTC_usdt", "1min", 60)
13+
_, err := client.GetKlinePrice("bTC_usdt", "60", time.Now().Add(-time.Hour), time.Now())
1314

1415
if err != nil {
1516
t.Fatalf("Unexpected error: %v", err)
1617
}
1718
})
1819

1920
t.Run("GetKlinePrice of unknown symbol", func(t *testing.T) {
20-
_, err := client.GetKlinePrice("abcedfg", "1min", 60)
21+
_, err := client.GetKlinePrice("abcedfg", "60", time.Now().Add(-time.Hour), time.Now())
2122

2223
if err == nil {
2324
t.Fatalf("Expecting error when fetching unknown price, but get nil")

go.mod

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,15 @@ module github.com/polyrabbit/my-token
33
go 1.12
44

55
require (
6-
github.com/buger/jsonparser v0.0.0-20180318095312-2cac668e8456
76
github.com/fatih/color v0.0.0-20180213133403-507f6050b856
87
github.com/gosuri/uilive v0.0.4
98
github.com/mattn/go-colorable v0.0.0-20180310133214-efa589957cd0
109
github.com/mattn/go-isatty v0.0.12 // indirect
1110
github.com/mattn/go-runewidth v0.0.8 // indirect
12-
github.com/mitchellh/gox v1.0.1 // indirect
1311
github.com/olekukonko/tablewriter v0.0.0-20180506121414-d4647c9c7a84
1412
github.com/preichenberger/go-coinbasepro/v2 v2.0.5
1513
github.com/sirupsen/logrus v1.4.2
1614
github.com/spf13/pflag v1.0.5
1715
github.com/spf13/viper v1.6.2
16+
github.com/tidwall/gjson v1.6.0
1817
)

0 commit comments

Comments
 (0)