use of tech.cassandre.trading.bot.dto.user.UserDTO in project cassandre-trading-bot by cassandre-tech.
the class UserServiceTest method checkGetUser.
@Test
@Tag("integration")
@DisplayName("Check get user, accounts and balances")
public void checkGetUser() {
// Expected values.
final int expectedAccounts = 1;
final int expectedWalletsInTradingAccount = 2;
// =============================================================================================================
// Retrieve the account.
Optional<UserDTO> user = userService.getUser();
// =============================================================================================================
// Testing Account.
assertTrue(user.isPresent());
assertNotNull(user.get().getTimestamp());
assertTrue(user.get().getTimestamp().isAfter(ZonedDateTime.now().minusSeconds(1)));
assertTrue(user.get().getTimestamp().isBefore(ZonedDateTime.now().plusSeconds(1)));
// =============================================================================================================
// Testing wallets.
assertEquals(expectedAccounts, user.get().getAccounts().size());
Map<String, AccountDTO> wallets = user.get().getAccounts();
// AccountDTO mainWallet = wallets.get("main");
// assertNotNull(mainWallet);
// assertEquals("main", mainWallet.getAccountId());
// assertEquals("main", mainWallet.getName());
// assertEquals(2, mainWallet.getFeatures().size());
// assertTrue(mainWallet.getFeatures().contains(TRADING));
// assertTrue(mainWallet.getFeatures().contains(FUNDING));
AccountDTO tradeWallet = wallets.get("trade");
assertNotNull(tradeWallet);
assertEquals("trade", tradeWallet.getAccountId());
assertEquals("trade", tradeWallet.getName());
assertEquals(2, tradeWallet.getFeatures().size());
assertTrue(tradeWallet.getFeatures().contains(TRADING));
assertTrue(tradeWallet.getFeatures().contains(FUNDING));
// =============================================================================================================
// Testing balances.
assertEquals(expectedWalletsInTradingAccount, tradeWallet.getBalances().size());
// Existing balances.
assertTrue(tradeWallet.getBalance("BTC").isPresent());
assertTrue(tradeWallet.getBalance(BTC).isPresent());
assertTrue(tradeWallet.getBalance("USDT").isPresent());
assertTrue(tradeWallet.getBalance(USDT).isPresent());
// Non-existing balances.
assertTrue(tradeWallet.getBalance("ANC").isEmpty());
assertTrue(tradeWallet.getBalance(ANC).isEmpty());
// Values.
assertEquals(1, tradeWallet.getBalance("BTC").get().getTotal().compareTo(ZERO));
assertEquals(1, tradeWallet.getBalance("USDT").get().getTotal().compareTo(ZERO));
}
use of tech.cassandre.trading.bot.dto.user.UserDTO in project cassandre-trading-bot by cassandre-tech.
the class UserServiceTest method checkBalancesUpdate.
@Test
@DisplayName("Check balances updates")
public void checkBalancesUpdate() {
// We retrieve the account information in the strategy.
assertTrue(strategy.getAccountsUpdatesReceived().isEmpty());
accountFlux.update();
await().untilAsserted(() -> assertEquals(3, strategy.getAccountsUpdatesReceived().size()));
// =============================================================================================================
// Received ticker for ETH/BTC - It means 1 ETH can be bought with 0.032661 BTC.
// last = 0.032661 (Last trade field is the price set during the last trade)
tickerFlux.emitValue(TickerDTO.builder().currencyPair(ETH_BTC).last(new BigDecimal("0.032666")).bid(new BigDecimal("0.032466")).ask(new BigDecimal("0.032657")).high(new BigDecimal("0.034441")).low(new BigDecimal("0.032355")).volume(new BigDecimal("33794.9795777")).quoteVolume(new BigDecimal("1146.8453384314658")).build());
await().untilAsserted(() -> assertEquals(1, strategy.getLastTickers().size()));
// =============================================================================================================
// Account before buying.
// Loaded from spring-boot-starter/autoconfigure/src/test/resources/user-trade.csv
// BTC => 0.99962937
// ETH => 10
Optional<UserDTO> user = userService.getUser();
assertTrue(user.isPresent());
AccountDTO tradeAccount = user.get().getAccounts().get("trade");
Optional<BalanceDTO> tradeBTC = tradeAccount.getBalance(BTC);
assertTrue(tradeBTC.isPresent());
assertEquals(0, new BigDecimal("0.99962937").compareTo(tradeBTC.get().getAvailable()));
Optional<BalanceDTO> tradeETH = tradeAccount.getBalance(ETH);
assertTrue(tradeETH.isPresent());
assertEquals(0, new BigDecimal("10").compareTo(tradeETH.get().getAvailable()));
// =============================================================================================================
// Buying 0.02 ETH for 0.00065332 BTC.
// Last price from ticker * amount ordered
// 0.032666 * 0.02 = 0.00065332 BTC
strategy.createBuyMarketOrder(ETH_BTC, new BigDecimal("0.02"));
// =============================================================================================================
// We expect one account update (with the new ETH and BTC balances).
accountFlux.update();
await().untilAsserted(() -> assertEquals(4, strategy.getAccountsUpdatesReceived().size()));
// =============================================================================================================
// Account after buying (from strategy).
// BTC => 0.99897605 (previous amount - amount bought = 0.99962937 - 0.00065332)
// ETH => 10.02 (we bought 0.02)
tradeAccount = strategy.getAccounts().get("trade");
assertNotNull(tradeAccount);
tradeBTC = tradeAccount.getBalance(BTC);
assertTrue(tradeBTC.isPresent());
assertEquals(0, new BigDecimal("0.99897605").compareTo(tradeBTC.get().getAvailable()));
tradeETH = tradeAccount.getBalance(ETH);
assertTrue(tradeETH.isPresent());
assertEquals(0, new BigDecimal("10.02").compareTo(tradeETH.get().getAvailable()));
// =============================================================================================================
// Account after buying (from user service).
// BTC => 0.99897605 (previous amount - amount bought = 0.99962937 - 0.00065332)
// ETH => 10.02
user = userService.getUser();
assertTrue(user.isPresent());
tradeAccount = user.get().getAccounts().get("trade");
tradeBTC = tradeAccount.getBalance(BTC);
assertTrue(tradeBTC.isPresent());
assertEquals(0, new BigDecimal("0.99897605").compareTo(tradeBTC.get().getAvailable()));
tradeETH = tradeAccount.getBalance(ETH);
assertTrue(tradeETH.isPresent());
assertEquals(0, new BigDecimal("10.02").compareTo(tradeETH.get().getAvailable()));
// =============================================================================================================
// Testing the trade received.
// Amount => 0.02
// Price => 0.032666
await().untilAsserted(() -> {
orderFlux.update();
tradeFlux.update();
assertEquals(1, tradeRepository.count());
});
final Optional<TradeDTO> buyingTrade = tradeRepository.findByTradeId("DRY_TRADE_000000001").map(TRADE_MAPPER::mapToTradeDTO);
assertTrue(buyingTrade.isPresent());
assertEquals(BID, buyingTrade.get().getType());
assertEquals(0, new BigDecimal("0.02").compareTo(buyingTrade.get().getAmount().getValue()));
assertEquals(ETH, buyingTrade.get().getAmount().getCurrency());
assertEquals(0, new BigDecimal("0.032666").compareTo(buyingTrade.get().getPrice().getValue()));
assertEquals(BTC, buyingTrade.get().getPrice().getCurrency());
// =============================================================================================================
// Received ticker for ETH/BTC - It means 1 ETH can be bought with 0.032466 BTC.
// last = 0.032466 (Last trade field is the price set during the last trade)
tickerFlux.emitValue(TickerDTO.builder().currencyPair(ETH_BTC).last(new BigDecimal("0.032466")).bid(new BigDecimal("0.032466")).ask(new BigDecimal("0.032657")).high(new BigDecimal("0.034441")).low(new BigDecimal("0.032355")).volume(new BigDecimal("33794.9795777")).quoteVolume(new BigDecimal("1146.8453384314658")).build());
await().untilAsserted(() -> assertEquals(2, strategy.getTickersUpdatesReceived().size()));
// =============================================================================================================
// Selling 0.02 ETH.
// Amount * Last price from ticker
// 0.02 * 0.032466 = 0.00064932 ETH
strategy.createSellMarketOrder(ETH_BTC, new BigDecimal("0.02"));
// =============================================================================================================
// We expect one account update (with the new ETH and BTC balances).
accountFlux.update();
await().untilAsserted(() -> assertEquals(5, strategy.getAccountsUpdatesReceived().size()));
// =============================================================================================================
// Account values in strategy should be :
// BTC => 0.99962537 (previous sold + amount sold = 0.99897605 + 0.00064932)
// ETH => 10 (0.02 sold)
tradeAccount = strategy.getAccounts().get("trade");
assertNotNull(tradeAccount);
tradeBTC = tradeAccount.getBalance(BTC);
assertTrue(tradeBTC.isPresent());
assertEquals(0, new BigDecimal("0.99962537").compareTo(tradeBTC.get().getAvailable()));
tradeETH = tradeAccount.getBalance(ETH);
assertTrue(tradeETH.isPresent());
assertEquals(0, new BigDecimal("10").compareTo(tradeETH.get().getAvailable()));
// =============================================================================================================
// Testing the trade.
// Amount => 0.02
// Price => 0.032466
await().untilAsserted(() -> {
orderFlux.update();
tradeFlux.update();
assertEquals(2, tradeRepository.count());
});
final Optional<TradeDTO> sellingTrade = tradeRepository.findByTradeId("DRY_TRADE_000000002").map(TRADE_MAPPER::mapToTradeDTO);
assertTrue(sellingTrade.isPresent());
assertEquals(ASK, sellingTrade.get().getType());
assertEquals(0, new BigDecimal("0.02").compareTo(sellingTrade.get().getAmount().getValue()));
assertEquals(ETH, sellingTrade.get().getAmount().getCurrency());
assertEquals(0, new BigDecimal("0.032466").compareTo(sellingTrade.get().getPrice().getValue()));
}
use of tech.cassandre.trading.bot.dto.user.UserDTO in project cassandre-trading-bot by cassandre-tech.
the class UserServiceTest method checkImportUserAccounts.
@Test
@DisplayName("Check imported user accounts")
public void checkImportUserAccounts() {
assertTrue(strategy.getConfiguration().isDryMode());
// Retrieve user.
final Optional<UserDTO> user = userService.getUser();
assertTrue(user.isPresent());
assertEquals(3, user.get().getAccounts().size());
// =============================================================================================================
// In dry mode, Cassandre simulates your accounts by loading files starting with "user-" and ending with "*sv".
// Main account.
// Loaded from spring-boot-starter/autoconfigure/src/test/resources/user-main.tsv
final AccountDTO mainAccount = user.get().getAccounts().get("main");
assertEquals("main", mainAccount.getAccountId());
assertEquals("main", mainAccount.getName());
assertEquals(1, mainAccount.getBalances().size());
Optional<BalanceDTO> mainBTC = mainAccount.getBalance(BTC);
assertTrue(mainBTC.isPresent());
assertEquals(0, new BigDecimal("99.0001").compareTo(mainBTC.get().getAvailable()));
// Trade account.
// Loaded from spring-boot-starter/autoconfigure/src/test/resources/user-trade.csv
final AccountDTO tradeAccount = user.get().getAccounts().get("trade");
assertEquals("trade", tradeAccount.getAccountId());
assertEquals("trade", tradeAccount.getName());
assertEquals(3, tradeAccount.getBalances().size());
Optional<BalanceDTO> tradeBTC = tradeAccount.getBalance(BTC);
assertTrue(tradeBTC.isPresent());
assertEquals(0, new BigDecimal("0.99962937").compareTo(tradeBTC.get().getAvailable()));
Optional<BalanceDTO> tradeUSDT = tradeAccount.getBalance(USDT);
assertTrue(tradeUSDT.isPresent());
assertEquals(0, new BigDecimal("1000").compareTo(tradeUSDT.get().getAvailable()));
Optional<BalanceDTO> tradeETH = tradeAccount.getBalance(ETH);
assertTrue(tradeETH.isPresent());
assertEquals(0, new BigDecimal("10").compareTo(tradeETH.get().getAvailable()));
// Savings account.
// Loaded from: spring-boot-starter/autoconfigure/src/test/resources/user-savings.csv
final AccountDTO savingsAccount = user.get().getAccounts().get("savings");
assertEquals("savings", savingsAccount.getAccountId());
assertEquals("savings", savingsAccount.getName());
assertEquals(3, savingsAccount.getBalances().size());
Optional<BalanceDTO> savingsBTC = savingsAccount.getBalance(BTC);
assertTrue(savingsBTC.isPresent());
assertEquals(0, new BigDecimal("1.1").compareTo(savingsBTC.get().getAvailable()));
Optional<BalanceDTO> savingsUSDT = savingsAccount.getBalance(USDT);
assertTrue(savingsUSDT.isPresent());
assertEquals(0, new BigDecimal("2.2").compareTo(savingsUSDT.get().getAvailable()));
Optional<BalanceDTO> savingsETH = savingsAccount.getBalance(ETH);
assertTrue(savingsETH.isPresent());
assertEquals(0, new BigDecimal("3.3").compareTo(savingsETH.get().getAvailable()));
}
use of tech.cassandre.trading.bot.dto.user.UserDTO in project cassandre-trading-bot by cassandre-tech.
the class UserServiceDryModeAOP method addToBalance.
/**
* Update balance of trade account (method called by trade service).
*
* @param strategy strategy
* @param currency currency
* @param amount amount
*/
public void addToBalance(final CassandreStrategy strategy, final Currency currency, final BigDecimal amount) {
final Optional<AccountDTO> tradeAccount = strategy.getTradeAccount();
if (tradeAccount.isEmpty()) {
logger.error("Trading account not found!");
} else {
// We build a new account information from what we saved.
Collection<Wallet> wallets = new LinkedHashSet<>();
// We retreat all the wallets we have.
accountInfo.getWallets().forEach((name, wallet) -> {
HashMap<Currency, Balance> balances = new LinkedHashMap<>();
// For each balance, we add it if nothing changed or, if on trading account, and we need to change the amount,
// Then we do it.
wallet.getBalances().forEach((balanceCurrency, balance) -> {
if (name.equals(tradeAccount.get().getName()) && balanceCurrency.equals(currency)) {
// If we are on the account and currency to update, we calculate the new value.
balances.put(balanceCurrency, new Balance(balanceCurrency, balance.getTotal().add(amount)));
} else {
// Else we keep the same value.
balances.put(balanceCurrency, balance);
}
});
// amounts, then we create a new balance.
if (name.equals(tradeAccount.get().getName()) && balances.get(currency) == null) {
balances.put(currency, new Balance(currency, amount));
}
// We add the wallet.
wallets.add(new Wallet(name, name, balances.values(), Collections.emptySet(), ZERO, ZERO));
});
// Creates the account info.
accountInfo = new AccountInfo(USER_ID, wallets);
// Updates all strategies.
final UserDTO userDTO = ACCOUNT_MAPPER.mapToUserDTO(accountInfo);
applicationContext.getBeansWithAnnotation(tech.cassandre.trading.bot.strategy.CassandreStrategy.class).values().stream().map(o -> (CassandreStrategyInterface) o).forEach(cassandreStrategyInterface -> cassandreStrategyInterface.initializeAccounts(userDTO.getAccounts()));
}
}
use of tech.cassandre.trading.bot.dto.user.UserDTO 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();
}
Aggregations