Merge pull request #1419 from c9s/edwin/bitget/fix-xmaker-error

FIX: [bitget] use the now - 90 days instead of return err if since is 90 days earlier
This commit is contained in:
bailantaotao 2023-11-17 16:54:28 +08:00 committed by GitHub
commit afc864dfc4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 167 additions and 92 deletions

View File

@ -130,8 +130,8 @@ the implementation.
- OKEx Spot Exchange - OKEx Spot Exchange
- Kucoin Spot Exchange - Kucoin Spot Exchange
- MAX Spot Exchange (located in Taiwan) - MAX Spot Exchange (located in Taiwan)
- Bitget Exchange (In Progress) - Bitget Exchange
- Bybit Exchange (In Progress) - Bybit Exchange
## Documentation and General Topics ## Documentation and General Topics

View File

@ -34,15 +34,16 @@ func TestClient(t *testing.T) {
ctx := context.Background() ctx := context.Background()
t.Run("GetUnfilledOrdersRequest", func(t *testing.T) { t.Run("GetUnfilledOrdersRequest", func(t *testing.T) {
req := client.NewGetUnfilledOrdersRequest().StartTime(1) startTime := time.Now().Add(-30 * 24 * time.Hour)
req := client.NewGetUnfilledOrdersRequest().StartTime(startTime)
resp, err := req.Do(ctx) resp, err := req.Do(ctx)
assert.NoError(t, err) assert.NoError(t, err)
t.Logf("resp: %+v", resp) t.Logf("resp: %+v", resp)
}) })
t.Run("GetHistoryOrdersRequest", func(t *testing.T) { t.Run("GetHistoryOrdersRequest", func(t *testing.T) {
// market buy startTime := time.Now().Add(-30 * 24 * time.Hour)
req, err := client.NewGetHistoryOrdersRequest().Symbol("APEUSDT").Do(ctx) req, err := client.NewGetHistoryOrdersRequest().Symbol("APEUSDT").StartTime(startTime).Do(ctx)
assert.NoError(t, err) assert.NoError(t, err)
t.Logf("place order resp: %+v", req) t.Logf("place order resp: %+v", req)
@ -61,7 +62,8 @@ func TestClient(t *testing.T) {
}) })
t.Run("GetTradeFillsRequest", func(t *testing.T) { t.Run("GetTradeFillsRequest", func(t *testing.T) {
req, err := client.NewGetTradeFillsRequest().Symbol("APEUSDT").Do(ctx) startTime := time.Now().Add(-30 * 24 * time.Hour)
req, err := client.NewGetTradeFillsRequest().Symbol("APEUSDT").StartTime(startTime).Do(ctx)
assert.NoError(t, err) assert.NoError(t, err)
t.Logf("get trade fills resp: %+v", req) t.Logf("get trade fills resp: %+v", req)

View File

@ -6,6 +6,7 @@ package bitgetapi
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"time"
"github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/types"
@ -92,8 +93,8 @@ type GetHistoryOrdersRequest struct {
limit *string `param:"limit,query"` limit *string `param:"limit,query"`
// idLessThan requests the content on the page before this ID (older data), the value input should be the orderId of the corresponding interface. // idLessThan requests the content on the page before this ID (older data), the value input should be the orderId of the corresponding interface.
idLessThan *string `param:"idLessThan,query"` idLessThan *string `param:"idLessThan,query"`
startTime *int64 `param:"startTime,query"` startTime *time.Time `param:"startTime,milliseconds,query"`
endTime *int64 `param:"endTime,query"` endTime *time.Time `param:"endTime,milliseconds,query"`
orderId *string `param:"orderId,query"` orderId *string `param:"orderId,query"`
} }

View File

@ -10,6 +10,8 @@ import (
"net/url" "net/url"
"reflect" "reflect"
"regexp" "regexp"
"strconv"
"time"
) )
func (g *GetHistoryOrdersRequest) Symbol(symbol string) *GetHistoryOrdersRequest { func (g *GetHistoryOrdersRequest) Symbol(symbol string) *GetHistoryOrdersRequest {
@ -27,12 +29,12 @@ func (g *GetHistoryOrdersRequest) IdLessThan(idLessThan string) *GetHistoryOrder
return g return g
} }
func (g *GetHistoryOrdersRequest) StartTime(startTime int64) *GetHistoryOrdersRequest { func (g *GetHistoryOrdersRequest) StartTime(startTime time.Time) *GetHistoryOrdersRequest {
g.startTime = &startTime g.startTime = &startTime
return g return g
} }
func (g *GetHistoryOrdersRequest) EndTime(endTime int64) *GetHistoryOrdersRequest { func (g *GetHistoryOrdersRequest) EndTime(endTime time.Time) *GetHistoryOrdersRequest {
g.endTime = &endTime g.endTime = &endTime
return g return g
} }
@ -74,7 +76,8 @@ func (g *GetHistoryOrdersRequest) GetQueryParameters() (url.Values, error) {
startTime := *g.startTime startTime := *g.startTime
// assign parameter of startTime // assign parameter of startTime
params["startTime"] = startTime // convert time.Time to milliseconds time stamp
params["startTime"] = strconv.FormatInt(startTime.UnixNano()/int64(time.Millisecond), 10)
} else { } else {
} }
// check endTime field -> json key endTime // check endTime field -> json key endTime
@ -82,7 +85,8 @@ func (g *GetHistoryOrdersRequest) GetQueryParameters() (url.Values, error) {
endTime := *g.endTime endTime := *g.endTime
// assign parameter of endTime // assign parameter of endTime
params["endTime"] = endTime // convert time.Time to milliseconds time stamp
params["endTime"] = strconv.FormatInt(endTime.UnixNano()/int64(time.Millisecond), 10)
} else { } else {
} }
// check orderId field -> json key orderId // check orderId field -> json key orderId

View File

@ -1,6 +1,8 @@
package bitgetapi package bitgetapi
import ( import (
"time"
"github.com/c9s/requestgen" "github.com/c9s/requestgen"
"github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/fixedpoint"
@ -60,8 +62,8 @@ type GetTradeFillsRequest struct {
limit *string `param:"limit,query"` limit *string `param:"limit,query"`
// idLessThan requests the content on the page before this ID (older data), the value input should be the orderId of the corresponding interface. // idLessThan requests the content on the page before this ID (older data), the value input should be the orderId of the corresponding interface.
idLessThan *string `param:"idLessThan,query"` idLessThan *string `param:"idLessThan,query"`
startTime *int64 `param:"startTime,query"` startTime *time.Time `param:"startTime,milliseconds,query"`
endTime *int64 `param:"endTime,query"` endTime *time.Time `param:"endTime,milliseconds,query"`
orderId *string `param:"orderId,query"` orderId *string `param:"orderId,query"`
} }

View File

@ -10,6 +10,8 @@ import (
"net/url" "net/url"
"reflect" "reflect"
"regexp" "regexp"
"strconv"
"time"
) )
func (s *GetTradeFillsRequest) Symbol(symbol string) *GetTradeFillsRequest { func (s *GetTradeFillsRequest) Symbol(symbol string) *GetTradeFillsRequest {
@ -27,12 +29,12 @@ func (s *GetTradeFillsRequest) IdLessThan(idLessThan string) *GetTradeFillsReque
return s return s
} }
func (s *GetTradeFillsRequest) StartTime(startTime int64) *GetTradeFillsRequest { func (s *GetTradeFillsRequest) StartTime(startTime time.Time) *GetTradeFillsRequest {
s.startTime = &startTime s.startTime = &startTime
return s return s
} }
func (s *GetTradeFillsRequest) EndTime(endTime int64) *GetTradeFillsRequest { func (s *GetTradeFillsRequest) EndTime(endTime time.Time) *GetTradeFillsRequest {
s.endTime = &endTime s.endTime = &endTime
return s return s
} }
@ -71,7 +73,8 @@ func (s *GetTradeFillsRequest) GetQueryParameters() (url.Values, error) {
startTime := *s.startTime startTime := *s.startTime
// assign parameter of startTime // assign parameter of startTime
params["startTime"] = startTime // convert time.Time to milliseconds time stamp
params["startTime"] = strconv.FormatInt(startTime.UnixNano()/int64(time.Millisecond), 10)
} else { } else {
} }
// check endTime field -> json key endTime // check endTime field -> json key endTime
@ -79,7 +82,8 @@ func (s *GetTradeFillsRequest) GetQueryParameters() (url.Values, error) {
endTime := *s.endTime endTime := *s.endTime
// assign parameter of endTime // assign parameter of endTime
params["endTime"] = endTime // convert time.Time to milliseconds time stamp
params["endTime"] = strconv.FormatInt(endTime.UnixNano()/int64(time.Millisecond), 10)
} else { } else {
} }
// check orderId field -> json key orderId // check orderId field -> json key orderId
@ -185,6 +189,12 @@ func (s *GetTradeFillsRequest) GetSlugsMap() (map[string]string, error) {
return slugs, nil return slugs, nil
} }
// GetPath returns the request path of the API
func (s *GetTradeFillsRequest) GetPath() string {
return "/api/v2/spot/trade/fills"
}
// Do generates the request object and send the request object to the API endpoint
func (s *GetTradeFillsRequest) Do(ctx context.Context) ([]Trade, error) { func (s *GetTradeFillsRequest) Do(ctx context.Context) ([]Trade, error) {
// no body params // no body params
@ -194,7 +204,9 @@ func (s *GetTradeFillsRequest) Do(ctx context.Context) ([]Trade, error) {
return nil, err return nil, err
} }
apiURL := "/api/v2/spot/trade/fills" var apiURL string
apiURL = s.GetPath()
req, err := s.client.NewAuthenticatedRequest(ctx, "GET", apiURL, query, params) req, err := s.client.NewAuthenticatedRequest(ctx, "GET", apiURL, query, params)
if err != nil { if err != nil {
@ -211,6 +223,15 @@ func (s *GetTradeFillsRequest) Do(ctx context.Context) ([]Trade, error) {
return nil, err 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 []Trade var data []Trade
if err := json.Unmarshal(apiResponse.Data, &data); err != nil { if err := json.Unmarshal(apiResponse.Data, &data); err != nil {
return nil, err return nil, err

View File

@ -4,6 +4,8 @@ package bitgetapi
//go:generate -command PostRequest requestgen -method POST -responseType .APIResponse -responseDataField Data //go:generate -command PostRequest requestgen -method POST -responseType .APIResponse -responseDataField Data
import ( import (
"time"
"github.com/c9s/requestgen" "github.com/c9s/requestgen"
"github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/fixedpoint"
@ -40,8 +42,8 @@ type GetUnfilledOrdersRequest struct {
limit *string `param:"limit,query"` limit *string `param:"limit,query"`
// idLessThan requests the content on the page before this ID (older data), the value input should be the orderId of the corresponding interface. // idLessThan requests the content on the page before this ID (older data), the value input should be the orderId of the corresponding interface.
idLessThan *string `param:"idLessThan,query"` idLessThan *string `param:"idLessThan,query"`
startTime *int64 `param:"startTime,query"` startTime *time.Time `param:"startTime,milliseconds,query"`
endTime *int64 `param:"endTime,query"` endTime *time.Time `param:"endTime,milliseconds,query"`
orderId *string `param:"orderId,query"` orderId *string `param:"orderId,query"`
} }

View File

@ -10,6 +10,8 @@ import (
"net/url" "net/url"
"reflect" "reflect"
"regexp" "regexp"
"strconv"
"time"
) )
func (g *GetUnfilledOrdersRequest) Symbol(symbol string) *GetUnfilledOrdersRequest { func (g *GetUnfilledOrdersRequest) Symbol(symbol string) *GetUnfilledOrdersRequest {
@ -27,12 +29,12 @@ func (g *GetUnfilledOrdersRequest) IdLessThan(idLessThan string) *GetUnfilledOrd
return g return g
} }
func (g *GetUnfilledOrdersRequest) StartTime(startTime int64) *GetUnfilledOrdersRequest { func (g *GetUnfilledOrdersRequest) StartTime(startTime time.Time) *GetUnfilledOrdersRequest {
g.startTime = &startTime g.startTime = &startTime
return g return g
} }
func (g *GetUnfilledOrdersRequest) EndTime(endTime int64) *GetUnfilledOrdersRequest { func (g *GetUnfilledOrdersRequest) EndTime(endTime time.Time) *GetUnfilledOrdersRequest {
g.endTime = &endTime g.endTime = &endTime
return g return g
} }
@ -74,7 +76,8 @@ func (g *GetUnfilledOrdersRequest) GetQueryParameters() (url.Values, error) {
startTime := *g.startTime startTime := *g.startTime
// assign parameter of startTime // assign parameter of startTime
params["startTime"] = startTime // convert time.Time to milliseconds time stamp
params["startTime"] = strconv.FormatInt(startTime.UnixNano()/int64(time.Millisecond), 10)
} else { } else {
} }
// check endTime field -> json key endTime // check endTime field -> json key endTime
@ -82,7 +85,8 @@ func (g *GetUnfilledOrdersRequest) GetQueryParameters() (url.Values, error) {
endTime := *g.endTime endTime := *g.endTime
// assign parameter of endTime // assign parameter of endTime
params["endTime"] = endTime // convert time.Time to milliseconds time stamp
params["endTime"] = strconv.FormatInt(endTime.UnixNano()/int64(time.Millisecond), 10)
} else { } else {
} }
// check orderId field -> json key orderId // check orderId field -> json key orderId
@ -188,6 +192,12 @@ func (g *GetUnfilledOrdersRequest) GetSlugsMap() (map[string]string, error) {
return slugs, nil return slugs, nil
} }
// GetPath returns the request path of the API
func (g *GetUnfilledOrdersRequest) GetPath() string {
return "/api/v2/spot/trade/unfilled-orders"
}
// Do generates the request object and send the request object to the API endpoint
func (g *GetUnfilledOrdersRequest) Do(ctx context.Context) ([]UnfilledOrder, error) { func (g *GetUnfilledOrdersRequest) Do(ctx context.Context) ([]UnfilledOrder, error) {
// no body params // no body params
@ -197,7 +207,9 @@ func (g *GetUnfilledOrdersRequest) Do(ctx context.Context) ([]UnfilledOrder, err
return nil, err return nil, err
} }
apiURL := "/api/v2/spot/trade/unfilled-orders" var apiURL string
apiURL = g.GetPath()
req, err := g.client.NewAuthenticatedRequest(ctx, "GET", apiURL, query, params) req, err := g.client.NewAuthenticatedRequest(ctx, "GET", apiURL, query, params)
if err != nil { if err != nil {
@ -213,6 +225,16 @@ func (g *GetUnfilledOrdersRequest) Do(ctx context.Context) ([]UnfilledOrder, err
if err := response.DecodeJSON(&apiResponse); err != nil { if err := response.DecodeJSON(&apiResponse); err != nil {
return nil, err 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 []UnfilledOrder var data []UnfilledOrder
if err := json.Unmarshal(apiResponse.Data, &data); err != nil { if err := json.Unmarshal(apiResponse.Data, &data); err != nil {
return nil, err return nil, err

View File

@ -24,7 +24,7 @@ const (
queryLimit = 100 queryLimit = 100
defaultKLineLimit = 100 defaultKLineLimit = 100
maxOrderIdLen = 36 maxOrderIdLen = 36
queryMaxDuration = 90 * 24 * time.Hour maxHistoricalDataQueryPeriod = 90 * 24 * time.Hour
) )
var log = logrus.WithFields(logrus.Fields{ var log = logrus.WithFields(logrus.Fields{
@ -123,7 +123,7 @@ func (e *Exchange) QueryTicker(ctx context.Context, symbol string) (*types.Ticke
return nil, fmt.Errorf("unexpected length of query single symbol: %+v", resp) return nil, fmt.Errorf("unexpected length of query single symbol: %+v", resp)
} }
ticker := toGlobalTicker(resp[1]) ticker := toGlobalTicker(resp[0])
return &ticker, nil return &ticker, nil
} }
@ -286,10 +286,10 @@ func (e *Exchange) SubmitOrder(ctx context.Context, order types.SubmitOrder) (cr
// we support only GTC/PostOnly, this is because: // we support only GTC/PostOnly, this is because:
// 1. We support only SPOT trading. // 1. We support only SPOT trading.
// 2. The query oepn/closed order does not including the `force` in SPOT. // 2. The query open/closed order does not include the `force` in SPOT.
// If we support FOK/IOC, but you can't query them, that would be unreasonable. // If we support FOK/IOC, but you can't query them, that would be unreasonable.
// The other case to consider is 'PostOnly', which is a trade-off because we want to support 'xmaker'. // The other case to consider is 'PostOnly', which is a trade-off because we want to support 'xmaker'.
if order.TimeInForce != types.TimeInForceGTC { if len(order.TimeInForce) != 0 && order.TimeInForce != types.TimeInForceGTC {
return nil, fmt.Errorf("time-in-force %s not supported", order.TimeInForce) return nil, fmt.Errorf("time-in-force %s not supported", order.TimeInForce)
} }
req.Force(v2.OrderForceGTC) req.Force(v2.OrderForceGTC)
@ -397,18 +397,24 @@ func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders [
// QueryClosedOrders queries closed order by time range(`CreatedTime`) and id. The order of the response is in descending order. // QueryClosedOrders queries closed order by time range(`CreatedTime`) and id. The order of the response is in descending order.
// If you need to retrieve all data, please utilize the function pkg/exchange/batch.ClosedOrderBatchQuery. // If you need to retrieve all data, please utilize the function pkg/exchange/batch.ClosedOrderBatchQuery.
// //
// REMARK: If your start time is 90 days earlier, we will update it to now - 90 days.
// ** Since is inclusive, Until is exclusive. If you use a time range to query, you must provide both a start time and an end time. ** // ** Since is inclusive, Until is exclusive. If you use a time range to query, you must provide both a start time and an end time. **
// ** Since and Until cannot exceed 90 days. ** // ** Since and Until cannot exceed 90 days. **
// ** Since from the last 90 days can be queried. ** // ** Since from the last 90 days can be queried **
func (e *Exchange) QueryClosedOrders(ctx context.Context, symbol string, since, until time.Time, lastOrderID uint64) (orders []types.Order, err error) { func (e *Exchange) QueryClosedOrders(ctx context.Context, symbol string, since, until time.Time, lastOrderID uint64) (orders []types.Order, err error) {
if time.Since(since) > queryMaxDuration { newSince := since
return nil, fmt.Errorf("start time from the last 90 days can be queried, got: %s", since) now := time.Now()
if time.Since(newSince) > maxHistoricalDataQueryPeriod {
newSince = now.Add(-maxHistoricalDataQueryPeriod)
log.Warnf("!!!BITGET EXCHANGE API NOTICE!!! The closed order API cannot query data beyond 90 days from the current date, update %s -> %s", since, newSince)
} }
if until.Before(since) { if until.Before(newSince) {
return nil, fmt.Errorf("end time %s before start %s", until, since) log.Warnf("!!!BITGET EXCHANGE API NOTICE!!! The 'until' comes before 'since', update until to now(%s -> %s).", until, now)
until = now
} }
if until.Sub(since) > queryMaxDuration { if until.Sub(newSince) > maxHistoricalDataQueryPeriod {
return nil, fmt.Errorf("the start time %s and end time %s cannot exceed 90 days", since, until) return nil, fmt.Errorf("the start time %s and end time %s cannot exceed 90 days", newSince, until)
} }
if lastOrderID != 0 { if lastOrderID != 0 {
log.Warn("!!!BITGET EXCHANGE API NOTICE!!! The order of response is in descending order, so the last order id not supported.") log.Warn("!!!BITGET EXCHANGE API NOTICE!!! The order of response is in descending order, so the last order id not supported.")
@ -420,8 +426,8 @@ func (e *Exchange) QueryClosedOrders(ctx context.Context, symbol string, since,
res, err := e.v2client.NewGetHistoryOrdersRequest(). res, err := e.v2client.NewGetHistoryOrdersRequest().
Symbol(symbol). Symbol(symbol).
Limit(strconv.Itoa(queryLimit)). Limit(strconv.Itoa(queryLimit)).
StartTime(since.UnixMilli()). StartTime(newSince).
EndTime(until.UnixMilli()). EndTime(until).
Do(ctx) Do(ctx)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to call get order histories error: %w", err) return nil, fmt.Errorf("failed to call get order histories error: %w", err)
@ -451,7 +457,7 @@ func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) (err
} }
for _, order := range orders { for _, order := range orders {
req := e.client.NewCancelOrderRequest() req := e.v2client.NewCancelOrderRequest()
reqId := "" reqId := ""
switch { switch {
@ -472,7 +478,7 @@ func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) (err
continue continue
} }
req.Symbol(order.Market.Symbol) req.Symbol(order.Symbol)
if err := cancelOrderRateLimiter.Wait(ctx); err != nil { if err := cancelOrderRateLimiter.Wait(ctx); err != nil {
errs = multierr.Append(errs, fmt.Errorf("cancel order rate limiter wait, order id: %s, error: %w", order.ClientOrderID, err)) errs = multierr.Append(errs, fmt.Errorf("cancel order rate limiter wait, order id: %s, error: %w", order.ClientOrderID, err))
@ -485,8 +491,8 @@ func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) (err
} }
// sanity check // sanity check
if res.OrderId != reqId && res.ClientOrderId != reqId { if res.OrderId.String() != reqId && res.ClientOrderId != reqId {
errs = multierr.Append(errs, fmt.Errorf("order id mismatch, exp: %s, respOrderId: %s, respClientOrderId: %s", reqId, res.OrderId, res.ClientOrderId)) errs = multierr.Append(errs, fmt.Errorf("order id mismatch, exp: %s, respOrderId: %d, respClientOrderId: %s", reqId, res.OrderId, res.ClientOrderId))
continue continue
} }
} }
@ -498,6 +504,7 @@ func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) (err
// using (`CreatedTime`) as the search criteria. // using (`CreatedTime`) as the search criteria.
// If you need to retrieve all data, please utilize the function pkg/exchange/batch.TradeBatchQuery. // If you need to retrieve all data, please utilize the function pkg/exchange/batch.TradeBatchQuery.
// //
// REMARK: If your start time is 90 days earlier, we will update it to now - 90 days.
// ** StartTime is inclusive, EndTime is exclusive. If you use the EndTime, the StartTime is required. ** // ** StartTime is inclusive, EndTime is exclusive. If you use the EndTime, the StartTime is required. **
// ** StartTime and EndTime cannot exceed 90 days. ** // ** StartTime and EndTime cannot exceed 90 days. **
func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *types.TradeQueryOptions) (trades []types.Trade, err error) { func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *types.TradeQueryOptions) (trades []types.Trade, err error) {
@ -508,24 +515,27 @@ func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *type
req := e.v2client.NewGetTradeFillsRequest() req := e.v2client.NewGetTradeFillsRequest()
req.Symbol(symbol) req.Symbol(symbol)
var newStartTime time.Time
if options.StartTime != nil { if options.StartTime != nil {
if time.Since(*options.StartTime) > queryMaxDuration { newStartTime = *options.StartTime
return nil, fmt.Errorf("start time from the last 90 days can be queried, got: %s", options.StartTime) if time.Since(newStartTime) > maxHistoricalDataQueryPeriod {
newStartTime = time.Now().Add(-maxHistoricalDataQueryPeriod)
log.Warnf("!!!BITGET EXCHANGE API NOTICE!!! The trade API cannot query data beyond 90 days from the current date, update %s -> %s", *options.StartTime, newStartTime)
} }
req.StartTime(options.StartTime.UnixMilli()) req.StartTime(newStartTime)
} }
if options.EndTime != nil { if options.EndTime != nil {
if options.StartTime == nil { if newStartTime.IsZero() {
return nil, errors.New("start time is required for query trades if you take end time") return nil, errors.New("start time is required for query trades if you take end time")
} }
if options.EndTime.Before(*options.StartTime) { if options.EndTime.Before(newStartTime) {
return nil, fmt.Errorf("end time %s before start %s", *options.EndTime, *options.StartTime) return nil, fmt.Errorf("end time %s before start %s", *options.EndTime, newStartTime)
} }
if options.EndTime.Sub(*options.StartTime) > queryMaxDuration { if options.EndTime.Sub(newStartTime) > maxHistoricalDataQueryPeriod {
return nil, fmt.Errorf("start time %s and end time %s cannot greater than 90 days", options.StartTime, options.EndTime) return nil, fmt.Errorf("start time %s and end time %s cannot greater than 90 days", newStartTime, options.EndTime)
} }
req.EndTime(options.EndTime.UnixMilli()) req.EndTime(*options.EndTime)
} }
limit := options.Limit limit := options.Limit

View File

@ -15,30 +15,6 @@ const DateFormat = "2006-01-02"
type ExchangeName string type ExchangeName string
func (n *ExchangeName) Value() (driver.Value, error) {
return n.String(), nil
}
func (n *ExchangeName) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
switch s {
case "max", "binance", "okex", "kucoin":
*n = ExchangeName(s)
return nil
}
return fmt.Errorf("unknown or unsupported exchange name: %s, valid names are: max, binance, okex, kucoin", s)
}
func (n ExchangeName) String() string {
return string(n)
}
const ( const (
ExchangeMax ExchangeName = "max" ExchangeMax ExchangeName = "max"
ExchangeBinance ExchangeName = "binance" ExchangeBinance ExchangeName = "binance"
@ -59,15 +35,43 @@ var SupportedExchanges = []ExchangeName{
// note: we are not using "backtest" // note: we are not using "backtest"
} }
func ValidExchangeName(a string) (ExchangeName, error) { func (n *ExchangeName) Value() (driver.Value, error) {
aa := strings.ToLower(a) return n.String(), nil
for _, n := range SupportedExchanges { }
if string(n) == aa {
return n, nil func (n *ExchangeName) UnmarshalJSON(data []byte) error {
} var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
} }
*n = ExchangeName(s)
if !n.IsValid() {
return fmt.Errorf("%s is an invalid exchange name", s)
}
return nil
}
func (n ExchangeName) IsValid() bool {
switch n {
case ExchangeBinance, ExchangeBitget, ExchangeBybit, ExchangeMax, ExchangeOKEx, ExchangeKucoin:
return true
}
return false
}
func (n ExchangeName) String() string {
return string(n)
}
func ValidExchangeName(a string) (ExchangeName, error) {
exName := ExchangeName(strings.ToLower(a))
if !exName.IsValid() {
return "", fmt.Errorf("invalid exchange name: %s", a) return "", fmt.Errorf("invalid exchange name: %s", a)
}
return exName, nil
} }
type ExchangeMinimal interface { type ExchangeMinimal interface {

View File

@ -41,3 +41,10 @@ func (s *StrInt64) UnmarshalJSON(body []byte) error {
return nil return nil
} }
func (s *StrInt64) String() string {
if s == nil {
return ""
}
return strconv.FormatInt(int64(*s), 10)
}