Skip to content

Commit

Permalink
pkg/exchange: remove the query after place order
Browse files Browse the repository at this point in the history
  • Loading branch information
bailantaotao committed Mar 14, 2024
1 parent 747b75f commit 86d7fad
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 246 deletions.
50 changes: 15 additions & 35 deletions pkg/exchange/bitget/exchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

"github.com/c9s/bbgo/pkg/exchange/bitget/bitgetapi"
v2 "github.com/c9s/bbgo/pkg/exchange/bitget/bitgetapi/v2"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
)

Expand Down Expand Up @@ -344,6 +345,7 @@ func (e *Exchange) SubmitOrder(ctx context.Context, order types.SubmitOrder) (cr
return nil, fmt.Errorf("place order rate limiter wait error: %w", err)
}

timeNow := time.Now()
res, err := req.Do(ctx)
if err != nil {
return nil, fmt.Errorf("failed to place order, order: %#v, err: %w", order, err)
Expand All @@ -355,44 +357,22 @@ func (e *Exchange) SubmitOrder(ctx context.Context, order types.SubmitOrder) (cr
return nil, fmt.Errorf("unexpected order id, resp: %#v, order: %#v", res, order)
}

orderId := res.OrderId

debugf("fetching unfilled order info for order #%s", orderId)
ordersResp, err := e.v2client.NewGetUnfilledOrdersRequest().OrderId(orderId).Do(ctx)
intOrderId, err := strconv.ParseUint(res.OrderId, 10, 64)
if err != nil {
return nil, fmt.Errorf("failed to query open order by order id: %s, err: %w", orderId, err)
}

debugf("unfilled order response for order#%s: %+v", orderId, ordersResp)

if len(ordersResp) == 1 {
// 2023/11/05 The market order will be executed immediately, so we cannot retrieve it through the NewGetUnfilledOrdersRequest API.
// Try to get the order from the NewGetHistoryOrdersRequest API.
// 2024/03/06 After placing a Market Order, we can retrieve it through the unfilledOrder API, so we still need to
// handle the Market Order status.
return unfilledOrderToGlobalOrder(ordersResp[0])
} else if len(ordersResp) == 0 {
ordersResp, err := e.v2client.NewGetHistoryOrdersRequest().OrderId(orderId).Do(ctx)
if err != nil {
return nil, fmt.Errorf("failed to query history order by order id: %s, err: %w", orderId, err)
}

if len(ordersResp) != 1 {
// 2023/03/12 If it's a maker order and there is a corresponding order to be executed, then the order will be canceled,
// you can receive the status immediately from the websocket, but the RestAPI requires at least 200ms waiting time.
//
// Therefore, We don't want to waste time waiting for him, so we choose to manually enter the order
// information and send it back.
if order.Type == types.OrderTypeLimitMaker {
return fallbackPostOnlyOrder(order, orderId)
}
return nil, fmt.Errorf("unexpected length of history orders, expecting: 1, given: %d, ids: %s", len(ordersResp), orderId)
}

return toGlobalOrder(ordersResp[0])
return nil, err
}

return nil, fmt.Errorf("unexpected length of unfilled orders, expecting: 1, given: %d, ids: %s", len(ordersResp), orderId)
return &types.Order{
SubmitOrder: order,
Exchange: types.ExchangeBitget,
OrderID: intOrderId,
UUID: res.OrderId,
Status: types.OrderStatusNew,
ExecutedQuantity: fixedpoint.Zero,
IsWorking: true,
CreationTime: types.Time(timeNow),
UpdateTime: types.Time(timeNow),
}, nil
}

