Skip to content

Commit d606aa2

Browse files
committed
support global order quantity convert to okx contract size
1 parent 092f133 commit d606aa2

File tree

4 files changed

+122
-51
lines changed

4 files changed

+122
-51
lines changed

pkg/exchange/okex/exchange.go

Lines changed: 21 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,7 @@ func (e *Exchange) QueryTicker(ctx context.Context, symbol string) (*types.Ticke
186186
return nil, fmt.Errorf("ticker rate limiter wait error: %w", err)
187187
}
188188

189-
symbol = toLocalSymbol(symbol)
190-
if e.IsFutures {
191-
symbol = toLocalSymbol(symbol, okexapi.InstrumentTypeSwap)
192-
}
189+
symbol = e.getInstrumentId(symbol)
193190
marketTicker, err := e.client.NewGetTickerRequest().InstId(symbol).Do(ctx)
194191
if err != nil {
195192
return nil, err
@@ -316,7 +313,7 @@ func (e *Exchange) queryAccountBalance(ctx context.Context) ([]okexapi.Account,
316313
func (e *Exchange) SubmitOrder(ctx context.Context, order types.SubmitOrder) (*types.Order, error) {
317314
orderReq := e.client.NewPlaceOrderRequest()
318315

319-
orderReq.InstrumentID(toLocalSymbol(order.Symbol))
316+
orderReq.InstrumentID(e.getInstrumentId(order.Symbol))
320317
orderReq.Side(toLocalSideType(order.Side))
321318
orderReq.Size(order.Market.FormatQuantity(order.Quantity))
322319

@@ -331,10 +328,6 @@ func (e *Exchange) SubmitOrder(ctx context.Context, order types.SubmitOrder) (*t
331328
orderReq.TradeMode(okexapi.TradeModeCross)
332329
}
333330
} else if e.IsFutures {
334-
quantity := order.Market.AdjustQuantityToContractSize(order.Quantity)
335-
orderReq.Size(order.Market.FormatQuantity(quantity))
336-
orderReq.InstrumentID(toLocalSymbol(order.Symbol, okexapi.InstrumentTypeSwap))
337-
338331
if e.FuturesSettings.IsIsolatedFutures {
339332
orderReq.TradeMode(okexapi.TradeModeIsolated)
340333
} else {
@@ -355,12 +348,8 @@ func (e *Exchange) SubmitOrder(ctx context.Context, order types.SubmitOrder) (*t
355348
case types.OrderTypeMarket:
356349
// target currency = Default is quote_ccy for buy, base_ccy for sell
357350
// Because our order.Quantity unit is base coin, so we indicate the target currency to Base.
358-
switch order.Side {
359-
case types.SideTypeSell:
360-
orderReq.Size(order.Market.FormatQuantity(order.Quantity))
361-
orderReq.TargetCurrency(okexapi.TargetCurrencyBase)
362-
case types.SideTypeBuy:
363-
orderReq.Size(order.Market.FormatQuantity(order.Quantity))
351+
// Only applicable to SPOT Market Orders
352+
if !e.IsFutures {
364353
orderReq.TargetCurrency(okexapi.TargetCurrencyBase)
365354
}
366355
}
@@ -424,7 +413,7 @@ func (e *Exchange) SubmitOrder(ctx context.Context, order types.SubmitOrder) (*t
424413
// QueryOpenOrders retrieves the pending orders. The data returned is ordered by createdTime, and we utilized the
425414
// `After` parameter to acquire all orders.
426415
func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders []types.Order, err error) {
427-
instrumentID := toLocalSymbol(symbol)
416+
instrumentID := e.getInstrumentId(symbol)
428417

429418
nextCursor := int64(0)
430419
for {
@@ -439,7 +428,6 @@ func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders [
439428
if e.MarginSettings.IsMargin {
440429
req.InstrumentType(okexapi.InstrumentTypeMargin)
441430
} else if e.IsFutures {
442-
req.InstrumentID(toLocalSymbol(symbol, okexapi.InstrumentTypeSwap))
443431
req.InstrumentType(okexapi.InstrumentTypeSwap)
444432
}
445433

@@ -484,10 +472,8 @@ func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) erro
484472
}
485473

486474
req := e.client.NewCancelOrderRequest()
487-
req.InstrumentID(toLocalSymbol(order.Symbol))
488-
if e.IsFutures {
489-
req.InstrumentID(toLocalSymbol(order.Symbol, okexapi.InstrumentTypeSwap))
490-
}
475+
req.InstrumentID(e.getInstrumentId(order.Symbol))
476+
491477
req.OrderID(strconv.FormatUint(order.OrderID, 10))
492478
if len(order.ClientOrderID) > 0 {
493479
if ok := clientOrderIdRegex.MatchString(order.ClientOrderID); !ok {
@@ -528,10 +514,7 @@ func (e *Exchange) QueryKLines(
528514
return nil, fmt.Errorf("failed to get interval: %w", err)
529515
}
530516

531-
instrumentID := toLocalSymbol(symbol)
532-
if e.IsFutures {
533-
instrumentID = toLocalSymbol(symbol, okexapi.InstrumentTypeSwap)
534-
}
517+
instrumentID := e.getInstrumentId(symbol)
535518

536519
req := e.client.NewGetCandlesRequest().InstrumentID(instrumentID)
537520
req.Bar(intervalParam)
@@ -566,10 +549,8 @@ func (e *Exchange) QueryOrder(ctx context.Context, q types.OrderQuery) (*types.O
566549
return nil, errors.New("okex.QueryOrder: OrderId or ClientOrderId is required parameter")
567550
}
568551
req := e.client.NewGetOrderDetailsRequest()
569-
instrumentID := toLocalSymbol(q.Symbol)
570-
if e.IsFutures {
571-
instrumentID = toLocalSymbol(q.Symbol, okexapi.InstrumentTypeSwap)
572-
}
552+
instrumentID := e.getInstrumentId(q.Symbol)
553+
573554
req.InstrumentID(instrumentID)
574555
// Either ordId or clOrdId is required, if both are passed, ordId will be used
575556
// ref: https://www.okx.com/docs-v5/en/#order-book-trading-trade-get-order-details
@@ -599,10 +580,7 @@ func (e *Exchange) QueryOrderTrades(ctx context.Context, q types.OrderQuery) (tr
599580

600581
req := e.client.NewGetThreeDaysTransactionHistoryRequest()
601582
if len(q.Symbol) != 0 {
602-
instrumentID := toLocalSymbol(q.Symbol)
603-
if e.IsFutures {
604-
instrumentID = toLocalSymbol(q.Symbol, okexapi.InstrumentTypeSwap)
605-
}
583+
instrumentID := e.getInstrumentId(q.Symbol)
606584
req.InstrumentID(instrumentID)
607585
}
608586

@@ -662,11 +640,7 @@ func (e *Exchange) QueryClosedOrders(
662640
}
663641

664642
req := e.client.NewGetOrderHistoryRequest()
665-
instrumentID := toLocalSymbol(symbol)
666-
if e.IsFutures {
667-
instrumentID = toLocalSymbol(symbol, okexapi.InstrumentTypeSwap)
668-
req.InstrumentType(okexapi.InstrumentTypeSwap)
669-
}
643+
instrumentID := e.getInstrumentId(symbol)
670644

671645
req.InstrumentID(instrumentID).
672646
StartTime(since).
@@ -822,6 +796,14 @@ func (e *Exchange) getInstrumentType() okexapi.InstrumentType {
822796
return okexapi.InstrumentTypeSpot
823797
}
824798

799+
func (e *Exchange) getInstrumentId(symbol string) string {
800+
if e.IsFutures {
801+
return toLocalSymbol(symbol, okexapi.InstrumentTypeSwap)
802+
}
803+
804+
return toLocalSymbol(symbol)
805+
}
806+
825807
func (e *Exchange) QueryDepositHistory(
826808
ctx context.Context, asset string, startTime, endTime *time.Time,
827809
) ([]types.Deposit, error) {
@@ -912,11 +894,7 @@ func (e *Exchange) QueryTrades(
912894
"req_id": uuid.New().String(),
913895
})
914896

915-
instrumentID := toLocalSymbol(symbol)
916-
if e.IsFutures {
917-
instrumentID = toLocalSymbol(symbol, okexapi.InstrumentTypeSwap)
918-
}
919-
897+
instrumentID := e.getInstrumentId(symbol)
920898
instrType := e.getInstrumentType()
921899

922900
if lessThan3Day {

pkg/exchange/okex/exchange_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,56 @@ func TestExchange_Futures_SubmitOrder(t *testing.T) {
500500
assert.NotEmpty(t, orders)
501501
}
502502

503+
func TestExchange_Futures_SubmitMarketOrder(t *testing.T) {
504+
key, secret, passphrase, ok := testutil.IntegrationTestWithPassphraseConfigured(t, "OKEX")
505+
if !ok {
506+
t.SkipNow()
507+
return
508+
}
509+
510+
ex := New(key, secret, passphrase)
511+
ex.UseFutures()
512+
dualSidePosition = true
513+
514+
markets, err := ex.QueryMarkets(context.Background())
515+
assert.NoError(t, err)
516+
517+
market := markets["BTCUSDT"]
518+
orders, err := ex.SubmitOrder(context.Background(), types.SubmitOrder{
519+
Symbol: "BTCUSDT",
520+
Side: types.SideTypeSell,
521+
Type: types.OrderTypeMarket,
522+
Quantity: market.MinQuantity,
523+
Market: market,
524+
})
525+
assert.NoError(t, err)
526+
assert.NotEmpty(t, orders)
527+
}
528+
529+
func TestExchange_Spot_SubmitMarketOrder(t *testing.T) {
530+
key, secret, passphrase, ok := testutil.IntegrationTestWithPassphraseConfigured(t, "OKEX")
531+
if !ok {
532+
t.SkipNow()
533+
return
534+
}
535+
536+
ex := New(key, secret, passphrase)
537+
538+
markets, err := ex.QueryMarkets(context.Background())
539+
assert.NoError(t, err)
540+
541+
market := markets["BTCUSDT"]
542+
orders, err := ex.SubmitOrder(context.Background(), types.SubmitOrder{
543+
Symbol: "BTCUSDT",
544+
Side: types.SideTypeBuy,
545+
Type: types.OrderTypeMarket,
546+
Quantity: market.MinQuantity,
547+
Market: market,
548+
})
549+
assert.NoError(t, err)
550+
assert.NotEmpty(t, orders)
551+
}
552+
503553
func TestExchange_QueryKlines(t *testing.T) {
504554
key, secret, passphrase, ok := testutil.IntegrationTestWithPassphraseConfigured(t, "OKEX")
505555
if !ok {
@@ -758,3 +808,52 @@ func TestExchange_QueryPositionRisk(t *testing.T) {
758808
}
759809
})
760810
}
811+
812+
func TestExchange_getInstrumentId(t *testing.T) {
813+
tests := []struct {
814+
name string
815+
symbol string
816+
isFutures bool
817+
expectedSymbol string
818+
}{
819+
{
820+
name: "BTCUSDT in spot mode",
821+
symbol: "BTCUSDT",
822+
isFutures: false,
823+
expectedSymbol: "BTC-USDT",
824+
},
825+
{
826+
name: "BTCUSDT in futures mode",
827+
symbol: "BTCUSDT",
828+
isFutures: true,
829+
expectedSymbol: "BTC-USDT-SWAP",
830+
},
831+
{
832+
name: "ETHUSDT in spot mode",
833+
symbol: "ETHUSDT",
834+
isFutures: false,
835+
expectedSymbol: "ETH-USDT",
836+
},
837+
{
838+
name: "ETHUSDT in futures mode",
839+
symbol: "ETHUSDT",
840+
isFutures: true,
841+
expectedSymbol: "ETH-USDT-SWAP",
842+
},
843+
{
844+
name: "empty symbol",
845+
symbol: "",
846+
isFutures: false,
847+
expectedSymbol: "",
848+
},
849+
}
850+
851+
for _, tt := range tests {
852+
t.Run(tt.name, func(t *testing.T) {
853+
ex := New("", "", "")
854+
ex.IsFutures = tt.isFutures
855+
got := ex.getInstrumentId(tt.symbol)
856+
assert.Equal(t, tt.expectedSymbol, got)
857+
})
858+
}
859+
}

pkg/types/market.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -269,9 +269,8 @@ func (m Market) AdjustQuantityByMaxAmount(quantity, currentPrice, maxAmount fixe
269269

270270
// AdjustQuantityToContractSize adjusts the quantity to contract size
271271
func (m Market) AdjustQuantityToContractSize(quantity fixedpoint.Value) fixedpoint.Value {
272-
// Validate input parameters
273-
if quantity.Sign() <= 0 || m.ContractValue.Sign() <= 0 || m.StepSize.Sign() <= 0 {
274-
return fixedpoint.Zero
272+
if m.ContractValue.Sign() <= 0 || m.StepSize.Sign() <= 0 {
273+
return quantity
275274
}
276275

277276
contractQuantity := quantity.Div(m.ContractValue)

pkg/types/market_test.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -300,11 +300,6 @@ func TestMarket_AdjustQuantityToContractSize(t *testing.T) {
300300
quantity: fixedpoint.Zero,
301301
expect: fixedpoint.Zero,
302302
},
303-
{
304-
name: "negative quantity",
305-
quantity: fixedpoint.NewFromFloat(-0.01),
306-
expect: fixedpoint.Zero,
307-
},
308303
{
309304
name: "small quantity",
310305
quantity: fixedpoint.NewFromFloat(0.005), // 0.5 contracts

0 commit comments

Comments
 (0)