From d200232c13c0efb9111c27e7161e5b90458da820 Mon Sep 17 00:00:00 2001 From: "Alan.sung" Date: Mon, 2 Oct 2023 12:55:30 +0800 Subject: [PATCH 1/5] add supported interval for okex --- pkg/exchange/okex/convert.go | 22 ++++++-- pkg/exchange/okex/exchange.go | 14 ++++- pkg/exchange/okex/query_kline_test.go | 78 +++++++++++++++++++++++++++ pkg/exchange/okex/types.go | 40 ++++++++++++++ 4 files changed, 148 insertions(+), 6 deletions(-) create mode 100644 pkg/exchange/okex/query_kline_test.go create mode 100644 pkg/exchange/okex/types.go diff --git a/pkg/exchange/okex/convert.go b/pkg/exchange/okex/convert.go index bc05ce744..afe8d545a 100644 --- a/pkg/exchange/okex/convert.go +++ b/pkg/exchange/okex/convert.go @@ -206,11 +206,23 @@ func toGlobalOrderType(orderType okexapi.OrderType) (types.OrderType, error) { return "", fmt.Errorf("unknown or unsupported okex order type: %s", orderType) } -func toLocalInterval(src string) string { - var re = regexp.MustCompile(`\d+[hdw]`) - return re.ReplaceAllStringFunc(src, func(w string) string { - return strings.ToUpper(w) - }) +func toLocalInterval(interval types.Interval) (string, error) { + if _, ok := SupportedIntervals[interval]; !ok { + return "", fmt.Errorf("interval %s is not supported", interval) + } + + switch i := interval.String(); { + case strings.HasSuffix(i, "m"): + return i, nil + case strings.HasSuffix(i, "mo"): + return "1M", nil + default: + hdwRegex := regexp.MustCompile("\\d+[hdw]$") + if hdwRegex.Match([]byte(i)) { + return strings.ToUpper(i), nil + } + } + return "", fmt.Errorf("interval %s is not supported", interval) } func toGlobalSide(side okexapi.SideType) (s types.SideType) { diff --git a/pkg/exchange/okex/exchange.go b/pkg/exchange/okex/exchange.go index 6122545d5..a63162908 100644 --- a/pkg/exchange/okex/exchange.go +++ b/pkg/exchange/okex/exchange.go @@ -316,7 +316,10 @@ func (e *Exchange) QueryKLines(ctx context.Context, symbol string, interval type return nil, err } - intervalParam := toLocalInterval(interval.String()) + intervalParam, err := toLocalInterval(interval) + if err != nil { + return nil, fmt.Errorf("fail to get interval: %w", err) + } req := e.client.NewCandlesticksRequest(toLocalSymbol(symbol)) req.Bar(intervalParam) @@ -541,3 +544,12 @@ func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *type } return trades, nil } + +func (e *Exchange) SupportedInterval() map[types.Interval]int { + return SupportedIntervals +} + +func (e *Exchange) IsSupportedInterval(interval types.Interval) bool { + _, ok := SupportedIntervals[interval] + return ok +} diff --git a/pkg/exchange/okex/query_kline_test.go b/pkg/exchange/okex/query_kline_test.go new file mode 100644 index 000000000..0a06c07ab --- /dev/null +++ b/pkg/exchange/okex/query_kline_test.go @@ -0,0 +1,78 @@ +package okex + +import ( + "context" + "testing" + "time" + + "github.com/c9s/bbgo/pkg/testutil" + "github.com/c9s/bbgo/pkg/types" + "github.com/stretchr/testify/assert" +) + +func Test_QueryKlines(t *testing.T) { + key, secret, passphrase, ok := testutil.IntegrationTestWithPassphraseConfigured(t, "OKEX") + if !ok { + t.Skip("Please configure all credentials about OKEX") + } + + e := New(key, secret, passphrase) + + queryOrder := types.OrderQuery{ + Symbol: "BTC-USDT", + } + + now := time.Now() + // test supported interval - minute + klineDetail, err := e.QueryKLines(context.Background(), queryOrder.Symbol, types.Interval1m, types.KLineQueryOptions{ + Limit: 50, + EndTime: &now}) + if assert.NoError(t, err) { + assert.NotEmpty(t, klineDetail) + } + // test supported interval - hour + klineDetail, err = e.QueryKLines(context.Background(), queryOrder.Symbol, types.Interval1h, types.KLineQueryOptions{ + Limit: 50, + EndTime: &now}) + if assert.NoError(t, err) { + assert.NotEmpty(t, klineDetail) + } + // test supported interval - day + klineDetail, err = e.QueryKLines(context.Background(), queryOrder.Symbol, types.Interval1d, types.KLineQueryOptions{ + Limit: 50, + EndTime: &now}) + if assert.NoError(t, err) { + assert.NotEmpty(t, klineDetail) + assert.NotEmpty(t, klineDetail[0].Exchange) + assert.NotEmpty(t, klineDetail[0].Symbol) + assert.NotEmpty(t, klineDetail[0].StartTime) + assert.NotEmpty(t, klineDetail[0].EndTime) + assert.NotEmpty(t, klineDetail[0].Interval) + assert.NotEmpty(t, klineDetail[0].Open) + assert.NotEmpty(t, klineDetail[0].Close) + assert.NotEmpty(t, klineDetail[0].High) + assert.NotEmpty(t, klineDetail[0].Low) + assert.NotEmpty(t, klineDetail[0].Volume) + } + // test supported interval - week + klineDetail, err = e.QueryKLines(context.Background(), queryOrder.Symbol, types.Interval1w, types.KLineQueryOptions{ + Limit: 50, + EndTime: &now}) + if assert.NoError(t, err) { + assert.NotEmpty(t, klineDetail) + } + // test supported interval - month + klineDetail, err = e.QueryKLines(context.Background(), queryOrder.Symbol, types.Interval1mo, types.KLineQueryOptions{ + Limit: 50, + EndTime: &now}) + if assert.NoError(t, err) { + assert.NotEmpty(t, klineDetail) + } + // test not supported interval + klineDetail, err = e.QueryKLines(context.Background(), queryOrder.Symbol, types.Interval("2m"), types.KLineQueryOptions{ + Limit: 50, + EndTime: &now}) + if assert.Error(t, err) { + assert.Empty(t, klineDetail) + } +} diff --git a/pkg/exchange/okex/types.go b/pkg/exchange/okex/types.go new file mode 100644 index 000000000..f09b78eb3 --- /dev/null +++ b/pkg/exchange/okex/types.go @@ -0,0 +1,40 @@ +package okex + +import "github.com/c9s/bbgo/pkg/types" + +var ( + // below are supported UTC timezone interval for okex + SupportedIntervals = map[types.Interval]int{ + types.Interval1m: 1 * 60, + types.Interval3m: 3 * 60, + types.Interval5m: 5 * 60, + types.Interval15m: 15 * 60, + types.Interval30m: 30 * 60, + types.Interval1h: 60 * 60, + types.Interval2h: 60 * 60 * 2, + types.Interval4h: 60 * 60 * 4, + types.Interval6h: 60 * 60 * 6, + types.Interval12h: 60 * 60 * 12, + types.Interval1d: 60 * 60 * 24, + types.Interval3d: 60 * 60 * 24 * 3, + types.Interval1w: 60 * 60 * 24 * 7, + types.Interval1mo: 60 * 60 * 24 * 30, + } + + ToGlobalInterval = map[string]types.Interval{ + "1m": types.Interval1m, + "3m": types.Interval3m, + "5m": types.Interval5m, + "15m": types.Interval15m, + "30m": types.Interval30m, + "1H": types.Interval1h, + "2H": types.Interval2h, + "4H": types.Interval4h, + "6H": types.Interval6h, + "12H": types.Interval12h, + "1D": types.Interval1d, + "3D": types.Interval3d, + "1W": types.Interval1w, + "1M": types.Interval1mo, + } +) From a83335817e43b718cd5414f4989a850dcb5de509 Mon Sep 17 00:00:00 2001 From: "Alan.sung" Date: Tue, 3 Oct 2023 16:59:46 +0800 Subject: [PATCH 2/5] use interval [1m/3m/5m/15m/30m/1H/2H/4H] and [/6Hutc/12Hutc/1Dutc/2Dutc/3Dutc/1Wutc/1Mutc] and add unit test --- pkg/exchange/okex/convert.go | 19 +++++++++++++++--- pkg/exchange/okex/query_kline_test.go | 9 ++++++++- pkg/exchange/okex/types.go | 28 +++++++++++++-------------- 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/pkg/exchange/okex/convert.go b/pkg/exchange/okex/convert.go index afe8d545a..16e751bd9 100644 --- a/pkg/exchange/okex/convert.go +++ b/pkg/exchange/okex/convert.go @@ -215,11 +215,24 @@ func toLocalInterval(interval types.Interval) (string, error) { case strings.HasSuffix(i, "m"): return i, nil case strings.HasSuffix(i, "mo"): - return "1M", nil + return "1Mutc", nil default: - hdwRegex := regexp.MustCompile("\\d+[hdw]$") + hdwRegex := regexp.MustCompile("\\d+[dw]$") if hdwRegex.Match([]byte(i)) { - return strings.ToUpper(i), nil + return strings.ToUpper(i) + "utc", nil + } + hdwRegex = regexp.MustCompile("(\\d+)[h]$") + if fs := hdwRegex.FindStringSubmatch(i); len(fs) > 0 { + digits, err := strconv.ParseInt(string(fs[1]), 10, 64) + if err != nil { + return "", fmt.Errorf("interval %s is not supported", interval) + } + if digits >= 6 { + return strings.ToUpper(i) + "utc", nil + } else { + return strings.ToUpper(i), nil + } + } } return "", fmt.Errorf("interval %s is not supported", interval) diff --git a/pkg/exchange/okex/query_kline_test.go b/pkg/exchange/okex/query_kline_test.go index 0a06c07ab..c3e703b45 100644 --- a/pkg/exchange/okex/query_kline_test.go +++ b/pkg/exchange/okex/query_kline_test.go @@ -30,13 +30,20 @@ func Test_QueryKlines(t *testing.T) { if assert.NoError(t, err) { assert.NotEmpty(t, klineDetail) } - // test supported interval - hour + // test supported interval - hour - 1 hour klineDetail, err = e.QueryKLines(context.Background(), queryOrder.Symbol, types.Interval1h, types.KLineQueryOptions{ Limit: 50, EndTime: &now}) if assert.NoError(t, err) { assert.NotEmpty(t, klineDetail) } + // test supported interval - hour - 6 hour to test UTC time + klineDetail, err = e.QueryKLines(context.Background(), queryOrder.Symbol, types.Interval6h, types.KLineQueryOptions{ + Limit: 50, + EndTime: &now}) + if assert.NoError(t, err) { + assert.NotEmpty(t, klineDetail) + } // test supported interval - day klineDetail, err = e.QueryKLines(context.Background(), queryOrder.Symbol, types.Interval1d, types.KLineQueryOptions{ Limit: 50, diff --git a/pkg/exchange/okex/types.go b/pkg/exchange/okex/types.go index f09b78eb3..d4d0f0029 100644 --- a/pkg/exchange/okex/types.go +++ b/pkg/exchange/okex/types.go @@ -22,19 +22,19 @@ var ( } ToGlobalInterval = map[string]types.Interval{ - "1m": types.Interval1m, - "3m": types.Interval3m, - "5m": types.Interval5m, - "15m": types.Interval15m, - "30m": types.Interval30m, - "1H": types.Interval1h, - "2H": types.Interval2h, - "4H": types.Interval4h, - "6H": types.Interval6h, - "12H": types.Interval12h, - "1D": types.Interval1d, - "3D": types.Interval3d, - "1W": types.Interval1w, - "1M": types.Interval1mo, + "1m": types.Interval1m, + "3m": types.Interval3m, + "5m": types.Interval5m, + "15m": types.Interval15m, + "30m": types.Interval30m, + "1H": types.Interval1h, + "2H": types.Interval2h, + "4H": types.Interval4h, + "6Hutc": types.Interval6h, + "12Hutc": types.Interval12h, + "1Dutc": types.Interval1d, + "3Dutc": types.Interval3d, + "1Wutc": types.Interval1w, + "1Mutc": types.Interval1mo, } ) From 0b5ce231ffb129b87d38a9c6264eca581b8ad945 Mon Sep 17 00:00:00 2001 From: "Alan.sung" Date: Wed, 4 Oct 2023 12:36:17 +0800 Subject: [PATCH 3/5] fix lint and rename i with in --- pkg/exchange/okex/convert.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pkg/exchange/okex/convert.go b/pkg/exchange/okex/convert.go index 16e751bd9..1d1713a84 100644 --- a/pkg/exchange/okex/convert.go +++ b/pkg/exchange/okex/convert.go @@ -211,26 +211,26 @@ func toLocalInterval(interval types.Interval) (string, error) { return "", fmt.Errorf("interval %s is not supported", interval) } - switch i := interval.String(); { - case strings.HasSuffix(i, "m"): - return i, nil - case strings.HasSuffix(i, "mo"): + switch in := interval.String(); { + case strings.HasSuffix(in, "m"): + return in, nil + case strings.HasSuffix(in, "mo"): return "1Mutc", nil default: - hdwRegex := regexp.MustCompile("\\d+[dw]$") - if hdwRegex.Match([]byte(i)) { - return strings.ToUpper(i) + "utc", nil + hdwRegex := regexp.MustCompile(`\d+[dw]$`) + if hdwRegex.Match([]byte(in)) { + return strings.ToUpper(in) + "utc", nil } - hdwRegex = regexp.MustCompile("(\\d+)[h]$") - if fs := hdwRegex.FindStringSubmatch(i); len(fs) > 0 { + hdwRegex = regexp.MustCompile(`(\d+)[h]$`) + if fs := hdwRegex.FindStringSubmatch(in); len(fs) > 0 { digits, err := strconv.ParseInt(string(fs[1]), 10, 64) if err != nil { return "", fmt.Errorf("interval %s is not supported", interval) } if digits >= 6 { - return strings.ToUpper(i) + "utc", nil + return strings.ToUpper(in) + "utc", nil } else { - return strings.ToUpper(i), nil + return strings.ToUpper(in), nil } } From 3b793b79b69225721b0475e9d748d27b8e3046d9 Mon Sep 17 00:00:00 2001 From: "Alan.sung" Date: Wed, 4 Oct 2023 14:23:13 +0800 Subject: [PATCH 4/5] turn ToGlobalInterval to ToLocalInterval, use Map to turn to local interval --- pkg/exchange/okex/convert.go | 25 ++----------------------- pkg/exchange/okex/types.go | 30 +++++++++++++++--------------- 2 files changed, 17 insertions(+), 38 deletions(-) diff --git a/pkg/exchange/okex/convert.go b/pkg/exchange/okex/convert.go index 1d1713a84..36f599c21 100644 --- a/pkg/exchange/okex/convert.go +++ b/pkg/exchange/okex/convert.go @@ -2,7 +2,6 @@ package okex import ( "fmt" - "regexp" "strconv" "strings" @@ -211,30 +210,10 @@ func toLocalInterval(interval types.Interval) (string, error) { return "", fmt.Errorf("interval %s is not supported", interval) } - switch in := interval.String(); { - case strings.HasSuffix(in, "m"): + if in, ok := ToLocalInterval[interval]; ok { return in, nil - case strings.HasSuffix(in, "mo"): - return "1Mutc", nil - default: - hdwRegex := regexp.MustCompile(`\d+[dw]$`) - if hdwRegex.Match([]byte(in)) { - return strings.ToUpper(in) + "utc", nil - } - hdwRegex = regexp.MustCompile(`(\d+)[h]$`) - if fs := hdwRegex.FindStringSubmatch(in); len(fs) > 0 { - digits, err := strconv.ParseInt(string(fs[1]), 10, 64) - if err != nil { - return "", fmt.Errorf("interval %s is not supported", interval) - } - if digits >= 6 { - return strings.ToUpper(in) + "utc", nil - } else { - return strings.ToUpper(in), nil - } - - } } + return "", fmt.Errorf("interval %s is not supported", interval) } diff --git a/pkg/exchange/okex/types.go b/pkg/exchange/okex/types.go index d4d0f0029..0217c1ee8 100644 --- a/pkg/exchange/okex/types.go +++ b/pkg/exchange/okex/types.go @@ -21,20 +21,20 @@ var ( types.Interval1mo: 60 * 60 * 24 * 30, } - ToGlobalInterval = map[string]types.Interval{ - "1m": types.Interval1m, - "3m": types.Interval3m, - "5m": types.Interval5m, - "15m": types.Interval15m, - "30m": types.Interval30m, - "1H": types.Interval1h, - "2H": types.Interval2h, - "4H": types.Interval4h, - "6Hutc": types.Interval6h, - "12Hutc": types.Interval12h, - "1Dutc": types.Interval1d, - "3Dutc": types.Interval3d, - "1Wutc": types.Interval1w, - "1Mutc": types.Interval1mo, + ToLocalInterval = map[types.Interval]string{ + types.Interval1m: "1m", + types.Interval3m: "3m", + types.Interval5m: "5m", + types.Interval15m: "15m", + types.Interval30m: "30m", + types.Interval1h: "1H", + types.Interval2h: "2H", + types.Interval4h: "4H", + types.Interval6h: "6Hutc", + types.Interval12h: "12Hutc", + types.Interval1d: "1Dutc", + types.Interval3d: "3Dutc", + types.Interval1w: "1Wutc", + types.Interval1mo: "1Mutc", } ) From 2309bbdee82c9df553b2728de35d09a012339e9a Mon Sep 17 00:00:00 2001 From: "Alan.sung" Date: Wed, 4 Oct 2023 16:24:32 +0800 Subject: [PATCH 5/5] print local interval in error message --- pkg/exchange/okex/convert.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/exchange/okex/convert.go b/pkg/exchange/okex/convert.go index 36f599c21..742ad56f3 100644 --- a/pkg/exchange/okex/convert.go +++ b/pkg/exchange/okex/convert.go @@ -210,11 +210,12 @@ func toLocalInterval(interval types.Interval) (string, error) { return "", fmt.Errorf("interval %s is not supported", interval) } - if in, ok := ToLocalInterval[interval]; ok { - return in, nil + in, ok := ToLocalInterval[interval] + if !ok { + return "", fmt.Errorf("interval %s is not supported, got local interval %s", interval, in) } - return "", fmt.Errorf("interval %s is not supported", interval) + return in, nil } func toGlobalSide(side okexapi.SideType) (s types.SideType) {