func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders []types.Order, err error) {
Expand Down
226 changes: 24 additions & 202 deletions pkg/exchange/bitget/exchange_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -546,15 +546,21 @@ func TestExchange_QueryAccountBalances(t *testing.T) {

func TestExchange_SubmitOrder(t *testing.T) {
var (
assert = assert.New(t)
ex = New("key", "secret", "passphrase")
placeOrderUrl = "/api/v2/spot/trade/place-order"
openOrderUrl = "/api/v2/spot/trade/unfilled-orders"
tickerUrl = "/api/v2/spot/market/tickers"
historyOrderUrl = "/api/v2/spot/trade/history-orders"
clientOrderId = "684a79df-f931-474f-a9a5-f1deab1cd770"
expBtcSymbol = "BTCUSDT"
expOrder = &types.Order{
assert = assert.New(t)
ex = New("key", "secret", "passphrase")
placeOrderUrl = "/api/v2/spot/trade/place-order"
tickerUrl = "/api/v2/spot/market/tickers"
clientOrderId = "684a79df-f931-474f-a9a5-f1deab1cd770"
expBtcSymbol = "BTCUSDT"
mkt = types.Market{
Symbol: expBtcSymbol,
LocalSymbol: expBtcSymbol,
PricePrecision: fixedpoint.MustNewFromString("2").Int(),
VolumePrecision: fixedpoint.MustNewFromString("6").Int(),
StepSize: fixedpoint.NewFromFloat(1.0 / math.Pow10(6)),
TickSize: fixedpoint.NewFromFloat(1.0 / math.Pow10(2)),
}
expOrder = &types.Order{
SubmitOrder: types.SubmitOrder{
ClientOrderID: clientOrderId,
Symbol: expBtcSymbol,
Expand All @@ -563,6 +569,7 @@ func TestExchange_SubmitOrder(t *testing.T) {
Quantity: fixedpoint.MustNewFromString("0.00009"),
Price: fixedpoint.MustNewFromString("66000"),
TimeInForce: types.TimeInForceGTC,
Market: mkt,
},
Exchange: types.ExchangeBitget,
OrderID: 1148903850645331968,
Expand All @@ -580,15 +587,8 @@ func TestExchange_SubmitOrder(t *testing.T) {
Type: types.OrderTypeLimit,
Quantity: fixedpoint.MustNewFromString("0.00009"),
Price: fixedpoint.MustNewFromString("66000"),
Market: types.Market{
Symbol: expBtcSymbol,
LocalSymbol: expBtcSymbol,
PricePrecision: fixedpoint.MustNewFromString("2").Int(),
VolumePrecision: fixedpoint.MustNewFromString("6").Int(),
StepSize: fixedpoint.NewFromFloat(1.0 / math.Pow10(6)),
TickSize: fixedpoint.NewFromFloat(1.0 / math.Pow10(2)),
},
TimeInForce: types.TimeInForceGTC,
Market: mkt,
TimeInForce: types.TimeInForceGTC,
}
)

Expand Down Expand Up @@ -629,31 +629,14 @@ func TestExchange_SubmitOrder(t *testing.T) {
return httptesting.BuildResponseString(http.StatusOK, string(placeOrderFile)), nil
})

unfilledFile, err := os.ReadFile("bitgetapi/v2/testdata/get_unfilled_orders_request_limit_order.json")
assert.NoError(err)

transport.GET(openOrderUrl, func(req *http.Request) (*http.Response, error) {
query := req.URL.Query()
assert.Len(query, 1)
assert.Contains(query, "orderId")
assert.Equal(query["orderId"], []string{strconv.FormatUint(expOrder.OrderID, 10)})
return httptesting.BuildResponseString(http.StatusOK, string(unfilledFile)), nil
})

acct, err := ex.SubmitOrder(context.Background(), reqLimitOrder)
assert.NoError(err)
expOrder.CreationTime = acct.CreationTime
expOrder.UpdateTime = acct.UpdateTime
assert.Equal(expOrder, acct)
})

t.Run("Limit Maker order", func(t *testing.T) {
emptyApiResp := v2.APIResponse{
Code: "00000",
Message: "",
Data: nil,
}
rawEmptyApiResp, err := json.Marshal(emptyApiResp)
assert.NoError(err)

transport := &httptesting.MockTransport{}
ex.client.HttpClient.Transport = transport

Expand All @@ -680,29 +663,12 @@ func TestExchange_SubmitOrder(t *testing.T) {
return httptesting.BuildResponseString(http.StatusOK, string(placeOrderFile)), nil
})

transport.GET(openOrderUrl, func(req *http.Request) (*http.Response, error) {
query := req.URL.Query()
assert.Len(query, 1)
assert.Contains(query, "orderId")
assert.Equal(query["orderId"], []string{strconv.FormatUint(expOrder.OrderID, 10)})
return httptesting.BuildResponseString(http.StatusOK, string(rawEmptyApiResp)), nil
})

transport.GET(historyOrderUrl, func(req *http.Request) (*http.Response, error) {
query := req.URL.Query()
assert.Len(query, 1)
assert.Contains(query, "orderId")
assert.Equal(query["orderId"], []string{strconv.FormatUint(expOrder.OrderID, 10)})
return httptesting.BuildResponseString(http.StatusOK, string(rawEmptyApiResp)), nil
})

reqLimitOrder2 := reqLimitOrder
reqLimitOrder2.Type = types.OrderTypeLimitMaker
acct, err := ex.SubmitOrder(context.Background(), reqLimitOrder2)
assert.NoError(err)

expOrder2 := *expOrder
expOrder2.OriginalStatus = "FALLBACK_STATUS"
expOrder2.Status = types.OrderStatusNew
expOrder2.IsWorking = true
expOrder2.Type = types.OrderTypeLimitMaker
Expand Down Expand Up @@ -751,18 +717,6 @@ func TestExchange_SubmitOrder(t *testing.T) {
return httptesting.BuildResponseString(http.StatusOK, string(placeOrderFile)), nil
})

// unfilled order
unfilledFile, err := os.ReadFile("bitgetapi/v2/testdata/get_unfilled_orders_request_market_buy_order.json")
assert.NoError(err)

transport.GET(openOrderUrl, func(req *http.Request) (*http.Response, error) {
query := req.URL.Query()
assert.Len(query, 1)
assert.Contains(query, "orderId")
assert.Equal(query["orderId"], []string{strconv.FormatUint(expOrder.OrderID, 10)})
return httptesting.BuildResponseString(http.StatusOK, string(unfilledFile)), nil
})

reqMarketOrder := reqLimitOrder
reqMarketOrder.Side = types.SideTypeBuy
reqMarketOrder.Type = types.OrderTypeMarket
Expand All @@ -771,8 +725,8 @@ func TestExchange_SubmitOrder(t *testing.T) {
expOrder2 := *expOrder
expOrder2.Side = types.SideTypeBuy
expOrder2.Type = types.OrderTypeMarket
expOrder2.Quantity = fixedpoint.Zero
expOrder2.Price = fixedpoint.Zero
expOrder2.CreationTime = acct.CreationTime
expOrder2.UpdateTime = acct.UpdateTime
assert.Equal(&expOrder2, acct)
})

Expand Down Expand Up @@ -814,18 +768,6 @@ func TestExchange_SubmitOrder(t *testing.T) {
return httptesting.BuildResponseString(http.StatusOK, string(placeOrderFile)), nil
})

// unfilled order
unfilledFile, err := os.ReadFile("bitgetapi/v2/testdata/get_unfilled_orders_request_market_sell_order.json")
assert.NoError(err)

transport.GET(openOrderUrl, func(req *http.Request) (*http.Response, error) {
query := req.URL.Query()
assert.Len(query, 1)
assert.Contains(query, "orderId")
assert.Equal(query["orderId"], []string{strconv.FormatUint(expOrder.OrderID, 10)})
return httptesting.BuildResponseString(http.StatusOK, string(unfilledFile)), nil
})

reqMarketOrder := reqLimitOrder
reqMarketOrder.Side = types.SideTypeSell
reqMarketOrder.Type = types.OrderTypeMarket
Expand All @@ -834,7 +776,8 @@ func TestExchange_SubmitOrder(t *testing.T) {
expOrder2 := *expOrder
expOrder2.Side = types.SideTypeSell
expOrder2.Type = types.OrderTypeMarket
expOrder2.Price = fixedpoint.Zero
expOrder2.CreationTime = acct.CreationTime
expOrder2.UpdateTime = acct.UpdateTime
assert.Equal(&expOrder2, acct)
})

