mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-22 06:53:52 +00:00
pkg/exchange: support place order for bybit
This commit is contained in:
parent
3fd66199d7
commit
151e8d2acf
|
@ -6,6 +6,7 @@ import (
|
|||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/testutil"
|
||||
|
@ -77,4 +78,23 @@ func TestClient(t *testing.T) {
|
|||
cursor = openOrders.NextPageCursor
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("PostPlaceOrderRequest", 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])
|
||||
})
|
||||
}
|
||||
|
|
57
pkg/exchange/bybit/bybitapi/post_place_order_request.go
Normal file
57
pkg/exchange/bybit/bybitapi/post_place_order_request.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package bybitapi
|
||||
|
||||
import (
|
||||
"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 PlaceOrderResponse struct {
|
||||
OrderId string `json:"orderId"`
|
||||
OrderLinkId string `json:"orderLinkId"`
|
||||
}
|
||||
|
||||
//go:generate PostRequest -url "/v5/order/create" -type PostPlaceOrderRequest -responseDataType .PlaceOrderResponse
|
||||
type PostPlaceOrderRequest struct {
|
||||
client requestgen.AuthenticatedAPIClient
|
||||
|
||||
category Category `param:"category" validValues:"spot"`
|
||||
symbol string `param:"symbol"`
|
||||
side Side `param:"side" validValues:"Buy,Sell"`
|
||||
orderType OrderType `param:"orderType" validValues:"Market,Limit"`
|
||||
qty string `param:"qty"`
|
||||
orderLinkId string `param:"orderLinkId"`
|
||||
timeInForce TimeInForce `param:"timeInForce"`
|
||||
|
||||
isLeverage *bool `param:"isLeverage"`
|
||||
price *string `param:"price"`
|
||||
triggerDirection *int `param:"triggerDirection"`
|
||||
// orderFilter default spot
|
||||
orderFilter *string `param:"orderFilter"`
|
||||
// triggerPrice when submitting an order, if triggerPrice is set, the order will be automatically converted into a conditional order.
|
||||
triggerPrice *string `param:"triggerPrice"`
|
||||
triggerBy *string `param:"triggerBy"`
|
||||
orderIv *string `param:"orderIv"`
|
||||
positionIdx *string `param:"positionIdx"`
|
||||
takeProfit *string `param:"takeProfit"`
|
||||
stopLoss *string `param:"stopLoss"`
|
||||
tpTriggerBy *string `param:"tpTriggerBy"`
|
||||
slTriggerBy *string `param:"slTriggerBy"`
|
||||
reduceOnly *bool `param:"reduceOnly"`
|
||||
closeOnTrigger *bool `param:"closeOnTrigger"`
|
||||
smpType *string `param:"smpType"`
|
||||
mmp *bool `param:"mmp"` // option only
|
||||
tpslMode *string `param:"tpslMode"`
|
||||
tpLimitPrice *string `param:"tpLimitPrice"`
|
||||
slLimitPrice *string `param:"slLimitPrice"`
|
||||
tpOrderType *string `param:"tpOrderType"`
|
||||
slOrderType *string `param:"slOrderType"`
|
||||
}
|
||||
|
||||
func (c *RestClient) NewPlaceOrderRequest() *PostPlaceOrderRequest {
|
||||
return &PostPlaceOrderRequest{
|
||||
client: c,
|
||||
category: CategorySpot,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,528 @@
|
|||
// Code generated by "requestgen -method POST -responseType .APIResponse -responseDataField Result -url /v5/order/create -type PostPlaceOrderRequest -responseDataType .PlaceOrderResponse"; DO NOT EDIT.
|
||||
|
||||
package bybitapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
func (p *PostPlaceOrderRequest) Category(category Category) *PostPlaceOrderRequest {
|
||||
p.category = category
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) Symbol(symbol string) *PostPlaceOrderRequest {
|
||||
p.symbol = symbol
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) Side(side Side) *PostPlaceOrderRequest {
|
||||
p.side = side
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) OrderType(orderType OrderType) *PostPlaceOrderRequest {
|
||||
p.orderType = orderType
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) Qty(qty string) *PostPlaceOrderRequest {
|
||||
p.qty = qty
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) OrderLinkId(orderLinkId string) *PostPlaceOrderRequest {
|
||||
p.orderLinkId = orderLinkId
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) TimeInForce(timeInForce TimeInForce) *PostPlaceOrderRequest {
|
||||
p.timeInForce = timeInForce
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) IsLeverage(isLeverage bool) *PostPlaceOrderRequest {
|
||||
p.isLeverage = &isLeverage
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) Price(price string) *PostPlaceOrderRequest {
|
||||
p.price = &price
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) TriggerDirection(triggerDirection int) *PostPlaceOrderRequest {
|
||||
p.triggerDirection = &triggerDirection
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) OrderFilter(orderFilter string) *PostPlaceOrderRequest {
|
||||
p.orderFilter = &orderFilter
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) TriggerPrice(triggerPrice string) *PostPlaceOrderRequest {
|
||||
p.triggerPrice = &triggerPrice
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) TriggerBy(triggerBy string) *PostPlaceOrderRequest {
|
||||
p.triggerBy = &triggerBy
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) OrderIv(orderIv string) *PostPlaceOrderRequest {
|
||||
p.orderIv = &orderIv
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) PositionIdx(positionIdx string) *PostPlaceOrderRequest {
|
||||
p.positionIdx = &positionIdx
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) TakeProfit(takeProfit string) *PostPlaceOrderRequest {
|
||||
p.takeProfit = &takeProfit
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) StopLoss(stopLoss string) *PostPlaceOrderRequest {
|
||||
p.stopLoss = &stopLoss
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) TpTriggerBy(tpTriggerBy string) *PostPlaceOrderRequest {
|
||||
p.tpTriggerBy = &tpTriggerBy
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) SlTriggerBy(slTriggerBy string) *PostPlaceOrderRequest {
|
||||
p.slTriggerBy = &slTriggerBy
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) ReduceOnly(reduceOnly bool) *PostPlaceOrderRequest {
|
||||
p.reduceOnly = &reduceOnly
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) CloseOnTrigger(closeOnTrigger bool) *PostPlaceOrderRequest {
|
||||
p.closeOnTrigger = &closeOnTrigger
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) SmpType(smpType string) *PostPlaceOrderRequest {
|
||||
p.smpType = &smpType
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) Mmp(mmp bool) *PostPlaceOrderRequest {
|
||||
p.mmp = &mmp
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) TpslMode(tpslMode string) *PostPlaceOrderRequest {
|
||||
p.tpslMode = &tpslMode
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) TpLimitPrice(tpLimitPrice string) *PostPlaceOrderRequest {
|
||||
p.tpLimitPrice = &tpLimitPrice
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) SlLimitPrice(slLimitPrice string) *PostPlaceOrderRequest {
|
||||
p.slLimitPrice = &slLimitPrice
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) TpOrderType(tpOrderType string) *PostPlaceOrderRequest {
|
||||
p.tpOrderType = &tpOrderType
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) SlOrderType(slOrderType string) *PostPlaceOrderRequest {
|
||||
p.slOrderType = &slOrderType
|
||||
return p
|
||||
}
|
||||
|
||||
// GetQueryParameters builds and checks the query parameters and returns url.Values
|
||||
func (p *PostPlaceOrderRequest) GetQueryParameters() (url.Values, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
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 (p *PostPlaceOrderRequest) GetParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
// check category field -> json key category
|
||||
category := p.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
|
||||
symbol := p.symbol
|
||||
|
||||
// assign parameter of symbol
|
||||
params["symbol"] = symbol
|
||||
// check side field -> json key side
|
||||
side := p.side
|
||||
|
||||
// TEMPLATE check-valid-values
|
||||
switch side {
|
||||
case "Buy", "Sell":
|
||||
params["side"] = side
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("side value %v is invalid", side)
|
||||
|
||||
}
|
||||
// END TEMPLATE check-valid-values
|
||||
|
||||
// assign parameter of side
|
||||
params["side"] = side
|
||||
// check orderType field -> json key orderType
|
||||
orderType := p.orderType
|
||||
|
||||
// TEMPLATE check-valid-values
|
||||
switch orderType {
|
||||
case "Market", "Limit":
|
||||
params["orderType"] = orderType
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("orderType value %v is invalid", orderType)
|
||||
|
||||
}
|
||||
// END TEMPLATE check-valid-values
|
||||
|
||||
// assign parameter of orderType
|
||||
params["orderType"] = orderType
|
||||
// check qty field -> json key qty
|
||||
qty := p.qty
|
||||
|
||||
// assign parameter of qty
|
||||
params["qty"] = qty
|
||||
// check orderLinkId field -> json key orderLinkId
|
||||
orderLinkId := p.orderLinkId
|
||||
|
||||
// assign parameter of orderLinkId
|
||||
params["orderLinkId"] = orderLinkId
|
||||
// check timeInForce field -> json key timeInForce
|
||||
timeInForce := p.timeInForce
|
||||
|
||||
// TEMPLATE check-valid-values
|
||||
switch timeInForce {
|
||||
case TimeInForceGTC, TimeInForceIOC, TimeInForceFOK:
|
||||
params["timeInForce"] = timeInForce
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("timeInForce value %v is invalid", timeInForce)
|
||||
|
||||
}
|
||||
// END TEMPLATE check-valid-values
|
||||
|
||||
// assign parameter of timeInForce
|
||||
params["timeInForce"] = timeInForce
|
||||
// check isLeverage field -> json key isLeverage
|
||||
if p.isLeverage != nil {
|
||||
isLeverage := *p.isLeverage
|
||||
|
||||
// assign parameter of isLeverage
|
||||
params["isLeverage"] = isLeverage
|
||||
} else {
|
||||
}
|
||||
// check price field -> json key price
|
||||
if p.price != nil {
|
||||
price := *p.price
|
||||
|
||||
// assign parameter of price
|
||||
params["price"] = price
|
||||
} else {
|
||||
}
|
||||
// check triggerDirection field -> json key triggerDirection
|
||||
if p.triggerDirection != nil {
|
||||
triggerDirection := *p.triggerDirection
|
||||
|
||||
// assign parameter of triggerDirection
|
||||
params["triggerDirection"] = triggerDirection
|
||||
} else {
|
||||
}
|
||||
// check orderFilter field -> json key orderFilter
|
||||
if p.orderFilter != nil {
|
||||
orderFilter := *p.orderFilter
|
||||
|
||||
// assign parameter of orderFilter
|
||||
params["orderFilter"] = orderFilter
|
||||
} else {
|
||||
}
|
||||
// check triggerPrice field -> json key triggerPrice
|
||||
if p.triggerPrice != nil {
|
||||
triggerPrice := *p.triggerPrice
|
||||
|
||||
// assign parameter of triggerPrice
|
||||
params["triggerPrice"] = triggerPrice
|
||||
} else {
|
||||
}
|
||||
// check triggerBy field -> json key triggerBy
|
||||
if p.triggerBy != nil {
|
||||
triggerBy := *p.triggerBy
|
||||
|
||||
// assign parameter of triggerBy
|
||||
params["triggerBy"] = triggerBy
|
||||
} else {
|
||||
}
|
||||
// check orderIv field -> json key orderIv
|
||||
if p.orderIv != nil {
|
||||
orderIv := *p.orderIv
|
||||
|
||||
// assign parameter of orderIv
|
||||
params["orderIv"] = orderIv
|
||||
} else {
|
||||
}
|
||||
// check positionIdx field -> json key positionIdx
|
||||
if p.positionIdx != nil {
|
||||
positionIdx := *p.positionIdx
|
||||
|
||||
// assign parameter of positionIdx
|
||||
params["positionIdx"] = positionIdx
|
||||
} else {
|
||||
}
|
||||
// check takeProfit field -> json key takeProfit
|
||||
if p.takeProfit != nil {
|
||||
takeProfit := *p.takeProfit
|
||||
|
||||
// assign parameter of takeProfit
|
||||
params["takeProfit"] = takeProfit
|
||||
} else {
|
||||
}
|
||||
// check stopLoss field -> json key stopLoss
|
||||
if p.stopLoss != nil {
|
||||
stopLoss := *p.stopLoss
|
||||
|
||||
// assign parameter of stopLoss
|
||||
params["stopLoss"] = stopLoss
|
||||
} else {
|
||||
}
|
||||
// check tpTriggerBy field -> json key tpTriggerBy
|
||||
if p.tpTriggerBy != nil {
|
||||
tpTriggerBy := *p.tpTriggerBy
|
||||
|
||||
// assign parameter of tpTriggerBy
|
||||
params["tpTriggerBy"] = tpTriggerBy
|
||||
} else {
|
||||
}
|
||||
// check slTriggerBy field -> json key slTriggerBy
|
||||
if p.slTriggerBy != nil {
|
||||
slTriggerBy := *p.slTriggerBy
|
||||
|
||||
// assign parameter of slTriggerBy
|
||||
params["slTriggerBy"] = slTriggerBy
|
||||
} else {
|
||||
}
|
||||
// check reduceOnly field -> json key reduceOnly
|
||||
if p.reduceOnly != nil {
|
||||
reduceOnly := *p.reduceOnly
|
||||
|
||||
// assign parameter of reduceOnly
|
||||
params["reduceOnly"] = reduceOnly
|
||||
} else {
|
||||
}
|
||||
// check closeOnTrigger field -> json key closeOnTrigger
|
||||
if p.closeOnTrigger != nil {
|
||||
closeOnTrigger := *p.closeOnTrigger
|
||||
|
||||
// assign parameter of closeOnTrigger
|
||||
params["closeOnTrigger"] = closeOnTrigger
|
||||
} else {
|
||||
}
|
||||
// check smpType field -> json key smpType
|
||||
if p.smpType != nil {
|
||||
smpType := *p.smpType
|
||||
|
||||
// assign parameter of smpType
|
||||
params["smpType"] = smpType
|
||||
} else {
|
||||
}
|
||||
// check mmp field -> json key mmp
|
||||
if p.mmp != nil {
|
||||
mmp := *p.mmp
|
||||
|
||||
// assign parameter of mmp
|
||||
params["mmp"] = mmp
|
||||
} else {
|
||||
}
|
||||
// check tpslMode field -> json key tpslMode
|
||||
if p.tpslMode != nil {
|
||||
tpslMode := *p.tpslMode
|
||||
|
||||
// assign parameter of tpslMode
|
||||
params["tpslMode"] = tpslMode
|
||||
} else {
|
||||
}
|
||||
// check tpLimitPrice field -> json key tpLimitPrice
|
||||
if p.tpLimitPrice != nil {
|
||||
tpLimitPrice := *p.tpLimitPrice
|
||||
|
||||
// assign parameter of tpLimitPrice
|
||||
params["tpLimitPrice"] = tpLimitPrice
|
||||
} else {
|
||||
}
|
||||
// check slLimitPrice field -> json key slLimitPrice
|
||||
if p.slLimitPrice != nil {
|
||||
slLimitPrice := *p.slLimitPrice
|
||||
|
||||
// assign parameter of slLimitPrice
|
||||
params["slLimitPrice"] = slLimitPrice
|
||||
} else {
|
||||
}
|
||||
// check tpOrderType field -> json key tpOrderType
|
||||
if p.tpOrderType != nil {
|
||||
tpOrderType := *p.tpOrderType
|
||||
|
||||
// assign parameter of tpOrderType
|
||||
params["tpOrderType"] = tpOrderType
|
||||
} else {
|
||||
}
|
||||
// check slOrderType field -> json key slOrderType
|
||||
if p.slOrderType != nil {
|
||||
slOrderType := *p.slOrderType
|
||||
|
||||
// assign parameter of slOrderType
|
||||
params["slOrderType"] = slOrderType
|
||||
} else {
|
||||
}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// GetParametersQuery converts the parameters from GetParameters into the url.Values format
|
||||
func (p *PostPlaceOrderRequest) GetParametersQuery() (url.Values, error) {
|
||||
query := url.Values{}
|
||||
|
||||
params, err := p.GetParameters()
|
||||
if err != nil {
|
||||
return query, err
|
||||
}
|
||||
|
||||
for _k, _v := range params {
|
||||
if p.isVarSlice(_v) {
|
||||
p.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 (p *PostPlaceOrderRequest) GetParametersJSON() ([]byte, error) {
|
||||
params, err := p.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 (p *PostPlaceOrderRequest) GetSlugParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) 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 (p *PostPlaceOrderRequest) 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 (p *PostPlaceOrderRequest) isVarSlice(_v interface{}) bool {
|
||||
rt := reflect.TypeOf(_v)
|
||||
switch rt.Kind() {
|
||||
case reflect.Slice:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) GetSlugsMap() (map[string]string, error) {
|
||||
slugs := map[string]string{}
|
||||
params, err := p.GetSlugParameters()
|
||||
if err != nil {
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
for _k, _v := range params {
|
||||
slugs[_k] = fmt.Sprintf("%v", _v)
|
||||
}
|
||||
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
func (p *PostPlaceOrderRequest) Do(ctx context.Context) (*PlaceOrderResponse, error) {
|
||||
|
||||
params, err := p.GetParameters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query := url.Values{}
|
||||
|
||||
apiURL := "/v5/order/create"
|
||||
|
||||
req, err := p.client.NewAuthenticatedRequest(ctx, "POST", apiURL, query, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := p.client.SendRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var apiResponse APIResponse
|
||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data PlaceOrderResponse
|
||||
if err := json.Unmarshal(apiResponse.Result, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &data, nil
|
||||
}
|
|
@ -170,3 +170,29 @@ func isWorking(status bybitapi.OrderStatus) (bool, error) {
|
|||
s, err := toGlobalOrderStatus(status)
|
||||
return s == types.OrderStatusNew || s == types.OrderStatusPartiallyFilled, err
|
||||
}
|
||||
|
||||
func toLocalOrderType(orderType types.OrderType) (bybitapi.OrderType, error) {
|
||||
switch orderType {
|
||||
case types.OrderTypeLimit:
|
||||
return bybitapi.OrderTypeLimit, nil
|
||||
|
||||
case types.OrderTypeMarket:
|
||||
return bybitapi.OrderTypeMarket, nil
|
||||
|
||||
default:
|
||||
return "", fmt.Errorf("order type %s not supported", orderType)
|
||||
}
|
||||
}
|
||||
|
||||
func toLocalSide(side types.SideType) (bybitapi.Side, error) {
|
||||
switch side {
|
||||
case types.SideTypeSell:
|
||||
return bybitapi.SideSell, nil
|
||||
|
||||
case types.SideTypeBuy:
|
||||
return bybitapi.SideBuy, nil
|
||||
|
||||
default:
|
||||
return "", fmt.Errorf("side type %s not supported", side)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package bybit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -356,3 +357,31 @@ func TestIsWorking(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_toLocalOrderType(t *testing.T) {
|
||||
orderType, err := toLocalOrderType(types.OrderTypeLimit)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, bybitapi.OrderTypeLimit, orderType)
|
||||
|
||||
orderType, err = toLocalOrderType(types.OrderTypeMarket)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, bybitapi.OrderTypeMarket, orderType)
|
||||
|
||||
orderType, err = toLocalOrderType("wrong type")
|
||||
assert.Error(t, fmt.Errorf("order type %s not supported", "wrong side"), err)
|
||||
assert.Equal(t, bybitapi.OrderType(""), orderType)
|
||||
}
|
||||
|
||||
func Test_toLocalSide(t *testing.T) {
|
||||
side, err := toLocalSide(types.SideTypeSell)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, bybitapi.SideSell, side)
|
||||
|
||||
side, err = toLocalSide(types.SideTypeBuy)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, bybitapi.SideBuy, side)
|
||||
|
||||
side, err = toLocalSide("wrong side")
|
||||
assert.Error(t, fmt.Errorf("side type %s not supported", "wrong side"), err)
|
||||
assert.Equal(t, bybitapi.Side(""), side)
|
||||
}
|
||||
|
|
|
@ -12,6 +12,10 @@ import (
|
|||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
const (
|
||||
maxOrderIdLen = 36
|
||||
)
|
||||
|
||||
// https://bybit-exchange.github.io/docs/zh-TW/v5/rate-limit
|
||||
// sharedRateLimiter indicates that the API belongs to the public API.
|
||||
//
|
||||
|
@ -20,6 +24,7 @@ import (
|
|||
var (
|
||||
sharedRateLimiter = rate.NewLimiter(rate.Every(time.Second/2), 2)
|
||||
tradeRateLimiter = rate.NewLimiter(rate.Every(time.Second/5), 5)
|
||||
orderRateLimiter = rate.NewLimiter(rate.Every(100*time.Millisecond), 10)
|
||||
|
||||
log = logrus.WithFields(logrus.Fields{
|
||||
"exchange": "bybit",
|
||||
|
@ -169,3 +174,76 @@ func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders [
|
|||
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
func (e *Exchange) SubmitOrder(ctx context.Context, order types.SubmitOrder) (*types.Order, error) {
|
||||
if len(order.Market.Symbol) == 0 {
|
||||
return nil, fmt.Errorf("order.Market.Symbol is required: %+v", order)
|
||||
}
|
||||
|
||||
req := e.client.NewPlaceOrderRequest()
|
||||
req.Symbol(order.Symbol)
|
||||
|
||||
// set order type
|
||||
orderType, err := toLocalOrderType(order.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.OrderType(orderType)
|
||||
|
||||
// set side
|
||||
side, err := toLocalSide(order.Side)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Side(side)
|
||||
|
||||
// set quantity
|
||||
req.Qty(order.Market.FormatQuantity(order.Quantity))
|
||||
|
||||
// set price
|
||||
switch order.Type {
|
||||
case types.OrderTypeLimit:
|
||||
req.Price(order.Market.FormatPrice(order.Price))
|
||||
}
|
||||
|
||||
// set timeInForce
|
||||
switch order.TimeInForce {
|
||||
case types.TimeInForceFOK:
|
||||
req.TimeInForce(bybitapi.TimeInForceFOK)
|
||||
case types.TimeInForceIOC:
|
||||
req.TimeInForce(bybitapi.TimeInForceIOC)
|
||||
default:
|
||||
req.TimeInForce(bybitapi.TimeInForceGTC)
|
||||
}
|
||||
|
||||
// set client order id
|
||||
if len(order.ClientOrderID) > maxOrderIdLen {
|
||||
return nil, fmt.Errorf("unexpected length of order id, got: %d", len(order.ClientOrderID))
|
||||
}
|
||||
req.OrderLinkId(order.ClientOrderID)
|
||||
|
||||
if err := orderRateLimiter.Wait(ctx); err != nil {
|
||||
log.WithError(err).Errorf("place order rate limiter wait error")
|
||||
return nil, err
|
||||
}
|
||||
res, err := req.Do(ctx)
|
||||
if err != nil {
|
||||
log.Warnf("failed to place order, order: %#v, err: %v", order, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(res.OrderId) == 0 || res.OrderLinkId != order.ClientOrderID {
|
||||
return nil, fmt.Errorf("unexpected order id, resp: %#v, order: %#v", res, order)
|
||||
}
|
||||
|
||||
ordersResp, err := e.client.NewGetOpenOrderRequest().OrderLinkId(res.OrderLinkId).Do(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query order by client order id: %s", res.OrderLinkId)
|
||||
}
|
||||
|
||||
if len(ordersResp.List) != 1 {
|
||||
return nil, fmt.Errorf("unexpected order length, client order id: %s", res.OrderLinkId)
|
||||
}
|
||||
|
||||
return toGlobalOrder(ordersResp.List[0])
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user