mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-06-08 07:26:48 +00:00
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:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user