mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-26 08:45:16 +00:00
Merge pull request #1252 from bailantaotao/edwin/query-closed-order
FEATURE: [bybit] query closed order
This commit is contained in:
commit
7eb6e402ca
|
@ -92,6 +92,9 @@ func TestClient(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
t.Logf("apiResp: %+v", apiResp)
|
t.Logf("apiResp: %+v", apiResp)
|
||||||
|
|
||||||
|
_, err = strconv.ParseUint(apiResp.OrderId, 10, 64)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
ordersResp, err := client.NewGetOpenOrderRequest().OrderLinkId(apiResp.OrderLinkId).Do(ctx)
|
ordersResp, err := client.NewGetOpenOrderRequest().OrderLinkId(apiResp.OrderLinkId).Do(ctx)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, len(ordersResp.List), 1)
|
assert.Equal(t, len(ordersResp.List), 1)
|
||||||
|
@ -129,4 +132,27 @@ func TestClient(t *testing.T) {
|
||||||
assert.Equal(t, ordersResp.List[0].OrderStatus, OrderStatusCancelled)
|
assert.Equal(t, ordersResp.List[0].OrderStatus, OrderStatusCancelled)
|
||||||
t.Logf("apiResp: %+v", ordersResp.List[0])
|
t.Logf("apiResp: %+v", ordersResp.List[0])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("GetOrderHistoriesRequest", func(t *testing.T) {
|
||||||
|
req := client.NewPlaceOrderRequest().
|
||||||
|
Symbol("DOTUSDT").
|
||||||
|
Side(SideBuy).
|
||||||
|
OrderType(OrderTypeLimit).
|
||||||
|
Qty("1").
|
||||||
|
Price("4.6").
|
||||||
|
OrderLinkId(uuid.NewString()).
|
||||||
|
TimeInForce(TimeInForceGTC)
|
||||||
|
apiResp, err := req.Do(ctx)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
t.Logf("apiResp: %+v", apiResp)
|
||||||
|
|
||||||
|
ordersResp, err := client.NewGetOpenOrderRequest().OrderLinkId(apiResp.OrderLinkId).Do(ctx)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, len(ordersResp.List), 1)
|
||||||
|
t.Logf("apiResp: %+v", ordersResp.List[0])
|
||||||
|
|
||||||
|
orderResp, err := client.NewGetOrderHistoriesRequest().Symbol("DOTUSDT").Cursor("0").Do(ctx)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
t.Logf("apiResp: %#v", orderResp)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,13 +9,13 @@ import (
|
||||||
//go:generate -command GetRequest requestgen -method GET -responseType .APIResponse -responseDataField Result
|
//go:generate -command GetRequest requestgen -method GET -responseType .APIResponse -responseDataField Result
|
||||||
//go:generate -command PostRequest requestgen -method POST -responseType .APIResponse -responseDataField Result
|
//go:generate -command PostRequest requestgen -method POST -responseType .APIResponse -responseDataField Result
|
||||||
|
|
||||||
type OpenOrdersResponse struct {
|
type OrdersResponse struct {
|
||||||
List []OpenOrder `json:"list"`
|
List []Order `json:"list"`
|
||||||
NextPageCursor string `json:"nextPageCursor"`
|
NextPageCursor string `json:"nextPageCursor"`
|
||||||
Category string `json:"category"`
|
Category string `json:"category"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OpenOrder struct {
|
type Order struct {
|
||||||
OrderId string `json:"orderId"`
|
OrderId string `json:"orderId"`
|
||||||
OrderLinkId string `json:"orderLinkId"`
|
OrderLinkId string `json:"orderLinkId"`
|
||||||
BlockTradeId string `json:"blockTradeId"`
|
BlockTradeId string `json:"blockTradeId"`
|
||||||
|
@ -59,7 +59,7 @@ type OpenOrder struct {
|
||||||
UpdatedTime types.MillisecondTimestamp `json:"updatedTime"`
|
UpdatedTime types.MillisecondTimestamp `json:"updatedTime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate GetRequest -url "/v5/order/realtime" -type GetOpenOrdersRequest -responseDataType .OpenOrdersResponse
|
//go:generate GetRequest -url "/v5/order/realtime" -type GetOpenOrdersRequest -responseDataType .OrdersResponse
|
||||||
type GetOpenOrdersRequest struct {
|
type GetOpenOrdersRequest struct {
|
||||||
client requestgen.AuthenticatedAPIClient
|
client requestgen.AuthenticatedAPIClient
|
||||||
|
|
||||||
|
@ -75,6 +75,8 @@ type GetOpenOrdersRequest struct {
|
||||||
cursor *string `param:"cursor,query"`
|
cursor *string `param:"cursor,query"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewGetOpenOrderRequest queries unfilled or partially filled orders in real-time. To query older order records,
|
||||||
|
// please use the order history interface.
|
||||||
func (c *RestClient) NewGetOpenOrderRequest() *GetOpenOrdersRequest {
|
func (c *RestClient) NewGetOpenOrderRequest() *GetOpenOrdersRequest {
|
||||||
return &GetOpenOrdersRequest{
|
return &GetOpenOrdersRequest{
|
||||||
client: c,
|
client: c,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Result -url /v5/order/realtime -type GetOpenOrdersRequest -responseDataType .OpenOrdersResponse"; DO NOT EDIT.
|
// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Result -url /v5/order/realtime -type GetOpenOrdersRequest -responseDataType .OrdersResponse"; DO NOT EDIT.
|
||||||
|
|
||||||
package bybitapi
|
package bybitapi
|
||||||
|
|
||||||
|
@ -258,7 +258,7 @@ func (g *GetOpenOrdersRequest) GetSlugsMap() (map[string]string, error) {
|
||||||
return slugs, nil
|
return slugs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GetOpenOrdersRequest) Do(ctx context.Context) (*OpenOrdersResponse, error) {
|
func (g *GetOpenOrdersRequest) Do(ctx context.Context) (*OrdersResponse, error) {
|
||||||
|
|
||||||
// no body params
|
// no body params
|
||||||
var params interface{}
|
var params interface{}
|
||||||
|
@ -283,7 +283,7 @@ func (g *GetOpenOrdersRequest) Do(ctx context.Context) (*OpenOrdersResponse, err
|
||||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var data OpenOrdersResponse
|
var data OrdersResponse
|
||||||
if err := json.Unmarshal(apiResponse.Result, &data); err != nil {
|
if err := json.Unmarshal(apiResponse.Result, &data); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,282 @@
|
||||||
|
// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Result -url /v5/order/history -type GetOrderHistoriesRequest -responseDataType .OrdersResponse"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package bybitapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (g *GetOrderHistoriesRequest) Category(category Category) *GetOrderHistoriesRequest {
|
||||||
|
g.category = category
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GetOrderHistoriesRequest) Symbol(symbol string) *GetOrderHistoriesRequest {
|
||||||
|
g.symbol = &symbol
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GetOrderHistoriesRequest) OrderId(orderId string) *GetOrderHistoriesRequest {
|
||||||
|
g.orderId = &orderId
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GetOrderHistoriesRequest) OrderFilter(orderFilter string) *GetOrderHistoriesRequest {
|
||||||
|
g.orderFilter = &orderFilter
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GetOrderHistoriesRequest) OrderStatus(orderStatus OrderStatus) *GetOrderHistoriesRequest {
|
||||||
|
g.orderStatus = &orderStatus
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GetOrderHistoriesRequest) StartTime(startTime time.Time) *GetOrderHistoriesRequest {
|
||||||
|
g.startTime = &startTime
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GetOrderHistoriesRequest) EndTime(endTime time.Time) *GetOrderHistoriesRequest {
|
||||||
|
g.endTime = &endTime
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GetOrderHistoriesRequest) Limit(limit uint64) *GetOrderHistoriesRequest {
|
||||||
|
g.limit = &limit
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GetOrderHistoriesRequest) Cursor(cursor string) *GetOrderHistoriesRequest {
|
||||||
|
g.cursor = &cursor
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetQueryParameters builds and checks the query parameters and returns url.Values
|
||||||
|
func (g *GetOrderHistoriesRequest) GetQueryParameters() (url.Values, error) {
|
||||||
|
var params = map[string]interface{}{}
|
||||||
|
// check category field -> json key category
|
||||||
|
category := g.category
|
||||||
|
|
||||||
|
// TEMPLATE check-valid-values
|
||||||
|
switch category {
|
||||||
|
case "spot":
|
||||||
|
params["category"] = category
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("category value %v is invalid", category)
|
||||||
|
|
||||||
|
}
|
||||||
|
// END TEMPLATE check-valid-values
|
||||||
|
|
||||||
|
// assign parameter of category
|
||||||
|
params["category"] = category
|
||||||
|
// check symbol field -> json key symbol
|
||||||
|
if g.symbol != nil {
|
||||||
|
symbol := *g.symbol
|
||||||
|
|
||||||
|
// assign parameter of symbol
|
||||||
|
params["symbol"] = symbol
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
// check orderId field -> json key orderId
|
||||||
|
if g.orderId != nil {
|
||||||
|
orderId := *g.orderId
|
||||||
|
|
||||||
|
// assign parameter of orderId
|
||||||
|
params["orderId"] = orderId
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
// check orderFilter field -> json key orderFilter
|
||||||
|
if g.orderFilter != nil {
|
||||||
|
orderFilter := *g.orderFilter
|
||||||
|
|
||||||
|
// assign parameter of orderFilter
|
||||||
|
params["orderFilter"] = orderFilter
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
// check orderStatus field -> json key orderStatus
|
||||||
|
if g.orderStatus != nil {
|
||||||
|
orderStatus := *g.orderStatus
|
||||||
|
|
||||||
|
// TEMPLATE check-valid-values
|
||||||
|
switch orderStatus {
|
||||||
|
case OrderStatusCreated, OrderStatusNew, OrderStatusRejected, OrderStatusPartiallyFilled, OrderStatusPartiallyFilledCanceled, OrderStatusFilled, OrderStatusCancelled, OrderStatusDeactivated, OrderStatusActive:
|
||||||
|
params["orderStatus"] = orderStatus
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("orderStatus value %v is invalid", orderStatus)
|
||||||
|
|
||||||
|
}
|
||||||
|
// END TEMPLATE check-valid-values
|
||||||
|
|
||||||
|
// assign parameter of orderStatus
|
||||||
|
params["orderStatus"] = orderStatus
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
// 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 {
|
||||||
|
}
|
||||||
|
// check cursor field -> json key cursor
|
||||||
|
if g.cursor != nil {
|
||||||
|
cursor := *g.cursor
|
||||||
|
|
||||||
|
// assign parameter of cursor
|
||||||
|
params["cursor"] = cursor
|
||||||
|
} 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 *GetOrderHistoriesRequest) 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 *GetOrderHistoriesRequest) 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 *GetOrderHistoriesRequest) 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 *GetOrderHistoriesRequest) GetSlugParameters() (map[string]interface{}, error) {
|
||||||
|
var params = map[string]interface{}{}
|
||||||
|
|
||||||
|
return params, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GetOrderHistoriesRequest) 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 *GetOrderHistoriesRequest) 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 *GetOrderHistoriesRequest) isVarSlice(_v interface{}) bool {
|
||||||
|
rt := reflect.TypeOf(_v)
|
||||||
|
switch rt.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GetOrderHistoriesRequest) 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 *GetOrderHistoriesRequest) Do(ctx context.Context) (*OrdersResponse, error) {
|
||||||
|
|
||||||
|
// no body params
|
||||||
|
var params interface{}
|
||||||
|
query, err := g.GetQueryParameters()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
apiURL := "/v5/order/history"
|
||||||
|
|
||||||
|
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 APIResponse
|
||||||
|
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var data OrdersResponse
|
||||||
|
if err := json.Unmarshal(apiResponse.Result, &data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &data, nil
|
||||||
|
}
|
51
pkg/exchange/bybit/bybitapi/get_order_history_request.go
Normal file
51
pkg/exchange/bybit/bybitapi/get_order_history_request.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package bybitapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/c9s/requestgen"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate -command GetRequest requestgen -method GET -responseType .APIResponse -responseDataField Result
|
||||||
|
//go:generate -command PostRequest requestgen -method POST -responseType .APIResponse -responseDataField Result
|
||||||
|
|
||||||
|
//go:generate GetRequest -url "/v5/order/history" -type GetOrderHistoriesRequest -responseDataType .OpenOrdersResponse
|
||||||
|
type GetOrderHistoriesRequest struct {
|
||||||
|
client requestgen.AuthenticatedAPIClient
|
||||||
|
|
||||||
|
category Category `param:"category,query" validValues:"spot"`
|
||||||
|
|
||||||
|
symbol *string `param:"symbol,query"`
|
||||||
|
orderId *string `param:"orderId,query"`
|
||||||
|
// orderFilter supports 3 types of Order:
|
||||||
|
// 1. active order, 2. StopOrder: conditional order, 3. tpslOrder: spot TP/SL order
|
||||||
|
// Normal spot: return Order active order by default
|
||||||
|
// Others: all kinds of orders by default
|
||||||
|
orderFilter *string `param:"orderFilter,query"`
|
||||||
|
// orderStatus if the account belongs to Normal spot, orderStatus is not supported.
|
||||||
|
//// For other accounts, return all status orders if not explicitly passed.
|
||||||
|
orderStatus *OrderStatus `param:"orderStatus,query"`
|
||||||
|
|
||||||
|
// startTime must
|
||||||
|
// Normal spot is not supported temporarily
|
||||||
|
// startTime and endTime must be passed together
|
||||||
|
// If not passed, query the past 7 days data by default
|
||||||
|
// For each request, startTime and endTime interval should be less then 7 days
|
||||||
|
startTime *time.Time `param:"startTime,query,milliseconds"`
|
||||||
|
|
||||||
|
// endTime for each request, startTime and endTime interval should be less then 7 days
|
||||||
|
endTime *time.Time `param:"endTime,query,milliseconds"`
|
||||||
|
|
||||||
|
// limit for data size per page. [1, 50]. Default: 20
|
||||||
|
limit *uint64 `param:"limit,query"`
|
||||||
|
// cursor uses the nextPageCursor token from the response to retrieve the next page of the result set
|
||||||
|
cursor *string `param:"cursor,query"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetOrderHistoriesRequest is descending order by createdTime
|
||||||
|
func (c *RestClient) NewGetOrderHistoriesRequest() *GetOrderHistoriesRequest {
|
||||||
|
return &GetOrderHistoriesRequest{
|
||||||
|
client: c,
|
||||||
|
category: CategorySpot,
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
"math"
|
"math"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/exchange/bybit/bybitapi"
|
"github.com/c9s/bbgo/pkg/exchange/bybit/bybitapi"
|
||||||
|
@ -46,7 +47,7 @@ func toGlobalTicker(stats bybitapi.Ticker, time time.Time) types.Ticker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toGlobalOrder(order bybitapi.OpenOrder) (*types.Order, error) {
|
func toGlobalOrder(order bybitapi.Order) (*types.Order, error) {
|
||||||
side, err := toGlobalSideType(order.Side)
|
side, err := toGlobalSideType(order.Side)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -67,6 +68,13 @@ func toGlobalOrder(order bybitapi.OpenOrder) (*types.Order, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// linear and inverse : 42f4f364-82e1-49d3-ad1d-cd8cf9aa308d (UUID format)
|
||||||
|
// spot : 1468264727470772736 (only numbers)
|
||||||
|
// Now we only use spot trading.
|
||||||
|
orderIdNum, err := strconv.ParseUint(order.OrderId, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unexpected order id: %s, err: %v", order.OrderId, err)
|
||||||
|
}
|
||||||
|
|
||||||
return &types.Order{
|
return &types.Order{
|
||||||
SubmitOrder: types.SubmitOrder{
|
SubmitOrder: types.SubmitOrder{
|
||||||
|
@ -79,7 +87,7 @@ func toGlobalOrder(order bybitapi.OpenOrder) (*types.Order, error) {
|
||||||
TimeInForce: timeInForce,
|
TimeInForce: timeInForce,
|
||||||
},
|
},
|
||||||
Exchange: types.ExchangeBybit,
|
Exchange: types.ExchangeBybit,
|
||||||
OrderID: hashStringID(order.OrderId),
|
OrderID: orderIdNum,
|
||||||
UUID: order.OrderId,
|
UUID: order.OrderId,
|
||||||
Status: status,
|
Status: status,
|
||||||
ExecutedQuantity: order.CumExecQty,
|
ExecutedQuantity: order.CumExecQty,
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
package bybit
|
package bybit
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"go.uber.org/multierr"
|
||||||
"math"
|
"math"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -13,6 +17,18 @@ import (
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestU(t *testing.T) {
|
||||||
|
e := returnErr()
|
||||||
|
|
||||||
|
t.Log(errors.Is(e, context.DeadlineExceeded))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func returnErr() error {
|
||||||
|
var err error
|
||||||
|
return multierr.Append(multierr.Append(err, fmt.Errorf("got err: %w", context.DeadlineExceeded)), fmt.Errorf("GG"))
|
||||||
|
}
|
||||||
|
|
||||||
func TestToGlobalMarket(t *testing.T) {
|
func TestToGlobalMarket(t *testing.T) {
|
||||||
// sample:
|
// sample:
|
||||||
//{
|
//{
|
||||||
|
@ -178,7 +194,7 @@ func TestToGlobalOrder(t *testing.T) {
|
||||||
// "UpdatedTime": "2023-07-25 17:12:57.868 +0800 CST"
|
// "UpdatedTime": "2023-07-25 17:12:57.868 +0800 CST"
|
||||||
//}
|
//}
|
||||||
timeNow := time.Now()
|
timeNow := time.Now()
|
||||||
openOrder := bybitapi.OpenOrder{
|
openOrder := bybitapi.Order{
|
||||||
OrderId: "1472539279335923200",
|
OrderId: "1472539279335923200",
|
||||||
OrderLinkId: "1690276361150",
|
OrderLinkId: "1690276361150",
|
||||||
BlockTradeId: "",
|
BlockTradeId: "",
|
||||||
|
@ -231,6 +247,8 @@ func TestToGlobalOrder(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
working, err := isWorking(openOrder.OrderStatus)
|
working, err := isWorking(openOrder.OrderStatus)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
orderIdNum, err := strconv.ParseUint(openOrder.OrderId, 10, 64)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
exp := types.Order{
|
exp := types.Order{
|
||||||
SubmitOrder: types.SubmitOrder{
|
SubmitOrder: types.SubmitOrder{
|
||||||
|
@ -243,7 +261,7 @@ func TestToGlobalOrder(t *testing.T) {
|
||||||
TimeInForce: tif,
|
TimeInForce: tif,
|
||||||
},
|
},
|
||||||
Exchange: types.ExchangeBybit,
|
Exchange: types.ExchangeBybit,
|
||||||
OrderID: hashStringID(openOrder.OrderId),
|
OrderID: orderIdNum,
|
||||||
UUID: openOrder.OrderId,
|
UUID: openOrder.OrderId,
|
||||||
Status: status,
|
Status: status,
|
||||||
ExecutedQuantity: openOrder.CumExecQty,
|
ExecutedQuantity: openOrder.CumExecQty,
|
||||||
|
|
|
@ -3,6 +3,7 @@ package bybit
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
@ -15,6 +16,7 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
maxOrderIdLen = 36
|
maxOrderIdLen = 36
|
||||||
|
defaultQueryClosedLen = 50
|
||||||
)
|
)
|
||||||
|
|
||||||
// https://bybit-exchange.github.io/docs/zh-TW/v5/rate-limit
|
// https://bybit-exchange.github.io/docs/zh-TW/v5/rate-limit
|
||||||
|
@ -26,6 +28,7 @@ var (
|
||||||
sharedRateLimiter = rate.NewLimiter(rate.Every(time.Second/2), 2)
|
sharedRateLimiter = rate.NewLimiter(rate.Every(time.Second/2), 2)
|
||||||
tradeRateLimiter = rate.NewLimiter(rate.Every(time.Second/5), 5)
|
tradeRateLimiter = rate.NewLimiter(rate.Every(time.Second/5), 5)
|
||||||
orderRateLimiter = rate.NewLimiter(rate.Every(100*time.Millisecond), 10)
|
orderRateLimiter = rate.NewLimiter(rate.Every(100*time.Millisecond), 10)
|
||||||
|
closedRateLimiter = rate.NewLimiter(rate.Every(time.Second), 1)
|
||||||
|
|
||||||
log = logrus.WithFields(logrus.Fields{
|
log = logrus.WithFields(logrus.Fields{
|
||||||
"exchange": "bybit",
|
"exchange": "bybit",
|
||||||
|
@ -67,7 +70,7 @@ func (e *Exchange) PlatformFeeCurrency() string {
|
||||||
|
|
||||||
func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) {
|
func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) {
|
||||||
if err := sharedRateLimiter.Wait(ctx); err != nil {
|
if err := sharedRateLimiter.Wait(ctx); err != nil {
|
||||||
return nil, fmt.Errorf("markets rate limiter wait error: %v", err)
|
return nil, fmt.Errorf("markets rate limiter wait error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
instruments, err := e.client.NewGetInstrumentsInfoRequest().Do(ctx)
|
instruments, err := e.client.NewGetInstrumentsInfoRequest().Do(ctx)
|
||||||
|
@ -85,12 +88,12 @@ func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) {
|
||||||
|
|
||||||
func (e *Exchange) QueryTicker(ctx context.Context, symbol string) (*types.Ticker, error) {
|
func (e *Exchange) QueryTicker(ctx context.Context, symbol string) (*types.Ticker, error) {
|
||||||
if err := sharedRateLimiter.Wait(ctx); err != nil {
|
if err := sharedRateLimiter.Wait(ctx); err != nil {
|
||||||
return nil, fmt.Errorf("ticker order rate limiter wait error: %v", err)
|
return nil, fmt.Errorf("ticker order rate limiter wait error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := e.client.NewGetTickersRequest().Symbol(symbol).DoWithResponseTime(ctx)
|
s, err := e.client.NewGetTickersRequest().Symbol(symbol).DoWithResponseTime(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to call ticker, symbol: %s, err: %w", symbol, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(s.List) != 1 {
|
if len(s.List) != 1 {
|
||||||
|
@ -117,11 +120,11 @@ func (e *Exchange) QueryTickers(ctx context.Context, symbols ...string) (map[str
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := sharedRateLimiter.Wait(ctx); err != nil {
|
if err := sharedRateLimiter.Wait(ctx); err != nil {
|
||||||
return nil, fmt.Errorf("tickers rate limiter wait error: %v", err)
|
return nil, fmt.Errorf("tickers rate limiter wait error: %w", err)
|
||||||
}
|
}
|
||||||
allTickers, err := e.client.NewGetTickersRequest().DoWithResponseTime(ctx)
|
allTickers, err := e.client.NewGetTickersRequest().DoWithResponseTime(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to call ticker, err: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, s := range allTickers.List {
|
for _, s := range allTickers.List {
|
||||||
|
@ -141,11 +144,11 @@ func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders [
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = tradeRateLimiter.Wait(ctx); err != nil {
|
if err = tradeRateLimiter.Wait(ctx); err != nil {
|
||||||
return nil, fmt.Errorf("place order rate limiter wait error: %v", err)
|
return nil, fmt.Errorf("place order rate limiter wait error: %w", err)
|
||||||
}
|
}
|
||||||
res, err := req.Do(ctx)
|
res, err := req.Do(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to query open orders, err: %v", err)
|
return nil, fmt.Errorf("failed to query open orders, err: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, order := range res.List {
|
for _, order := range res.List {
|
||||||
|
@ -214,11 +217,11 @@ func (e *Exchange) SubmitOrder(ctx context.Context, order types.SubmitOrder) (*t
|
||||||
req.OrderLinkId(order.ClientOrderID)
|
req.OrderLinkId(order.ClientOrderID)
|
||||||
|
|
||||||
if err := orderRateLimiter.Wait(ctx); err != nil {
|
if err := orderRateLimiter.Wait(ctx); err != nil {
|
||||||
return nil, fmt.Errorf("place order rate limiter wait error: %v", err)
|
return nil, fmt.Errorf("place order rate limiter wait error: %w", err)
|
||||||
}
|
}
|
||||||
res, err := req.Do(ctx)
|
res, err := req.Do(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to place order, order: %#v, err: %v", order, err)
|
return nil, fmt.Errorf("failed to place order, order: %#v, err: %w", order, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(res.OrderId) == 0 || res.OrderLinkId != order.ClientOrderID {
|
if len(res.OrderId) == 0 || res.OrderLinkId != order.ClientOrderID {
|
||||||
|
@ -227,7 +230,7 @@ func (e *Exchange) SubmitOrder(ctx context.Context, order types.SubmitOrder) (*t
|
||||||
|
|
||||||
ordersResp, err := e.client.NewGetOpenOrderRequest().OrderLinkId(res.OrderLinkId).Do(ctx)
|
ordersResp, err := e.client.NewGetOpenOrderRequest().OrderLinkId(res.OrderLinkId).Do(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to query order by client order id: %s", res.OrderLinkId)
|
return nil, fmt.Errorf("failed to query order by client order id: %s, err: %w", res.OrderLinkId, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ordersResp.List) != 1 {
|
if len(ordersResp.List) != 1 {
|
||||||
|
@ -261,12 +264,12 @@ func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) (err
|
||||||
req.Symbol(order.Market.Symbol)
|
req.Symbol(order.Market.Symbol)
|
||||||
|
|
||||||
if err := orderRateLimiter.Wait(ctx); err != nil {
|
if err := orderRateLimiter.Wait(ctx); err != nil {
|
||||||
errs = multierr.Append(errs, fmt.Errorf("cancel order rate limiter wait, order id: %s, error: %v", order.ClientOrderID, err))
|
errs = multierr.Append(errs, fmt.Errorf("cancel order rate limiter wait, order id: %s, error: %w", order.ClientOrderID, err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
res, err := req.Do(ctx)
|
res, err := req.Do(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = multierr.Append(errs, fmt.Errorf("failed to cancel order id: %s, err: %v", order.ClientOrderID, err))
|
errs = multierr.Append(errs, fmt.Errorf("failed to cancel order id: %s, err: %w", order.ClientOrderID, err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if res.OrderId != order.UUID || res.OrderLinkId != order.ClientOrderID {
|
if res.OrderId != order.UUID || res.OrderLinkId != order.ClientOrderID {
|
||||||
|
@ -277,3 +280,38 @@ func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) (err
|
||||||
|
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Exchange) QueryClosedOrders(ctx context.Context, symbol string, since, util time.Time, lastOrderID uint64) (orders []types.Order, err error) {
|
||||||
|
if !since.IsZero() || !util.IsZero() {
|
||||||
|
log.Warn("!!!BYBIT EXCHANGE API NOTICE!!! the since/until conditions will not be effected on SPOT account, bybit exchange does not support time-range-based query currently")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := closedRateLimiter.Wait(ctx); err != nil {
|
||||||
|
return nil, fmt.Errorf("query closed order rate limiter wait error: %w", err)
|
||||||
|
}
|
||||||
|
res, err := e.client.NewGetOrderHistoriesRequest().
|
||||||
|
Symbol(symbol).
|
||||||
|
Cursor(strconv.FormatUint(lastOrderID, 10)).
|
||||||
|
Limit(defaultQueryClosedLen).
|
||||||
|
Do(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to call get order histories error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, order := range res.List {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -116,6 +116,12 @@ const (
|
||||||
OrderStatusRejected OrderStatus = "REJECTED"
|
OrderStatusRejected OrderStatus = "REJECTED"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (o OrderStatus) Closed() bool {
|
||||||
|
return o == OrderStatusFilled ||
|
||||||
|
o == OrderStatusCanceled ||
|
||||||
|
o == OrderStatusRejected
|
||||||
|
}
|
||||||
|
|
||||||
type SubmitOrder struct {
|
type SubmitOrder struct {
|
||||||
ClientOrderID string `json:"clientOrderID,omitempty" db:"client_order_id"`
|
ClientOrderID string `json:"clientOrderID,omitempty" db:"client_order_id"`
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user