use of tech.cassandre.trading.bot.domain.Strategy in project cassandre-trading-bot by cassandre-tech.
the class MultipleStrategiesTest method checkMultipleStrategyBehavior.
@Test
@DisplayName("Check multiple strategies behavior with dry mode")
public void checkMultipleStrategyBehavior() {
// ==============================================================================================================
// Strategies tested.
// Strategy 1 - Requesting BTC/USDT.
// Strategy 2 - Requesting BTC/ETH.
// Strategy 3 - Requesting BTC/USDT & ETH/USDT.
// ==============================================================================================================
// Checking the MarketService have all the currency pairs.
final Set<CurrencyPairDTO> availableCurrencyPairs = exchangeService.getAvailableCurrencyPairs();
assertEquals(3, availableCurrencyPairs.size());
assertTrue(availableCurrencyPairs.contains(BTC_USDT));
assertTrue(availableCurrencyPairs.contains(BTC_ETH));
assertTrue(availableCurrencyPairs.contains(ETH_USDT));
// ==============================================================================================================
// Checking the three strategies is stored in the database.
assertEquals(3, strategyRepository.count());
final Optional<Strategy> s1 = strategyRepository.findByStrategyId("01");
assertTrue(s1.isPresent());
assertEquals(1, s1.get().getUid());
assertEquals("01", s1.get().getStrategyId());
assertEquals("Strategy 1", s1.get().getName());
final Optional<Strategy> s2 = strategyRepository.findByStrategyId("02");
assertTrue(s2.isPresent());
assertEquals(2, s2.get().getUid());
assertEquals("02", s2.get().getStrategyId());
assertEquals("Strategy 2", s2.get().getName());
final Optional<Strategy> s3 = strategyRepository.findByStrategyId("03");
assertTrue(s3.isPresent());
assertEquals(3, s3.get().getUid());
assertEquals("03", s3.get().getStrategyId());
assertEquals("Strategy 3", s3.get().getName());
// ==============================================================================================================
// Check balances on each strategy & onAccountUpdate(), we should have the same values in all strategies.
// As we are in dry mode, balances are loaded from:
// spring-boot-starter/autoconfigure/src/test/resources/user-main.csv
// spring-boot-starter/autoconfigure/src/test/resources/user-trade.csv
// spring-boot-starter/autoconfigure/src/test/resources/user-savings.csv
accountFlux.update();
await().untilAsserted(() -> assertEquals(3, strategy3.getAccountsUpdatesReceived().size()));
// Strategy 1 accounts data test.
Map<String, AccountDTO> strategy1Accounts = strategy1.getAccounts();
Optional<AccountDTO> strategy1TradeAccount = strategy1.getTradeAccount();
assertEquals(3, strategy1Accounts.size());
assertTrue(strategy1Accounts.containsKey("main"));
assertTrue(strategy1Accounts.containsKey("trade"));
assertTrue(strategy1Accounts.containsKey("savings"));
assertTrue(strategy1TradeAccount.isPresent());
assertEquals("trade", strategy1TradeAccount.get().getName());
assertEquals(3, strategy1TradeAccount.get().getBalances().size());
assertEquals(3, strategy1TradeAccount.get().getBalances().size());
assertTrue(strategy1TradeAccount.get().getBalance(BTC).isPresent());
assertEquals(0, new BigDecimal("0.99962937").compareTo(strategy1TradeAccount.get().getBalance(BTC).get().getAvailable()));
assertTrue(strategy1TradeAccount.get().getBalance(USDT).isPresent());
assertEquals(0, new BigDecimal("1000").compareTo(strategy1TradeAccount.get().getBalance(USDT).get().getAvailable()));
assertTrue(strategy1TradeAccount.get().getBalance(ETH).isPresent());
assertEquals(0, new BigDecimal("10").compareTo(strategy1TradeAccount.get().getBalance(ETH).get().getAvailable()));
// Strategy 2 accounts data test.
Map<String, AccountDTO> strategy2Accounts = strategy2.getAccounts();
Optional<AccountDTO> strategy2TradeAccount = strategy2.getTradeAccount();
assertEquals(3, strategy2Accounts.size());
assertTrue(strategy2Accounts.containsKey("main"));
assertTrue(strategy2Accounts.containsKey("trade"));
assertTrue(strategy2Accounts.containsKey("savings"));
assertTrue(strategy2TradeAccount.isPresent());
assertEquals("trade", strategy2TradeAccount.get().getName());
assertEquals(3, strategy2TradeAccount.get().getBalances().size());
assertTrue(strategy2TradeAccount.get().getBalance(BTC).isPresent());
assertEquals(0, new BigDecimal("0.99962937").compareTo(strategy2TradeAccount.get().getBalance(BTC).get().getAvailable()));
assertTrue(strategy2TradeAccount.get().getBalance(USDT).isPresent());
assertEquals(0, new BigDecimal("1000").compareTo(strategy2TradeAccount.get().getBalance(USDT).get().getAvailable()));
assertTrue(strategy2TradeAccount.get().getBalance(ETH).isPresent());
assertEquals(0, new BigDecimal("10").compareTo(strategy2TradeAccount.get().getBalance(ETH).get().getAvailable()));
// Strategy 3 accounts data test.
Map<String, AccountDTO> strategy3Accounts = strategy3.getAccounts();
Optional<AccountDTO> strategy3TradeAccount = strategy3.getTradeAccount();
assertEquals(3, strategy3Accounts.size());
assertTrue(strategy3Accounts.containsKey("main"));
assertTrue(strategy3Accounts.containsKey("trade"));
assertTrue(strategy3Accounts.containsKey("savings"));
assertTrue(strategy3TradeAccount.isPresent());
assertEquals("trade", strategy3TradeAccount.get().getName());
assertEquals(3, strategy3TradeAccount.get().getBalances().size());
assertTrue(strategy3TradeAccount.get().getBalance(BTC).isPresent());
assertEquals(0, new BigDecimal("0.99962937").compareTo(strategy3TradeAccount.get().getBalance(BTC).get().getAvailable()));
assertTrue(strategy3TradeAccount.get().getBalance(USDT).isPresent());
assertEquals(0, new BigDecimal("1000").compareTo(strategy3TradeAccount.get().getBalance(USDT).get().getAvailable()));
assertTrue(strategy3TradeAccount.get().getBalance(ETH).isPresent());
assertEquals(0, new BigDecimal("10").compareTo(strategy3TradeAccount.get().getBalance(ETH).get().getAvailable()));
// ==============================================================================================================
// Emitting tickers and checking received tickers by strategies.
// Sending BTC/USDT - BTC/ETH - ETH/USDT.
// Strategy 1 - Requesting BTC/USDT should receive one ticker - 1 BTC costs 50 000 USDT.
// Strategy 2 - Requesting BTC/ETH should receive one ticker - 1 BTC costs 25 ETH.
// Strategy 3 - Requesting BTC/USDT & ETH/USDT should receive two tickers - 1 ETH costs 2000 USDT.
tickerFlux.emitValue(TickerDTO.builder().currencyPair(BTC_USDT).last(new BigDecimal("50000")).build());
tickerFlux.emitValue(TickerDTO.builder().currencyPair(BTC_ETH).last(new BigDecimal("25")).build());
tickerFlux.emitValue(TickerDTO.builder().currencyPair(ETH_USDT).last(new BigDecimal("2000")).build());
await().untilAsserted(() -> assertEquals(2, strategy3.getTickersUpdatesReceived().size()));
// Strategy 1.
assertEquals(1, strategy1.getTickersUpdatesReceived().size());
final TickerDTO strategy1Ticker1 = strategy1.getTickersUpdatesReceived().get(0);
assertNotNull(strategy1Ticker1);
assertEquals(BTC_USDT, strategy1Ticker1.getCurrencyPair());
assertEquals(0, new BigDecimal("50000").compareTo(strategy1Ticker1.getLast()));
// Strategy 2.
assertEquals(1, strategy2.getTickersUpdatesReceived().size());
final TickerDTO strategy2Ticker1 = strategy2.getTickersUpdatesReceived().get(0);
assertNotNull(strategy2Ticker1);
assertEquals(BTC_ETH, strategy2Ticker1.getCurrencyPair());
assertEquals(0, new BigDecimal("25").compareTo(strategy2Ticker1.getLast()));
// Strategy 3.
assertEquals(2, strategy3.getTickersUpdatesReceived().size());
final TickerDTO strategy3Ticker1 = strategy3.getTickersUpdatesReceived().get(0);
assertNotNull(strategy3Ticker1);
assertEquals(BTC_USDT, strategy3Ticker1.getCurrencyPair());
assertEquals(0, new BigDecimal("50000").compareTo(strategy3Ticker1.getLast()));
final TickerDTO strategy3Ticker2 = strategy3.getTickersUpdatesReceived().get(1);
assertNotNull(strategy3Ticker2);
assertEquals(ETH_USDT, strategy3Ticker2.getCurrencyPair());
assertEquals(0, new BigDecimal("2000").compareTo(strategy3Ticker2.getLast()));
// ==============================================================================================================
// Strategy 1 - Creating 1 position on BTC/USDT (0.001 BTC for 50 USDT).
// The price of 1 BTC is 50 000 USDT, and we buy 0.001 BTC for 50 USDT.
// We stop at 100% gain.
final PositionCreationResultDTO position1Result = strategy1.createLongPosition(BTC_USDT, new BigDecimal("0.001"), PositionRulesDTO.builder().stopGainPercentage(100f).build());
assertTrue(position1Result.isSuccessful());
final long position1Uid = position1Result.getPosition().getUid();
final long position1PositionId = position1Result.getPosition().getPositionId();
await().untilAsserted(() -> {
orderFlux.update();
tradeFlux.update();
assertEquals(OPENED, getPositionDTO(position1Uid).getStatus());
});
// Check position uid & position id.
assertEquals(1, position1Uid);
assertEquals(1, position1PositionId);
// Check onPositionUpdate() & onPositionStatusUpdate().
// For strategy 1:
// 4 positions updates:
// - Position OPENING with opening order with PENDING_NEW status.
// - Position OPENING with opening order with NEW status.
// - Position OPENING with opening order with FILLED status.
// - Position OPENED with opening order with FILLED status and the corresponding trade.
// Positions status updates 2: OPENING and then OPENED.
await().untilAsserted(() -> assertEquals(4, strategy1.getPositionsUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(2, strategy1.getPositionsStatusUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(0, strategy2.getPositionsUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(0, strategy2.getPositionsStatusUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(0, strategy3.getPositionsStatusUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(0, strategy3.getPositionsUpdatesReceived().size()));
// Check onOrderUpdate().
// - Order with NEW status.
// - Order with FILLED status.
// (PENDING_NEW orders are not in events).
await().untilAsserted(() -> assertEquals(2, strategy1.getOrdersUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(0, strategy2.getOrdersUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(0, strategy3.getOrdersUpdatesReceived().size()));
// Check onTradeUpdate().
// - Trade arrives for the opening order.
await().untilAsserted(() -> assertEquals(1, strategy1.getTradesUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(0, strategy2.getTradesUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(0, strategy3.getTradesUpdatesReceived().size()));
// Check getOrders() & getOrderByOrderId().
// Only strategy 1 should have received an order.
assertEquals(1, strategy1.getOrders().size());
assertTrue(strategy1.getOrderByOrderId("DRY_ORDER_000000001").isPresent());
assertEquals(0, strategy2.getOrders().size());
assertTrue(strategy2.getOrderByOrderId("DRY_ORDER_000000001").isEmpty());
assertEquals(0, strategy3.getOrders().size());
assertTrue(strategy3.getOrderByOrderId("DRY_ORDER_000000001").isEmpty());
// Check getTrades() & getTradeByTradeId().
// Only strategy 1 should have received a trade.
assertEquals(1, strategy1.getTrades().size());
assertTrue(strategy1.getTradeByTradeId("DRY_TRADE_000000001").isPresent());
assertEquals(0, strategy2.getTrades().size());
assertTrue(strategy2.getTradeByTradeId("DRY_TRADE_000000001").isEmpty());
assertEquals(0, strategy3.getTrades().size());
assertTrue(strategy3.getTradeByTradeId("DRY_TRADE_000000001").isEmpty());
// Check getAmountsLockedByPosition().
BigDecimal amountLockedForBTC = strategy1.getAmountsLockedByCurrency(BTC);
assertEquals(0, new BigDecimal("0.001").compareTo(amountLockedForBTC));
amountLockedForBTC = strategy2.getAmountsLockedByCurrency(BTC);
assertEquals(0, new BigDecimal("0.001").compareTo(amountLockedForBTC));
amountLockedForBTC = strategy3.getAmountsLockedByCurrency(BTC);
assertEquals(0, new BigDecimal("0.001").compareTo(amountLockedForBTC));
// ==============================================================================================================
// Strategy 2 - Creating 1 position on BTC/ETH (0.2 BTC for 0.5 ETH).
// The price of 1 BTC is 25 ETH, and we buy 0.02 BTC for 0.5 ETH.
// We stop at 100% gain.
final PositionCreationResultDTO position2Result = strategy2.createLongPosition(BTC_ETH, new BigDecimal("0.02"), PositionRulesDTO.builder().stopGainPercentage(100f).build());
assertTrue(position2Result.isSuccessful());
final long position2Uid = position2Result.getPosition().getUid();
final long position2PositionId = position2Result.getPosition().getPositionId();
await().untilAsserted(() -> {
orderFlux.update();
tradeFlux.update();
assertEquals(OPENED, getPositionDTO(position2Uid).getStatus());
});
// Check position uid & position id.
assertEquals(2, position2Uid);
assertEquals(1, position2PositionId);
// Check onPositionUpdate() & onPositionStatusUpdate().
// For strategy 2:
// 4 positions updates:
// - Position OPENING with opening order with PENDING_NEW status.
// - Position OPENING with opening order with NEW status.
// - Position OPENING with opening order with FILLED status.
// - Position OPENED with opening order with FILLED status and the corresponding trade.
// Positions status updates 2: OPENING and then OPENED.
await().untilAsserted(() -> assertEquals(4, strategy1.getPositionsUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(2, strategy1.getPositionsStatusUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(4, strategy2.getPositionsUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(2, strategy2.getPositionsStatusUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(0, strategy3.getPositionsStatusUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(0, strategy3.getPositionsUpdatesReceived().size()));
// Check onOrderUpdate().
// - Order with NEW status.
// - Order with FILLED status.
// (PENDING_NEW orders are not in events).
await().untilAsserted(() -> assertEquals(2, strategy1.getOrdersUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(2, strategy2.getOrdersUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(0, strategy3.getOrdersUpdatesReceived().size()));
// Check onTradeUpdate().
// - Trade arrives for the opening order.
await().untilAsserted(() -> assertEquals(1, strategy1.getTradesUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(1, strategy2.getTradesUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(0, strategy3.getTradesUpdatesReceived().size()));
// Check getOrders() & getOrderByOrderId().
assertEquals(1, strategy1.getOrders().size());
assertTrue(strategy1.getOrderByOrderId("DRY_ORDER_000000001").isPresent());
assertEquals(1, strategy2.getOrders().size());
assertTrue(strategy2.getOrderByOrderId("DRY_ORDER_000000002").isPresent());
assertEquals(0, strategy3.getOrders().size());
assertTrue(strategy3.getOrderByOrderId("DRY_ORDER_000000001").isEmpty());
// Check getTrades() & getTradeByTradeId().
assertEquals(1, strategy1.getTrades().size());
assertTrue(strategy1.getTradeByTradeId("DRY_TRADE_000000001").isPresent());
assertEquals(1, strategy2.getTrades().size());
assertTrue(strategy2.getTradeByTradeId("DRY_TRADE_000000002").isPresent());
assertEquals(0, strategy3.getTrades().size());
assertTrue(strategy3.getTradeByTradeId("DRY_TRADE_000000001").isEmpty());
// Check getAmountsLockedByPosition().
amountLockedForBTC = strategy1.getAmountsLockedByCurrency(BTC);
assertEquals(0, new BigDecimal("0.021").compareTo(amountLockedForBTC));
amountLockedForBTC = strategy2.getAmountsLockedByCurrency(BTC);
assertEquals(0, new BigDecimal("0.021").compareTo(amountLockedForBTC));
amountLockedForBTC = strategy3.getAmountsLockedByCurrency(BTC);
assertEquals(0, new BigDecimal("0.021").compareTo(amountLockedForBTC));
// =============================================================================================================
// Changing the price for BTC/USDT.
// A bitcoin now costs 10000 USDT and the price of position should have change.
tickerFlux.emitValue(TickerDTO.builder().currencyPair(BTC_USDT).last(new BigDecimal("10000")).build());
await().untilAsserted(() -> assertEquals(3, strategy3.getTickersUpdatesReceived().size()));
assertEquals(2, strategy1.getTickersUpdatesReceived().size());
assertEquals(1, strategy2.getTickersUpdatesReceived().size());
assertEquals(3, strategy3.getTickersUpdatesReceived().size());
// =============================================================================================================
// Strategy 3
// - Creating one position on BTC/USDT (0.01 BTC for 100 USDT).
final PositionCreationResultDTO position3Result = strategy3.createLongPosition(BTC_USDT, new BigDecimal("0.01"), PositionRulesDTO.builder().stopGainPercentage(100f).build());
assertTrue(position3Result.isSuccessful());
final long position3Uid = position3Result.getPosition().getUid();
final long position3PositionId = position3Result.getPosition().getPositionId();
await().untilAsserted(() -> {
orderFlux.update();
tradeFlux.update();
assertEquals(OPENED, getPositionDTO(position3Uid).getStatus());
});
// - Creating one position on ETH/USDT (0.1 ETH for 200 USDT).
final PositionCreationResultDTO position4Result = strategy3.createLongPosition(ETH_USDT, new BigDecimal("0.1"), PositionRulesDTO.builder().stopGainPercentage(100f).build());
assertTrue(position4Result.isSuccessful());
final long position4Uid = position4Result.getPosition().getUid();
final long position4PositionId = position4Result.getPosition().getPositionId();
await().untilAsserted(() -> {
orderFlux.update();
tradeFlux.update();
assertEquals(OPENED, getPositionDTO(position4Uid).getStatus());
});
// Check position uid & position id.
assertEquals(3, position3Uid);
assertEquals(1, position3PositionId);
assertEquals(4, position4Uid);
assertEquals(2, position4PositionId);
// Check onPositionUpdate() & onPositionStatusUpdate().
// 8 positions updates:
// - 2 positions OPENING with opening order with PENDING_NEW status.
// - 2 positions OPENING with opening order with NEW status.
// - 2 positions OPENING with opening order with FILLED status.
// - 2 positions OPENED with opening order with FILLED status and the corresponding trade.
// Positions status updates 4: 2 orders OPENING and then OPENED.
await().untilAsserted(() -> assertEquals(5, strategy1.getPositionsUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(2, strategy1.getPositionsStatusUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(4, strategy2.getPositionsUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(2, strategy2.getPositionsStatusUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(8, strategy3.getPositionsUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(4, strategy3.getPositionsStatusUpdatesReceived().size()));
// Check onOrderUpdate().
await().untilAsserted(() -> assertEquals(2, strategy1.getOrdersUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(2, strategy2.getOrdersUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(4, strategy3.getOrdersUpdatesReceived().size()));
// Check onTradeUpdate().
await().untilAsserted(() -> assertEquals(1, strategy1.getTradesUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(1, strategy2.getTradesUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(2, strategy3.getTradesUpdatesReceived().size()));
// Check getOrders() & getOrderByOrderId().
// - 2 orders with NEW status.
// - 2 orders with FILLED status.
// (PENDING_NEW orders are not in events).
assertEquals(1, strategy1.getOrders().size());
assertTrue(strategy1.getOrderByOrderId("DRY_ORDER_000000001").isPresent());
assertEquals(1, strategy2.getOrders().size());
assertTrue(strategy2.getOrderByOrderId("DRY_ORDER_000000002").isPresent());
assertEquals(2, strategy3.getOrders().size());
assertTrue(strategy3.getOrderByOrderId("DRY_ORDER_000000003").isPresent());
assertTrue(strategy3.getOrderByOrderId("DRY_ORDER_000000004").isPresent());
// Check getTrades() & getTradeByTradeId().
// - 2 trades arrives for the 2 opening orders.
assertEquals(1, strategy1.getTrades().size());
assertTrue(strategy1.getTradeByTradeId("DRY_TRADE_000000001").isPresent());
assertEquals(1, strategy2.getTrades().size());
assertTrue(strategy2.getTradeByTradeId("DRY_TRADE_000000002").isPresent());
assertEquals(2, strategy3.getTrades().size());
assertTrue(strategy3.getTradeByTradeId("DRY_TRADE_000000003").isPresent());
assertTrue(strategy3.getTradeByTradeId("DRY_TRADE_000000004").isPresent());
// Check getAmountsLockedByPosition().
amountLockedForBTC = strategy1.getAmountsLockedByCurrency(BTC);
assertEquals(0, new BigDecimal("0.031").compareTo(amountLockedForBTC));
amountLockedForBTC = strategy2.getAmountsLockedByCurrency(BTC);
assertEquals(0, new BigDecimal("0.031").compareTo(amountLockedForBTC));
amountLockedForBTC = strategy3.getAmountsLockedByCurrency(BTC);
assertEquals(0, new BigDecimal("0.031").compareTo(amountLockedForBTC));
BigDecimal amountLockedForETH = strategy1.getAmountsLockedByCurrency(ETH);
assertEquals(0, new BigDecimal("0.1").compareTo(amountLockedForETH));
amountLockedForETH = strategy2.getAmountsLockedByCurrency(ETH);
assertEquals(0, new BigDecimal("0.1").compareTo(amountLockedForETH));
amountLockedForETH = strategy3.getAmountsLockedByCurrency(ETH);
assertEquals(0, new BigDecimal("0.1").compareTo(amountLockedForETH));
// ==============================================================================================================
// Check balances, canBuy() & canSell().
// At the beginning my balances was:
// - 0.99962937 BTC.
// - 1 000 USDT.
// - 10 ETH.
// ---
// Position 1 - Bought 0.001 BTC for 50 USDT - BTC/USDT : 50000.
// Position 2 - Bought 0.2 BTC for 0.5 ETH - BTC/ETH : 25.
// Position 3 - Bought 0.01 BTC for 100 USDT - BTC/USDT : 10000
// Position 4 - Bought 0.1 ETH for 200 USDT - ETH/USDT : 2000.
// --
// BTC : 0.99962937 + 0.001 (P1) + 0.02 (P2) + 0.01 (P2) = 1.03062937 BTC
// USDT : 1000 - 50 (P1) - 100 (P3) - 200 (P4) = 650 USDT
// ETH : 10 - 0.5 (P2) + 0.1 (P4) = 5.1 ETH
accountFlux.update();
await().untilAsserted(() -> assertEquals(4, strategy3.getAccountsUpdatesReceived().size()));
// Strategy 1 balances.
assertEquals(0, new BigDecimal("1.03062937").compareTo(strategy1.getTradeAccountBalances().get(BTC).getAvailable()));
assertEquals(0, new BigDecimal("650").compareTo(strategy1.getTradeAccountBalances().get(USDT).getAvailable()));
assertEquals(0, new BigDecimal("9.6").compareTo(strategy1.getTradeAccountBalances().get(ETH).getAvailable()));
// Strategy 2 balances.
assertEquals(0, new BigDecimal("1.03062937").compareTo(strategy2.getTradeAccountBalances().get(BTC).getAvailable()));
assertEquals(0, new BigDecimal("650").compareTo(strategy2.getTradeAccountBalances().get(USDT).getAvailable()));
assertEquals(0, new BigDecimal("9.6").compareTo(strategy2.getTradeAccountBalances().get(ETH).getAvailable()));
// Strategy 3 balances.
assertEquals(0, new BigDecimal("1.03062937").compareTo(strategy3.getTradeAccountBalances().get(BTC).getAvailable()));
assertEquals(0, new BigDecimal("650").compareTo(strategy3.getTradeAccountBalances().get(USDT).getAvailable()));
assertEquals(0, new BigDecimal("9.6").compareTo(strategy3.getTradeAccountBalances().get(ETH).getAvailable()));
// ==============================================================================================================
// New ticker on BTC USDT to close a position.
// Before : BTC/USDT - 10 000
// New : BTC/USDT - 20 000 => Should trigger closing Position 3 (Position 1 of strategy 3).
// Position 1 - Bought 0.001 BTC for 50 USDT - BTC/USDT : 50000.
// Position 2 - Bought 0.2 BTC for 0.5 ETH - BTC/ETH : 25.
// Position 3 - Bought 0.01 BTC for 100 USDT - BTC/USDT : 20000 => Now close, sold 0.01 BTC for 200 USDT.
// Position 4 - Bought 0.1 ETH for 200 USDT - ETH/USDT : 2000.
tickerFlux.emitValue(TickerDTO.builder().currencyPair(BTC_USDT).last(new BigDecimal("20000")).build());
await().untilAsserted(() -> assertEquals(CLOSING, getPositionDTO(position3Uid).getStatus()));
await().untilAsserted(() -> {
orderFlux.update();
tradeFlux.update();
assertEquals(CLOSED, getPositionDTO(position3Uid).getStatus());
});
// Check position status.
assertEquals(OPENED, getPositionDTO(position1Uid).getStatus());
assertEquals(OPENED, getPositionDTO(position2Uid).getStatus());
assertEquals(CLOSED, getPositionDTO(position3Uid).getStatus());
assertEquals(OPENED, getPositionDTO(position4Uid).getStatus());
// Check onPositionUpdate() & onPositionStatusUpdate().
// strategy1: 1 position update because of prices updates.
// strategy3, 5 position updates:
// - Position prices updates.
// - Position CLOSING with opening order with PENDING_NEW status.
// - Position CLOSING with opening order with NEW status.
// - Position CLOSING with opening order with FILLED status.
// - Position CLOSED with opening order with FILLED status and the corresponding trade.
// Positions status updates : CLOSING and then CLOSED.
await().untilAsserted(() -> assertEquals(6, strategy1.getPositionsUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(2, strategy1.getPositionsStatusUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(4, strategy2.getPositionsUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(2, strategy2.getPositionsStatusUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(13, strategy3.getPositionsUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(6, strategy3.getPositionsStatusUpdatesReceived().size()));
// Check onOrderUpdate().
// - Order with NEW status.
// - Order with FILLED status.
// (PENDING_NEW orders are not in events).
await().untilAsserted(() -> assertEquals(2, strategy1.getOrdersUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(2, strategy2.getOrdersUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(6, strategy3.getOrdersUpdatesReceived().size()));
// Check onTradeUpdate().
// - Trade arrives for the closing order.
await().untilAsserted(() -> assertEquals(1, strategy1.getTradesUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(1, strategy2.getTradesUpdatesReceived().size()));
await().untilAsserted(() -> assertEquals(3, strategy3.getTradesUpdatesReceived().size()));
// Check getOrders() & getOrderByOrderId().
assertEquals(1, strategy1.getOrders().size());
assertTrue(strategy1.getOrderByOrderId("DRY_ORDER_000000001").isPresent());
assertEquals(1, strategy2.getOrders().size());
assertTrue(strategy2.getOrderByOrderId("DRY_ORDER_000000002").isPresent());
assertEquals(3, strategy3.getOrders().size());
assertTrue(strategy3.getOrderByOrderId("DRY_ORDER_000000003").isPresent());
assertTrue(strategy3.getOrderByOrderId("DRY_ORDER_000000004").isPresent());
assertTrue(strategy3.getOrderByOrderId("DRY_ORDER_000000005").isPresent());
// Check getTrades() & getTradeByTradeId().
assertEquals(1, strategy1.getTrades().size());
assertTrue(strategy1.getTradeByTradeId("DRY_TRADE_000000001").isPresent());
assertEquals(1, strategy2.getTrades().size());
assertTrue(strategy2.getTradeByTradeId("DRY_TRADE_000000002").isPresent());
assertEquals(3, strategy3.getTrades().size());
assertTrue(strategy3.getTradeByTradeId("DRY_TRADE_000000003").isPresent());
assertTrue(strategy3.getTradeByTradeId("DRY_TRADE_000000004").isPresent());
assertTrue(strategy3.getTradeByTradeId("DRY_TRADE_000000005").isPresent());
// Check getAmountsLockedByPosition().
// Position 3 - Bought 0.01 BTC for 100 USDT - BTC/USDT : 20000 => Now close, sold 0.01 BTC for 200 USDT.
amountLockedForBTC = strategy1.getAmountsLockedByCurrency(BTC);
assertEquals(0, new BigDecimal("0.021").compareTo(amountLockedForBTC));
amountLockedForBTC = strategy2.getAmountsLockedByCurrency(BTC);
assertEquals(0, new BigDecimal("0.021").compareTo(amountLockedForBTC));
amountLockedForBTC = strategy3.getAmountsLockedByCurrency(BTC);
assertEquals(0, new BigDecimal("0.021").compareTo(amountLockedForBTC));
amountLockedForETH = strategy1.getAmountsLockedByCurrency(ETH);
assertEquals(0, new BigDecimal("0.1").compareTo(amountLockedForETH));
amountLockedForETH = strategy2.getAmountsLockedByCurrency(ETH);
assertEquals(0, new BigDecimal("0.1").compareTo(amountLockedForETH));
amountLockedForETH = strategy3.getAmountsLockedByCurrency(ETH);
assertEquals(0, new BigDecimal("0.1").compareTo(amountLockedForETH));
// ==============================================================================================================
// Check balances, canBuy() & canSell().
// At the beginning my balances was:
// - 0.99962937 BTC.
// - 1 000 USDT.
// - 10 ETH.
// ---
// Position 1 - Bought 0.001 BTC for 50 USDT - BTC/USDT : 50000.
// Position 2 - Bought 0.2 BTC for 0.5 ETH - BTC/ETH : 25.
// Position 3 - Bought 0.01 BTC for 100 USDT - BTC/USDT : 10000
// Position 4 - Bought 0.1 ETH for 200 USDT - ETH/USDT : 2000.
// New event.
// Position 3 - Bought 0.01 BTC for 100 USDT - BTC/USDT : 20000 => Now close, sold 0.01 BTC for 200 USDT.
// --
// BTC : 0.99962937 + 0.001 (P1) + 0.02 (P2) + 0.01 (P2) - 0.01 (P3 close) = 1.02062937 BTC
// USDT : 1000 - 50 (P1) - 100 (P3) - 200 (P4) + 200 (P3) = 850 USDT
// ETH : 10 - 0.5 (P2) + 0.1 (P4) = 5.1 ETH
accountFlux.update();
await().untilAsserted(() -> assertEquals(5, strategy3.getAccountsUpdatesReceived().size()));
// Strategy 1.
assertEquals(0, new BigDecimal("1.02062937").compareTo(strategy1.getTradeAccountBalances().get(BTC).getAvailable()));
assertEquals(0, new BigDecimal("850").compareTo(strategy1.getTradeAccountBalances().get(USDT).getAvailable()));
assertEquals(0, new BigDecimal("9.6").compareTo(strategy1.getTradeAccountBalances().get(ETH).getAvailable()));
// Strategy 2.
assertEquals(0, new BigDecimal("1.02062937").compareTo(strategy2.getTradeAccountBalances().get(BTC).getAvailable()));
assertEquals(0, new BigDecimal("850").compareTo(strategy2.getTradeAccountBalances().get(USDT).getAvailable()));
assertEquals(0, new BigDecimal("9.6").compareTo(strategy2.getTradeAccountBalances().get(ETH).getAvailable()));
// Strategy 3.
assertEquals(0, new BigDecimal("1.02062937").compareTo(strategy3.getTradeAccountBalances().get(BTC).getAvailable()));
assertEquals(0, new BigDecimal("850").compareTo(strategy3.getTradeAccountBalances().get(USDT).getAvailable()));
assertEquals(0, new BigDecimal("9.6").compareTo(strategy3.getTradeAccountBalances().get(ETH).getAvailable()));
}
use of tech.cassandre.trading.bot.domain.Strategy in project cassandre-trading-bot by cassandre-tech.
the class StrategiesAutoConfiguration method configure.
/**
* Search for strategies and runs them.
*/
@PostConstruct
@SuppressWarnings("checkstyle:MethodLength")
public void configure() {
// Retrieving all the beans have the @Strategy annotation.
final Map<String, Object> strategies = applicationContext.getBeansWithAnnotation(CassandreStrategy.class);
// =============================================================================================================
// Configuration check.
// We run tests to display and check if everything is ok with the configuration.
final UserDTO user = checkConfiguration(strategies);
// =============================================================================================================
// Maintenance code.
// If a position is blocked in OPENING or CLOSING, we send again the trades.
// This could happen if cassandre crashes after saving a trade and did not have time to send it to
// positionService. Here we force the status recalculation in PositionDTO, and we save it.
positionRepository.findByStatusIn(Stream.of(OPENING, CLOSING).toList()).stream().map(POSITION_MAPPER::mapToPositionDTO).map(POSITION_MAPPER::mapToPosition).forEach(positionRepository::save);
// =============================================================================================================
// Importing tickers & candles into database.
// Feature documentation is here: https://trading-bot.cassandre.tech/learn/import-historical-data.html
loadTickersFromFiles();
loadCandlesFromFiles();
// =============================================================================================================
// Creating flux.
final ConnectableFlux<Set<AccountDTO>> connectableAccountFlux = accountFlux.getFlux().publish();
final ConnectableFlux<Set<PositionDTO>> connectablePositionFlux = positionFlux.getFlux().publish();
final ConnectableFlux<Set<OrderDTO>> connectableOrderFlux = orderFlux.getFlux().publish();
final ConnectableFlux<Set<TickerDTO>> connectableTickerFlux = tickerFlux.getFlux().publish();
final ConnectableFlux<Set<TradeDTO>> connectableTradeFlux = tradeFlux.getFlux().publish();
// =============================================================================================================
// Configuring strategies.
// Data in database, services, flux...
logger.info("Running the following strategies:");
strategies.values().forEach(s -> {
CassandreStrategyInterface strategy = (CassandreStrategyInterface) s;
CassandreStrategy annotation = s.getClass().getAnnotation(CassandreStrategy.class);
// Retrieving strategy information from annotation.
final String strategyId = annotation.strategyId();
final String strategyName = annotation.strategyName();
// Displaying information about strategy.
logger.info("- Strategy '{}/{}' (requires {})", strategyId, strategyName, strategy.getRequestedCurrencyPairs().stream().map(CurrencyPairDTO::toString).collect(Collectors.joining(", ")));
// StrategyDTO: saving or updating the strategy in database.
StrategyDTO strategyDTO;
final Optional<Strategy> strategyInDatabase = strategyRepository.findByStrategyId(annotation.strategyId());
if (strategyInDatabase.isEmpty()) {
// =============================================================================================
// If the strategy is NOT in database.
Strategy newStrategy = new Strategy();
newStrategy.setStrategyId(annotation.strategyId());
newStrategy.setName(annotation.strategyName());
strategyDTO = STRATEGY_MAPPER.mapToStrategyDTO(strategyRepository.save(newStrategy));
logger.debug("Strategy created in database: {}", newStrategy);
} else {
// =============================================================================================
// If the strategy is in database.
strategyInDatabase.get().setName(strategyName);
strategyDTO = STRATEGY_MAPPER.mapToStrategyDTO(strategyRepository.save(strategyInDatabase.get()));
logger.debug("Strategy updated in database: {}", strategyInDatabase.get());
}
strategyDTO.initializeLastPositionIdUsed(positionRepository.getLastPositionIdUsedByStrategy(strategyDTO.getUid()));
// Setting up configuration, dependencies and accounts in strategy.
strategy.initializeAccounts(user.getAccounts());
strategy.setConfiguration(getCassandreStrategyConfiguration(strategyDTO));
strategy.setDependencies(getCassandreStrategyDependencies());
// Calling user defined initialize() method.
strategy.initialize();
// Connecting flux to strategy.
connectableAccountFlux.subscribe(strategy::accountsUpdates, throwable -> logger.error("AccountsUpdates failing: {}", throwable.getMessage()));
connectablePositionFlux.subscribe(strategy::positionsUpdates, throwable -> logger.error("PositionsUpdates failing: {}", throwable.getMessage()));
connectableOrderFlux.subscribe(strategy::ordersUpdates, throwable -> logger.error("OrdersUpdates failing: {}", throwable.getMessage()));
connectableTradeFlux.subscribe(strategy::tradesUpdates, throwable -> logger.error("TradesUpdates failing: {}", throwable.getMessage()));
connectableTickerFlux.subscribe(strategy::tickersUpdates, throwable -> logger.error("TickersUpdates failing: {}", throwable.getMessage()));
});
// =============================================================================================================
// Starting flux.
connectableAccountFlux.connect();
connectablePositionFlux.connect();
connectableOrderFlux.connect();
connectableTradeFlux.connect();
connectableTickerFlux.connect();
}
use of tech.cassandre.trading.bot.domain.Strategy in project cassandre-trading-bot by cassandre-tech.
the class BasicCassandreStrategyTest method checkStrategyBehavior.
@Test
@DisplayName("Check strategy behavior")
public void checkStrategyBehavior() {
// =============================================================================================================
// We check that the strategy is correctly registered in database.
Optional<Strategy> strategyInDatabase = strategyRepository.findByStrategyId("01");
assertTrue(strategyInDatabase.isPresent());
assertEquals(1, strategyInDatabase.get().getUid());
assertEquals("01", strategyInDatabase.get().getStrategyId());
assertEquals("Testable strategy", strategyInDatabase.get().getName());
// =============================================================================================================
// We check accounts updates (4 replies : account 01, account 02, account 03 and again account 03).
// But the 4 replies only concerns 3 account.
// We have 1 BTC, 10 ETH and 100 USDT.
with().await().untilAsserted(() -> assertEquals(4, strategy.getAccountsUpdatesReceived().size()));
assertEquals(3, strategy.getAccounts().size());
// Testing trade account.
final Optional<AccountDTO> tradeAccount = strategy.getTradeAccount();
assertTrue(tradeAccount.isPresent());
assertEquals("03", tradeAccount.get().getAccountId());
assertEquals("trade", tradeAccount.get().getName());
// Testing trade account balances.
assertEquals(3, strategy.getTradeAccountBalances().size());
assertEquals(0, new BigDecimal("1").compareTo(strategy.getTradeAccountBalances().get(BTC).getAvailable()));
assertEquals(0, new BigDecimal("10").compareTo(strategy.getTradeAccountBalances().get(ETH).getAvailable()));
assertEquals(0, new BigDecimal("100").compareTo(strategy.getTradeAccountBalances().get(USDT).getAvailable()));
// =============================================================================================================
// We check that all tickers arrived (6 ETH/BTC & 1 ETH/USDT).
// 1 ETH = 6 BTC.
// 1 ETH = 10 000 USDT.
with().await().untilAsserted(() -> assertEquals(7, strategy.getTickersUpdatesReceived().size()));
assertEquals(2, strategy.getLastTickers().size());
assertNotNull(strategy.getLastTickers().get(ETH_BTC));
assertNotNull(strategy.getLastTickers().get(ETH_USDT));
assertEquals(0, new BigDecimal("6").compareTo(strategy.getLastTickers().get(ETH_BTC).getLast()));
assertEquals(0, new BigDecimal("10000").compareTo(strategy.getLastTickers().get(ETH_USDT).getLast()));
// =============================================================================================================
// Check getEstimatedBuyingCost().
// As 1 ETH cost 6 BTC, 2 ETH would cost 12 BTC.
final Optional<CurrencyAmountDTO> estimatedBuyingCost = strategy.getEstimatedBuyingCost(ETH_BTC, new BigDecimal(2));
assertTrue(estimatedBuyingCost.isPresent());
assertEquals(0, new BigDecimal("12").compareTo(estimatedBuyingCost.get().getValue()));
// =============================================================================================================
// Check getEstimatedBuyableAmount()
// As 1 ETH cost 10 000 USDT, with 5 000 USDT, I should be able to buy 0.5 ETH.
// And I check I can't 260 EUR with ETH as I don't have EURO price.
// And I check I can't buy USDT with EURO as I don't have EURO.
final Optional<BigDecimal> estimatedBuyableAmount = strategy.getEstimatedBuyableAmount(new CurrencyAmountDTO(new BigDecimal(5000), USDT), ETH);
assertTrue(estimatedBuyableAmount.isPresent());
assertEquals(0, new BigDecimal("0.5").compareTo(estimatedBuyableAmount.get()));
assertFalse(strategy.getEstimatedBuyableAmount(new CurrencyAmountDTO(new BigDecimal(260), EUR), ETH).isPresent());
assertFalse(strategy.getEstimatedBuyableAmount(new CurrencyAmountDTO(new BigDecimal(260), USDT), EUR).isPresent());
// =============================================================================================================
// Check canBuy() & canSell().
// Our assets: we have 1 BTC, 10 ETH and 100 USDT.
// 1 ETH costs 6 BTC.
// 1 ETH costs 10 000 USDT.
final AccountDTO account = strategy.getAccounts().get("03");
assertNotNull(account);
// canBuy().
// Buying something for an asset we don't have.
assertFalse(strategy.canBuy(BTC_ETH, new BigDecimal("0.00001")));
assertFalse(strategy.canBuy(account, BTC_ETH, new BigDecimal("0.00001")));
// Trying to buy a full bitcoin, but we only have 2 000 USDT.
assertFalse(strategy.canBuy(ETH_USDT, new BigDecimal("1")));
assertFalse(strategy.canBuy(account, ETH_USDT, new BigDecimal("1")));
// Trying to buy a 0.01 ETH that will cost 100 USDT, and we have 100 USDT, it should work but not for 0.011.
assertTrue(strategy.canBuy(ETH_USDT, new BigDecimal("0.01")));
assertTrue(strategy.canBuy(account, ETH_USDT, new BigDecimal("0.01")));
assertFalse(strategy.canBuy(account, ETH_USDT, new BigDecimal("0.011")));
// Trying to buy a 0.01 bitcoin that costs 100 USDT (we have 100 USDT). But we want to have 1 USDT left.
assertFalse(strategy.canBuy(ETH_USDT, new BigDecimal("0.01"), new BigDecimal("1")));
assertFalse(strategy.canBuy(ETH_USDT, new BigDecimal("0.01"), new BigDecimal("1")));
assertFalse(strategy.canBuy(account, ETH_USDT, new BigDecimal("0.01"), new BigDecimal("1")));
assertFalse(strategy.canBuy(account, ETH_USDT, new BigDecimal("0.01"), new BigDecimal("1")));
// canSell().
// Selling an asset we don't have (EUR).
assertFalse(strategy.canSell(EUR, new BigDecimal("0.00001")));
assertFalse(strategy.canSell(account, EUR, new BigDecimal("0.00001")));
// Trying to sell 1 BTC (we have them).
assertTrue(strategy.canSell(BTC, new BigDecimal("1")));
assertTrue(strategy.canSell(account, BTC, new BigDecimal("1")));
// Trying to sell 3 BTC (we don't have them).
assertFalse(strategy.canSell(BTC, new BigDecimal("3")));
assertFalse(strategy.canSell(account, BTC, new BigDecimal("3")));
// Trying to sell 1 BTC and still have 1 (not possible).
assertFalse(strategy.canSell(BTC, new BigDecimal("1"), new BigDecimal("1")));
assertFalse(strategy.canSell(account, BTC, new BigDecimal("1"), new BigDecimal("1")));
}
use of tech.cassandre.trading.bot.domain.Strategy in project cassandre-trading-bot by cassandre-tech.
the class BasicTa4jCassandreStrategyTest method checkStrategyBehavior.
@Test
@DisplayName("Check strategy behavior")
public void checkStrategyBehavior() {
// Check type.
Optional<Strategy> strategyInDatabase = strategyRepository.findByStrategyId("01");
assertTrue(strategyInDatabase.isPresent());
assertEquals(BASIC_TA4J_STRATEGY, strategyInDatabase.get().getType());
await().untilAsserted(() -> assertEquals(3, strategy.getAccounts().size()));
await().untilAsserted(() -> assertEquals(4, strategy.getOrders().size()));
await().untilAsserted(() -> assertEquals(3, strategy.getTrades().size()));
await().untilAsserted(() -> assertEquals(15, strategy.getTickersUpdateReceived().size()));
await().untilAsserted(() -> assertEquals(1, strategy.getLastTickers().size()));
await().untilAsserted(() -> assertEquals(0, new BigDecimal("130").compareTo(strategy.getLastTickers().get(BTC_USDT).getLast())));
// Check ta4j results.
// await().untilAsserted(() -> assertEquals(5, strategy.getEnterCount()));
// await().untilAsserted(() -> assertEquals(2, strategy.getExitCount()));
// TODO: discuss the above - is it really correct? The bars have same value, there should be no entry
await().untilAsserted(() -> assertEquals(7, strategy.getSeries().getBarCount()));
// Check getEstimatedBuyingCost()
assertTrue(strategy.getEstimatedBuyingCost(BTC_USDT, new BigDecimal(3)).isPresent());
assertEquals(0, new BigDecimal("390").compareTo(strategy.getEstimatedBuyingCost(BTC_USDT, new BigDecimal(3)).get().getValue()));
// Check getEstimatedBuyableAmount()
// 1 BTC = 130 USDT.
final Optional<BigDecimal> buyableAmount = strategy.getEstimatedBuyableAmount(new CurrencyAmountDTO(new BigDecimal(260), USDT), BTC);
assertTrue(buyableAmount.isPresent());
assertEquals(0, new BigDecimal("2").compareTo(buyableAmount.get()));
assertFalse(strategy.getEstimatedBuyableAmount(new CurrencyAmountDTO(new BigDecimal(260), ETH), BTC).isPresent());
assertFalse(strategy.getEstimatedBuyableAmount(new CurrencyAmountDTO(new BigDecimal(260), USDT), ETH).isPresent());
// Test for simplified canBuy() & canSell().
// 1 BTC / 150 in my account.
// 1 BTC = 390 UST.
final AccountDTO account = strategy.getAccounts().get("03");
assertNotNull(account);
strategy.getAccounts().remove("01");
// Check canBuy().
// Trying to buy 1 bitcoin for 390 USDT per bitcoin - should work.
assertTrue(strategy.canBuy(new BigDecimal("1")));
assertTrue(strategy.canBuy(account, new BigDecimal("1")));
// Trying to buy 2 bitcoin for 390 USDT per bitcoin - should not work.
assertFalse(strategy.canBuy(new BigDecimal("2")));
assertFalse(strategy.canBuy(account, new BigDecimal("2")));
// Trying to buy 1 bitcoin for 390 USDT per bitcoin, but I want 400 USDT left - should not work.
assertFalse(strategy.canBuy(new BigDecimal("2"), new BigDecimal("400")));
assertFalse(strategy.canBuy(account, new BigDecimal("2"), new BigDecimal("400")));
// Check canSell().
// 1 BTC / 500 in my account.
// Wanting to sell 1 bitcoin - I have them.
assertTrue(strategy.canSell(new BigDecimal("1")));
assertTrue(strategy.canSell(account, new BigDecimal("1")));
// Wanting to sell 2 bitcoin - I don't have them.
assertFalse(strategy.canSell(new BigDecimal("2")));
assertFalse(strategy.canSell(account, new BigDecimal("2")));
// Wanting to sell 1 bitcoin but have one left after - Not possible.
assertFalse(strategy.canSell(new BigDecimal("1"), new BigDecimal('1')));
assertFalse(strategy.canSell(account, new BigDecimal("1"), new BigDecimal('1')));
}
Aggregations