Kucoin: Update order execution limits (#2124)

* refactor(kucoin): enhance contract and symbol structures, update order execution limits tests

* fix(number): handle null input in UnmarshalJSON and update tests

* Update exchanges/kucoin/kucoin_futures_types.go

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

* Update exchanges/kucoin/kucoin_futures_types.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/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/kucoin/kucoin_types.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>

* glorious: destroyed this code across all implementations

* glorious: rename

* ai overlord: nit

* Update exchanges/kucoin/kucoin_futures_types.go

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

* thrasher: nits

---------

Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
Co-authored-by: Scott <gloriousCode@users.noreply.github.com>
Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>
This commit is contained in:
Ryan O'Hara-Reid
2025-12-17 12:42:34 +11:00
committed by GitHub
parent b1e4983f49
commit 7f412e2772
7 changed files with 206 additions and 204 deletions

View File

@@ -14,61 +14,86 @@ var validGranularity = []string{
// Contract store contract details
type Contract struct {
Symbol string `json:"symbol"`
RootSymbol string `json:"rootSymbol"`
ContractType string `json:"type"`
FirstOpenDate types.Time `json:"firstOpenDate"`
ExpireDate types.Time `json:"expireDate"`
SettleDate types.Time `json:"settleDate"`
BaseCurrency string `json:"baseCurrency"`
QuoteCurrency string `json:"quoteCurrency"`
SettleCurrency string `json:"settleCurrency"`
MaxOrderQty float64 `json:"maxOrderQty"`
MaxPrice float64 `json:"maxPrice"`
LotSize float64 `json:"lotSize"`
TickSize float64 `json:"tickSize"`
IndexPriceTickSize float64 `json:"indexPriceTickSize"`
Multiplier float64 `json:"multiplier"`
InitialMargin float64 `json:"initialMargin"`
MaintainMargin float64 `json:"maintainMargin"`
MaxRiskLimit float64 `json:"maxRiskLimit"`
MinRiskLimit float64 `json:"minRiskLimit"`
RiskStep float64 `json:"riskStep"`
MakerFeeRate float64 `json:"makerFeeRate"`
TakerFeeRate float64 `json:"takerFeeRate"`
TakerFixFee float64 `json:"takerFixFee"`
MakerFixFee float64 `json:"makerFixFee"`
SettlementFee float64 `json:"settlementFee"`
IsDeleverage bool `json:"isDeleverage"`
IsQuanto bool `json:"isQuanto"`
IsInverse bool `json:"isInverse"`
MarkMethod string `json:"markMethod"`
FairMethod string `json:"fairMethod"`
FundingBaseSymbol string `json:"fundingBaseSymbol"`
FundingQuoteSymbol string `json:"fundingQuoteSymbol"`
FundingRateSymbol string `json:"fundingRateSymbol"`
IndexSymbol string `json:"indexSymbol"`
SettlementSymbol string `json:"settlementSymbol"`
Status string `json:"status"`
FundingFeeRate float64 `json:"fundingFeeRate"`
PredictedFundingFeeRate float64 `json:"predictedFundingFeeRate"`
OpenInterest types.Number `json:"openInterest"`
TurnoverOf24h float64 `json:"turnoverOf24h"`
VolumeOf24h float64 `json:"volumeOf24h"`
MarkPrice float64 `json:"markPrice"`
IndexPrice float64 `json:"indexPrice"`
LastTradePrice float64 `json:"lastTradePrice"`
NextFundingRateTime int64 `json:"nextFundingRateTime"`
MaxLeverage float64 `json:"maxLeverage"`
SourceExchanges []string `json:"sourceExchanges"`
PremiumsSymbol1M string `json:"premiumsSymbol1M"`
PremiumsSymbol8H string `json:"premiumsSymbol8H"`
FundingBaseSymbol1M string `json:"fundingBaseSymbol1M"`
FundingQuoteSymbol1M string `json:"fundingQuoteSymbol1M"`
LowPrice float64 `json:"lowPrice"`
HighPrice float64 `json:"highPrice"`
PriceChgPct float64 `json:"priceChgPct"`
PriceChg float64 `json:"priceChg"`
Symbol string `json:"symbol"`
RootSymbol currency.Code `json:"rootSymbol"`
ContractType string `json:"type"`
FirstOpenDate types.Time `json:"firstOpenDate"`
ExpireDate types.Time `json:"expireDate"`
SettleDate types.Time `json:"settleDate"`
BaseCurrency currency.Code `json:"baseCurrency"`
QuoteCurrency currency.Code `json:"quoteCurrency"`
SettleCurrency currency.Code `json:"settleCurrency"`
MaxOrderQty float64 `json:"maxOrderQty"`
MarketMaxOrderQty float64 `json:"marketMaxOrderQty"`
MaxPrice float64 `json:"maxPrice"`
LotSize float64 `json:"lotSize"`
TickSize float64 `json:"tickSize"`
IndexPriceTickSize float64 `json:"indexPriceTickSize"`
Multiplier float64 `json:"multiplier"`
InitialMargin float64 `json:"initialMargin"`
MaintainMargin float64 `json:"maintainMargin"`
MaxRiskLimit float64 `json:"maxRiskLimit"`
MinRiskLimit float64 `json:"minRiskLimit"`
RiskStep float64 `json:"riskStep"`
MakerFeeRate float64 `json:"makerFeeRate"`
TakerFeeRate float64 `json:"takerFeeRate"`
TakerFixFee float64 `json:"takerFixFee"`
MakerFixFee float64 `json:"makerFixFee"`
SettlementFee float64 `json:"settlementFee"`
IsDeleverage bool `json:"isDeleverage"`
IsQuanto bool `json:"isQuanto"`
IsInverse bool `json:"isInverse"`
MarkMethod string `json:"markMethod"`
FairMethod string `json:"fairMethod"`
FundingBaseSymbol string `json:"fundingBaseSymbol"`
FundingQuoteSymbol string `json:"fundingQuoteSymbol"`
FundingRateSymbol string `json:"fundingRateSymbol"`
IndexSymbol string `json:"indexSymbol"`
SettlementSymbol string `json:"settlementSymbol"`
Status string `json:"status"`
FundingFeeRate float64 `json:"fundingFeeRate"`
PredictedFundingFeeRate float64 `json:"predictedFundingFeeRate"`
DailyInterestRate float64 `json:"dailyInterestRate"`
FundingRateGranularity int64 `json:"fundingRateGranularity"`
FundingRateCap float64 `json:"fundingRateCap"`
FundingRateFloor float64 `json:"fundingRateFloor"`
Period float64 `json:"period"`
EffectiveFundingRateCycleStartTime types.Time `json:"effectiveFundingRateCycleStartTime"`
CurrentFundingRateGranularity int64 `json:"currentFundingRateGranularity"`
OpenInterest types.Number `json:"openInterest"`
TurnoverOf24h float64 `json:"turnoverOf24h"`
VolumeOf24h float64 `json:"volumeOf24h"`
MarkPrice float64 `json:"markPrice"`
IndexPrice float64 `json:"indexPrice"`
LastTradePrice float64 `json:"lastTradePrice"`
NextFundingRateTime int64 `json:"nextFundingRateTime"` // Not a timestamp
NextFundingRateDateTime types.Time `json:"nextFundingRateDateTime"`
MaxLeverage float64 `json:"maxLeverage"`
SourceExchanges []string `json:"sourceExchanges"`
PremiumsSymbol1M string `json:"premiumsSymbol1M"`
PremiumsSymbol8H string `json:"premiumsSymbol8H"`
FundingBaseSymbol1M string `json:"fundingBaseSymbol1M"`
FundingQuoteSymbol1M string `json:"fundingQuoteSymbol1M"`
LowPrice float64 `json:"lowPrice"`
HighPrice float64 `json:"highPrice"`
PriceChangePercentage float64 `json:"priceChgPct"`
PriceChange float64 `json:"priceChg"`
K float64 `json:"k"` // Max open size amplification factor
M float64 `json:"m"` // Margin-curve slope/smoothing constant (affects MMR growth with size)
F float64 `json:"f"` // IMR to MMR safety multiplier
MMRLimit float64 `json:"mmrLimit"`
MMRLeverageConstant float64 `json:"mmrLevConstant"`
SupportCross bool `json:"supportCross"`
BuyLimit float64 `json:"buyLimit"`
SellLimit float64 `json:"sellLimit"`
AdjustK types.Number `json:"adjustK"`
AdjustM types.Number `json:"adjustM"`
AdjustMMRLeverageConstant types.Number `json:"adjustMmrLevConstant"`
AdjustActiveTime types.Time `json:"adjustActiveTime"`
CrossRiskLimit float64 `json:"crossRiskLimit"`
MarketStage string `json:"marketStage"`
PreMarketToPerpDate types.Time `json:"preMarketToPerpDate"`
OrderPriceRange float64 `json:"orderPriceRange"`
}
// FuturesTicker stores ticker data

View File

@@ -3289,22 +3289,6 @@ func TestGetFuturesPositionOrders(t *testing.T) {
assert.NotNil(t, result)
}
func TestUpdateOrderExecutionLimits(t *testing.T) {
t.Parallel()
testexch.UpdatePairsOnce(t, e)
for _, a := range e.GetAssetTypes(false) {
t.Run(a.String(), func(t *testing.T) {
t.Parallel()
require.NoError(t, e.UpdateOrderExecutionLimits(t.Context(), a), "UpdateOrderExecutionLimits must not error")
pairs, err := e.CurrencyPairs.GetPairs(a, true)
require.NoError(t, err, "GetPairs must not error")
l, err := e.GetOrderExecutionLimits(a, pairs[0])
require.NoError(t, err, "GetOrderExecutionLimits must not error")
assert.Positive(t, l.AmountStepIncrementSize, "AmountStepIncrementSize should not be zero")
})
}
}
func BenchmarkIntervalToString(b *testing.B) {
for b.Loop() {
result, err := IntervalToString(kline.OneWeek)

View File

@@ -89,23 +89,34 @@ func (e Error) GetError() error {
// SymbolInfo stores symbol information
type SymbolInfo struct {
Symbol string `json:"symbol"`
Name string `json:"name"`
BaseCurrency string `json:"baseCurrency"`
QuoteCurrency string `json:"quoteCurrency"`
FeeCurrency string `json:"feeCurrency"`
Market string `json:"market"`
BaseMinSize float64 `json:"baseMinSize,string"`
QuoteMinSize float64 `json:"quoteMinSize,string"`
BaseMaxSize float64 `json:"baseMaxSize,string"`
QuoteMaxSize float64 `json:"quoteMaxSize,string"`
BaseIncrement float64 `json:"baseIncrement,string"`
QuoteIncrement float64 `json:"quoteIncrement,string"`
PriceIncrement float64 `json:"priceIncrement,string"`
PriceLimitRate float64 `json:"priceLimitRate,string"`
MinFunds float64 `json:"minFunds,string"`
IsMarginEnabled bool `json:"isMarginEnabled"`
EnableTrading bool `json:"enableTrading"`
Symbol currency.Pair `json:"symbol"`
Name currency.Pair `json:"name"`
BaseCurrency currency.Code `json:"baseCurrency"`
QuoteCurrency currency.Code `json:"quoteCurrency"`
FeeCurrency currency.Code `json:"feeCurrency"`
Market string `json:"market"`
BaseMinSize types.Number `json:"baseMinSize"`
QuoteMinSize types.Number `json:"quoteMinSize"`
BaseMaxSize types.Number `json:"baseMaxSize"`
QuoteMaxSize types.Number `json:"quoteMaxSize"`
BaseIncrement types.Number `json:"baseIncrement"`
QuoteIncrement types.Number `json:"quoteIncrement"`
PriceIncrement types.Number `json:"priceIncrement"`
PriceLimitRate types.Number `json:"priceLimitRate"`
MinFunds types.Number `json:"minFunds"`
IsMarginEnabled bool `json:"isMarginEnabled"`
EnableTrading bool `json:"enableTrading"`
FeeCategory int64 `json:"feeCategory"`
MakerFeeCoefficient types.Number `json:"makerFeeCoefficient"`
TakerFeeCoefficient types.Number `json:"takerFeeCoefficient"`
SpecialTreatment bool `json:"st"`
CallAuctionIsEnabled bool `json:"callauctionIsEnabled"`
CallAuctionPriceFloor types.Number `json:"callauctionPriceFloor"`
CallAuctionPriceCeiling types.Number `json:"callauctionPriceCeiling"`
CallAuctionFirstStageStartTime types.Time `json:"callauctionFirstStageStartTime"`
CallAuctionSecondStageStartTime types.Time `json:"callauctionSecondStageStartTime"`
CallAuctionThirdStageStartTime types.Time `json:"callauctionThirdStageStartTime"`
TradingStartTime types.Time `json:"tradingStartTime"`
}
// Ticker stores ticker data

View File

@@ -220,7 +220,6 @@ func (e *Exchange) Setup(exch *config.Exchange) error {
// FetchTradablePairs returns a list of the exchanges tradable pairs
func (e *Exchange) FetchTradablePairs(ctx context.Context, assetType asset.Item) (currency.Pairs, error) {
var cp currency.Pair
switch assetType {
case asset.Futures:
myPairs, err := e.GetFuturesOpenContracts(ctx)
@@ -232,11 +231,8 @@ func (e *Exchange) FetchTradablePairs(ctx context.Context, assetType asset.Item)
if strings.ToLower(myPairs[x].Status) != "open" { //nolint:gocritic // strings.ToLower is faster
continue
}
cp, err = currency.NewPairFromStrings(myPairs[x].BaseCurrency, myPairs[x].Symbol[len(myPairs[x].BaseCurrency):])
if err != nil {
return nil, err
}
pairs = pairs.Add(cp)
quote := currency.NewCode(myPairs[x].Symbol[len(myPairs[x].BaseCurrency.String()):])
pairs = pairs.Add(currency.NewPair(myPairs[x].BaseCurrency, quote))
}
configFormat, err := e.GetPairFormat(asset.Futures, false)
if err != nil {
@@ -255,11 +251,7 @@ func (e *Exchange) FetchTradablePairs(ctx context.Context, assetType asset.Item)
}
// Symbol field must be used to generate pair as this is the symbol
// to fetch data from the API. e.g. BSV-USDT name is BCHSV-USDT as symbol.
cp, err = currency.NewPairFromString(strings.ToUpper(myPairs[x].Symbol))
if err != nil {
return nil, err
}
pairs = pairs.Add(cp)
pairs = pairs.Add(myPairs[x].Symbol)
}
return pairs, nil
default:
@@ -309,11 +301,8 @@ func (e *Exchange) UpdateTickers(ctx context.Context, assetType asset.Item) erro
return err
}
for x := range ticks {
var pair currency.Pair
pair, err = currency.NewPairFromStrings(ticks[x].BaseCurrency, ticks[x].Symbol[len(ticks[x].BaseCurrency):])
if err != nil {
return err
}
pair := currency.NewPair(ticks[x].BaseCurrency,
currency.NewCode(ticks[x].Symbol[len(ticks[x].BaseCurrency.String()):]))
if !pairs.Contains(pair, true) {
continue
}
@@ -1213,16 +1202,10 @@ func (e *Exchange) GetActiveOrders(ctx context.Context, getOrdersRequest *order.
if !futuresOrders.Items[x].IsActive {
continue
}
var dPair currency.Pair
var enabled bool
dPair, enabled, err = e.MatchSymbolCheckEnabled(futuresOrders.Items[x].Symbol, getOrdersRequest.AssetType, false)
pair, err := e.MatchSymbolWithAvailablePairs(futuresOrders.Items[x].Symbol, getOrdersRequest.AssetType, false)
if err != nil {
return nil, err
}
if !enabled {
continue
}
side, err := order.StringToOrderSide(futuresOrders.Items[x].Side)
if err != nil {
return nil, err
@@ -1257,7 +1240,7 @@ func (e *Exchange) GetActiveOrders(ctx context.Context, getOrdersRequest *order.
Price: futuresOrders.Items[x].Price,
Side: side,
Type: oType,
Pair: dPair,
Pair: pair,
TimeInForce: StringToTimeInForce(futuresOrders.Items[x].TimeInForce, futuresOrders.Items[x].PostOnly),
ReduceOnly: futuresOrders.Items[x].ReduceOnly,
Status: status,
@@ -1322,16 +1305,11 @@ func (e *Exchange) GetActiveOrders(ctx context.Context, getOrdersRequest *order.
if response.Items[a].Status != "New" {
continue
}
var dPair currency.Pair
var enabled bool
dPair, enabled, err = e.MatchSymbolCheckEnabled(response.Items[a].Symbol, getOrdersRequest.AssetType, false)
pair, err := e.MatchSymbolWithAvailablePairs(response.Items[a].Symbol, getOrdersRequest.AssetType, false)
if err != nil {
return nil, err
}
if !enabled {
continue
}
if len(getOrdersRequest.Pairs) > 1 && !getOrdersRequest.Pairs.Contains(dPair, true) {
if len(getOrdersRequest.Pairs) > 1 && !getOrdersRequest.Pairs.Contains(pair, true) {
continue
}
side, err := order.StringToOrderSide(response.Items[a].Side)
@@ -1353,7 +1331,7 @@ func (e *Exchange) GetActiveOrders(ctx context.Context, getOrdersRequest *order.
Price: response.Items[a].Price,
Side: side,
Type: order.Stop,
Pair: dPair,
Pair: pair,
TimeInForce: StringToTimeInForce(response.Items[a].TimeInForce, response.Items[a].PostOnly),
Status: status,
AssetType: getOrdersRequest.AssetType,
@@ -1380,16 +1358,11 @@ func (e *Exchange) GetActiveOrders(ctx context.Context, getOrdersRequest *order.
if !spotOrders.Items[x].IsActive {
continue
}
var dPair currency.Pair
var isEnabled bool
dPair, isEnabled, err = e.MatchSymbolCheckEnabled(spotOrders.Items[x].Symbol, getOrdersRequest.AssetType, true)
pair, err := e.MatchSymbolWithAvailablePairs(spotOrders.Items[x].Symbol, getOrdersRequest.AssetType, true)
if err != nil {
return nil, err
}
if !isEnabled {
continue
}
if len(getOrdersRequest.Pairs) > 0 && !getOrdersRequest.Pairs.Contains(dPair, true) {
if len(getOrdersRequest.Pairs) > 0 && !getOrdersRequest.Pairs.Contains(pair, true) {
continue
}
side, err := order.StringToOrderSide(spotOrders.Items[x].Side)
@@ -1410,7 +1383,7 @@ func (e *Exchange) GetActiveOrders(ctx context.Context, getOrdersRequest *order.
Price: spotOrders.Items[x].Price.Float64(),
Side: side,
Type: oType,
Pair: dPair,
Pair: pair,
})
}
}
@@ -1439,10 +1412,7 @@ func (e *Exchange) GetOrderHistory(ctx context.Context, getOrdersRequest *order.
}
var orders []order.Detail
var orderSide order.Side
var orderStatus order.Status
var oType order.Type
var pair currency.Pair
switch getOrdersRequest.AssetType {
case asset.Futures:
var futuresOrders *FutureOrdersResponse
@@ -1471,19 +1441,15 @@ func (e *Exchange) GetOrderHistory(ctx context.Context, getOrdersRequest *order.
}
orders = make(order.FilteredOrders, 0, len(futuresOrders.Items))
for i := range orders {
orderSide, err = order.StringToOrderSide(futuresOrders.Items[i].Side)
orderSide, err := order.StringToOrderSide(futuresOrders.Items[i].Side)
if err != nil {
return nil, err
}
var isEnabled bool
pair, isEnabled, err = e.MatchSymbolCheckEnabled(futuresOrders.Items[i].Symbol, getOrdersRequest.AssetType, true)
pair, err := e.MatchSymbolWithAvailablePairs(futuresOrders.Items[i].Symbol, getOrdersRequest.AssetType, true)
if err != nil {
return nil, err
}
if !isEnabled {
continue
}
oType, err = order.StringToOrderType(futuresOrders.Items[i].OrderType)
oType, err := order.StringToOrderType(futuresOrders.Items[i].OrderType)
if err != nil {
log.Errorf(log.ExchangeSys, "%s %v", e.Name, err)
}
@@ -1555,16 +1521,11 @@ func (e *Exchange) GetOrderHistory(ctx context.Context, getOrdersRequest *order.
return nil, err
}
for a := range response.Items {
var dPair currency.Pair
var enabled bool
dPair, enabled, err = e.MatchSymbolCheckEnabled(response.Items[a].Symbol, getOrdersRequest.AssetType, false)
pair, err := e.MatchSymbolWithAvailablePairs(response.Items[a].Symbol, getOrdersRequest.AssetType, false)
if err != nil {
return nil, err
}
if !enabled {
continue
}
if len(getOrdersRequest.Pairs) > 1 && !getOrdersRequest.Pairs.Contains(dPair, true) {
if len(getOrdersRequest.Pairs) > 1 && !getOrdersRequest.Pairs.Contains(pair, true) {
continue
}
var (
@@ -1591,7 +1552,7 @@ func (e *Exchange) GetOrderHistory(ctx context.Context, getOrdersRequest *order.
Price: response.Items[a].Price,
Side: side,
Type: order.Stop,
Pair: dPair,
Pair: pair,
TimeInForce: StringToTimeInForce(response.Items[a].TimeInForce, response.Items[a].PostOnly),
Status: status,
AssetType: getOrdersRequest.AssetType,
@@ -1621,17 +1582,15 @@ func (e *Exchange) GetOrderHistory(ctx context.Context, getOrdersRequest *order.
}
orders = make([]order.Detail, len(responseOrders.Items))
for i := range orders {
orderSide, err = order.StringToOrderSide(responseOrders.Items[i].Side)
orderSide, err := order.StringToOrderSide(responseOrders.Items[i].Side)
if err != nil {
return nil, err
}
var orderStatus order.Status
pair, err = currency.NewPairFromString(responseOrders.Items[i].Symbol)
pair, err := currency.NewPairFromString(responseOrders.Items[i].Symbol)
if err != nil {
return nil, err
}
var oType order.Type
oType, err = order.StringToOrderType(responseOrders.Items[i].Type)
oType, err := order.StringToOrderType(responseOrders.Items[i].Type)
if err != nil {
log.Errorf(log.ExchangeSys, "%s %v", e.Name, err)
}
@@ -1868,21 +1827,9 @@ func (e *Exchange) GetFuturesContractDetails(ctx context.Context, item asset.Ite
resp := make([]futures.Contract, len(contracts))
for i := range contracts {
var cp, underlying currency.Pair
underlying, err = currency.NewPairFromStrings(contracts[i].BaseCurrency, contracts[i].QuoteCurrency)
if err != nil {
return nil, err
}
cp, err = currency.NewPairFromStrings(contracts[i].BaseCurrency, contracts[i].Symbol[len(contracts[i].BaseCurrency):])
if err != nil {
return nil, err
}
settleCurr := currency.NewCode(contracts[i].SettleCurrency)
var ct futures.ContractType
ct := futures.Quarterly
if contracts[i].ContractType == "FFWCSX" {
ct = futures.Perpetual
} else {
ct = futures.Quarterly
}
contractSettlementType := futures.Linear
if contracts[i].IsInverse {
@@ -1897,11 +1844,12 @@ func (e *Exchange) GetFuturesContractDetails(ctx context.Context, item asset.Ite
}
timeOfCurrentFundingRate := time.Now().Add((time.Duration(contracts[i].NextFundingRateTime) * time.Millisecond) - fri).Truncate(time.Hour).UTC()
resp[i] = futures.Contract{
Exchange: e.Name,
Name: cp,
Underlying: underlying,
SettlementCurrency: settleCurr,
MarginCurrency: settleCurr,
Exchange: e.Name,
Name: currency.NewPair(contracts[i].BaseCurrency,
currency.NewCode(contracts[i].Symbol[len(contracts[i].BaseCurrency.String()):])),
Underlying: currency.NewPair(contracts[i].BaseCurrency, contracts[i].QuoteCurrency),
SettlementCurrency: contracts[i].SettleCurrency,
MarginCurrency: contracts[i].SettleCurrency,
Asset: item,
StartDate: contracts[i].FirstOpenDate.Time(),
EndDate: contracts[i].ExpireDate.Time(),
@@ -1944,11 +1892,8 @@ func (e *Exchange) GetLatestFundingRates(ctx context.Context, r *fundingrate.Lat
resp := make([]fundingrate.LatestRateResponse, 0, len(contracts))
for i := range contracts {
timeOfNextFundingRate := time.Now().Add(time.Duration(contracts[i].NextFundingRateTime) * time.Millisecond).Truncate(time.Hour).UTC()
var cp currency.Pair
cp, err = currency.NewPairFromStrings(contracts[i].BaseCurrency, contracts[i].Symbol[len(contracts[i].BaseCurrency):])
if err != nil {
return nil, err
}
cp := currency.NewPair(contracts[i].BaseCurrency,
currency.NewCode(contracts[i].Symbol[len(contracts[i].BaseCurrency.String()):]))
var isPerp bool
isPerp, err = e.IsPerpetualFutureCurrency(r.Asset, cp)
if err != nil {
@@ -2323,22 +2268,16 @@ func (e *Exchange) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item)
if a == asset.Margin && !symbols[x].IsMarginEnabled {
continue
}
pair, enabled, err := e.MatchSymbolCheckEnabled(symbols[x].Symbol, a, true)
if err != nil && !errors.Is(err, currency.ErrPairNotFound) {
return err
}
if !enabled {
continue
}
l = append(l, limits.MinMaxLevel{
Key: key.NewExchangeAssetPair(e.Name, a, pair),
AmountStepIncrementSize: symbols[x].BaseIncrement,
QuoteStepIncrementSize: symbols[x].QuoteIncrement,
PriceStepIncrementSize: symbols[x].PriceIncrement,
MinimumBaseAmount: symbols[x].BaseMinSize,
MaximumBaseAmount: symbols[x].BaseMaxSize,
MinimumQuoteAmount: symbols[x].QuoteMinSize,
MaximumQuoteAmount: symbols[x].QuoteMaxSize,
Key: key.NewExchangeAssetPair(e.Name, a, symbols[x].Symbol),
AmountStepIncrementSize: symbols[x].BaseIncrement.Float64(),
QuoteStepIncrementSize: symbols[x].QuoteIncrement.Float64(),
PriceStepIncrementSize: symbols[x].PriceIncrement.Float64(),
MinimumBaseAmount: symbols[x].BaseMinSize.Float64(),
MaximumBaseAmount: symbols[x].BaseMaxSize.Float64(),
MinimumQuoteAmount: symbols[x].QuoteMinSize.Float64(),
MaximumQuoteAmount: symbols[x].QuoteMaxSize.Float64(),
Listed: symbols[x].TradingStartTime.Time(),
})
}
case asset.Futures:
@@ -2346,21 +2285,36 @@ func (e *Exchange) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item)
if err != nil {
return err
}
l = make([]limits.MinMaxLevel, 0, len(contract))
for x := range contract {
pair, enabled, err := e.MatchSymbolCheckEnabled(contract[x].Symbol, a, false)
if err != nil && !errors.Is(err, currency.ErrPairNotFound) {
pair, err := e.MatchSymbolWithAvailablePairs(contract[x].Symbol, a, false)
if err != nil {
return err
}
if !enabled {
continue
priceDivisor := 1.0
if contract[x].Symbol[:2] == "10" { // handle 1000SHIBUSDT, 1000PEPEUSDT etc; exclude 1INCHUSDT
for _, r := range contract[x].Symbol[1:] {
if r != '0' {
break
}
priceDivisor *= 10
}
}
l = append(l, limits.MinMaxLevel{
Key: key.NewExchangeAssetPair(e.Name, a, pair),
AmountStepIncrementSize: contract[x].LotSize,
QuoteStepIncrementSize: contract[x].TickSize,
MinimumBaseAmount: contract[x].LotSize,
MaximumBaseAmount: contract[x].MaxOrderQty,
MaximumQuoteAmount: contract[x].MaxPrice,
MultiplierDecimal: contract[x].Multiplier,
Listed: contract[x].FirstOpenDate.Time(),
Delisted: contract[x].ExpireDate.Time(),
Expiry: contract[x].SettleDate.Time(),
PriceDivisor: priceDivisor,
})
}
}
@@ -2382,18 +2336,13 @@ func (e *Exchange) GetOpenInterest(ctx context.Context, k ...key.PairAsset) ([]f
}
resp := make([]futures.OpenInterest, 0, len(contracts))
for i := range contracts {
var symbol currency.Pair
var enabled bool
symbol, enabled, err = e.MatchSymbolCheckEnabled(contracts[i].Symbol, asset.Futures, true)
pair, err := e.MatchSymbolWithAvailablePairs(contracts[i].Symbol, asset.Futures, true)
if err != nil && !errors.Is(err, currency.ErrPairNotFound) {
return nil, err
}
if !enabled {
continue
}
var appendData bool
for j := range k {
if k[j].Pair().Equal(symbol) {
if k[j].Pair().Equal(pair) {
appendData = true
break
}
@@ -2402,7 +2351,7 @@ func (e *Exchange) GetOpenInterest(ctx context.Context, k ...key.PairAsset) ([]f
continue
}
resp = append(resp, futures.OpenInterest{
Key: key.NewExchangeAssetPair(e.Name, asset.Futures, symbol),
Key: key.NewExchangeAssetPair(e.Name, asset.Futures, pair),
OpenInterest: contracts[i].OpenInterest.Float64(),
})
}

View File

@@ -0,0 +1,27 @@
package kucoin
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
testexch "github.com/thrasher-corp/gocryptotrader/internal/testing/exchange"
)
func TestUpdateOrderExecutionLimits(t *testing.T) {
t.Parallel()
testexch.UpdatePairsOnce(t, e)
for _, a := range e.GetAssetTypes(false) {
t.Run(a.String(), func(t *testing.T) {
t.Parallel()
require.NoError(t, e.UpdateOrderExecutionLimits(t.Context(), a), "UpdateOrderExecutionLimits must not error")
pairs, err := e.GetAvailablePairs(a)
require.NoError(t, err, "GetPairs must not error")
for _, p := range pairs {
l, err := e.GetOrderExecutionLimits(a, p)
require.NoError(t, err, "GetOrderExecutionLimits must not error")
assert.Positive(t, l.AmountStepIncrementSize, "AmountStepIncrementSize should not be zero")
}
})
}
}