kucoin: implement QueryKLines and fix interval conversion

This commit is contained in:
c9s 2021-12-26 02:23:06 +08:00
parent e3181202db
commit 1da0c8e755
4 changed files with 297 additions and 5 deletions

View File

@ -72,6 +72,9 @@ func toLocalInterval(i types.Interval) string {
case types.Interval1m: case types.Interval1m:
return "1min" return "1min"
case types.Interval5m:
return "5min"
case types.Interval15m: case types.Interval15m:
return "15min" return "15min"
@ -87,9 +90,18 @@ func toLocalInterval(i types.Interval) string {
case types.Interval4h: case types.Interval4h:
return "4hour" return "4hour"
case types.Interval6h:
return "6hour"
case types.Interval12h:
return "12hour"
case types.Interval1d:
return "1day"
} }
return "1h" return "1hour"
} }
// convertSubscriptions global subscription to local websocket command // convertSubscriptions global subscription to local websocket command

View File

@ -27,6 +27,7 @@ type Exchange struct {
client *kucoinapi.RestClient client *kucoinapi.RestClient
} }
func New(key, secret, passphrase string) *Exchange { func New(key, secret, passphrase string) *Exchange {
client := kucoinapi.NewClient() client := kucoinapi.NewClient()
@ -125,8 +126,64 @@ func (e *Exchange) QueryTickers(ctx context.Context, symbols ...string) (map[str
return tickers, nil return tickers, nil
} }
var supportedIntervals = map[types.Interval]int{
types.Interval1m: 60,
types.Interval5m: 60 * 5,
types.Interval5m: 60 * 15,
types.Interval30m: 60 * 30,
types.Interval1h: 60 * 60,
types.Interval2h: 60 * 60 * 2,
types.Interval4h: 60 * 60 * 4,
types.Interval6h: 60 * 60 * 6,
// types.Interval8h: 60 * 60 * 8,
types.Interval12h: 60 * 60 * 12,
}
func (e *Exchange) SupportedInterval() map[types.Interval]int {
return supportedIntervals
}
func (e *Exchange) IsSupportedInterval(interval types.Interval) bool {
_, ok := supportedIntervals[interval]
return ok
}
func (e *Exchange) QueryKLines(ctx context.Context, symbol string, interval types.Interval, options types.KLineQueryOptions) ([]types.KLine, error) { func (e *Exchange) QueryKLines(ctx context.Context, symbol string, interval types.Interval, options types.KLineQueryOptions) ([]types.KLine, error) {
panic("implement me") req := e.client.MarketDataService.NewGetKLinesRequest()
req.Symbol(toLocalSymbol(symbol))
req.Interval(toLocalInterval(interval))
if options.StartTime != nil {
req.StartAt(*options.StartTime)
} else if options.EndTime != nil {
req.StartAt(*options.EndTime)
}
ks, err := req.Do(ctx)
if err != nil {
return nil, err
}
var klines []types.KLine
for _, k := range ks {
gi := toGlobalInterval(k.Interval)
klines = append(klines, types.KLine{
Exchange: types.ExchangeKucoin,
Symbol: toGlobalSymbol(k.Symbol),
StartTime: types.Time(k.StartTime),
EndTime: types.Time(k.StartTime.Add(gi.Duration() - time.Millisecond)),
Interval: gi,
Open: k.Open.Float64(),
Close: k.Close.Float64(),
High: k.High.Float64(),
Low: k.Low.Float64(),
Volume: k.Volume.Float64(),
QuoteVolume: k.QuoteVolume.Float64(),
Closed: true,
})
}
return klines, nil
} }
func (e *Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) (createdOrders types.OrderSlice, err error) { func (e *Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) (createdOrders types.OrderSlice, err error) {

View File

@ -0,0 +1,100 @@
// Code generated by "requestgen -type GetKLinesRequest"; DO NOT EDIT.
package kucoinapi
import (
"encoding/json"
"fmt"
"net/url"
"strconv"
"time"
)
func (g *GetKLinesRequest) Symbol(symbol string) *GetKLinesRequest {
g.symbol = symbol
return g
}
func (g *GetKLinesRequest) Interval(interval string) *GetKLinesRequest {
g.interval = interval
return g
}
func (g *GetKLinesRequest) StartAt(startAt time.Time) *GetKLinesRequest {
g.startAt = &startAt
return g
}
func (g *GetKLinesRequest) EndAt(endAt time.Time) *GetKLinesRequest {
g.endAt = &endAt
return g
}
func (g *GetKLinesRequest) GetParameters() (map[string]interface{}, error) {
var params = map[string]interface{}{}
// check symbol field -> json key symbol
symbol := g.symbol
// assign parameter of symbol
params["symbol"] = symbol
// check interval field -> json key interval
interval := g.interval
switch interval {
case "1min", "3min", "5min", "15min", "30min", "1hour", "2hour", "4hour", "6hour", "8hour", "12hour", "1day", "1week":
params["interval"] = interval
default:
return params, fmt.Errorf("interval value %v is invalid", interval)
}
// assign parameter of interval
params["interval"] = interval
// check startAt field -> json key startAt
if g.startAt != nil {
startAt := *g.startAt
// assign parameter of startAt
// convert time.Time to seconds time stamp
params["startAt"] = strconv.FormatInt(startAt.Unix(), 10)
}
// check endAt field -> json key endAt
if g.endAt != nil {
endAt := *g.endAt
// assign parameter of endAt
// convert time.Time to seconds time stamp
params["endAt"] = strconv.FormatInt(endAt.Unix(), 10)
}
return params, nil
}
func (g *GetKLinesRequest) GetParametersQuery() (url.Values, error) {
query := url.Values{}
params, err := g.GetParameters()
if err != nil {
return query, err
}
for k, v := range params {
query.Add(k, fmt.Sprintf("%v", v))
}
return query, nil
}
func (g *GetKLinesRequest) GetParametersJSON() ([]byte, error) {
params, err := g.GetParameters()
if err != nil {
return nil, err
}
return json.Marshal(params)
}

View File

@ -1,20 +1,29 @@
package kucoinapi package kucoinapi
import ( import (
"context"
"encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
"time"
"github.com/pkg/errors"
"github.com/valyala/fastjson"
"github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/types"
"github.com/pkg/errors"
) )
type MarketDataService struct { type MarketDataService struct {
client *RestClient client *RestClient
} }
func (s *MarketDataService) NewGetKLinesRequest() *GetKLinesRequest {
return &GetKLinesRequest{client: s.client}
}
type Symbol struct { type Symbol struct {
Symbol string `json:"symbol"` Symbol string `json:"symbol"`
Name string `json:"name"` Name string `json:"name"`
@ -268,8 +277,6 @@ func (s *MarketDataService) GetOrderBook(symbol string, depth int) (*OrderBook,
return nil, err return nil, err
} }
fmt.Println(string(response.Body))
var apiResponse struct { var apiResponse struct {
Code string `json:"code"` Code string `json:"code"`
Message string `json:"msg"` Message string `json:"msg"`
@ -282,3 +289,119 @@ func (s *MarketDataService) GetOrderBook(symbol string, depth int) (*OrderBook,
return apiResponse.Data, nil return apiResponse.Data, nil
} }
//go:generate requestgen -type GetKLinesRequest
type GetKLinesRequest struct {
client *RestClient
symbol string `param:"symbol"`
interval string `param:"interval" validValues:"1min,3min,5min,15min,30min,1hour,2hour,4hour,6hour,8hour,12hour,1day,1week"`
startAt *time.Time `param:"startAt,seconds"`
endAt *time.Time `param:"endAt,seconds"`
}
type KLine struct {
Symbol string
Interval string
StartTime time.Time
Open fixedpoint.Value
High fixedpoint.Value
Low fixedpoint.Value
Close fixedpoint.Value
Volume, QuoteVolume fixedpoint.Value
}
func (r *GetKLinesRequest) Do(ctx context.Context) ([]KLine, error) {
params, err := r.GetParametersQuery()
if err != nil {
return nil, err
}
req, err := r.client.NewRequest("GET", "/api/v1/market/candles", params, nil)
if err != nil {
return nil, err
}
response, err := r.client.SendRequest(req)
if err != nil {
return nil, err
}
var apiResponse struct {
Code string `json:"code"`
Message string `json:"msg"`
Data json.RawMessage `json:"data"`
}
if err := response.DecodeJSON(&apiResponse); err != nil {
return nil, err
}
if apiResponse.Data == nil {
return nil, errors.New("api error: [" + apiResponse.Code + "] " + apiResponse.Message)
}
return parseKLines(apiResponse.Data, r.symbol, r.interval)
}
func parseKLines(b []byte, symbol, interval string) (klines []KLine, err error) {
s, err := fastjson.ParseBytes(b)
if err != nil {
return klines, err
}
for _, v := range s.GetArray() {
arr := v.GetArray()
ts, err := strconv.ParseInt(string(arr[0].GetStringBytes()), 10, 64)
if err != nil {
return klines, err
}
o, err := fixedpoint.NewFromString(string(arr[1].GetStringBytes()))
if err != nil {
return klines, err
}
c, err := fixedpoint.NewFromString(string(arr[2].GetStringBytes()))
if err != nil {
return klines, err
}
h, err := fixedpoint.NewFromString(string(arr[3].GetStringBytes()))
if err != nil {
return klines, err
}
l, err := fixedpoint.NewFromString(string(arr[4].GetStringBytes()))
if err != nil {
return klines, err
}
vv, err := fixedpoint.NewFromString(string(arr[5].GetStringBytes()))
if err != nil {
return klines, err
}
qv, err := fixedpoint.NewFromString(string(arr[6].GetStringBytes()))
if err != nil {
return klines, err
}
klines = append(klines, KLine{
Symbol: symbol,
Interval: interval,
StartTime: time.Unix(ts, 0),
Open: o,
High: h,
Low: l,
Close: c,
Volume: vv,
QuoteVolume: qv,
})
}
return klines, err
}