Expand All @@ -858,127 +801,6 @@ func TestExchange_SubmitOrder(t *testing.T) {
_, err = ex.SubmitOrder(context.Background(), reqMarketOrder)
assert.ErrorContains(err, "Invalid IP")
})

t.Run("get order from history due to unfilled order not found", func(t *testing.T) {
transport := &httptesting.MockTransport{}
ex.client.HttpClient.Transport = transport

// get ticker to calculate btc amount
tickerFile, err := os.ReadFile("bitgetapi/v2/testdata/get_ticker_request.json")
assert.NoError(err)

transport.GET(tickerUrl, func(req *http.Request) (*http.Response, error) {
assert.Contains(req.URL.Query(), "symbol")
assert.Equal(req.URL.Query()["symbol"], []string{expBtcSymbol})
return httptesting.BuildResponseString(http.StatusOK, string(tickerFile)), nil
})

// place order
placeOrderFile, err := os.ReadFile("bitgetapi/v2/testdata/place_order_request.json")
assert.NoError(err)

transport.POST(placeOrderUrl, func(req *http.Request) (*http.Response, error) {
raw, err := io.ReadAll(req.Body)
assert.NoError(err)

reqq := &NewOrder{}
err = json.Unmarshal(raw, &reqq)
assert.NoError(err)
assert.Equal(&NewOrder{
ClientOid: expOrder.ClientOrderID,
Force: string(v2.OrderForceGTC),
OrderType: string(v2.OrderTypeMarket),
Price: "",
Side: string(v2.SideTypeBuy),
Size: reqLimitOrder.Market.FormatQuantity(fixedpoint.MustNewFromString("66554").Mul(fixedpoint.MustNewFromString("0.00009"))), // ticker: 66554, size: 0.00009
Symbol: expBtcSymbol,
}, reqq)

return httptesting.BuildResponseString(http.StatusOK, string(placeOrderFile)), nil
})

// unfilled order
transport.GET(openOrderUrl, func(req *http.Request) (*http.Response, error) {
query := req.URL.Query()
assert.Len(query, 1)
assert.Contains(query, "orderId")
assert.Equal(query["orderId"], []string{strconv.FormatUint(expOrder.OrderID, 10)})

apiResp := v2.APIResponse{Code: "00000"}
raw, err := json.Marshal(apiResp)
assert.NoError(err)
return httptesting.BuildResponseString(http.StatusOK, string(raw)), nil
})

// order history
historyOrderFile, err := os.ReadFile("bitgetapi/v2/testdata/get_history_orders_request_market_buy.json")
assert.NoError(err)

transport.GET(historyOrderUrl, func(req *http.Request) (*http.Response, error) {
query := req.URL.Query()
assert.Len(query, 1)
assert.Contains(query, "orderId")
assert.Equal(query["orderId"], []string{strconv.FormatUint(expOrder.OrderID, 10)})
return httptesting.BuildResponseString(http.StatusOK, string(historyOrderFile)), nil
})

reqMarketOrder := reqLimitOrder
reqMarketOrder.Side = types.SideTypeBuy
reqMarketOrder.Type = types.OrderTypeMarket
acct, err := ex.SubmitOrder(context.Background(), reqMarketOrder)
assert.NoError(err)
expOrder2 := *expOrder
expOrder2.Side = types.SideTypeBuy
expOrder2.Type = types.OrderTypeMarket
expOrder2.Status = types.OrderStatusFilled
expOrder2.ExecutedQuantity = fixedpoint.MustNewFromString("0.000089")
expOrder2.Quantity = fixedpoint.MustNewFromString("0.000089")
expOrder2.Price = fixedpoint.MustNewFromString("67360.87")
expOrder2.IsWorking = false
assert.Equal(&expOrder2, acct)
})
})

