mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +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:
@@ -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{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user