mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-22 14:55:16 +00:00
Merge pull request #1394 from c9s/edwin/bitget/ClosedOpenOrders
FEATURE: [bitget] support query closed orders
This commit is contained in:
commit
66caf78556
|
@ -2,12 +2,11 @@ package bitgetapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/exchange/bitget/bitgetapi"
|
"github.com/c9s/bbgo/pkg/exchange/bitget/bitgetapi"
|
||||||
"github.com/c9s/bbgo/pkg/testutil"
|
"github.com/c9s/bbgo/pkg/testutil"
|
||||||
)
|
)
|
||||||
|
@ -25,7 +24,6 @@ func getTestClientOrSkip(t *testing.T) *Client {
|
||||||
|
|
||||||
client := bitgetapi.NewClient()
|
client := bitgetapi.NewClient()
|
||||||
client.Auth(key, secret, os.Getenv("BITGET_API_PASSPHRASE"))
|
client.Auth(key, secret, os.Getenv("BITGET_API_PASSPHRASE"))
|
||||||
|
|
||||||
return NewClient(client)
|
return NewClient(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,4 +37,12 @@ func TestClient(t *testing.T) {
|
||||||
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) {
|
||||||
|
// market buy
|
||||||
|
req, err := client.NewGetHistoryOrdersRequest().Symbol("APEUSDT").Do(ctx)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
t.Logf("place order resp: %+v", req)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
102
pkg/exchange/bitget/bitgetapi/v2/get_history_orders_request.go
Normal file
102
pkg/exchange/bitget/bitgetapi/v2/get_history_orders_request.go
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
package bitgetapi
|
||||||
|
|
||||||
|
//go:generate -command GetRequest requestgen -method GET -responseType .APIResponse -responseDataField Data
|
||||||
|
//go:generate -command PostRequest requestgen -method POST -responseType .APIResponse -responseDataField Data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
|
"github.com/c9s/requestgen"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FeeDetail struct {
|
||||||
|
// NewFees should have a value because when I was integrating, it already prompted,
|
||||||
|
// "If there is no 'newFees' field, this data represents earlier historical data."
|
||||||
|
NewFees struct {
|
||||||
|
// Amount deducted by coupons, unit:currency obtained from the transaction.
|
||||||
|
DeductedByCoupon fixedpoint.Value `json:"c"`
|
||||||
|
// Amount deducted in BGB (Bitget Coin), unit:BGB
|
||||||
|
DeductedInBGB fixedpoint.Value `json:"d"`
|
||||||
|
// If the BGB balance is insufficient to cover the fees, the remaining amount is deducted from the
|
||||||
|
//currency obtained from the transaction.
|
||||||
|
DeductedFromCurrency fixedpoint.Value `json:"r"`
|
||||||
|
// The total fee amount to be paid, unit :currency obtained from the transaction.
|
||||||
|
ToBePaid fixedpoint.Value `json:"t"`
|
||||||
|
// ignored
|
||||||
|
Deduction bool `json:"deduction"`
|
||||||
|
// ignored
|
||||||
|
TotalDeductionFee fixedpoint.Value `json:"totalDeductionFee"`
|
||||||
|
} `json:"newFees"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderDetail struct {
|
||||||
|
UserId types.StrInt64 `json:"userId"`
|
||||||
|
Symbol string `json:"symbol"`
|
||||||
|
// OrderId are always numeric. It's confirmed with official customer service. https://t.me/bitgetOpenapi/24172
|
||||||
|
OrderId types.StrInt64 `json:"orderId"`
|
||||||
|
ClientOrderId string `json:"clientOid"`
|
||||||
|
Price fixedpoint.Value `json:"price"`
|
||||||
|
// Size is base coin when orderType=limit; quote coin when orderType=market
|
||||||
|
Size fixedpoint.Value `json:"size"`
|
||||||
|
OrderType OrderType `json:"orderType"`
|
||||||
|
Side SideType `json:"side"`
|
||||||
|
Status OrderStatus `json:"status"`
|
||||||
|
PriceAvg fixedpoint.Value `json:"priceAvg"`
|
||||||
|
BaseVolume fixedpoint.Value `json:"baseVolume"`
|
||||||
|
QuoteVolume fixedpoint.Value `json:"quoteVolume"`
|
||||||
|
EnterPointSource string `json:"enterPointSource"`
|
||||||
|
// The value is json string, so we unmarshal it after unmarshal OrderDetail
|
||||||
|
FeeDetailRaw string `json:"feeDetail"`
|
||||||
|
OrderSource string `json:"orderSource"`
|
||||||
|
CTime types.MillisecondTimestamp `json:"cTime"`
|
||||||
|
UTime types.MillisecondTimestamp `json:"uTime"`
|
||||||
|
|
||||||
|
FeeDetail FeeDetail
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OrderDetail) UnmarshalJSON(data []byte) error {
|
||||||
|
if o == nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal json from nil pointer order detail")
|
||||||
|
}
|
||||||
|
// define new type to avoid loop reference
|
||||||
|
type AuxOrderDetail OrderDetail
|
||||||
|
|
||||||
|
var aux AuxOrderDetail
|
||||||
|
if err := json.Unmarshal(data, &aux); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*o = OrderDetail(aux)
|
||||||
|
|
||||||
|
if len(aux.FeeDetailRaw) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var feeDetail FeeDetail
|
||||||
|
if err := json.Unmarshal([]byte(aux.FeeDetailRaw), &feeDetail); err != nil {
|
||||||
|
return fmt.Errorf("unexpected fee detail raw: %s, err: %w", aux.FeeDetailRaw, err)
|
||||||
|
}
|
||||||
|
o.FeeDetail = feeDetail
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate GetRequest -url "/api/v2/spot/trade/history-orders" -type GetHistoryOrdersRequest -responseDataType []OrderDetail
|
||||||
|
type GetHistoryOrdersRequest struct {
|
||||||
|
client requestgen.AuthenticatedAPIClient
|
||||||
|
|
||||||
|
symbol *string `param:"symbol,query"`
|
||||||
|
// Limit number default 100 max 100
|
||||||
|
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 *string `param:"idLessThan,query"`
|
||||||
|
startTime *int64 `param:"startTime,query"`
|
||||||
|
endTime *int64 `param:"endTime,query"`
|
||||||
|
orderId *string `param:"orderId,query"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) NewGetHistoryOrdersRequest() *GetHistoryOrdersRequest {
|
||||||
|
return &GetHistoryOrdersRequest{client: c.Client}
|
||||||
|
}
|
|
@ -0,0 +1,222 @@
|
||||||
|
// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Data -url /api/v2/spot/trade/history-orders -type GetHistoryOrdersRequest -responseDataType []OrderDetail"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package bitgetapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/c9s/bbgo/pkg/exchange/bitget/bitgetapi"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (g *GetHistoryOrdersRequest) Symbol(symbol string) *GetHistoryOrdersRequest {
|
||||||
|
g.symbol = &symbol
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GetHistoryOrdersRequest) Limit(limit string) *GetHistoryOrdersRequest {
|
||||||
|
g.limit = &limit
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GetHistoryOrdersRequest) IdLessThan(idLessThan string) *GetHistoryOrdersRequest {
|
||||||
|
g.idLessThan = &idLessThan
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GetHistoryOrdersRequest) StartTime(startTime int64) *GetHistoryOrdersRequest {
|
||||||
|
g.startTime = &startTime
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GetHistoryOrdersRequest) EndTime(endTime int64) *GetHistoryOrdersRequest {
|
||||||
|
g.endTime = &endTime
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GetHistoryOrdersRequest) OrderId(orderId string) *GetHistoryOrdersRequest {
|
||||||
|
g.orderId = &orderId
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetQueryParameters builds and checks the query parameters and returns url.Values
|
||||||
|
func (g *GetHistoryOrdersRequest) GetQueryParameters() (url.Values, error) {
|
||||||
|
var params = map[string]interface{}{}
|
||||||
|
// check symbol field -> json key symbol
|
||||||
|
if g.symbol != nil {
|
||||||
|
symbol := *g.symbol
|
||||||
|
|
||||||
|
// assign parameter of symbol
|
||||||
|
params["symbol"] = symbol
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
// check limit field -> json key limit
|
||||||
|
if g.limit != nil {
|
||||||
|
limit := *g.limit
|
||||||
|
|
||||||
|
// assign parameter of limit
|
||||||
|
params["limit"] = limit
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
// check idLessThan field -> json key idLessThan
|
||||||
|
if g.idLessThan != nil {
|
||||||
|
idLessThan := *g.idLessThan
|
||||||
|
|
||||||
|
// assign parameter of idLessThan
|
||||||
|
params["idLessThan"] = idLessThan
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
// check startTime field -> json key startTime
|
||||||
|
if g.startTime != nil {
|
||||||
|
startTime := *g.startTime
|
||||||
|
|
||||||
|
// assign parameter of startTime
|
||||||
|
params["startTime"] = startTime
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
// check endTime field -> json key endTime
|
||||||
|
if g.endTime != nil {
|
||||||
|
endTime := *g.endTime
|
||||||
|
|
||||||
|
// assign parameter of endTime
|
||||||
|
params["endTime"] = endTime
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
// check orderId field -> json key orderId
|
||||||
|
if g.orderId != nil {
|
||||||
|
orderId := *g.orderId
|
||||||
|
|
||||||
|
// assign parameter of orderId
|
||||||
|
params["orderId"] = orderId
|
||||||
|
} 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 *GetHistoryOrdersRequest) 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 *GetHistoryOrdersRequest) 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 *GetHistoryOrdersRequest) 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 *GetHistoryOrdersRequest) GetSlugParameters() (map[string]interface{}, error) {
|
||||||
|
var params = map[string]interface{}{}
|
||||||
|
|
||||||
|
return params, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GetHistoryOrdersRequest) 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 *GetHistoryOrdersRequest) 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 *GetHistoryOrdersRequest) isVarSlice(_v interface{}) bool {
|
||||||
|
rt := reflect.TypeOf(_v)
|
||||||
|
switch rt.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GetHistoryOrdersRequest) 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GetHistoryOrdersRequest) Do(ctx context.Context) ([]OrderDetail, error) {
|
||||||
|
|
||||||
|
// no body params
|
||||||
|
var params interface{}
|
||||||
|
query, err := g.GetQueryParameters()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
apiURL := "/api/v2/spot/trade/history-orders"
|
||||||
|
|
||||||
|
req, err := g.client.NewAuthenticatedRequest(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
|
||||||
|
}
|
||||||
|
|
||||||
|
var data []OrderDetail
|
||||||
|
if err := json.Unmarshal(apiResponse.Data, &data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
package bitgetapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOrderDetail_UnmarshalJSON(t *testing.T) {
|
||||||
|
var (
|
||||||
|
assert = assert.New(t)
|
||||||
|
)
|
||||||
|
t.Run("empty fee", func(t *testing.T) {
|
||||||
|
input := `{
|
||||||
|
"userId":"8672173294",
|
||||||
|
"symbol":"APEUSDT",
|
||||||
|
"orderId":"1104342023170068480",
|
||||||
|
"clientOid":"f3d6a1ee-4e94-48b5-a6e0-25f3e93d92e1",
|
||||||
|
"price":"1.2000000000000000",
|
||||||
|
"size":"5.0000000000000000",
|
||||||
|
"orderType":"limit",
|
||||||
|
"side":"buy",
|
||||||
|
"status":"cancelled",
|
||||||
|
"priceAvg":"0",
|
||||||
|
"baseVolume":"0.0000000000000000",
|
||||||
|
"quoteVolume":"0.0000000000000000",
|
||||||
|
"enterPointSource":"API",
|
||||||
|
"feeDetail":"",
|
||||||
|
"orderSource":"normal",
|
||||||
|
"cTime":"1699021576683",
|
||||||
|
"uTime":"1699021649099"
|
||||||
|
}`
|
||||||
|
var od OrderDetail
|
||||||
|
err := json.Unmarshal([]byte(input), &od)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(OrderDetail{
|
||||||
|
UserId: types.StrInt64(8672173294),
|
||||||
|
Symbol: "APEUSDT",
|
||||||
|
OrderId: types.StrInt64(1104342023170068480),
|
||||||
|
ClientOrderId: "f3d6a1ee-4e94-48b5-a6e0-25f3e93d92e1",
|
||||||
|
Price: fixedpoint.NewFromFloat(1.2),
|
||||||
|
Size: fixedpoint.NewFromFloat(5),
|
||||||
|
OrderType: OrderTypeLimit,
|
||||||
|
Side: SideTypeBuy,
|
||||||
|
Status: OrderStatusCancelled,
|
||||||
|
PriceAvg: fixedpoint.Zero,
|
||||||
|
BaseVolume: fixedpoint.Zero,
|
||||||
|
QuoteVolume: fixedpoint.Zero,
|
||||||
|
EnterPointSource: "API",
|
||||||
|
FeeDetailRaw: "",
|
||||||
|
OrderSource: "normal",
|
||||||
|
CTime: types.NewMillisecondTimestampFromInt(1699021576683),
|
||||||
|
UTime: types.NewMillisecondTimestampFromInt(1699021649099),
|
||||||
|
FeeDetail: FeeDetail{},
|
||||||
|
}, od)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("fee", func(t *testing.T) {
|
||||||
|
input := `{
|
||||||
|
"userId":"8672173294",
|
||||||
|
"symbol":"APEUSDT",
|
||||||
|
"orderId":"1104337778433757184",
|
||||||
|
"clientOid":"8afea7bd-d873-44fe-aff8-6a1fae3cc765",
|
||||||
|
"price":"1.4000000000000000",
|
||||||
|
"size":"5.0000000000000000",
|
||||||
|
"orderType":"limit",
|
||||||
|
"side":"sell",
|
||||||
|
"status":"filled",
|
||||||
|
"priceAvg":"1.4001000000000000",
|
||||||
|
"baseVolume":"5.0000000000000000",
|
||||||
|
"quoteVolume":"7.0005000000000000",
|
||||||
|
"enterPointSource":"API",
|
||||||
|
"feeDetail":"{\"newFees\":{\"c\":0,\"d\":0,\"deduction\":false,\"r\":-0.0070005,\"t\":-0.0070005,\"totalDeductionFee\":0},\"USDT\":{\"deduction\":false,\"feeCoinCode\":\"USDT\",\"totalDeductionFee\":0,\"totalFee\":-0.007000500000}}",
|
||||||
|
"orderSource":"normal",
|
||||||
|
"cTime":"1699020564659",
|
||||||
|
"uTime":"1699020564688"
|
||||||
|
}`
|
||||||
|
var od OrderDetail
|
||||||
|
err := json.Unmarshal([]byte(input), &od)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(OrderDetail{
|
||||||
|
UserId: types.StrInt64(8672173294),
|
||||||
|
Symbol: "APEUSDT",
|
||||||
|
OrderId: types.StrInt64(1104337778433757184),
|
||||||
|
ClientOrderId: "8afea7bd-d873-44fe-aff8-6a1fae3cc765",
|
||||||
|
Price: fixedpoint.NewFromFloat(1.4),
|
||||||
|
Size: fixedpoint.NewFromFloat(5),
|
||||||
|
OrderType: OrderTypeLimit,
|
||||||
|
Side: SideTypeSell,
|
||||||
|
Status: OrderStatusFilled,
|
||||||
|
PriceAvg: fixedpoint.NewFromFloat(1.4001),
|
||||||
|
BaseVolume: fixedpoint.NewFromFloat(5),
|
||||||
|
QuoteVolume: fixedpoint.NewFromFloat(7.0005),
|
||||||
|
EnterPointSource: "API",
|
||||||
|
FeeDetailRaw: `{"newFees":{"c":0,"d":0,"deduction":false,"r":-0.0070005,"t":-0.0070005,"totalDeductionFee":0},"USDT":{"deduction":false,"feeCoinCode":"USDT","totalDeductionFee":0,"totalFee":-0.007000500000}}`,
|
||||||
|
OrderSource: "normal",
|
||||||
|
CTime: types.NewMillisecondTimestampFromInt(1699020564659),
|
||||||
|
UTime: types.NewMillisecondTimestampFromInt(1699020564688),
|
||||||
|
FeeDetail: FeeDetail{
|
||||||
|
NewFees: struct {
|
||||||
|
DeductedByCoupon fixedpoint.Value `json:"c"`
|
||||||
|
DeductedInBGB fixedpoint.Value `json:"d"`
|
||||||
|
DeductedFromCurrency fixedpoint.Value `json:"r"`
|
||||||
|
ToBePaid fixedpoint.Value `json:"t"`
|
||||||
|
Deduction bool `json:"deduction"`
|
||||||
|
TotalDeductionFee fixedpoint.Value `json:"totalDeductionFee"`
|
||||||
|
}{DeductedByCoupon: fixedpoint.NewFromFloat(0),
|
||||||
|
DeductedInBGB: fixedpoint.NewFromFloat(0),
|
||||||
|
DeductedFromCurrency: fixedpoint.NewFromFloat(-0.0070005),
|
||||||
|
ToBePaid: fixedpoint.NewFromFloat(-0.0070005),
|
||||||
|
Deduction: false,
|
||||||
|
TotalDeductionFee: fixedpoint.Zero,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, od)
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package bitget
|
package bitget
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -158,3 +159,87 @@ func unfilledOrderToGlobalOrder(order v2.UnfilledOrder) (*types.Order, error) {
|
||||||
UpdateTime: types.Time(order.UTime.Time()),
|
UpdateTime: types.Time(order.UTime.Time()),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toGlobalOrder(order v2.OrderDetail) (*types.Order, error) {
|
||||||
|
side, err := toGlobalSideType(order.Side)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
orderType, err := toGlobalOrderType(order.OrderType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
status, err := toGlobalOrderStatus(order.Status)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
qty := order.Size
|
||||||
|
price := order.Price
|
||||||
|
|
||||||
|
if orderType == types.OrderTypeMarket {
|
||||||
|
price = order.PriceAvg
|
||||||
|
if side == types.SideTypeBuy {
|
||||||
|
qty, err = processMarketBuyQuantity(order.BaseVolume, order.QuoteVolume, order.PriceAvg, order.Size, order.Status)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.Order{
|
||||||
|
SubmitOrder: types.SubmitOrder{
|
||||||
|
ClientOrderID: order.ClientOrderId,
|
||||||
|
Symbol: order.Symbol,
|
||||||
|
Side: side,
|
||||||
|
Type: orderType,
|
||||||
|
Quantity: qty,
|
||||||
|
Price: price,
|
||||||
|
// Bitget does not include the "time-in-force" field in its API response for spot trading, so we set GTC.
|
||||||
|
TimeInForce: types.TimeInForceGTC,
|
||||||
|
},
|
||||||
|
Exchange: types.ExchangeBitget,
|
||||||
|
OrderID: uint64(order.OrderId),
|
||||||
|
UUID: strconv.FormatInt(int64(order.OrderId), 10),
|
||||||
|
Status: status,
|
||||||
|
ExecutedQuantity: order.BaseVolume,
|
||||||
|
IsWorking: order.Status.IsWorking(),
|
||||||
|
CreationTime: types.Time(order.CTime.Time()),
|
||||||
|
UpdateTime: types.Time(order.UTime.Time()),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processMarketBuyQuantity returns the estimated base quantity or real. The order size will be 'quote quantity' when side is buy and
|
||||||
|
// type is market, so we need to convert that. This is because the unit of types.Order.Quantity is base coin.
|
||||||
|
//
|
||||||
|
// If the order status is PartialFilled, return estimated base coin quantity.
|
||||||
|
// If the order status is Filled, return the filled base quantity instead of the buy quantity, because a market order on the buy side
|
||||||
|
// cannot execute all.
|
||||||
|
// Otherwise, return zero.
|
||||||
|
func processMarketBuyQuantity(filledQty, filledPrice, priceAvg, buyQty fixedpoint.Value, orderStatus v2.OrderStatus) (fixedpoint.Value, error) {
|
||||||
|
switch orderStatus {
|
||||||
|
case v2.OrderStatusInit, v2.OrderStatusNew, v2.OrderStatusLive, v2.OrderStatusCancelled:
|
||||||
|
return fixedpoint.Zero, nil
|
||||||
|
|
||||||
|
case v2.OrderStatusPartialFilled:
|
||||||
|
// sanity check for avoid divide 0
|
||||||
|
if priceAvg.IsZero() {
|
||||||
|
return fixedpoint.Zero, errors.New("priceAvg for a partialFilled should not be zero")
|
||||||
|
}
|
||||||
|
// calculate the remaining quote coin quantity.
|
||||||
|
remainPrice := buyQty.Sub(filledPrice)
|
||||||
|
// calculate the remaining base coin quantity.
|
||||||
|
remainBaseCoinQty := remainPrice.Div(priceAvg)
|
||||||
|
// Estimated quantity that may be purchased.
|
||||||
|
return filledQty.Add(remainBaseCoinQty), nil
|
||||||
|
|
||||||
|
case v2.OrderStatusFilled:
|
||||||
|
// Market buy orders may not purchase the entire quantity, hence the use of filledQty here.
|
||||||
|
return filledQty, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fixedpoint.Zero, fmt.Errorf("failed to execute market buy quantity due to unexpected order status %s ", orderStatus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -272,3 +272,196 @@ func Test_unfilledOrderToGlobalOrder(t *testing.T) {
|
||||||
assert.ErrorContains(err, "xxx")
|
assert.ErrorContains(err, "xxx")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_toGlobalOrder(t *testing.T) {
|
||||||
|
var (
|
||||||
|
assert = assert.New(t)
|
||||||
|
orderId = 1105087175647989764
|
||||||
|
unfilledOrder = v2.OrderDetail{
|
||||||
|
UserId: 123456,
|
||||||
|
Symbol: "BTCUSDT",
|
||||||
|
OrderId: types.StrInt64(orderId),
|
||||||
|
ClientOrderId: "74b86af3-6098-479c-acac-bfb074c067f3",
|
||||||
|
Price: fixedpoint.NewFromFloat(1.2),
|
||||||
|
Size: fixedpoint.NewFromFloat(5),
|
||||||
|
OrderType: v2.OrderTypeLimit,
|
||||||
|
Side: v2.SideTypeBuy,
|
||||||
|
Status: v2.OrderStatusFilled,
|
||||||
|
PriceAvg: fixedpoint.NewFromFloat(1.4),
|
||||||
|
BaseVolume: fixedpoint.NewFromFloat(5),
|
||||||
|
QuoteVolume: fixedpoint.NewFromFloat(7.0005),
|
||||||
|
EnterPointSource: "API",
|
||||||
|
FeeDetailRaw: `{\"newFees\":{\"c\":0,\"d\":0,\"deduction\":false,\"r\":-0.0070005,\"t\":-0.0070005,\"totalDeductionFee\":0},\"USDT\":{\"deduction\":false,\"feeCoinCode\":\"USDT\",\"totalDeductionFee\":0,\"totalFee\":-0.007000500000}}`,
|
||||||
|
OrderSource: "normal",
|
||||||
|
CTime: types.NewMillisecondTimestampFromInt(1660704288118),
|
||||||
|
UTime: types.NewMillisecondTimestampFromInt(1660704288118),
|
||||||
|
}
|
||||||
|
|
||||||
|
expOrder = &types.Order{
|
||||||
|
SubmitOrder: types.SubmitOrder{
|
||||||
|
ClientOrderID: "74b86af3-6098-479c-acac-bfb074c067f3",
|
||||||
|
Symbol: "BTCUSDT",
|
||||||
|
Side: types.SideTypeBuy,
|
||||||
|
Type: types.OrderTypeLimit,
|
||||||
|
Quantity: fixedpoint.NewFromFloat(5),
|
||||||
|
Price: fixedpoint.NewFromFloat(1.2),
|
||||||
|
TimeInForce: types.TimeInForceGTC,
|
||||||
|
},
|
||||||
|
Exchange: types.ExchangeBitget,
|
||||||
|
OrderID: uint64(orderId),
|
||||||
|
UUID: strconv.FormatInt(int64(orderId), 10),
|
||||||
|
Status: types.OrderStatusFilled,
|
||||||
|
ExecutedQuantity: fixedpoint.NewFromFloat(5),
|
||||||
|
IsWorking: false,
|
||||||
|
CreationTime: types.Time(types.NewMillisecondTimestampFromInt(1660704288118).Time()),
|
||||||
|
UpdateTime: types.Time(types.NewMillisecondTimestampFromInt(1660704288118).Time()),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run("succeeds with limit buy", func(t *testing.T) {
|
||||||
|
order, err := toGlobalOrder(unfilledOrder)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(expOrder, order)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("succeeds with limit sell", func(t *testing.T) {
|
||||||
|
newUnfilledOrder := unfilledOrder
|
||||||
|
newUnfilledOrder.Side = v2.SideTypeSell
|
||||||
|
|
||||||
|
newExpOrder := *expOrder
|
||||||
|
newExpOrder.Side = types.SideTypeSell
|
||||||
|
|
||||||
|
order, err := toGlobalOrder(newUnfilledOrder)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(&newExpOrder, order)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("succeeds with market sell", func(t *testing.T) {
|
||||||
|
newUnfilledOrder := unfilledOrder
|
||||||
|
newUnfilledOrder.Side = v2.SideTypeSell
|
||||||
|
newUnfilledOrder.OrderType = v2.OrderTypeMarket
|
||||||
|
|
||||||
|
newExpOrder := *expOrder
|
||||||
|
newExpOrder.Side = types.SideTypeSell
|
||||||
|
newExpOrder.Type = types.OrderTypeMarket
|
||||||
|
newExpOrder.Price = newUnfilledOrder.PriceAvg
|
||||||
|
|
||||||
|
order, err := toGlobalOrder(newUnfilledOrder)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(&newExpOrder, order)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("succeeds with market buy", func(t *testing.T) {
|
||||||
|
newUnfilledOrder := unfilledOrder
|
||||||
|
newUnfilledOrder.Side = v2.SideTypeBuy
|
||||||
|
newUnfilledOrder.OrderType = v2.OrderTypeMarket
|
||||||
|
|
||||||
|
newExpOrder := *expOrder
|
||||||
|
newExpOrder.Side = types.SideTypeBuy
|
||||||
|
newExpOrder.Type = types.OrderTypeMarket
|
||||||
|
newExpOrder.Price = newUnfilledOrder.PriceAvg
|
||||||
|
newExpOrder.Quantity = newUnfilledOrder.BaseVolume
|
||||||
|
|
||||||
|
order, err := toGlobalOrder(newUnfilledOrder)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(&newExpOrder, order)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("succeeds with limit buy", func(t *testing.T) {
|
||||||
|
order, err := toGlobalOrder(unfilledOrder)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(&types.Order{
|
||||||
|
SubmitOrder: types.SubmitOrder{
|
||||||
|
ClientOrderID: "74b86af3-6098-479c-acac-bfb074c067f3",
|
||||||
|
Symbol: "BTCUSDT",
|
||||||
|
Side: types.SideTypeBuy,
|
||||||
|
Type: types.OrderTypeLimit,
|
||||||
|
Quantity: fixedpoint.NewFromFloat(5),
|
||||||
|
Price: fixedpoint.NewFromFloat(1.2),
|
||||||
|
TimeInForce: types.TimeInForceGTC,
|
||||||
|
},
|
||||||
|
Exchange: types.ExchangeBitget,
|
||||||
|
OrderID: uint64(orderId),
|
||||||
|
UUID: strconv.FormatInt(int64(orderId), 10),
|
||||||
|
Status: types.OrderStatusFilled,
|
||||||
|
ExecutedQuantity: fixedpoint.NewFromFloat(5),
|
||||||
|
IsWorking: false,
|
||||||
|
CreationTime: types.Time(types.NewMillisecondTimestampFromInt(1660704288118).Time()),
|
||||||
|
UpdateTime: types.Time(types.NewMillisecondTimestampFromInt(1660704288118).Time()),
|
||||||
|
}, order)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("failed to convert side", func(t *testing.T) {
|
||||||
|
newOrder := unfilledOrder
|
||||||
|
newOrder.Side = "xxx"
|
||||||
|
|
||||||
|
_, err := toGlobalOrder(newOrder)
|
||||||
|
assert.ErrorContains(err, "xxx")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("failed to convert oder type", func(t *testing.T) {
|
||||||
|
newOrder := unfilledOrder
|
||||||
|
newOrder.OrderType = "xxx"
|
||||||
|
|
||||||
|
_, err := toGlobalOrder(newOrder)
|
||||||
|
assert.ErrorContains(err, "xxx")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("failed to convert oder status", func(t *testing.T) {
|
||||||
|
newOrder := unfilledOrder
|
||||||
|
newOrder.Status = "xxx"
|
||||||
|
|
||||||
|
_, err := toGlobalOrder(newOrder)
|
||||||
|
assert.ErrorContains(err, "xxx")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_processMarketBuyQuantity(t *testing.T) {
|
||||||
|
var (
|
||||||
|
assert = assert.New(t)
|
||||||
|
filledBaseCoinQty = fixedpoint.NewFromFloat(3.5648)
|
||||||
|
filledPrice = fixedpoint.NewFromFloat(4.99998848)
|
||||||
|
priceAvg = fixedpoint.NewFromFloat(1.4026)
|
||||||
|
buyQty = fixedpoint.NewFromFloat(5)
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run("zero quantity on Init/New/Live/Cancelled", func(t *testing.T) {
|
||||||
|
qty, err := processMarketBuyQuantity(filledBaseCoinQty, filledPrice, priceAvg, buyQty, v2.OrderStatusInit)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(fixedpoint.Zero, qty)
|
||||||
|
|
||||||
|
qty, err = processMarketBuyQuantity(filledBaseCoinQty, filledPrice, priceAvg, buyQty, v2.OrderStatusNew)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(fixedpoint.Zero, qty)
|
||||||
|
|
||||||
|
qty, err = processMarketBuyQuantity(filledBaseCoinQty, filledPrice, priceAvg, buyQty, v2.OrderStatusLive)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(fixedpoint.Zero, qty)
|
||||||
|
|
||||||
|
qty, err = processMarketBuyQuantity(filledBaseCoinQty, filledPrice, priceAvg, buyQty, v2.OrderStatusCancelled)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(fixedpoint.Zero, qty)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("5 on PartialFilled", func(t *testing.T) {
|
||||||
|
priceAvg := fixedpoint.NewFromFloat(2)
|
||||||
|
buyQty := fixedpoint.NewFromFloat(10)
|
||||||
|
filledPrice := fixedpoint.NewFromFloat(4)
|
||||||
|
filledBaseCoinQty := fixedpoint.NewFromFloat(2)
|
||||||
|
|
||||||
|
qty, err := processMarketBuyQuantity(filledBaseCoinQty, filledPrice, priceAvg, buyQty, v2.OrderStatusPartialFilled)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(fixedpoint.NewFromFloat(5), qty)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("3.5648 on Filled", func(t *testing.T) {
|
||||||
|
qty, err := processMarketBuyQuantity(filledBaseCoinQty, filledPrice, priceAvg, buyQty, v2.OrderStatusFilled)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(fixedpoint.NewFromFloat(3.5648), qty)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("unexpected order status", func(t *testing.T) {
|
||||||
|
_, err := processMarketBuyQuantity(filledBaseCoinQty, filledPrice, priceAvg, buyQty, "xxx")
|
||||||
|
assert.ErrorContains(err, "xxx")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"go.uber.org/multierr"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/exchange/bitget/bitgetapi"
|
"github.com/c9s/bbgo/pkg/exchange/bitget/bitgetapi"
|
||||||
|
@ -19,7 +20,9 @@ const (
|
||||||
|
|
||||||
PlatformToken = "BGB"
|
PlatformToken = "BGB"
|
||||||
|
|
||||||
queryOpenOrdersLimit = 100
|
queryLimit = 100
|
||||||
|
maxOrderIdLen = 36
|
||||||
|
queryMaxDuration = 90 * 24 * time.Hour
|
||||||
)
|
)
|
||||||
|
|
||||||
var log = logrus.WithFields(logrus.Fields{
|
var log = logrus.WithFields(logrus.Fields{
|
||||||
|
@ -37,6 +40,8 @@ var (
|
||||||
queryTickersRateLimiter = rate.NewLimiter(rate.Every(time.Second/10), 5)
|
queryTickersRateLimiter = rate.NewLimiter(rate.Every(time.Second/10), 5)
|
||||||
// queryOpenOrdersRateLimiter has its own rate limit. https://www.bitget.com/zh-CN/api-doc/spot/trade/Get-Unfilled-Orders
|
// 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)
|
queryOpenOrdersRateLimiter = rate.NewLimiter(rate.Every(time.Second/10), 5)
|
||||||
|
// 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)
|
||||||
)
|
)
|
||||||
|
|
||||||
type Exchange struct {
|
type Exchange struct {
|
||||||
|
@ -192,7 +197,7 @@ func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders [
|
||||||
|
|
||||||
req := e.v2Client.NewGetUnfilledOrdersRequest().
|
req := e.v2Client.NewGetUnfilledOrdersRequest().
|
||||||
Symbol(symbol).
|
Symbol(symbol).
|
||||||
Limit(strconv.FormatInt(queryOpenOrdersLimit, 10))
|
Limit(strconv.FormatInt(queryLimit, 10))
|
||||||
if nextCursor != 0 {
|
if nextCursor != 0 {
|
||||||
req.IdLessThan(strconv.FormatInt(int64(nextCursor), 10))
|
req.IdLessThan(strconv.FormatInt(int64(nextCursor), 10))
|
||||||
}
|
}
|
||||||
|
@ -213,11 +218,11 @@ func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders [
|
||||||
|
|
||||||
orderLen := len(openOrders)
|
orderLen := len(openOrders)
|
||||||
// a defensive programming to ensure the length of order response is expected.
|
// a defensive programming to ensure the length of order response is expected.
|
||||||
if orderLen > queryOpenOrdersLimit {
|
if orderLen > queryLimit {
|
||||||
return nil, fmt.Errorf("unexpected open orders length %d", orderLen)
|
return nil, fmt.Errorf("unexpected open orders length %d", orderLen)
|
||||||
}
|
}
|
||||||
|
|
||||||
if orderLen < queryOpenOrdersLimit {
|
if orderLen < queryLimit {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
nextCursor = openOrders[orderLen-1].OrderId
|
nextCursor = openOrders[orderLen-1].OrderId
|
||||||
|
@ -226,6 +231,57 @@ func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders [
|
||||||
return orders, nil
|
return orders, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QueryClosedOrders queries closed order by time range(`CTime`) 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.
|
||||||
|
//
|
||||||
|
// ** 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 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) {
|
||||||
|
if since.Sub(time.Now()) > queryMaxDuration {
|
||||||
|
return nil, fmt.Errorf("start time from the last 90 days can be queried, got: %s", since)
|
||||||
|
}
|
||||||
|
if until.Before(since) {
|
||||||
|
return nil, fmt.Errorf("end time %s before start %s", until, since)
|
||||||
|
}
|
||||||
|
if until.Sub(since) > queryMaxDuration {
|
||||||
|
return nil, fmt.Errorf("the start time %s and end time %s cannot exceed 90 days", since, until)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
res, err := e.v2Client.NewGetHistoryOrdersRequest().
|
||||||
|
Symbol(symbol).
|
||||||
|
Limit(strconv.Itoa(queryLimit)).
|
||||||
|
StartTime(since.UnixMilli()).
|
||||||
|
EndTime(until.UnixMilli()).
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) error {
|
func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) error {
|
||||||
// TODO implement me
|
// TODO implement me
|
||||||
panic("implement me")
|
panic("implement me")
|
||||||
|
|
Loading…
Reference in New Issue
Block a user