gateio/kucoin: assortment of fixes (#1404)

* gateio: fix unmarshal bug and update fields

* gateio: fix wrapper function function, add helper methods

* update order types and add kucoin wrapper fix

* currency pairs

* Add tests

* gateio; inspect error and continue for no funds in account, kucoin: fetch all settlement amounts

* futures: order fixit

* finish off gateio updates for market orders

* cute line

* Update exchanges/kucoin/kucoin_wrapper.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update exchanges/kucoin/kucoin_wrapper.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update exchanges/gateio/gateio.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update exchanges/gateio/gateio_wrapper.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update exchanges/gateio/gateio_wrapper.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* glorious: nits

* glorious: nits - filter by pair match and fix bug where the endpoint returns details instead of message

* Add fix for leverage check (non-merge) my ip has been blocked from gateio still... scammmmmmmm

* glorious: nitters

* Update exchanges/gateio/gateio_test.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* Update exchanges/gateio/gateio_test.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* Update exchanges/gateio/gateio_test.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

---------

Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
Co-authored-by: Scott <gloriousCode@users.noreply.github.com>
Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>
This commit is contained in:
Ryan O'Hara-Reid
2024-03-06 13:06:13 +11:00
committed by GitHub
parent 64dbfd6e4f
commit 6ccb0e0c2f
11 changed files with 558 additions and 245 deletions

View File

@@ -164,6 +164,7 @@ var (
errInvalidSubAccountUserID = errors.New("sub-account user id is required")
errCannotParseSettlementCurrency = errors.New("cannot derive settlement currency")
errMissingAPIKey = errors.New("missing API key information")
errInvalidTextValue = errors.New("invalid text value, requires prefix `t-`")
)
// Gateio is the overarching type across this package
@@ -554,7 +555,6 @@ func (g *Gateio) CreateBatchOrders(ctx context.Context, args []CreateOrderReques
if len(args) > 10 {
return nil, fmt.Errorf("%w only 10 orders are canceled at once", errMultipleOrders)
}
var err error
for x := range args {
if (x != 0) && args[x-1].Account != args[x].Account {
return nil, errDifferentAccount
@@ -574,13 +574,6 @@ func (g *Gateio) CreateBatchOrders(ctx context.Context, args []CreateOrderReques
!strings.EqualFold(args[x].Account, asset.Margin.String()) {
return nil, errors.New("only spot, margin, and cross_margin area allowed")
}
if args[x].Text == "" {
args[x].Text, err = common.GenerateRandomString(10, common.NumberCharacters)
if err != nil {
return nil, err
}
args[x].Text = "t-" + args[x].Text
}
if args[x].Amount <= 0 {
return nil, errInvalidAmount
}
@@ -651,15 +644,6 @@ func (g *Gateio) PlaceSpotOrder(ctx context.Context, arg *CreateOrderRequestData
!strings.EqualFold(arg.Account, asset.Margin.String()) {
return nil, errors.New("only 'spot', 'cross_margin', and 'margin' area allowed")
}
if arg.Text != "" {
arg.Text = "t-" + arg.Text
} else {
randomString, err := common.GenerateRandomString(10, common.NumberCharacters)
if err != nil {
return nil, err
}
arg.Text = "t-" + randomString
}
if arg.Amount <= 0 {
return nil, errInvalidAmount
}
@@ -2155,12 +2139,16 @@ func (g *Gateio) GetFuturesAccountBooks(ctx context.Context, settle string, limi
}
// GetAllFuturesPositionsOfUsers list all positions of users.
func (g *Gateio) GetAllFuturesPositionsOfUsers(ctx context.Context, settle string) (*Position, error) {
func (g *Gateio) GetAllFuturesPositionsOfUsers(ctx context.Context, settle string, realPositionsOnly bool) ([]Position, error) {
if settle == "" {
return nil, errEmptySettlementCurrency
}
var response *Position
return response, g.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, perpetualSwapPrivateEPL, http.MethodGet, futuresPath+settle+"/positions", nil, nil, &response)
params := url.Values{}
if realPositionsOnly {
params.Set("holding", "true")
}
var response []Position
return response, g.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, perpetualSwapPrivateEPL, http.MethodGet, futuresPath+settle+"/positions", params, nil, &response)
}
// GetSinglePosition returns a single position
@@ -2342,20 +2330,14 @@ func (g *Gateio) PlaceFuturesOrder(ctx context.Context, arg *OrderCreateParams)
if arg.Size == 0 {
return nil, fmt.Errorf("%w, specify positive number to make a bid, and negative number to ask", errInvalidOrderSide)
}
if arg.TimeInForce != gtcTIF && arg.TimeInForce != iocTIF && arg.TimeInForce != pocTIF && arg.TimeInForce != focTIF {
if arg.TimeInForce != gtcTIF && arg.TimeInForce != iocTIF && arg.TimeInForce != pocTIF && arg.TimeInForce != fokTIF {
return nil, errInvalidTimeInForce
}
if arg.Price < 0 {
if arg.Price == "" {
return nil, errInvalidPrice
}
if arg.Text != "" {
arg.Text = "t-" + arg.Text
} else {
randomString, err := common.GenerateRandomString(10, common.NumberCharacters)
if err != nil {
return nil, err
}
arg.Text = "t-" + randomString
if arg.Price == "0" && arg.TimeInForce != iocTIF && arg.TimeInForce != fokTIF {
return nil, errInvalidTimeInForce
}
if arg.AutoSize != "" && (arg.AutoSize == "close_long" || arg.AutoSize == "close_short") {
return nil, errInvalidAutoSizeValue
@@ -2366,10 +2348,13 @@ func (g *Gateio) PlaceFuturesOrder(ctx context.Context, arg *OrderCreateParams)
}
var response *Order
return response, g.SendAuthenticatedHTTPRequest(ctx,
exchange.RestSpot, perpetualSwapPlaceOrdersEPL,
exchange.RestSpot,
perpetualSwapPlaceOrdersEPL,
http.MethodPost,
futuresPath+arg.Settle+ordersPath,
nil, &arg, &response)
nil,
&arg,
&response)
}
// GetFuturesOrders retrieves list of futures orders
@@ -2378,11 +2363,10 @@ func (g *Gateio) GetFuturesOrders(ctx context.Context, contract currency.Pair, s
if settle == "" {
return nil, errEmptySettlementCurrency
}
if contract.IsInvalid() {
return nil, fmt.Errorf("%w, currency pair for contract must not be empty", errInvalidOrMissingContractParam)
}
params := url.Values{}
params.Set("contract", contract.String())
if !contract.IsEmpty() {
params.Set("contract", contract.String())
}
if status != statusOpen && status != statusFinished {
return nil, fmt.Errorf("%w, only 'open' and 'finished' status are supported", errInvalidOrderStatus)
}
@@ -2448,17 +2432,17 @@ func (g *Gateio) PlaceBatchFuturesOrders(ctx context.Context, settle string, arg
if args[x].TimeInForce != gtcTIF &&
args[x].TimeInForce != iocTIF &&
args[x].TimeInForce != pocTIF &&
args[x].TimeInForce != focTIF {
args[x].TimeInForce != fokTIF {
return nil, errInvalidTimeInForce
}
if args[x].Price > 0 && args[x].TimeInForce == iocTIF {
args[x].Price = 0
}
if args[x].Price < 0 {
if args[x].Price == "" {
return nil, errInvalidPrice
}
if args[x].Price == "0" && args[x].TimeInForce != iocTIF && args[x].TimeInForce != fokTIF {
return nil, errInvalidTimeInForce
}
if args[x].Text != "" && !strings.HasPrefix(args[x].Text, "t-") {
args[x].Text = "t-" + args[x].Text
return nil, errInvalidTextValue
}
if args[x].AutoSize != "" && (args[x].AutoSize == "close_long" || args[x].AutoSize == "close_short") {
return nil, errInvalidAutoSizeValue
@@ -2980,21 +2964,12 @@ func (g *Gateio) PlaceDeliveryOrder(ctx context.Context, arg *OrderCreateParams)
if arg.Size == 0 {
return nil, fmt.Errorf("%w, specify positive number to make a bid, and negative number to ask", errInvalidOrderSide)
}
if arg.TimeInForce != gtcTIF && arg.TimeInForce != iocTIF && arg.TimeInForce != pocTIF && arg.TimeInForce != focTIF {
if arg.TimeInForce != gtcTIF && arg.TimeInForce != iocTIF && arg.TimeInForce != pocTIF && arg.TimeInForce != fokTIF {
return nil, errInvalidTimeInForce
}
if arg.Price < 0 {
if arg.Price == "" {
return nil, errInvalidPrice
}
if arg.Text != "" {
arg.Text = "t-" + arg.Text
} else {
randomString, err := common.GenerateRandomString(10, common.NumberCharacters)
if err != nil {
return nil, err
}
arg.Text = "t-" + randomString
}
if arg.AutoSize != "" && (arg.AutoSize == "close_long" || arg.AutoSize == "close_short") {
return nil, errInvalidAutoSizeValue
}
@@ -3013,11 +2988,10 @@ func (g *Gateio) GetDeliveryOrders(ctx context.Context, contract currency.Pair,
if settle == "" {
return nil, errEmptySettlementCurrency
}
if contract.IsInvalid() {
return nil, fmt.Errorf("%w, currency pair for contract must not be empty", errInvalidOrMissingContractParam)
}
params := url.Values{}
params.Set("contract", contract.String())
if !contract.IsEmpty() {
params.Set("contract", contract.String())
}
if status != statusOpen && status != statusFinished {
return nil, fmt.Errorf("%w, only 'open' and 'finished' status are supported", errInvalidOrderStatus)
}
@@ -3498,12 +3472,6 @@ func (g *Gateio) PlaceOptionOrder(ctx context.Context, arg *OptionOrderParam) (*
if arg.TimeInForce == iocTIF || arg.Price < 0 {
arg.Price = 0
}
var err error
arg.Text, err = common.GenerateRandomString(10, common.NumberCharacters)
if err != nil {
return nil, err
}
arg.Text = "t-" + arg.Text
var response *OptionOrderResponse
return response, g.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, perpetualSwapPlaceOrdersEPL, http.MethodPost,
gateioOptionsOrders, nil, &arg, &response)

View File

@@ -12,6 +12,7 @@ import (
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/config"
@@ -1117,7 +1118,7 @@ func TestGetFuturesAccountBooks(t *testing.T) {
func TestGetAllPositionsOfUsers(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, g)
if _, err := g.GetAllFuturesPositionsOfUsers(context.Background(), settleUSDT); err != nil {
if _, err := g.GetAllFuturesPositionsOfUsers(context.Background(), settleUSDT, true); err != nil {
t.Errorf("%s GetAllPositionsOfUsers() error %v", g.Name, err)
}
}
@@ -1177,7 +1178,7 @@ func TestCreateDeliveryOrder(t *testing.T) {
Contract: getPair(t, asset.DeliveryFutures),
Size: 6024,
Iceberg: 0,
Price: 3765,
Price: "3765",
Text: "t-my-custom-id",
Settle: settle,
TimeInForce: gtcTIF,
@@ -1388,7 +1389,7 @@ func TestCreateFuturesOrder(t *testing.T) {
Contract: getPair(t, asset.Futures),
Size: 6024,
Iceberg: 0,
Price: 3765,
Price: "3765",
TimeInForce: "gtc",
Text: "t-my-custom-id",
Settle: settle,
@@ -1441,7 +1442,7 @@ func TestCreateBatchFuturesOrders(t *testing.T) {
Contract: getPair(t, asset.Futures),
Size: 6024,
Iceberg: 0,
Price: 3765,
Price: "3765",
TimeInForce: "gtc",
Text: "t-my-custom-id",
Settle: settle,
@@ -1450,7 +1451,7 @@ func TestCreateBatchFuturesOrders(t *testing.T) {
Contract: currency.NewPair(currency.BTC, currency.USDT),
Size: 232,
Iceberg: 0,
Price: 376225,
Price: "376225",
TimeInForce: "gtc",
Text: "t-my-custom-id",
Settle: settleBTC,
@@ -3483,3 +3484,78 @@ func getPair(tb testing.TB, a asset.Item) currency.Pair {
return pairs[a]
}
func TestGetClientOrderIDFromText(t *testing.T) {
t.Parallel()
assert.Zero(t, getClientOrderIDFromText("api"), "should not return anything")
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)
assert.Equal(t, order.Long, side, "should be a buy order")
assert.Equal(t, 1.0, amount, "should be 1.0")
assert.Equal(t, 1.0, remaining, "should be 1.0")
side, amount, remaining = getSideAndAmountFromSize(-1, -1)
assert.Equal(t, order.Short, side, "should be a sell order")
assert.Equal(t, 1.0, amount, "should be 1.0")
assert.Equal(t, 1.0, remaining, "should be 1.0")
}
func TestGetFutureOrderSize(t *testing.T) {
t.Parallel()
_, err := getFutureOrderSize(&order.Submit{Side: order.CouldNotCloseShort, Amount: 1})
assert.ErrorIs(t, err, errInvalidOrderSide)
ret, err := getFutureOrderSize(&order.Submit{Side: order.Buy, Amount: 1})
require.NoError(t, err)
assert.Equal(t, 1.0, ret)
ret, err = getFutureOrderSize(&order.Submit{Side: order.Sell, Amount: 1})
require.NoError(t, err)
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)
}

View File

@@ -21,7 +21,7 @@ const (
gtcTIF = "gtc" // good-'til-canceled
iocTIF = "ioc" // immediate-or-cancel
pocTIF = "poc"
focTIF = "foc" // fill-or-kill
fokTIF = "fok" // fill-or-kill
// frequently used order Status
@@ -1742,19 +1742,19 @@ type Position struct {
Size int64 `json:"size"`
Leverage types.Number `json:"leverage"`
RiskLimit types.Number `json:"risk_limit"`
LeverageMax string `json:"leverage_max"`
LeverageMax types.Number `json:"leverage_max"`
MaintenanceRate types.Number `json:"maintenance_rate"`
Value types.Number `json:"value"`
Margin types.Number `json:"margin"`
EntryPrice types.Number `json:"entry_price"`
LiqPrice types.Number `json:"liq_price"`
MarkPrice types.Number `json:"mark_price"`
UnrealisedPnl string `json:"unrealised_pnl"`
RealisedPnl string `json:"realised_pnl"`
HistoryPnl string `json:"history_pnl"`
LastClosePnl string `json:"last_close_pnl"`
RealisedPoint string `json:"realised_point"`
HistoryPoint string `json:"history_point"`
UnrealisedPnl types.Number `json:"unrealised_pnl"`
RealisedPnl types.Number `json:"realised_pnl"`
HistoryPnl types.Number `json:"history_pnl"`
LastClosePnl types.Number `json:"last_close_pnl"`
RealisedPoint types.Number `json:"realised_point"`
HistoryPoint types.Number `json:"history_point"`
AdlRanking int64 `json:"adl_ranking"`
PendingOrders int64 `json:"pending_orders"`
CloseOrder struct {
@@ -1794,18 +1794,16 @@ type DualModeResponse struct {
// OrderCreateParams represents future order creation parameters
type OrderCreateParams struct {
Contract currency.Pair `json:"contract"`
Size float64 `json:"size"`
Iceberg int64 `json:"iceberg"`
Price types.Number `json:"price"`
TimeInForce string `json:"tif"`
Text string `json:"text"`
// Optional Parameters
ClosePosition bool `json:"close,omitempty"`
ReduceOnly bool `json:"reduce_only,omitempty"`
AutoSize string `json:"auto_size,omitempty"`
Settle string `json:"-"`
Contract currency.Pair `json:"contract"`
Size float64 `json:"size"`
Iceberg int64 `json:"iceberg"`
Price string `json:"price"` // NOTE: Market orders require string "0"
TimeInForce string `json:"tif"`
Text string `json:"text"`
ClosePosition bool `json:"close,omitempty"`
ReduceOnly bool `json:"reduce_only,omitempty"`
AutoSize string `json:"auto_size,omitempty"`
Settle string `json:"-"` // Used in URL.
}
// Order represents future order response

View File

@@ -34,6 +34,11 @@ import (
"github.com/thrasher-corp/gocryptotrader/types"
)
// unfundedFuturesAccount defines an error string when an account associated
// with a settlement currency has not been funded. Use specific pairs to avoid
// this error.
const unfundedFuturesAccount = `please transfer funds first to create futures account`
// GetDefaultConfig returns a default exchange config
func (g *Gateio) GetDefaultConfig(ctx context.Context) (*config.Exchange, error) {
g.SetDefaults()
@@ -780,7 +785,7 @@ func (g *Gateio) UpdateAccountInfo(ctx context.Context, a asset.Item) (account.H
Currencies: currencies,
})
case asset.Futures, asset.DeliveryFutures:
currencies := make([]account.Balance, 3)
currencies := make([]account.Balance, 0, 3)
settles := []currency.Code{currency.BTC, currency.USDT, currency.USD}
for x := range settles {
var balance *FuturesAccount
@@ -793,14 +798,20 @@ func (g *Gateio) UpdateAccountInfo(ctx context.Context, a asset.Item) (account.H
balance, err = g.GetDeliveryFuturesAccounts(ctx, settles[x].String())
}
if err != nil {
if strings.Contains(err.Error(), unfundedFuturesAccount) {
if g.Verbose {
log.Warnf(log.ExchangeSys, "%s %v for settlement: %v", g.Name, err, settles[x])
}
continue
}
return info, err
}
currencies[x] = account.Balance{
currencies = append(currencies, account.Balance{
Currency: currency.NewCode(balance.Currency),
Total: balance.Total.Float64(),
Hold: balance.Total.Float64() - balance.Available.Float64(),
Free: balance.Available.Float64(),
}
})
}
info.Accounts = append(info.Accounts, account.SubAccount{
AssetType: a,
@@ -999,15 +1010,7 @@ func (g *Gateio) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Submi
if err != nil {
return nil, err
}
var orderTypeFormat string
switch {
case s.Side.IsLong():
orderTypeFormat = order.Buy.Lower()
case s.Side.IsShort():
orderTypeFormat = order.Sell.Lower()
default:
return nil, errInvalidOrderSide
}
s.Pair, err = g.FormatExchangeCurrency(s.Pair, s.AssetType)
if err != nil {
return nil, err
@@ -1018,8 +1021,16 @@ func (g *Gateio) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Submi
if s.Type != order.Limit {
return nil, errOnlyLimitOrderType
}
switch {
case s.Side.IsLong():
s.Side = order.Buy
case s.Side.IsShort():
s.Side = order.Sell
default:
return nil, errInvalidOrderSide
}
sOrder, err := g.PlaceSpotOrder(ctx, &CreateOrderRequestData{
Side: orderTypeFormat,
Side: s.Side.Lower(),
Type: s.Type.Lower(),
Account: g.assetTypeToString(s.AssetType),
Amount: types.Number(s.Amount),
@@ -1053,24 +1064,32 @@ func (g *Gateio) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Submi
response.LastUpdated = sOrder.UpdateTimeMs.Time()
return response, nil
case asset.Futures:
// TODO: See https://www.gate.io/docs/developers/apiv4/en/#create-a-futures-order
// * iceberg orders
// * auto_size (close_long, close_short)
// * stp_act (self trade prevention)
settle, err := g.getSettlementFromCurrency(s.Pair, true)
if err != nil {
return nil, err
}
if orderTypeFormat == "bid" && s.Price < 0 {
s.Price = -s.Price
} else if orderTypeFormat == "ask" && s.Price > 0 {
s.Price = -s.Price
var amountWithDirection float64
amountWithDirection, err = getFutureOrderSize(s)
if err != nil {
return nil, err
}
var timeInForce string
timeInForce, err = getTimeInForce(s)
if err != nil {
return nil, err
}
fOrder, err := g.PlaceFuturesOrder(ctx, &OrderCreateParams{
Contract: s.Pair,
Size: s.Amount,
Price: types.Number(s.Price),
Size: amountWithDirection,
Price: strconv.FormatFloat(s.Price, 'f', -1, 64), // Cannot be an empty string, requires "0" for market orders.
Settle: settle,
ReduceOnly: s.ReduceOnly,
TimeInForce: "gtc",
Text: s.ClientOrderID,
})
TimeInForce: timeInForce,
Text: s.ClientOrderID})
if err != nil {
return nil, err
}
@@ -1078,34 +1097,44 @@ func (g *Gateio) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Submi
if err != nil {
return nil, err
}
status, err := order.StringToOrderStatus(fOrder.Status)
if err != nil {
return nil, err
var status = order.Open
if fOrder.Status != "open" {
status, err = order.StringToOrderStatus(fOrder.FinishAs)
if err != nil {
return nil, err
}
}
response.Status = status
response.Pair = s.Pair
response.Date = fOrder.CreateTime.Time()
response.ClientOrderID = fOrder.Text
response.ClientOrderID = getClientOrderIDFromText(fOrder.Text)
response.ReduceOnly = fOrder.IsReduceOnly
response.Amount = fOrder.RemainingAmount
response.Amount = math.Abs(fOrder.Size)
response.Price = fOrder.OrderPrice.Float64()
response.AverageExecutedPrice = fOrder.FillPrice.Float64()
return response, nil
case asset.DeliveryFutures:
settle, err := g.getSettlementFromCurrency(s.Pair, false)
if err != nil {
return nil, err
}
if orderTypeFormat == "bid" && s.Price < 0 {
s.Price = -s.Price
} else if orderTypeFormat == "ask" && s.Price > 0 {
s.Price = -s.Price
var amountWithDirection float64
amountWithDirection, err = getFutureOrderSize(s)
if err != nil {
return nil, err
}
var timeInForce string
timeInForce, err = getTimeInForce(s)
if err != nil {
return nil, err
}
newOrder, err := g.PlaceDeliveryOrder(ctx, &OrderCreateParams{
Contract: s.Pair,
Size: s.Amount,
Price: types.Number(s.Price),
Size: amountWithDirection,
Price: strconv.FormatFloat(s.Price, 'f', -1, 64), // Cannot be an empty string, requires "0" for market orders.
Settle: settle,
ReduceOnly: s.ReduceOnly,
TimeInForce: "gtc",
TimeInForce: timeInForce,
Text: s.ClientOrderID,
})
if err != nil {
@@ -1115,16 +1144,20 @@ func (g *Gateio) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Submi
if err != nil {
return nil, err
}
status, err := order.StringToOrderStatus(newOrder.Status)
if err != nil {
return nil, err
var status = order.Open
if newOrder.Status != "open" {
status, err = order.StringToOrderStatus(newOrder.FinishAs)
if err != nil {
return nil, err
}
}
response.Status = status
response.Pair = s.Pair
response.Date = newOrder.CreateTime.Time()
response.ClientOrderID = newOrder.Text
response.Amount = newOrder.Size
response.ClientOrderID = getClientOrderIDFromText(newOrder.Text)
response.Amount = math.Abs(newOrder.Size)
response.Price = newOrder.OrderPrice.Float64()
response.AverageExecutedPrice = newOrder.FillPrice.Float64()
return response, nil
case asset.Options:
optionOrder, err := g.PlaceOptionOrder(ctx, &OptionOrderParam{
@@ -1415,11 +1448,7 @@ func (g *Gateio) GetOrderInfo(ctx context.Context, orderID string, pair currency
}, nil
case asset.Futures, asset.DeliveryFutures:
var settle string
if a == asset.Futures {
settle, err = g.getSettlementFromCurrency(pair, true)
} else {
settle, err = g.getSettlementFromCurrency(pair, false)
}
settle, err = g.getSettlementFromCurrency(pair, a == asset.Futures)
if err != nil {
return nil, err
}
@@ -1433,25 +1462,38 @@ func (g *Gateio) GetOrderInfo(ctx context.Context, orderID string, pair currency
if err != nil {
return nil, err
}
orderStatus, err := order.StringToOrderStatus(fOrder.Status)
if err != nil {
return nil, err
orderStatus := order.Open
if fOrder.Status != "open" {
orderStatus, err = order.StringToOrderStatus(fOrder.FinishAs)
if err != nil {
return nil, err
}
}
pair, err = currency.NewPairFromString(fOrder.Contract)
if err != nil {
return nil, err
}
side, amount, remaining := getSideAndAmountFromSize(fOrder.Size, fOrder.RemainingAmount)
ordertype, postonly := getTypeFromTimeInForce(fOrder.TimeInForce)
return &order.Detail{
Amount: fOrder.Size,
ExecutedAmount: fOrder.Size - fOrder.RemainingAmount,
Exchange: g.Name,
OrderID: orderID,
Status: orderStatus,
Price: fOrder.OrderPrice.Float64(),
Date: fOrder.CreateTime.Time(),
LastUpdated: fOrder.FinishTime.Time(),
Pair: pair,
AssetType: a,
Amount: amount,
ExecutedAmount: amount - remaining,
RemainingAmount: remaining,
Exchange: g.Name,
OrderID: orderID,
ClientOrderID: getClientOrderIDFromText(fOrder.Text),
Status: orderStatus,
Price: fOrder.OrderPrice.Float64(),
AverageExecutedPrice: fOrder.FillPrice.Float64(),
Date: fOrder.CreateTime.Time(),
LastUpdated: fOrder.FinishTime.Time(),
Pair: pair,
AssetType: a,
Type: ordertype,
PostOnly: postonly,
Side: side,
}, nil
case asset.Options:
optionOrder, err := g.GetSingleOptionOrder(ctx, orderID)
@@ -1619,50 +1661,68 @@ func (g *Gateio) GetActiveOrders(ctx context.Context, req *order.MultiOrderReque
}
}
case asset.Futures, asset.DeliveryFutures:
settlements := map[string]bool{}
if len(req.Pairs) == 0 {
return nil, currency.ErrCurrencyPairsEmpty
settlements["btc"] = true
settlements["usdt"] = true
settlements["usd"] = true
} else {
for x := range req.Pairs {
var s string
s, err = g.getSettlementFromCurrency(req.Pairs[x], req.AssetType == asset.Futures)
if err != nil {
return nil, err
}
settlements[s] = true
}
}
for z := range req.Pairs {
var settle string
if req.AssetType == asset.Futures {
settle, err = g.getSettlementFromCurrency(req.Pairs[z], true)
} else {
settle, err = g.getSettlementFromCurrency(req.Pairs[z], false)
}
if err != nil {
return nil, err
}
for settlement := range settlements {
var futuresOrders []Order
if req.AssetType == asset.Futures {
futuresOrders, err = g.GetFuturesOrders(ctx, req.Pairs[z], "open", "", settle, 0, 0, 0)
futuresOrders, err = g.GetFuturesOrders(ctx, currency.EMPTYPAIR, "open", "", settlement, 0, 0, 0)
} else {
futuresOrders, err = g.GetDeliveryOrders(ctx, req.Pairs[z], "open", settle, "", 0, 0, 0)
futuresOrders, err = g.GetDeliveryOrders(ctx, currency.EMPTYPAIR, "open", settlement, "", 0, 0, 0)
}
if err != nil {
if strings.Contains(err.Error(), unfundedFuturesAccount) {
log.Warnf(log.ExchangeSys, "%s %v", g.Name, err)
continue
}
return nil, err
}
for x := range futuresOrders {
if futuresOrders[x].Status != "open" {
var pair currency.Pair
pair, err = currency.NewPairFromString(futuresOrders[x].Contract)
if err != nil {
return nil, err
}
if futuresOrders[x].Status != "open" || (len(req.Pairs) > 0 && !req.Pairs.Contains(pair, true)) {
continue
}
var status order.Status
status, err = order.StringToOrderStatus(futuresOrders[x].Status)
if err != nil {
log.Errorf(log.ExchangeSys, "%s %v", g.Name, err)
}
side, amount, remaining := getSideAndAmountFromSize(futuresOrders[x].Size, futuresOrders[x].RemainingAmount)
orders = append(orders, order.Detail{
Status: status,
Amount: futuresOrders[x].Size,
Pair: req.Pairs[x],
OrderID: strconv.FormatInt(futuresOrders[x].ID, 10),
Price: futuresOrders[x].OrderPrice.Float64(),
ExecutedAmount: futuresOrders[x].Size - futuresOrders[x].RemainingAmount,
RemainingAmount: futuresOrders[x].RemainingAmount,
LastUpdated: futuresOrders[x].FinishTime.Time(),
Date: futuresOrders[x].CreateTime.Time(),
ClientOrderID: futuresOrders[x].Text,
Exchange: g.Name,
AssetType: req.AssetType,
Status: order.Open,
Amount: amount,
ContractAmount: amount,
Pair: pair,
OrderID: strconv.FormatInt(futuresOrders[x].ID, 10),
ClientOrderID: getClientOrderIDFromText(futuresOrders[x].Text),
Price: futuresOrders[x].OrderPrice.Float64(),
ExecutedAmount: amount - remaining,
RemainingAmount: remaining,
LastUpdated: futuresOrders[x].FinishTime.Time(),
Date: futuresOrders[x].CreateTime.Time(),
Exchange: g.Name,
AssetType: req.AssetType,
Side: side,
Type: order.Limit,
SettlementCurrency: currency.NewCode(settlement),
ReduceOnly: futuresOrders[x].IsReduceOnly,
PostOnly: futuresOrders[x].TimeInForce == "poc",
AverageExecutedPrice: futuresOrders[x].FillPrice.Float64(),
})
}
}
@@ -2452,3 +2512,66 @@ func (g *Gateio) GetOpenInterest(ctx context.Context, k ...key.PairAsset) ([]fut
}
return resp, nil
}
// getClientOrderIDFromText returns the client order ID from the text response
func getClientOrderIDFromText(text string) string {
if strings.HasPrefix(text, "t-") {
return text
}
return ""
}
// getTypeFromTimeInForce returns the order type and if the order is post only
func getTypeFromTimeInForce(tif string) (orderType order.Type, postOnly bool) {
switch tif {
case "ioc":
return order.Market, false
case "fok":
return order.Market, false
case "poc":
return order.Limit, true
default:
return order.Limit, false
}
}
// getSideAndAmountFromSize returns the order side, amount and remaining amounts
func getSideAndAmountFromSize(size, left float64) (side order.Side, amount, remaining float64) {
if size < 0 {
return order.Short, math.Abs(size), math.Abs(left)
}
return order.Long, size, left
}
// getFutureOrderSize sets the amount to a negative value if shorting.
func getFutureOrderSize(s *order.Submit) (float64, error) {
switch {
case s.Side.IsLong():
return s.Amount, nil
case s.Side.IsShort():
return -s.Amount, nil
default:
return 0, errInvalidOrderSide
}
}
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 = "ioc" // market taker only
}
if s.PostOnly {
if s.Type != order.Limit {
return "", fmt.Errorf("%w not for %v", errPostOnlyOrderTypeUnsupported, s.Type)
}
timeInForce = "poc" // limit order maker only
}
if s.FillOrKill {
timeInForce = "fok" // market order entire fill or kill
}
return timeInForce, nil
}