mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-22 14:55:16 +00:00
pkg/exchange: add QueryOpenOrders API for bybit
This commit is contained in:
parent
ff78637c8f
commit
6d4deb54cc
|
@ -5,7 +5,7 @@ import (
|
|||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
@ -23,7 +23,7 @@ const defaultHTTPTimeout = time.Second * 15
|
|||
const RestBaseURL = "https://api.bybit.com"
|
||||
|
||||
// defaultRequestWindowMilliseconds specify how long an HTTP request is valid. It is also used to prevent replay attacks.
|
||||
var defaultRequestWindowMilliseconds = fmt.Sprintf("%d", time.Millisecond*5000)
|
||||
var defaultRequestWindowMilliseconds = fmt.Sprintf("%d", 5*time.Second.Milliseconds())
|
||||
|
||||
type RestClient struct {
|
||||
requestgen.BaseAPIClient
|
||||
|
@ -124,7 +124,7 @@ func sign(payload string, secret string) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
return base64.StdEncoding.EncodeToString(sig.Sum(nil))
|
||||
return hex.EncodeToString(sig.Sum(nil))
|
||||
}
|
||||
|
||||
func castPayload(payload interface{}) ([]byte, error) {
|
||||
|
|
|
@ -57,4 +57,24 @@ func TestClient(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
t.Logf("tickers: %+v", tickers)
|
||||
})
|
||||
|
||||
t.Run("GetOpenOrderRequest", func(t *testing.T) {
|
||||
cursor := ""
|
||||
for {
|
||||
req := client.NewGetOpenOrderRequest().Limit(1)
|
||||
if len(cursor) != 0 {
|
||||
req = req.Cursor(cursor)
|
||||
}
|
||||
openOrders, err := req.Do(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, o := range openOrders.List {
|
||||
t.Logf("openOrders: %+v", o)
|
||||
}
|
||||
if len(openOrders.NextPageCursor) == 0 {
|
||||
break
|
||||
}
|
||||
cursor = openOrders.NextPageCursor
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
83
pkg/exchange/bybit/bybitapi/get_open_order_request.go
Normal file
83
pkg/exchange/bybit/bybitapi/get_open_order_request.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
package bybitapi
|
||||
|
||||
import (
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
"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
|
||||
|
||||
type OpenOrdersResponse struct {
|
||||
List []OpenOrder `json:"list"`
|
||||
NextPageCursor string `json:"nextPageCursor"`
|
||||
Category string `json:"category"`
|
||||
}
|
||||
|
||||
type OpenOrder struct {
|
||||
OrderId string `json:"orderId"`
|
||||
OrderLinkId string `json:"orderLinkId"`
|
||||
BlockTradeId string `json:"blockTradeId"`
|
||||
Symbol string `json:"symbol"`
|
||||
Price fixedpoint.Value `json:"price"`
|
||||
Qty fixedpoint.Value `json:"qty"`
|
||||
Side Side `json:"side"`
|
||||
IsLeverage string `json:"isLeverage"`
|
||||
PositionIdx int `json:"positionIdx"`
|
||||
OrderStatus OrderStatus `json:"orderStatus"`
|
||||
CancelType string `json:"cancelType"`
|
||||
RejectReason string `json:"rejectReason"`
|
||||
AvgPrice fixedpoint.Value `json:"avgPrice"`
|
||||
LeavesQty fixedpoint.Value `json:"leavesQty"`
|
||||
LeavesValue fixedpoint.Value `json:"leavesValue"`
|
||||
CumExecQty fixedpoint.Value `json:"cumExecQty"`
|
||||
CumExecValue fixedpoint.Value `json:"cumExecValue"`
|
||||
CumExecFee fixedpoint.Value `json:"cumExecFee"`
|
||||
TimeInForce TimeInForce `json:"timeInForce"`
|
||||
OrderType OrderType `json:"orderType"`
|
||||
StopOrderType string `json:"stopOrderType"`
|
||||
OrderIv string `json:"orderIv"`
|
||||
TriggerPrice fixedpoint.Value `json:"triggerPrice"`
|
||||
TakeProfit fixedpoint.Value `json:"takeProfit"`
|
||||
StopLoss fixedpoint.Value `json:"stopLoss"`
|
||||
TpTriggerBy string `json:"tpTriggerBy"`
|
||||
SlTriggerBy string `json:"slTriggerBy"`
|
||||
TriggerDirection int `json:"triggerDirection"`
|
||||
TriggerBy string `json:"triggerBy"`
|
||||
LastPriceOnCreated string `json:"lastPriceOnCreated"`
|
||||
ReduceOnly bool `json:"reduceOnly"`
|
||||
CloseOnTrigger bool `json:"closeOnTrigger"`
|
||||
SmpType string `json:"smpType"`
|
||||
SmpGroup int `json:"smpGroup"`
|
||||
SmpOrderId string `json:"smpOrderId"`
|
||||
TpslMode string `json:"tpslMode"`
|
||||
TpLimitPrice string `json:"tpLimitPrice"`
|
||||
SlLimitPrice string `json:"slLimitPrice"`
|
||||
PlaceType string `json:"placeType"`
|
||||
CreatedTime types.MillisecondTimestamp `json:"createdTime"`
|
||||
UpdatedTime types.MillisecondTimestamp `json:"updatedTime"`
|
||||
}
|
||||
|
||||
//go:generate GetRequest -url "/v5/order/realtime" -type GetOpenOrdersRequest -responseDataType .OpenOrdersResponse
|
||||
type GetOpenOrdersRequest struct {
|
||||
client requestgen.AuthenticatedAPIClient
|
||||
|
||||
category Category `param:"category,query" validValues:"spot"`
|
||||
symbol *string `param:"symbol,query"`
|
||||
baseCoin *string `param:"baseCoin,query"`
|
||||
settleCoin *string `param:"settleCoin,query"`
|
||||
orderId *string `param:"orderId,query"`
|
||||
orderLinkId *string `param:"orderLinkId,query"`
|
||||
openOnly *OpenOnly `param:"openOnly,query" validValues:"0"`
|
||||
orderFilter *string `param:"orderFilter,query"`
|
||||
limit *uint64 `param:"limit,query"`
|
||||
cursor *string `param:"cursor,query"`
|
||||
}
|
||||
|
||||
func (c *RestClient) NewGetOpenOrderRequest() *GetOpenOrdersRequest {
|
||||
return &GetOpenOrdersRequest{
|
||||
client: c,
|
||||
category: CategorySpot,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,291 @@
|
|||
// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Result -url /v5/order/realtime -type GetOpenOrdersRequest -responseDataType .OpenOrdersResponse"; DO NOT EDIT.
|
||||
|
||||
package bybitapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
func (g *GetOpenOrdersRequest) Category(category Category) *GetOpenOrdersRequest {
|
||||
g.category = category
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *GetOpenOrdersRequest) Symbol(symbol string) *GetOpenOrdersRequest {
|
||||
g.symbol = &symbol
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *GetOpenOrdersRequest) BaseCoin(baseCoin string) *GetOpenOrdersRequest {
|
||||
g.baseCoin = &baseCoin
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *GetOpenOrdersRequest) SettleCoin(settleCoin string) *GetOpenOrdersRequest {
|
||||
g.settleCoin = &settleCoin
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *GetOpenOrdersRequest) OrderId(orderId string) *GetOpenOrdersRequest {
|
||||
g.orderId = &orderId
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *GetOpenOrdersRequest) OrderLinkId(orderLinkId string) *GetOpenOrdersRequest {
|
||||
g.orderLinkId = &orderLinkId
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *GetOpenOrdersRequest) OpenOnly(openOnly OpenOnly) *GetOpenOrdersRequest {
|
||||
g.openOnly = &openOnly
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *GetOpenOrdersRequest) OrderFilter(orderFilter string) *GetOpenOrdersRequest {
|
||||
g.orderFilter = &orderFilter
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *GetOpenOrdersRequest) Limit(limit uint64) *GetOpenOrdersRequest {
|
||||
g.limit = &limit
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *GetOpenOrdersRequest) Cursor(cursor string) *GetOpenOrdersRequest {
|
||||
g.cursor = &cursor
|
||||
return g
|
||||
}
|
||||
|
||||
// GetQueryParameters builds and checks the query parameters and returns url.Values
|
||||
func (g *GetOpenOrdersRequest) 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 baseCoin field -> json key baseCoin
|
||||
if g.baseCoin != nil {
|
||||
baseCoin := *g.baseCoin
|
||||
|
||||
// assign parameter of baseCoin
|
||||
params["baseCoin"] = baseCoin
|
||||
} else {
|
||||
}
|
||||
// check settleCoin field -> json key settleCoin
|
||||
if g.settleCoin != nil {
|
||||
settleCoin := *g.settleCoin
|
||||
|
||||
// assign parameter of settleCoin
|
||||
params["settleCoin"] = settleCoin
|
||||
} else {
|
||||
}
|
||||
// check orderId field -> json key orderId
|
||||
if g.orderId != nil {
|
||||
orderId := *g.orderId
|
||||
|
||||
// assign parameter of orderId
|
||||
params["orderId"] = orderId
|
||||
} else {
|
||||
}
|
||||
// check orderLinkId field -> json key orderLinkId
|
||||
if g.orderLinkId != nil {
|
||||
orderLinkId := *g.orderLinkId
|
||||
|
||||
// assign parameter of orderLinkId
|
||||
params["orderLinkId"] = orderLinkId
|
||||
} else {
|
||||
}
|
||||
// check openOnly field -> json key openOnly
|
||||
if g.openOnly != nil {
|
||||
openOnly := *g.openOnly
|
||||
|
||||
// TEMPLATE check-valid-values
|
||||
switch openOnly {
|
||||
case OpenOnlyOrder:
|
||||
params["openOnly"] = openOnly
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("openOnly value %v is invalid", openOnly)
|
||||
|
||||
}
|
||||
// END TEMPLATE check-valid-values
|
||||
|
||||
// assign parameter of openOnly
|
||||
params["openOnly"] = openOnly
|
||||
} else {
|
||||
}
|
||||
// check orderFilter field -> json key orderFilter
|
||||
if g.orderFilter != nil {
|
||||
orderFilter := *g.orderFilter
|
||||
|
||||
// assign parameter of orderFilter
|
||||
params["orderFilter"] = orderFilter
|
||||
} 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 *GetOpenOrdersRequest) 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 *GetOpenOrdersRequest) 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 *GetOpenOrdersRequest) 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 *GetOpenOrdersRequest) GetSlugParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (g *GetOpenOrdersRequest) 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 *GetOpenOrdersRequest) 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 *GetOpenOrdersRequest) isVarSlice(_v interface{}) bool {
|
||||
rt := reflect.TypeOf(_v)
|
||||
switch rt.Kind() {
|
||||
case reflect.Slice:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (g *GetOpenOrdersRequest) 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 *GetOpenOrdersRequest) Do(ctx context.Context) (*OpenOrdersResponse, error) {
|
||||
|
||||
// no body params
|
||||
var params interface{}
|
||||
query, err := g.GetQueryParameters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apiURL := "/v5/order/realtime"
|
||||
|
||||
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 OpenOrdersResponse
|
||||
if err := json.Unmarshal(apiResponse.Result, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &data, nil
|
||||
}
|
|
@ -12,3 +12,78 @@ const (
|
|||
// StatusTrading is only include the "Trading" status for `spot` category.
|
||||
StatusTrading Status = "Trading"
|
||||
)
|
||||
|
||||
type OpenOnly int
|
||||
|
||||
const (
|
||||
OpenOnlyOrder OpenOnly = 0
|
||||
)
|
||||
|
||||
type Side string
|
||||
|
||||
const (
|
||||
SideBuy Side = "Buy"
|
||||
SideSell Side = "Sell"
|
||||
)
|
||||
|
||||
type OrderStatus string
|
||||
|
||||
const (
|
||||
// OrderStatusCreated order has been accepted by the system but not yet put through the matching engine
|
||||
OrderStatusCreated OrderStatus = "Created"
|
||||
// OrderStatusNew is order has been placed successfully.
|
||||
OrderStatusNew OrderStatus = "New"
|
||||
OrderStatusRejected OrderStatus = "Rejected"
|
||||
OrderStatusPartiallyFilled OrderStatus = "PartiallyFilled"
|
||||
OrderStatusPartiallyFilledCanceled OrderStatus = "PartiallyFilledCanceled"
|
||||
OrderStatusFilled OrderStatus = "Filled"
|
||||
OrderStatusCancelled OrderStatus = "Cancelled"
|
||||
|
||||
// Following statuses is conditional orders. Once you place conditional orders, it will be in untriggered status.
|
||||
// Untriggered -> Triggered -> New
|
||||
// Once the trigger price reached, order status will be moved to triggered
|
||||
// Singe BBGO not support Untriggered/Triggered, so comment it.
|
||||
//
|
||||
// OrderStatusUntriggered means that the order not triggered
|
||||
//OrderStatusUntriggered OrderStatus = "Untriggered"
|
||||
//// OrderStatusTriggered means that the order has been triggered
|
||||
//OrderStatusTriggered OrderStatus = "Triggered"
|
||||
|
||||
// Following statuses is stop orders
|
||||
// OrderStatusDeactivated is an order status for stopOrders.
|
||||
//e.g. when you place a conditional order, then you cancel it, this order status is "Deactivated"
|
||||
OrderStatusDeactivated OrderStatus = "Deactivated"
|
||||
|
||||
// OrderStatusActive order has been triggered and the new active order has been successfully placed. Is the final
|
||||
// state of a successful conditional order
|
||||
OrderStatusActive OrderStatus = "Active"
|
||||
)
|
||||
|
||||
var (
|
||||
AllOrderStatuses = []OrderStatus{
|
||||
OrderStatusCreated,
|
||||
OrderStatusNew,
|
||||
OrderStatusRejected,
|
||||
OrderStatusPartiallyFilled,
|
||||
OrderStatusPartiallyFilledCanceled,
|
||||
OrderStatusFilled,
|
||||
OrderStatusCancelled,
|
||||
OrderStatusDeactivated,
|
||||
OrderStatusActive,
|
||||
}
|
||||
)
|
||||
|
||||
type OrderType string
|
||||
|
||||
const (
|
||||
OrderTypeMarket OrderType = "Market"
|
||||
OrderTypeLimit OrderType = "Limit"
|
||||
)
|
||||
|
||||
type TimeInForce string
|
||||
|
||||
const (
|
||||
TimeInForceGTC TimeInForce = "GTC"
|
||||
TimeInForceIOC TimeInForce = "IOC"
|
||||
TimeInForceFOK TimeInForce = "FOK"
|
||||
)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package bybit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
|
@ -43,3 +45,128 @@ func toGlobalTicker(stats bybitapi.Ticker, time time.Time) types.Ticker {
|
|||
Time: time,
|
||||
}
|
||||
}
|
||||
|
||||
func toGlobalOrder(order bybitapi.OpenOrder) (*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
|
||||
}
|
||||
timeInForce, err := toGlobalTimeInForce(order.TimeInForce)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
status, err := toGlobalOrderStatus(order.OrderStatus)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
working, err := isWorking(order.OrderStatus)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.Order{
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
ClientOrderID: order.OrderLinkId,
|
||||
Symbol: order.Symbol,
|
||||
Side: side,
|
||||
Type: orderType,
|
||||
Quantity: order.Qty,
|
||||
Price: order.Price,
|
||||
TimeInForce: timeInForce,
|
||||
},
|
||||
Exchange: types.ExchangeBybit,
|
||||
OrderID: hashStringID(order.OrderId),
|
||||
UUID: order.OrderId,
|
||||
Status: status,
|
||||
ExecutedQuantity: order.CumExecQty,
|
||||
IsWorking: working,
|
||||
CreationTime: types.Time(order.CreatedTime.Time()),
|
||||
UpdateTime: types.Time(order.UpdatedTime.Time()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toGlobalSideType(side bybitapi.Side) (types.SideType, error) {
|
||||
switch side {
|
||||
case bybitapi.SideBuy:
|
||||
return types.SideTypeBuy, nil
|
||||
|
||||
case bybitapi.SideSell:
|
||||
return types.SideTypeSell, nil
|
||||
|
||||
default:
|
||||
return types.SideType(side), fmt.Errorf("unexpected side: %s", side)
|
||||
}
|
||||
}
|
||||
|
||||
func toGlobalOrderType(s bybitapi.OrderType) (types.OrderType, error) {
|
||||
switch s {
|
||||
case bybitapi.OrderTypeMarket:
|
||||
return types.OrderTypeMarket, nil
|
||||
|
||||
case bybitapi.OrderTypeLimit:
|
||||
return types.OrderTypeLimit, nil
|
||||
|
||||
default:
|
||||
return types.OrderType(s), fmt.Errorf("unexpected order type: %s", s)
|
||||
}
|
||||
}
|
||||
|
||||
func toGlobalTimeInForce(force bybitapi.TimeInForce) (types.TimeInForce, error) {
|
||||
switch force {
|
||||
case bybitapi.TimeInForceGTC:
|
||||
return types.TimeInForceGTC, nil
|
||||
|
||||
case bybitapi.TimeInForceIOC:
|
||||
return types.TimeInForceIOC, nil
|
||||
|
||||
case bybitapi.TimeInForceFOK:
|
||||
return types.TimeInForceFOK, nil
|
||||
|
||||
default:
|
||||
return types.TimeInForce(force), fmt.Errorf("unexpected timeInForce type: %s", force)
|
||||
}
|
||||
}
|
||||
|
||||
func toGlobalOrderStatus(status bybitapi.OrderStatus) (types.OrderStatus, error) {
|
||||
switch status {
|
||||
case bybitapi.OrderStatusCreated,
|
||||
bybitapi.OrderStatusNew,
|
||||
bybitapi.OrderStatusActive:
|
||||
return types.OrderStatusNew, nil
|
||||
|
||||
case bybitapi.OrderStatusFilled:
|
||||
return types.OrderStatusFilled, nil
|
||||
|
||||
case bybitapi.OrderStatusPartiallyFilled:
|
||||
return types.OrderStatusPartiallyFilled, nil
|
||||
|
||||
case bybitapi.OrderStatusCancelled,
|
||||
bybitapi.OrderStatusPartiallyFilledCanceled,
|
||||
bybitapi.OrderStatusDeactivated:
|
||||
return types.OrderStatusCanceled, nil
|
||||
|
||||
case bybitapi.OrderStatusRejected:
|
||||
return types.OrderStatusRejected, nil
|
||||
|
||||
default:
|
||||
// following not supported
|
||||
// bybitapi.OrderStatusUntriggered
|
||||
// bybitapi.OrderStatusTriggered
|
||||
return types.OrderStatus(status), fmt.Errorf("unexpected order status: %s", status)
|
||||
}
|
||||
}
|
||||
|
||||
func hashStringID(s string) uint64 {
|
||||
h := fnv.New64a()
|
||||
h.Write([]byte(s))
|
||||
return h.Sum64()
|
||||
}
|
||||
|
||||
func isWorking(status bybitapi.OrderStatus) (bool, error) {
|
||||
s, err := toGlobalOrderStatus(status)
|
||||
return s == types.OrderStatusNew || s == types.OrderStatusPartiallyFilled, err
|
||||
}
|
||||
|
|
|
@ -130,3 +130,229 @@ func TestToGlobalTicker(t *testing.T) {
|
|||
|
||||
assert.Equal(t, toGlobalTicker(ticker, timeNow), exp)
|
||||
}
|
||||
|
||||
func TestToGlobalOrder(t *testing.T) {
|
||||
// sample: partialFilled
|
||||
//{
|
||||
// "OrderId": 1472539279335923200,
|
||||
// "OrderLinkId": 1690276361150,
|
||||
// "BlockTradeId": null,
|
||||
// "Symbol": "DOTUSDT",
|
||||
// "Price": 7.278,
|
||||
// "Qty": 0.8,
|
||||
// "Side": "Sell",
|
||||
// "IsLeverage": 0,
|
||||
// "PositionIdx": 0,
|
||||
// "OrderStatus": "PartiallyFilled",
|
||||
// "CancelType": "UNKNOWN",
|
||||
// "RejectReason": null,
|
||||
// "AvgPrice": 7.278,
|
||||
// "LeavesQty": 0,
|
||||
// "LeavesValue": 0,
|
||||
// "CumExecQty": 0.5,
|
||||
// "CumExecValue": 0,
|
||||
// "CumExecFee": 0,
|
||||
// "TimeInForce": "GTC",
|
||||
// "OrderType": "Limit",
|
||||
// "StopOrderType": null,
|
||||
// "OrderIv": null,
|
||||
// "TriggerPrice": 0,
|
||||
// "TakeProfit": 0,
|
||||
// "StopLoss": 0,
|
||||
// "TpTriggerBy": null,
|
||||
// "SlTriggerBy": null,
|
||||
// "TriggerDirection": 0,
|
||||
// "TriggerBy": null,
|
||||
// "LastPriceOnCreated": null,
|
||||
// "ReduceOnly": false,
|
||||
// "CloseOnTrigger": false,
|
||||
// "SmpType": "None",
|
||||
// "SmpGroup": 0,
|
||||
// "SmpOrderId": null,
|
||||
// "TpslMode": null,
|
||||
// "TpLimitPrice": null,
|
||||
// "SlLimitPrice": null,
|
||||
// "PlaceType": null,
|
||||
// "CreatedTime": "2023-07-25 17:12:41.325 +0800 CST",
|
||||
// "UpdatedTime": "2023-07-25 17:12:57.868 +0800 CST"
|
||||
//}
|
||||
timeNow := time.Now()
|
||||
openOrder := bybitapi.OpenOrder{
|
||||
OrderId: "1472539279335923200",
|
||||
OrderLinkId: "1690276361150",
|
||||
BlockTradeId: "",
|
||||
Symbol: "DOTUSDT",
|
||||
Price: fixedpoint.NewFromFloat(7.278),
|
||||
Qty: fixedpoint.NewFromFloat(0.8),
|
||||
Side: bybitapi.SideSell,
|
||||
IsLeverage: "0",
|
||||
PositionIdx: 0,
|
||||
OrderStatus: bybitapi.OrderStatusPartiallyFilled,
|
||||
CancelType: "UNKNOWN",
|
||||
RejectReason: "",
|
||||
AvgPrice: fixedpoint.NewFromFloat(7.728),
|
||||
LeavesQty: fixedpoint.NewFromFloat(0),
|
||||
LeavesValue: fixedpoint.NewFromFloat(0),
|
||||
CumExecQty: fixedpoint.NewFromFloat(0.5),
|
||||
CumExecValue: fixedpoint.NewFromFloat(0),
|
||||
CumExecFee: fixedpoint.NewFromFloat(0),
|
||||
TimeInForce: "GTC",
|
||||
OrderType: bybitapi.OrderTypeLimit,
|
||||
StopOrderType: "",
|
||||
OrderIv: "",
|
||||
TriggerPrice: fixedpoint.NewFromFloat(0),
|
||||
TakeProfit: fixedpoint.NewFromFloat(0),
|
||||
StopLoss: fixedpoint.NewFromFloat(0),
|
||||
TpTriggerBy: "",
|
||||
SlTriggerBy: "",
|
||||
TriggerDirection: 0,
|
||||
TriggerBy: "",
|
||||
LastPriceOnCreated: "",
|
||||
ReduceOnly: false,
|
||||
CloseOnTrigger: false,
|
||||
SmpType: "None",
|
||||
SmpGroup: 0,
|
||||
SmpOrderId: "",
|
||||
TpslMode: "",
|
||||
TpLimitPrice: "",
|
||||
SlLimitPrice: "",
|
||||
PlaceType: "",
|
||||
CreatedTime: types.MillisecondTimestamp(timeNow),
|
||||
UpdatedTime: types.MillisecondTimestamp(timeNow),
|
||||
}
|
||||
side, err := toGlobalSideType(openOrder.Side)
|
||||
assert.NoError(t, err)
|
||||
orderType, err := toGlobalOrderType(openOrder.OrderType)
|
||||
assert.NoError(t, err)
|
||||
tif, err := toGlobalTimeInForce(openOrder.TimeInForce)
|
||||
assert.NoError(t, err)
|
||||
status, err := toGlobalOrderStatus(openOrder.OrderStatus)
|
||||
assert.NoError(t, err)
|
||||
working, err := isWorking(openOrder.OrderStatus)
|
||||
assert.NoError(t, err)
|
||||
|
||||
exp := types.Order{
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
ClientOrderID: openOrder.OrderLinkId,
|
||||
Symbol: openOrder.Symbol,
|
||||
Side: side,
|
||||
Type: orderType,
|
||||
Quantity: openOrder.Qty,
|
||||
Price: openOrder.Price,
|
||||
TimeInForce: tif,
|
||||
},
|
||||
Exchange: types.ExchangeBybit,
|
||||
OrderID: hashStringID(openOrder.OrderId),
|
||||
UUID: openOrder.OrderId,
|
||||
Status: status,
|
||||
ExecutedQuantity: openOrder.CumExecQty,
|
||||
IsWorking: working,
|
||||
CreationTime: types.Time(timeNow),
|
||||
UpdateTime: types.Time(timeNow),
|
||||
IsFutures: false,
|
||||
IsMargin: false,
|
||||
IsIsolated: false,
|
||||
}
|
||||
res, err := toGlobalOrder(openOrder)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, res, &exp)
|
||||
}
|
||||
|
||||
func TestToGlobalSideType(t *testing.T) {
|
||||
res, err := toGlobalSideType(bybitapi.SideBuy)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, types.SideTypeBuy, res)
|
||||
|
||||
res, err = toGlobalSideType(bybitapi.SideSell)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, types.SideTypeSell, res)
|
||||
|
||||
res, err = toGlobalSideType("GG")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestToGlobalOrderType(t *testing.T) {
|
||||
res, err := toGlobalOrderType(bybitapi.OrderTypeMarket)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, types.OrderTypeMarket, res)
|
||||
|
||||
res, err = toGlobalOrderType(bybitapi.OrderTypeLimit)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, types.OrderTypeLimit, res)
|
||||
|
||||
res, err = toGlobalOrderType("GG")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestToGlobalTimeInForce(t *testing.T) {
|
||||
res, err := toGlobalTimeInForce(bybitapi.TimeInForceGTC)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, types.TimeInForceGTC, res)
|
||||
|
||||
res, err = toGlobalTimeInForce(bybitapi.TimeInForceIOC)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, types.TimeInForceIOC, res)
|
||||
|
||||
res, err = toGlobalTimeInForce(bybitapi.TimeInForceFOK)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, types.TimeInForceFOK, res)
|
||||
|
||||
res, err = toGlobalTimeInForce("GG")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestToGlobalOrderStatus(t *testing.T) {
|
||||
t.Run("New", func(t *testing.T) {
|
||||
res, err := toGlobalOrderStatus(bybitapi.OrderStatusNew)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, types.OrderStatusNew, res)
|
||||
|
||||
res, err = toGlobalOrderStatus(bybitapi.OrderStatusActive)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, types.OrderStatusNew, res)
|
||||
})
|
||||
|
||||
t.Run("Filled", func(t *testing.T) {
|
||||
res, err := toGlobalOrderStatus(bybitapi.OrderStatusFilled)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, types.OrderStatusFilled, res)
|
||||
})
|
||||
|
||||
t.Run("PartiallyFilled", func(t *testing.T) {
|
||||
res, err := toGlobalOrderStatus(bybitapi.OrderStatusPartiallyFilled)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, types.OrderStatusPartiallyFilled, res)
|
||||
})
|
||||
|
||||
t.Run("OrderStatusCanceled", func(t *testing.T) {
|
||||
res, err := toGlobalOrderStatus(bybitapi.OrderStatusCancelled)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, types.OrderStatusCanceled, res)
|
||||
|
||||
res, err = toGlobalOrderStatus(bybitapi.OrderStatusPartiallyFilledCanceled)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, types.OrderStatusCanceled, res)
|
||||
|
||||
res, err = toGlobalOrderStatus(bybitapi.OrderStatusDeactivated)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, types.OrderStatusCanceled, res)
|
||||
})
|
||||
|
||||
t.Run("OrderStatusRejected", func(t *testing.T) {
|
||||
res, err := toGlobalOrderStatus(bybitapi.OrderStatusRejected)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, types.OrderStatusRejected, res)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIsWorking(t *testing.T) {
|
||||
for _, s := range bybitapi.AllOrderStatuses {
|
||||
res, err := isWorking(s)
|
||||
assert.NoError(t, err)
|
||||
if res {
|
||||
gos, err := toGlobalOrderStatus(s)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, gos == types.OrderStatusNew || gos == types.OrderStatusPartiallyFilled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,11 +17,14 @@ import (
|
|||
//
|
||||
// The default order limiter apply 2 requests per second and a 2 initial bucket
|
||||
// this includes QueryMarkets, QueryTicker
|
||||
var sharedRateLimiter = rate.NewLimiter(rate.Every(time.Second/2), 2)
|
||||
var (
|
||||
sharedRateLimiter = rate.NewLimiter(rate.Every(time.Second/2), 2)
|
||||
tradeRateLimiter = rate.NewLimiter(rate.Every(time.Second/5), 5)
|
||||
|
||||
var log = logrus.WithFields(logrus.Fields{
|
||||
log = logrus.WithFields(logrus.Fields{
|
||||
"exchange": "bybit",
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
type Exchange struct {
|
||||
key, secret string
|
||||
|
@ -128,3 +131,41 @@ func (e *Exchange) QueryTickers(ctx context.Context, symbols ...string) (map[str
|
|||
|
||||
return tickers, nil
|
||||
}
|
||||
|
||||
func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders []types.Order, err error) {
|
||||
cursor := ""
|
||||
for {
|
||||
req := e.client.NewGetOpenOrderRequest().Symbol(symbol)
|
||||
if len(cursor) != 0 {
|
||||
// the default limit is 20.
|
||||
req = req.Cursor(cursor)
|
||||
}
|
||||
|
||||
if err = tradeRateLimiter.Wait(ctx); err != nil {
|
||||
log.WithError(err).Errorf("trade rate limiter wait error")
|
||||
return nil, err
|
||||
}
|
||||
res, err := req.Do(ctx)
|
||||
if err != nil {
|
||||
log.Warnf("failed to get open order, cursor: %s, err: %v", cursor, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, order := range res.List {
|
||||
order, err := toGlobalOrder(order)
|
||||
if err != nil {
|
||||
log.Warnf("failed to convert order, err: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orders = append(orders, *order)
|
||||
}
|
||||
|
||||
if len(res.NextPageCursor) == 0 {
|
||||
break
|
||||
}
|
||||
cursor = res.NextPageCursor
|
||||
}
|
||||
|
||||
return orders, nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user