t.Run("error on query open orders", func(t *testing.T) {
transport := &httptesting.MockTransport{}
ex.client.HttpClient.Transport = transport

placeOrderFile, err := os.ReadFile("bitgetapi/v2/testdata/place_order_request.json")
assert.NoError(err)

transport.POST(placeOrderUrl, func(req *http.Request) (*http.Response, error) {
raw, err := io.ReadAll(req.Body)
assert.NoError(err)

reqq := &NewOrder{}
err = json.Unmarshal(raw, &reqq)
assert.NoError(err)
assert.Equal(&NewOrder{
ClientOid: expOrder.ClientOrderID,
Force: string(v2.OrderForceGTC),
OrderType: string(v2.OrderTypeLimit),
Price: "66000.00",
Side: string(v2.SideTypeBuy),
Size: "0.000090",
Symbol: expBtcSymbol,
}, reqq)

return httptesting.BuildResponseString(http.StatusOK, string(placeOrderFile)), nil
})

unfilledFile, err := os.ReadFile("bitgetapi/v2/testdata/request_error.json")
assert.NoError(err)

transport.GET(openOrderUrl, func(req *http.Request) (*http.Response, error) {
query := req.URL.Query()
assert.Len(query, 1)
assert.Contains(query, "orderId")
assert.Equal(query["orderId"], []string{strconv.FormatUint(expOrder.OrderID, 10)})
return httptesting.BuildResponseString(http.StatusBadRequest, string(unfilledFile)), nil
})

_, err = ex.SubmitOrder(context.Background(), reqLimitOrder)
assert.ErrorContains(err, "failed to query open order")
})

t.Run("unexpected client order id", func(t *testing.T) {
Expand Down

0 comments on commit 86d7fad

Please sign in to comment.