Skip to content

Commit

Permalink
Kucoin: Add order execution limits (thrasher-corp#1369)
Browse files Browse the repository at this point in the history
* cmd/exchange_template: Add wrapper function

* exchanges: force UpdateOrderExecutionLimits to wrappers as this is important for order deployment

* kucoin: Add spot order execution limits

* linter: fix

* glorious: nits

* kucoin: change from name to symbol for correct fee fetching and order deployment

* WHAAAAAAAT says Vitalik

* kucoin: Add futures limit support, fix insertion of non margin tradable pairs, update naming and add comments.

* kucoin: implement master branch changes

* Update exchanges/kucoin/kucoin_test.go

Co-authored-by: Scott <[email protected]>

* kucoin/test: update commentary

---------

Co-authored-by: Ryan O'Hara-Reid <[email protected]>
Co-authored-by: Scott <[email protected]>
  • Loading branch information
3 people authored Nov 14, 2023
1 parent 340558b commit a13a6c3
Show file tree
Hide file tree
Showing 24 changed files with 202 additions and 22 deletions.
4 changes: 4 additions & 0 deletions cmd/exchange_template/wrapper_file.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -553,5 +553,9 @@ func ({{.Variable}} *{{.CapitalName}}) GetLatestFundingRates(_ context.Context,
return nil, common.ErrNotYetImplemented
}

// UpdateOrderExecutionLimits updates order execution limits
func ({{.Variable}} *{{.CapitalName}}) UpdateOrderExecutionLimits(_ context.Context, _ asset.Item) error {
return common.ErrNotYetImplemented
}

{{end}}
1 change: 0 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -1817,7 +1817,6 @@ func (c *Config) LoadConfig(configPath string, dryrun bool) error {
if err != nil {
return fmt.Errorf(ErrFailureOpeningConfig, configPath, err)
}

return c.CheckConfig()
}

Expand Down
5 changes: 5 additions & 0 deletions exchanges/alphapoint/alphapoint_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -488,3 +488,8 @@ func (a *Alphapoint) GetFuturesContractDetails(context.Context, asset.Item) ([]f
func (a *Alphapoint) GetLatestFundingRates(context.Context, *fundingrate.LatestRateRequest) ([]fundingrate.LatestRateResponse, error) {
return nil, common.ErrFunctionNotSupported
}

// UpdateOrderExecutionLimits updates order execution limits
func (a *Alphapoint) UpdateOrderExecutionLimits(_ context.Context, _ asset.Item) error {
return common.ErrNotYetImplemented
}
5 changes: 5 additions & 0 deletions exchanges/binanceus/binanceus_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -998,3 +998,8 @@ func (bi *Binanceus) GetFuturesContractDetails(context.Context, asset.Item) ([]f
func (bi *Binanceus) GetLatestFundingRates(context.Context, *fundingrate.LatestRateRequest) ([]fundingrate.LatestRateResponse, error) {
return nil, common.ErrFunctionNotSupported
}

// UpdateOrderExecutionLimits updates order execution limits
func (bi *Binanceus) UpdateOrderExecutionLimits(_ context.Context, _ asset.Item) error {
return common.ErrNotYetImplemented
}
5 changes: 5 additions & 0 deletions exchanges/bitflyer/bitflyer_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -510,3 +510,8 @@ func (b *Bitflyer) GetFuturesContractDetails(context.Context, asset.Item) ([]fut
func (b *Bitflyer) GetLatestFundingRates(context.Context, *fundingrate.LatestRateRequest) ([]fundingrate.LatestRateResponse, error) {
return nil, common.ErrFunctionNotSupported
}

// UpdateOrderExecutionLimits updates order execution limits
func (b *Bitflyer) UpdateOrderExecutionLimits(_ context.Context, _ asset.Item) error {
return common.ErrNotYetImplemented
}
5 changes: 5 additions & 0 deletions exchanges/bitmex/bitmex_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -1332,3 +1332,8 @@ func (b *Bitmex) GetLatestFundingRates(ctx context.Context, r *fundingrate.Lates
func (b *Bitmex) IsPerpetualFutureCurrency(a asset.Item, _ currency.Pair) (bool, error) {
return a == asset.PerpetualContract, nil
}

// UpdateOrderExecutionLimits updates order execution limits
func (b *Bitmex) UpdateOrderExecutionLimits(_ context.Context, _ asset.Item) error {
return common.ErrNotYetImplemented
}
5 changes: 5 additions & 0 deletions exchanges/bittrex/bittrex_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -1101,3 +1101,8 @@ func (b *Bittrex) GetFuturesContractDetails(context.Context, asset.Item) ([]futu
func (b *Bittrex) GetLatestFundingRates(context.Context, *fundingrate.LatestRateRequest) ([]fundingrate.LatestRateResponse, error) {
return nil, common.ErrFunctionNotSupported
}

// UpdateOrderExecutionLimits updates order execution limits
func (b *Bittrex) UpdateOrderExecutionLimits(_ context.Context, _ asset.Item) error {
return common.ErrNotYetImplemented
}
5 changes: 5 additions & 0 deletions exchanges/btse/btse_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -1291,3 +1291,8 @@ func (b *BTSE) GetLatestFundingRates(ctx context.Context, r *fundingrate.LatestR
func (b *BTSE) IsPerpetualFutureCurrency(a asset.Item, p currency.Pair) (bool, error) {
return a == asset.Futures && p.Quote.Equal(currency.PFC), nil
}

// UpdateOrderExecutionLimits updates order execution limits
func (b *BTSE) UpdateOrderExecutionLimits(_ context.Context, _ asset.Item) error {
return common.ErrNotYetImplemented
}
5 changes: 5 additions & 0 deletions exchanges/coinbasepro/coinbasepro_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -991,3 +991,8 @@ func (c *CoinbasePro) GetLatestFundingRates(context.Context, *fundingrate.Latest
func (c *CoinbasePro) GetFuturesContractDetails(context.Context, asset.Item) ([]futures.Contract, error) {
return nil, common.ErrFunctionNotSupported
}

// UpdateOrderExecutionLimits updates order execution limits
func (c *CoinbasePro) UpdateOrderExecutionLimits(_ context.Context, _ asset.Item) error {
return common.ErrNotYetImplemented
}
5 changes: 5 additions & 0 deletions exchanges/coinut/coinut_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -1205,3 +1205,8 @@ func (c *COINUT) GetFuturesContractDetails(context.Context, asset.Item) ([]futur
func (c *COINUT) GetLatestFundingRates(context.Context, *fundingrate.LatestRateRequest) ([]fundingrate.LatestRateResponse, error) {
return nil, common.ErrFunctionNotSupported
}

// UpdateOrderExecutionLimits updates order execution limits
func (c *COINUT) UpdateOrderExecutionLimits(_ context.Context, _ asset.Item) error {
return common.ErrNotYetImplemented
}
5 changes: 0 additions & 5 deletions exchanges/exchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -1417,11 +1417,6 @@ func getURLTypeFromString(ep string) (URL, error) {
}
}

// UpdateOrderExecutionLimits updates order execution limits this is overridable
func (b *Base) UpdateOrderExecutionLimits(_ context.Context, _ asset.Item) error {
return common.ErrNotYetImplemented
}

// DisableAssetWebsocketSupport disables websocket functionality for the
// supplied asset item. In the case that websocket functionality has not yet
// been implemented for that specific asset type. This is a base method to
Expand Down
8 changes: 0 additions & 8 deletions exchanges/exchange_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2453,14 +2453,6 @@ func TestUpdateCurrencyStates(t *testing.T) {
}
}

func TestUpdateOrderExecutionLimits(t *testing.T) {
t.Parallel()
var b Base
if err := b.UpdateOrderExecutionLimits(context.Background(), asset.Spot); !errors.Is(err, common.ErrNotYetImplemented) {
t.Errorf("received: %v, expected: %v", err, common.ErrNotYetImplemented)
}
}

func TestSetTradeFeedStatus(t *testing.T) {
t.Parallel()
b := Base{
Expand Down
5 changes: 5 additions & 0 deletions exchanges/exmo/exmo_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -840,3 +840,8 @@ func (e *EXMO) GetFuturesContractDetails(context.Context, asset.Item) ([]futures
func (e *EXMO) GetLatestFundingRates(context.Context, *fundingrate.LatestRateRequest) ([]fundingrate.LatestRateResponse, error) {
return nil, common.ErrFunctionNotSupported
}

// UpdateOrderExecutionLimits updates order execution limits
func (e *EXMO) UpdateOrderExecutionLimits(_ context.Context, _ asset.Item) error {
return common.ErrNotYetImplemented
}
5 changes: 5 additions & 0 deletions exchanges/hitbtc/hitbtc_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -999,3 +999,8 @@ func (h *HitBTC) GetFuturesContractDetails(context.Context, asset.Item) ([]futur
func (h *HitBTC) GetLatestFundingRates(context.Context, *fundingrate.LatestRateRequest) ([]fundingrate.LatestRateResponse, error) {
return nil, common.ErrFunctionNotSupported
}

// UpdateOrderExecutionLimits updates order execution limits
func (h *HitBTC) UpdateOrderExecutionLimits(_ context.Context, _ asset.Item) error {
return common.ErrNotYetImplemented
}
5 changes: 5 additions & 0 deletions exchanges/huobi/huobi_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -2299,3 +2299,8 @@ func (h *HUOBI) GetLatestFundingRates(ctx context.Context, r *fundingrate.Latest
func (h *HUOBI) IsPerpetualFutureCurrency(a asset.Item, _ currency.Pair) (bool, error) {
return a == asset.CoinMarginedFutures, nil
}

// UpdateOrderExecutionLimits updates order execution limits
func (h *HUOBI) UpdateOrderExecutionLimits(_ context.Context, _ asset.Item) error {
return common.ErrNotYetImplemented
}
5 changes: 5 additions & 0 deletions exchanges/itbit/itbit_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -704,3 +704,8 @@ func (i *ItBit) GetFuturesContractDetails(context.Context, asset.Item) ([]future
func (i *ItBit) GetLatestFundingRates(context.Context, *fundingrate.LatestRateRequest) ([]fundingrate.LatestRateResponse, error) {
return nil, common.ErrFunctionNotSupported
}

// UpdateOrderExecutionLimits updates order execution limits
func (i *ItBit) UpdateOrderExecutionLimits(_ context.Context, _ asset.Item) error {
return common.ErrNotYetImplemented
}
7 changes: 4 additions & 3 deletions exchanges/kucoin/kucoin.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,11 @@ const (
)

// GetSymbols gets pairs details on the exchange
func (ku *Kucoin) GetSymbols(ctx context.Context, ccy string) ([]SymbolInfo, error) {
// For market details see endpoint: https://www.kucoin.com/docs/rest/spot-trading/market-data/get-market-list
func (ku *Kucoin) GetSymbols(ctx context.Context, market string) ([]SymbolInfo, error) {
params := url.Values{}
if ccy != "" {
params.Set("market", ccy)
if market != "" {
params.Set("market", market)
}
var resp []SymbolInfo
return resp, ku.SendHTTPRequest(ctx, exchange.RestSpot, defaultSpotEPL, common.EncodeURLValues(kucoinGetSymbols, params), &resp)
Expand Down
37 changes: 35 additions & 2 deletions exchanges/kucoin/kucoin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,17 @@ func TestMain(m *testing.M) {
// Spot asset test cases starts from here
func TestGetSymbols(t *testing.T) {
t.Parallel()
_, err := ku.GetSymbols(context.Background(), "")
symbols, err := ku.GetSymbols(context.Background(), "")
if err != nil {
t.Error("GetSymbols() error", err)
}
_, err = ku.GetSymbols(context.Background(), currency.BTC.String())
assert.NotEmpty(t, symbols, "should return all available spot/margin symbols")
// Using market string reduces the scope of what is returned.
symbols, err = ku.GetSymbols(context.Background(), "ETF")
if err != nil {
t.Error("GetSymbols() error", err)
}
assert.NotEmpty(t, symbols, "should return all available ETF symbols")
}

func TestGetTicker(t *testing.T) {
Expand Down Expand Up @@ -2558,3 +2561,33 @@ func TestGetFuturesPositionOrders(t *testing.T) {
_, err = ku.GetFuturesPositionOrders(context.Background(), req)
assert.ErrorIs(t, err, nil)
}

func TestUpdateOrderExecutionLimits(t *testing.T) {
t.Parallel()

err := ku.UpdateOrderExecutionLimits(context.Background(), asset.Binary)
if !errors.Is(err, asset.ErrNotSupported) {
t.Fatalf("Received %v, expected %v", err, asset.ErrNotSupported)
}

assets := []asset.Item{asset.Spot, asset.Futures, asset.Margin}
for x := range assets {
err = ku.UpdateOrderExecutionLimits(context.Background(), assets[x])
if !errors.Is(err, nil) {
t.Fatalf("received %v, expected %v", err, nil)
}

enabled, err := ku.GetEnabledPairs(assets[x])
if err != nil {
t.Fatal(err)
}

for y := range enabled {
lim, err := ku.GetOrderExecutionLimits(assets[x], enabled[y])
if err != nil {
t.Fatalf("%v %s %v", err, enabled[y], assets[x])
}
assert.NotEmpty(t, lim, "limit cannot be empty")
}
}
}
1 change: 0 additions & 1 deletion exchanges/kucoin/kucoin_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ var (
errSizeOrFundIsRequired = errors.New("at least one required among size and funds")
errInvalidLeverage = errors.New("invalid leverage value")
errInvalidClientOrderID = errors.New("invalid client order ID")
errCurrencyPairNotEnabled = errors.New("currency pair not enabled")

subAccountRegExp = regexp.MustCompile("^[a-zA-Z0-9]{7-32}$")
subAccountPassphraseRegExp = regexp.MustCompile("^[a-zA-Z0-9]{7-24}$")
Expand Down
81 changes: 79 additions & 2 deletions exchanges/kucoin/kucoin_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,16 @@ func (ku *Kucoin) Run(ctx context.Context) {
ku.PrintEnabledPairs()
}

assetTypes := ku.GetAssetTypes(false)
for i := range assetTypes {
if err := ku.UpdateOrderExecutionLimits(ctx, assetTypes[i]); err != nil && !errors.Is(err, common.ErrNotYetImplemented) {
log.Errorf(log.ExchangeSys,
"%s failed to set exchange order execution limits. Err: %v",
ku.Name,
err)
}
}

if !ku.GetEnabledFeatures().AutoPairUpdates {
return
}
Expand Down Expand Up @@ -294,10 +304,12 @@ func (ku *Kucoin) FetchTradablePairs(ctx context.Context, assetType asset.Item)
}
pairs := make(currency.Pairs, 0, len(myPairs))
for x := range myPairs {
if !myPairs[x].EnableTrading {
if !myPairs[x].EnableTrading || (assetType == asset.Margin && !myPairs[x].IsMarginEnabled) {
continue
}
cp, err = currency.NewPairFromString(strings.ToUpper(myPairs[x].Name))
// 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
}
Expand Down Expand Up @@ -1904,3 +1916,68 @@ func (ku *Kucoin) GetFuturesPositionOrders(ctx context.Context, r *futures.Posit
}
return resp, nil
}

// UpdateOrderExecutionLimits updates order execution limits
func (ku *Kucoin) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) error {
if !ku.SupportsAsset(a) {
return fmt.Errorf("%w %v", asset.ErrNotSupported, a)
}

var limits []order.MinMaxLevel
switch a {
case asset.Spot, asset.Margin:
symbols, err := ku.GetSymbols(ctx, "")
if err != nil {
return err
}
limits = make([]order.MinMaxLevel, 0, len(symbols))
for x := range symbols {
if a == asset.Margin && !symbols[x].IsMarginEnabled {
continue
}
pair, enabled, err := ku.MatchSymbolCheckEnabled(symbols[x].Symbol, a, true)
if err != nil {
return err
}
if !enabled {
continue
}
limits = append(limits, order.MinMaxLevel{
Pair: pair,
Asset: a,
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,
})
}
case asset.Futures:
contract, err := ku.GetFuturesOpenContracts(ctx)
if err != nil {
return err
}
limits = make([]order.MinMaxLevel, 0, len(contract))
for x := range contract {
pair, enabled, err := ku.MatchSymbolCheckEnabled(contract[x].Symbol, a, false)
if err != nil {
return err
}
if !enabled {
continue
}
limits = append(limits, order.MinMaxLevel{
Pair: pair,
Asset: a,
AmountStepIncrementSize: contract[x].LotSize,
QuoteStepIncrementSize: contract[x].TickSize,
MaximumBaseAmount: contract[x].MaxOrderQty,
MaximumQuoteAmount: contract[x].MaxPrice,
})
}
}

return ku.LoadLimits(limits)
}
5 changes: 5 additions & 0 deletions exchanges/lbank/lbank_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -1031,3 +1031,8 @@ func (l *Lbank) GetFuturesContractDetails(context.Context, asset.Item) ([]future
func (l *Lbank) GetLatestFundingRates(context.Context, *fundingrate.LatestRateRequest) ([]fundingrate.LatestRateResponse, error) {
return nil, common.ErrFunctionNotSupported
}

// UpdateOrderExecutionLimits updates order execution limits
func (l *Lbank) UpdateOrderExecutionLimits(_ context.Context, _ asset.Item) error {
return common.ErrNotYetImplemented
}
5 changes: 5 additions & 0 deletions exchanges/poloniex/poloniex_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -1143,3 +1143,8 @@ func (p *Poloniex) GetLatestFundingRates(context.Context, *fundingrate.LatestRat
// TODO: implement with API upgrade
return nil, common.ErrNotYetImplemented
}

// UpdateOrderExecutionLimits updates order execution limits
func (p *Poloniex) UpdateOrderExecutionLimits(_ context.Context, _ asset.Item) error {
return common.ErrNotYetImplemented
}
5 changes: 5 additions & 0 deletions exchanges/yobit/yobit_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -772,3 +772,8 @@ func (y *Yobit) GetFuturesContractDetails(context.Context, asset.Item) ([]future
func (y *Yobit) GetLatestFundingRates(context.Context, *fundingrate.LatestRateRequest) ([]fundingrate.LatestRateResponse, error) {
return nil, common.ErrFunctionNotSupported
}

// UpdateOrderExecutionLimits updates order execution limits
func (y *Yobit) UpdateOrderExecutionLimits(_ context.Context, _ asset.Item) error {
return common.ErrNotYetImplemented
}
5 changes: 5 additions & 0 deletions exchanges/zb/zb_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -1158,3 +1158,8 @@ func (z *ZB) GetFuturesContractDetails(context.Context, asset.Item) ([]futures.C
func (z *ZB) GetLatestFundingRates(context.Context, *fundingrate.LatestRateRequest) ([]fundingrate.LatestRateResponse, error) {
return nil, common.ErrFunctionNotSupported
}

// UpdateOrderExecutionLimits updates order execution limits
func (z *ZB) UpdateOrderExecutionLimits(_ context.Context, _ asset.Item) error {
return common.ErrNotYetImplemented
}

0 comments on commit a13a6c3

Please sign in to comment.