From 6ccb0e0c2ff03af0a41db1c94406d2c633ef6dff Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Wed, 6 Mar 2024 13:06:13 +1100 Subject: [PATCH] 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 * Update exchanges/kucoin/kucoin_wrapper.go Co-authored-by: Scott * Update exchanges/gateio/gateio.go Co-authored-by: Scott * Update exchanges/gateio/gateio_wrapper.go Co-authored-by: Scott * Update exchanges/gateio/gateio_wrapper.go Co-authored-by: Scott * 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 * Update exchanges/gateio/gateio_test.go Co-authored-by: Adrian Gallagher * Update exchanges/gateio/gateio_test.go Co-authored-by: Adrian Gallagher --------- Co-authored-by: Ryan O'Hara-Reid Co-authored-by: Scott Co-authored-by: Adrian Gallagher --- .../exchange_wrapper_standards_test.go | 6 + currency/pairs.go | 34 ++ currency/pairs_test.go | 82 +++++ exchanges/gateio/gateio.go | 94 ++---- exchanges/gateio/gateio_test.go | 86 ++++- exchanges/gateio/gateio_types.go | 38 ++- exchanges/gateio/gateio_wrapper.go | 299 ++++++++++++------ exchanges/kucoin/kucoin.go | 10 +- exchanges/kucoin/kucoin_test.go | 7 +- exchanges/kucoin/kucoin_wrapper.go | 123 ++++--- exchanges/order/order_types.go | 24 +- 11 files changed, 558 insertions(+), 245 deletions(-) diff --git a/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go b/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go index 13bc451d..23717b2b 100644 --- a/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go +++ b/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go @@ -244,6 +244,11 @@ func CallExchangeMethod(t *testing.T, methodToCall reflect.Value, methodValues [ if isUnacceptableError(t, err) != nil { literalInputs := make([]interface{}, len(methodValues)) for j := range methodValues { + if methodValues[j].Kind() == reflect.Ptr { + // dereference pointers just to add a bit more clarity + literalInputs[j] = methodValues[j].Elem().Interface() + continue + } literalInputs[j] = methodValues[j].Interface() } t.Errorf("%v Func '%v' Error: '%v'. Inputs: %v.", exch.GetName(), methodName, err, literalInputs) @@ -448,6 +453,7 @@ func generateMethodArg(ctx context.Context, t *testing.T, argGenerator *MethodAr ClientID: "1337", ClientOrderID: "13371337", ImmediateOrCancel: true, + Leverage: 1, }) case argGenerator.MethodInputType.AssignableTo(orderModifyParam): input = reflect.ValueOf(&order.Modify{ diff --git a/currency/pairs.go b/currency/pairs.go index 887b46fc..a52fb29e 100644 --- a/currency/pairs.go +++ b/currency/pairs.go @@ -458,3 +458,37 @@ func (p Pairs) GetFormatting() (PairFormat, error) { } return pFmt, nil } + +// GetPairsByQuote returns all pairs that have a matching quote currency +func (p Pairs) GetPairsByQuote(quoteTerm Code) (Pairs, error) { + if len(p) == 0 { + return nil, ErrCurrencyPairsEmpty + } + if quoteTerm.IsEmpty() { + return nil, ErrCurrencyCodeEmpty + } + pairs := make(Pairs, 0, len(p)) + for i := range p { + if p[i].Quote.Equal(quoteTerm) { + pairs = append(pairs, p[i]) + } + } + return pairs, nil +} + +// GetPairsByBase returns all pairs that have a matching base currency +func (p Pairs) GetPairsByBase(baseTerm Code) (Pairs, error) { + if len(p) == 0 { + return nil, ErrCurrencyPairsEmpty + } + if baseTerm.IsEmpty() { + return nil, ErrCurrencyCodeEmpty + } + pairs := make(Pairs, 0, len(p)) + for i := range p { + if p[i].Base.Equal(baseTerm) { + pairs = append(pairs, p[i]) + } + } + return pairs, nil +} diff --git a/currency/pairs_test.go b/currency/pairs_test.go index 2b28b508..66b4e40b 100644 --- a/currency/pairs_test.go +++ b/currency/pairs_test.go @@ -816,3 +816,85 @@ func TestPairs_GetFormatting(t *testing.T) { t.Error(err) } } + +func TestGetPairsByQuote(t *testing.T) { + t.Parallel() + + var available Pairs + if _, err := available.GetPairsByQuote(EMPTYCODE); !errors.Is(err, ErrCurrencyPairsEmpty) { + t.Fatalf("received: '%v' but expected '%v'", err, ErrCurrencyPairsEmpty) + } + + available = Pairs{ + NewPair(BTC, USD), + NewPair(LTC, USD), + NewPair(USD, NZD), + NewPair(LTC, USDT), + NewPair(LTC, DAI), + NewPair(USDT, XRP), + NewPair(DAI, XRP), + } + + if _, err := available.GetPairsByQuote(EMPTYCODE); !errors.Is(err, ErrCurrencyCodeEmpty) { + t.Fatalf("received: '%v' but expected '%v'", err, ErrCurrencyCodeEmpty) + } + + got, err := available.GetPairsByQuote(USD) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected '%v'", err, nil) + } + + if len(got) != 2 { + t.Fatalf("received: '%v' but expected '%v'", len(got), 2) + } + + got, err = available.GetPairsByQuote(BTC) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected '%v'", err, nil) + } + + if len(got) != 0 { + t.Fatalf("received: '%v' but expected '%v'", len(got), 0) + } +} + +func TestGetPairsByBase(t *testing.T) { + t.Parallel() + + var available Pairs + if _, err := available.GetPairsByBase(EMPTYCODE); !errors.Is(err, ErrCurrencyPairsEmpty) { + t.Fatalf("received: '%v' but expected '%v'", err, ErrCurrencyPairsEmpty) + } + + available = Pairs{ + NewPair(BTC, USD), + NewPair(LTC, USD), + NewPair(USD, NZD), + NewPair(LTC, USDT), + NewPair(LTC, DAI), + NewPair(USDT, XRP), + NewPair(DAI, XRP), + } + + if _, err := available.GetPairsByBase(EMPTYCODE); !errors.Is(err, ErrCurrencyCodeEmpty) { + t.Fatalf("received: '%v' but expected '%v'", err, ErrCurrencyCodeEmpty) + } + + got, err := available.GetPairsByBase(USD) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected '%v'", err, nil) + } + + if len(got) != 1 { + t.Fatalf("received: '%v' but expected '%v'", len(got), 1) + } + + got, err = available.GetPairsByBase(LTC) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected '%v'", err, nil) + } + + if len(got) != 3 { + t.Fatalf("received: '%v' but expected '%v'", len(got), 3) + } +} diff --git a/exchanges/gateio/gateio.go b/exchanges/gateio/gateio.go index c60bf0e9..2b68b35c 100644 --- a/exchanges/gateio/gateio.go +++ b/exchanges/gateio/gateio.go @@ -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) diff --git a/exchanges/gateio/gateio_test.go b/exchanges/gateio/gateio_test.go index 0aa5076e..ce39ae91 100644 --- a/exchanges/gateio/gateio_test.go +++ b/exchanges/gateio/gateio_test.go @@ -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) +} diff --git a/exchanges/gateio/gateio_types.go b/exchanges/gateio/gateio_types.go index 28ed0998..9ccc1113 100644 --- a/exchanges/gateio/gateio_types.go +++ b/exchanges/gateio/gateio_types.go @@ -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 diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index 9dc825cc..1e67f41f 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -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 +} diff --git a/exchanges/kucoin/kucoin.go b/exchanges/kucoin/kucoin.go index 2c71d13c..5cb30594 100644 --- a/exchanges/kucoin/kucoin.go +++ b/exchanges/kucoin/kucoin.go @@ -1845,10 +1845,12 @@ func (ku *Kucoin) orderTypeToString(orderType order.Type) (string, error) { } func (ku *Kucoin) orderSideString(side order.Side) (string, error) { - switch side { - case order.Buy, order.Sell: - return side.Lower(), nil - case order.AnySide: + switch { + case side.IsLong(): + return order.Buy.Lower(), nil + case side.IsShort(): + return order.Sell.Lower(), nil + case side == order.AnySide: return "", nil default: return "", fmt.Errorf("%w, side:%s", order.ErrSideIsInvalid, side.String()) diff --git a/exchanges/kucoin/kucoin_test.go b/exchanges/kucoin/kucoin_test.go index 3fba95e8..1404a506 100644 --- a/exchanges/kucoin/kucoin_test.go +++ b/exchanges/kucoin/kucoin_test.go @@ -2209,13 +2209,8 @@ func TestSubmitOrder(t *testing.T) { ClientOrderID: "myOrder", AssetType: asset.Spot, } - _, err := ku.SubmitOrder(context.Background(), orderSubmission) - if !errors.Is(err, order.ErrSideIsInvalid) { - t.Errorf("expected %v, but found %v", asset.ErrNotSupported, err) - } - orderSubmission.Side = order.Buy orderSubmission.AssetType = asset.Options - _, err = ku.SubmitOrder(context.Background(), orderSubmission) + _, err := ku.SubmitOrder(context.Background(), orderSubmission) if !errors.Is(err, asset.ErrNotSupported) { t.Errorf("expected %v, but found %v", asset.ErrNotSupported, err) } diff --git a/exchanges/kucoin/kucoin_wrapper.go b/exchanges/kucoin/kucoin_wrapper.go index 432fa038..18d71434 100644 --- a/exchanges/kucoin/kucoin_wrapper.go +++ b/exchanges/kucoin/kucoin_wrapper.go @@ -473,27 +473,30 @@ func (ku *Kucoin) UpdateOrderbook(ctx context.Context, pair currency.Pair, asset // UpdateAccountInfo retrieves balances for all enabled currencies func (ku *Kucoin) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (account.Holdings, error) { - holding := account.Holdings{ - Exchange: ku.Name, - } + holding := account.Holdings{Exchange: ku.Name} err := ku.CurrencyPairs.IsAssetEnabled(assetType) if err != nil { return holding, fmt.Errorf("%w %v", asset.ErrNotSupported, assetType) } switch assetType { case asset.Futures: - accountH, err := ku.GetFuturesAccountOverview(ctx, "") - if err != nil { - return account.Holdings{}, err - } - holding.Accounts = append(holding.Accounts, account.SubAccount{ - AssetType: assetType, - Currencies: []account.Balance{{ + balances := make([]account.Balance, 2) + for i, settlement := range []string{"XBT", "USDT"} { + accountH, err := ku.GetFuturesAccountOverview(ctx, settlement) + if err != nil { + return account.Holdings{}, err + } + + balances[i] = account.Balance{ Currency: currency.NewCode(accountH.Currency), Total: accountH.AvailableBalance + accountH.FrozenFunds, Hold: accountH.FrozenFunds, Free: accountH.AvailableBalance, - }}, + } + } + holding.Accounts = append(holding.Accounts, account.SubAccount{ + AssetType: assetType, + Currencies: balances, }) case asset.Spot, asset.Margin: accountH, err := ku.GetAllAccounts(ctx, "", ku.accountTypeToString(assetType)) @@ -709,14 +712,18 @@ func (ku *Kucoin) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm } switch s.AssetType { case asset.Futures: - if s.Leverage == 0 { - s.Leverage = 1 - } o, err := ku.PostFuturesOrder(ctx, &FuturesOrderParam{ - ClientOrderID: s.ClientOrderID, Side: sideString, Symbol: s.Pair, - OrderType: s.Type.Lower(), Size: s.Amount, Price: s.Price, StopPrice: s.TriggerPrice, - Leverage: s.Leverage, VisibleSize: 0, ReduceOnly: s.ReduceOnly, - PostOnly: s.PostOnly, Hidden: s.Hidden}) + ClientOrderID: s.ClientOrderID, + Side: sideString, + Symbol: s.Pair, + OrderType: s.Type.Lower(), + Size: s.Amount, + Price: s.Price, + StopPrice: s.TriggerPrice, + Leverage: s.Leverage, + ReduceOnly: s.ReduceOnly, + PostOnly: s.PostOnly, + Hidden: s.Hidden}) if err != nil { return nil, err } @@ -907,6 +914,11 @@ func (ku *Kucoin) GetOrderInfo(ctx context.Context, orderID string, pair currenc if err != nil { return nil, err } + if side == order.Sell { + side = order.Short + } else if side == order.Buy { + side = order.Long + } if !pair.IsEmpty() && !nPair.Equal(pair) { return nil, fmt.Errorf("order with id %s and currency Pair %v does not exist", orderID, pair) } @@ -1048,8 +1060,8 @@ func (ku *Kucoin) GetActiveOrders(ctx context.Context, getOrdersRequest *order.M if err != nil { return nil, err } - pair := "" - orders := []order.Detail{} + var pair string + var orders []order.Detail switch getOrdersRequest.AssetType { case asset.Futures: if len(getOrdersRequest.Pairs) == 1 { @@ -1072,40 +1084,55 @@ func (ku *Kucoin) GetActiveOrders(ctx context.Context, getOrdersRequest *order.M continue } var dPair currency.Pair - var isEnabled bool - dPair, isEnabled, err = ku.MatchSymbolCheckEnabled(futuresOrders.Items[x].Symbol, getOrdersRequest.AssetType, true) + var enabled bool + dPair, enabled, err = ku.MatchSymbolCheckEnabled(futuresOrders.Items[x].Symbol, getOrdersRequest.AssetType, false) if err != nil { return nil, err } - if !isEnabled { + if !enabled { continue } - for i := range getOrdersRequest.Pairs { - if !getOrdersRequest.Pairs[i].Equal(dPair) { - continue - } - side, err := order.StringToOrderSide(futuresOrders.Items[x].Side) - if err != nil { - return nil, err - } - oType, err := order.StringToOrderType(futuresOrders.Items[x].OrderType) - if err != nil { - return nil, fmt.Errorf("asset type: %v order type: %v err: %w", getOrdersRequest.AssetType, getOrdersRequest.Type, err) - } - orders = append(orders, order.Detail{ - OrderID: futuresOrders.Items[x].ID, - Amount: futuresOrders.Items[x].Size, - RemainingAmount: futuresOrders.Items[x].Size - futuresOrders.Items[x].FilledSize, - ExecutedAmount: futuresOrders.Items[x].FilledSize, - Exchange: ku.Name, - Date: futuresOrders.Items[x].CreatedAt.Time(), - LastUpdated: futuresOrders.Items[x].UpdatedAt.Time(), - Price: futuresOrders.Items[x].Price, - Side: side, - Type: oType, - Pair: dPair, - }) + side, err := order.StringToOrderSide(futuresOrders.Items[x].Side) + if err != nil { + return nil, err } + if side == order.Sell { + side = order.Short + } else if side == order.Buy { + side = order.Long + } + oType, err := order.StringToOrderType(futuresOrders.Items[x].OrderType) + if err != nil { + return nil, fmt.Errorf("asset type: %v order type: %v err: %w", getOrdersRequest.AssetType, getOrdersRequest.Type, err) + } + + status, err := order.StringToOrderStatus(futuresOrders.Items[x].Status) + if err != nil { + return nil, err + } + + orders = append(orders, order.Detail{ + OrderID: futuresOrders.Items[x].ID, + ClientOrderID: futuresOrders.Items[x].ClientOid, + Amount: futuresOrders.Items[x].Size, + ContractAmount: futuresOrders.Items[x].Size, + RemainingAmount: futuresOrders.Items[x].Size - futuresOrders.Items[x].FilledSize, + ExecutedAmount: futuresOrders.Items[x].FilledSize, + Exchange: ku.Name, + Date: futuresOrders.Items[x].CreatedAt.Time(), + LastUpdated: futuresOrders.Items[x].UpdatedAt.Time(), + Price: futuresOrders.Items[x].Price, + Side: side, + Type: oType, + Pair: dPair, + PostOnly: futuresOrders.Items[x].PostOnly, + ReduceOnly: futuresOrders.Items[x].ReduceOnly, + Status: status, + SettlementCurrency: currency.NewCode(futuresOrders.Items[x].SettleCurrency), + Leverage: futuresOrders.Items[x].Leverage, + AssetType: getOrdersRequest.AssetType, + HiddenOrder: futuresOrders.Items[x].Hidden, + }) } case asset.Spot, asset.Margin: if len(getOrdersRequest.Pairs) == 1 { diff --git a/exchanges/order/order_types.go b/exchanges/order/order_types.go index a2e07161..9e47c2fa 100644 --- a/exchanges/order/order_types.go +++ b/exchanges/order/order_types.go @@ -101,17 +101,18 @@ type SubmitResponse struct { Pair currency.Pair AssetType asset.Item - ImmediateOrCancel bool - FillOrKill bool - PostOnly bool - ReduceOnly bool - Leverage float64 - Price float64 - Amount float64 - QuoteAmount float64 - TriggerPrice float64 - ClientID string - ClientOrderID string + ImmediateOrCancel bool + FillOrKill bool + PostOnly bool + ReduceOnly bool + Leverage float64 + Price float64 + AverageExecutedPrice float64 + Amount float64 + QuoteAmount float64 + TriggerPrice float64 + ClientID string + ClientOrderID string LastUpdated time.Time Date time.Time @@ -222,6 +223,7 @@ type Detail struct { Pair currency.Pair MarginType margin.Type Trades []TradeHistory + SettlementCurrency currency.Code } // Filter contains all properties an order can be filtered for