cassandre-tech / cassandre-trading-bot

Create your Java crypto trading bot in minutes. Our Spring boot starter takes care of exchange connections, accounts, orders, trades, and positions so you can focus on building your strategies.
https://trading-bot.cassandre.tech
GNU General Public License v3.0
583 stars 166 forks source link

Limit Order - Invalid Quantity #994

Closed ricardo-zenki closed 2 years ago

ricardo-zenki commented 2 years ago

Release number Present in 5.0.8 and 6.0.0

Describe the bug

I am getting an error when I place a Limit order:

2022-05-26 18:54:44,182 DEBUG [cassandre-flux-2] tech.cassandre.trading.bot.service.TradeServiceXChangeImplementation: Sending limit order: ASK - BTC/USDT - 0 org.knowm.xchange.exceptions.OrderNotValidException: Invalid quantity. (HTTP status code: 400) at org.knowm.xchange.binance.BinanceErrorAdapter.createOrderNotValidException(BinanceErrorAdapter.java:58) at org.knowm.xchange.binance.BinanceErrorAdapter.adapt(BinanceErrorAdapter.java:39) at org.knowm.xchange.binance.service.BinanceTradeService.placeOrder(BinanceTradeService.java:160) at org.knowm.xchange.binance.service.BinanceTradeService.placeLimitOrder(BinanceTradeService.java:114) at org.knowm.xchange.binance.service.BinanceTradeService$$FastClassBySpringCGLIB$$fd5f00ba.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:783) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698) at org.knowm.xchange.binance.service.BinanceTradeService$$EnhancerBySpringCGLIB$$a80e00b5.placeLimitOrder(<generated>) at tech.cassandre.trading.bot.service.TradeServiceXChangeImplementation.createLimitOrder(TradeServiceXChangeImplementation.java:209) at tech.cassandre.trading.bot.service.TradeServiceXChangeImplementation.createSellLimitOrder(TradeServiceXChangeImplementation.java:109) at tech.cassandre.trading.bot.service.TradeServiceXChangeImplementation$$FastClassBySpringCGLIB$$eb657f62.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:783) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698) at tech.cassandre.trading.bot.service.TradeServiceXChangeImplementation$$EnhancerBySpringCGLIB$$d131e5ad.createSellLimitOrder(<generated>) at tech.cassandre.trading.bot.strategy.internal.CassandreStrategy.createSellLimitOrder(CassandreStrategy.java:340)

I think the mapper is not placing the amount in the right field of xchange dto.

To Reproduce I am just calling createSellLimitOrder(ticker.getCurrencyPair(), amountToSell, limitPrice) in my custom simple strategy.

Expected behavior Place a limit order properly

Additional context This happened using Binance (production) and KuCoin (sandbox) exchanges in dry mode

straumat commented 2 years ago

@ricardo-zenki sorry for the delayed reply, i try to fix this tonight !

straumat commented 2 years ago

In your logs, I can see: Sending limit order: ASK - BTC/USDT - 0 and the error returned by the exchange is Invalid quantity.

This means that Cassandre sends a 0 quantity.

Thx. I think it comes from this method I use: amount.setScale(currencyPair.getBaseCurrencyPrecision(), FLOOR), It seems that the currencyBasePrecision is equals to 0 in your case and I need to find why.

ricardo-zenki commented 2 years ago

Hello, sure and thank you for your help =)

This is the code I am using for testing the limit order creation:

@Override
public final void onTickersUpdates(final Map<CurrencyPairDTO, TickerDTO> tickers) {
    // Here we will receive all tickers we required from the exchange.
    tickers.values().forEach(ticker -> {
        log.debug("Received information about a ticker : " + ticker);

        BigDecimal amountToSell = BigDecimal.valueOf(0.034d); //1000 BTC/USDC Aprox 
        BigDecimal minimalPriceToAsk = ticker.getHigh(); 
        BigDecimal limitPrice =  minimalPriceToAsk.max(ticker.getLast());

        log.debug("Creating a limit order of {} {} at {} price",amountToSell,ticker.getCurrencyPair(),limitPrice);
        OrderCreationResultDTO sellLimitOrder = createSellLimitOrder(ticker.getCurrencyPair(), amountToSell, limitPrice);

        if(sellLimitOrder.isSuccessful()) {
            log.debug("Order created! ID: {}", sellLimitOrder.getOrderId());
        } else {
            log.warn("Cannot create order! Error: {}", sellLimitOrder.getErrorMessage());
        }
     });
}

This is how I set the currency pair:

public Set<CurrencyPairDTO> getRequestedCurrencyPairs() {
    return Set.of(new CurrencyPairDTO(CurrencyDTO.BTC,CurrencyDTO.USDT));
}
ricardo-zenki commented 2 years ago

currencyPair.getBaseCurrencyPrecision(), FLOOR

Adjusting the precision didn't work either:

amountToSell.setScale(ticker.getCurrencyPair().getBaseCurrencyPrecision(), RoundingMode.FLOOR);
straumat commented 2 years ago

@ricardo-zenki I found the problem. Indeed, it comes from the mapper who is using the builder and not the constructor for the events method (onTickersUpdates()).

I will fix this but you can already make it work by declaring new CurrencyPairDTO(CurrencyDTO.BTC,CurrencyDTO.USDT) as a class variable and using this value instead of ticker.getCurrencyPair() when calling createSellLimitOrder.

ricardo-zenki commented 2 years ago

All right! Thank you!