mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-22 14:55:16 +00:00
Merge pull request #1779 from c9s/edwin/bybit/uta
FEATURE: [bybit] upgrade classic account to UTA
This commit is contained in:
commit
8fcd76cb59
|
@ -12,7 +12,7 @@ type CancelOrderResponse struct {
|
||||||
OrderLinkId string `json:"orderLinkId"`
|
OrderLinkId string `json:"orderLinkId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate PostRequest -url "/v5/order/cancel" -type CancelOrderRequest -responseDataType .CancelOrderResponse
|
//go:generate PostRequest -url "/v5/order/cancel" -type CancelOrderRequest -responseDataType .CancelOrderResponse -rateLimiter 5+15/1s
|
||||||
type CancelOrderRequest struct {
|
type CancelOrderRequest struct {
|
||||||
client requestgen.AuthenticatedAPIClient
|
client requestgen.AuthenticatedAPIClient
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Code generated by "requestgen -method POST -responseType .APIResponse -responseDataField Result -url /v5/order/cancel -type CancelOrderRequest -responseDataType .CancelOrderResponse"; DO NOT EDIT.
|
// Code generated by "requestgen -method POST -responseType .APIResponse -responseDataField Result -url /v5/order/cancel -type CancelOrderRequest -responseDataType .CancelOrderResponse -rateLimiter 5+15/1s"; DO NOT EDIT.
|
||||||
|
|
||||||
package bybitapi
|
package bybitapi
|
||||||
|
|
||||||
|
@ -6,11 +6,14 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var CancelOrderRequestLimiter = rate.NewLimiter(15.000000150000002, 5)
|
||||||
|
|
||||||
func (p *CancelOrderRequest) Category(category Category) *CancelOrderRequest {
|
func (p *CancelOrderRequest) Category(category Category) *CancelOrderRequest {
|
||||||
p.category = category
|
p.category = category
|
||||||
return p
|
return p
|
||||||
|
@ -194,6 +197,9 @@ func (p *CancelOrderRequest) GetPath() string {
|
||||||
|
|
||||||
// Do generates the request object and send the request object to the API endpoint
|
// Do generates the request object and send the request object to the API endpoint
|
||||||
func (p *CancelOrderRequest) Do(ctx context.Context) (*CancelOrderResponse, error) {
|
func (p *CancelOrderRequest) Do(ctx context.Context) (*CancelOrderResponse, error) {
|
||||||
|
if err := CancelOrderRequestLimiter.Wait(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
params, err := p.GetParameters()
|
params, err := p.GetParameters()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -216,15 +222,29 @@ func (p *CancelOrderRequest) Do(ctx context.Context) (*CancelOrderResponse, erro
|
||||||
}
|
}
|
||||||
|
|
||||||
var apiResponse APIResponse
|
var apiResponse APIResponse
|
||||||
|
|
||||||
|
type responseUnmarshaler interface {
|
||||||
|
Unmarshal(data []byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
if unmarshaler, ok := interface{}(&apiResponse).(responseUnmarshaler); ok {
|
||||||
|
if err := unmarshaler.Unmarshal(response.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The line below checks the content type, however, some API server might not send the correct content type header,
|
||||||
|
// Hence, this is commented for backward compatibility
|
||||||
|
// response.IsJSON()
|
||||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type responseValidator interface {
|
type responseValidator interface {
|
||||||
Validate() error
|
Validate() error
|
||||||
}
|
}
|
||||||
validator, ok := interface{}(apiResponse).(responseValidator)
|
|
||||||
if ok {
|
if validator, ok := interface{}(&apiResponse).(responseValidator); ok {
|
||||||
if err := validator.Validate(); err != nil {
|
if err := validator.Validate(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,6 +80,26 @@ func TestClient(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("GetTrade", func(t *testing.T) {
|
||||||
|
cursor := ""
|
||||||
|
for {
|
||||||
|
req := client.NewGetExecutionListRequest().Limit(50)
|
||||||
|
if len(cursor) != 0 {
|
||||||
|
req = req.Cursor(cursor)
|
||||||
|
}
|
||||||
|
trades, err := req.Do(ctx)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
for _, o := range trades.List {
|
||||||
|
t.Logf("openOrders: %+v", o)
|
||||||
|
}
|
||||||
|
if len(trades.NextPageCursor) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
cursor = trades.NextPageCursor
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("PlaceOrderRequest", func(t *testing.T) {
|
t.Run("PlaceOrderRequest", func(t *testing.T) {
|
||||||
req := client.NewPlaceOrderRequest().
|
req := client.NewPlaceOrderRequest().
|
||||||
Symbol("DOTUSDT").
|
Symbol("DOTUSDT").
|
||||||
|
|
|
@ -30,6 +30,8 @@ type Trade struct {
|
||||||
ExecQty fixedpoint.Value `json:"execQty"`
|
ExecQty fixedpoint.Value `json:"execQty"`
|
||||||
ExecTime types.MillisecondTimestamp `json:"execTime"`
|
ExecTime types.MillisecondTimestamp `json:"execTime"`
|
||||||
IsMaker bool `json:"isMaker"`
|
IsMaker bool `json:"isMaker"`
|
||||||
|
FeeRate fixedpoint.Value `json:"feeRate"`
|
||||||
|
FeeCurrency string `json:"feeCurrency"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate GetRequest -url "/v5/execution/list" -type GetExecutionListRequest -responseDataType .TradesResponse -rateLimiter 5+15/1s
|
//go:generate GetRequest -url "/v5/execution/list" -type GetExecutionListRequest -responseDataType .TradesResponse -rateLimiter 5+15/1s
|
||||||
|
@ -40,6 +42,7 @@ type GetExecutionListRequest struct {
|
||||||
|
|
||||||
symbol *string `param:"symbol,query"`
|
symbol *string `param:"symbol,query"`
|
||||||
orderId *string `param:"orderId,query"`
|
orderId *string `param:"orderId,query"`
|
||||||
|
orderLinkId *string `param:"orderLinkId,query"`
|
||||||
|
|
||||||
// startTime the start timestamp (ms)
|
// startTime the start timestamp (ms)
|
||||||
// startTime and endTime are not passed, return 7 days by default;
|
// startTime and endTime are not passed, return 7 days by default;
|
||||||
|
|
|
@ -31,6 +31,11 @@ func (g *GetExecutionListRequest) OrderId(orderId string) *GetExecutionListReque
|
||||||
return g
|
return g
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *GetExecutionListRequest) OrderLinkId(orderLinkId string) *GetExecutionListRequest {
|
||||||
|
g.orderLinkId = &orderLinkId
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
func (g *GetExecutionListRequest) StartTime(startTime time.Time) *GetExecutionListRequest {
|
func (g *GetExecutionListRequest) StartTime(startTime time.Time) *GetExecutionListRequest {
|
||||||
g.startTime = &startTime
|
g.startTime = &startTime
|
||||||
return g
|
return g
|
||||||
|
@ -86,6 +91,14 @@ func (g *GetExecutionListRequest) GetQueryParameters() (url.Values, error) {
|
||||||
params["orderId"] = orderId
|
params["orderId"] = orderId
|
||||||
} else {
|
} else {
|
||||||
}
|
}
|
||||||
|
// check orderLinkId field -> json key orderLinkId
|
||||||
|
if g.orderLinkId != nil {
|
||||||
|
orderLinkId := *g.orderLinkId
|
||||||
|
|
||||||
|
// assign parameter of orderLinkId
|
||||||
|
params["orderLinkId"] = orderLinkId
|
||||||
|
} else {
|
||||||
|
}
|
||||||
// check startTime field -> json key startTime
|
// check startTime field -> json key startTime
|
||||||
if g.startTime != nil {
|
if g.startTime != nil {
|
||||||
startTime := *g.startTime
|
startTime := *g.startTime
|
||||||
|
@ -275,7 +288,6 @@ func (g *GetExecutionListRequest) Do(ctx context.Context) (*TradesResponse, erro
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var data TradesResponse
|
var data TradesResponse
|
||||||
if err := json.Unmarshal(apiResponse.Result, &data); err != nil {
|
if err := json.Unmarshal(apiResponse.Result, &data); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -78,7 +78,7 @@ type Order struct {
|
||||||
PlaceType string `json:"placeType"`
|
PlaceType string `json:"placeType"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate GetRequest -url "/v5/order/realtime" -type GetOpenOrdersRequest -responseDataType .OrdersResponse
|
//go:generate GetRequest -url "/v5/order/realtime" -type GetOpenOrdersRequest -responseDataType .OrdersResponse -rateLimiter 1+45/1s
|
||||||
type GetOpenOrdersRequest struct {
|
type GetOpenOrdersRequest struct {
|
||||||
client requestgen.AuthenticatedAPIClient
|
client requestgen.AuthenticatedAPIClient
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Result -url /v5/order/realtime -type GetOpenOrdersRequest -responseDataType .OrdersResponse"; DO NOT EDIT.
|
// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Result -url /v5/order/realtime -type GetOpenOrdersRequest -responseDataType .OrdersResponse -rateLimiter 1+45/1s"; DO NOT EDIT.
|
||||||
|
|
||||||
package bybitapi
|
package bybitapi
|
||||||
|
|
||||||
|
@ -6,11 +6,14 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var GetOpenOrdersRequestLimiter = rate.NewLimiter(45.00000045, 1)
|
||||||
|
|
||||||
func (g *GetOpenOrdersRequest) Category(category Category) *GetOpenOrdersRequest {
|
func (g *GetOpenOrdersRequest) Category(category Category) *GetOpenOrdersRequest {
|
||||||
g.category = category
|
g.category = category
|
||||||
return g
|
return g
|
||||||
|
@ -265,6 +268,9 @@ func (g *GetOpenOrdersRequest) GetPath() string {
|
||||||
|
|
||||||
// Do generates the request object and send the request object to the API endpoint
|
// Do generates the request object and send the request object to the API endpoint
|
||||||
func (g *GetOpenOrdersRequest) Do(ctx context.Context) (*OrdersResponse, error) {
|
func (g *GetOpenOrdersRequest) Do(ctx context.Context) (*OrdersResponse, error) {
|
||||||
|
if err := GetOpenOrdersRequestLimiter.Wait(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// no body params
|
// no body params
|
||||||
var params interface{}
|
var params interface{}
|
||||||
|
@ -288,15 +294,29 @@ func (g *GetOpenOrdersRequest) Do(ctx context.Context) (*OrdersResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
var apiResponse APIResponse
|
var apiResponse APIResponse
|
||||||
|
|
||||||
|
type responseUnmarshaler interface {
|
||||||
|
Unmarshal(data []byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
if unmarshaler, ok := interface{}(&apiResponse).(responseUnmarshaler); ok {
|
||||||
|
if err := unmarshaler.Unmarshal(response.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The line below checks the content type, however, some API server might not send the correct content type header,
|
||||||
|
// Hence, this is commented for backward compatibility
|
||||||
|
// response.IsJSON()
|
||||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type responseValidator interface {
|
type responseValidator interface {
|
||||||
Validate() error
|
Validate() error
|
||||||
}
|
}
|
||||||
validator, ok := interface{}(apiResponse).(responseValidator)
|
|
||||||
if ok {
|
if validator, ok := interface{}(&apiResponse).(responseValidator); ok {
|
||||||
if err := validator.Validate(); err != nil {
|
if err := validator.Validate(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ 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
|
||||||
|
|
||||||
//go:generate GetRequest -url "/v5/order/history" -type GetOrderHistoriesRequest -responseDataType .OrdersResponse
|
//go:generate GetRequest -url "/v5/order/history" -type GetOrderHistoriesRequest -responseDataType .OrdersResponse -rateLimiter 5+45/1s
|
||||||
type GetOrderHistoriesRequest struct {
|
type GetOrderHistoriesRequest struct {
|
||||||
client requestgen.AuthenticatedAPIClient
|
client requestgen.AuthenticatedAPIClient
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Result -url /v5/order/history -type GetOrderHistoriesRequest -responseDataType .OrdersResponse"; DO NOT EDIT.
|
// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Result -url /v5/order/history -type GetOrderHistoriesRequest -responseDataType .OrdersResponse -rateLimiter 5+45/1s"; DO NOT EDIT.
|
||||||
|
|
||||||
package bybitapi
|
package bybitapi
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -13,6 +14,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var GetOrderHistoriesRequestLimiter = rate.NewLimiter(45.00000045, 5)
|
||||||
|
|
||||||
func (g *GetOrderHistoriesRequest) Category(category Category) *GetOrderHistoriesRequest {
|
func (g *GetOrderHistoriesRequest) Category(category Category) *GetOrderHistoriesRequest {
|
||||||
g.category = category
|
g.category = category
|
||||||
return g
|
return g
|
||||||
|
@ -269,6 +272,9 @@ func (g *GetOrderHistoriesRequest) GetPath() string {
|
||||||
|
|
||||||
// Do generates the request object and send the request object to the API endpoint
|
// Do generates the request object and send the request object to the API endpoint
|
||||||
func (g *GetOrderHistoriesRequest) Do(ctx context.Context) (*OrdersResponse, error) {
|
func (g *GetOrderHistoriesRequest) Do(ctx context.Context) (*OrdersResponse, error) {
|
||||||
|
if err := GetOrderHistoriesRequestLimiter.Wait(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// no body params
|
// no body params
|
||||||
var params interface{}
|
var params interface{}
|
||||||
|
@ -292,15 +298,29 @@ func (g *GetOrderHistoriesRequest) Do(ctx context.Context) (*OrdersResponse, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var apiResponse APIResponse
|
var apiResponse APIResponse
|
||||||
|
|
||||||
|
type responseUnmarshaler interface {
|
||||||
|
Unmarshal(data []byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
if unmarshaler, ok := interface{}(&apiResponse).(responseUnmarshaler); ok {
|
||||||
|
if err := unmarshaler.Unmarshal(response.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The line below checks the content type, however, some API server might not send the correct content type header,
|
||||||
|
// Hence, this is commented for backward compatibility
|
||||||
|
// response.IsJSON()
|
||||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type responseValidator interface {
|
type responseValidator interface {
|
||||||
Validate() error
|
Validate() error
|
||||||
}
|
}
|
||||||
validator, ok := interface{}(apiResponse).(responseValidator)
|
|
||||||
if ok {
|
if validator, ok := interface{}(&apiResponse).(responseValidator); ok {
|
||||||
if err := validator.Validate(); err != nil {
|
if err := validator.Validate(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,14 +70,14 @@ type WalletBalances struct {
|
||||||
} `json:"coin"`
|
} `json:"coin"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate GetRequest -url "/v5/account/wallet-balance" -type GetWalletBalancesRequest -responseDataType .WalletBalancesResponse -rateLimiter 1+15/1s
|
//go:generate GetRequest -url "/v5/account/wallet-balance" -type GetWalletBalancesRequest -responseDataType .WalletBalancesResponse -rateLimiter 1+45/1s
|
||||||
type GetWalletBalancesRequest struct {
|
type GetWalletBalancesRequest struct {
|
||||||
client requestgen.AuthenticatedAPIClient
|
client requestgen.AuthenticatedAPIClient
|
||||||
|
|
||||||
// Account type
|
// Account type
|
||||||
// - Unified account: UNIFIED (trade spot/linear/options), CONTRACT(trade inverse)
|
// - Unified account: UNIFIED (trade spot/linear/options), CONTRACT(trade inverse)
|
||||||
// - Normal account: CONTRACT, SPOT
|
// - Normal account: CONTRACT, SPOT
|
||||||
accountType AccountType `param:"accountType,query" validValues:"SPOT"`
|
accountType AccountType `param:"accountType,query" validValues:"UNIFIED"`
|
||||||
// Coin name
|
// Coin name
|
||||||
// - If not passed, it returns non-zero asset info
|
// - If not passed, it returns non-zero asset info
|
||||||
// - You can pass multiple coins to query, separated by comma. USDT,USDC
|
// - You can pass multiple coins to query, separated by comma. USDT,USDC
|
||||||
|
@ -87,6 +87,6 @@ type GetWalletBalancesRequest struct {
|
||||||
func (c *RestClient) NewGetWalletBalancesRequest() *GetWalletBalancesRequest {
|
func (c *RestClient) NewGetWalletBalancesRequest() *GetWalletBalancesRequest {
|
||||||
return &GetWalletBalancesRequest{
|
return &GetWalletBalancesRequest{
|
||||||
client: c,
|
client: c,
|
||||||
accountType: AccountTypeSpot,
|
accountType: AccountTypeUnified,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Result -url /v5/account/wallet-balance -type GetWalletBalancesRequest -responseDataType .WalletBalancesResponse -rateLimiter 1+15/1s"; DO NOT EDIT.
|
// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Result -url /v5/account/wallet-balance -type GetWalletBalancesRequest -responseDataType .WalletBalancesResponse -rateLimiter 1+45/1s"; DO NOT EDIT.
|
||||||
|
|
||||||
package bybitapi
|
package bybitapi
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var GetWalletBalancesRequestLimiter = rate.NewLimiter(15.000000150000002, 1)
|
var GetWalletBalancesRequestLimiter = rate.NewLimiter(45.00000045, 1)
|
||||||
|
|
||||||
func (g *GetWalletBalancesRequest) AccountType(accountType AccountType) *GetWalletBalancesRequest {
|
func (g *GetWalletBalancesRequest) AccountType(accountType AccountType) *GetWalletBalancesRequest {
|
||||||
g.accountType = accountType
|
g.accountType = accountType
|
||||||
|
@ -32,7 +32,7 @@ func (g *GetWalletBalancesRequest) GetQueryParameters() (url.Values, error) {
|
||||||
|
|
||||||
// TEMPLATE check-valid-values
|
// TEMPLATE check-valid-values
|
||||||
switch accountType {
|
switch accountType {
|
||||||
case "SPOT":
|
case "UNIFIED":
|
||||||
params["accountType"] = accountType
|
params["accountType"] = accountType
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -12,7 +12,7 @@ type PlaceOrderResponse struct {
|
||||||
OrderLinkId string `json:"orderLinkId"`
|
OrderLinkId string `json:"orderLinkId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate PostRequest -url "/v5/order/create" -type PlaceOrderRequest -responseDataType .PlaceOrderResponse
|
//go:generate PostRequest -url "/v5/order/create" -type PlaceOrderRequest -responseDataType .PlaceOrderResponse -rateLimiter 5+15/1s
|
||||||
type PlaceOrderRequest struct {
|
type PlaceOrderRequest struct {
|
||||||
client requestgen.AuthenticatedAPIClient
|
client requestgen.AuthenticatedAPIClient
|
||||||
|
|
||||||
|
@ -23,6 +23,8 @@ type PlaceOrderRequest struct {
|
||||||
qty string `param:"qty"`
|
qty string `param:"qty"`
|
||||||
orderLinkId string `param:"orderLinkId"`
|
orderLinkId string `param:"orderLinkId"`
|
||||||
timeInForce TimeInForce `param:"timeInForce"`
|
timeInForce TimeInForce `param:"timeInForce"`
|
||||||
|
// Select the unit for qty when create Spot market orders for UTA account
|
||||||
|
marketUnit *MarketUnit `param:"marketUnit"`
|
||||||
|
|
||||||
isLeverage *bool `param:"isLeverage"`
|
isLeverage *bool `param:"isLeverage"`
|
||||||
price *string `param:"price"`
|
price *string `param:"price"`
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Code generated by "requestgen -method POST -responseType .APIResponse -responseDataField Result -url /v5/order/create -type PlaceOrderRequest -responseDataType .PlaceOrderResponse"; DO NOT EDIT.
|
// Code generated by "requestgen -method POST -responseType .APIResponse -responseDataField Result -url /v5/order/create -type PlaceOrderRequest -responseDataType .PlaceOrderResponse -rateLimiter 5+15/1s"; DO NOT EDIT.
|
||||||
|
|
||||||
package bybitapi
|
package bybitapi
|
||||||
|
|
||||||
|
@ -6,11 +6,14 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var PlaceOrderRequestLimiter = rate.NewLimiter(15.000000150000002, 5)
|
||||||
|
|
||||||
func (p *PlaceOrderRequest) Category(category Category) *PlaceOrderRequest {
|
func (p *PlaceOrderRequest) Category(category Category) *PlaceOrderRequest {
|
||||||
p.category = category
|
p.category = category
|
||||||
return p
|
return p
|
||||||
|
@ -46,6 +49,11 @@ func (p *PlaceOrderRequest) TimeInForce(timeInForce TimeInForce) *PlaceOrderRequ
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *PlaceOrderRequest) MarketUnit(marketUnit MarketUnit) *PlaceOrderRequest {
|
||||||
|
p.marketUnit = &marketUnit
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
func (p *PlaceOrderRequest) IsLeverage(isLeverage bool) *PlaceOrderRequest {
|
func (p *PlaceOrderRequest) IsLeverage(isLeverage bool) *PlaceOrderRequest {
|
||||||
p.isLeverage = &isLeverage
|
p.isLeverage = &isLeverage
|
||||||
return p
|
return p
|
||||||
|
@ -245,6 +253,25 @@ func (p *PlaceOrderRequest) GetParameters() (map[string]interface{}, error) {
|
||||||
|
|
||||||
// assign parameter of timeInForce
|
// assign parameter of timeInForce
|
||||||
params["timeInForce"] = timeInForce
|
params["timeInForce"] = timeInForce
|
||||||
|
// check marketUnit field -> json key marketUnit
|
||||||
|
if p.marketUnit != nil {
|
||||||
|
marketUnit := *p.marketUnit
|
||||||
|
|
||||||
|
// TEMPLATE check-valid-values
|
||||||
|
switch marketUnit {
|
||||||
|
case MarketUnitBase, MarketUnitQuote:
|
||||||
|
params["marketUnit"] = marketUnit
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("marketUnit value %v is invalid", marketUnit)
|
||||||
|
|
||||||
|
}
|
||||||
|
// END TEMPLATE check-valid-values
|
||||||
|
|
||||||
|
// assign parameter of marketUnit
|
||||||
|
params["marketUnit"] = marketUnit
|
||||||
|
} else {
|
||||||
|
}
|
||||||
// check isLeverage field -> json key isLeverage
|
// check isLeverage field -> json key isLeverage
|
||||||
if p.isLeverage != nil {
|
if p.isLeverage != nil {
|
||||||
isLeverage := *p.isLeverage
|
isLeverage := *p.isLeverage
|
||||||
|
@ -503,6 +530,9 @@ func (p *PlaceOrderRequest) GetPath() string {
|
||||||
|
|
||||||
// Do generates the request object and send the request object to the API endpoint
|
// Do generates the request object and send the request object to the API endpoint
|
||||||
func (p *PlaceOrderRequest) Do(ctx context.Context) (*PlaceOrderResponse, error) {
|
func (p *PlaceOrderRequest) Do(ctx context.Context) (*PlaceOrderResponse, error) {
|
||||||
|
if err := PlaceOrderRequestLimiter.Wait(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
params, err := p.GetParameters()
|
params, err := p.GetParameters()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -525,15 +555,29 @@ func (p *PlaceOrderRequest) Do(ctx context.Context) (*PlaceOrderResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
var apiResponse APIResponse
|
var apiResponse APIResponse
|
||||||
|
|
||||||
|
type responseUnmarshaler interface {
|
||||||
|
Unmarshal(data []byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
if unmarshaler, ok := interface{}(&apiResponse).(responseUnmarshaler); ok {
|
||||||
|
if err := unmarshaler.Unmarshal(response.Body); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The line below checks the content type, however, some API server might not send the correct content type header,
|
||||||
|
// Hence, this is commented for backward compatibility
|
||||||
|
// response.IsJSON()
|
||||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type responseValidator interface {
|
type responseValidator interface {
|
||||||
Validate() error
|
Validate() error
|
||||||
}
|
}
|
||||||
validator, ok := interface{}(apiResponse).(responseValidator)
|
|
||||||
if ok {
|
if validator, ok := interface{}(&apiResponse).(responseValidator); ok {
|
||||||
if err := validator.Validate(); err != nil {
|
if err := validator.Validate(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,4 +127,11 @@ const (
|
||||||
|
|
||||||
type AccountType string
|
type AccountType string
|
||||||
|
|
||||||
const AccountTypeSpot AccountType = "SPOT"
|
const AccountTypeUnified AccountType = "UNIFIED"
|
||||||
|
|
||||||
|
type MarketUnit string
|
||||||
|
|
||||||
|
const (
|
||||||
|
MarketUnitBase MarketUnit = "baseCoin"
|
||||||
|
MarketUnitQuote MarketUnit = "quoteCoin"
|
||||||
|
)
|
||||||
|
|
|
@ -76,9 +76,9 @@ func toGlobalOrder(order bybitapi.Order) (*types.Order, error) {
|
||||||
return nil, fmt.Errorf("unexpected order id: %s, err: %w", order.OrderId, err)
|
return nil, fmt.Errorf("unexpected order id: %s, err: %w", order.OrderId, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
qty, err := processMarketBuyQuantity(order)
|
price := order.Price
|
||||||
if err != nil {
|
if orderType == types.OrderTypeMarket {
|
||||||
return nil, err
|
price = order.AvgPrice
|
||||||
}
|
}
|
||||||
|
|
||||||
return &types.Order{
|
return &types.Order{
|
||||||
|
@ -87,8 +87,10 @@ func toGlobalOrder(order bybitapi.Order) (*types.Order, error) {
|
||||||
Symbol: order.Symbol,
|
Symbol: order.Symbol,
|
||||||
Side: side,
|
Side: side,
|
||||||
Type: orderType,
|
Type: orderType,
|
||||||
Quantity: qty,
|
// We specified the quantity for the market buy order as the base coin when we submitted the order,
|
||||||
Price: order.Price,
|
// so we can just use the Qty field.
|
||||||
|
Quantity: order.Qty,
|
||||||
|
Price: price,
|
||||||
TimeInForce: timeInForce,
|
TimeInForce: timeInForce,
|
||||||
},
|
},
|
||||||
Exchange: types.ExchangeBybit,
|
Exchange: types.ExchangeBybit,
|
||||||
|
@ -325,7 +327,7 @@ func v3ToGlobalTrade(trade v3.Trade) (*types.Trade, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func toGlobalTrade(trade bybitapi.Trade, feeDetail SymbolFeeDetail) (*types.Trade, error) {
|
func toGlobalTrade(trade bybitapi.Trade) (*types.Trade, error) {
|
||||||
side, err := toGlobalSideType(trade.Side)
|
side, err := toGlobalSideType(trade.Side)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unexpected side: %s, err: %w", trade.Side, err)
|
return nil, fmt.Errorf("unexpected side: %s, err: %w", trade.Side, err)
|
||||||
|
@ -339,8 +341,6 @@ func toGlobalTrade(trade bybitapi.Trade, feeDetail SymbolFeeDetail) (*types.Trad
|
||||||
return nil, fmt.Errorf("unexpected trade id: %s, err: %w", trade.ExecId, err)
|
return nil, fmt.Errorf("unexpected trade id: %s, err: %w", trade.ExecId, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fc, _ := calculateFee(trade, feeDetail)
|
|
||||||
|
|
||||||
return &types.Trade{
|
return &types.Trade{
|
||||||
ID: tradeIdNum,
|
ID: tradeIdNum,
|
||||||
OrderID: orderIdNum,
|
OrderID: orderIdNum,
|
||||||
|
@ -354,7 +354,7 @@ func toGlobalTrade(trade bybitapi.Trade, feeDetail SymbolFeeDetail) (*types.Trad
|
||||||
IsMaker: trade.IsMaker,
|
IsMaker: trade.IsMaker,
|
||||||
Time: types.Time(trade.ExecTime),
|
Time: types.Time(trade.ExecTime),
|
||||||
Fee: trade.ExecFee,
|
Fee: trade.ExecFee,
|
||||||
FeeCurrency: fc,
|
FeeCurrency: trade.FeeCurrency,
|
||||||
IsMargin: false,
|
IsMargin: false,
|
||||||
IsFutures: false,
|
IsFutures: false,
|
||||||
IsIsolated: false,
|
IsIsolated: false,
|
||||||
|
@ -364,14 +364,14 @@ func toGlobalTrade(trade bybitapi.Trade, feeDetail SymbolFeeDetail) (*types.Trad
|
||||||
func toGlobalBalanceMap(events []bybitapi.WalletBalances) types.BalanceMap {
|
func toGlobalBalanceMap(events []bybitapi.WalletBalances) types.BalanceMap {
|
||||||
bm := types.BalanceMap{}
|
bm := types.BalanceMap{}
|
||||||
for _, event := range events {
|
for _, event := range events {
|
||||||
if event.AccountType != bybitapi.AccountTypeSpot {
|
if event.AccountType != bybitapi.AccountTypeUnified {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, obj := range event.Coins {
|
for _, obj := range event.Coins {
|
||||||
bm[obj.Coin] = types.Balance{
|
bm[obj.Coin] = types.Balance{
|
||||||
Currency: obj.Coin,
|
Currency: obj.Coin,
|
||||||
Available: obj.Free,
|
Available: obj.WalletBalance.Sub(obj.Locked),
|
||||||
Locked: obj.Locked,
|
Locked: obj.Locked,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@ const (
|
||||||
defaultKLineLimit = 1000
|
defaultKLineLimit = 1000
|
||||||
|
|
||||||
queryTradeDurationLimit = 7 * 24 * time.Hour
|
queryTradeDurationLimit = 7 * 24 * time.Hour
|
||||||
|
|
||||||
|
maxHistoricalDataQueryPeriod = 2 * 365 * 24 * time.Hour
|
||||||
)
|
)
|
||||||
|
|
||||||
// https://bybit-exchange.github.io/docs/zh-TW/v5/rate-limit
|
// https://bybit-exchange.github.io/docs/zh-TW/v5/rate-limit
|
||||||
|
@ -32,9 +34,6 @@ var (
|
||||||
// The default order limiter apply 5 requests per second and a 5 initial bucket
|
// The default order limiter apply 5 requests per second and a 5 initial bucket
|
||||||
// this includes QueryMarkets, QueryTicker, QueryAccountBalances, GetFeeRates
|
// this includes QueryMarkets, QueryTicker, QueryAccountBalances, GetFeeRates
|
||||||
sharedRateLimiter = rate.NewLimiter(rate.Every(time.Second/5), 5)
|
sharedRateLimiter = rate.NewLimiter(rate.Every(time.Second/5), 5)
|
||||||
queryOrderTradeRateLimiter = rate.NewLimiter(rate.Every(time.Second/5), 5)
|
|
||||||
orderRateLimiter = rate.NewLimiter(rate.Every(time.Second/10), 10)
|
|
||||||
closedOrderQueryLimiter = rate.NewLimiter(rate.Every(time.Second), 1)
|
|
||||||
|
|
||||||
log = logrus.WithFields(logrus.Fields{
|
log = logrus.WithFields(logrus.Fields{
|
||||||
"exchange": "bybit",
|
"exchange": "bybit",
|
||||||
|
@ -163,18 +162,24 @@ func (e *Exchange) QueryTickers(ctx context.Context, symbols ...string) (map[str
|
||||||
return tickers, nil
|
return tickers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QueryOpenOrders queries open orders by symbol.
|
||||||
|
//
|
||||||
|
// Primarily query unfilled or partially filled orders in real-time, but also supports querying recent 500 closed status
|
||||||
|
// (Cancelled, Filled) orders. Please see the usage of request param openOnly.
|
||||||
|
// UTA2.0 can query filled, canceled, and rejected orders to the most recent 500 orders for spot, linear, inverse and
|
||||||
|
// option categories
|
||||||
|
//
|
||||||
|
// The records are sorted by the createdTime from newest to oldest.
|
||||||
func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders []types.Order, err error) {
|
func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders []types.Order, err error) {
|
||||||
cursor := ""
|
cursor := ""
|
||||||
|
// OpenOnlyOrder: UTA2.0, UTA1.0, classic account query open status orders (e.g., New, PartiallyFilled) only
|
||||||
|
req := e.client.NewGetOpenOrderRequest().Symbol(symbol).OpenOnly(bybitapi.OpenOnlyOrder).Limit(defaultQueryLimit)
|
||||||
for {
|
for {
|
||||||
req := e.client.NewGetOpenOrderRequest().Symbol(symbol)
|
|
||||||
if len(cursor) != 0 {
|
if len(cursor) != 0 {
|
||||||
// the default limit is 20.
|
// the default limit is 20.
|
||||||
req = req.Cursor(cursor)
|
req = req.Cursor(cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = queryOrderTradeRateLimiter.Wait(ctx); err != nil {
|
|
||||||
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: %w", err)
|
return nil, fmt.Errorf("failed to query open orders, err: %w", err)
|
||||||
|
@ -231,15 +236,17 @@ func (e *Exchange) QueryOrder(ctx context.Context, q types.OrderQuery) (*types.O
|
||||||
return toGlobalOrder(res.List[0])
|
return toGlobalOrder(res.List[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QueryOrderTrades You can query by symbol, baseCoin, orderId and orderLinkId, and if you pass multiple params,
|
||||||
|
// the system will process them according to this priority: orderId > orderLinkId > symbol > baseCoin.
|
||||||
func (e *Exchange) QueryOrderTrades(ctx context.Context, q types.OrderQuery) (trades []types.Trade, err error) {
|
func (e *Exchange) QueryOrderTrades(ctx context.Context, q types.OrderQuery) (trades []types.Trade, err error) {
|
||||||
|
req := e.client.NewGetExecutionListRequest()
|
||||||
if len(q.ClientOrderID) != 0 {
|
if len(q.ClientOrderID) != 0 {
|
||||||
log.Warn("!!!BYBIT EXCHANGE API NOTICE!!! Bybit does not support searching for trades using OrderClientId.")
|
req.OrderLinkId(q.ClientOrderID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(q.OrderID) == 0 {
|
if len(q.OrderID) != 0 {
|
||||||
return nil, errors.New("orderID is required parameter")
|
req.OrderLinkId(q.OrderID)
|
||||||
}
|
}
|
||||||
req := e.client.NewGetExecutionListRequest().OrderId(q.OrderID)
|
|
||||||
|
|
||||||
if len(q.Symbol) != 0 {
|
if len(q.Symbol) != 0 {
|
||||||
req.Symbol(q.Symbol)
|
req.Symbol(q.Symbol)
|
||||||
|
@ -270,23 +277,18 @@ func (e *Exchange) SubmitOrder(ctx context.Context, order types.SubmitOrder) (*t
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
req.Side(side)
|
req.Side(side)
|
||||||
|
req.Qty(order.Market.FormatQuantity(order.Quantity))
|
||||||
|
|
||||||
// set quantity
|
|
||||||
orderQty := 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
|
|
||||||
}
|
|
||||||
orderQty = order.Quantity.Mul(ticker.Buy)
|
|
||||||
}
|
|
||||||
req.Qty(order.Market.FormatQuantity(orderQty))
|
|
||||||
|
|
||||||
// set price
|
|
||||||
switch order.Type {
|
switch order.Type {
|
||||||
case types.OrderTypeLimit:
|
case types.OrderTypeStopLimit, types.OrderTypeLimit, types.OrderTypeLimitMaker:
|
||||||
req.Price(order.Market.FormatPrice(order.Price))
|
req.Price(order.Market.FormatPrice(order.Price))
|
||||||
|
case types.OrderTypeMarket:
|
||||||
|
// Because our order.Quantity unit is base coin, so we indicate the target currency to Base.
|
||||||
|
if order.Side == types.SideTypeBuy {
|
||||||
|
req.MarketUnit(bybitapi.MarketUnitBase)
|
||||||
|
} else {
|
||||||
|
req.MarketUnit(bybitapi.MarketUnitQuote)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// set timeInForce
|
// set timeInForce
|
||||||
|
@ -307,9 +309,6 @@ 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 {
|
|
||||||
return nil, fmt.Errorf("place order rate limiter wait error: %w", err)
|
|
||||||
}
|
|
||||||
timeNow := time.Now()
|
timeNow := time.Now()
|
||||||
res, err := req.Do(ctx)
|
res, err := req.Do(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -367,10 +366,6 @@ 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 {
|
|
||||||
errs = multierr.Append(errs, fmt.Errorf("cancel order rate limiter wait, order id: %s, error: %w", order.ClientOrderID, err))
|
|
||||||
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: %w", order.ClientOrderID, err))
|
errs = multierr.Append(errs, fmt.Errorf("failed to cancel order id: %s, err: %w", order.ClientOrderID, err))
|
||||||
|
@ -387,38 +382,68 @@ func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) (err
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QueryClosedOrders queries closed orders by symbol, since, until, and lastOrderID.
|
||||||
|
// startTime and endTime are not passed, return 7 days by default
|
||||||
|
// Only startTime is passed, return range between startTime and startTime+7 days
|
||||||
|
// Only endTime is passed, return range between endTime-7 days and endTime
|
||||||
|
// If both are passed, the rule is endTime - startTime <= 7 days
|
||||||
|
//
|
||||||
|
// ** since and until are inclusive. **
|
||||||
|
// ** sort by creation time in descending order. **
|
||||||
func (e *Exchange) QueryClosedOrders(
|
func (e *Exchange) QueryClosedOrders(
|
||||||
ctx context.Context, symbol string, since, util time.Time, lastOrderID uint64,
|
ctx context.Context, symbol string, since, until time.Time, _ uint64,
|
||||||
) (orders []types.Order, err error) {
|
) (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")
|
now := time.Now()
|
||||||
|
|
||||||
|
if time.Since(since) > maxHistoricalDataQueryPeriod {
|
||||||
|
newSince := now.Add(-maxHistoricalDataQueryPeriod)
|
||||||
|
log.Warnf("!!!BYBIT EXCHANGE API NOTICE!!! The closed order API cannot query data beyond 2 years from the current date, update %s -> %s", since, newSince)
|
||||||
|
since = newSince
|
||||||
|
}
|
||||||
|
if until.Before(since) {
|
||||||
|
newUntil := since.Add(queryTradeDurationLimit)
|
||||||
|
log.Warnf("!!!BYBIT EXCHANGE API NOTICE!!! The 'until' comes before 'since', add 7 days to until (%s -> %s).", until, newUntil)
|
||||||
|
until = newUntil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := closedOrderQueryLimiter.Wait(ctx); err != nil {
|
// if the time range exceeds the server boundary, get the last 7 days of data
|
||||||
return nil, fmt.Errorf("query closed order rate limiter wait error: %w", err)
|
if until.Sub(since) > queryTradeDurationLimit {
|
||||||
|
newStartTime := until.Add(-queryTradeDurationLimit)
|
||||||
|
|
||||||
|
log.Warnf("!!!BYBIT EXCHANGE API NOTICE!!! The time range exceeds the server boundary: %s, start time: %s, end time: %s, updated start time %s -> %s", queryTradeDurationLimit, since.String(), until.String(), since.String(), newStartTime.String())
|
||||||
|
since = newStartTime
|
||||||
}
|
}
|
||||||
res, err := e.client.NewGetOrderHistoriesRequest().
|
req := e.client.NewGetOrderHistoriesRequest().
|
||||||
Symbol(symbol).
|
Symbol(symbol).
|
||||||
Cursor(strconv.FormatUint(lastOrderID, 10)).
|
|
||||||
Limit(defaultQueryLimit).
|
Limit(defaultQueryLimit).
|
||||||
Do(ctx)
|
StartTime(since).
|
||||||
|
EndTime(until)
|
||||||
|
|
||||||
|
cursor := ""
|
||||||
|
for {
|
||||||
|
if len(cursor) != 0 {
|
||||||
|
req = req.Cursor(cursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := req.Do(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to call get order histories error: %w", err)
|
return nil, fmt.Errorf("failed to call get order histories error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, order := range res.List {
|
for _, order := range res.List {
|
||||||
o, err2 := toGlobalOrder(order)
|
order, err := toGlobalOrder(order)
|
||||||
if err2 != nil {
|
if err != nil {
|
||||||
err = multierr.Append(err, err2)
|
return nil, fmt.Errorf("failed to convert order, err: %v", err)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.Status.Closed() {
|
orders = append(orders, *order)
|
||||||
orders = append(orders, *o)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(res.NextPageCursor) == 0 {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
if err != nil {
|
cursor = res.NextPageCursor
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return types.SortOrdersAscending(orders), nil
|
return types.SortOrdersAscending(orders), nil
|
||||||
|
@ -437,11 +462,7 @@ func (e *Exchange) queryTrades(ctx context.Context, req *bybitapi.GetExecutionLi
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, trade := range res.List {
|
for _, trade := range res.List {
|
||||||
feeRate, err := pollAndGetFeeRate(ctx, trade.Symbol, e.FeeRatePoller, e.marketsInfo)
|
trade, err := toGlobalTrade(trade)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get fee rate, err: %v", err)
|
|
||||||
}
|
|
||||||
trade, err := toGlobalTrade(trade, feeRate)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to convert trade, err: %v", err)
|
return nil, fmt.Errorf("failed to convert trade, err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -438,14 +438,6 @@ func (s *Stream) handleKLineEvent(klineEvent KLineEvent) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func pollAndGetFeeRate(ctx context.Context, symbol string, poller FeeRatePoller, marketsInfo types.MarketMap) (SymbolFeeDetail, error) {
|
|
||||||
err := poller.PollFeeRate(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return SymbolFeeDetail{}, err
|
|
||||||
}
|
|
||||||
return getFeeRate(symbol, poller, marketsInfo), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFeeRate(symbol string, poller FeeRatePoller, marketsInfo types.MarketMap) SymbolFeeDetail {
|
func getFeeRate(symbol string, poller FeeRatePoller, marketsInfo types.MarketMap) SymbolFeeDetail {
|
||||||
feeRate, found := poller.GetFeeRate(symbol)
|
feeRate, found := poller.GetFeeRate(symbol)
|
||||||
if !found {
|
if !found {
|
||||||
|
|
|
@ -107,6 +107,7 @@ const (
|
||||||
TopicTypeWallet TopicType = "wallet"
|
TopicTypeWallet TopicType = "wallet"
|
||||||
TopicTypeOrder TopicType = "order"
|
TopicTypeOrder TopicType = "order"
|
||||||
TopicTypeKLine TopicType = "kline"
|
TopicTypeKLine TopicType = "kline"
|
||||||
|
TopicTypeFastTrade TopicType = "execution.fast"
|
||||||
TopicTypeTrade TopicType = "execution"
|
TopicTypeTrade TopicType = "execution"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -385,6 +386,11 @@ func (t *TradeEvent) toGlobalTrade(symbolFee SymbolFeeDetail) (*types.Trade, err
|
||||||
// IsMakerOrder = FALSE
|
// IsMakerOrder = FALSE
|
||||||
// -> Side = Buy -> base currency (BTC)
|
// -> Side = Buy -> base currency (BTC)
|
||||||
// -> Side = Sell -> quote currency (USDT)
|
// -> Side = Sell -> quote currency (USDT)
|
||||||
|
//
|
||||||
|
// The `execution.fast` topic doesn't support fee currency and execution fees, so we're calculating the transaction fees here.
|
||||||
|
// Although the `execution` topic is supported, it only covers the execution fee, so you'll still need to calculate the
|
||||||
|
// fee currency.
|
||||||
|
// Overall, I chose the one with lower latency (execution.fast) and calculated the fee myself.
|
||||||
func calculateFee(t bybitapi.Trade, feeDetail SymbolFeeDetail) (string, fixedpoint.Value) {
|
func calculateFee(t bybitapi.Trade, feeDetail SymbolFeeDetail) (string, fixedpoint.Value) {
|
||||||
if feeDetail.MakerFeeRate.Sign() > 0 || !t.IsMaker {
|
if feeDetail.MakerFeeRate.Sign() > 0 || !t.IsMaker {
|
||||||
if t.Side == bybitapi.SideBuy {
|
if t.Side == bybitapi.SideBuy {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user