2023-05-17 05:43:21 +00:00
package bitget
import (
2023-08-09 07:20:33 +00:00
"context"
2023-11-09 02:35:53 +00:00
"errors"
2023-10-31 03:56:15 +00:00
"fmt"
2023-11-05 15:41:57 +00:00
"strconv"
2023-10-31 03:56:15 +00:00
"time"
2023-08-09 07:20:33 +00:00
2023-05-17 05:43:21 +00:00
"github.com/sirupsen/logrus"
2023-11-06 03:51:16 +00:00
"go.uber.org/multierr"
2023-10-31 03:56:15 +00:00
"golang.org/x/time/rate"
2023-05-17 05:43:21 +00:00
"github.com/c9s/bbgo/pkg/exchange/bitget/bitgetapi"
2023-11-05 15:41:57 +00:00
v2 "github.com/c9s/bbgo/pkg/exchange/bitget/bitgetapi/v2"
2023-05-17 05:43:21 +00:00
"github.com/c9s/bbgo/pkg/types"
)
2023-11-05 15:41:57 +00:00
const (
ID = "bitget"
2023-05-17 05:43:21 +00:00
2023-11-05 15:41:57 +00:00
PlatformToken = "BGB"
2023-11-17 04:24:04 +00:00
queryLimit = 100
defaultKLineLimit = 100
maxOrderIdLen = 36
maxHistoricalDataQueryPeriod = 90 * 24 * time . Hour
2023-11-05 15:41:57 +00:00
)
2023-05-17 05:43:21 +00:00
var log = logrus . WithFields ( logrus . Fields {
"exchange" : ID ,
} )
2023-10-31 03:56:15 +00:00
var (
// queryMarketRateLimiter has its own rate limit. https://bitgetlimited.github.io/apidoc/en/spot/#get-symbols
queryMarketRateLimiter = rate . NewLimiter ( rate . Every ( time . Second / 10 ) , 5 )
2023-12-12 08:37:43 +00:00
2023-11-01 03:46:43 +00:00
// queryAccountRateLimiter has its own rate limit. https://bitgetlimited.github.io/apidoc/en/spot/#get-account-assets
queryAccountRateLimiter = rate . NewLimiter ( rate . Every ( time . Second / 5 ) , 5 )
2023-12-12 08:37:43 +00:00
2023-11-01 03:46:43 +00:00
// queryTickerRateLimiter has its own rate limit. https://bitgetlimited.github.io/apidoc/en/spot/#get-single-ticker
queryTickerRateLimiter = rate . NewLimiter ( rate . Every ( time . Second / 10 ) , 5 )
2023-12-12 08:37:43 +00:00
2023-11-01 08:14:21 +00:00
// queryTickersRateLimiter has its own rate limit. https://bitgetlimited.github.io/apidoc/en/spot/#get-all-tickers
queryTickersRateLimiter = rate . NewLimiter ( rate . Every ( time . Second / 10 ) , 5 )
2023-12-12 08:37:43 +00:00
2023-11-05 15:41:57 +00:00
// queryOpenOrdersRateLimiter has its own rate limit. https://www.bitget.com/zh-CN/api-doc/spot/trade/Get-Unfilled-Orders
queryOpenOrdersRateLimiter = rate . NewLimiter ( rate . Every ( time . Second / 10 ) , 5 )
2023-12-12 08:37:43 +00:00
2023-11-06 03:51:16 +00:00
// closedQueryOrdersRateLimiter has its own rate limit. https://www.bitget.com/api-doc/spot/trade/Get-History-Orders
closedQueryOrdersRateLimiter = rate . NewLimiter ( rate . Every ( time . Second / 15 ) , 5 )
2023-12-12 08:37:43 +00:00
// submitOrderRateLimiter has its own rate limit. https://www.bitget.com/zh-CN/api-doc/spot/trade/Place-Order
submitOrderRateLimiter = rate . NewLimiter ( rate . Every ( time . Second / 5 ) , 5 )
2023-11-09 02:35:53 +00:00
// queryTradeRateLimiter has its own rate limit. https://www.bitget.com/zh-CN/api-doc/spot/trade/Get-Fills
queryTradeRateLimiter = rate . NewLimiter ( rate . Every ( time . Second / 5 ) , 5 )
2023-12-12 08:37:43 +00:00
2023-11-09 02:38:32 +00:00
// 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 )
2023-12-12 08:37:43 +00:00
2023-11-13 11:21:58 +00:00
// kLineRateLimiter has its own rate limit. https://www.bitget.com/api-doc/spot/market/Get-Candle-Data
2023-12-12 08:37:43 +00:00
kLineRateLimiter = rate . NewLimiter ( rate . Every ( time . Second / 10 ) , 5 )
2023-10-31 03:56:15 +00:00
)
2023-05-17 05:43:21 +00:00
type Exchange struct {
key , secret , passphrase string
2023-11-05 15:41:57 +00:00
client * bitgetapi . RestClient
2023-11-14 12:42:11 +00:00
v2client * v2 . Client
2023-05-17 05:43:21 +00:00
}
func New ( key , secret , passphrase string ) * Exchange {
client := bitgetapi . NewClient ( )
if len ( key ) > 0 && len ( secret ) > 0 {
client . Auth ( key , secret , passphrase )
}
return & Exchange {
key : key ,
secret : secret ,
passphrase : passphrase ,
client : client ,
2023-11-14 12:42:11 +00:00
v2client : v2 . NewClient ( client ) ,
2023-05-17 05:43:21 +00:00
}
}
func ( e * Exchange ) Name ( ) types . ExchangeName {
return types . ExchangeBitget
}
func ( e * Exchange ) PlatformFeeCurrency ( ) string {
return PlatformToken
}
2023-08-09 07:20:33 +00:00
func ( e * Exchange ) NewStream ( ) types . Stream {
2023-11-10 13:56:18 +00:00
return NewStream ( e . key , e . secret , e . passphrase )
2023-08-09 07:20:33 +00:00
}
func ( e * Exchange ) QueryMarkets ( ctx context . Context ) ( types . MarketMap , error ) {
2023-10-31 03:56:15 +00:00
if err := queryMarketRateLimiter . Wait ( ctx ) ; err != nil {
return nil , fmt . Errorf ( "markets rate limiter wait error: %w" , err )
}
2023-11-14 12:42:11 +00:00
req := e . v2client . NewGetSymbolsRequest ( )
2023-08-09 07:20:33 +00:00
symbols , err := req . Do ( ctx )
if err != nil {
return nil , err
}
markets := types . MarketMap { }
for _ , s := range symbols {
2024-01-08 09:46:09 +00:00
if s . Status == v2 . SymbolStatusOffline {
// ignore offline symbols
continue
}
2023-11-14 06:35:16 +00:00
markets [ s . Symbol ] = toGlobalMarket ( s )
2023-08-09 07:20:33 +00:00
}
return markets , nil
}
func ( e * Exchange ) QueryTicker ( ctx context . Context , symbol string ) ( * types . Ticker , error ) {
2023-11-01 03:46:43 +00:00
if err := queryTickerRateLimiter . Wait ( ctx ) ; err != nil {
return nil , fmt . Errorf ( "ticker rate limiter wait error: %w" , err )
}
2023-11-14 12:42:11 +00:00
req := e . v2client . NewGetTickersRequest ( )
2023-08-09 07:25:38 +00:00
req . Symbol ( symbol )
2023-11-01 08:14:21 +00:00
resp , err := req . Do ( ctx )
2023-08-09 07:25:38 +00:00
if err != nil {
2024-01-03 03:30:50 +00:00
return nil , fmt . Errorf ( "failed to query ticker, symbol: %s, err: %w" , symbol , err )
2023-08-09 07:25:38 +00:00
}
2023-11-14 06:57:07 +00:00
if len ( resp ) != 1 {
2024-01-03 03:30:50 +00:00
return nil , fmt . Errorf ( "unexpected length of query single symbol: %s, resp: %+v" , symbol , resp )
2023-11-14 06:57:07 +00:00
}
2023-08-09 07:25:38 +00:00
2023-11-16 05:33:42 +00:00
ticker := toGlobalTicker ( resp [ 0 ] )
2023-11-01 08:14:21 +00:00
return & ticker , nil
2023-08-09 07:20:33 +00:00
}
2023-11-01 08:14:21 +00:00
func ( e * Exchange ) QueryTickers ( ctx context . Context , symbols ... string ) ( map [ string ] types . Ticker , error ) {
tickers := map [ string ] types . Ticker { }
if len ( symbols ) > 0 {
for _ , s := range symbols {
t , err := e . QueryTicker ( ctx , s )
if err != nil {
return nil , err
}
tickers [ s ] = * t
}
return tickers , nil
}
if err := queryTickersRateLimiter . Wait ( ctx ) ; err != nil {
return nil , fmt . Errorf ( "tickers rate limiter wait error: %w" , err )
}
2023-11-14 12:42:11 +00:00
resp , err := e . v2client . NewGetTickersRequest ( ) . Do ( ctx )
2023-11-01 08:14:21 +00:00
if err != nil {
return nil , fmt . Errorf ( "failed to query tickers: %w" , err )
}
for _ , s := range resp {
tickers [ s . Symbol ] = toGlobalTicker ( s )
}
return tickers , nil
2023-08-09 07:20:33 +00:00
}
2023-11-13 11:21:58 +00:00
// 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.
2023-12-08 01:45:53 +00:00
func ( e * Exchange ) QueryKLines (
ctx context . Context , symbol string , interval types . Interval , options types . KLineQueryOptions ,
) ( [ ] types . KLine , error ) {
2023-11-14 12:42:11 +00:00
req := e . v2client . NewGetKLineRequest ( ) . Symbol ( symbol )
2023-11-13 11:21:58 +00:00
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 )
}
2023-12-12 08:37:43 +00:00
if err := kLineRateLimiter . Wait ( ctx ) ; err != nil {
2023-11-13 11:21:58 +00:00
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
2023-08-09 07:20:33 +00:00
}
func ( e * Exchange ) QueryAccount ( ctx context . Context ) ( * types . Account , error ) {
2023-11-01 03:46:43 +00:00
bals , err := e . QueryAccountBalances ( ctx )
if err != nil {
return nil , err
}
account := types . NewAccount ( )
account . UpdateBalances ( bals )
return account , nil
}
func ( e * Exchange ) QueryAccountBalances ( ctx context . Context ) ( types . BalanceMap , error ) {
if err := queryAccountRateLimiter . Wait ( ctx ) ; err != nil {
return nil , fmt . Errorf ( "account rate limiter wait error: %w" , err )
}
2024-01-03 03:25:46 +00:00
req := e . v2client . NewGetAccountAssetsRequest ( ) . AssetType ( v2 . AssetTypeHoldOnly )
2023-08-09 07:30:28 +00:00
resp , err := req . Do ( ctx )
if err != nil {
2023-11-01 03:46:43 +00:00
return nil , fmt . Errorf ( "failed to query account assets: %w" , err )
2023-08-09 07:30:28 +00:00
}
bals := types . BalanceMap { }
for _ , asset := range resp {
b := toGlobalBalance ( asset )
2024-01-03 03:25:46 +00:00
bals [ asset . Coin ] = b
2023-08-09 07:30:28 +00:00
}
2023-11-01 03:46:43 +00:00
return bals , nil
2023-08-09 07:20:33 +00:00
}
2023-11-06 03:51:16 +00:00
// SubmitOrder submits an order.
//
// Remark:
// 1. We support only GTC for time-in-force, because the response from queryOrder does not include time-in-force information.
// 2. For market buy orders, the size unit is quote currency, whereas the unit for order.Quantity is in base currency.
// Therefore, we need to calculate the equivalent quote currency amount based on the ticker data.
//
// Note that there is a bug in Bitget where you can place a market order with the 'post_only' option successfully,
// which should not be possible. The issue has been reported.
2023-08-09 07:20:33 +00:00
func ( e * Exchange ) SubmitOrder ( ctx context . Context , order types . SubmitOrder ) ( createdOrder * types . Order , err error ) {
2023-11-06 03:51:16 +00:00
if len ( order . Market . Symbol ) == 0 {
return nil , fmt . Errorf ( "order.Market.Symbol is required: %+v" , order )
}
2023-11-14 12:42:11 +00:00
req := e . v2client . NewPlaceOrderRequest ( )
2023-11-06 03:51:16 +00:00
req . Symbol ( order . Market . Symbol )
// set order type
orderType , err := toLocalOrderType ( order . Type )
if err != nil {
return nil , err
}
2023-12-08 01:45:53 +00:00
2023-11-06 03:51:16 +00:00
req . OrderType ( orderType )
// set side
side , err := toLocalSide ( order . Side )
if err != nil {
return nil , err
}
2023-12-08 01:45:53 +00:00
2023-11-06 03:51:16 +00:00
req . Side ( side )
// set quantity
qty := order . Quantity
// if the order is market buy, the quantity is quote coin, instead of base coin. so we need to convert it.
if order . Type == types . OrderTypeMarket && order . Side == types . SideTypeBuy {
ticker , err := e . QueryTicker ( ctx , order . Market . Symbol )
if err != nil {
return nil , err
}
qty = order . Quantity . Mul ( ticker . Buy )
}
2023-12-08 01:45:53 +00:00
2023-11-06 03:51:16 +00:00
req . Size ( order . Market . FormatQuantity ( qty ) )
2023-12-08 01:45:53 +00:00
// set TimeInForce
// we only support GTC/PostOnly, because:
// 1. we only support SPOT trading.
2023-11-17 04:24:04 +00:00
// 2. The query open/closed order does not include the `force` in SPOT.
2023-11-06 03:51:16 +00:00
// 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'.
2023-11-17 04:24:04 +00:00
if len ( order . TimeInForce ) != 0 && order . TimeInForce != types . TimeInForceGTC {
2023-11-06 03:51:16 +00:00
return nil , fmt . Errorf ( "time-in-force %s not supported" , order . TimeInForce )
}
2023-12-08 01:45:53 +00:00
switch order . Type {
case types . OrderTypeLimitMaker :
req . Force ( v2 . OrderForcePostOnly )
default :
req . Force ( v2 . OrderForceGTC )
}
2023-11-06 03:51:16 +00:00
// set price
2023-12-08 01:45:53 +00:00
switch order . Type {
case types . OrderTypeLimit , types . OrderTypeLimitMaker :
2023-11-06 03:51:16 +00:00
req . Price ( order . Market . FormatPrice ( order . Price ) )
}
// set client order id
if len ( order . ClientOrderID ) > maxOrderIdLen {
return nil , fmt . Errorf ( "unexpected length of order id, got: %d" , len ( order . ClientOrderID ) )
}
2023-12-08 01:45:53 +00:00
2023-11-06 03:51:16 +00:00
if len ( order . ClientOrderID ) > 0 {
req . ClientOrderId ( order . ClientOrderID )
}
2023-12-12 08:37:43 +00:00
if err := submitOrderRateLimiter . Wait ( ctx ) ; err != nil {
2023-11-06 03:51:16 +00:00
return nil , fmt . Errorf ( "place order rate limiter wait error: %w" , err )
}
2023-12-12 08:37:43 +00:00
2023-11-06 03:51:16 +00:00
res , err := req . Do ( ctx )
if err != nil {
return nil , fmt . Errorf ( "failed to place order, order: %#v, err: %w" , order , err )
}
2023-12-12 08:37:43 +00:00
debugf ( "order created: %+v" , res )
2023-11-06 03:51:16 +00:00
if len ( res . OrderId ) == 0 || ( len ( order . ClientOrderID ) != 0 && res . ClientOrderId != order . ClientOrderID ) {
return nil , fmt . Errorf ( "unexpected order id, resp: %#v, order: %#v" , res , order )
}
orderId := res . OrderId
2023-12-12 08:37:43 +00:00
debugf ( "fetching unfilled order info for order #%s" , orderId )
2023-11-14 12:42:11 +00:00
ordersResp , err := e . v2client . NewGetUnfilledOrdersRequest ( ) . OrderId ( orderId ) . Do ( ctx )
2023-11-06 03:51:16 +00:00
if err != nil {
return nil , fmt . Errorf ( "failed to query open order by order id: %s, err: %w" , orderId , err )
}
2024-02-26 03:40:13 +00:00
debugf ( "unfilled order response for order#%s: %+v" , orderId , ordersResp )
2023-12-12 08:37:43 +00:00
if len ( ordersResp ) == 1 {
return unfilledOrderToGlobalOrder ( ordersResp [ 0 ] )
} else if len ( ordersResp ) == 0 {
2023-11-06 03:51:16 +00:00
// The market order will be executed immediately, so we cannot retrieve it through the NewGetUnfilledOrdersRequest API.
// Try to get the order from the NewGetHistoryOrdersRequest API.
2023-11-14 12:42:11 +00:00
ordersResp , err := e . v2client . NewGetHistoryOrdersRequest ( ) . OrderId ( orderId ) . Do ( ctx )
2023-11-06 03:51:16 +00:00
if err != nil {
return nil , fmt . Errorf ( "failed to query history order by order id: %s, err: %w" , orderId , err )
}
if len ( ordersResp ) != 1 {
2024-02-26 03:40:13 +00:00
return nil , fmt . Errorf ( "unexpected length of history orders, expecting: 1, given: %d, ids: %s" , len ( ordersResp ) , orderId )
2023-11-06 03:51:16 +00:00
}
return toGlobalOrder ( ordersResp [ 0 ] )
}
2023-12-12 08:37:43 +00:00
2024-02-26 03:40:13 +00:00
return nil , fmt . Errorf ( "unexpected length of unfilled orders, expecting: 1, given: %d, ids: %s" , len ( ordersResp ) , orderId )
2023-08-09 07:20:33 +00:00
}
func ( e * Exchange ) QueryOpenOrders ( ctx context . Context , symbol string ) ( orders [ ] types . Order , err error ) {
2023-11-05 15:41:57 +00:00
var nextCursor types . StrInt64
for {
if err := queryOpenOrdersRateLimiter . Wait ( ctx ) ; err != nil {
return nil , fmt . Errorf ( "open order rate limiter wait error: %w" , err )
}
2023-11-14 12:42:11 +00:00
req := e . v2client . NewGetUnfilledOrdersRequest ( ) .
2023-11-05 15:41:57 +00:00
Symbol ( symbol ) .
2023-11-06 03:51:16 +00:00
Limit ( strconv . FormatInt ( queryLimit , 10 ) )
2023-11-05 15:41:57 +00:00
if nextCursor != 0 {
req . IdLessThan ( strconv . FormatInt ( int64 ( nextCursor ) , 10 ) )
}
openOrders , err := req . Do ( ctx )
if err != nil {
return nil , fmt . Errorf ( "failed to query open orders: %w" , err )
}
for _ , o := range openOrders {
order , err := unfilledOrderToGlobalOrder ( o )
if err != nil {
return nil , fmt . Errorf ( "failed to convert order, err: %v" , err )
}
orders = append ( orders , * order )
}
2023-11-08 14:08:21 +00:00
orderLen := len ( openOrders )
// a defensive programming to ensure the length of order response is expected.
2023-11-06 03:51:16 +00:00
if orderLen > queryLimit {
2023-11-08 14:08:21 +00:00
return nil , fmt . Errorf ( "unexpected open orders length %d" , orderLen )
}
2023-11-06 03:51:16 +00:00
if orderLen < queryLimit {
2023-11-05 15:41:57 +00:00
break
}
2023-11-08 14:08:21 +00:00
nextCursor = openOrders [ orderLen - 1 ] . OrderId
2023-11-05 15:41:57 +00:00
}
return orders , nil
2023-08-09 07:20:33 +00:00
}
2023-11-15 02:46:18 +00:00
// QueryClosedOrders queries closed order by time range(`CreatedTime`) and id. The order of the response is in descending order.
2023-11-06 03:51:16 +00:00
// If you need to retrieve all data, please utilize the function pkg/exchange/batch.ClosedOrderBatchQuery.
//
2023-11-15 14:18:09 +00:00
// REMARK: If your start time is 90 days earlier, we will update it to now - 90 days.
2023-11-06 03:51:16 +00:00
// ** 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. **
2023-11-15 14:18:09 +00:00
// ** Since from the last 90 days can be queried **
2023-12-08 01:45:53 +00:00
func ( e * Exchange ) QueryClosedOrders (
ctx context . Context , symbol string , since , until time . Time , lastOrderID uint64 ,
) ( orders [ ] types . Order , err error ) {
2023-11-15 14:18:09 +00:00
newSince := since
2023-11-17 04:24:04 +00:00
now := time . Now ( )
2023-11-15 14:18:09 +00:00
2023-11-17 04:24:04 +00:00
if time . Since ( newSince ) > maxHistoricalDataQueryPeriod {
newSince = now . Add ( - maxHistoricalDataQueryPeriod )
2023-11-15 14:18:09 +00:00
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 )
2023-11-06 03:51:16 +00:00
}
2023-11-15 14:18:09 +00:00
if until . Before ( newSince ) {
2023-11-17 04:24:04 +00:00
log . Warnf ( "!!!BITGET EXCHANGE API NOTICE!!! The 'until' comes before 'since', update until to now(%s -> %s)." , until , now )
until = now
2023-11-06 03:51:16 +00:00
}
2023-11-17 04:24:04 +00:00
if until . Sub ( newSince ) > maxHistoricalDataQueryPeriod {
2023-11-15 14:18:09 +00:00
return nil , fmt . Errorf ( "the start time %s and end time %s cannot exceed 90 days" , newSince , until )
2023-11-06 03:51:16 +00:00
}
if lastOrderID != 0 {
log . Warn ( "!!!BITGET EXCHANGE API NOTICE!!! The order of response is in descending order, so the last order id not supported." )
}
if err := closedQueryOrdersRateLimiter . Wait ( ctx ) ; err != nil {
return nil , fmt . Errorf ( "query closed order rate limiter wait error: %w" , err )
}
2023-11-14 12:42:11 +00:00
res , err := e . v2client . NewGetHistoryOrdersRequest ( ) .
2023-11-06 03:51:16 +00:00
Symbol ( symbol ) .
Limit ( strconv . Itoa ( queryLimit ) ) .
2023-11-15 14:18:09 +00:00
StartTime ( newSince ) .
2023-11-15 14:20:26 +00:00
EndTime ( until ) .
2023-11-06 03:51:16 +00:00
Do ( ctx )
if err != nil {
return nil , fmt . Errorf ( "failed to call get order histories error: %w" , err )
}
for _ , order := range res {
o , err2 := toGlobalOrder ( order )
if err2 != nil {
err = multierr . Append ( err , err2 )
continue
}
if o . Status . Closed ( ) {
orders = append ( orders , * o )
}
}
if err != nil {
return nil , err
}
return types . SortOrdersAscending ( orders ) , nil
}
2023-11-09 02:38:32 +00:00
func ( e * Exchange ) CancelOrders ( ctx context . Context , orders ... types . Order ) ( errs error ) {
if len ( orders ) == 0 {
return nil
}
for _ , order := range orders {
2023-11-15 14:18:09 +00:00
req := e . v2client . NewCancelOrderRequest ( )
2023-11-09 02:38:32 +00:00
reqId := ""
switch {
// use the OrderID first, then the ClientOrderID
case order . OrderID > 0 :
req . OrderId ( strconv . FormatUint ( order . OrderID , 10 ) )
reqId = strconv . FormatUint ( order . OrderID , 10 )
case len ( order . ClientOrderID ) != 0 :
req . ClientOrderId ( order . ClientOrderID )
reqId = order . ClientOrderID
default :
errs = multierr . Append (
errs ,
fmt . Errorf ( "the order uuid and client order id are empty, order: %#v" , order ) ,
)
continue
}
2023-11-15 14:18:09 +00:00
req . Symbol ( order . Symbol )
2023-11-09 02:38:32 +00:00
if err := cancelOrderRateLimiter . Wait ( ctx ) ; err != nil {
2024-03-04 14:40:25 +00:00
errs = multierr . Append ( errs , fmt . Errorf ( "cancel order rate limiter wait, orderId: %d, clientOrderId: %s, error: %w" , order . OrderID , order . ClientOrderID , err ) )
2023-11-09 02:38:32 +00:00
continue
}
res , err := req . Do ( ctx )
if err != nil {
2024-03-04 14:40:25 +00:00
errs = multierr . Append ( errs , fmt . Errorf ( "failed to cancel orderId: %d, clientOrderId: %s, err: %w" , order . OrderID , order . ClientOrderID , err ) )
2023-11-09 02:38:32 +00:00
continue
}
// sanity check
2023-11-15 14:18:09 +00:00
if res . OrderId . String ( ) != reqId && res . ClientOrderId != reqId {
errs = multierr . Append ( errs , fmt . Errorf ( "order id mismatch, exp: %s, respOrderId: %d, respClientOrderId: %s" , reqId , res . OrderId , res . ClientOrderId ) )
2023-11-09 02:38:32 +00:00
continue
}
}
return errs
2023-08-09 07:20:33 +00:00
}
2023-11-09 02:35:53 +00:00
// QueryTrades queries fill trades. The trade of the response is in descending order. The time-based query are typically
2023-11-15 02:46:18 +00:00
// using (`CreatedTime`) as the search criteria.
2023-11-09 02:35:53 +00:00
// If you need to retrieve all data, please utilize the function pkg/exchange/batch.TradeBatchQuery.
//
2023-11-15 14:18:09 +00:00
// REMARK: If your start time is 90 days earlier, we will update it to now - 90 days.
2023-11-09 02:35:53 +00:00
// ** StartTime is inclusive, EndTime is exclusive. If you use the EndTime, the StartTime is required. **
// ** StartTime and EndTime cannot exceed 90 days. **
2023-12-08 01:45:53 +00:00
func ( e * Exchange ) QueryTrades (
ctx context . Context , symbol string , options * types . TradeQueryOptions ,
) ( trades [ ] types . Trade , err error ) {
2023-11-09 02:35:53 +00:00
if options . LastTradeID != 0 {
log . Warn ( "!!!BITGET EXCHANGE API NOTICE!!! The trade of response is in descending order, so the last trade id not supported." )
}
2023-11-14 12:42:11 +00:00
req := e . v2client . NewGetTradeFillsRequest ( )
2023-11-09 02:35:53 +00:00
req . Symbol ( symbol )
2023-11-15 14:18:09 +00:00
var newStartTime time . Time
2023-11-09 02:35:53 +00:00
if options . StartTime != nil {
2023-11-15 14:18:09 +00:00
newStartTime = * options . StartTime
2023-11-17 04:24:04 +00:00
if time . Since ( newStartTime ) > maxHistoricalDataQueryPeriod {
newStartTime = time . Now ( ) . Add ( - maxHistoricalDataQueryPeriod )
2023-11-15 14:18:09 +00:00
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 )
2023-11-09 02:35:53 +00:00
}
2023-11-15 14:18:09 +00:00
req . StartTime ( newStartTime )
2023-11-09 02:35:53 +00:00
}
if options . EndTime != nil {
2023-11-15 14:18:09 +00:00
if newStartTime . IsZero ( ) {
2023-11-09 02:35:53 +00:00
return nil , errors . New ( "start time is required for query trades if you take end time" )
}
2023-11-15 14:18:09 +00:00
if options . EndTime . Before ( newStartTime ) {
return nil , fmt . Errorf ( "end time %s before start %s" , * options . EndTime , newStartTime )
2023-11-09 02:35:53 +00:00
}
2023-11-17 04:24:04 +00:00
if options . EndTime . Sub ( newStartTime ) > maxHistoricalDataQueryPeriod {
2023-11-15 14:18:09 +00:00
return nil , fmt . Errorf ( "start time %s and end time %s cannot greater than 90 days" , newStartTime , options . EndTime )
2023-11-09 02:35:53 +00:00
}
2023-11-15 14:20:26 +00:00
req . EndTime ( * options . EndTime )
2023-11-09 02:35:53 +00:00
}
limit := options . Limit
if limit > queryLimit || limit <= 0 {
log . Debugf ( "limtit is exceeded or zero, update to %d, got: %d" , queryLimit , options . Limit )
limit = queryLimit
}
req . Limit ( strconv . FormatInt ( limit , 10 ) )
if err := queryTradeRateLimiter . Wait ( ctx ) ; err != nil {
return nil , fmt . Errorf ( "trade rate limiter wait error: %w" , err )
}
response , err := req . Do ( ctx )
if err != nil {
return nil , fmt . Errorf ( "failed to query trades, err: %w" , err )
}
var errs error
for _ , trade := range response {
res , err := toGlobalTrade ( trade )
if err != nil {
errs = multierr . Append ( errs , err )
continue
}
trades = append ( trades , * res )
}
if errs != nil {
return nil , errs
}
return trades , nil
}