mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-22 06:53:52 +00:00
pkg/exchange: implement query kline api
This commit is contained in:
parent
784030821e
commit
755ea5e427
|
@ -5,6 +5,7 @@ import (
|
|||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
|
@ -78,4 +79,12 @@ func TestClient(t *testing.T) {
|
|||
resp, err := client.NewCancelOrderRequest().Symbol("APEUSDT").OrderId(req.OrderId).Do(ctx)
|
||||
t.Logf("cancel order resp: %+v", resp)
|
||||
})
|
||||
|
||||
t.Run("GetKLineRequest", func(t *testing.T) {
|
||||
startTime := time.Date(2023, 8, 12, 0, 0, 0, 0, time.UTC)
|
||||
endTime := time.Date(2023, 10, 14, 0, 0, 0, 0, time.UTC)
|
||||
resp, err := client.NewGetKLineRequest().Symbol("APEUSDT").Granularity("30min").StartTime(startTime).EndTime(endTime).Limit("1000").Do(ctx)
|
||||
assert.NoError(t, err)
|
||||
t.Logf("resp: %+v", resp)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -188,6 +188,12 @@ func (g *GetHistoryOrdersRequest) GetSlugsMap() (map[string]string, error) {
|
|||
return slugs, nil
|
||||
}
|
||||
|
||||
// GetPath returns the request path of the API
|
||||
func (g *GetHistoryOrdersRequest) GetPath() string {
|
||||
return "/api/v2/spot/trade/history-orders"
|
||||
}
|
||||
|
||||
// Do generates the request object and send the request object to the API endpoint
|
||||
func (g *GetHistoryOrdersRequest) Do(ctx context.Context) ([]OrderDetail, error) {
|
||||
|
||||
// no body params
|
||||
|
@ -197,7 +203,9 @@ func (g *GetHistoryOrdersRequest) Do(ctx context.Context) ([]OrderDetail, error)
|
|||
return nil, err
|
||||
}
|
||||
|
||||
apiURL := "/api/v2/spot/trade/history-orders"
|
||||
var apiURL string
|
||||
|
||||
apiURL = g.GetPath()
|
||||
|
||||
req, err := g.client.NewAuthenticatedRequest(ctx, "GET", apiURL, query, params)
|
||||
if err != nil {
|
||||
|
@ -214,6 +222,15 @@ func (g *GetHistoryOrdersRequest) Do(ctx context.Context) ([]OrderDetail, error)
|
|||
return nil, err
|
||||
}
|
||||
|
||||
type responseValidator interface {
|
||||
Validate() error
|
||||
}
|
||||
validator, ok := interface{}(apiResponse).(responseValidator)
|
||||
if ok {
|
||||
if err := validator.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
var data []OrderDetail
|
||||
if err := json.Unmarshal(apiResponse.Data, &data); err != nil {
|
||||
return nil, err
|
||||
|
|
83
pkg/exchange/bitget/bitgetapi/v2/get_k_line.go
Normal file
83
pkg/exchange/bitget/bitgetapi/v2/get_k_line.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
package bitgetapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/c9s/requestgen"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
//go:generate -command GetRequest requestgen -method GET -responseType .APIResponse -responseDataField Data
|
||||
//go:generate -command PostRequest requestgen -method POST -responseType .APIResponse -responseDataField Data
|
||||
|
||||
type KLine struct {
|
||||
// System timestamp, Unix millisecond timestamp, e.g. 1690196141868
|
||||
Ts types.MillisecondTimestamp
|
||||
Open fixedpoint.Value
|
||||
High fixedpoint.Value
|
||||
Low fixedpoint.Value
|
||||
Close fixedpoint.Value
|
||||
// Trading volume in base currency, e.g. "BTC" in the "BTCUSD" pair.
|
||||
Volume fixedpoint.Value
|
||||
// Trading volume in quote currency, e.g. "USD" in the "BTCUSD" pair.
|
||||
QuoteVolume fixedpoint.Value
|
||||
// Trading volume in USDT
|
||||
UsdtVolume fixedpoint.Value
|
||||
}
|
||||
|
||||
type KLineResponse []KLine
|
||||
|
||||
const KLinesArrayLen = 8
|
||||
|
||||
func (k *KLine) UnmarshalJSON(data []byte) error {
|
||||
var jsonArr []json.RawMessage
|
||||
err := json.Unmarshal(data, &jsonArr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal jsonRawMessage: %v, err: %w", string(data), err)
|
||||
}
|
||||
if len(jsonArr) != KLinesArrayLen {
|
||||
return fmt.Errorf("unexpected K Lines array length: %d, exp: %d", len(jsonArr), KLinesArrayLen)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(jsonArr[0], &k.Ts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal resp index 0: %v, err: %w", string(jsonArr[0]), err)
|
||||
}
|
||||
|
||||
values := make([]fixedpoint.Value, len(jsonArr)-1)
|
||||
for i, jsonRaw := range jsonArr[1:] {
|
||||
err = json.Unmarshal(jsonRaw, &values[i])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal resp index %d: %v, err: %w", i+1, string(jsonRaw), err)
|
||||
}
|
||||
}
|
||||
k.Open = values[0]
|
||||
k.High = values[1]
|
||||
k.Low = values[2]
|
||||
k.Close = values[3]
|
||||
k.Volume = values[4]
|
||||
k.QuoteVolume = values[5]
|
||||
k.UsdtVolume = values[6]
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//go:generate GetRequest -url "/api/v2/spot/market/candles" -type GetKLineRequest -responseDataType .KLineResponse
|
||||
type GetKLineRequest struct {
|
||||
client requestgen.APIClient
|
||||
|
||||
symbol string `param:"symbol,query"`
|
||||
granularity string `param:"granularity,query"`
|
||||
startTime *time.Time `param:"startTime,milliseconds,query"`
|
||||
endTime *time.Time `param:"endTime,milliseconds,query"`
|
||||
// Limit number default 100 max 1000
|
||||
limit *string `param:"limit,query"`
|
||||
}
|
||||
|
||||
func (s *Client) NewGetKLineRequest() *GetKLineRequest {
|
||||
return &GetKLineRequest{client: s.Client}
|
||||
}
|
|
@ -0,0 +1,224 @@
|
|||
// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Data -url /api/v2/spot/market/candles -type GetKLineRequest -responseDataType .KLineResponse"; DO NOT EDIT.
|
||||
|
||||
package bitgetapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/c9s/bbgo/pkg/exchange/bitget/bitgetapi"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (g *GetKLineRequest) Symbol(symbol string) *GetKLineRequest {
|
||||
g.symbol = symbol
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *GetKLineRequest) Granularity(granularity string) *GetKLineRequest {
|
||||
g.granularity = granularity
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *GetKLineRequest) StartTime(startTime time.Time) *GetKLineRequest {
|
||||
g.startTime = &startTime
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *GetKLineRequest) EndTime(endTime time.Time) *GetKLineRequest {
|
||||
g.endTime = &endTime
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *GetKLineRequest) Limit(limit string) *GetKLineRequest {
|
||||
g.limit = &limit
|
||||
return g
|
||||
}
|
||||
|
||||
// GetQueryParameters builds and checks the query parameters and returns url.Values
|
||||
func (g *GetKLineRequest) GetQueryParameters() (url.Values, error) {
|
||||
var params = map[string]interface{}{}
|
||||
// check symbol field -> json key symbol
|
||||
symbol := g.symbol
|
||||
|
||||
// assign parameter of symbol
|
||||
params["symbol"] = symbol
|
||||
// check granularity field -> json key granularity
|
||||
granularity := g.granularity
|
||||
|
||||
// assign parameter of granularity
|
||||
params["granularity"] = granularity
|
||||
// check startTime field -> json key startTime
|
||||
if g.startTime != nil {
|
||||
startTime := *g.startTime
|
||||
|
||||
// assign parameter of startTime
|
||||
// convert time.Time to milliseconds time stamp
|
||||
params["startTime"] = strconv.FormatInt(startTime.UnixNano()/int64(time.Millisecond), 10)
|
||||
} else {
|
||||
}
|
||||
// check endTime field -> json key endTime
|
||||
if g.endTime != nil {
|
||||
endTime := *g.endTime
|
||||
|
||||
// assign parameter of endTime
|
||||
// convert time.Time to milliseconds time stamp
|
||||
params["endTime"] = strconv.FormatInt(endTime.UnixNano()/int64(time.Millisecond), 10)
|
||||
} else {
|
||||
}
|
||||
// check limit field -> json key limit
|
||||
if g.limit != nil {
|
||||
limit := *g.limit
|
||||
|
||||
// assign parameter of limit
|
||||
params["limit"] = limit
|
||||
} else {
|
||||
}
|
||||
|
||||
query := url.Values{}
|
||||
for _k, _v := range params {
|
||||
query.Add(_k, fmt.Sprintf("%v", _v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParameters builds and checks the parameters and return the result in a map object
|
||||
func (g *GetKLineRequest) GetParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// GetParametersQuery converts the parameters from GetParameters into the url.Values format
|
||||
func (g *GetKLineRequest) GetParametersQuery() (url.Values, error) {
|
||||
query := url.Values{}
|
||||
|
||||
params, err := g.GetParameters()
|
||||
if err != nil {
|
||||
return query, err
|
||||
}
|
||||
|
||||
for _k, _v := range params {
|
||||
if g.isVarSlice(_v) {
|
||||
g.iterateSlice(_v, func(it interface{}) {
|
||||
query.Add(_k+"[]", fmt.Sprintf("%v", it))
|
||||
})
|
||||
} else {
|
||||
query.Add(_k, fmt.Sprintf("%v", _v))
|
||||
}
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParametersJSON converts the parameters from GetParameters into the JSON format
|
||||
func (g *GetKLineRequest) GetParametersJSON() ([]byte, error) {
|
||||
params, err := g.GetParameters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(params)
|
||||
}
|
||||
|
||||
// GetSlugParameters builds and checks the slug parameters and return the result in a map object
|
||||
func (g *GetKLineRequest) GetSlugParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (g *GetKLineRequest) applySlugsToUrl(url string, slugs map[string]string) string {
|
||||
for _k, _v := range slugs {
|
||||
needleRE := regexp.MustCompile(":" + _k + "\\b")
|
||||
url = needleRE.ReplaceAllString(url, _v)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
func (g *GetKLineRequest) iterateSlice(slice interface{}, _f func(it interface{})) {
|
||||
sliceValue := reflect.ValueOf(slice)
|
||||
for _i := 0; _i < sliceValue.Len(); _i++ {
|
||||
it := sliceValue.Index(_i).Interface()
|
||||
_f(it)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GetKLineRequest) isVarSlice(_v interface{}) bool {
|
||||
rt := reflect.TypeOf(_v)
|
||||
switch rt.Kind() {
|
||||
case reflect.Slice:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (g *GetKLineRequest) GetSlugsMap() (map[string]string, error) {
|
||||
slugs := map[string]string{}
|
||||
params, err := g.GetSlugParameters()
|
||||
if err != nil {
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
for _k, _v := range params {
|
||||
slugs[_k] = fmt.Sprintf("%v", _v)
|
||||
}
|
||||
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
// GetPath returns the request path of the API
|
||||
func (g *GetKLineRequest) GetPath() string {
|
||||
return "/api/v2/spot/market/candles"
|
||||
}
|
||||
|
||||
// Do generates the request object and send the request object to the API endpoint
|
||||
func (g *GetKLineRequest) Do(ctx context.Context) (KLineResponse, error) {
|
||||
|
||||
// no body params
|
||||
var params interface{}
|
||||
query, err := g.GetQueryParameters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var apiURL string
|
||||
|
||||
apiURL = g.GetPath()
|
||||
|
||||
req, err := g.client.NewRequest(ctx, "GET", apiURL, query, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := g.client.SendRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var apiResponse bitgetapi.APIResponse
|
||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
type responseValidator interface {
|
||||
Validate() error
|
||||
}
|
||||
validator, ok := interface{}(apiResponse).(responseValidator)
|
||||
if ok {
|
||||
if err := validator.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
var data KLineResponse
|
||||
if err := json.Unmarshal(apiResponse.Data, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
|
@ -6,6 +6,7 @@ import (
|
|||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/exchange/bitget/bitgetapi"
|
||||
v2 "github.com/c9s/bbgo/pkg/exchange/bitget/bitgetapi/v2"
|
||||
|
@ -341,3 +342,26 @@ func toGlobalBalanceMap(balances []Balance) types.BalanceMap {
|
|||
}
|
||||
return bm
|
||||
}
|
||||
|
||||
func toGlobalKLines(symbol string, interval types.Interval, kLines v2.KLineResponse) []types.KLine {
|
||||
gKLines := make([]types.KLine, len(kLines))
|
||||
for i, kline := range kLines {
|
||||
endTime := types.Time(kline.Ts.Time().Add(interval.Duration() - time.Millisecond))
|
||||
gKLines[i] = types.KLine{
|
||||
Exchange: types.ExchangeBitget,
|
||||
Symbol: symbol,
|
||||
StartTime: types.Time(kline.Ts),
|
||||
EndTime: endTime,
|
||||
Interval: interval,
|
||||
Open: kline.Open,
|
||||
Close: kline.Close,
|
||||
High: kline.High,
|
||||
Low: kline.Low,
|
||||
Volume: kline.Volume,
|
||||
QuoteVolume: kline.QuoteVolume,
|
||||
// Bitget doesn't support close flag in REST API
|
||||
Closed: false,
|
||||
}
|
||||
}
|
||||
return gKLines
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package bitget
|
|||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
|
@ -598,3 +599,88 @@ func Test_toGlobalBalanceMap(t *testing.T) {
|
|||
},
|
||||
}))
|
||||
}
|
||||
|
||||
func Test_toGlobalKLines(t *testing.T) {
|
||||
symbol := "BTCUSDT"
|
||||
interval := types.Interval15m
|
||||
|
||||
resp := v2.KLineResponse{
|
||||
/*
|
||||
[
|
||||
{
|
||||
"Ts": "1699816800000",
|
||||
"OpenPrice": 29045.3,
|
||||
"HighPrice": 29228.56,
|
||||
"LowPrice": 29045.3,
|
||||
"ClosePrice": 29228.56,
|
||||
"Volume": 9.265593,
|
||||
"QuoteVolume": 270447.43520753,
|
||||
"UsdtVolume": 270447.43520753
|
||||
},
|
||||
{
|
||||
"Ts": "1699816800000",
|
||||
"OpenPrice": 29167.33,
|
||||
"HighPrice": 29229.08,
|
||||
"LowPrice": 29000,
|
||||
"ClosePrice": 29045.3,
|
||||
"Volume": 9.295508,
|
||||
"QuoteVolume": 270816.87513775,
|
||||
"UsdtVolume": 270816.87513775
|
||||
}
|
||||
]
|
||||
*/
|
||||
{
|
||||
Ts: types.NewMillisecondTimestampFromInt(1691486100000),
|
||||
Open: fixedpoint.NewFromFloat(29045.3),
|
||||
High: fixedpoint.NewFromFloat(29228.56),
|
||||
Low: fixedpoint.NewFromFloat(29045.3),
|
||||
Close: fixedpoint.NewFromFloat(29228.56),
|
||||
Volume: fixedpoint.NewFromFloat(9.265593),
|
||||
QuoteVolume: fixedpoint.NewFromFloat(270447.43520753),
|
||||
UsdtVolume: fixedpoint.NewFromFloat(270447.43520753),
|
||||
},
|
||||
{
|
||||
Ts: types.NewMillisecondTimestampFromInt(1691487000000),
|
||||
Open: fixedpoint.NewFromFloat(29167.33),
|
||||
High: fixedpoint.NewFromFloat(29229.08),
|
||||
Low: fixedpoint.NewFromFloat(29000),
|
||||
Close: fixedpoint.NewFromFloat(29045.3),
|
||||
Volume: fixedpoint.NewFromFloat(9.295508),
|
||||
QuoteVolume: fixedpoint.NewFromFloat(270816.87513775),
|
||||
UsdtVolume: fixedpoint.NewFromFloat(270447.43520753),
|
||||
},
|
||||
}
|
||||
|
||||
expKlines := []types.KLine{
|
||||
{
|
||||
Exchange: types.ExchangeBitget,
|
||||
Symbol: symbol,
|
||||
StartTime: types.Time(resp[0].Ts.Time()),
|
||||
EndTime: types.Time(resp[0].Ts.Time().Add(interval.Duration() - time.Millisecond)),
|
||||
Interval: interval,
|
||||
Open: fixedpoint.NewFromFloat(29045.3),
|
||||
Close: fixedpoint.NewFromFloat(29228.56),
|
||||
High: fixedpoint.NewFromFloat(29228.56),
|
||||
Low: fixedpoint.NewFromFloat(29045.3),
|
||||
Volume: fixedpoint.NewFromFloat(9.265593),
|
||||
QuoteVolume: fixedpoint.NewFromFloat(270447.43520753),
|
||||
Closed: false,
|
||||
},
|
||||
{
|
||||
Exchange: types.ExchangeBitget,
|
||||
Symbol: symbol,
|
||||
StartTime: types.Time(resp[1].Ts.Time()),
|
||||
EndTime: types.Time(resp[1].Ts.Time().Add(interval.Duration() - time.Millisecond)),
|
||||
Interval: interval,
|
||||
Open: fixedpoint.NewFromFloat(29167.33),
|
||||
Close: fixedpoint.NewFromFloat(29045.3),
|
||||
High: fixedpoint.NewFromFloat(29229.08),
|
||||
Low: fixedpoint.NewFromFloat(29000),
|
||||
Volume: fixedpoint.NewFromFloat(9.295508),
|
||||
QuoteVolume: fixedpoint.NewFromFloat(270816.87513775),
|
||||
Closed: false,
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, toGlobalKLines(symbol, interval, resp), expKlines)
|
||||
}
|
||||
|
|
|
@ -21,9 +21,10 @@ const (
|
|||
|
||||
PlatformToken = "BGB"
|
||||
|
||||
queryLimit = 100
|
||||
maxOrderIdLen = 36
|
||||
queryMaxDuration = 90 * 24 * time.Hour
|
||||
queryLimit = 100
|
||||
defaultKLineLimit = 100
|
||||
maxOrderIdLen = 36
|
||||
queryMaxDuration = 90 * 24 * time.Hour
|
||||
)
|
||||
|
||||
var log = logrus.WithFields(logrus.Fields{
|
||||
|
@ -49,6 +50,8 @@ var (
|
|||
queryTradeRateLimiter = rate.NewLimiter(rate.Every(time.Second/5), 5)
|
||||
// cancelOrderRateLimiter has its own rate limit. https://www.bitget.com/api-doc/spot/trade/Cancel-Order
|
||||
cancelOrderRateLimiter = rate.NewLimiter(rate.Every(time.Second/5), 5)
|
||||
// kLineRateLimiter has its own rate limit. https://www.bitget.com/api-doc/spot/market/Get-Candle-Data
|
||||
kLineOrderRateLimiter = rate.NewLimiter(rate.Every(time.Second/10), 5)
|
||||
)
|
||||
|
||||
type Exchange struct {
|
||||
|
@ -153,9 +156,56 @@ func (e *Exchange) QueryTickers(ctx context.Context, symbols ...string) (map[str
|
|||
return tickers, nil
|
||||
}
|
||||
|
||||
// QueryKLines queries the k line data by interval and time range...etc.
|
||||
//
|
||||
// If you provide only the start time, the system will return the latest data.
|
||||
// If you provide both the start and end times, the system will return data within the specified range.
|
||||
// If you provide only the end time, the system will return data that occurred before the end time.
|
||||
//
|
||||
// The end time has different limits. 1m, 5m can query for one month,15m can query for 52 days,30m can query for 62 days,
|
||||
// 1H can query for 83 days,4H can query for 240 days,6H can query for 360 days.
|
||||
func (e *Exchange) QueryKLines(ctx context.Context, symbol string, interval types.Interval, options types.KLineQueryOptions) ([]types.KLine, error) {
|
||||
// TODO implement me
|
||||
panic("implement me")
|
||||
req := e.v2Client.NewGetKLineRequest().Symbol(symbol)
|
||||
intervalStr, found := toLocalGranularity[interval]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("%s not supported, supported granlarity: %+v", intervalStr, toLocalGranularity)
|
||||
}
|
||||
req.Granularity(intervalStr)
|
||||
|
||||
limit := uint64(options.Limit)
|
||||
if limit > defaultKLineLimit || limit <= 0 {
|
||||
log.Debugf("limtit is exceeded or zero, update to %d, got: %d", defaultKLineLimit, options.Limit)
|
||||
limit = defaultKLineLimit
|
||||
}
|
||||
req.Limit(strconv.FormatUint(limit, 10))
|
||||
|
||||
if options.StartTime != nil {
|
||||
req.StartTime(*options.StartTime)
|
||||
}
|
||||
|
||||
if options.EndTime != nil {
|
||||
if options.StartTime != nil && options.EndTime.Before(*options.StartTime) {
|
||||
return nil, fmt.Errorf("end time %s before start time %s", *options.EndTime, *options.StartTime)
|
||||
}
|
||||
|
||||
ok, duration := hasMaxDuration(interval)
|
||||
if ok && time.Since(*options.EndTime) > duration {
|
||||
return nil, fmt.Errorf("end time %s are greater than max duration %s", *options.EndTime, duration)
|
||||
}
|
||||
req.EndTime(*options.EndTime)
|
||||
}
|
||||
|
||||
if err := kLineOrderRateLimiter.Wait(ctx); err != nil {
|
||||
return nil, fmt.Errorf("query klines rate limiter wait error: %w", err)
|
||||
}
|
||||
|
||||
resp, err := req.Do(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to call k line, err: %w", err)
|
||||
}
|
||||
|
||||
kLines := toGlobalKLines(symbol, interval, resp)
|
||||
return types.SortKLinesAscending(kLines), nil
|
||||
}
|
||||
|
||||
func (e *Exchange) QueryAccount(ctx context.Context) (*types.Account, error) {
|
||||
|
|
|
@ -299,8 +299,43 @@ var (
|
|||
"candle1D": types.Interval1d,
|
||||
"candle1W": types.Interval1w,
|
||||
}
|
||||
|
||||
// we align utc time zone
|
||||
toLocalGranularity = map[types.Interval]string{
|
||||
types.Interval1m: "1min",
|
||||
types.Interval5m: "5min",
|
||||
types.Interval15m: "15min",
|
||||
types.Interval30m: "30min",
|
||||
types.Interval1h: "1h",
|
||||
types.Interval4h: "4h",
|
||||
types.Interval6h: "6Hutc",
|
||||
types.Interval12h: "12Hutc",
|
||||
types.Interval1d: "1Dutc",
|
||||
types.Interval3d: "3Dutc",
|
||||
types.Interval1w: "1Wutc",
|
||||
types.Interval1mo: "1Mutc",
|
||||
}
|
||||
)
|
||||
|
||||
func hasMaxDuration(interval types.Interval) (bool, time.Duration) {
|
||||
switch interval {
|
||||
case types.Interval1m, types.Interval5m:
|
||||
return true, 30 * 24 * time.Hour
|
||||
case types.Interval15m:
|
||||
return true, 52 * 24 * time.Hour
|
||||
case types.Interval30m:
|
||||
return true, 62 * 24 * time.Hour
|
||||
case types.Interval1h:
|
||||
return true, 83 * 24 * time.Hour
|
||||
case types.Interval4h:
|
||||
return true, 240 * 24 * time.Hour
|
||||
case types.Interval6h:
|
||||
return true, 360 * 24 * time.Hour
|
||||
default:
|
||||
return false, 0 * time.Duration(0)
|
||||
}
|
||||
}
|
||||
|
||||
type KLine struct {
|
||||
StartTime types.MillisecondTimestamp
|
||||
OpenPrice fixedpoint.Value
|
||||
|
|
|
@ -6,38 +6,35 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
func TestKLine_ToGlobal(t *testing.T) {
|
||||
startTime := int64(1698744600000)
|
||||
interval := types.Interval1m
|
||||
k := KLine{
|
||||
StartTime: types.NewMillisecondTimestampFromInt(startTime),
|
||||
OpenPrice: fixedpoint.NewFromFloat(34361.49),
|
||||
HighestPrice: fixedpoint.NewFromFloat(34458.98),
|
||||
LowestPrice: fixedpoint.NewFromFloat(34355.53),
|
||||
ClosePrice: fixedpoint.NewFromFloat(34416.41),
|
||||
Volume: fixedpoint.NewFromFloat(99.6631),
|
||||
}
|
||||
func Test_hasMaxDuration(t *testing.T) {
|
||||
ok, duration := hasMaxDuration(types.Interval1m)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 30*24*time.Hour, duration)
|
||||
|
||||
assert.Equal(t, types.KLine{
|
||||
Exchange: types.ExchangeBitget,
|
||||
Symbol: "BTCUSDT",
|
||||
StartTime: types.Time(types.NewMillisecondTimestampFromInt(startTime).Time()),
|
||||
EndTime: types.Time(types.NewMillisecondTimestampFromInt(startTime).Time().Add(interval.Duration() - time.Millisecond)),
|
||||
Interval: interval,
|
||||
Open: fixedpoint.NewFromFloat(34361.49),
|
||||
Close: fixedpoint.NewFromFloat(34416.41),
|
||||
High: fixedpoint.NewFromFloat(34458.98),
|
||||
Low: fixedpoint.NewFromFloat(34355.53),
|
||||
Volume: fixedpoint.NewFromFloat(99.6631),
|
||||
QuoteVolume: fixedpoint.Zero,
|
||||
TakerBuyBaseAssetVolume: fixedpoint.Zero,
|
||||
TakerBuyQuoteAssetVolume: fixedpoint.Zero,
|
||||
LastTradeID: 0,
|
||||
NumberOfTrades: 0,
|
||||
Closed: false,
|
||||
}, k.ToGlobal(interval, "BTCUSDT"))
|
||||
ok, duration = hasMaxDuration(types.Interval5m)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 30*24*time.Hour, duration)
|
||||
|
||||
ok, duration = hasMaxDuration(types.Interval15m)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 52*24*time.Hour, duration)
|
||||
|
||||
ok, duration = hasMaxDuration(types.Interval30m)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 62*24*time.Hour, duration)
|
||||
|
||||
ok, duration = hasMaxDuration(types.Interval1h)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 83*24*time.Hour, duration)
|
||||
|
||||
ok, duration = hasMaxDuration(types.Interval4h)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 240*24*time.Hour, duration)
|
||||
|
||||
ok, duration = hasMaxDuration(types.Interval6h)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 360*24*time.Hour, duration)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user