exchanges/order: Add TimeInForce type (#1382)

* Added TimeInForce type and updated related files

* Linter issue fix and minor coinbasepro type update

* Bitrex consts update

* added unit test and minor changes in bittrex

* Unit tests update

* Fix minor linter issues

* Update TestStringToTimeInForce unit test

* fix conflict with gateio timeInForce

* Update order tests

* Complete updating the order unit tests

* update kucoin and deribit wrapper to match the time in force change

* fix time-in-force related test errors

* linter issue fix

* time in force constants, functions and unit tests update

* shift tif policies to TimeInForce

* Update time-in-force, related functions, and unit tests

* fix linter issue and time-in-force processing

* added a good till crossing tif value

* order type fix and fix related tim-in-force entries

* update time-in-force unmarshaling and unit test

* fix time-in-force error in gateio

* linter issue fix

* update based on review comments

* add unit test and fix missing issues

* minor fix and added benchmark unit test

* change GTT to GTC for limit

* fix linter issue

* added time-in-force value to place order param

* fix minor issues based on review comment and move tif code to separate files

* update on exchanges linked to time-in-force

* resolve missing review comments

* minor linter issues fix

* added time-in-force handler and update timeInForce parametered endpoint

* minor fixes based on review

* nits fix

* update based on review

* linter fix

* rm getTimeInForce func and minor change to time-in-force

* minor change

* update based on review comments

* wrappers and time-in-force calling approach

* minor change

* update gateio string to timeInForce conversion and unit test

* updated order test unit tes functions

* minor fixes on unit tests

* nits fix based on feedback

* update TestDeriveCancel unit test assert messages

* update TestDeriveCancel unit test assert messages

* update timeInForceFromString method to return formatted error and update functions using it

* restructure and fix minor exchanges time-in-force handling issues

* replaced unused getTypeFromTimeInForce with inline switch-based order type check

* separated the repeated timeInForce conversion code to  a function

* update exchanges time-in-force handling based on review comments

* limter fix

* edded comment to validTimesInForce var

* added comment to gateio's timeInForceString func

* added goodTillCancel switch case to gateio timeInForceString func
This commit is contained in:
Samuael A.
2025-05-23 02:07:09 +03:00
committed by GitHub
parent 8c678063b5
commit 640960aec1
46 changed files with 1201 additions and 1424 deletions

View File

@@ -152,7 +152,6 @@ var (
errInvalidLeverageValue = errors.New("invalid leverage value")
errInvalidRiskLimit = errors.New("new position risk limit")
errInvalidCountTotalValue = errors.New("invalid \"count_total\" value, supported \"count_total\" values are 0 and 1")
errInvalidTimeInForce = errors.New("invalid time in force value")
errInvalidAutoSizeValue = errors.New("invalid \"auto_size\" value, only \"close_long\" and \"close_short\" are supported")
errTooManyOrderRequest = errors.New("too many order creation request")
errInvalidTimeout = errors.New("invalid timeout, should be in seconds At least 5 seconds, 0 means cancel the countdown")
@@ -168,6 +167,24 @@ var (
errInvalidTextValue = errors.New("invalid text value, requires prefix `t-`")
)
// validTimesInForce holds a list of supported time-in-force values and corresponding string representations.
// slice iteration outperforms map with this few elements
var validTimesInForce = []struct {
String string
TimeInForce order.TimeInForce
}{
{gtcTIF, order.GoodTillCancel}, {iocTIF, order.ImmediateOrCancel}, {pocTIF, order.PostOnly}, {fokTIF, order.FillOrKill},
}
func timeInForceFromString(tif string) (order.TimeInForce, error) {
for a := range validTimesInForce {
if validTimesInForce[a].String == tif {
return validTimesInForce[a].TimeInForce, nil
}
}
return order.UnknownTIF, fmt.Errorf("%w: %q", order.ErrUnsupportedTimeInForce, tif)
}
// Gateio is the overarching type across this package
type Gateio struct {
Counter common.Counter // Must be first due to alignment requirements
@@ -829,7 +846,7 @@ func (g *Gateio) CreatePriceTriggeredOrder(ctx context.Context, arg *PriceTrigge
return nil, errNilArgument
}
if arg.Put.TimeInForce != gtcTIF && arg.Put.TimeInForce != iocTIF {
return nil, fmt.Errorf("%w, only 'gct' and 'ioc' are supported", errInvalidTimeInForce)
return nil, fmt.Errorf("%w: %q only 'gct' and 'ioc' are supported", order.ErrUnsupportedTimeInForce, arg.Put.TimeInForce)
}
if arg.Market.IsEmpty() {
return nil, fmt.Errorf("%w, %s", currency.ErrCurrencyPairEmpty, "field market is required")
@@ -2294,14 +2311,14 @@ func (g *Gateio) PlaceFuturesOrder(ctx context.Context, arg *ContractOrderCreate
if arg.Size == 0 {
return nil, fmt.Errorf("%w, specify positive number to make a bid, and negative number to ask", order.ErrSideIsInvalid)
}
if arg.TimeInForce != gtcTIF && arg.TimeInForce != iocTIF && arg.TimeInForce != pocTIF && arg.TimeInForce != fokTIF {
return nil, errInvalidTimeInForce
if _, err := timeInForceFromString(arg.TimeInForce); err != nil {
return nil, err
}
if arg.Price == "" {
return nil, errInvalidPrice
}
if arg.Price == "0" && arg.TimeInForce != iocTIF && arg.TimeInForce != fokTIF {
return nil, errInvalidTimeInForce
return nil, fmt.Errorf("%w: %q; only 'IOC' and 'FOK' allowed for market order", order.ErrUnsupportedTimeInForce, arg.TimeInForce)
}
if arg.AutoSize != "" && (arg.AutoSize == "close_long" || arg.AutoSize == "close_short") {
return nil, errInvalidAutoSizeValue
@@ -2383,17 +2400,14 @@ func (g *Gateio) PlaceBatchFuturesOrders(ctx context.Context, settle currency.Co
if args[x].Size == 0 {
return nil, fmt.Errorf("%w, specify positive number to make a bid, and negative number to ask", order.ErrSideIsInvalid)
}
if args[x].TimeInForce != gtcTIF &&
args[x].TimeInForce != iocTIF &&
args[x].TimeInForce != pocTIF &&
args[x].TimeInForce != fokTIF {
return nil, errInvalidTimeInForce
if _, err := timeInForceFromString(args[x].TimeInForce); err != nil {
return nil, err
}
if args[x].Price == "" {
return nil, errInvalidPrice
}
if args[x].Price == "0" && args[x].TimeInForce != iocTIF && args[x].TimeInForce != fokTIF {
return nil, errInvalidTimeInForce
return nil, fmt.Errorf("%w: %q; only 'ioc' and 'fok' allowed for market order", order.ErrUnsupportedTimeInForce, args[x].TimeInForce)
}
if args[x].Text != "" && !strings.HasPrefix(args[x].Text, "t-") {
return nil, errInvalidTextValue
@@ -2547,7 +2561,7 @@ func (g *Gateio) CreatePriceTriggeredFuturesOrder(ctx context.Context, settle cu
return nil, fmt.Errorf("%w, price must be greater than 0", errInvalidPrice)
}
if arg.Initial.TimeInForce != "" && arg.Initial.TimeInForce != gtcTIF && arg.Initial.TimeInForce != iocTIF {
return nil, fmt.Errorf("%w, only time in force value 'gtc' and 'ioc' are supported", errInvalidTimeInForce)
return nil, fmt.Errorf("%w: %q; only 'gtc' and 'ioc' are allowed", order.ErrInvalidTimeInForce, arg.Initial.TimeInForce)
}
if arg.Trigger.StrategyType != 0 && arg.Trigger.StrategyType != 1 {
return nil, errors.New("strategy type must be 0 or 1, 0: by price, and 1: by price gap")
@@ -2870,8 +2884,8 @@ func (g *Gateio) PlaceDeliveryOrder(ctx context.Context, arg *ContractOrderCreat
if arg.Size == 0 {
return nil, fmt.Errorf("%w, specify positive number to make a bid, and negative number to ask", order.ErrSideIsInvalid)
}
if arg.TimeInForce != gtcTIF && arg.TimeInForce != iocTIF && arg.TimeInForce != pocTIF && arg.TimeInForce != fokTIF {
return nil, errInvalidTimeInForce
if _, err := timeInForceFromString(arg.TimeInForce); err != nil {
return nil, err
}
if arg.Price == "" {
return nil, errInvalidPrice
@@ -3071,7 +3085,7 @@ func (g *Gateio) GetDeliveryPriceTriggeredOrder(ctx context.Context, settle curr
}
if arg.Initial.TimeInForce != "" &&
arg.Initial.TimeInForce != gtcTIF && arg.Initial.TimeInForce != iocTIF {
return nil, fmt.Errorf("%w, only time in force value 'gtc' and 'ioc' are supported", errInvalidTimeInForce)
return nil, fmt.Errorf("%w: %q; only 'gtc' and 'ioc' are allowed", order.ErrUnsupportedTimeInForce, arg.Initial.TimeInForce)
}
if arg.Trigger.StrategyType != 0 && arg.Trigger.StrategyType != 1 {
return nil, errors.New("strategy type must be 0 or 1, 0: by price, and 1: by price gap")

View File

@@ -1816,13 +1816,14 @@ func TestSubmitOrder(t *testing.T) {
sharedtestvalues.SkipTestIfCredentialsUnset(t, g, canManipulateRealOrders)
for _, a := range g.GetAssetTypes(false) {
_, err := g.SubmitOrder(t.Context(), &order.Submit{
Exchange: g.Name,
Pair: getPair(t, a),
Side: order.Buy,
Type: order.Limit,
Price: 1,
Amount: 1,
AssetType: a,
Exchange: g.Name,
Pair: getPair(t, a),
Side: order.Buy,
Type: order.Limit,
Price: 1,
Amount: 1,
AssetType: a,
TimeInForce: order.GoodTillCancel,
})
assert.NoErrorf(t, err, "SubmitOrder should not error for %s", a)
}
@@ -2738,25 +2739,6 @@ func TestGetClientOrderIDFromText(t *testing.T) {
assert.Equal(t, "t-123", getClientOrderIDFromText("t-123"), "should return t-123")
}
func TestGetTypeFromTimeInForce(t *testing.T) {
t.Parallel()
typeResp, postOnly := getTypeFromTimeInForce("gtc")
assert.Equal(t, order.Limit, typeResp, "should be a limit order")
assert.False(t, postOnly, "should return false")
typeResp, postOnly = getTypeFromTimeInForce("ioc")
assert.Equal(t, order.Market, typeResp, "should be market order")
assert.False(t, postOnly, "should return false")
typeResp, postOnly = getTypeFromTimeInForce("poc")
assert.Equal(t, order.Limit, typeResp, "should be limit order")
assert.True(t, postOnly, "should return true")
typeResp, postOnly = getTypeFromTimeInForce("fok")
assert.Equal(t, order.Market, typeResp, "should be market order")
assert.False(t, postOnly, "should return false")
}
func TestGetSideAndAmountFromSize(t *testing.T) {
t.Parallel()
side, amount, remaining := getSideAndAmountFromSize(1, 1)
@@ -2784,29 +2766,6 @@ func TestGetFutureOrderSize(t *testing.T) {
assert.Equal(t, -1.0, ret)
}
func TestGetTimeInForce(t *testing.T) {
t.Parallel()
_, err := getTimeInForce(&order.Submit{Type: order.Market, PostOnly: true})
assert.ErrorIs(t, err, errPostOnlyOrderTypeUnsupported)
ret, err := getTimeInForce(&order.Submit{Type: order.Market})
require.NoError(t, err)
assert.Equal(t, "ioc", ret)
ret, err = getTimeInForce(&order.Submit{Type: order.Limit, PostOnly: true})
require.NoError(t, err)
assert.Equal(t, "poc", ret)
ret, err = getTimeInForce(&order.Submit{Type: order.Limit})
require.NoError(t, err)
assert.Equal(t, "gtc", ret)
ret, err = getTimeInForce(&order.Submit{Type: order.Market, FillOrKill: true})
require.NoError(t, err)
assert.Equal(t, "fok", ret)
}
func TestProcessFuturesOrdersPushData(t *testing.T) {
t.Parallel()
testCases := []struct {
@@ -2967,7 +2926,7 @@ func TestDeriveSpotWebsocketOrderResponse(t *testing.T) {
Type: order.Market,
Side: order.Sell,
Status: order.Filled,
ImmediateOrCancel: true,
TimeInForce: order.ImmediateOrCancel,
Cost: 0.0001,
Purchased: 9.35033,
}, got)
@@ -3010,7 +2969,7 @@ func TestDeriveSpotWebsocketOrderResponses(t *testing.T) {
Type: order.Market,
Side: order.Sell,
Status: order.Filled,
ImmediateOrCancel: true,
TimeInForce: order.ImmediateOrCancel,
Cost: 0.0001,
Purchased: 9.35033,
},
@@ -3028,7 +2987,7 @@ func TestDeriveSpotWebsocketOrderResponses(t *testing.T) {
Type: order.Market,
Side: order.Buy,
Status: order.Filled,
ImmediateOrCancel: true,
TimeInForce: order.ImmediateOrCancel,
Cost: 9.991512,
Purchased: 816.3,
},
@@ -3046,7 +3005,7 @@ func TestDeriveSpotWebsocketOrderResponses(t *testing.T) {
Type: order.Limit,
Side: order.Buy,
Status: order.Filled,
FillOrKill: true,
TimeInForce: order.FillOrKill,
Cost: 7.346,
Purchased: 200,
},
@@ -3064,7 +3023,7 @@ func TestDeriveSpotWebsocketOrderResponses(t *testing.T) {
Type: order.Limit,
Side: order.Buy,
Status: order.Open,
PostOnly: true,
TimeInForce: order.PostOnly,
},
{
Exchange: g.Name,
@@ -3080,6 +3039,7 @@ func TestDeriveSpotWebsocketOrderResponses(t *testing.T) {
Type: order.Limit,
Side: order.Sell,
Status: order.Open,
TimeInForce: order.GoodTillCancel,
},
},
},
@@ -3127,7 +3087,7 @@ func TestDeriveFuturesWebsocketOrderResponse(t *testing.T) {
Type: order.Market,
Side: order.Long,
Status: order.Filled,
ImmediateOrCancel: true,
TimeInForce: order.ImmediateOrCancel,
ReduceOnly: true,
}, got)
}
@@ -3170,7 +3130,7 @@ func TestDeriveFuturesWebsocketOrderResponses(t *testing.T) {
Type: order.Market,
Side: order.Long,
Status: order.Filled,
ImmediateOrCancel: true,
TimeInForce: order.ImmediateOrCancel,
ReduceOnly: true,
},
{
@@ -3186,7 +3146,7 @@ func TestDeriveFuturesWebsocketOrderResponses(t *testing.T) {
Type: order.Market,
Side: order.Short,
Status: order.Filled,
ImmediateOrCancel: true,
TimeInForce: order.ImmediateOrCancel,
},
{
Exchange: g.Name,
@@ -3201,6 +3161,7 @@ func TestDeriveFuturesWebsocketOrderResponses(t *testing.T) {
Type: order.Limit,
Side: order.Long,
Status: order.Open,
TimeInForce: order.GoodTillCancel,
},
{
Exchange: g.Name,
@@ -3215,6 +3176,7 @@ func TestDeriveFuturesWebsocketOrderResponses(t *testing.T) {
Type: order.Limit,
Side: order.Short,
Status: order.Open,
TimeInForce: order.GoodTillCancel,
},
{
Exchange: g.Name,
@@ -3228,7 +3190,7 @@ func TestDeriveFuturesWebsocketOrderResponses(t *testing.T) {
Type: order.Market,
Side: order.Long,
Status: order.Filled,
ImmediateOrCancel: true,
TimeInForce: order.ImmediateOrCancel,
},
{
Exchange: g.Name,
@@ -3242,7 +3204,7 @@ func TestDeriveFuturesWebsocketOrderResponses(t *testing.T) {
Type: order.Market,
Side: order.Short,
Status: order.Filled,
ImmediateOrCancel: true,
TimeInForce: order.ImmediateOrCancel,
ReduceOnly: true,
},
},
@@ -3335,3 +3297,51 @@ func getPairs(tb testing.TB, a asset.Item) currency.Pairs {
return enabledPairs
}
func BenchmarkTimeInForceFromString(b *testing.B) {
for b.Loop() {
for _, tifString := range []string{gtcTIF, iocTIF, pocTIF, fokTIF} {
if _, err := timeInForceFromString(tifString); err != nil {
b.Fatal(tifString)
}
}
}
}
func TestTimeInForceFromString(t *testing.T) {
t.Parallel()
_, err := timeInForceFromString("abcdef")
assert.ErrorIs(t, err, order.ErrUnsupportedTimeInForce)
for k, v := range map[string]order.TimeInForce{gtcTIF: order.GoodTillCancel, iocTIF: order.ImmediateOrCancel, pocTIF: order.PostOnly, fokTIF: order.FillOrKill} {
t.Run(k, func(t *testing.T) {
t.Parallel()
tif, err := timeInForceFromString(k)
require.NoError(t, err)
assert.Equal(t, v, tif)
})
}
}
func TestGetTypeFromTimeInForce(t *testing.T) {
t.Parallel()
typeResp := getTypeFromTimeInForce("gtc", 0)
assert.Equal(t, order.Limit, typeResp)
typeResp = getTypeFromTimeInForce("ioc", 0)
assert.Equal(t, order.Market, typeResp, "should be market order")
typeResp = getTypeFromTimeInForce("poc", 123)
assert.Equal(t, order.Limit, typeResp, "should be limit order")
typeResp = getTypeFromTimeInForce("fok", 0)
assert.Equal(t, order.Market, typeResp, "should be market order")
}
func TestTimeInForceString(t *testing.T) {
t.Parallel()
assert.Empty(t, timeInForceString(order.UnknownTIF))
for _, valid := range validTimesInForce {
assert.Equal(t, valid.String, timeInForceString(valid.TimeInForce))
}
}

View File

@@ -972,10 +972,6 @@ func (g *Gateio) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Submi
if err != nil {
return nil, err
}
timeInForce, err := getTimeInForce(s)
if err != nil {
return nil, err
}
settle, err := getSettlementCurrency(s.Pair, s.AssetType)
if err != nil {
return nil, err
@@ -986,7 +982,7 @@ func (g *Gateio) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Submi
Price: strconv.FormatFloat(s.Price, 'f', -1, 64), // Cannot be an empty string, requires "0" for market orders.
Settle: settle,
ReduceOnly: s.ReduceOnly,
TimeInForce: timeInForce,
TimeInForce: timeInForceString(s.TimeInForce),
Text: s.ClientOrderID,
}
var o *Order
@@ -1304,8 +1300,10 @@ func (g *Gateio) GetOrderInfo(ctx context.Context, orderID string, pair currency
}
side, amount, remaining := getSideAndAmountFromSize(fOrder.Size, fOrder.RemainingAmount)
ordertype, postonly := getTypeFromTimeInForce(fOrder.TimeInForce)
tif, err := timeInForceFromString(fOrder.TimeInForce)
if err != nil {
return nil, err
}
return &order.Detail{
Amount: amount,
ExecutedAmount: amount - remaining,
@@ -1320,8 +1318,8 @@ func (g *Gateio) GetOrderInfo(ctx context.Context, orderID string, pair currency
LastUpdated: fOrder.FinishTime.Time(),
Pair: pair,
AssetType: a,
Type: ordertype,
PostOnly: postonly,
Type: getTypeFromTimeInForce(fOrder.TimeInForce, fOrder.OrderPrice.Float64()),
TimeInForce: tif,
Side: side,
}, nil
case asset.Options:
@@ -1507,8 +1505,11 @@ func (g *Gateio) GetActiveOrders(ctx context.Context, req *order.MultiOrderReque
if futuresOrders[i].Status != statusOpen || (len(req.Pairs) > 0 && !req.Pairs.Contains(pair, true)) {
continue
}
side, amount, remaining := getSideAndAmountFromSize(futuresOrders[i].Size, futuresOrders[i].RemainingAmount)
tif, err := timeInForceFromString(futuresOrders[i].TimeInForce)
if err != nil {
return nil, err
}
orders = append(orders, order.Detail{
Status: order.Open,
Amount: amount,
@@ -1527,7 +1528,7 @@ func (g *Gateio) GetActiveOrders(ctx context.Context, req *order.MultiOrderReque
Type: order.Limit,
SettlementCurrency: settle,
ReduceOnly: futuresOrders[i].IsReduceOnly,
PostOnly: futuresOrders[i].TimeInForce == "poc",
TimeInForce: tif,
AverageExecutedPrice: futuresOrders[i].FillPrice.Float64(),
})
}
@@ -2252,16 +2253,17 @@ func getClientOrderIDFromText(text string) string {
}
// getTypeFromTimeInForce returns the order type and if the order is post only
func getTypeFromTimeInForce(tif string) (orderType order.Type, postOnly bool) {
func getTypeFromTimeInForce(tif string, price float64) (orderType order.Type) {
switch tif {
case iocTIF:
return order.Market, false
case fokTIF:
return order.Market, false
case pocTIF:
return order.Limit, true
case iocTIF, fokTIF:
return order.Market
case pocTIF, gtcTIF:
return order.Limit
default:
return order.Limit, false
if price == 0 {
return order.Market
}
return order.Limit
}
}
@@ -2285,27 +2287,6 @@ func getFutureOrderSize(s *order.Submit) (float64, error) {
}
}
var errPostOnlyOrderTypeUnsupported = errors.New("post only is only supported for limit orders")
// getTimeInForce returns the time in force for a given order. If Market order
// IOC
func getTimeInForce(s *order.Submit) (string, error) {
timeInForce := "gtc" // limit order taker/maker
if s.Type == order.Market || s.ImmediateOrCancel {
timeInForce = iocTIF // market taker only
}
if s.PostOnly {
if s.Type != order.Limit {
return "", fmt.Errorf("%w not for %v", errPostOnlyOrderTypeUnsupported, s.Type)
}
timeInForce = pocTIF // limit order maker only
}
if s.FillOrKill {
timeInForce = fokTIF // limit order entire fill or kill
}
return timeInForce, nil
}
// GetCurrencyTradeURL returns the URL to the exchange's trade page for the given asset and currency pair
func (g *Gateio) GetCurrencyTradeURL(_ context.Context, a asset.Item, cp currency.Pair) (string, error) {
_, err := g.CurrencyPairs.IsPairEnabled(cp, a)
@@ -2362,17 +2343,12 @@ func (g *Gateio) WebsocketSubmitOrder(ctx context.Context, s *order.Submit) (*or
return nil, err
}
timeInForce, err := getTimeInForce(s)
if err != nil {
return nil, err
}
resp, err := g.WebsocketFuturesSubmitOrder(ctx, s.AssetType, &ContractOrderCreateParams{
Contract: s.Pair,
Size: amountWithDirection,
Price: strconv.FormatFloat(s.Price, 'f', -1, 64),
ReduceOnly: s.ReduceOnly,
TimeInForce: timeInForce,
TimeInForce: timeInForceString(s.TimeInForce),
Text: s.ClientOrderID,
})
if err != nil {
@@ -2384,6 +2360,24 @@ func (g *Gateio) WebsocketSubmitOrder(ctx context.Context, s *order.Submit) (*or
}
}
// timeInForceString returns the most relevant time-in-force exchange string for a TimeInForce
// Any TIF value that is combined with POC, IOC or FOK will just return that
// Otherwise the lowercase representation is returned
func timeInForceString(tif order.TimeInForce) string {
switch {
case tif.Is(order.PostOnly):
return "poc"
case tif.Is(order.ImmediateOrCancel):
return iocTIF
case tif.Is(order.FillOrKill):
return fokTIF
case tif.Is(order.GoodTillCancel):
return gtcTIF
default:
return tif.Lower()
}
}
func (g *Gateio) deriveSpotWebsocketOrderResponse(responses *WebsocketOrderResponse) (*order.SubmitResponse, error) {
resp, err := g.deriveSpotWebsocketOrderResponses([]*WebsocketOrderResponse{responses})
if err != nil {
@@ -2427,7 +2421,10 @@ func (g *Gateio) deriveSpotWebsocketOrderResponses(responses []*WebsocketOrderRe
purchased = resp.FilledTotal.Float64()
}
}
tif, err := order.StringToTimeInForce(resp.TimeInForce)
if err != nil {
return nil, err
}
out = append(out, &order.SubmitResponse{
Exchange: g.Name,
OrderID: resp.ID,
@@ -2443,9 +2440,7 @@ func (g *Gateio) deriveSpotWebsocketOrderResponses(responses []*WebsocketOrderRe
Type: oType,
Side: side,
Status: status,
ImmediateOrCancel: resp.TimeInForce == iocTIF,
FillOrKill: resp.TimeInForce == fokTIF,
PostOnly: resp.TimeInForce == pocTIF,
TimeInForce: tif,
Cost: cost,
Purchased: purchased,
Fee: resp.Fee.Float64(),
@@ -2494,7 +2489,10 @@ func (g *Gateio) deriveFuturesWebsocketOrderResponses(responses []*WebsocketFutu
if resp.Text != "" && strings.HasPrefix(resp.Text, "t-") {
clientOrderID = resp.Text
}
tif, err := order.StringToTimeInForce(resp.TimeInForce)
if err != nil {
return nil, err
}
out = append(out, &order.SubmitResponse{
Exchange: g.Name,
OrderID: strconv.FormatInt(resp.ID, 10),
@@ -2510,9 +2508,7 @@ func (g *Gateio) deriveFuturesWebsocketOrderResponses(responses []*WebsocketFutu
Type: oType,
Side: side,
Status: status,
ImmediateOrCancel: resp.TimeInForce == iocTIF,
FillOrKill: resp.TimeInForce == fokTIF,
PostOnly: resp.TimeInForce == pocTIF,
TimeInForce: tif,
ReduceOnly: resp.IsReduceOnly,
})
}
@@ -2529,9 +2525,12 @@ func (g *Gateio) getSpotOrderRequest(s *order.Submit) (*CreateOrderRequest, erro
return nil, order.ErrSideIsInvalid
}
timeInForce, err := getTimeInForce(s)
if err != nil {
return nil, err
var timeInForce string
switch s.TimeInForce {
case order.ImmediateOrCancel, order.FillOrKill, order.GoodTillCancel:
timeInForce = s.TimeInForce.Lower()
case order.PostOnly:
timeInForce = "poc"
}
return &CreateOrderRequest{