diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index efdf1c9bd..000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -patreon: straumat diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 08406e6df..bc4ed6c88 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,6 +7,9 @@ assignees: '' --- +**Release number** +Cassandre release you are using. + **Describe the bug** A clear and concise description of what the bug is. diff --git a/.github/workflows/branch-push.yml b/.github/workflows/branch-push.yml index 3a6d9ae25..fdc27fe8f 100644 --- a/.github/workflows/branch-push.yml +++ b/.github/workflows/branch-push.yml @@ -25,18 +25,18 @@ jobs: id: sources uses: actions/checkout@v1 - # ================================================================================================================ - - name: Build, run tests, package and deploy to Maven central - id: package - run: | - mvn install -Dgpg.skip - # ================================================================================================================ - name: Retrieve the version id: version run: | echo "::set-output name=version::$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" + # ================================================================================================================ + - name: Build, run tests, package and deploy to Maven central + id: package + run: | + mvn install -Dgpg.skip + # ================================================================================================================ - name: Test Cassandre trading bot maven archetype - basic strategy id: cassandre-trading-bot-spring-boot-starter-basic-archetype @@ -49,7 +49,7 @@ jobs: -DartifactId=archetype-test-basic \ -Dversion=1.0-SNAPSHOT \ -Dpackage=com.example - mvn -f archetype-test-basic/pom.xml compile + mvn -f archetype-test-basic/pom.xml test # ================================================================================================================ - name: Test Cassandre trading bot maven archetype - basic ta4j strategy @@ -63,4 +63,4 @@ jobs: -DartifactId=archetype-test-ta4j-basic \ -Dversion=1.0-SNAPSHOT \ -Dpackage=com.example - mvn -f archetype-test-ta4j-basic/pom.xml compile + mvn -f archetype-test-ta4j-basic/pom.xml test diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index bc582edb1..d0d0a8cd1 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -66,7 +66,7 @@ jobs: -DartifactId=archetype-test-basic \ -Dversion=1.0-SNAPSHOT \ -Dpackage=com.example - mvn -f archetype-test-basic/pom.xml compile + mvn -f archetype-test-basic/pom.xml test # ================================================================================================================ - name: Test Cassandre trading bot maven archetype - basic ta4j strategy @@ -80,4 +80,4 @@ jobs: -DartifactId=archetype-test-ta4j-basic \ -Dversion=1.0-SNAPSHOT \ -Dpackage=com.example - mvn -f archetype-test-ta4j-basic/pom.xml compile + mvn -f archetype-test-ta4j-basic/pom.xml test diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index d86f3f98b..bf14fa918 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -49,32 +49,4 @@ jobs: BINANCE_KEY: ${{ secrets.BINANCE_KEY }} BINANCE_SECRET: ${{ secrets.BINANCE_SECRET }} run: | - mvn -Pintegration package - - # ================================================================================================================ - - name: Test Cassandre trading bot maven archetype - basic strategy - id: cassandre-trading-bot-spring-boot-starter-basic-archetype - run: | - mvn -B archetype:generate \ - -DarchetypeGroupId=tech.cassandre.trading.bot \ - -DarchetypeArtifactId=cassandre-trading-bot-spring-boot-starter-basic-archetype \ - -DarchetypeVersion=${{ steps.version.outputs.version }} \ - -DgroupId=com.example \ - -DartifactId=archetype-test-basic \ - -Dversion=1.0-SNAPSHOT \ - -Dpackage=com.example - mvn -f archetype-test-basic/pom.xml test - - # ================================================================================================================ - - name: Test Cassandre trading bot maven archetype - basic ta4j strategy - id: cassandre-trading-bot-spring-boot-starter-basic-ta4j-archetype - run: | - mvn -B archetype:generate \ - -DarchetypeGroupId=tech.cassandre.trading.bot \ - -DarchetypeArtifactId=cassandre-trading-bot-spring-boot-starter-basic-ta4j-archetype \ - -DarchetypeVersion=${{ steps.version.outputs.version }} \ - -DgroupId=com.example \ - -DartifactId=archetype-test-ta4j-basic \ - -Dversion=1.0-SNAPSHOT \ - -Dpackage=com.example - mvn -f archetype-test-ta4j-basic/pom.xml test \ No newline at end of file + mvn -Pintegration package \ No newline at end of file diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml deleted file mode 100644 index 088d64018..000000000 --- a/.github/workflows/pull-request.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Pull request - -on: pull_request - -jobs: - build: - # ================================================================================================================== - # Configuration. - runs-on: ubuntu-latest - - steps: - # ================================================================================================================ - - name: JDK 11 Setup - id: jdk - uses: actions/setup-java@v1 - with: - java-version: '11' - - # ================================================================================================================ - - name: Retrieve the sources - id: sources - uses: actions/checkout@v1 - - # ================================================================================================================ - - name: Build, run tests, package and deploy to Maven central - id: package - run: | - mvn install -Dgpg.skip - - # ================================================================================================================ - - name: Retrieve the version - id: version - run: | - echo "::set-output name=version::$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" - - # ================================================================================================================ - - name: Test Cassandre trading bot maven archetype - basic strategy - id: cassandre-trading-bot-spring-boot-starter-basic-archetype - run: | - mvn -B archetype:generate \ - -DarchetypeGroupId=tech.cassandre.trading.bot \ - -DarchetypeArtifactId=cassandre-trading-bot-spring-boot-starter-basic-archetype \ - -DarchetypeVersion=${{ steps.version.outputs.version }} \ - -DgroupId=com.example \ - -DartifactId=archetype-test-basic \ - -Dversion=1.0-SNAPSHOT \ - -Dpackage=com.example - mvn -f archetype-test-basic/pom.xml compile - - # ================================================================================================================ - - name: Test Cassandre trading bot maven archetype - basic ta4j strategy - id: cassandre-trading-bot-spring-boot-starter-basic-ta4j-archetype - run: | - mvn -B archetype:generate \ - -DarchetypeGroupId=tech.cassandre.trading.bot \ - -DarchetypeArtifactId=cassandre-trading-bot-spring-boot-starter-basic-ta4j-archetype \ - -DarchetypeVersion=${{ steps.version.outputs.version }} \ - -DgroupId=com.example \ - -DartifactId=archetype-test-ta4j-basic \ - -Dversion=1.0-SNAPSHOT \ - -Dpackage=com.example - mvn -f archetype-test-ta4j-basic/pom.xml compile diff --git a/.github/workflows/release-creation.yml b/.github/workflows/release-creation.yml index 617560cdc..b00780335 100644 --- a/.github/workflows/release-creation.yml +++ b/.github/workflows/release-creation.yml @@ -20,10 +20,6 @@ jobs: env: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - # ================================================================================================================ - - name: Retrieve the sources - uses: actions/checkout@v1 - # ================================================================================================================ - name: JDK 11 Setup uses: actions/setup-java@v1 @@ -34,7 +30,11 @@ jobs: server-password: MAVEN_PASSWORD # ================================================================================================================ - - name: Build, run tests, package + - name: Retrieve the sources + uses: actions/checkout@v1 + + # ================================================================================================================ + - name: Build, run tests, package and deploy id: package env: # Environment variables. GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/docs/src/.vuepress/config.js b/docs/src/.vuepress/config.js index 64ece1a71..bbb48268b 100755 --- a/docs/src/.vuepress/config.js +++ b/docs/src/.vuepress/config.js @@ -105,7 +105,7 @@ module.exports = { ariaLabel: 'Deploy & run', items: [ {text: 'Using Docker', link: '/deploy-and-run/docker'}, - {text: 'Using Qovery', link: '/deploy-and-run/qovery'} + {text: 'Using Qovery', link: '/deploy-and-run/qovery'}, ] }, { @@ -130,16 +130,11 @@ module.exports = { }, { text: 'How-tos', items: [ - { - text: 'Install development tools', - link: '/ressources/how-tos/how-to-install-development-tools' - }, + {text: 'Install development tools', link: '/ressources/how-tos/how-to-install-development-tools'}, + {text: 'Install terraform', link: '/ressources/how-tos/how-to-install-terraform'}, {text: 'Build from sources', link: '/ressources/how-tos/how-to-build-from-sources'}, {text: 'Create a release', link: '/ressources/how-tos/how-to-create-a-release'}, - { - text: 'Create a Kucoin account', - link: '/ressources/how-tos/how-to-create-a-kucoin-account' - }, + {text: 'Create a Kucoin account', link: '/ressources/how-tos/how-to-create-a-kucoin-account'}, ] }, ], diff --git a/docs/src/deploy-and-run/docker.md b/docs/src/deploy-and-run/docker.md index c2b2ff414..6791de131 100644 --- a/docs/src/deploy-and-run/docker.md +++ b/docs/src/deploy-and-run/docker.md @@ -172,7 +172,7 @@ The CI script does the following: --security-opt apparmor=unconfined \ --network="cassandre" \ -e TZ=Europe/Paris \ - -e CASSANDRE_TRADING_BOT_EXCHANGE_NAME='${{ secrets.CASSANDRE_TRADING_BOT_EXCHANGE_NAME }}' \ + -e CASSANDRE_TRADING_BOT_EXCHANGE_DRIVER_CLASS_NAME='${{ secrets.CASSANDRE_TRADING_BOT_EXCHANGE_DRIVER_CLASS_NAME }}' \ -e CASSANDRE_TRADING_BOT_EXCHANGE_USERNAME='${{ secrets.CASSANDRE_TRADING_BOT_EXCHANGE_USERNAME }}' \ -e CASSANDRE_TRADING_BOT_EXCHANGE_PASSPHRASE='${{ secrets.CASSANDRE_TRADING_BOT_EXCHANGE_PASSPHRASE }}' \ -e CASSANDRE_TRADING_BOT_EXCHANGE_KEY='${{ secrets.CASSANDRE_TRADING_BOT_EXCHANGE_KEY }}' \ @@ -182,11 +182,10 @@ The CI script does the following: -e CASSANDRE_TRADING_BOT_EXCHANGE_RATES_ACCOUNT='${{ secrets.CASSANDRE_TRADING_BOT_EXCHANGE_RATES_ACCOUNT }}' \ -e CASSANDRE_TRADING_BOT_EXCHANGE_RATES_TICKER='${{ secrets.CASSANDRE_TRADING_BOT_EXCHANGE_RATES_TICKER }}' \ -e CASSANDRE_TRADING_BOT_EXCHANGE_RATES_ORDER='${{ secrets.CASSANDRE_TRADING_BOT_EXCHANGE_RATES_ORDER }}' \ - -e CASSANDRE_TRADING_BOT_DATABASE_DATASOURCE_DRIVER-CLASS-NAME=${{ secrets.CASSANDRE_TRADING_BOT_DATABASE_DATASOURCE_DRIVER_CLASS_NAME }} \ - -e CASSANDRE_TRADING_BOT_DATABASE_DATASOURCE_URL=${{ secrets.CASSANDRE_TRADING_BOT_DATABASE_DATASOURCE_URL }} \ - -e CASSANDRE_TRADING_BOT_DATABASE_DATASOURCE_USERNAME=${{ secrets.CASSANDRE_TRADING_BOT_DATABASE_DATASOURCE_USERNAME }} \ - -e CASSANDRE_TRADING_BOT_DATABASE_DATASOURCE_PASSWORD=${{ secrets.CASSANDRE_TRADING_BOT_DATABASE_DATASOURCE_PASSWORD }} \ - -e CASSANDRE_TRADING_BOT_DATABASE_TABLE-PREFIX=${{ secrets.CASSANDRE_TRADING_BOT_DATABASE_TABLE_PREFIX }} \ + -e SPRING_DATASOURCE_DRIVER_CLASS_NAME=${{ secrets.CASSANDRE_TRADING_BOT_DATABASE_DATASOURCE_DRIVER_CLASS_NAME }} \ + -e SPRING_DATASOURCE_URL=${{ secrets.CASSANDRE_TRADING_BOT_DATABASE_DATASOURCE_URL }} \ + -e SPRING_DATASOURCE_USERNAME=${{ secrets.CASSANDRE_TRADING_BOT_DATABASE_DATASOURCE_USERNAME }} \ + -e SPRING_DATASOURCE_PASSWORD=${{ secrets.CASSANDRE_TRADING_BOT_DATABASE_DATASOURCE_PASSWORD }} \ -l trading-bot \ straumat/trading-bot:latest ``` diff --git a/docs/src/deploy-and-run/qovery-application-type-choice.png b/docs/src/deploy-and-run/images/qovery-application-type-choice.png similarity index 100% rename from docs/src/deploy-and-run/qovery-application-type-choice.png rename to docs/src/deploy-and-run/images/qovery-application-type-choice.png diff --git a/docs/src/deploy-and-run/qovery-configure-project.png b/docs/src/deploy-and-run/images/qovery-configure-project.png similarity index 100% rename from docs/src/deploy-and-run/qovery-configure-project.png rename to docs/src/deploy-and-run/images/qovery-configure-project.png diff --git a/docs/src/deploy-and-run/qovery-create-application.png b/docs/src/deploy-and-run/images/qovery-create-application.png similarity index 100% rename from docs/src/deploy-and-run/qovery-create-application.png rename to docs/src/deploy-and-run/images/qovery-create-application.png diff --git a/docs/src/deploy-and-run/qovery-create-project.png b/docs/src/deploy-and-run/images/qovery-create-project.png similarity index 100% rename from docs/src/deploy-and-run/qovery-create-project.png rename to docs/src/deploy-and-run/images/qovery-create-project.png diff --git a/docs/src/deploy-and-run/qovery-database-choice.png b/docs/src/deploy-and-run/images/qovery-database-choice.png similarity index 100% rename from docs/src/deploy-and-run/qovery-database-choice.png rename to docs/src/deploy-and-run/images/qovery-database-choice.png diff --git a/docs/src/deploy-and-run/qovery-database-configuration.png b/docs/src/deploy-and-run/images/qovery-database-configuration.png similarity index 100% rename from docs/src/deploy-and-run/qovery-database-configuration.png rename to docs/src/deploy-and-run/images/qovery-database-configuration.png diff --git a/docs/src/deploy-and-run/qovery-deployment-summary.png b/docs/src/deploy-and-run/images/qovery-deployment-summary.png similarity index 100% rename from docs/src/deploy-and-run/qovery-deployment-summary.png rename to docs/src/deploy-and-run/images/qovery-deployment-summary.png diff --git a/docs/src/deploy-and-run/qovery-select-github-project.png b/docs/src/deploy-and-run/images/qovery-select-github-project.png similarity index 100% rename from docs/src/deploy-and-run/qovery-select-github-project.png rename to docs/src/deploy-and-run/images/qovery-select-github-project.png diff --git a/docs/src/deploy-and-run/qovery.md b/docs/src/deploy-and-run/qovery.md index 9e07b0b6e..84363eb94 100644 --- a/docs/src/deploy-and-run/qovery.md +++ b/docs/src/deploy-and-run/qovery.md @@ -40,7 +40,7 @@ Edit `src/main/resources/application.properties` to configure the database:, we ```properties # Exchange configuration. -cassandre.trading.bot.exchange.name=kucoin +cassandre.trading.bot.exchange.driver-class-name=kucoin cassandre.trading.bot.exchange.username=cassandre.crypto.bot@gmail.com cassandre.trading.bot.exchange.passphrase=cassandre cassandre.trading.bot.exchange.key=5df8eea30092f40009cb3c6a @@ -56,10 +56,10 @@ cassandre.trading.bot.exchange.rates.ticker=PT1S cassandre.trading.bot.exchange.rates.trade=PT1S # # Database configuration. -cassandre.trading.bot.database.datasource.driver-class-name=org.postgresql.Driver -cassandre.trading.bot.database.datasource.url=jdbc:postgresql://${QOVERY_DATABASE_QOVERY_TEST_HOST}:5432/${QOVERY_DATABASE_QOVERY_TEST_DATABASE_NAME} -cassandre.trading.bot.database.datasource.username=${QOVERY_DATABASE_QOVERY_TEST_USERNAME} -cassandre.trading.bot.database.datasource.password=${QOVERY_DATABASE_QOVERY_TEST_PASSWORD} +spring.datasource.driver-class-name=org.postgresql.Driver +spring.datasource.url=jdbc:postgresql://${QOVERY_DATABASE_QOVERY_TEST_HOST}:5432/${QOVERY_DATABASE_QOVERY_TEST_DATABASE_NAME} +spring.datasource.username=${QOVERY_DATABASE_QOVERY_TEST_USERNAME} +spring.datasource.password=${QOVERY_DATABASE_QOVERY_TEST_PASSWORD} ``` ### Add the PostgreSQL driver To connect to a PostgreSQL server, you need to add the JDBC driver to your project. Edit your pom.xml and add: @@ -120,35 +120,35 @@ git push -u origin main ## Configure Qovery Connect to [Qovery](https://www.qovery.com/) and signup, then go to the project menu and click on `create a new project`. -![qovery - Create a project](./qovery-create-project.png) +![qovery - Create a project](./images/qovery-create-project.png) Enter your project name (`qovery-test`): -![qovery - Configure the project](./qovery-configure-project.png) +![qovery - Configure the project](./images/qovery-configure-project.png) Now, create an application with the name (`qovery-test-app`) and choose `I have an application` on the next screen: -![qovery - Create an application](./qovery-create-application.png) +![qovery - Create an application](./images/qovery-create-application.png) Then, select the github project you want to deploy to Qovery: -![qovery - Select Github project](./qovery-select-github-project.png) +![qovery - Select Github project](./images/qovery-select-github-project.png) Choose the type of application you have (java): -![qovery - Select application type](./qovery-application-type-choice.png) +![qovery - Select application type](./images/qovery-application-type-choice.png) Choose the type of database (PostgreSQL): -![qovery - Select database type](./qovery-database-choice.png) +![qovery - Select database type](./images/qovery-database-choice.png) Choose the name of the database (`qovery-test-database`): -![qovery - Configure database](./qovery-database-configuration.png) +![qovery - Configure database](./images/qovery-database-configuration.png) Now, check everything is ok the summary page and press create ! -![qovery - Deployment summary](./qovery-deployment-summary.png) +![qovery - Deployment summary](./images/qovery-deployment-summary.png) Qovery will now connect to your repo, creates a configuration file, creates your server and your database, retrieve your sources, build the docker image and run it ! That's it. diff --git a/docs/src/index.md b/docs/src/index.md index 49855435f..dcaa95ecf 100755 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -8,7 +8,7 @@ features: - title: Get into trading with Minimum fuss details: Available as a Spring boot starter, Cassandre takes care of exchange connections, accounts, orders, trades, and positions, so you can focus on building your strategy. - title: Create your strategy in minutes - details: Just code when you want to take short/long positions, set the rules, and we take care of everything (buying, selling, rules management, orders, trades, and tickers). + details: Just code when you want to create short/long positions, set the rules, and we take care of everything (buying, selling, rules management, orders, trades, and tickers). - title: Dry mode & backtesting made easy details: We provide a dry mode to simulate exchange replies to your orders to easily test your strategy. We also provide a spring boot starter to backtest your bot on historical data. footer: diff --git a/docs/src/learn/architecture.md b/docs/src/learn/architecture.md index 4d0321408..95c3416d6 100644 --- a/docs/src/learn/architecture.md +++ b/docs/src/learn/architecture.md @@ -1,2 +1,2 @@ # Architecture -![Cassandre architecture](./cassandre-trading-bot-architecture.png) +![Cassandre architecture](./images/cassandre-trading-bot-architecture.png) diff --git a/docs/src/learn/cassandre-trading-bot-architecture.png b/docs/src/learn/cassandre-trading-bot-architecture.png deleted file mode 100644 index f35fc3bde..000000000 Binary files a/docs/src/learn/cassandre-trading-bot-architecture.png and /dev/null differ diff --git a/docs/src/learn/cassandre-trading-bot-database.png b/docs/src/learn/cassandre-trading-bot-database.png deleted file mode 100644 index 1c7961c44..000000000 Binary files a/docs/src/learn/cassandre-trading-bot-database.png and /dev/null differ diff --git a/docs/src/learn/database-structure.md b/docs/src/learn/database-structure.md index 638f32842..00740ed22 100644 --- a/docs/src/learn/database-structure.md +++ b/docs/src/learn/database-structure.md @@ -1,2 +1,2 @@ # Database structure -![Database structure](./cassandre-trading-bot-database.png) +![Database structure](./images/cassandre-trading-bot-database.png) diff --git a/docs/src/learn/dry-mode-and-backtesting.md b/docs/src/learn/dry-mode-and-backtesting.md index 9e21ae004..74d044aca 100644 --- a/docs/src/learn/dry-mode-and-backtesting.md +++ b/docs/src/learn/dry-mode-and-backtesting.md @@ -62,13 +62,14 @@ curl -s "https://api.kucoin.com/api/v1/market/candles?type=1day&symbol=BTC-USDT& It will create a file named `tickers-btc-usdt.tsv` that contains the historical rate of BTC-USDT from `startDate` (3 months ago) to `endDate` (now). Of course, you can choose your own dates and currency pair. -Place this file in the `src/test/resources` folder of your project and add this line to your JUnit test class: +Place this file in the `src/test/resources` folder of your project and add those lines to your JUnit test class: ```java +@ComponentScan("tech.cassandre.trading.bot") @Import(TickerFluxMock.class) ``` Now, during the tests, instead of receiving tickers from the exchange, you will receive tickers imported from the `tsv/csv` files you put in `src/test/resources`. -You can see an example of dry mode and backtesting in the [Technical analysis chapter](src/learn/technical-analysis-backup/overview.md). +You can see an example of dry mode and backtesting in the [Technical analysis chapter](./technical-analysis). diff --git a/docs/src/learn/images/cassandre-trading-bot-architecture.png b/docs/src/learn/images/cassandre-trading-bot-architecture.png new file mode 100644 index 000000000..dd62612dd Binary files /dev/null and b/docs/src/learn/images/cassandre-trading-bot-architecture.png differ diff --git a/docs/src/learn/images/cassandre-trading-bot-database.png b/docs/src/learn/images/cassandre-trading-bot-database.png new file mode 100644 index 000000000..5b0a2289a Binary files /dev/null and b/docs/src/learn/images/cassandre-trading-bot-database.png differ diff --git a/docs/src/learn/technical_analysis_chart.png b/docs/src/learn/images/technical_analysis_chart.png similarity index 100% rename from docs/src/learn/technical_analysis_chart.png rename to docs/src/learn/images/technical_analysis_chart.png diff --git a/docs/src/learn/position-management.md b/docs/src/learn/position-management.md index 6f2dba755..5e84d4c9f 100644 --- a/docs/src/learn/position-management.md +++ b/docs/src/learn/position-management.md @@ -24,7 +24,7 @@ Then, you can create the position with that rule: createLongPosition(new CurrencyPairDTO(ETH, BTC), new BigDecimal("0.5"), rules); ``` -At this moment, Cassandre will create a buy order of 0.5 ETH (At that moment, 1 ETH costs 1500 USDT), and this will cost us 750 USDT. The position status will be [OPENING](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/dto/position/PositionStatusDTO.html#OPENING), and when all the corresponding trades have arrived, the status will move to [OPENED](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/dto/position/PositionStatusDTO.html#OPENED). +At this moment, Cassandre will create a buy order of 0.5 ETH (1 ETH costs 1500 USDT), and this will cost us 750 USDT. The position status will be [OPENING](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/dto/position/PositionStatusDTO.html#OPENING), and when all the corresponding trades have arrived, the status will move to [OPENED](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/dto/position/PositionStatusDTO.html#OPENED). ::: tip Note: if you want to check if you have enough funds available (at least 750 USDT in our case) before creating the position, you can use the [canBuy()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.html#canBuy%28tech.cassandre.trading.bot.dto.user.AccountDTO,tech.cassandre.trading.bot.dto.util.CurrencyPairDTO,java.math.BigDecimal%29) method. @@ -45,7 +45,7 @@ Let's say you create a short position on 1 ETH with this command: createShortPosition(new CurrencyPairDTO(ETH, BTC), new BigDecimal("1"), rules); ``` -Cassandre will sell 1 ETH and get 1 500 USDT and wait until the price is down enough to buy 2 ETH with that 1 500 USDT. +Cassandre will sell 1 ETH and get 1 500 USDT and wait until the price is down enough to buy 2 ETH with 1 500 USDT. ::: tip Note: if you want to check if you have enough funds available (at 1 ETH in our case) before creating the position, you can use the [canSell()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.html#canSell%28tech.cassandre.trading.bot.dto.util.CurrencyDTO,java.math.BigDecimal%29) method. @@ -57,4 +57,4 @@ On a position you can get the: * The highest calculated gain with [getHighestGainPrice()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/dto/position/PositionDTO.html#getHighestGainPrice()) * The latest calculated gain with [getLatestGainPrice](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/dto/position/PositionDTO.html#getLatestGainPrice()) -Once the position is closed, you can get the gain & fees with [getGain()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/dto/position/PositionDTO.html#getGain()) \ No newline at end of file +Once the position is closed, you can get the final gain & fees with [getGain()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/dto/position/PositionDTO.html#getGain()) \ No newline at end of file diff --git a/docs/src/learn/quickstart.md b/docs/src/learn/quickstart.md index 252fc037a..1c30199f2 100644 --- a/docs/src/learn/quickstart.md +++ b/docs/src/learn/quickstart.md @@ -61,7 +61,7 @@ Your bot configuration is located in `src/main/resources/application.properties` ```properties # # Exchange configuration. -cassandre.trading.bot.exchange.name=kucoin +cassandre.trading.bot.exchange.driver-class-name=kucoin cassandre.trading.bot.exchange.username=kucoin.cassandre.test@gmail.com cassandre.trading.bot.exchange.passphrase=cassandre cassandre.trading.bot.exchange.key=6054ad25365ac6000689a998 @@ -77,10 +77,10 @@ cassandre.trading.bot.exchange.rates.ticker=2000 cassandre.trading.bot.exchange.rates.trade=2000 # # Database configuration. -cassandre.trading.bot.database.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver -cassandre.trading.bot.database.datasource.url=jdbc:hsqldb:mem:cassandre -cassandre.trading.bot.database.datasource.username=sa -cassandre.trading.bot.database.datasource.password= +spring.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver +spring.datasource.url=jdbc:hsqldb:mem:cassandre +spring.datasource.username=sa +spring.datasource.password= ``` ::: tip @@ -134,55 +134,60 @@ import java.util.Set; @CassandreStrategy(strategyName = "Simple strategy") public final class SimpleStrategy extends BasicCassandreStrategy { - @Override - public Set getRequestedCurrencyPairs() { - // We only ask about ETC/BTC (Base currency : BTC / Quote currency : USDT). - return Set.of(new CurrencyPairDTO(BTC, USDT)); - } - - @Override - public Optional getTradeAccount(Set accounts) { - // From all the accounts retrieved by the server, we return the one we used for trading. - return accounts.stream() - .filter(a -> "trade".equals(a.getName())) - .findFirst(); - } - - @Override - public void onAccountUpdate(final AccountDTO account) { - // Here, we will receive an AccountDTO each time there is a change on your account. - System.out.println("Received information about an account : " + account); - } - - @Override - public void onTickerUpdate(final TickerDTO ticker) { - // Here we will receive a TickerDTO each time a new one is available. - System.out.println("Received information about a ticker : " + ticker); - } - - @Override - public void onOrderUpdate(final OrderDTO order) { - // Here, we will receive an OrderDTO each time order data has changed on the exchange. - System.out.println("Received information about an order : " + order); - } - - @Override - public void onTradeUpdate(final TradeDTO trade) { - // Here, we will receive a TradeDTO each time trade data has changed on the exchange. - System.out.println("Received information about a trade : " + trade); - } - - @Override - public void onPositionUpdate(final PositionDTO position) { - // Here, we will receive a PositionDTO each time a position has changed. - System.out.println("Received information about a position : " + position); - } - - @Override - public void onPositionStatusUpdate(final PositionDTO position) { - // Here, we will receive a PositionDTO each time a position status has changed. - System.out.println("Received information about a position status : " + position); - } + @Override + public Set getRequestedCurrencyPairs() { + // We only ask about ETC/BTC (Base currency : BTC / Quote currency : USDT). + return Set.of(new CurrencyPairDTO(BTC, USDT)); + } + + @Override + public Optional getTradeAccount(Set accounts) { + // From all the accounts retrieved by the server, we return the one we used for trading. + if (accounts.size() == 1) { + // Used for Gemini integration tests. + return accounts.stream().findAny(); + } else { + return accounts.stream() + .filter(a -> "trade".equals(a.getName())) + .findFirst(); + } + } + + @Override + public final void onAccountsUpdates(final Map accounts) { + // Here, we will receive an AccountDTO each time there is a change on your account. + accounts.values().forEach(account -> System.out.println("Received information about an account : " + account)); + } + + @Override + public final void onTickersUpdates(final Map tickers) { + // Here we will receive tickers received. + tickers.values().forEach(ticker -> System.out.println("Received information about a ticker : " + ticker)); + } + + @Override + public final void onOrdersUpdates(final Map orders) { + // Here, we will receive an OrderDTO each time order data has changed on the exchange. + orders.values().forEach(order -> System.out.println("Received information about an order : " + order)); + } + + @Override + public void onTradesUpdates(final Map trades) { + // Here, we will receive a TradeDTO each time trade data has changed on the exchange. + trades.values().forEach(trade -> System.out.println("Received information about a trade : " + trade)); + } + + @Override + public void onPositionsUpdates(final Map positions) { + // Here, we will receive a PositionDTO each time a position has changed. + positions.values().forEach(position -> System.out.println("Received information about a position : " + position)); + } + + @Override + public void onPositionsStatusUpdates(final Map positions) { + // Here, we will receive a PositionDTO each time a position status has changed. + positions.values().forEach(position -> System.out.println("Received information about a position status : " + position)); + } } @@ -194,19 +199,19 @@ This is how it works : * In [getRequestedCurrencyPairs()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/CassandreStrategyInterface.html#getRequestedCurrencyPairs%28%29), you have to return the list of currency pairs updates you want to receive from the exchange. * On the exchange, you usually have several accounts, and Cassandre needs to know which one of your accounts is the trading one. To do so, you have to implement the [getTradeAccount()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/CassandreStrategyInterface.html#getTradeAccount%28java.util.Set%29) method, which gives you as a parameter the list of accounts you own, and from that list, you have to return only one. -* If there is a change in your account data, [onAccountUpdate()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.html#onAccountUpdate%28tech.cassandre.trading.bot.dto.user.AccountDTO%29) will be called. -* When a new ticker is available, [onTickerUpdate()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.html#onTickerUpdate%28tech.cassandre.trading.bot.dto.market.TickerDTO%29) will be called. -* If there is a change in your orders, [onOrderUpdate()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.html#onOrderUpdate%28tech.cassandre.trading.bot.dto.trade.OrderDTO%29) will be called. -* If there is a change in your trades, [onTradeUpdate()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.html#onTradeUpdate%28tech.cassandre.trading.bot.dto.trade.TradeDTO%29) will be called. -* If there is a change in your positions, [onPositionUpdate()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.html#onPositionUpdate%28tech.cassandre.trading.bot.dto.position.PositionDTO%29) will be called. -* If there is a change in your position status, [onPositionStatusUpdate()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.html#onPositionStatusUpdate%28tech.cassandre.trading.bot.dto.position.PositionDTO%29) will be called. +* If there is a change in your account data, [onAccountsUpdates()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.html#onAccountsUpdates(java.util.Map)) will be called. +* When a new ticker is available, [onTickersUpdates()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.html#onTickersUpdates(java.util.Map)) will be called. +* If there is a change in your orders, [onOrdersUpdates()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.html#onOrdersUpdates(java.util.Map)) will be called. +* If there is a change in your trades, [onTradesUpdates()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.html#onTradesUpdates(java.util.Map)) will be called. +* If there is a change in your positions, [onPositionsUpdates()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.html#onPositionsUpdates(java.util.Map)) will be called. +* If there is a change in your position status, [onPositionsStatusUpdates()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.html#onPositionsStatusUpdates(java.util.Map)) will be called. ## Manage orders and positions You can create an order like this : ```java @Override -public void onTickerUpdate(final TickerDTO ticker) { +public final void onTickersUpdates(final Map tickers) { createBuyMarketOrder(new CurrencyPairDTO(BTC, USDT), new BigDecimal("0,001")); } ``` diff --git a/docs/src/learn/technical-analysis.md b/docs/src/learn/technical-analysis.md index 17bdf4286..d1a5d9950 100644 --- a/docs/src/learn/technical-analysis.md +++ b/docs/src/learn/technical-analysis.md @@ -25,7 +25,7 @@ On a chart, the horizontal axis is time, and the vertical axis is the price. The This is how it looks like : -![Technical analysis chart](./technical_analysis_chart.png) +![Technical analysis chart](./images/technical_analysis_chart.png) After adding bars to this chart, you can draw lines to forecast future prices. For example, you can draw a line connecting the highest prices, expecting other traders will sell at this point. @@ -139,9 +139,14 @@ On the exchange, you usually have several accounts, and Cassandre needs to know ```java @Override public Optional getTradeAccount(Set accounts) { - return accounts.stream() - .filter(a -> "trade".equals(a.getName())) - .findFirst(); + // From all the accounts retrieved by the server, we return the one we used for trading. + if (accounts.size() == 1) { + return accounts.stream().findAny(); + } else { + return accounts.stream() + .filter(a -> "trade".equals(a.getName())) + .findFirst(); + } } ``` @@ -166,7 +171,7 @@ public Duration getDelayBetweenTwoBars() { ``` ::: tip -This method allows you, for example, to receive tickers every second but only add one to the bar every day. +This method allows you, for example, to receive tickers every second but only add one to your bar every day. ::: ### Create your strategy @@ -252,9 +257,10 @@ curl -s "https://api.kucoin.com/api/v1/market/candles?type=1day&symbol=BTC-USDT& It will create a file named `tickers-btc-usdt.tsv` that contains the historical rate of `btc-usdt` from `startDate` (3 months ago) to `endDate` (now). Of course, you can change dates and currency pair. -Now place this file in the `src/test/resources` folder of our project and add this line to your JUnit test class: +Now place this file in the `src/test/resources` folder of our project and add those lines to your JUnit test class: ```java +@ComponentScan("tech.cassandre.trading.bot") @Import(TickerFluxMock.class) ``` @@ -268,15 +274,26 @@ Now we write the tests : @Test @DisplayName("Check gains") public void gainTest() { + await().forever().until(() -> tickerFluxMock.isFluxDone()); + + final Map gains = strategy.getGains(); + System.out.println("Cumulated gains:"); gains.forEach((currency, gain) -> System.out.println(currency + " : " + gain.getAmount())); - System.out.println("Position still opened :"); + System.out.println("Position closed:"); + strategy.getPositions() + .values() + .stream() + .filter(p -> p.getStatus().equals(CLOSED)) + .forEach(p -> System.out.println(" - " + p.getDescription())); + + System.out.println("Position not closed:"); strategy.getPositions() - .values() - .stream() - .filter(p -> p.getStatus().equals(OPENED)) - .forEach(p -> System.out.println(" - " + p.getDescription())); + .values() + .stream() + .filter(p -> !p.getStatus().equals(CLOSED)) + .forEach(p -> System.out.println(" - " + p.getDescription())); assertTrue(gains.get(strategy.getRequestedCurrencyPair().getQuoteCurrency()).getPercentage() > 0); } @@ -284,4 +301,4 @@ public void gainTest() { The first thing we do with the `await()` method is to wait until all data from `btc-usdt.csv` are imported. Then, we calculate every closed position's gain, and we check that the profits are superior to zero. -The last thing we do is display the list of open positions to see if there are things to improve. +The last thing we do is display the list of opened positions to see if there are things to improve. diff --git a/docs/src/ressources/how-tos/how-to-build-from-sources.md b/docs/src/ressources/how-tos/how-to-build-from-sources.md index 2c42ddc2a..dfca4f35d 100644 --- a/docs/src/ressources/how-tos/how-to-build-from-sources.md +++ b/docs/src/ressources/how-tos/how-to-build-from-sources.md @@ -12,9 +12,9 @@ git clone git@github.com:cassandre-tech/cassandre-trading-bot.git cd cassandre-trading-bot ``` -### Run the build +### Build & install ```bash -mvn package +mvn install -Dgpg.skip ``` ## Build documentation diff --git a/docs/src/ressources/how-tos/how-to-create-a-release.md b/docs/src/ressources/how-tos/how-to-create-a-release.md index 5fe0fc759..00f8617bb 100644 --- a/docs/src/ressources/how-tos/how-to-create-a-release.md +++ b/docs/src/ressources/how-tos/how-to-create-a-release.md @@ -8,7 +8,7 @@ * [trading-bot-archetypes/basic-ta4j-archetype/src/main/resources/archetype-resources/pom.xml](https://github.com/cassandre-tech/cassandre-trading-bot/blob/development/trading-bot-archetypes/basic-ta4j-archetype/src/main/resources/archetype-resources/pom.xml). ## Create the release with Maven -You must be using `ssh` and not `https`, to switch to `ssh`, type : +You must be using `ssh` and not `https`. To switch to `ssh`, type : ```bash git remote set-url origin git@github.com:cassandre-tech/cassandre-trading-bot.git ``` @@ -18,7 +18,7 @@ Start the release with : mvn gitflow:release-start ``` -After choosing the release number, finish the release, push branches and tags, with this command : +After choosing the release number, finish the release, push branches and tags, with this command: ```bash mvn gitflow:release-finish ``` diff --git a/docs/src/ressources/how-tos/how-to-install-terraform.md b/docs/src/ressources/how-tos/how-to-install-terraform.md new file mode 100644 index 000000000..0d9a6ca43 --- /dev/null +++ b/docs/src/ressources/how-tos/how-to-install-terraform.md @@ -0,0 +1,13 @@ +# Install Terraform + +## Install required dependencies +```bash +sudo apt-get update && sudo apt-get install -y gnupg software-properties-common curl` +``` + +## Install Terraform +```bash +curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add - +sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main" +sudo apt-get update && sudo apt-get install terraform +``` diff --git a/docs/src/why-cassandre/features-and-roadmap.md b/docs/src/why-cassandre/features-and-roadmap.md index 24bd5281b..da139149e 100644 --- a/docs/src/why-cassandre/features-and-roadmap.md +++ b/docs/src/why-cassandre/features-and-roadmap.md @@ -7,6 +7,6 @@ * Buy / Sell market & limit orders. * [Automatic position management](../learn/position-management.md) (with stop gain & stop loss rules). * [Dry mode & backtesting](../learn/dry-mode-and-backtesting.md). -* Extensively tested and [documented](../learn/quickstart.md). +* Extensively tested and documented. Our Roadmap is available on [Github](https://github.com/cassandre-tech/cassandre-trading-bot/milestones?direction=asc&sort=due_date&state=open). \ No newline at end of file diff --git a/docs/src/why-cassandre/overview.md b/docs/src/why-cassandre/overview.md index 8eac9c805..7db84e62d 100644 --- a/docs/src/why-cassandre/overview.md +++ b/docs/src/why-cassandre/overview.md @@ -29,14 +29,14 @@ For a [BasicTa4jCassandreStrategy](https://www.javadoc.io/doc/tech.cassandre.tra * [shouldExit()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/BasicTa4jCassandreStrategy.html#shouldExit%28%29) to indicate what you want to do when it's time to sell. ## Data updates -To be notified of new data, you can override the following methods : - -* [onAccountUpdate()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.html#onAccountUpdate%28tech.cassandre.trading.bot.dto.user.AccountDTO%29) to receive updates about your account. -* [onTickerUpdate()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.html#onTickerUpdate%28tech.cassandre.trading.bot.dto.market.TickerDTO%29) to receive new tickers. -* [onOrderUpdate()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.html#onOrderUpdate%28tech.cassandre.trading.bot.dto.trade.OrderDTO%29) to receive updates about your orders. -* [onTradeUpdate()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.html#onTradeUpdate%28tech.cassandre.trading.bot.dto.trade.TradeDTO%29) to receive updates about your trades. -* [onPositionUpdate()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.html#onPositionUpdate%28tech.cassandre.trading.bot.dto.position.PositionDTO%29) to receive updates about your positions. -* [onPositionStatusUpdate()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.html#onPositionStatusUpdate%28tech.cassandre.trading.bot.dto.position.PositionDTO%29) to receive updates about position status change. +To be notified of new data, you can override the following methods : + +* [onAccountsUpdates()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.html#onAccountsUpdates(java.util.Map)) to receive updates about your account. +* [onTickersUpdates()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.html#onTickersUpdates(java.util.Map)) to receive new tickers. +* [onOrdersUpdates()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.html#onOrdersUpdates(java.util.Map)) to receive updates about your orders. +* [onTradesUpdates()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.html#onTradesUpdates(java.util.Map)) to receive updates about your trades. +* [onPositionsUpdates()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.html#onPositionsUpdates(java.util.Map)) to receive updates about your positions. +* [onPositionsStatusUpdates()](https://www.javadoc.io/doc/tech.cassandre.trading.bot/cassandre-trading-bot-spring-boot-autoconfigure/latest/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.html#onPositionsStatusUpdates(java.util.Map)) to receive updates about position status change. ## Buying/selling Inside your strategy, you can create market orders with the methods : diff --git a/docs/src/why-cassandre/supported-cryptocurrency-exchanges.md b/docs/src/why-cassandre/supported-cryptocurrency-exchanges.md index 0d7a92def..4cd2d88d6 100644 --- a/docs/src/why-cassandre/supported-cryptocurrency-exchanges.md +++ b/docs/src/why-cassandre/supported-cryptocurrency-exchanges.md @@ -11,4 +11,6 @@ Cassandre can theoretically support the 60+ cryptocurrency exchanges the way XCh Some exchanges provide a sandbox. On those ones, we were able to create integration tests for : * [Coinbase](https://github.com/cassandre-tech/cassandre-trading-bot/tree/development/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/coinbasepro): Working except cancel order. * [Gemini](https://github.com/cassandre-tech/cassandre-trading-bot/tree/development/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/gemini): Working but market orders are not supported. - * [Kucoin](https://github.com/cassandre-tech/cassandre-trading-bot/tree/development/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/kucoin): Working. \ No newline at end of file + * [Kucoin](https://github.com/cassandre-tech/cassandre-trading-bot/tree/development/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/kucoin): Working. + +Since 5.0.1, we also run real bots with real assets on [Kucoin](https://www.kucoin.com/ucenter/signup?utm_source=Cassandre), [Coinbase](https://www.coinbase.com/join/straumat) and [Binance](https://accounts.binance.com/en/register?ref=122742137&utm_campaign=web_share_link) to make sure everything runs fine. \ No newline at end of file diff --git a/pom.xml b/pom.xml index 14e69dc90..b96692e95 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ tech.cassandre.trading.bot cassandre-trading-bot-project - 5.0.0 + 5.0.1 pom Cassandre trading bot https://github.com/cassandre-tech/cassandre-trading-bot diff --git a/spring-boot-starter-test/autoconfigure/pom.xml b/spring-boot-starter-test/autoconfigure/pom.xml index f9d45406d..08e27882c 100644 --- a/spring-boot-starter-test/autoconfigure/pom.xml +++ b/spring-boot-starter-test/autoconfigure/pom.xml @@ -50,14 +50,14 @@ org.hsqldb hsqldb - 2.5.2 + 2.6.0 test org.knowm.xchange xchange-simulated - 5.0.8 + 5.0.9 test @@ -85,7 +85,7 @@ com.puppycrawl.tools checkstyle - 8.43 + 8.44 @@ -222,7 +222,7 @@ tech.cassandre.trading.bot cassandre-trading-bot-project - 5.0.0 + 5.0.1 ../../pom.xml diff --git a/spring-boot-starter-test/autoconfigure/src/main/java/tech/cassandre/trading/bot/test/mock/TickerFluxMock.java b/spring-boot-starter-test/autoconfigure/src/main/java/tech/cassandre/trading/bot/test/mock/TickerFluxMock.java index e36e012f8..069134028 100644 --- a/spring-boot-starter-test/autoconfigure/src/main/java/tech/cassandre/trading/bot/test/mock/TickerFluxMock.java +++ b/spring-boot-starter-test/autoconfigure/src/main/java/tech/cassandre/trading/bot/test/mock/TickerFluxMock.java @@ -20,7 +20,6 @@ import tech.cassandre.trading.bot.dto.util.CurrencyDTO; import tech.cassandre.trading.bot.dto.util.CurrencyPairDTO; import tech.cassandre.trading.bot.repository.OrderRepository; -import tech.cassandre.trading.bot.repository.PositionRepository; import tech.cassandre.trading.bot.repository.TradeRepository; import tech.cassandre.trading.bot.service.MarketService; @@ -70,14 +69,6 @@ public class TickerFluxMock { @Autowired private ApplicationContext applicationContext; - /** Order flux. */ - @Autowired - private OrderFlux orderFlux; - - /** Trade flux. */ - @Autowired - private TradeFlux tradeFlux; - /** Order repository. */ @Autowired private OrderRepository orderRepository; @@ -86,9 +77,13 @@ public class TickerFluxMock { @Autowired private TradeRepository tradeRepository; - /** Position repository. */ + /** Order flux. */ @Autowired - private PositionRepository positionRepository; + private OrderFlux orderFlux; + + /** Trade flux. */ + @Autowired + private TradeFlux tradeFlux; /** Logger. */ private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); @@ -118,7 +113,7 @@ public MarketService marketService() { MarketService marketService = mock(MarketService.class); // We don't use the getTickers method. - given(marketService.getTickers(any())).willThrow(new NotAvailableFromExchangeException("Not available in this mode")); + given(marketService.getTickers(any())).willThrow(new NotAvailableFromExchangeException("Not available in dry mode")); // For every files. getFilesToLoad() @@ -126,7 +121,7 @@ public MarketService marketService() { .forEach(resource -> { // Adding data. final CurrencyPairDTO cp = getCurrencyPairFromFileName(resource); - logger.info("Adding tests data from " + resource.getFilename().substring(resource.getFilename().indexOf(TICKERS_FILE_PREFIX))); + logger.info("Adding tests data from {}", resource.getFilename().substring(resource.getFilename().indexOf(TICKERS_FILE_PREFIX))); fluxTerminated.put(cp, false); //noinspection rawtypes when(marketService.getTicker(cp)).thenAnswer(new Answer() { @@ -164,7 +159,7 @@ public List getFilesToLoad() { final Resource[] resources = resolver.getResources("classpath*:" + TICKERS_FILE_PREFIX + "*" + TICKERS_FILE_SUFFIX); return Arrays.asList(resources); } catch (IOException e) { - logger.error("TickerFluxMock encountered an error : " + e.getMessage()); + logger.error("TickerFluxMock encountered an error: {}", e.getMessage()); } return Collections.emptyList(); } @@ -241,7 +236,7 @@ private List getTickersFromFile(final Resource file) { } catch (FileNotFoundException e) { logger.error("{} not found !", file.getFilename()); } catch (IOException e) { - logger.error("IOException : " + e); + logger.error("IOException : {}", e.getMessage()); } return tickers; } diff --git a/spring-boot-starter-test/starter/pom.xml b/spring-boot-starter-test/starter/pom.xml index 21db6b0ea..078a447e9 100644 --- a/spring-boot-starter-test/starter/pom.xml +++ b/spring-boot-starter-test/starter/pom.xml @@ -46,7 +46,7 @@ com.puppycrawl.tools checkstyle - 8.43 + 8.44 @@ -116,7 +116,7 @@ tech.cassandre.trading.bot cassandre-trading-bot-project - 5.0.0 + 5.0.1 ../../pom.xml diff --git a/spring-boot-starter/autoconfigure/pom.xml b/spring-boot-starter/autoconfigure/pom.xml index b265ade87..b7027f70d 100644 --- a/spring-boot-starter/autoconfigure/pom.xml +++ b/spring-boot-starter/autoconfigure/pom.xml @@ -53,14 +53,14 @@ org.liquibase liquibase-core - 4.3.5 + 4.4.0 org.knowm.xchange xchange-core - 5.0.8 + 5.0.9 @@ -114,48 +114,48 @@ org.hsqldb hsqldb - 2.5.1 + 2.6.0 test org.knowm.xchange xchange-simulated - 5.0.8 + 5.0.9 test org.knowm.xchange xchange-kucoin - 5.0.8 + 5.0.9 test org.knowm.xchange xchange-coinbase - 5.0.8 + 5.0.9 test org.knowm.xchange xchange-coinbasepro - 5.0.8 + 5.0.9 test org.knowm.xchange xchange-gemini - 5.0.8 + 5.0.9 test org.knowm.xchange xchange-binance - 5.0.8 + 5.0.9 test @@ -199,7 +199,7 @@ com.puppycrawl.tools checkstyle - 8.43 + 8.44 @@ -368,7 +368,7 @@ tech.cassandre.trading.bot cassandre-trading-bot-project - 5.0.0 + 5.0.1 ../../pom.xml diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/batch/AccountFlux.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/batch/AccountFlux.java index d0961b99a..cd70b342f 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/batch/AccountFlux.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/batch/AccountFlux.java @@ -24,23 +24,23 @@ public class AccountFlux extends BaseFlux { @Override protected final Set getNewValues() { - logger.debug("AccountFlux - Retrieving accounts information from exchange"); + logger.debug("Retrieving accounts information from exchange"); Set newValues = new LinkedHashSet<>(); // Calling the service and treating results. userService.getUser().ifPresent(user -> { - // For each account, we check if there is something new. + // For each account, we check if value changed. user.getAccounts().forEach((accountId, account) -> { - logger.debug("AccountFlux - Treating account: {}", accountId); + logger.debug("Checking account: {}", accountId); if (previousValues.containsKey(accountId)) { - // If in the previous values, check the balances. + // If the account is already in the previous values, check if the balances changed. if (!account.equals(previousValues.get(accountId))) { - logger.debug("AccountFlux - Account {} has changed: {}", accountId, account); + logger.debug("Account {} has changed to: {}", accountId, account); newValues.add(account); } } else { - // Send if it does not exist. - logger.debug("AccountFlux - New account: {}", account); + // If it's a new account, we add it. + logger.debug("New account: {}", account); newValues.add(account); } }); diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/batch/OrderFlux.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/batch/OrderFlux.java index 1674e0978..0c668cd7a 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/batch/OrderFlux.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/batch/OrderFlux.java @@ -18,34 +18,34 @@ @RequiredArgsConstructor public class OrderFlux extends BaseFlux { - /** Trade service. */ - private final TradeService tradeService; - /** Order repository. */ private final OrderRepository orderRepository; + /** Trade service. */ + private final TradeService tradeService; + @Override protected final Set getNewValues() { - logger.debug("OrderFlux - Retrieving new orders from exchange"); + logger.debug("Retrieving orders from exchange"); Set newValues = new LinkedHashSet<>(); // Getting all the orders from the exchange. tradeService.getOrders() .forEach(order -> { - logger.debug("OrderFlux - Treating order: {}", order.getOrderId()); + logger.debug("Checking order: {}", order.getOrderId()); final Optional orderInDatabase = orderRepository.findByOrderId(order.getOrderId()); // If the order is not in database, we insert it only if strategy is set on that order. - // If strategy is not set, it means that Cassandre did not yet save the locally created order. + // If strategy is not set, it means that Cassandre did not yet save its locally created order. if (orderInDatabase.isEmpty() && order.getStrategy() != null) { - logger.debug("OrderFlux - New order from exchange: {}", order); + logger.debug("New order from exchange: {}", order); newValues.add(order); } // If the local order is already saved in database and the order retrieved from the exchange // is different, then, we update the order in database. if (orderInDatabase.isPresent() && !orderMapper.mapToOrderDTO(orderInDatabase.get()).equals(order)) { - logger.debug("OrderFlux - Updated order from exchange: {}", order); + logger.debug("Updated order from exchange: {}", order); newValues.add(order); } }); @@ -63,12 +63,12 @@ protected final Set saveValues(final Set newValues) { // Update order. orderMapper.updateOrder(newValue, order); orders.add(orderRepository.save(order)); - logger.debug("OrderFlux - Updating order in database: {}", order); + logger.debug("Updating order in database: {}", order); }, () -> { // Create order. final Order newOrder = orderMapper.mapToOrder(newValue); orders.add(orderRepository.save(newOrder)); - logger.debug("OrderFlux - Creating order in database: {}", newValue); + logger.debug("Creating order in database: {}", newOrder); })); return orders.stream() diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/batch/PositionFlux.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/batch/PositionFlux.java index 23826c4e5..69eb4c59b 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/batch/PositionFlux.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/batch/PositionFlux.java @@ -35,12 +35,12 @@ public final Set saveValues(final Set newValues) { newValues.forEach(positionDTO -> { final Optional position = positionRepository.findById(positionDTO.getId()); if (position.isPresent()) { - // If the position is in database (which must be always true), we update it. + // If the position is in database (which should be always true), we update it. positionMapper.updatePosition(positionDTO, position.get()); positions.add(positionRepository.save(position.get())); - logger.debug("PositionFlux - Updating position in database: {}", positionDTO); + logger.debug("Updating position in database: {}", positionDTO); } else { - logger.error("PositionFlux - Position {} not found in database:", positionDTO.getId()); + logger.error("Position {} not found in database:", positionDTO.getId()); } }); diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/batch/TickerFlux.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/batch/TickerFlux.java index 21ff6740e..745e24cee 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/batch/TickerFlux.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/batch/TickerFlux.java @@ -13,6 +13,7 @@ import java.util.LinkedHashSet; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -30,7 +31,7 @@ public class TickerFlux extends BaseFlux { @Override protected final Set getNewValues() { - logger.debug("TickerFlux - Retrieving tickers from exchange"); + logger.debug("Retrieving tickers from exchange"); Set newValues = new LinkedHashSet<>(); // We retrieve the list of currency pairs asked by all strategies. @@ -38,7 +39,7 @@ protected final Set getNewValues() { .getBeansWithAnnotation(CassandreStrategy.class) .values() .stream() - .map(o -> ((CassandreStrategyInterface) o)) + .map(o -> (CassandreStrategyInterface) o) .map(CassandreStrategyInterface::getRequestedCurrencyPairs) .flatMap(Set::stream) .collect(Collectors.toCollection(LinkedHashSet::new)); @@ -47,18 +48,17 @@ protected final Set getNewValues() { // Get all tickers at once from market service if the method is implemented. marketService.getTickers(requestedCurrencyPairs).stream() .filter(Objects::nonNull) - .forEach(tickerDTO -> { - logger.debug("TickerFlux - New ticker received: {}", tickerDTO); - newValues.add(tickerDTO); - }); + .peek(tickerDTO -> logger.debug("New ticker received: {}", tickerDTO)) + .forEach(newValues::add); } catch (NotAvailableFromExchangeException | NotYetImplementedForExchangeException e) { // If getAllTickers is not available, we retrieve tickers one bye one. requestedCurrencyPairs.stream() .filter(Objects::nonNull) - .forEach(currencyPairDTO -> marketService.getTicker(currencyPairDTO).ifPresent(tickerDTO -> { - logger.debug("TickerFlux - New ticker received: {}", tickerDTO); - newValues.add(tickerDTO); - })); + .map(marketService::getTicker) + .filter(Optional::isPresent) + .peek(tickerDTO -> logger.debug("New ticker received: {}", tickerDTO)) + .map(Optional::get) + .forEach(newValues::add); } return newValues; diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/batch/TradeFlux.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/batch/TradeFlux.java index 7176b3a45..a9093ac34 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/batch/TradeFlux.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/batch/TradeFlux.java @@ -19,29 +19,38 @@ @RequiredArgsConstructor public class TradeFlux extends BaseFlux { - /** Trade service. */ - private final TradeService tradeService; - /** Order repository. */ private final OrderRepository orderRepository; /** Trade repository. */ private final TradeRepository tradeRepository; + /** Trade service. */ + private final TradeService tradeService; + @Override protected final Set getNewValues() { - logger.debug("TradeFlux - Retrieving new trades from exchange"); + logger.debug("Retrieving trades from exchange"); Set newValues = new LinkedHashSet<>(); // Finding which trades has been updated. tradeService.getTrades() .stream() - .filter(t -> orderRepository.findByOrderId(t.getOrderId()).isPresent()) // We only accept trades with order present in database + // Note: we only save trades when the order present in database. + .filter(t -> orderRepository.findByOrderId(t.getOrderId()).isPresent()) .forEach(trade -> { - logger.debug("TradeFlux - Treating trade: {}", trade.getTradeId()); + logger.debug("Checking trade: {}", trade.getTradeId()); final Optional tradeInDatabase = tradeRepository.findByTradeId(trade.getTradeId()); - if (tradeInDatabase.isEmpty() || !tradeMapper.mapToTradeDTO(tradeInDatabase.get()).equals(trade)) { - logger.debug("TradeFlux - Updated trade from exchange: {}", trade); + + // The trade is not in database. + if (tradeInDatabase.isEmpty()) { + logger.debug("New trade from exchange: {}", trade); + newValues.add(trade); + } + + // The trade is in database but the trade values from the server changed. + if (tradeInDatabase.isPresent() && !tradeMapper.mapToTradeDTO(tradeInDatabase.get()).equals(trade)) { + logger.debug("Updated trade from exchange: {}", trade); newValues.add(trade); } }); @@ -59,13 +68,14 @@ public final Set saveValues(final Set newValues) { // Update trade. tradeMapper.updateTrade(newValue, trade); trades.add(tradeRepository.save(trade)); - logger.debug("TradeFlux - Updating trade in database: {}", trade); + logger.debug("Updating trade in database: {}", trade); }, () -> { // Create trade. final Trade newTrade = tradeMapper.mapToTrade(newValue); + // Order is always present as we check it in getNewValues(). orderRepository.findByOrderId(newValue.getOrderId()).ifPresent(newTrade::setOrder); trades.add(tradeRepository.save(newTrade)); - logger.debug("TradeFlux - Creating trade in database: {}", newTrade); + logger.debug("Creating trade in database: {}", newTrade); })); return trades.stream() diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/configuration/DatabaseAutoConfiguration.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/configuration/DatabaseAutoConfiguration.java index a25a713f0..04cd6143b 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/configuration/DatabaseAutoConfiguration.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/configuration/DatabaseAutoConfiguration.java @@ -12,7 +12,7 @@ import java.util.Optional; /** - * Database configures the database. + * DatabaseAutoConfiguration configures the database. */ @Configuration @EnableJpaAuditing(dateTimeProviderRef = "auditingDateTimeProvider") diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/configuration/ExchangeAutoConfiguration.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/configuration/ExchangeAutoConfiguration.java index b63012ee8..f08dc00f2 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/configuration/ExchangeAutoConfiguration.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/configuration/ExchangeAutoConfiguration.java @@ -22,12 +22,12 @@ import tech.cassandre.trading.bot.repository.PositionRepository; import tech.cassandre.trading.bot.repository.TradeRepository; import tech.cassandre.trading.bot.service.ExchangeService; -import tech.cassandre.trading.bot.service.MarketService; -import tech.cassandre.trading.bot.service.TradeService; -import tech.cassandre.trading.bot.service.UserService; import tech.cassandre.trading.bot.service.ExchangeServiceXChangeImplementation; +import tech.cassandre.trading.bot.service.MarketService; import tech.cassandre.trading.bot.service.MarketServiceXChangeImplementation; +import tech.cassandre.trading.bot.service.TradeService; import tech.cassandre.trading.bot.service.TradeServiceXChangeImplementation; +import tech.cassandre.trading.bot.service.UserService; import tech.cassandre.trading.bot.service.UserServiceXChangeImplementation; import tech.cassandre.trading.bot.util.base.configuration.BaseConfiguration; import tech.cassandre.trading.bot.util.exception.ConfigurationException; @@ -60,6 +60,15 @@ public class ExchangeAutoConfiguration extends BaseConfiguration { /** Exchange parameters. */ private final ExchangeParameters exchangeParameters; + /** Order repository. */ + private final OrderRepository orderRepository; + + /** Trade repository. */ + private final TradeRepository tradeRepository; + + /** Position repository. */ + private final PositionRepository positionRepository; + /** XChange. */ private Exchange xChangeExchange; @@ -99,17 +108,8 @@ public class ExchangeAutoConfiguration extends BaseConfiguration { /** Position flux. */ private PositionFlux positionFlux; - /** Order repository. */ - private final OrderRepository orderRepository; - - /** Trade repository. */ - private final TradeRepository tradeRepository; - - /** Position repository. */ - private final PositionRepository positionRepository; - /** - * Instantiating the exchange based on the parameter. + * Instantiating the exchange services based on user parameters. */ @PostConstruct public void configure() { @@ -124,6 +124,7 @@ public void configure() { exchangeSpecification.setExchangeSpecificParametersItem(PASSPHRASE_PARAMETER, exchangeParameters.getPassphrase()); exchangeSpecification.setApiKey(exchangeParameters.getKey()); exchangeSpecification.setSecretKey(exchangeParameters.getSecret()); + exchangeSpecification.getResilience().setRateLimiterEnabled(true); // Specific parameters. if (exchangeParameters.getProxyHost() != null) { @@ -151,7 +152,7 @@ public void configure() { xChangeMarketDataService = xChangeExchange.getMarketDataService(); xChangeTradeService = xChangeExchange.getTradeService(); - // Retrieve rates. + // Retrieve rates from parameters. long accountRate = getRateValue(exchangeParameters.getRates().getAccount()); long tickerRate = getRateValue(exchangeParameters.getRates().getTicker()); long tradeRate = getRateValue(exchangeParameters.getRates().getTrade()); @@ -165,26 +166,27 @@ public void configure() { // Creates Cassandre flux. accountFlux = new AccountFlux(getUserService()); tickerFlux = new TickerFlux(applicationContext, getMarketService()); - orderFlux = new OrderFlux(getTradeService(), orderRepository); - tradeFlux = new TradeFlux(getTradeService(), orderRepository, tradeRepository); + orderFlux = new OrderFlux(orderRepository, getTradeService()); + tradeFlux = new TradeFlux(orderRepository, tradeRepository, getTradeService()); positionFlux = new PositionFlux(positionRepository); // Force login to check credentials. + logger.info("Exchange connection with {} driver.", exchangeParameters.getDriverClassName()); xChangeAccountService.getAccountInfo(); - logger.info("Exchange connection with username {} successful (Dry mode : {} / Sandbox : {})", + logger.info("Exchange connection with username {} successful (Dry mode: {} / Sandbox: {}.)", exchangeParameters.getUsername(), exchangeParameters.getModes().getDry(), exchangeParameters.getModes().getSandbox()); // Prints all the supported currency pairs. - logger.info("Supported currency pairs by the exchange : {}.", exchangeService.getAvailableCurrencyPairs() + logger.info("Supported currency pairs by the exchange: {}.", exchangeService.getAvailableCurrencyPairs() .stream() .map(CurrencyPairDTO::toString) .collect(Collectors.joining(", "))); } catch (ClassNotFoundException e) { // If we can't find the exchange class. - throw new ConfigurationException("Impossible to find the exchange you requested : " + exchangeParameters.getDriverClassName(), + throw new ConfigurationException("Impossible to find the exchange you requested: " + exchangeParameters.getDriverClassName(), "Choose a valid exchange (https://github.com/knowm/XChange) and add the dependency to Cassandre"); } catch (HttpStatusIOException e) { if (e.getHttpStatusCode() == UNAUTHORIZED_STATUS_CODE) { @@ -193,15 +195,16 @@ public void configure() { "Check your exchange credentials : " + e.getMessage() + " - login used : " + exchangeParameters.getUsername()); } else { // Another HTTP failure. - throw new ConfigurationException("Error while connecting to the exchange : " + e.getMessage()); + throw new ConfigurationException("Error while connecting to the exchange: " + e.getMessage()); } } catch (Exception e) { - throw new ConfigurationException("Unknown configuration error : " + e.getMessage()); + throw new ConfigurationException("Unknown configuration error: " + e.getMessage()); } } /** * Returns the XChange class based on the exchange name. + * This is used in case the full driver class with package is not given in the parameters. * * @return XChange class name */ @@ -215,7 +218,7 @@ private String getExchangeClassName() { final String xChangeClassPackage = "org.knowm.xchange."; final String xChangeCLassSuffix = "Exchange"; - // Returns the XChange package name. + // Returns the XChange package name from exchange name. assert exchangeParameters.getDriverClassName() != null; return xChangeClassPackage // Package (org.knowm.xchange.). .concat(exchangeParameters.getDriverClassName().toLowerCase()) // domain (kucoin). diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/configuration/ScheduleAutoConfiguration.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/configuration/ScheduleAutoConfiguration.java index b4e8958f2..e6e13e048 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/configuration/ScheduleAutoConfiguration.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/configuration/ScheduleAutoConfiguration.java @@ -29,14 +29,14 @@ public class ScheduleAutoConfiguration extends BaseConfiguration { /** Scheduler pool size. */ private static final int SCHEDULER_POOL_SIZE = 3; - /** Initial delay before starting threads in milliseconds. */ - private static final int AWAIT_START_IN_MILLISECONDS = 1_000; + /** Start delay in milliseconds. */ + private static final int START_DELAY_IN_MILLISECONDS = 1_000; - /** Await termination delay in milliseconds. */ - private static final int AWAIT_TERMINATION_IN_MILLISECONDS = 30_000; + /** Termination delay in milliseconds. */ + private static final int TERMINATION_DELAY_IN_MILLISECONDS = 10_000; /** Thread prefix for schedulers. */ - private static final String THREAD_NAME_PREFIX = "Cassandre-flux-"; + private static final String THREAD_NAME_PREFIX = "cassandre-flux-"; /** Flux continues to run as long as enabled is set to true. */ private final AtomicBoolean enabled = new AtomicBoolean(true); @@ -61,24 +61,24 @@ public class ScheduleAutoConfiguration extends BaseConfiguration { @Bean public TaskScheduler taskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); - scheduler.setThreadNamePrefix(THREAD_NAME_PREFIX); - scheduler.setAwaitTerminationMillis(AWAIT_TERMINATION_IN_MILLISECONDS); scheduler.setWaitForTasksToCompleteOnShutdown(true); + scheduler.setAwaitTerminationMillis(TERMINATION_DELAY_IN_MILLISECONDS); + scheduler.setThreadNamePrefix(THREAD_NAME_PREFIX); scheduler.setPoolSize(SCHEDULER_POOL_SIZE); scheduler.setErrorHandler(t -> { try { - logger.error("ScheduleAutoConfiguration - Error in scheduled tasks: {}", t.getMessage()); + logger.error("Error in scheduled tasks: {}", t.getMessage()); } catch (Exception e) { - logger.error("ScheduleAutoConfiguration - Error in scheduled tasks: {}", e.getMessage()); + logger.error("Error in scheduled tasks: {}", e.getMessage()); } }); return scheduler; } /** - * Recurrent calls the account flux. + * Recurrent calls to the account flux. */ - @Scheduled(initialDelay = AWAIT_START_IN_MILLISECONDS, fixedDelay = 1) + @Scheduled(initialDelay = START_DELAY_IN_MILLISECONDS, fixedDelay = 1) public void accountFluxUpdate() { if (enabled.get()) { accountFlux.update(); @@ -86,9 +86,9 @@ public void accountFluxUpdate() { } /** - * Recurrent calls the ticker flux. + * Recurrent calls to the ticker flux. */ - @Scheduled(initialDelay = AWAIT_START_IN_MILLISECONDS, fixedDelay = 1) + @Scheduled(initialDelay = START_DELAY_IN_MILLISECONDS, fixedDelay = 1) public void tickerFluxUpdate() { if (enabled.get()) { tickerFlux.update(); @@ -96,10 +96,10 @@ public void tickerFluxUpdate() { } /** - * Recurrent calls the order/trade flux. + * Recurrent calls to the order and trade flux. */ - @Scheduled(initialDelay = AWAIT_START_IN_MILLISECONDS, fixedDelay = 1) - public void tradeFluxUpdate() { + @Scheduled(initialDelay = START_DELAY_IN_MILLISECONDS, fixedDelay = 1) + public void orderAndTradeFluxUpdate() { if (enabled.get()) { orderFlux.update(); tradeFlux.update(); diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/configuration/StrategiesAutoConfiguration.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/configuration/StrategiesAutoConfiguration.java index 34924705a..4519bef47 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/configuration/StrategiesAutoConfiguration.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/configuration/StrategiesAutoConfiguration.java @@ -45,6 +45,7 @@ import java.util.Set; import java.util.stream.Collectors; +import static java.math.BigDecimal.ZERO; import static tech.cassandre.trading.bot.dto.position.PositionStatusDTO.CLOSING; import static tech.cassandre.trading.bot.dto.position.PositionStatusDTO.OPENING; import static tech.cassandre.trading.bot.dto.strategy.StrategyTypeDTO.BASIC_STRATEGY; @@ -60,15 +61,30 @@ public class StrategiesAutoConfiguration extends BaseConfiguration { /** Application context. */ private final ApplicationContext applicationContext; + /** Strategy repository. */ + private final StrategyRepository strategyRepository; + + /** Order repository. */ + private final OrderRepository orderRepository; + + /** Trade repository. */ + private final TradeRepository tradeRepository; + + /** Position repository. */ + private final PositionRepository positionRepository; + + /** Exchange service. */ + private final ExchangeService exchangeService; + + /** User service. */ + private final UserService userService; + /** Trade service. */ private final TradeService tradeService; /** Position service. */ private PositionService positionService; - /** User service. */ - private final UserService userService; - /** Account flux. */ private final AccountFlux accountFlux; @@ -78,21 +94,6 @@ public class StrategiesAutoConfiguration extends BaseConfiguration { /** Order flux. */ private final OrderFlux orderFlux; - /** Exchange service. */ - private final ExchangeService exchangeService; - - /** Strategy repository. */ - private final StrategyRepository strategyRepository; - - /** Order repository. */ - private final OrderRepository orderRepository; - - /** Trade repository. */ - private final TradeRepository tradeRepository; - - /** Position repository. */ - private final PositionRepository positionRepository; - /** Trade flux. */ private final TradeFlux tradeFlux; @@ -114,45 +115,52 @@ public void configure() { // Retrieve accounts information. final Optional user = userService.getUser(); if (user.isEmpty()) { - throw new ConfigurationException("Impossible to retrieve your user information.", - "Impossible to retrieve your user information. Check logs"); + throw new ConfigurationException("Impossible to retrieve your user information", + "Impossible to retrieve your user information - Check logs."); } else { - logger.info("Accounts available on the exchange:"); + logger.info("Available accounts on the exchange:"); user.get() .getAccounts() .values() - .forEach(account -> logger.info("- Account id / Account name: {} / {}.", + .forEach(account -> { + logger.info("- Account id / name: {} / {}.", account.getAccountId(), - account.getName())); + account.getName()); + account.getBalances() + .values() + .stream() + .filter(balance -> balance.getAvailable().compareTo(ZERO) != 0) + .forEach(balance -> logger.info(" - {} {}.", balance.getAvailable(), balance.getCurrency())); + }); } // Check that there is at least one strategy. if (strategies.isEmpty()) { - throw new ConfigurationException("No strategy found", "You must have one class with @CassandreStrategy."); + throw new ConfigurationException("No strategy found", "You must have one class with @CassandreStrategy annotation."); } // Check that all strategies extends CassandreStrategyInterface. - Set strategiesWithErrors = strategies.values() + Set strategiesWithoutExtends = strategies.values() .stream() .filter(strategy -> !(strategy instanceof CassandreStrategyInterface)) .map(strategy -> strategy.getClass().getSimpleName()) .collect(Collectors.toSet()); - if (!strategiesWithErrors.isEmpty()) { - final String list = String.join(",", strategiesWithErrors); - throw new ConfigurationException(list + " doesn't extend BasicCassandreStrategy or BasicTa4jCassandreStrategy.", + if (!strategiesWithoutExtends.isEmpty()) { + final String list = String.join(",", strategiesWithoutExtends); + throw new ConfigurationException(list + " doesn't extend BasicCassandreStrategy or BasicTa4jCassandreStrategy", list + " must extend BasicCassandreStrategy or BasicTa4jCassandreStrategy"); } // Check that all strategies specifies an existing trade account. final Set accountsAvailableOnExchange = new HashSet<>(user.get().getAccounts().values()); - strategiesWithErrors = strategies.values() + Set strategiesWithoutTradeAccount = strategies.values() .stream() .filter(strategy -> ((CassandreStrategyInterface) strategy).getTradeAccount(accountsAvailableOnExchange).isEmpty()) .map(strategy -> strategy.getClass().toString()) .collect(Collectors.toSet()); - if (!strategiesWithErrors.isEmpty()) { - final String strategyList = String.join(",", strategiesWithErrors); - throw new ConfigurationException("Your strategies specifies a trading account that doesn't exist.", + if (!strategiesWithoutTradeAccount.isEmpty()) { + final String strategyList = String.join(",", strategiesWithoutTradeAccount); + throw new ConfigurationException("Your strategies specifies a trading account that doesn't exist", "Check your getTradeAccount(Set accounts) method as it returns an empty result - Strategies in error : " + strategyList); } @@ -171,6 +179,22 @@ public void configure() { "You have duplicated strategy ids: " + String.join(", ", duplicatedStrategyIds)); } + // Check that the currency pairs required by the strategies are available on the exchange. + final Set availableCurrencyPairs = exchangeService.getAvailableCurrencyPairs(); + final Set notAvailableCurrencyPairs = applicationContext + .getBeansWithAnnotation(CassandreStrategy.class) + .values() + .stream() + .map(o -> (CassandreStrategyInterface) o) + .map(CassandreStrategyInterface::getRequestedCurrencyPairs) + .flatMap(Set::stream) + .filter(currencyPairDTO -> !availableCurrencyPairs.contains(currencyPairDTO)) + .map(CurrencyPairDTO::toString) + .collect(Collectors.toSet()); + if (!notAvailableCurrencyPairs.isEmpty()) { + logger.warn("Your exchange doesn't support the following currency pairs you requested: {}.", String.join(", ", notAvailableCurrencyPairs)); + } + // ============================================================================================================= // Creating position service. this.positionService = new PositionServiceCassandreImplementation(applicationContext, positionRepository, tradeService, positionFlux); @@ -184,7 +208,7 @@ public void configure() { final ConnectableFlux> connectableTradeFlux = tradeFlux.getFlux().publish(); // ============================================================================================================= - // Connecting flux to positions that requires them. + // Connecting flux to position service. connectableOrderFlux.subscribe(positionService::ordersUpdates); connectableTradeFlux.subscribe(positionService::tradesUpdates); @@ -193,7 +217,7 @@ public void configure() { logger.info("Running the following strategies:"); strategies.values() .forEach(s -> { - CassandreStrategyInterface strategy = ((CassandreStrategyInterface) s); + CassandreStrategyInterface strategy = (CassandreStrategyInterface) s; CassandreStrategy annotation = s.getClass().getAnnotation(CassandreStrategy.class); // Displaying information about strategy. @@ -204,16 +228,15 @@ public void configure() { .map(CurrencyPairDTO::toString) .collect(Collectors.joining(", "))); - // Saving strategy in database. - final Optional strategyInDatabase = strategyRepository.findByStrategyId(annotation.strategyId()); - strategyInDatabase.ifPresentOrElse(existingStrategy -> { + // Saving or updating strategy in database. + strategyRepository.findByStrategyId(annotation.strategyId()).ifPresentOrElse(existingStrategy -> { // Update. existingStrategy.setName(annotation.strategyName()); strategyRepository.save(existingStrategy); final StrategyDTO strategyDTO = strategyMapper.mapToStrategyDTO(existingStrategy); strategyDTO.initializeLastPositionIdUsed(positionRepository.getLastPositionIdUsedByStrategy(strategyDTO.getId())); strategy.setStrategy(strategyDTO); - logger.debug("StrategyConfiguration - Strategy updated in database: {}", existingStrategy); + logger.debug("Strategy updated in database: {}", existingStrategy); }, () -> { // Creation. Strategy newStrategy = new Strategy(); @@ -226,7 +249,7 @@ public void configure() { if (strategy instanceof BasicTa4jCassandreStrategy) { newStrategy.setType(BASIC_TA4J_STRATEGY); } - logger.debug("StrategyConfiguration - Strategy saved in database: {}", newStrategy); + logger.debug("Strategy created in database: {}", newStrategy); StrategyDTO strategyDTO = strategyMapper.mapToStrategyDTO(strategyRepository.save(newStrategy)); strategyDTO.initializeLastPositionIdUsed(positionRepository.getLastPositionIdUsedByStrategy(strategyDTO.getId())); strategy.setStrategy(strategyDTO); @@ -235,7 +258,7 @@ public void configure() { // Initialize accounts values in strategy. strategy.initializeAccounts(user.get().getAccounts()); - // Setting services & repositories. + // Setting services & repositories to strategy. strategy.setOrderRepository(orderRepository); strategy.setTradeRepository(tradeRepository); strategy.setExchangeService(exchangeService); @@ -243,7 +266,7 @@ public void configure() { strategy.setPositionService(positionService); strategy.setPositionRepository(positionRepository); - // Setting flux. + // Connecting flux to strategy. connectableAccountFlux.subscribe(strategy::accountsUpdates); connectablePositionFlux.subscribe(strategy::positionsUpdates); connectableOrderFlux.subscribe(strategy::ordersUpdates); diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/domain/Strategy.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/domain/Strategy.java index 10d3f6aaf..385b5039e 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/domain/Strategy.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/domain/Strategy.java @@ -34,7 +34,7 @@ public class Strategy extends BaseDomain { @Column(name = "STRATEGY_ID") private String strategyId; - /** Strategy type - Basic or ta4j. */ + /** Strategy type - Basic or Ta4j. */ @Enumerated(STRING) @Column(name = "TYPE") private StrategyTypeDTO type; @@ -63,7 +63,6 @@ public final boolean equals(final Object o) { public final int hashCode() { return new HashCodeBuilder() .append(id) - .append(strategyId) .toHashCode(); } diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/position/PositionCreationResultDTO.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/position/PositionCreationResultDTO.java index a474c1b03..8ee90fa9a 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/position/PositionCreationResultDTO.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/position/PositionCreationResultDTO.java @@ -18,16 +18,12 @@ public class PositionCreationResultDTO { /** Exception (filled if position creation failed). */ Exception exception; - /** Indicates if the position creation was successful or not. */ - boolean successful; - /** * Constructor for successful position creation. * * @param newPosition position */ public PositionCreationResultDTO(final PositionDTO newPosition) { - successful = true; this.position = newPosition; this.errorMessage = null; this.exception = null; @@ -40,10 +36,18 @@ public PositionCreationResultDTO(final PositionDTO newPosition) { * @param newException exception */ public PositionCreationResultDTO(final String newErrorMessage, final Exception newException) { - successful = false; this.position = null; this.errorMessage = newErrorMessage; this.exception = newException; } + /** + * Getter successful. + * + * @return successful + */ + public final boolean isSuccessful() { + return position != null; + } + } diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/position/PositionDTO.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/position/PositionDTO.java index 4de82af7d..14e327bad 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/position/PositionDTO.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/position/PositionDTO.java @@ -4,6 +4,7 @@ import lombok.Builder; import lombok.Getter; import lombok.ToString; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.HashCodeBuilder; import tech.cassandre.trading.bot.dto.market.TickerDTO; import tech.cassandre.trading.bot.dto.strategy.StrategyDTO; @@ -50,7 +51,7 @@ public class PositionDTO { private final long id; /** An identifier that uniquely identifies the position. */ - private final Long positionId; + private final long positionId; /** Position type (Long or Short). */ private final PositionTypeDTO type; @@ -86,7 +87,7 @@ public class PositionDTO { private CurrencyAmountDTO latestGainPrice; /** 100%. */ - private static final int ONE_HUNDRED_FLOAT = 100; + private static final int ONE_HUNDRED_INTEGER = 100; /** 100%. */ private static final BigDecimal ONE_HUNDRED_BIG_DECIMAL = new BigDecimal("100"); @@ -133,35 +134,33 @@ public PositionDTO(final long newId, */ @ToString.Include(name = "status") public final PositionStatusDTO getStatus() { - // No closing order. if (closingOrder == null) { - // Error. + // No closing order. + + // An error occurred with the order. if (openingOrder.getStatus().isInError()) { return OPENING_FAILURE; } - + // Checking if fulfilled or not. if (openingOrder.isFulfilled()) { return OPENED; + } else { + return OPENING; } - } + } else { + // Closing order present. - // Closing order present - if (closingOrder != null) { - // Error. + // An error occurred with the order. if (closingOrder.getStatus().isInError()) { return CLOSING_FAILURE; } - // Checking if fulfilled or not. - if (!closingOrder.isFulfilled()) { - return CLOSING; - } else { + if (closingOrder.isFulfilled()) { return CLOSED; + } else { + return CLOSING; } } - - // If non others status is set, it means we are just starting so it's opening. - return OPENING; } /** @@ -171,8 +170,8 @@ public final PositionStatusDTO getStatus() { * @return gain */ public Optional calculateGainFromPrice(final BigDecimal price) { - if (price != null) { - // How gain calculation works for long positions ? + if (price != null && ZERO.compareTo(price) != 0) { + // How gain calculation works for a long positions: // - Bought 10 ETH with a price of 5 -> Amount of 50 USDT. // - Sold 10 ETH with a price of 6 -> Amount of 60 USDT. // Gain value: 10 USDT @@ -210,7 +209,7 @@ public Optional calculateGainFromPrice(final BigDecimal price) { .build()); } - // How gain calculation works for short positions ? + // How gain calculation works for a short positions: // - Sold 10 ETH with a price of 5 USDT -> I now have 50 USDT. // - Bought 5 ETH with my 50 USDT as the price raised to 10 USDT. // Gain = ((5 - 10) / 10) * 100 = -50 % (I calculate evolution backward, from bought price to sold price). @@ -256,16 +255,18 @@ public Optional calculateGainFromPrice(final BigDecimal price) { } /** - * Method called by on every order update. + * Method called by Cassandre on every order update. * * @param updatedOrder order * @return true if the the order updated the position. */ public final boolean orderUpdate(final OrderDTO updatedOrder) { + // Check if it's for the opening order. if (openingOrder.getOrderId().equals(updatedOrder.getOrderId())) { this.openingOrder = updatedOrder; return true; } + // Check if it's for the closing order. if (closingOrder != null && closingOrder.getOrderId().equals(updatedOrder.getOrderId())) { this.closingOrder = updatedOrder; return true; @@ -274,19 +275,19 @@ public final boolean orderUpdate(final OrderDTO updatedOrder) { } /** - * Method called by on every trade update. + * Method called by Cassandre on every trade update. * * @param trade trade * @return true if the the trade updated the position. */ public boolean tradeUpdate(final TradeDTO trade) { - // Return true signaling there is an update if this trade was for this position. + // Return true to indicate that the trade was for this position. return trade.getOrderId().equals(openingOrder.getOrderId()) || (closingOrder != null && trade.getOrderId().equals(closingOrder.getOrderId())); } /** - * Method called by on every ticker update. + * Method called by Cassandre on every ticker update. * * @param ticker ticker * @return true if the the ticker updated the position. @@ -295,36 +296,34 @@ public final boolean tickerUpdate(final TickerDTO ticker) { // If the position is not closing and the ticker is the one expected. if (getStatus() == OPENED && ticker.getCurrencyPair().equals(currencyPair)) { - // We retrieve the gains. - final Optional calculatedGain = calculateGainFromPrice(ticker.getLast()); - final Optional lowestCalculatedGain = getLowestCalculatedGain(); - final Optional highestCalculatedGain = getHighestCalculatedGain(); + // We calculate the gain and we update fields price fields. + // LastGain for sure. + // Lowest and highest if it changes. + calculateGainFromPrice(ticker.getLast()).ifPresent(gain -> { - // We set the new values. - calculatedGain.ifPresent(gain -> { - final CurrencyAmountDTO price = CurrencyAmountDTO.builder() + // We update the last calculated gain. + latestGainPrice = CurrencyAmountDTO.builder() .value(ticker.getLast()) .currency(ticker.getQuoteCurrency()) .build(); - // We save the last calculated gain. - latestGainPrice = price; - // If we don't close now, we update lowest and latest. if (!shouldBeClosed()) { // If we don't have a lowest gain or if it's a lowest gain. - if (lowestCalculatedGain.isEmpty() || calculatedGain.get().isInferiorTo(lowestCalculatedGain.get())) { - lowestGainPrice = price; + final Optional lowestCalculatedGain = getLowestCalculatedGain(); + if (lowestCalculatedGain.isEmpty() || gain.isInferiorTo(lowestCalculatedGain.get())) { + lowestGainPrice = latestGainPrice; } // If we don't have a highest gain or if it's a highest gain. - if (highestCalculatedGain.isEmpty() || calculatedGain.get().isSuperiorTo(highestCalculatedGain.get())) { - highestGainPrice = price; + final Optional highestCalculatedGain = getHighestCalculatedGain(); + if (highestCalculatedGain.isEmpty() || gain.isSuperiorTo(highestCalculatedGain.get())) { + highestGainPrice = latestGainPrice; } } }); return true; } else { - // Not a ticker for this position. + // Not a ticker for this position or the position is no more opened. return false; } } @@ -405,7 +404,7 @@ public boolean shouldBeClosed() { public final void closePositionWithOrder(final OrderDTO newCloseOrder) { // This method should only be called when in status OPENED. if (getStatus() != OPENED) { - throw new PositionException("Impossible to close position " + id + " because of its status"); + throw new PositionException("Impossible to close position " + id + " because of its status " + getStatus()); } closingOrder = newCloseOrder; } @@ -490,11 +489,12 @@ public GainDTO getGain() { // Calculate fees. BigDecimal fees = Stream.concat(openingOrder.getTrades().stream(), closingOrder.getTrades().stream()) + .filter(tradeDTO -> tradeDTO.getFee() != null) .map(TradeDTO::getFeeValue) .reduce(ZERO, BigDecimal::add); CurrencyDTO feeCurrency; final Optional firstTrade = Stream.concat(openingOrder.getTrades().stream(), closingOrder.getTrades().stream()).findFirst(); - if (firstTrade.isPresent()) { + if (firstTrade.isPresent() && firstTrade.get().getFee() != null) { feeCurrency = firstTrade.get().getFee().getCurrency(); } else { feeCurrency = currencyPair.getQuoteCurrency(); @@ -531,11 +531,12 @@ public GainDTO getGain() { // Calculate fees. BigDecimal fees = Stream.concat(openingOrder.getTrades().stream(), closingOrder.getTrades().stream()) + .filter(tradeDTO -> tradeDTO.getFee() != null) .map(TradeDTO::getFeeValue) .reduce(ZERO, BigDecimal::add); CurrencyDTO feeCurrency; final Optional firstTrade = Stream.concat(openingOrder.getTrades().stream(), closingOrder.getTrades().stream()).findFirst(); - if (firstTrade.isPresent()) { + if (firstTrade.isPresent() && firstTrade.get().getFee() != null) { feeCurrency = firstTrade.get().getFee().getCurrency(); } else { feeCurrency = currencyPair.getQuoteCurrency(); @@ -555,6 +556,7 @@ public GainDTO getGain() { .build(); } } + // If the position is not closed: we gain zero. return GainDTO.ZERO; } @@ -566,8 +568,9 @@ public GainDTO getGain() { @SuppressWarnings("unused") public final String getDescription() { try { - String value = type.toString().toLowerCase(Locale.ROOT) + " position n°" + positionId + " (rules : "; + String value = StringUtils.capitalize(type.toString().toLowerCase(Locale.ROOT)) + " position n°" + positionId; // Rules. + value += " (rules : "; if (!rules.isStopGainPercentageSet() && !rules.isStopLossPercentageSet()) { value += "no rules"; } @@ -612,7 +615,7 @@ public final String getDescription() { } return value; } catch (Exception e) { - return "Position " + getId() + " (error in description generation)"; + return "Position " + getId() + " (error in getDescription() method)"; } } diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/position/PositionRulesDTO.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/position/PositionRulesDTO.java index 1bfc894cd..e95e73415 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/position/PositionRulesDTO.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/position/PositionRulesDTO.java @@ -8,10 +8,10 @@ /** * Position rules for {@link PositionDTO}. - * It is used to know if cassandre should close a position. + * It is used to know when cassandre should close a position. * Supported rules : - * - Stop gain with percentage. - * - Stop loss with percentage. + * - Stop gain in percentage. + * - Stop loss in percentage. */ @Getter public class PositionRulesDTO { diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/strategy/StrategyDTO.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/strategy/StrategyDTO.java index 7a4e99e75..bd3f724c7 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/strategy/StrategyDTO.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/strategy/StrategyDTO.java @@ -33,7 +33,7 @@ public class StrategyDTO { String name; /** Last position id used. */ - @ToString.Include + @ToString.Exclude AtomicLong lastPositionIdUsed = new AtomicLong(); /** @@ -74,7 +74,6 @@ public final boolean equals(final Object o) { public final int hashCode() { return new HashCodeBuilder() .append(id) - .append(strategyId) .toHashCode(); } diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/trade/OrderCreationResultDTO.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/trade/OrderCreationResultDTO.java index 2d9882aaf..15730f7c2 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/trade/OrderCreationResultDTO.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/trade/OrderCreationResultDTO.java @@ -17,16 +17,12 @@ public final class OrderCreationResultDTO { /** Exception (filled if order creation failed). */ private final Exception exception; - /** Indicates if the position creation was successful or not. */ - private final boolean successful; - /** * Constructor for successful order creation. * * @param newOrder order */ public OrderCreationResultDTO(final OrderDTO newOrder) { - successful = true; this.order = newOrder; this.errorMessage = null; this.exception = null; @@ -39,11 +35,19 @@ public OrderCreationResultDTO(final OrderDTO newOrder) { * @param newException exception */ public OrderCreationResultDTO(final String newErrorMessage, final Exception newException) { - successful = false; this.errorMessage = newErrorMessage; this.exception = newException; } + /** + * Getter successful. + * + * @return successful + */ + public boolean isSuccessful() { + return order != null; + } + /** * Getter orderId. * @@ -59,7 +63,7 @@ public String getOrderId() { @Override public String toString() { - if (successful) { + if (isSuccessful()) { return "OrderCreationResultDTO{" + " order='" + order + '\'' + '}'; diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/trade/OrderDTO.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/trade/OrderDTO.java index e26f254fa..422dcc32c 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/trade/OrderDTO.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/trade/OrderDTO.java @@ -168,11 +168,11 @@ public BigDecimal getCumulativeAmountValue() { * @return true if order completed */ public final boolean isFulfilled() { - final BigDecimal tradesAmount = getTrades() + return getTrades() .stream() .map(TradeDTO::getAmountValue) - .reduce(ZERO, BigDecimal::add); - return getAmountValue().compareTo(tradesAmount) == 0; + .reduce(ZERO, BigDecimal::add) + .compareTo(getAmountValue()) == 0; } @Override diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/user/AccountDTO.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/user/AccountDTO.java index 452219a69..6b3aae18f 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/user/AccountDTO.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/user/AccountDTO.java @@ -102,7 +102,6 @@ public final boolean equals(final Object o) { public final int hashCode() { return new HashCodeBuilder() .append(accountId) - .append(name) .toHashCode(); } diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/user/BalanceDTO.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/user/BalanceDTO.java index 77da3249b..589aaacae 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/user/BalanceDTO.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/user/BalanceDTO.java @@ -23,25 +23,25 @@ public class BalanceDTO { /** Currency. */ CurrencyDTO currency; - /** Returns the total amount of the CurrencyDTO in this balance. */ + /** Returns the total amount of the {@link CurrencyDTO} in this balance. */ BigDecimal total; - /** Returns the amount of the CurrencyDTO in this balance that is available to trade. */ + /** Returns the amount of the {@link CurrencyDTO} in this balance that is available to trade. */ BigDecimal available; - /** Returns the frozen amount of the CurrencyDTO in this balance that is locked in trading. */ + /** Returns the frozen amount of the {@link CurrencyDTO} in this balance that is locked in trading. */ BigDecimal frozen; - /** Returns the loaned amount of the total CurrencyDTO in this balance that will be returned. */ + /** Returns the loaned amount of the total {@link CurrencyDTO} in this balance that will be returned. */ BigDecimal loaned; - /** Returns the borrowed amount of the available CurrencyDTO in this balance that must be repaid. */ + /** Returns the borrowed amount of the available {@link CurrencyDTO} in this balance that must be repaid. */ BigDecimal borrowed; - /** Returns the amount of the CurrencyDTO in this balance that is locked in withdrawal. */ + /** Returns the amount of the {@link CurrencyDTO} in this balance that is locked in withdrawal. */ BigDecimal withdrawing; - /** Returns the amount of the CurrencyDTO in this balance that is locked in the deposit. */ + /** Returns the amount of the {@link CurrencyDTO} in this balance that is locked in the deposit. */ BigDecimal depositing; @Override diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/util/CurrencyDTO.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/util/CurrencyDTO.java index a6de355cb..790df8b9b 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/util/CurrencyDTO.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/dto/util/CurrencyDTO.java @@ -666,6 +666,9 @@ public final class CurrencyDTO implements Serializable { /** Tether USD Anchor. */ public static final CurrencyDTO USDT = createCurrency("USDT", "Tether USD Anchor", null); + /** USD Coin. */ + public static final CurrencyDTO USDC = createCurrency("USDC", "USD Coin", null); + /** Unitary Status Dollar eCoin. */ public static final CurrencyDTO USDE = createCurrency("USDE", "Unitary Status Dollar eCoin", null); diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/repository/OrderRepository.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/repository/OrderRepository.java index 4b0645a06..81fe11431 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/repository/OrderRepository.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/repository/OrderRepository.java @@ -28,7 +28,7 @@ public interface OrderRepository extends JpaRepository { Optional findByOrderId(String orderId); /** - * Find orders by its status. + * Find orders with a specific status. * * @param orderStatusDTO order status * @return orders @@ -36,7 +36,7 @@ public interface OrderRepository extends JpaRepository { List findByStatus(OrderStatusDTO orderStatusDTO); /** - * Find orders by its status. + * Find orders without a specific status. * * @param orderStatusDTO order status * @return orders @@ -44,7 +44,7 @@ public interface OrderRepository extends JpaRepository { List findByStatusNot(OrderStatusDTO orderStatusDTO); /** - * Retrieve all orders by its timestamp. + * Retrieve all orders (sorted by timestamp). * * @return orders */ @@ -52,7 +52,7 @@ public interface OrderRepository extends JpaRepository { /** * Update order amount. - * (WARNING: Only used by dry mode, please do not use). + * WARNING: Only used by the dry mode, please do not use. * * @param id order id * @param value new amount diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/repository/PositionRepository.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/repository/PositionRepository.java index 24e98c0b7..23326ba86 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/repository/PositionRepository.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/repository/PositionRepository.java @@ -35,7 +35,7 @@ public interface PositionRepository extends JpaRepository { List findByOrderById(); /** - * Retrieve all positions by its status. + * Find positions with a specific status. * * @param status status * @return positions @@ -43,7 +43,7 @@ public interface PositionRepository extends JpaRepository { List findByStatus(PositionStatusDTO status); /** - * Retrieve all positions not having a specific status. + * Find positions without a specific status. * * @param status status * @return positions @@ -51,7 +51,7 @@ public interface PositionRepository extends JpaRepository { List findByStatusNot(PositionStatusDTO status); /** - * Retrieve all positions with specific status. + * Find positions with any of specific status. * * @param status list of status * @return positions @@ -59,7 +59,7 @@ public interface PositionRepository extends JpaRepository { List findByStatusIn(Set status); /** - * Returns the last position id for a strategy. + * Returns the last position id used by a strategy. * * @param strategyId strategy id * @return positions @@ -93,7 +93,7 @@ public interface PositionRepository extends JpaRepository { * Update force closing. * * @param id position id - * @param value new value + * @param value true to force closing */ @Transactional @Modifying diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/repository/TradeRepository.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/repository/TradeRepository.java index 3b662472a..79abb9e77 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/repository/TradeRepository.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/repository/TradeRepository.java @@ -22,7 +22,7 @@ public interface TradeRepository extends JpaRepository { Optional findByTradeId(String tradeId); /** - * Retrieve all trades order by its timestamp. + * Retrieve all trades (sorted by timestamp). * * @return trades */ diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/ExchangeServiceXChangeImplementation.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/ExchangeServiceXChangeImplementation.java index 9da9d1d00..dd9dd5146 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/ExchangeServiceXChangeImplementation.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/ExchangeServiceXChangeImplementation.java @@ -21,12 +21,12 @@ public class ExchangeServiceXChangeImplementation extends BaseService implements @Override @SuppressWarnings("checkstyle:DesignForExtension") public Set getAvailableCurrencyPairs() { - logger.debug("ExchangeService - Retrieving available currency pairs"); + logger.debug("Retrieving available currency pairs"); return exchange.getExchangeMetaData() .getCurrencyPairs() .keySet() .stream() - .peek(cp -> logger.debug("ExchangeService - {} available", cp)) + .peek(cp -> logger.debug(" - {} available", cp)) .map(currencyMapper::mapToCurrencyPairDTO) .collect(Collectors.toCollection(LinkedHashSet::new)); } diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/MarketServiceXChangeImplementation.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/MarketServiceXChangeImplementation.java index 5417c6023..25cd2bd2a 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/MarketServiceXChangeImplementation.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/MarketServiceXChangeImplementation.java @@ -43,15 +43,14 @@ public Optional getTicker(final CurrencyPairDTO currencyPair) { // If a token is not available this method will block until the refill adds one to the bucket. bucket.asScheduler().consume(1); - logger.debug("MarketService - Getting ticker for {}", currencyPair); + logger.debug("Getting ticker for {} currency pair", currencyPair); TickerDTO t = tickerMapper.mapToTickerDTO(marketDataService.getTicker(currencyMapper.mapToCurrencyPair(currencyPair))); - logger.debug("MarketService - Retrieved value is: {}", t); + logger.debug(" - New ticker {}", t); return Optional.ofNullable(t); } catch (IOException e) { - logger.error("MarketService - Error retrieving ticker for {}: {}", currencyPair, e.getMessage()); + logger.error("Error retrieving ticker: {}", e.getMessage()); return Optional.empty(); } catch (InterruptedException e) { - logger.error("MarketService - InterruptedException {}: {}", currencyPair, e.getMessage()); return Optional.empty(); } } @@ -70,17 +69,16 @@ public Set getTickers(final Set currencyPairs) { // If a token is not available this method will block until the refill adds one to the bucket. bucket.asScheduler().consume(1); - logger.debug("MarketService - Getting tickers for {} currency pairs", currencyPairs.size()); + logger.debug("Getting tickers for {} currency pairs", currencyPairs.size()); final List tickers = marketDataService.getTickers(params); return tickers.stream() .map(tickerMapper::mapToTickerDTO) - .peek(t -> logger.debug("MarketService - Retrieved value: {}", t)) + .peek(t -> logger.debug(" - New ticker: {}", t)) .collect(Collectors.toCollection(LinkedHashSet::new)); } catch (IOException e) { - logger.error("MarketService - Error retrieving tickers: {}", e.getMessage()); + logger.error("Error retrieving tickers: {}", e.getMessage()); return Collections.emptySet(); } catch (InterruptedException e) { - logger.error("MarketService - InterruptedException: {}", e.getMessage()); return Collections.emptySet(); } } diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/PositionService.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/PositionService.java index 534a91243..a3bdeea48 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/PositionService.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/PositionService.java @@ -18,7 +18,7 @@ import java.util.Set; /** - * Service allowing you to manage positions. + * Service allowing you to manage your positions. */ public interface PositionService { @@ -106,11 +106,11 @@ PositionCreationResultDTO createShortPosition(GenericCassandreStrategy strategy, void tickersUpdates(Set tickers); /** - * Returns the amounts locked by every position. + * Returns the amounts locked by each position. * - * @return amounts locked by every position + * @return amounts locked by each position */ - Map amountsLockedByPosition(); + Map getAmountsLockedByPosition(); /** * Return the gains made by all closed positions. diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/PositionServiceCassandreImplementation.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/PositionServiceCassandreImplementation.java index d69620c44..d4c947986 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/PositionServiceCassandreImplementation.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/PositionServiceCassandreImplementation.java @@ -8,7 +8,6 @@ import tech.cassandre.trading.bot.dto.position.PositionCreationResultDTO; import tech.cassandre.trading.bot.dto.position.PositionDTO; import tech.cassandre.trading.bot.dto.position.PositionRulesDTO; -import tech.cassandre.trading.bot.dto.position.PositionStatusDTO; import tech.cassandre.trading.bot.dto.position.PositionTypeDTO; import tech.cassandre.trading.bot.dto.trade.OrderCreationResultDTO; import tech.cassandre.trading.bot.dto.trade.OrderDTO; @@ -23,6 +22,7 @@ import tech.cassandre.trading.bot.util.base.service.BaseService; import java.math.BigDecimal; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; @@ -31,6 +31,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -91,7 +92,7 @@ public final PositionCreationResultDTO createPosition(final GenericCassandreStra final CurrencyPairDTO currencyPair, final BigDecimal amount, final PositionRulesDTO rules) { - logger.debug("PositionService - Creating a {} position for {} on {} with the rules : {}", + logger.debug("Creating a {} position for {} on {} with the rules : {}", type.toString().toLowerCase(Locale.ROOT), amount, currencyPair, @@ -120,7 +121,7 @@ public final PositionCreationResultDTO createPosition(final GenericCassandreStra // Creates the position dto. PositionDTO p = new PositionDTO(position.getId(), type, strategy.getStrategyDTO(), currencyPair, amount, orderCreationResult.getOrder(), rules); positionRepository.save(positionMapper.mapToPosition(p)); - logger.debug("PositionService - Position {} opened with order {}", + logger.debug("Position {} opened with order {}", p.getPositionId(), orderCreationResult.getOrder().getOrderId()); @@ -129,14 +130,14 @@ public final PositionCreationResultDTO createPosition(final GenericCassandreStra positionFlux.emitValue(p); return new PositionCreationResultDTO(p); } else { - logger.error("PositionService - Position creation failure : {}", orderCreationResult.getErrorMessage()); + logger.error("Position creation failure : {}", orderCreationResult.getErrorMessage()); return new PositionCreationResultDTO(orderCreationResult.getErrorMessage(), orderCreationResult.getException()); } } @Override public final void updatePositionRules(final long id, final PositionRulesDTO newRules) { - logger.debug("PositionService - Update position {} with the rules: {}", id, newRules); + logger.debug("Update position {} with the rules: {}", id, newRules); final Optional p = positionRepository.findById(id); // If position exists and position is not closed. if (p.isPresent() && p.get().getStatus() != CLOSED) { @@ -157,22 +158,21 @@ public final void updatePositionRules(final long id, final PositionRulesDTO newR @Override public final void closePosition(final long id) { - logger.debug("PositionService - Force closing position {}", id); + logger.debug("Force position {} to close", id); positionRepository.updateForceClosing(id, true); } @Override public final Set getPositions() { - logger.debug("PositionService - Retrieving all positions"); - return positionRepository.findByOrderById() - .stream() + logger.debug("Retrieving all positions"); + return positionRepository.findByOrderById().stream() .map(positionMapper::mapToPositionDTO) .collect(Collectors.toCollection(LinkedHashSet::new)); } @Override public final Optional getPositionById(final long id) { - logger.debug("PositionService - Retrieving position by id {}", id); + logger.debug("Retrieving position by id {}", id); final Optional position = positionRepository.findById(id); return position.map(positionMapper::mapToPositionDTO); } @@ -180,48 +180,36 @@ public final Optional getPositionById(final long id) { @Override public final void ordersUpdates(final Set orders) { orders.forEach(orderDTO -> { - logger.debug("PositionService - Updating positions with order {}", orderDTO); - positionRepository.findByStatusNot(CLOSED) - .stream() + logger.debug("Updating positions with order {}", orderDTO); + positionRepository.findByStatusNot(CLOSED).stream() .map(positionMapper::mapToPositionDTO) - .forEach(p -> { - if (p.orderUpdate(orderDTO)) { - logger.debug("PositionService - Position {} updated with order {}", - p.getPositionId(), - orderDTO); - positionFlux.emitValue(p); - } - }); + .filter(positionDTO -> positionDTO.orderUpdate(orderDTO)) + .peek(positionDTO -> logger.debug("Position {} updated with order {}", positionDTO.getPositionId(), orderDTO)) + .forEach(positionFlux::emitValue); }); } @Override public final void tradesUpdates(final Set trades) { trades.forEach(tradeDTO -> { - logger.debug("PositionService - Updating positions with trade {}", tradeDTO); - positionRepository.findByStatusNot(CLOSED) - .stream() + logger.debug("Updating positions with trade {}", tradeDTO); + positionRepository.findByStatusNot(CLOSED).stream() .map(positionMapper::mapToPositionDTO) - .forEach(p -> { - if (p.tradeUpdate(tradeDTO)) { - logger.debug("PositionService - Position {} updated with trade {}", - p.getPositionId(), - tradeDTO); - positionFlux.emitValue(p); - } - }); + .filter(positionDTO -> positionDTO.tradeUpdate(tradeDTO)) + .peek(positionDTO -> logger.debug("Position {} updated with trade {}", positionDTO.getPositionId(), tradeDTO)) + .forEach(positionFlux::emitValue); }); } @Override public final void tickersUpdates(final Set tickers) { // With the ticker received, we check for every opened position, if it should be closed. - logger.debug("PositionService - Updating position with {} ticker", tickers.size()); + logger.debug("Updating position with {} ticker", tickers.size()); tickers.forEach(ticker -> positionRepository.findByStatusNot(CLOSED) .stream() .map(positionMapper::mapToPositionDTO) .filter(p -> p.tickerUpdate(ticker)) - .peek(p -> logger.debug("PositionService - Position {} updated with ticker {}", p.getPositionId(), ticker)) + .peek(p -> logger.debug("Position {} updated with ticker {}", p.getPositionId(), ticker)) .forEach(p -> { // We close the position if it triggers the rules. // Or if the position was forced to close. @@ -253,27 +241,23 @@ public final void tickersUpdates(final Set tickers) { if (orderCreationResult.isSuccessful()) { p.closePositionWithOrder(orderCreationResult.getOrder()); - logger.debug("PositionService - Position {} closed with order {}", p.getPositionId(), orderCreationResult.getOrder().getOrderId()); + logger.debug("Position {} closed with order {}", p.getPositionId(), orderCreationResult.getOrder().getOrderId()); } else { - logger.error("PositionService - Position {} not closed: {}", p.getPositionId(), orderCreationResult.getErrorMessage()); + logger.error("Position {} not closed: {}", p.getPositionId(), orderCreationResult.getErrorMessage()); } } else { logger.error("Strategy {} not found", p.getStrategy().getStrategyId()); } } + // We emit the position even anyway because the ticker updated it. positionFlux.emitValue(p); })); } @Override - public final Map amountsLockedByPosition() { + public final Map getAmountsLockedByPosition() { // List of status that locks amounts. - Set status = new HashSet<>(); - status.add(OPENING); - status.add(OPENED); - - return positionRepository.findByStatusIn(status) - .stream() + return positionRepository.findByStatusIn(new HashSet<>(Arrays.asList(OPENING, OPENED))).stream() .map(positionMapper::mapToPositionDTO) .collect(Collectors.toMap(PositionDTO::getId, PositionDTO::getAmountToLock, (key, value) -> key, HashMap::new)); } @@ -325,7 +309,9 @@ public final HashMap getGains() { } // And now the fees. - Stream.concat(p.getOpeningOrder().getTrades().stream(), p.getClosingOrder().getTrades().stream()).forEach(t -> totalFees.add(t.getFee())); + Stream.concat(p.getOpeningOrder().getTrades().stream(), p.getClosingOrder().getTrades().stream()) + .filter(tradeDTO -> tradeDTO.getFee() != null) + .forEach(tradeDTO -> totalFees.add(tradeDTO.getFee())); }); gains.keySet() @@ -338,6 +324,7 @@ public final HashMap getGains() { // We calculate the fees for the currency. final BigDecimal fees = totalFees.stream() + .filter(Objects::nonNull) .filter(amount -> amount.getCurrency().equals(currency)) .map(CurrencyAmountDTO::getValue) .reduce(ZERO, BigDecimal::add); diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/TradeServiceXChangeImplementation.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/TradeServiceXChangeImplementation.java index 438144b8a..def1100cc 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/TradeServiceXChangeImplementation.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/TradeServiceXChangeImplementation.java @@ -1,8 +1,10 @@ package tech.cassandre.trading.bot.service; import org.apache.commons.lang3.time.DateUtils; +import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.trade.LimitOrder; import org.knowm.xchange.dto.trade.MarketOrder; +import org.knowm.xchange.service.trade.params.DefaultCancelOrderByCurrencyPairAndIdParams; import org.knowm.xchange.service.trade.params.TradeHistoryParamsAll; import tech.cassandre.trading.bot.domain.Order; import tech.cassandre.trading.bot.dto.trade.OrderCreationResultDTO; @@ -14,7 +16,6 @@ import tech.cassandre.trading.bot.repository.OrderRepository; import tech.cassandre.trading.bot.strategy.GenericCassandreStrategy; import tech.cassandre.trading.bot.util.base.service.BaseService; -import tech.cassandre.trading.bot.util.system.TimeProvider; import java.io.IOException; import java.math.BigDecimal; @@ -76,7 +77,7 @@ private OrderCreationResultDTO createMarketOrder(final GenericCassandreStrategy MarketOrder m = new MarketOrder(utilMapper.mapToOrderType(orderTypeDTO), amount, currencyMapper.mapToCurrencyPair(currencyPair)); - logger.debug("TradeService - Sending market order: {} - {} - {}", orderTypeDTO, currencyPair, amount); + logger.debug("Sending market order: {} - {} - {}", orderTypeDTO, currencyPair, amount); // Sending the order. OrderDTO order = OrderDTO.builder() @@ -110,7 +111,7 @@ private OrderCreationResultDTO createMarketOrder(final GenericCassandreStrategy savedOrder = Optional.of(orderRepository.save(orderMapper.mapToOrder(order))); } final OrderCreationResultDTO result = new OrderCreationResultDTO(orderMapper.mapToOrderDTO(savedOrder.get())); - logger.debug("TradeService - Order created: {}", result); + logger.debug("Order created: {}", result); return result; } catch (Exception e) { final String error = "TradeService - Error calling createMarketOrder for " + amount + " " + currencyPair + ": " + e.getMessage(); @@ -143,7 +144,7 @@ private OrderCreationResultDTO createLimitOrder(final GenericCassandreStrategy s null, null, limitPrice); - logger.debug("TradeService - Sending market order: {} - {} - {}", orderTypeDTO, currencyPair, amount); + logger.debug("Sending market order: {} - {} - {}", orderTypeDTO, currencyPair, amount); // Sending & creating the order. OrderDTO order = OrderDTO.builder() @@ -181,7 +182,7 @@ private OrderCreationResultDTO createLimitOrder(final GenericCassandreStrategy s savedOrder = Optional.of(orderRepository.save(orderMapper.mapToOrder(order))); } final OrderCreationResultDTO result = new OrderCreationResultDTO(orderMapper.mapToOrderDTO(savedOrder.get())); - logger.debug("TradeService - Order creation result: {}", result); + logger.debug("Order creation result: {}", result); return result; } catch (Exception e) { final String error = "TradeService - Error calling createLimitOrder for " + amount + " " + currencyPair + " : " + e.getMessage(); @@ -228,17 +229,27 @@ public OrderCreationResultDTO createSellLimitOrder(final GenericCassandreStrateg @Override @SuppressWarnings("checkstyle:DesignForExtension") public boolean cancelOrder(final String orderId) { - logger.debug("TradeService - Canceling order {}", orderId); + logger.debug("Canceling order {}", orderId); if (orderId != null) { try { - logger.debug("TradeService - Successfully canceled order {}", orderId); - return tradeService.cancelOrder(orderId); + // We retrieve the order currency pair. + final Optional order = orderRepository.findByOrderId(orderId); + if (order.isPresent()) { + OrderDTO orderDTO = orderMapper.mapToOrderDTO(order.get()); + final CurrencyPair currencyPair = currencyMapper.mapToCurrencyPair(orderDTO.getCurrencyPair()); + DefaultCancelOrderByCurrencyPairAndIdParams params = new DefaultCancelOrderByCurrencyPairAndIdParams(currencyPair, orderId); + logger.debug("Successfully canceled order {}", orderId); + return tradeService.cancelOrder(params); + } else { + logger.error(" Error canceling order {}: order not found in database", orderId); + return false; + } } catch (Exception e) { - logger.error("TradeService - Error canceling order {}: {}", orderId, e.getMessage()); + logger.error("Error canceling order {}: {}", orderId, e.getMessage()); return false; } } else { - logger.error("TradeService - Error canceling order, order id provided is null"); + logger.error("Error canceling order, order id provided is null"); return false; } } @@ -246,7 +257,7 @@ public boolean cancelOrder(final String orderId) { @Override @SuppressWarnings("checkstyle:DesignForExtension") public Set getOrders() { - logger.debug("TradeService - Getting orders from exchange"); + logger.debug("Getting orders from exchange"); try { // Consume a token from the token bucket. // If a token is not available this method will block until the refill adds one to the bucket. @@ -257,7 +268,7 @@ public Set getOrders() { .stream() .map(orderMapper::mapToOrderDTO) .sorted(Comparator.comparing(OrderDTO::getTimestamp)) - .peek(orderDTO -> logger.debug("TradeService - local order retrieved: {}", orderDTO)) + .peek(orderDTO -> logger.debug("Local order retrieved: {}", orderDTO)) .peek(orderDTO -> orderDTO.updateStatus(NEW)) .collect(Collectors.toCollection(LinkedHashSet::new)); @@ -270,14 +281,13 @@ public Set getOrders() { .getOpenOrders() .stream() .map(orderMapper::mapToOrderDTO) - .peek(orderDTO -> logger.debug("TradeService - remote order retrieved: {}", orderDTO)) + .peek(orderDTO -> logger.debug("Remote order retrieved: {}", orderDTO)) .collect(Collectors.toCollection(LinkedHashSet::new)); } } catch (IOException e) { - logger.error("TradeService - Error retrieving orders: {}", e.getMessage()); + logger.error("Error retrieving orders: {}", e.getMessage()); return Collections.emptySet(); } catch (InterruptedException e) { - logger.error("TradeService - InterruptedException: {}", e.getMessage()); return Collections.emptySet(); } } @@ -285,20 +295,21 @@ public Set getOrders() { @Override @SuppressWarnings("checkstyle:DesignForExtension") public Set getTrades() { - logger.debug("TradeService - Getting trades from exchange"); + logger.debug("Getting trades from exchange"); // Query trades from the last 24 jours (24 hours is the maximum because of Binance limitations). TradeHistoryParamsAll params = new TradeHistoryParamsAll(); - Date now = TimeProvider.now(); + Date now = new Date(); Date startDate = DateUtils.addDays(now, -1); params.setStartTime(startDate); params.setEndTime(now); // We only ask for trades with currency pairs that was used in the previous orders we made. + // And we only choose the orders that are not fulfilled. final LinkedHashSet currencyPairs = orderRepository.findByOrderByTimestampAsc() .stream() - .map(Order::getCurrencyPair) - .distinct() - .map(currencyMapper::mapToCurrencyPairDTO) + .map(orderMapper::mapToOrderDTO) + .filter(orderDTO -> !orderDTO.isFulfilled()) + .map(OrderDTO::getCurrencyPair) .collect(Collectors.toCollection(LinkedHashSet::new)); Set results = new LinkedHashSet<>(); @@ -319,13 +330,13 @@ public Set getTrades() { .collect(Collectors.toCollection(LinkedHashSet::new)) ); } catch (IOException e) { - logger.error("TradeService - Error retrieving trades: {}", e.getMessage()); + logger.error("Error retrieving trades: {}", e.getMessage()); } catch (InterruptedException e) { - logger.error("TradeService - InterruptedException: {}", e.getMessage()); + logger.error("InterruptedException: {}", e.getMessage()); } }); } - logger.debug("TradeService - {} trade(s) found", results.size()); + logger.debug("{} trade(s) found", results.size()); return results; } diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/UserService.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/UserService.java index 733e364c0..8237db791 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/UserService.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/UserService.java @@ -1,7 +1,9 @@ package tech.cassandre.trading.bot.service; +import tech.cassandre.trading.bot.dto.user.AccountDTO; import tech.cassandre.trading.bot.dto.user.UserDTO; +import java.util.Map; import java.util.Optional; /** @@ -16,4 +18,11 @@ public interface UserService { */ Optional getUser(); + /** + * Retrieve user accounts information from exchange. + * + * @return account + */ + Map getAccounts(); + } diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/UserServiceXChangeImplementation.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/UserServiceXChangeImplementation.java index f53142994..f12133feb 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/UserServiceXChangeImplementation.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/service/UserServiceXChangeImplementation.java @@ -1,9 +1,12 @@ package tech.cassandre.trading.bot.service; +import tech.cassandre.trading.bot.dto.user.AccountDTO; import tech.cassandre.trading.bot.dto.user.UserDTO; import tech.cassandre.trading.bot.util.base.service.BaseService; import java.io.IOException; +import java.util.Collections; +import java.util.Map; import java.util.Optional; /** @@ -33,17 +36,27 @@ public Optional getUser() { // If a token is not available this method will block until the refill adds one to the bucket. bucket.asScheduler().consume(1); - logger.debug("UserService - Retrieving account information"); + logger.debug("Retrieving account information"); final UserDTO user = accountMapper.mapToUserDTO(xChangeAccountService.getAccountInfo()); - logger.debug("UserService - Account information retrieved " + user); + logger.debug("Account information retrieved " + user); return Optional.ofNullable(user); } catch (IOException e) { - logger.error("UserService - Error retrieving account information: {}", e.getMessage()); + logger.error("Error retrieving account information: {}", e.getMessage()); return Optional.empty(); } catch (InterruptedException e) { - logger.error("UserService - InterruptedException: {}", e.getMessage()); return Optional.empty(); } } + @Override + @SuppressWarnings("checkstyle:DesignForExtension") + public Map getAccounts() { + final Optional user = getUser(); + if (user.isPresent()) { + return user.get().getAccounts(); + } else { + return Collections.emptyMap(); + } + } + } diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/strategy/BasicTa4jCassandreStrategy.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/strategy/BasicTa4jCassandreStrategy.java index e36eb8768..92417eafa 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/strategy/BasicTa4jCassandreStrategy.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/strategy/BasicTa4jCassandreStrategy.java @@ -227,12 +227,12 @@ public final boolean canSell(final AccountDTO account, } /** - * Called when your strategy says you should enter. + * Called when your strategy think you should enter. */ public abstract void shouldEnter(); /** - * Called when your strategy says your should exit. + * Called when your strategy think your should exit. */ public abstract void shouldExit(); diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/strategy/CassandreStrategyInterface.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/strategy/CassandreStrategyInterface.java index f9d271dc6..8e817c65c 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/strategy/CassandreStrategyInterface.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/strategy/CassandreStrategyInterface.java @@ -20,7 +20,7 @@ /** * Cassandre strategy interface. - * This allows the framework to communicate with the strategy. + * This interface defines how Cassandre interacts with a user strategy. */ @SuppressWarnings("unused") public interface CassandreStrategyInterface { @@ -124,7 +124,7 @@ public interface CassandreStrategyInterface { Set getRequestedCurrencyPairs(); /** - * Implements this method to tell the bot which account from the accounts you own is the trading one. + * Implements this method to tell the bot which account from the accounts you own is the one you use for trading. * * @param accounts all your accounts * @return your trading account @@ -139,42 +139,42 @@ public interface CassandreStrategyInterface { Optional getTradeAccount(); /** - * Method called by streams on accounts updates. + * Method called by Cassandre when there are accounts updates. * * @param accounts accounts updates */ void onAccountsUpdates(Map accounts); /** - * Method called by streams on tickers updates. + * Method called by Cassandre when there are tickers updates. * * @param tickers tickers updates */ void onTickersUpdates(Map tickers); /** - * Method called by streams on orders updates. + * Method called by Cassandre when there are orders updates. * * @param orders orders updates */ void onOrdersUpdates(Map orders); /** - * Method called by streams on trades updates. + * Method called by Cassandre when there are trades updates. * * @param trades trades updates */ void onTradesUpdates(Map trades); /** - * Method called by streams on positions updates. + * Method called by Cassandre when there are positions updates. * * @param positions positions updates */ void onPositionsUpdates(Map positions); /** - * Method called by streams on positions status updates. + * Method called by Cassandre when there are positions status updates. * * @param positions positions status updates */ diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.java index 0b1e74bb2..8072ec4a3 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/strategy/GenericCassandreStrategy.java @@ -83,12 +83,12 @@ public abstract class GenericCassandreStrategy implements CassandreStrategyInter /** The accounts owned by the user. */ private final Map userAccounts = new LinkedHashMap<>(); - /** Positions previous status. */ - private final Map previousPositionsStatus = new LinkedHashMap<>(); - /** Last tickers received. */ private final Map lastTickers = new LinkedHashMap<>(); + /** Positions previous status - used for onPositionsStatusUpdates() - Internal use only. */ + private final Map previousPositionsStatus = new LinkedHashMap<>(); + // ================================================================================================================= // Internal methods to setup dependencies. @@ -115,14 +115,11 @@ public final void setStrategy(final StrategyDTO newStrategyDTO) { this.strategy = newStrategyDTO; } - @Override - public void initializeAccounts(final Map newAccounts) { - userAccounts.putAll(newAccounts); - } - @Override public final void setPositionRepository(final PositionRepository newPositionRepository) { this.positionRepository = newPositionRepository; + // To manage onPositionsStatusUpdates, we retrieve the positions status from our database. + this.positionRepository.findByStatusNot(CLOSED).forEach(position -> previousPositionsStatus.put(position.getId(), position.getStatus())); } @Override @@ -131,13 +128,13 @@ public final void setOrderRepository(final OrderRepository newOrderRepository) { } @Override - public void setExchangeService(final ExchangeService newExchangeService) { - this.exchangeService = newExchangeService; + public final void setTradeRepository(final TradeRepository newTradeRepository) { + this.tradeRepository = newTradeRepository; } @Override - public final void setTradeRepository(final TradeRepository newTradeRepository) { - this.tradeRepository = newTradeRepository; + public void setExchangeService(final ExchangeService newExchangeService) { + this.exchangeService = newExchangeService; } @Override @@ -148,11 +145,11 @@ public final void setTradeService(final TradeService newTradeService) { @Override public final void setPositionService(final PositionService newPositionService) { this.positionService = newPositionService; - // We set the previous positions status from database. - this.positionService.getPositions() - .stream() - .filter(p -> p.getStatus() != CLOSED) - .forEach(p -> previousPositionsStatus.put(p.getId(), p.getStatus())); + } + + @Override + public void initializeAccounts(final Map newAccounts) { + userAccounts.putAll(newAccounts); } // ================================================================================================================= @@ -160,9 +157,9 @@ public final void setPositionService(final PositionService newPositionService) { @Override public void accountsUpdates(final Set accounts) { // We notify the strategy. - final Map accountsUpdates = accounts - .stream() - .peek(accountDTO -> userAccounts.put(accountDTO.getAccountId(), accountDTO)) // We store the account values in the strategy. + final Map accountsUpdates = accounts.stream() + // We store the account values in the strategy. + .peek(accountDTO -> userAccounts.put(accountDTO.getAccountId(), accountDTO)) .collect(Collectors.toMap(AccountDTO::getAccountId, Function.identity(), (id, value)->id, LinkedHashMap::new)); // We notify the strategy. @@ -173,19 +170,18 @@ public void accountsUpdates(final Set accounts) { public void tickersUpdates(final Set tickers) { // We only retrieve the tickers requested by the strategy. final Map tickersUpdates = tickers.stream() - .filter(ticker -> getRequestedCurrencyPairs().contains(ticker.getCurrencyPair())) + .filter(tickerDTO -> getRequestedCurrencyPairs().contains(tickerDTO.getCurrencyPair())) + // We update the values of the last tickers that can be found in the strategy. + .peek(tickerDTO -> lastTickers.put(tickerDTO.getCurrencyPair(), tickerDTO)) .collect(Collectors.toMap(TickerDTO::getCurrencyPair, Function.identity(), (id, value)->id, LinkedHashMap::new)); - // We update the values of the last tickers that can be found in the strategy. - tickersUpdates.values().forEach(ticker -> lastTickers.put(ticker.getCurrencyPair(), ticker)); - // We notify the strategy. onTickersUpdates(tickersUpdates); } @Override public void ordersUpdates(final Set orders) { - // We only retrieve the orders for the strategy. + // We only retrieve the orders created by this strategy. final Map ordersUpdates = orders.stream() .filter(orderDTO -> orderDTO.getStrategy().getId().equals(strategy.getId())) .collect(Collectors.toMap(OrderDTO::getOrderId, Function.identity(), (id, value)->id, LinkedHashMap::new)); @@ -196,7 +192,7 @@ public void ordersUpdates(final Set orders) { @Override public void tradesUpdates(final Set trades) { - // We only retrieve the orders for the strategy. + // We only retrieve the trades created by this strategy. final Map tradesUpdates = trades.stream() .filter(tradeDTO -> tradeDTO.getOrder().getStrategy().getId().equals(strategy.getId())) .collect(Collectors.toMap(TradeDTO::getTradeId, Function.identity(), (id, value)->id, LinkedHashMap::new)); @@ -217,10 +213,12 @@ public void positionsUpdates(final Set positions) { // From positionUpdate(), we see if it's also a onPositionStatusUpdate(). if (previousPositionsStatus.get(positionDTO.getId()) != positionDTO.getStatus()) { - previousPositionsStatus.put(positionDTO.getId(), positionDTO.getStatus()); if (positionDTO.getStatus() == CLOSED) { // As CLOSED positions cannot change anymore, we don't need to store their previous positions. previousPositionsStatus.remove(positionDTO.getId()); + } else { + // As we have a new status for this positions, we update it in previousPositionsStatus. + previousPositionsStatus.put(positionDTO.getId(), positionDTO.getStatus()); } positionsStatusUpdates.put(positionDTO.getPositionId(), positionDTO); } @@ -269,11 +267,11 @@ public final Optional getTradeAccount() { * @return amountsLockedByPosition */ public final Map getAmountsLockedByPosition() { - return positionService.amountsLockedByPosition(); + return positionService.getAmountsLockedByPosition(); } /** - * Returns the amount locked by currency. + * Returns the amounts locked for a specific currency. * * @param currency currency * @return amount diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/base/domain/BaseDomain.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/base/domain/BaseDomain.java index 1e8bc63e6..daac3a4f2 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/base/domain/BaseDomain.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/base/domain/BaseDomain.java @@ -11,7 +11,7 @@ import java.time.ZonedDateTime; /** - * Base domain (manage createdOn and updatedOn). + * Base domain (manage createdOn and updatedOn fields). */ @Data @MappedSuperclass diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/dry/ExchangeServiceDryModeAOP.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/dry/ExchangeServiceDryModeAOP.java index 76d7bcfc9..f0338ac79 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/dry/ExchangeServiceDryModeAOP.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/dry/ExchangeServiceDryModeAOP.java @@ -42,10 +42,9 @@ public final ExchangeMetaData getExchangeMetaData(final ProceedingJoinPoint pjp) .getBeansWithAnnotation(CassandreStrategy.class) .values() // We get the list of all required cp of all strategies. .stream() - .map(o -> ((CassandreStrategyInterface) o)) + .map(o -> (CassandreStrategyInterface) o) .map(CassandreStrategyInterface::getRequestedCurrencyPairs) .flatMap(Set::stream) - .distinct() .map(currencyMapper::mapToCurrencyPair) .collect(HashMap::new, (map, cp) -> map.put(cp, null), Map::putAll); diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/dry/TradeServiceDryModeAOP.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/dry/TradeServiceDryModeAOP.java index 4ead48b7a..4d4b77ca3 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/dry/TradeServiceDryModeAOP.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/dry/TradeServiceDryModeAOP.java @@ -12,10 +12,10 @@ import org.knowm.xchange.service.trade.params.TradeHistoryParams; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; -import tech.cassandre.trading.bot.domain.Order; import tech.cassandre.trading.bot.dto.market.TickerDTO; import tech.cassandre.trading.bot.dto.position.PositionDTO; import tech.cassandre.trading.bot.dto.trade.OrderCreationResultDTO; +import tech.cassandre.trading.bot.dto.trade.OrderStatusDTO; import tech.cassandre.trading.bot.dto.trade.TradeDTO; import tech.cassandre.trading.bot.dto.user.AccountDTO; import tech.cassandre.trading.bot.dto.user.BalanceDTO; @@ -58,6 +58,9 @@ public class TradeServiceDryModeAOP extends BaseService { /** Dry order prefix. */ private static final String DRY_ORDER_PREFIX = "DRY_ORDER_"; + /** Order counter. */ + private final AtomicInteger orderCounter = new AtomicInteger(1); + /** Dry trade prefix. */ private static final String DRY_TRADE_PREFIX = "DRY_TRADE_"; @@ -67,12 +70,12 @@ public class TradeServiceDryModeAOP extends BaseService { /** Position repository. */ private final PositionRepository positionRepository; - /** Order counter. */ - private final AtomicInteger orderCounter = new AtomicInteger(1); - /** User service - dry mode. */ private final UserServiceDryModeAOP userService; + /** 100%. */ + private static final BigDecimal ONE_HUNDRED_BIG_DECIMAL = new BigDecimal("100"); + @Around(value = "execution(* tech.cassandre.trading.bot.service.TradeService.createBuyMarketOrder(..)) && args(strategy, currencyPair, amount)", argNames = "pjp, strategy, currencyPair, amount") public final OrderCreationResultDTO createBuyMarketOrder(final ProceedingJoinPoint pjp, final GenericCassandreStrategy strategy, @@ -124,7 +127,7 @@ public final OrderCreationResultDTO createSellMarketOrder(final ProceedingJoinPo // We check that we have the trade account. final Optional tradeAccount = strategy.getTradeAccount(); if (tradeAccount.isEmpty()) { - throw new RuntimeException("Trade account was not found"); + throw new DryModeException("Trade account was not found"); } // Selling order - we sell ETH to buy BTC. @@ -164,13 +167,7 @@ public final String placeMarketOrder(final ProceedingJoinPoint pjp, final Market @Around(value = "execution(* tech.cassandre.trading.bot.service.TradeService.cancelOrder(..)) && args(orderId))", argNames = "pjp, orderId") public final boolean cancelOrder(final ProceedingJoinPoint pjp, final String orderId) { - final Optional order = orderRepository.findByOrderId(orderId); - if (order.isPresent()) { - orderRepository.delete(order.get()); - return true; - } else { - return false; - } + throw new DryModeException("Not supported"); } @Around("execution(* org.knowm.xchange.service.trade.TradeService.getOpenOrders())") @@ -201,6 +198,7 @@ public final UserTrades getTradeHistory(final ProceedingJoinPoint pjp, final Tra Map tradePrices = new HashMap<>(); orderRepository.findByOrderByTimestampAsc() .stream() + .filter(order -> order.getStatus() == OrderStatusDTO.FILLED) .map(orderMapper::mapToOrderDTO) .filter(orderDTO -> !orderDTO.isFulfilled()) // Only orders with trades not arrived .forEach(orderDTO -> { @@ -213,13 +211,13 @@ public final UserTrades getTradeHistory(final ProceedingJoinPoint pjp, final Tra .map(positionMapper::mapToPositionDTO) .findFirst(); - // If this order is used to close position, we calculate a new price. + // If this order is used to close position, we calculate a new price to match rules percentages. // A gain was made, we recalculate it from the order. if (positionDTO.isPresent()) { final Optional gainDTO = positionDTO.get().calculateGainFromPrice(orderDTO.getMarketPriceValue()); if (gainDTO.isPresent()) { - // We need the opening trade to know the price the asset was bought. + // We need the trade of the opening order to know the price the asset was bought. final TradeDTO openingTrade = positionDTO.get().getOpeningOrder().getTrades().iterator().next(); if (positionDTO.get().getType().equals(LONG)) { @@ -239,9 +237,8 @@ public final UserTrades getTradeHistory(final ProceedingJoinPoint pjp, final Tra // openingTrade market price * (( openingTrade market price * rules gain)/100) final BigDecimal augmentation = positionDTO.get().getOpeningOrder().getMarketPriceValue() .multiply(BigDecimal.valueOf(positionDTO.get().getRules().getStopGainPercentage())) - .divide(new BigDecimal("100"), BIGINTEGER_SCALE, FLOOR); + .divide(ONE_HUNDRED_BIG_DECIMAL, BIGINTEGER_SCALE, FLOOR); tradePrices.put(orderDTO.getOrderId(), openingTrade.getPriceValue().add(augmentation)); - } else if (positionDTO.get().getRules().isStopLossPercentageSet() && gainDTO.get().getPercentage() < 0) { // If the position has a stop gain percentage and the real gain is superior to this percentage. @@ -255,7 +252,7 @@ public final UserTrades getTradeHistory(final ProceedingJoinPoint pjp, final Tra // openingTrade market price * (( openingTrade market price * rules gain)/100) final BigDecimal reduction = positionDTO.get().getOpeningOrder().getMarketPriceValue() .multiply(BigDecimal.valueOf(positionDTO.get().getRules().getStopLossPercentage())) - .divide(new BigDecimal("100"), BIGINTEGER_SCALE, FLOOR); + .divide(ONE_HUNDRED_BIG_DECIMAL, BIGINTEGER_SCALE, FLOOR); tradePrices.put(orderDTO.getOrderId(), openingTrade.getPriceValue().subtract(reduction)); } // ===================================================================================== @@ -282,7 +279,7 @@ public final UserTrades getTradeHistory(final ProceedingJoinPoint pjp, final Tra // 2 * price = 70 000 USDT => price = 70 000/2 = 35 000 final BigDecimal augmentation = openingTrade.getAmountValue() .multiply(BigDecimal.valueOf(positionDTO.get().getRules().getStopGainPercentage())) - .divide(new BigDecimal("100"), BIGINTEGER_SCALE, FLOOR); + .divide(ONE_HUNDRED_BIG_DECIMAL, BIGINTEGER_SCALE, FLOOR); orderRepository.updateAmount(orderDTO.getId(), openingTrade.getAmountValue().add(augmentation)); tradePrices.put(orderDTO.getOrderId(), positionDTO.get().getOpeningOrder().getMarketPriceValue().divide(openingTrade.getAmountValue().add(augmentation), BIGINTEGER_SCALE, FLOOR)); @@ -306,24 +303,18 @@ public final UserTrades getTradeHistory(final ProceedingJoinPoint pjp, final Tra // 0.9 * price = 40 000 USDT => price = 40 000/0.9 final BigDecimal reduction = openingTrade.getAmountValue() .multiply(BigDecimal.valueOf(positionDTO.get().getRules().getStopLossPercentage())) - .divide(new BigDecimal("100"), BIGINTEGER_SCALE, FLOOR); + .divide(ONE_HUNDRED_BIG_DECIMAL, BIGINTEGER_SCALE, FLOOR); orderRepository.updateAmount(orderDTO.getId(), openingTrade.getAmountValue().subtract(reduction)); tradePrices.put(orderDTO.getOrderId(), positionDTO.get().getOpeningOrder().getMarketPriceValue().divide(openingTrade.getAmountValue().subtract(reduction), BIGINTEGER_SCALE, FLOOR)); } // ===================================================================================== } -// if (positionDTO.get().getId() == 1) { -// System.out.println("===> " + positionDTO); -// System.out.println("===> " + orderDTO); -// System.out.println("===> " + gainDTO); -// tradePrices.forEach((s, bigDecimal) -> System.out.println(s + "=>" + bigDecimal)); -// } } } } ); - // For every orders in database, we will simulate an equivalent trade to close things. + // For every orders not fulfilled in database, we will simulate an equivalent trade to close it. List trades = orderRepository.findByOrderByTimestampAsc() .stream() .map(orderMapper::mapToOrderDTO) @@ -340,7 +331,6 @@ public final UserTrades getTradeHistory(final ProceedingJoinPoint pjp, final Tra .timestamp(Timestamp.valueOf(orderDTO.getTimestamp().toLocalDateTime())) .build()) .collect(Collectors.toList()); - return new UserTrades(trades, SortByTimestamp); } diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/dry/UserServiceDryModeAOP.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/dry/UserServiceDryModeAOP.java index 5b8e987d4..e53a48259 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/dry/UserServiceDryModeAOP.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/dry/UserServiceDryModeAOP.java @@ -51,12 +51,12 @@ public class UserServiceDryModeAOP extends BaseService { /** User ID. */ private static final String USER_ID = "user"; - /** Account information. */ - private AccountInfo accountInfo; - /** Application context. */ private final ApplicationContext applicationContext; + /** Account information. */ + private AccountInfo accountInfo; + /** * Constructor. * @@ -160,11 +160,10 @@ public void addToBalance(final GenericCassandreStrategy strategy, final Currency accountInfo = new AccountInfo(USER_ID, wallets); // Updates all strategies. final UserDTO userDTO = accountMapper.mapToUserDTO(accountInfo); - applicationContext - .getBeansWithAnnotation(CassandreStrategy.class) + applicationContext.getBeansWithAnnotation(CassandreStrategy.class) .values() // We get the list of all required cp of all strategies. .stream() - .map(o -> ((CassandreStrategyInterface) o)) + .map(o -> (CassandreStrategyInterface) o) .forEach(cassandreStrategyInterface -> cassandreStrategyInterface.initializeAccounts(userDTO.getAccounts())); } } diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/exception/ConfigurationException.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/exception/ConfigurationException.java index 5216b79db..26b849533 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/exception/ConfigurationException.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/exception/ConfigurationException.java @@ -9,7 +9,7 @@ public class ConfigurationException extends RuntimeException { private String advisedAction; /** - * Constructor without action. + * Configuration exception without action. * * @param message error message */ @@ -18,7 +18,7 @@ public ConfigurationException(final String message) { } /** - * Constructor. + * Configuration exception. * * @param message error message * @param newAdvisedAction advised action to fix this problem diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/exception/ConfigurationFailureAnalyzer.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/exception/ConfigurationFailureAnalyzer.java index fe7c01891..de7c3c0d8 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/exception/ConfigurationFailureAnalyzer.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/exception/ConfigurationFailureAnalyzer.java @@ -4,7 +4,7 @@ import org.springframework.boot.diagnostics.FailureAnalysis; /** - * Failure analyzer for configuration error. + * Failure analyzer for Cassandre configuration error. */ public class ConfigurationFailureAnalyzer extends AbstractFailureAnalyzer { diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/exception/DryModeException.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/exception/DryModeException.java index f820c3b9a..cc8bf9b5f 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/exception/DryModeException.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/exception/DryModeException.java @@ -1,7 +1,7 @@ package tech.cassandre.trading.bot.util.exception; /** - * Dry mode exception. + * Exception in cassandre dry mode. */ public class DryModeException extends RuntimeException { diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/exception/PositionException.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/exception/PositionException.java index 10056a1a8..2f6c5f1a3 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/exception/PositionException.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/exception/PositionException.java @@ -1,7 +1,7 @@ package tech.cassandre.trading.bot.util.exception; /** - * Position exception. + * Exception in cassandre position. */ public class PositionException extends RuntimeException { diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/java/EqualsBuilder.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/java/EqualsBuilder.java index 469759737..459ddaeae 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/java/EqualsBuilder.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/java/EqualsBuilder.java @@ -4,7 +4,7 @@ import java.time.ZonedDateTime; /** - * Equals builder. + * Equals builder dealing correctly with BigDecimal and ZonedDateTime. */ public class EqualsBuilder { diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/jpa/CurrencyAmount.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/jpa/CurrencyAmount.java index 088408b48..533b2947c 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/jpa/CurrencyAmount.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/jpa/CurrencyAmount.java @@ -54,7 +54,7 @@ public final String toString() { if (value != null) { return value + " " + currency; } else { - return "Not provided"; + return "Null"; } } diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/mapper/PositionMapper.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/mapper/PositionMapper.java index fd094b48d..5cbafe9a5 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/mapper/PositionMapper.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/mapper/PositionMapper.java @@ -25,13 +25,13 @@ public interface PositionMapper { Position mapToPosition(PositionDTO source); @Mapping(target = "id", ignore = true) + @Mapping(target = "strategy", ignore = true) @Mapping(target = "positionId", ignore = true) @Mapping(target = "stopGainPercentageRule", ignore = true) @Mapping(target = "stopLossPercentageRule", ignore = true) @Mapping(target = "forceClosing", ignore = true) @Mapping(target = "createdOn", ignore = true) @Mapping(target = "updatedOn", ignore = true) - @Mapping(target = "strategy", ignore = true) void updatePosition(PositionDTO source, @MappingTarget Position target); // ================================================================================================================= diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/mapper/TradeMapper.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/mapper/TradeMapper.java index 01f0be9e9..9863262bb 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/mapper/TradeMapper.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/mapper/TradeMapper.java @@ -37,19 +37,27 @@ public interface TradeMapper { @Mapping(target = "updatedOn", ignore = true) default CurrencyAmountDTO mapUserTradeToTradeDTOAmount(UserTrade source) { CurrencyPairDTO cp = new CurrencyPairDTO(source.getInstrument()); - return CurrencyAmountDTO.builder() - .value(source.getOriginalAmount()) - .currency(cp.getBaseCurrency()) - .build(); + if (source.getOriginalAmount() != null && source.getInstrument() != null) { + return CurrencyAmountDTO.builder() + .value(source.getOriginalAmount()) + .currency(cp.getBaseCurrency()) + .build(); + } else { + return null; + } } @Named("mapUserTradeToTradeDTOPrice") default CurrencyAmountDTO mapUserTradeToTradeDTOPrice(UserTrade source) { CurrencyPairDTO cp = new CurrencyPairDTO(source.getInstrument()); - return CurrencyAmountDTO.builder() - .value(source.getPrice()) - .currency(cp.getQuoteCurrency()) - .build(); + if (source.getPrice() != null && source.getInstrument() != null) { + return CurrencyAmountDTO.builder() + .value(source.getPrice()) + .currency(cp.getQuoteCurrency()) + .build(); + } else { + return null; + } } @Named("mapUserTradeToTradeDTOFee") @@ -60,7 +68,7 @@ default CurrencyAmountDTO mapUserTradeToTradeDTOFee(UserTrade source) { .currency(new CurrencyDTO(source.getFeeCurrency().toString())) .build(); } else { - return CurrencyAmountDTO.ZERO; + return null; } } diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/parameters/ExchangeParameters.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/parameters/ExchangeParameters.java index df576ad86..b5dde3583 100644 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/parameters/ExchangeParameters.java +++ b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/parameters/ExchangeParameters.java @@ -25,7 +25,7 @@ @ConfigurationProperties(prefix = "cassandre.trading.bot.exchange") public class ExchangeParameters { - /** Driver class name. For example : coinbase, kraken, kucoin. */ + /** Driver class name. For example : org.knowm.xchange.coinbasepro.CoinbaseProExchange, kraken, kucoin. */ @NotEmpty(message = "Driver class name required, for example : org.knowm.xchange.coinbasepro.CoinbaseProExchange") private String driverClassName; @@ -34,7 +34,6 @@ public class ExchangeParameters { private String username; /** API passphrase. */ - @NotEmpty(message = "API passphrase required") private String passphrase; /** API key. */ diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/system/TimeProvider.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/system/TimeProvider.java deleted file mode 100644 index 475735cae..000000000 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/system/TimeProvider.java +++ /dev/null @@ -1,18 +0,0 @@ -package tech.cassandre.trading.bot.util.system; - -import java.util.Date; - -/** - * Time provider. - */ -public abstract class TimeProvider { - - /** - * Returns now. - * @return date. - */ - public static Date now() { - return new Date(); - } - -} diff --git a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/system/package-info.java b/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/system/package-info.java deleted file mode 100644 index 8e3355ec7..000000000 --- a/spring-boot-starter/autoconfigure/src/main/java/tech/cassandre/trading/bot/util/system/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * System utils. - */ -package tech.cassandre.trading.bot.util.system; diff --git a/spring-boot-starter/autoconfigure/src/main/resources/db/changelog/db.changelog-5.0.1.xml b/spring-boot-starter/autoconfigure/src/main/resources/db/changelog/db.changelog-5.0.1.xml new file mode 100644 index 000000000..bbf2e4594 --- /dev/null +++ b/spring-boot-starter/autoconfigure/src/main/resources/db/changelog/db.changelog-5.0.1.xml @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/spring-boot-starter/autoconfigure/src/main/resources/db/changelog/db.changelog-master.yaml b/spring-boot-starter/autoconfigure/src/main/resources/db/changelog/db.changelog-master.yaml index bdef5e4ac..42ba24ab9 100644 --- a/spring-boot-starter/autoconfigure/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/spring-boot-starter/autoconfigure/src/main/resources/db/changelog/db.changelog-master.yaml @@ -1,9 +1,9 @@ databaseChangeLog: - include: - file: db/changelog/db.changelog-4.0.0.xml + file: /db/changelog/db.changelog-4.0.0.xml - include: - file: db/changelog/db.changelog-4.1.0.xml + file: /db/changelog/db.changelog-4.1.0.xml - include: - file: db/changelog/db.changelog-4.1.1.xml + file: /db/changelog/db.changelog-4.1.1.xml - include: - file: db/changelog/db.changelog-5.0.0.xml \ No newline at end of file + file: /db/changelog/db.changelog-5.0.0.xml \ No newline at end of file diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/binance/MarketServiceTest.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/binance/MarketServiceTest.java index 0f722788d..f04444ea4 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/binance/MarketServiceTest.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/binance/MarketServiceTest.java @@ -15,7 +15,6 @@ import tech.cassandre.trading.bot.test.util.strategies.TestableCassandreStrategy; import java.util.Optional; -import java.util.concurrent.TimeUnit; import static java.math.BigDecimal.ZERO; import static org.awaitility.Awaitility.await; @@ -98,11 +97,10 @@ public void checkGetTicker() { @Test @Tag("integration") @DisplayName("Check ticker flux") - public void checkTickerFlux() throws InterruptedException { + public void checkTickerFlux() { tickerFlux.update(); - TimeUnit.SECONDS.sleep(WAITING_TIME_IN_SECONDS); tickerFlux.update(); - // We should have two tickers received by the strategy + // We should have four tickers received by the strategy await().untilAsserted(() -> assertEquals(4, strategy.getTickersUpdatesReceived().size())); } diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/coinbasepro/TradeServiceTest.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/coinbasepro/TradeServiceTest.java index 4191c6742..f726050de 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/coinbasepro/TradeServiceTest.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/coinbasepro/TradeServiceTest.java @@ -77,7 +77,7 @@ public void checkCreateBuySellMarketOrder() { final OrderCreationResultDTO result1 = strategy.createBuyMarketOrder(cp, new BigDecimal("0.00000001")); assertFalse(result1.isSuccessful()); assertNull(result1.getOrder()); - assertEquals("TradeService - Error calling createMarketOrder for 1E-8 ETH/BTC: org.knowm.xchange.coinbasepro.dto.CoinbaseProException: funds must be a number", result1.getErrorMessage()); + assertTrue(result1.getErrorMessage().contains("size must be a number")); assertNotNull(result1.getException()); // ============================================================================================================= @@ -137,7 +137,7 @@ public void checkCreateBuyLimitOrder() { @Test @Tag("integration") @DisplayName("Check cancel an order") - @Disabled("Seems Coinbase pro doesn't support canceling an order") + @Disabled("Not supported by coinbase") public void checkCancelOrder() { final CurrencyPairDTO cp = new CurrencyPairDTO(ETH, BTC); diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/kucoin/ExchangeServiceTest.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/kucoin/ExchangeServiceTest.java index a22165671..f9ee414a7 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/kucoin/ExchangeServiceTest.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/kucoin/ExchangeServiceTest.java @@ -28,9 +28,9 @@ "cassandre.trading.bot.exchange.passphrase=${KUCOIN_PASSPHRASE}", "cassandre.trading.bot.exchange.key=${KUCOIN_KEY}", "cassandre.trading.bot.exchange.secret=${KUCOIN_SECRET}", - "cassandre.trading.bot.exchange.rates.account=100", - "cassandre.trading.bot.exchange.rates.ticker=101", - "cassandre.trading.bot.exchange.rates.trade=102", + "cassandre.trading.bot.exchange.rates.account=1000", + "cassandre.trading.bot.exchange.rates.ticker=1001", + "cassandre.trading.bot.exchange.rates.trade=1002", "cassandre.trading.bot.database.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver", "cassandre.trading.bot.database.datasource.url=jdbc:hsqldb:mem:cassandre-database;shutdown=true", "cassandre.trading.bot.database.datasource.username=sa", diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/kucoin/MarketServiceTest.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/kucoin/MarketServiceTest.java index 094e0caa4..677fe56f9 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/kucoin/MarketServiceTest.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/kucoin/MarketServiceTest.java @@ -32,9 +32,9 @@ "cassandre.trading.bot.exchange.passphrase=${KUCOIN_PASSPHRASE}", "cassandre.trading.bot.exchange.key=${KUCOIN_KEY}", "cassandre.trading.bot.exchange.secret=${KUCOIN_SECRET}", - "cassandre.trading.bot.exchange.rates.account=100", - "cassandre.trading.bot.exchange.rates.ticker=101", - "cassandre.trading.bot.exchange.rates.trade=102", + "cassandre.trading.bot.exchange.rates.account=1000", + "cassandre.trading.bot.exchange.rates.ticker=1001", + "cassandre.trading.bot.exchange.rates.trade=1002", "cassandre.trading.bot.database.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver", "cassandre.trading.bot.database.datasource.url=jdbc:hsqldb:mem:cassandre-database;shutdown=true", "cassandre.trading.bot.database.datasource.username=sa", @@ -69,12 +69,6 @@ public void checkGetTicker() { // ask. assertNotNull(t.get().getAsk()); assertTrue(t.get().getAsk().compareTo(ZERO) > 0); - // high. - assertNotNull(t.get().getHigh()); - assertTrue(t.get().getHigh().compareTo(ZERO) > 0); - // low. - assertNotNull(t.get().getLow()); - assertTrue(t.get().getLow().compareTo(ZERO) > 0); // volume. assertNotNull(t.get().getVolume()); assertTrue(t.get().getVolume().compareTo(ZERO) > 0); diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/kucoin/TradeServiceTest.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/kucoin/TradeServiceTest.java index 37b64bc2c..3306827c7 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/kucoin/TradeServiceTest.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/kucoin/TradeServiceTest.java @@ -40,9 +40,9 @@ "cassandre.trading.bot.exchange.passphrase=${KUCOIN_PASSPHRASE}", "cassandre.trading.bot.exchange.key=${KUCOIN_KEY}", "cassandre.trading.bot.exchange.secret=${KUCOIN_SECRET}", - "cassandre.trading.bot.exchange.rates.account=100", - "cassandre.trading.bot.exchange.rates.ticker=101", - "cassandre.trading.bot.exchange.rates.trade=102", + "cassandre.trading.bot.exchange.rates.account=1000", + "cassandre.trading.bot.exchange.rates.ticker=1001", + "cassandre.trading.bot.exchange.rates.trade=1002", "cassandre.trading.bot.database.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver", "cassandre.trading.bot.database.datasource.url=jdbc:hsqldb:mem:cassandre-database;shutdown=true", "cassandre.trading.bot.database.datasource.username=sa", diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/kucoin/UserServiceTest.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/kucoin/UserServiceTest.java index 803fe5767..705338c33 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/kucoin/UserServiceTest.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/integration/kucoin/UserServiceTest.java @@ -36,9 +36,9 @@ "cassandre.trading.bot.exchange.passphrase=${KUCOIN_PASSPHRASE}", "cassandre.trading.bot.exchange.key=${KUCOIN_KEY}", "cassandre.trading.bot.exchange.secret=${KUCOIN_SECRET}", - "cassandre.trading.bot.exchange.rates.account=100", - "cassandre.trading.bot.exchange.rates.ticker=101", - "cassandre.trading.bot.exchange.rates.trade=102", + "cassandre.trading.bot.exchange.rates.account=1000", + "cassandre.trading.bot.exchange.rates.ticker=1001", + "cassandre.trading.bot.exchange.rates.trade=1002", "cassandre.trading.bot.database.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver", "cassandre.trading.bot.database.datasource.url=jdbc:hsqldb:mem:cassandre-database;shutdown=true", "cassandre.trading.bot.database.datasource.username=sa", @@ -98,7 +98,7 @@ public void checkGetUser() { assertTrue(tradeWallet.getBalance(BTC).isPresent()); assertTrue(tradeWallet.getBalance("ETH").isPresent()); assertTrue(tradeWallet.getBalance(ETH).isPresent()); - assertTrue(mainWallet.getBalance("USDT").isPresent()); + assertTrue(tradeWallet.getBalance("USDT").isPresent()); assertTrue(tradeWallet.getBalance(USDT).isPresent()); // Non existing balances. assertTrue(tradeWallet.getBalance("ANC").isEmpty()); diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/issues/v4_x/v4_0_0/Issue426TestMock.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/issues/v4_x/v4_0_0/Issue426TestMock.java index 5e80ee12b..5d275b76d 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/issues/v4_x/v4_0_0/Issue426TestMock.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/issues/v4_x/v4_0_0/Issue426TestMock.java @@ -53,13 +53,13 @@ public TickerFlux tickerFlux() { @Bean @Primary public OrderFlux orderFlux() { - return new OrderFlux(tradeService(), orderRepository); + return new OrderFlux(orderRepository, tradeService()); } @Bean @Primary public TradeFlux tradeFlux() { - return new TradeFlux(tradeService(), orderRepository, tradeRepository); + return new TradeFlux(orderRepository, tradeRepository, tradeService()); } @Bean diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/issues/v4_x/v4_0_0/Issue427TestMock.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/issues/v4_x/v4_0_0/Issue427TestMock.java index 74dbf1036..6ddc43edf 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/issues/v4_x/v4_0_0/Issue427TestMock.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/issues/v4_x/v4_0_0/Issue427TestMock.java @@ -54,13 +54,13 @@ public TickerFlux tickerFlux() { @Bean @Primary public OrderFlux orderFlux() { - return new OrderFlux(tradeService(), orderRepository); + return new OrderFlux(orderRepository, tradeService()); } @Bean @Primary public TradeFlux tradeFlux() { - return new TradeFlux(tradeService(), orderRepository, tradeRepository); + return new TradeFlux(orderRepository, tradeRepository, tradeService()); } @Bean diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/issues/v4_x/v4_2_0/Issue539Test.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/issues/v4_x/v4_2_0/Issue539Test.java index 4ee86a0f1..25a0b68b7 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/issues/v4_x/v4_2_0/Issue539Test.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/issues/v4_x/v4_2_0/Issue539Test.java @@ -38,7 +38,7 @@ public void checkExceptionInScheduledTasks() { tickerFlux.update(); tickerFlux.update(); - // We should received three tickers. + // We should received two tickers. await().untilAsserted(() -> assertEquals(2, strategy.getTickersUpdatesReceived().size())); } diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/configuration/exchange/NoConfigurationTest.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/configuration/exchange/NoConfigurationTest.java index 9314d7f43..82c503771 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/configuration/exchange/NoConfigurationTest.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/configuration/exchange/NoConfigurationTest.java @@ -49,7 +49,6 @@ public void checkErrorMessages() { e.printStackTrace(); assertTrue(message.contains("'driverClassName'")); assertTrue(message.contains("'username'")); - assertTrue(message.contains("'passphrase'")); assertTrue(message.contains("'key'")); assertTrue(message.contains("'secret'")); assertTrue(message.contains("'modes.sandbox'")); diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/configuration/exchange/UnknownExchangeTest.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/configuration/exchange/UnknownExchangeTest.java index 22d9b9c2d..a32de9ff6 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/configuration/exchange/UnknownExchangeTest.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/configuration/exchange/UnknownExchangeTest.java @@ -28,7 +28,7 @@ public void checkErrorMessages() { fail("Exception not raised"); } catch (Exception e) { final String message = ExceptionUtils.getRootCause(e).getMessage(); - assertTrue(message.contains("Impossible to find the exchange you requested : foo")); + assertTrue(message.contains("Impossible to find the exchange you requested: foo")); } } diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/domain/OrderTest.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/domain/OrderTest.java index 27e895054..f684ee460 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/domain/OrderTest.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/domain/OrderTest.java @@ -96,7 +96,8 @@ public void checkLoadOrderFromDatabase() { assertEquals(0, new BigDecimal("0.000004").compareTo(o.get().getCumulativeAmount().getValue())); assertEquals(ETH, o.get().getCumulativeAmount().getCurrency()); assertEquals("My reference 1", o.get().getUserReference()); - assertEquals(createZonedDateTime("18-11-2020"), o.get().getTimestamp()); + assertTrue(createZonedDateTime("18-11-2020").isEqual(o.get().getTimestamp())); + assertEquals(0, o.get().getTrades().size()); // Test equals. @@ -126,7 +127,7 @@ public void checkLoadOrderFromDatabase() { assertEquals(0, new BigDecimal("0.000014").compareTo(o.get().getCumulativeAmount().getValue())); assertEquals(USDT, o.get().getCumulativeAmount().getCurrency()); assertEquals("My reference 2", o.get().getUserReference()); - assertEquals(createZonedDateTime("19-11-2020"), o.get().getTimestamp()); + assertTrue(createZonedDateTime("19-11-2020").isEqual(o.get().getTimestamp())); assertEquals(0, o.get().getTrades().size()); // Check trades of orders. @@ -203,7 +204,7 @@ public void checkSaveOrderInDatabase() { assertEquals(0, new BigDecimal("1.00002").compareTo(orderInDatabase.get().getCumulativeAmount().getValue())); assertEquals(ETH_BTC.getBaseCurrency().toString(), orderInDatabase.get().getCumulativeAmount().getCurrency()); assertEquals("MY_REF_3", orderInDatabase.get().getUserReference()); - assertEquals(createZonedDateTime("01-01-2020"), orderInDatabase.get().getTimestamp()); + assertTrue(createZonedDateTime("01-01-2020").isEqual(orderInDatabase.get().getTimestamp())); // Tests for created on and updated on fields. ZonedDateTime createdOn = orderInDatabase.get().getCreatedOn(); assertNotNull(createdOn); @@ -232,7 +233,7 @@ public void checkSaveOrderInDatabase() { assertEquals(0, new BigDecimal("1.00002").compareTo(order.get().getCumulativeAmount().getValue())); assertEquals(ETH_BTC.getBaseCurrency(), order.get().getCumulativeAmount().getCurrency()); assertEquals("MY_REF_3", order.get().getUserReference()); - assertEquals(createZonedDateTime("01-01-2020"), order.get().getTimestamp()); + assertTrue(createZonedDateTime("01-01-2020").isEqual(order.get().getTimestamp())); // ============================================================================================================= // Updating the order and adding a trade - first time. @@ -307,6 +308,7 @@ public void checkSaveOrderInDatabase() { /** * Retrieve order from database. + * * @param id order id * @return order */ diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/domain/PositionTest.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/domain/PositionTest.java index 63cb747a0..21fdcd05b 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/domain/PositionTest.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/domain/PositionTest.java @@ -310,9 +310,12 @@ public void checkSavedNewPosition() { assertNull(p6.getClosingOrder()); // If we wait a bit, the order and trade will arrive and the position status will be OPENED. - orderFlux.update(); - tradeFlux.update(); - await().untilAsserted(() -> assertEquals(OPENED, getPosition(6L).getStatus())); + await().untilAsserted(() -> { + orderFlux.update(); + tradeFlux.update(); + assertEquals(OPENED, getPosition(6L).getStatus()); + }); + p6 = getPosition(6L); assertEquals(6L, p6.getId()); assertEquals(6L, p6.getPositionId()); @@ -417,8 +420,11 @@ public void checkSavedDataDuringPositionLifecycle() { // ============================================================================================================= // We should now be OPENED. // We are in dry mode, we wait for order and trade to arrive, position will now be opened. - tradeFlux.update(); - await().untilAsserted(() -> assertEquals(OPENED, getPosition(positionId).getStatus())); + await().untilAsserted(() -> { + orderFlux.update(); + tradeFlux.update(); + assertEquals(OPENED, getPosition(positionId).getStatus()); + }); // Check saved position in database. p = getPosition(positionId); @@ -557,6 +563,7 @@ public void checkSavedDataDuringPositionLifecycle() { /** * Retrieve position from database. + * * @param id position id * @return position */ @@ -571,6 +578,7 @@ private Position getPosition(final long id) { /** * Retrieve position from database. + * * @param id position id * @return position */ diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/domain/StrategyTest.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/domain/StrategyTest.java index d9402c522..8a0104c5e 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/domain/StrategyTest.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/domain/StrategyTest.java @@ -20,7 +20,7 @@ import static tech.cassandre.trading.bot.test.util.junit.configuration.ConfigurationExtension.PARAMETER_EXCHANGE_DRY; @SpringBootTest -@DisplayName("Domain - Strategy - Creation") +@DisplayName("Domain - Strategy") @Configuration({ @Property(key = PARAMETER_EXCHANGE_DRY, value = "false") }) diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/domain/TradeTest.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/domain/TradeTest.java index 62208d9d8..f2d8f9cff 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/domain/TradeTest.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/domain/TradeTest.java @@ -93,7 +93,7 @@ public void checkLoadTradeFromDatabase() { assertEquals(0, new BigDecimal("1").compareTo(t.get().getFee().getValue())); assertEquals(USDT, t.get().getFee().getCurrency()); assertEquals("Trade 01", t.get().getUserReference()); - assertEquals(createZonedDateTime("01-08-2020"), t.get().getTimestamp()); + assertTrue(createZonedDateTime("01-08-2020").isEqual(t.get().getTimestamp())); // Test equals. Optional tBis = strategy.getTradeByTradeId("BACKUP_TRADE_01"); @@ -116,7 +116,7 @@ public void checkLoadTradeFromDatabase() { assertEquals(0, new BigDecimal("2").compareTo(t.get().getFee().getValue())); assertEquals(USDT, t.get().getFee().getCurrency()); assertEquals("Trade 02", t.get().getUserReference()); - assertEquals(createZonedDateTime("02-08-2020"), t.get().getTimestamp()); + assertTrue(createZonedDateTime("02-08-2020").isEqual(t.get().getTimestamp())); // ============================================================================================================= // Check trade 03. @@ -134,7 +134,7 @@ public void checkLoadTradeFromDatabase() { assertEquals(0, new BigDecimal("3").compareTo(t.get().getFee().getValue())); assertEquals(USDT, t.get().getFee().getCurrency()); assertEquals("Trade 03", t.get().getUserReference()); - assertEquals(createZonedDateTime("03-08-2020"), t.get().getTimestamp()); + assertTrue(createZonedDateTime("03-08-2020").isEqual(t.get().getTimestamp())); // ============================================================================================================= // Check trade 04. @@ -152,7 +152,7 @@ public void checkLoadTradeFromDatabase() { assertEquals(0, new BigDecimal("4").compareTo(t.get().getFee().getValue())); assertEquals(USDT, t.get().getFee().getCurrency()); assertEquals("Trade 04", t.get().getUserReference()); - assertEquals(createZonedDateTime("04-08-2020"), t.get().getTimestamp()); + assertTrue(createZonedDateTime("04-08-2020").isEqual(t.get().getTimestamp())); // ============================================================================================================= // Check trade 05. @@ -170,7 +170,7 @@ public void checkLoadTradeFromDatabase() { assertEquals(0, new BigDecimal("5").compareTo(t.get().getFee().getValue())); assertEquals(USD, t.get().getFee().getCurrency()); assertEquals("Trade 05", t.get().getUserReference()); - assertEquals(createZonedDateTime("05-08-2020"), t.get().getTimestamp()); + assertTrue(createZonedDateTime("05-08-2020").isEqual(t.get().getTimestamp())); } @Test @@ -219,7 +219,7 @@ public void checkSaveTradeInDatabase() { assertEquals(0, tradeInDatabase.get().getFee().getValue().compareTo(new BigDecimal("3.300003"))); assertEquals("BTC", tradeInDatabase.get().getFee().getCurrency()); assertEquals("My reference !", tradeInDatabase.get().getUserReference()); - assertEquals(createZonedDateTime("01-09-2020"), tradeInDatabase.get().getTimestamp()); + assertTrue(createZonedDateTime("01-09-2020").isEqual(tradeInDatabase.get().getTimestamp())); // Tests for created on and updated on fields. ZonedDateTime createdOn = tradeInDatabase.get().getCreatedOn(); @@ -244,7 +244,7 @@ public void checkSaveTradeInDatabase() { assertEquals(0, tradeDTO.getFee().getValue().compareTo(new BigDecimal("3.300003"))); assertEquals(BTC, tradeDTO.getFee().getCurrency()); assertEquals("My reference !", tradeDTO.getUserReference()); - assertEquals(createZonedDateTime("01-09-2020"), tradeDTO.getTimestamp()); + assertTrue(createZonedDateTime("01-09-2020").isEqual(tradeDTO.getTimestamp())); // ============================================================================================================= // Updating the trade - first time. @@ -260,8 +260,10 @@ public void checkSaveTradeInDatabase() { .timestamp(createZonedDateTime("01-09-2020")) .build()); await().untilAsserted(() -> assertEquals(2, strategy.getTradesUpdatesReceived().size())); - await().untilAsserted(() -> assertNotNull(tradeRepository.findByTradeId("BACKUP_TRADE_11").get().getUpdatedOn())); - assertEquals(createdOn, tradeRepository.findByTradeId("BACKUP_TRADE_11").get().getCreatedOn()); + Optional trade11 = tradeRepository.findByTradeId("BACKUP_TRADE_11"); + assertTrue(trade11.isPresent()); + assertNotNull(trade11.get().getUpdatedOn()); + assertEquals(createdOn, trade11.get().getCreatedOn()); ZonedDateTime updatedOn = tradeInDatabase.get().getCreatedOn(); // ============================================================================================================= @@ -276,8 +278,11 @@ public void checkSaveTradeInDatabase() { .timestamp(createZonedDateTime("01-09-2020")) .fee(new CurrencyAmountDTO(new BigDecimal("3.300003"), BTC)) .build()); - await().untilAsserted(() -> assertTrue(updatedOn.isBefore(tradeRepository.findByTradeId("BACKUP_TRADE_11").get().getUpdatedOn()))); - assertEquals(createdOn, tradeRepository.findByTradeId("BACKUP_TRADE_11").get().getCreatedOn()); + await().untilAsserted(() -> assertEquals(3, strategy.getTradesUpdatesReceived().size())); + trade11 = tradeRepository.findByTradeId("BACKUP_TRADE_11"); + assertTrue(trade11.isPresent()); + assertTrue(updatedOn.isBefore(trade11.get().getUpdatedOn())); + assertEquals(createdOn, trade11.get().getCreatedOn()); // We check if we still have the strategy set. final Optional optionalTrade = strategy.getTradeByTradeId("BACKUP_TRADE_11"); assertTrue(optionalTrade.isPresent()); diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/repository/OrderRepositoryTest.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/repository/OrderRepositoryTest.java index 5edc6603a..a7e1a8896 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/repository/OrderRepositoryTest.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/repository/OrderRepositoryTest.java @@ -65,7 +65,7 @@ public void checkImportedOrders() { assertEquals(NEW, o.getStatus()); assertEquals(0, new BigDecimal("0.000004").compareTo(o.getCumulativeAmount().getValue())); assertEquals("My reference 1", o.getUserReference()); - assertEquals(createZonedDateTime("18-11-2020"), o.getTimestamp()); + assertTrue(createZonedDateTime("18-11-2020").isEqual(o.getTimestamp())); // Retrieving order 1 with findByOrderId(). Optional oBis = orderRepository.findByOrderId("BACKUP_ORDER_01"); @@ -91,7 +91,7 @@ public void checkImportedOrders() { assertEquals(PENDING_NEW, o.getStatus()); assertEquals(0, new BigDecimal("0.000014").compareTo(o.getCumulativeAmount().getValue())); assertEquals("My reference 2", o.getUserReference()); - assertEquals(createZonedDateTime("19-11-2020"), o.getTimestamp()); + assertTrue(createZonedDateTime("19-11-2020").isEqual(o.getTimestamp())); // Retrieving order 2 with findByOrderId(). oBis = orderRepository.findByOrderId("BACKUP_ORDER_02"); diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/repository/TradeRepositoryTest.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/repository/TradeRepositoryTest.java index 9df53bace..7029727e0 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/repository/TradeRepositoryTest.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/repository/TradeRepositoryTest.java @@ -55,7 +55,7 @@ public void checkImportedTrades() { assertEquals(0, t.getFee().getValue().compareTo(new BigDecimal("1"))); assertEquals("USDT", t.getFee().getCurrency()); assertEquals("Trade 01", t.getUserReference()); - assertEquals(createZonedDateTime("01-08-2020"), t.getTimestamp()); + assertTrue(createZonedDateTime("01-08-2020").isEqual(t.getTimestamp())); // Retrieving order 1 with findByOrderId(). Optional tBis = tradeRepository.findByTradeId("BACKUP_TRADE_01"); @@ -76,7 +76,7 @@ public void checkImportedTrades() { assertEquals(0, t.getFee().getValue().compareTo(new BigDecimal("2"))); assertEquals("USDT", t.getFee().getCurrency()); assertEquals("Trade 02", t.getUserReference()); - assertEquals(createZonedDateTime("02-08-2020"), t.getTimestamp()); + assertTrue(createZonedDateTime("02-08-2020").isEqual(t.getTimestamp())); // Retrieving order 2 with findByOrderId(). tBis = tradeRepository.findByTradeId("BACKUP_TRADE_02"); @@ -97,7 +97,7 @@ public void checkImportedTrades() { assertEquals(0, t.getFee().getValue().compareTo(new BigDecimal("3"))); assertEquals("USDT", t.getFee().getCurrency()); assertEquals("Trade 03", t.getUserReference()); - assertEquals(createZonedDateTime("03-08-2020"), t.getTimestamp()); + assertTrue(createZonedDateTime("03-08-2020").isEqual(t.getTimestamp())); // Retrieving order 3 with findByOrderId(). tBis = tradeRepository.findByTradeId("BACKUP_TRADE_03"); @@ -118,7 +118,7 @@ public void checkImportedTrades() { assertEquals(0, t.getFee().getValue().compareTo(new BigDecimal("4"))); assertEquals("USDT", t.getFee().getCurrency()); assertEquals("Trade 04", t.getUserReference()); - assertEquals(createZonedDateTime("04-08-2020"), t.getTimestamp()); + assertTrue(createZonedDateTime("04-08-2020").isEqual(t.getTimestamp())); // Retrieving order 4 with findByOrderId(). tBis = tradeRepository.findByTradeId("BACKUP_TRADE_04"); @@ -139,7 +139,7 @@ public void checkImportedTrades() { assertEquals(0, t.getFee().getValue().compareTo(new BigDecimal("5"))); assertEquals("USD", t.getFee().getCurrency()); assertEquals("Trade 05", t.getUserReference()); - assertEquals(createZonedDateTime("05-08-2020"), t.getTimestamp()); + assertTrue(createZonedDateTime("05-08-2020").isEqual(t.getTimestamp())); // Retrieving order 5 with findByOrderId(). tBis = tradeRepository.findByTradeId("BACKUP_TRADE_05"); diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/services/dry/PositionServiceForceClosingTest.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/services/dry/PositionServiceForceClosingTest.java index 1c977858b..9d4867317 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/services/dry/PositionServiceForceClosingTest.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/services/dry/PositionServiceForceClosingTest.java @@ -81,9 +81,11 @@ public void checkForceClosing() { // First: because of position creation. // Second: order update with status to NEW. // Third: trade corresponding to the order arrives. - orderFlux.update(); - tradeFlux.update(); - await().untilAsserted(() -> assertEquals(OPENED, getPositionDTO(position1Id).getStatus())); + await().untilAsserted(() -> { + orderFlux.update(); + tradeFlux.update(); + assertEquals(OPENED, getPositionDTO(position1Id).getStatus()); + }); // ============================================================================================================= // Step 2 - Creates position 2 (ETH_USDT, 0.0002, 20% stop loss, price of 0.3). @@ -101,9 +103,11 @@ public void checkForceClosing() { // First: because of position creation. // Second: order update with status to NEW. // Third: trade corresponding to the order arrives. - orderFlux.update(); - tradeFlux.update(); - await().untilAsserted(() -> assertEquals(OPENED, getPositionDTO(position2Id).getStatus())); + await().untilAsserted(() -> { + orderFlux.update(); + tradeFlux.update(); + assertEquals(OPENED, getPositionDTO(position2Id).getStatus()); + }); // ============================================================================================================= // Tickers are coming. @@ -123,7 +127,7 @@ public void checkForceClosing() { // We will force closing of position 2. strategy.closePosition(position2Id); - // New tickers will noy trigger close. + // New tickers will not trigger close. tickerFlux.emitValue(TickerDTO.builder().currencyPair(ETH_BTC).last(new BigDecimal("0.32")).build()); tickerFlux.emitValue(TickerDTO.builder().currencyPair(ETH_USDT).last(new BigDecimal("0.32")).build()); await().untilAsserted(() -> { diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/services/dry/TradeServiceTest.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/services/dry/TradeServiceTest.java index 5e2a0b717..53a896f8c 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/services/dry/TradeServiceTest.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/services/dry/TradeServiceTest.java @@ -29,7 +29,6 @@ import static tech.cassandre.trading.bot.dto.trade.OrderStatusDTO.NEW; import static tech.cassandre.trading.bot.dto.trade.OrderTypeDTO.ASK; import static tech.cassandre.trading.bot.dto.trade.OrderTypeDTO.BID; -import static tech.cassandre.trading.bot.dto.util.CurrencyAmountDTO.ZERO; import static tech.cassandre.trading.bot.test.util.junit.configuration.ConfigurationExtension.PARAMETER_EXCHANGE_DRY; @SpringBootTest @@ -106,7 +105,7 @@ public void checkCreateBuyAndSellOrder() { assertEquals(ETH_BTC.getBaseCurrency(), trade01.get().getAmount().getCurrency()); assertEquals(0, new BigDecimal("0.2").compareTo(trade01.get().getPrice().getValue())); assertEquals(ETH_BTC.getQuoteCurrency(), trade01.get().getPrice().getCurrency()); - assertEquals(ZERO, trade01.get().getFee()); + assertNull(trade01.get().getFee()); assertNull(trade01.get().getUserReference()); assertNotNull(trade01.get().getTimestamp()); @@ -151,7 +150,7 @@ public void checkCreateBuyAndSellOrder() { assertEquals(ETH_BTC.getBaseCurrency(), trade02.get().getAmount().getCurrency()); assertEquals(0, new BigDecimal("0.2").compareTo(trade02.get().getPrice().getValue())); assertEquals(ETH_BTC.getQuoteCurrency(), trade02.get().getPrice().getCurrency()); - assertEquals(ZERO, trade02.get().getFee()); + assertNull(trade02.get().getFee()); assertNull(trade02.get().getUserReference()); assertNotNull(trade02.get().getTimestamp()); } diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/services/dry/UserServiceTest.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/services/dry/UserServiceTest.java index 93de852e0..07fe7d9b1 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/services/dry/UserServiceTest.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/services/dry/UserServiceTest.java @@ -8,7 +8,9 @@ import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ActiveProfiles; import tech.cassandre.trading.bot.batch.AccountFlux; +import tech.cassandre.trading.bot.batch.OrderFlux; import tech.cassandre.trading.bot.batch.TickerFlux; +import tech.cassandre.trading.bot.batch.TradeFlux; import tech.cassandre.trading.bot.dto.market.TickerDTO; import tech.cassandre.trading.bot.dto.trade.OrderCreationResultDTO; import tech.cassandre.trading.bot.dto.trade.TradeDTO; @@ -16,7 +18,8 @@ import tech.cassandre.trading.bot.dto.user.BalanceDTO; import tech.cassandre.trading.bot.dto.user.UserDTO; import tech.cassandre.trading.bot.dto.util.CurrencyPairDTO; -import tech.cassandre.trading.bot.service.TradeService; +import tech.cassandre.trading.bot.repository.OrderRepository; +import tech.cassandre.trading.bot.repository.TradeRepository; import tech.cassandre.trading.bot.service.UserService; import tech.cassandre.trading.bot.test.services.dry.mocks.TradeServiceDryModeTestMock; import tech.cassandre.trading.bot.test.util.junit.BaseTest; @@ -28,7 +31,6 @@ import java.util.Optional; import static org.awaitility.Awaitility.await; -import static org.awaitility.Awaitility.with; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -57,10 +59,13 @@ public class UserServiceTest extends BaseTest { @Autowired - private UserService userService; + private OrderRepository orderRepository; + + @Autowired + private TradeRepository tradeRepository; @Autowired - private TradeService tradeService; + private UserService userService; @Autowired private TickerFlux tickerFlux; @@ -68,6 +73,12 @@ public class UserServiceTest extends BaseTest { @Autowired private AccountFlux accountFlux; + @Autowired + private OrderFlux orderFlux; + + @Autowired + private TradeFlux tradeFlux; + @Autowired private LargeTestableCassandreStrategy strategy; @@ -197,9 +208,15 @@ public void checkBalancesUpdate() { // Testing the trade. // Amount => 0.02 // Price => 0.032666 - with().await().until(() -> tradeService.getTrades().stream().anyMatch(t -> t.getOrderId().equals(buyMarketOrder.getOrder().getOrderId()))); - final Optional buyingTrade = tradeService.getTrades() + await().untilAsserted(() -> { + orderFlux.update(); + tradeFlux.update(); + assertTrue(orderRepository.findByOrderId(buyMarketOrder.getOrder().getOrderId()).isPresent()); + assertTrue(tradeRepository.findByOrderByTimestampAsc().stream().anyMatch(t -> t.getOrder().getOrderId().equals(buyMarketOrder.getOrder().getOrderId()))); + }); + final Optional buyingTrade = tradeRepository.findByOrderByTimestampAsc() .stream() + .map(tradeMapper::mapToTradeDTO) .filter(t -> t.getOrderId().equals(buyMarketOrder.getOrder().getOrderId())).findFirst(); assertTrue(buyingTrade.isPresent()); assertEquals(BID, buyingTrade.get().getType()); @@ -249,9 +266,15 @@ public void checkBalancesUpdate() { // Testing the trade. // Amount => 0.02 // Price => 0.032466 - with().await().until(() -> tradeService.getTrades().stream().anyMatch(t -> t.getOrderId().equals(sellMarketOrder.getOrder().getOrderId()))); - final Optional sellingTrade = tradeService.getTrades() + await().untilAsserted(() -> { + orderFlux.update(); + tradeFlux.update(); + assertTrue(orderRepository.findByOrderId(sellMarketOrder.getOrder().getOrderId()).isPresent()); + assertTrue(tradeRepository.findByOrderByTimestampAsc().stream().anyMatch(t -> t.getOrder().getOrderId().equals(sellMarketOrder.getOrder().getOrderId()))); + }); + final Optional sellingTrade = tradeRepository.findByOrderByTimestampAsc() .stream() + .map(tradeMapper::mapToTradeDTO) .filter(t -> t.getOrderId().equals(sellMarketOrder.getOrder().getOrderId())).findFirst(); assertTrue(sellingTrade.isPresent()); assertEquals(ASK, sellingTrade.get().getType()); @@ -346,7 +369,6 @@ public void checkSellingError() { // Selling with a currency we don't have. final OrderCreationResultDTO sellMarketOrder1 = strategy.createSellMarketOrder(new CurrencyPairDTO(ETH, EUR), new BigDecimal("1000")); assertFalse(sellMarketOrder1.isSuccessful()); - System.out.println("=> " + sellMarketOrder1.getErrorMessage()); assertTrue(sellMarketOrder1.getErrorMessage().contains("Not enough assets")); // ============================================================================================================= diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/services/xchange/PositionServiceTest.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/services/xchange/PositionServiceTest.java index 7632765d1..19a852f48 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/services/xchange/PositionServiceTest.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/services/xchange/PositionServiceTest.java @@ -643,7 +643,6 @@ public void checkLowestHighestAndLatestGain() { // We had 2 positions updates (Closing then closed). // +1 with trade arriving tickerFlux.emitValue(TickerDTO.builder().currencyPair(ETH_BTC).last(new BigDecimal("0.18")).build()); - // TODO Was 6 and then passed to 4! await().untilAsserted(() -> assertEquals(4, strategy.getPositionsUpdatesReceived().size())); position1 = getPositionDTO(position1Id); assertTrue(position1.getLowestCalculatedGain().isPresent()); diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/services/xchange/mocks/RatesTestMock.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/services/xchange/mocks/RatesTestMock.java index e7141dac5..61f4077d2 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/services/xchange/mocks/RatesTestMock.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/services/xchange/mocks/RatesTestMock.java @@ -69,14 +69,14 @@ public TickerFlux tickerFlux() { @Bean @Primary public OrderFlux orderFlux() { - return new OrderFlux(tradeService(), orderRepository); + return new OrderFlux(orderRepository, tradeService()); } @Bean @Primary public TradeFlux tradeFlux() { - return new TradeFlux(tradeService(), orderRepository,tradeRepository); + return new TradeFlux(orderRepository, tradeRepository, tradeService()); } @Bean diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/strategy/basic/BasicCassandreStrategyTestMock.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/strategy/basic/BasicCassandreStrategyTestMock.java index 351a94df9..e3c782474 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/strategy/basic/BasicCassandreStrategyTestMock.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/strategy/basic/BasicCassandreStrategyTestMock.java @@ -71,13 +71,13 @@ public AccountFlux accountFlux() { @Bean @Primary public OrderFlux orderFlux() { - return new OrderFlux(tradeService(), orderRepository); + return new OrderFlux(orderRepository, tradeService()); } @Bean @Primary public TradeFlux tradeFlux() { - return new TradeFlux(tradeService(), orderRepository, tradeRepository); + return new TradeFlux(orderRepository, tradeRepository, tradeService()); } @Bean diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/strategy/multiple/MultipleStrategiesTest.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/strategy/multiple/MultipleStrategiesTest.java index 134b57364..e3fab4743 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/strategy/multiple/MultipleStrategiesTest.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/strategy/multiple/MultipleStrategiesTest.java @@ -100,6 +100,7 @@ public class MultipleStrategiesTest extends BaseTest { @Test @DisplayName("Check multiple strategies behavior") public void checkMultipleStrategyBehavior() { + // TODO Check every order updates count. //============================================================================================================== // Strategies tested. // Strategy 1 - Requesting BTC/USDT. @@ -232,9 +233,11 @@ public void checkMultipleStrategyBehavior() { assertTrue(position1Result.isSuccessful()); final long position1Id = position1Result.getPosition().getId(); final long position1PositionId = position1Result.getPosition().getPositionId(); - orderFlux.update(); - tradeFlux.update(); - await().untilAsserted(() -> assertEquals(OPENED, getPositionDTO(position1Id).getStatus())); + await().untilAsserted(() -> { + orderFlux.update(); + tradeFlux.update(); + assertEquals(OPENED, getPositionDTO(position1Id).getStatus()); + }); // Check positionId & positionId. assertEquals(1, position1Id); @@ -244,7 +247,7 @@ public void checkMultipleStrategyBehavior() { // For strategy 1: // Positions updates 3 : Position created in OPENING, move to OPENED, Updated order. // Positions status updates 2 : OPENING and then OPENED. - await().untilAsserted(() -> assertEquals(3, strategy1.getPositionsUpdatesReceived().size())); + 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())); @@ -252,7 +255,7 @@ public void checkMultipleStrategyBehavior() { await().untilAsserted(() -> assertEquals(0, strategy3.getPositionsUpdatesReceived().size())); // Check onOrderUpdate(). - await().untilAsserted(() -> assertEquals(1, strategy1.getOrdersUpdatesReceived().size())); + await().untilAsserted(() -> assertEquals(2, strategy1.getOrdersUpdatesReceived().size())); await().untilAsserted(() -> assertEquals(0, strategy2.getOrdersUpdatesReceived().size())); await().untilAsserted(() -> assertEquals(0, strategy3.getOrdersUpdatesReceived().size())); @@ -297,9 +300,11 @@ public void checkMultipleStrategyBehavior() { assertTrue(position2Result.isSuccessful()); final long position2Id = position2Result.getPosition().getId(); final long position2PositionId = position2Result.getPosition().getPositionId(); - orderFlux.update(); - tradeFlux.update(); - await().untilAsserted(() -> assertEquals(OPENED, getPositionDTO(position2Id).getStatus())); + await().untilAsserted(() -> { + orderFlux.update(); + tradeFlux.update(); + assertEquals(OPENED, getPositionDTO(position2Id).getStatus()); + }); // Check positionId & positionId. assertEquals(2, position2Id); @@ -309,16 +314,16 @@ public void checkMultipleStrategyBehavior() { // For strategy 2: // Positions updates 3 : Position created in OPENING, move to OPENED, Updated order. // Positions status updates 2 : OPENING and then OPENED. - await().untilAsserted(() -> assertEquals(3, strategy1.getPositionsUpdatesReceived().size())); + await().untilAsserted(() -> assertEquals(4, strategy1.getPositionsUpdatesReceived().size())); await().untilAsserted(() -> assertEquals(2, strategy1.getPositionsStatusUpdatesReceived().size())); - await().untilAsserted(() -> assertEquals(3, strategy2.getPositionsUpdatesReceived().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(). - await().untilAsserted(() -> assertEquals(1, strategy1.getOrdersUpdatesReceived().size())); - await().untilAsserted(() -> assertEquals(1, strategy2.getOrdersUpdatesReceived().size())); + await().untilAsserted(() -> assertEquals(2, strategy1.getOrdersUpdatesReceived().size())); + await().untilAsserted(() -> assertEquals(2, strategy2.getOrdersUpdatesReceived().size())); await().untilAsserted(() -> assertEquals(0, strategy3.getOrdersUpdatesReceived().size())); // Check onTradeUpdate(). @@ -368,9 +373,11 @@ public void checkMultipleStrategyBehavior() { assertTrue(position3Result.isSuccessful()); final long position3Id = position3Result.getPosition().getId(); final long position3PositionId = position3Result.getPosition().getPositionId(); - orderFlux.update(); - tradeFlux.update(); - await().untilAsserted(() -> assertEquals(OPENED, getPositionDTO(position3Id).getStatus())); + await().untilAsserted(() -> { + orderFlux.update(); + tradeFlux.update(); + assertEquals(OPENED, getPositionDTO(position3Id).getStatus()); + }); // - Creating one position on ETH/USDT (0.1 ETH for 200 USDT). final PositionCreationResultDTO position4Result = strategy3.createLongPosition(ETH_USDT, @@ -379,9 +386,11 @@ public void checkMultipleStrategyBehavior() { assertTrue(position4Result.isSuccessful()); final long position4Id = position4Result.getPosition().getId(); final long position4PositionId = position4Result.getPosition().getPositionId(); - orderFlux.update(); - tradeFlux.update(); - await().untilAsserted(() -> assertEquals(OPENED, getPositionDTO(position4Id).getStatus())); + await().untilAsserted(() -> { + orderFlux.update(); + tradeFlux.update(); + assertEquals(OPENED, getPositionDTO(position4Id).getStatus()); + }); // Check positionId & positionId. assertEquals(3, position3Id); @@ -390,17 +399,17 @@ public void checkMultipleStrategyBehavior() { assertEquals(2, position4PositionId); // Check onPositionUpdate() & onPositionStatusUpdate(). - await().untilAsserted(() -> assertEquals(4, strategy1.getPositionsUpdatesReceived().size())); + await().untilAsserted(() -> assertEquals(5, strategy1.getPositionsUpdatesReceived().size())); await().untilAsserted(() -> assertEquals(2, strategy1.getPositionsStatusUpdatesReceived().size())); - await().untilAsserted(() -> assertEquals(3, strategy2.getPositionsUpdatesReceived().size())); + await().untilAsserted(() -> assertEquals(4, strategy2.getPositionsUpdatesReceived().size())); await().untilAsserted(() -> assertEquals(2, strategy2.getPositionsStatusUpdatesReceived().size())); - await().untilAsserted(() -> assertEquals(6, strategy3.getPositionsUpdatesReceived().size())); + await().untilAsserted(() -> assertEquals(8, strategy3.getPositionsUpdatesReceived().size())); await().untilAsserted(() -> assertEquals(4, strategy3.getPositionsStatusUpdatesReceived().size())); // Check onOrderUpdate(). - await().untilAsserted(() -> assertEquals(1, strategy1.getOrdersUpdatesReceived().size())); - await().untilAsserted(() -> assertEquals(1, strategy2.getOrdersUpdatesReceived().size())); - await().untilAsserted(() -> assertEquals(2, strategy3.getOrdersUpdatesReceived().size())); + 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())); @@ -480,9 +489,11 @@ public void checkMultipleStrategyBehavior() { await().untilAsserted(() -> assertEquals(OPENED, getPositionDTO(position3Id).getStatus())); tickerFlux.emitValue(TickerDTO.builder().currencyPair(BTC_USDT).last(new BigDecimal("20000")).build()); await().untilAsserted(() -> assertEquals(CLOSING, getPositionDTO(position3Id).getStatus())); - orderFlux.update(); - tradeFlux.update(); - await().untilAsserted(() -> assertEquals(CLOSED, getPositionDTO(position3Id).getStatus())); + await().untilAsserted(() -> { + orderFlux.update(); + tradeFlux.update(); + assertEquals(CLOSED, getPositionDTO(position3Id).getStatus()); + }); // Check position status. assertEquals(OPENED, getPositionDTO(position1Id).getStatus()); @@ -491,17 +502,17 @@ public void checkMultipleStrategyBehavior() { assertEquals(OPENED, getPositionDTO(position4Id).getStatus()); // Check onPositionUpdate() & onPositionStatusUpdate(). - await().untilAsserted(() -> assertEquals(5, strategy1.getPositionsUpdatesReceived().size())); + await().untilAsserted(() -> assertEquals(6, strategy1.getPositionsUpdatesReceived().size())); await().untilAsserted(() -> assertEquals(2, strategy1.getPositionsStatusUpdatesReceived().size())); - await().untilAsserted(() -> assertEquals(3, strategy2.getPositionsUpdatesReceived().size())); + await().untilAsserted(() -> assertEquals(4, strategy2.getPositionsUpdatesReceived().size())); await().untilAsserted(() -> assertEquals(2, strategy2.getPositionsStatusUpdatesReceived().size())); - await().untilAsserted(() -> assertEquals(9, strategy3.getPositionsUpdatesReceived().size())); + await().untilAsserted(() -> assertEquals(12, strategy3.getPositionsUpdatesReceived().size())); await().untilAsserted(() -> assertEquals(6, strategy3.getPositionsStatusUpdatesReceived().size())); // Check onOrderUpdate(). - await().untilAsserted(() -> assertEquals(1, strategy1.getOrdersUpdatesReceived().size())); - await().untilAsserted(() -> assertEquals(1, strategy2.getOrdersUpdatesReceived().size())); - await().untilAsserted(() -> assertEquals(3, strategy3.getOrdersUpdatesReceived().size())); + await().untilAsserted(() -> assertEquals(2, strategy1.getOrdersUpdatesReceived().size())); + await().untilAsserted(() -> assertEquals(2, strategy2.getOrdersUpdatesReceived().size())); + await().untilAsserted(() -> assertEquals(6, strategy3.getOrdersUpdatesReceived().size())); // Check onTradeUpdate(). await().untilAsserted(() -> assertEquals(1, strategy1.getTradesUpdatesReceived().size())); diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/strategy/ta4j/BasicTa4jCassandreStrategyTestMock.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/strategy/ta4j/BasicTa4jCassandreStrategyTestMock.java index 11adc5d01..50f732815 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/strategy/ta4j/BasicTa4jCassandreStrategyTestMock.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/strategy/ta4j/BasicTa4jCassandreStrategyTestMock.java @@ -71,13 +71,13 @@ public AccountFlux accountFlux() { @Bean @Primary public OrderFlux orderFlux() { - return new OrderFlux(tradeService(), orderRepository); + return new OrderFlux(orderRepository, tradeService()); } @Bean @Primary public TradeFlux tradeFlux() { - return new TradeFlux(tradeService(), orderRepository, tradeRepository); + return new TradeFlux(orderRepository, tradeRepository, tradeService()); } @Bean diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/ta4j/BarContextTest.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/ta4j/BarContextTest.java index 9250818f8..dbb562955 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/ta4j/BarContextTest.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/ta4j/BarContextTest.java @@ -91,8 +91,7 @@ public void testUpdateWithoutClosePrice() { } ZonedDateTime getTime(String value) { - return LocalDateTime.parse(value, dateTimeFormatter) - .atZone(ZoneId.systemDefault()); + return LocalDateTime.parse(value, dateTimeFormatter).atZone(ZoneId.systemDefault()); } } \ No newline at end of file diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/util/junit/BaseMock.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/util/junit/BaseMock.java index 5131c45ed..4e53afd7d 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/util/junit/BaseMock.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/util/junit/BaseMock.java @@ -43,12 +43,12 @@ */ public class BaseMock extends BaseTest { + /** Service rate. */ + private static final int SERVICE_RATE = 900; + @Autowired private ApplicationContext applicationContext; - /** Service rate. */ - public static final int SERVICE_RATE = 900; - @Autowired protected OrderRepository orderRepository; @@ -73,14 +73,14 @@ public TickerFlux tickerFlux() { @Bean @Primary public OrderFlux orderFlux() { - return new OrderFlux(tradeService(), orderRepository); + return new OrderFlux(orderRepository, tradeService()); } @Bean @Primary public TradeFlux tradeFlux() { - return new TradeFlux(tradeService(), orderRepository,tradeRepository); + return new TradeFlux(orderRepository,tradeRepository, tradeService()); } @Bean diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/util/junit/BaseTest.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/util/junit/BaseTest.java index 38b06513f..7e1647f5d 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/util/junit/BaseTest.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/util/junit/BaseTest.java @@ -3,15 +3,23 @@ import org.awaitility.Awaitility; import org.knowm.xchange.currency.Currency; import org.knowm.xchange.currency.CurrencyPair; +import org.mapstruct.factory.Mappers; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import tech.cassandre.trading.bot.dto.market.TickerDTO; import tech.cassandre.trading.bot.dto.strategy.StrategyDTO; import tech.cassandre.trading.bot.dto.util.CurrencyPairDTO; +import tech.cassandre.trading.bot.util.mapper.AccountMapper; +import tech.cassandre.trading.bot.util.mapper.CurrencyMapper; +import tech.cassandre.trading.bot.util.mapper.OrderMapper; +import tech.cassandre.trading.bot.util.mapper.PositionMapper; +import tech.cassandre.trading.bot.util.mapper.StrategyMapper; +import tech.cassandre.trading.bot.util.mapper.TickerMapper; +import tech.cassandre.trading.bot.util.mapper.TradeMapper; +import tech.cassandre.trading.bot.util.mapper.UtilMapper; import java.math.BigDecimal; import java.time.Instant; -import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; @@ -33,6 +41,30 @@ */ public class BaseTest { + /** Type mapper. */ + protected final UtilMapper utilMapper = Mappers.getMapper(UtilMapper.class); + + /** Currency mapper. */ + protected final CurrencyMapper currencyMapper = Mappers.getMapper(CurrencyMapper.class); + + /** Strategy mapper. */ + protected final StrategyMapper strategyMapper = Mappers.getMapper(StrategyMapper.class); + + /** Account mapper. */ + protected final AccountMapper accountMapper = Mappers.getMapper(AccountMapper.class); + + /** Ticker mapper. */ + protected final TickerMapper tickerMapper = Mappers.getMapper(TickerMapper.class); + + /** Order mapper. */ + protected final OrderMapper orderMapper = Mappers.getMapper(OrderMapper.class); + + /** Trade mapper. */ + protected final TradeMapper tradeMapper = Mappers.getMapper(TradeMapper.class); + + /** Position mapper. */ + protected final PositionMapper positionMapper = Mappers.getMapper(PositionMapper.class); + /** Default strategy. */ protected final StrategyDTO strategyDTO = StrategyDTO.builder() .id(1L) @@ -138,8 +170,7 @@ protected static ZonedDateTime createZonedDateTime(final int day) { * @return ZonedDateTime */ protected static ZonedDateTime createZonedDateTime(final String date) { - LocalDateTime ldt = LocalDateTime.parse(date + " 00:00", DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm")); - return ldt.atZone(ZoneId.systemDefault()); + return ZonedDateTime.parse(date + " 00:00:00 UTC", DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss VV")); } } diff --git a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/util/junit/configuration/ConfigurationExtension.java b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/util/junit/configuration/ConfigurationExtension.java index e88caeb1e..e350d99fb 100644 --- a/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/util/junit/configuration/ConfigurationExtension.java +++ b/spring-boot-starter/autoconfigure/src/test/java/tech/cassandre/trading/bot/test/util/junit/configuration/ConfigurationExtension.java @@ -20,7 +20,7 @@ /** * Configuration extension - set and clear system properties. */ -@NotThreadSafe // System properties are JVM-global, so don't run tests in parallel with this rule. +@NotThreadSafe // System properties are JVM-global, so don't run tests in parallel. public class ConfigurationExtension implements BeforeAllCallback, AfterAllCallback { /** Driver class name parameter. */ diff --git a/spring-boot-starter/autoconfigure/src/test/resources/application.properties b/spring-boot-starter/autoconfigure/src/test/resources/application.properties index 25c7154f1..bcab561ca 100644 --- a/spring-boot-starter/autoconfigure/src/test/resources/application.properties +++ b/spring-boot-starter/autoconfigure/src/test/resources/application.properties @@ -13,6 +13,7 @@ spring.main.allow-bean-definition-overriding=true # # For JPA. spring.jpa.hibernate.ddl-auto=none +spring.jpa.properties.hibernate.jdbc.time_zone=UTC # # For JDBC debug. # logging.level.org.hibernate.SQL=DEBUG diff --git a/spring-boot-starter/autoconfigure/src/test/resources/db/backup.yaml b/spring-boot-starter/autoconfigure/src/test/resources/db/backup.yaml index 8625d994a..7aaac5f66 100644 --- a/spring-boot-starter/autoconfigure/src/test/resources/db/backup.yaml +++ b/spring-boot-starter/autoconfigure/src/test/resources/db/backup.yaml @@ -1,5 +1,5 @@ databaseChangeLog: - include: - file: db/changelog/db.changelog-master.yaml + file: /db/changelog/db.changelog-master.yaml - include: - file: db/backup.sql \ No newline at end of file + file: /db/backup.sql \ No newline at end of file diff --git a/spring-boot-starter/autoconfigure/src/test/resources/db/gains-test.yaml b/spring-boot-starter/autoconfigure/src/test/resources/db/gains-test.yaml index 45942aadd..8cf479f3e 100644 --- a/spring-boot-starter/autoconfigure/src/test/resources/db/gains-test.yaml +++ b/spring-boot-starter/autoconfigure/src/test/resources/db/gains-test.yaml @@ -1,5 +1,5 @@ databaseChangeLog: - include: - file: db/changelog/db.changelog-master.yaml + file: /db/changelog/db.changelog-master.yaml - include: - file: db/gains-test.sql \ No newline at end of file + file: /db/gains-test.sql \ No newline at end of file diff --git a/spring-boot-starter/autoconfigure/src/test/resources/db/issue483.yaml b/spring-boot-starter/autoconfigure/src/test/resources/db/issue483.yaml index 569aec612..f6b3d898f 100644 --- a/spring-boot-starter/autoconfigure/src/test/resources/db/issue483.yaml +++ b/spring-boot-starter/autoconfigure/src/test/resources/db/issue483.yaml @@ -1,5 +1,5 @@ databaseChangeLog: - include: - file: db/changelog/db.changelog-master.yaml + file: /db/changelog/db.changelog-master.yaml - include: - file: db/issue483.sql \ No newline at end of file + file: /db/issue483.sql \ No newline at end of file diff --git a/spring-boot-starter/autoconfigure/src/test/resources/db/issue509.yaml b/spring-boot-starter/autoconfigure/src/test/resources/db/issue509.yaml index 4be34d4a2..9e671d264 100644 --- a/spring-boot-starter/autoconfigure/src/test/resources/db/issue509.yaml +++ b/spring-boot-starter/autoconfigure/src/test/resources/db/issue509.yaml @@ -1,5 +1,5 @@ databaseChangeLog: - include: - file: db/changelog/db.changelog-master.yaml + file: /db/changelog/db.changelog-master.yaml - include: - file: db/issue509.sql \ No newline at end of file + file: /db/issue509.sql \ No newline at end of file diff --git a/spring-boot-starter/autoconfigure/src/test/resources/db/issue510.yaml b/spring-boot-starter/autoconfigure/src/test/resources/db/issue510.yaml index af553534a..f1809ce6e 100644 --- a/spring-boot-starter/autoconfigure/src/test/resources/db/issue510.yaml +++ b/spring-boot-starter/autoconfigure/src/test/resources/db/issue510.yaml @@ -1,5 +1,5 @@ databaseChangeLog: - include: - file: db/changelog/db.changelog-master.yaml + file: /db/changelog/db.changelog-master.yaml - include: - file: db/issue510.sql \ No newline at end of file + file: /db/issue510.sql \ No newline at end of file diff --git a/spring-boot-starter/autoconfigure/src/test/resources/db/trade-test.yaml b/spring-boot-starter/autoconfigure/src/test/resources/db/trade-test.yaml index 6499a1330..655411dbc 100644 --- a/spring-boot-starter/autoconfigure/src/test/resources/db/trade-test.yaml +++ b/spring-boot-starter/autoconfigure/src/test/resources/db/trade-test.yaml @@ -1,5 +1,5 @@ databaseChangeLog: - include: - file: db/changelog/db.changelog-master.yaml + file: /db/changelog/db.changelog-master.yaml - include: - file: db/trade-test.sql \ No newline at end of file + file: /db/trade-test.sql \ No newline at end of file diff --git a/spring-boot-starter/starter/pom.xml b/spring-boot-starter/starter/pom.xml index c0b25c3d8..890f86824 100644 --- a/spring-boot-starter/starter/pom.xml +++ b/spring-boot-starter/starter/pom.xml @@ -42,7 +42,7 @@ com.puppycrawl.tools checkstyle - 8.43 + 8.44 @@ -112,7 +112,7 @@ tech.cassandre.trading.bot cassandre-trading-bot-project - 5.0.0 + 5.0.1 ../../pom.xml diff --git a/trading-bot-archetypes/basic-archetype/pom.xml b/trading-bot-archetypes/basic-archetype/pom.xml index 0fed3087b..7fa784f55 100644 --- a/trading-bot-archetypes/basic-archetype/pom.xml +++ b/trading-bot-archetypes/basic-archetype/pom.xml @@ -104,7 +104,7 @@ tech.cassandre.trading.bot cassandre-trading-bot-project - 5.0.0 + 5.0.1 ../../pom.xml diff --git a/trading-bot-archetypes/basic-archetype/src/main/resources/archetype-resources/pom.xml b/trading-bot-archetypes/basic-archetype/src/main/resources/archetype-resources/pom.xml index 7faa383e7..7147b627a 100644 --- a/trading-bot-archetypes/basic-archetype/src/main/resources/archetype-resources/pom.xml +++ b/trading-bot-archetypes/basic-archetype/src/main/resources/archetype-resources/pom.xml @@ -35,7 +35,7 @@ org.hsqldb hsqldb - 2.5.1 + 2.6.0 @@ -49,7 +49,13 @@ org.knowm.xchange xchange-kucoin - 5.0.8 + 5.0.9 + + + org.knowm.xchange + xchange-simulated + 5.0.9 + test diff --git a/trading-bot-archetypes/basic-archetype/src/main/resources/archetype-resources/src/main/java/SimpleStrategy.java b/trading-bot-archetypes/basic-archetype/src/main/resources/archetype-resources/src/main/java/SimpleStrategy.java index d69d3a629..448a5ab98 100644 --- a/trading-bot-archetypes/basic-archetype/src/main/resources/archetype-resources/src/main/java/SimpleStrategy.java +++ b/trading-bot-archetypes/basic-archetype/src/main/resources/archetype-resources/src/main/java/SimpleStrategy.java @@ -37,7 +37,6 @@ public Set getRequestedCurrencyPairs() { public Optional getTradeAccount(Set accounts) { // From all the accounts retrieved by the server, we return the one we used for trading. if (accounts.size() == 1) { - // Used for Gemini integration tests. return accounts.stream().findAny(); } else { return accounts.stream() diff --git a/trading-bot-archetypes/basic-archetype/src/main/resources/archetype-resources/src/test/resources/application.properties b/trading-bot-archetypes/basic-archetype/src/main/resources/archetype-resources/src/test/resources/application.properties index 27bf43313..d58ad0fbe 100644 --- a/trading-bot-archetypes/basic-archetype/src/main/resources/archetype-resources/src/test/resources/application.properties +++ b/trading-bot-archetypes/basic-archetype/src/main/resources/archetype-resources/src/test/resources/application.properties @@ -7,7 +7,7 @@ ${symbol_pound} How to do it : https://trading-bot.cassandre.tech/ressources/how ${symbol_pound} ====================================================================================================================== ${symbol_pound} ${symbol_pound} Exchange configuration. -cassandre.trading.bot.exchange.driver-class-name=kucoin +cassandre.trading.bot.exchange.driver-class-name=org.knowm.xchange.simulated.SimulatedExchange cassandre.trading.bot.exchange.username=kucoin.cassandre.test@gmail.com cassandre.trading.bot.exchange.passphrase=cassandre cassandre.trading.bot.exchange.key=6054ad25365ac6000689a998 diff --git a/trading-bot-archetypes/basic-ta4j-archetype/pom.xml b/trading-bot-archetypes/basic-ta4j-archetype/pom.xml index 9a2430261..8cb7a7540 100644 --- a/trading-bot-archetypes/basic-ta4j-archetype/pom.xml +++ b/trading-bot-archetypes/basic-ta4j-archetype/pom.xml @@ -104,7 +104,7 @@ tech.cassandre.trading.bot cassandre-trading-bot-project - 5.0.0 + 5.0.1 ../../pom.xml diff --git a/trading-bot-archetypes/basic-ta4j-archetype/src/main/resources/archetype-resources/pom.xml b/trading-bot-archetypes/basic-ta4j-archetype/src/main/resources/archetype-resources/pom.xml index 7faa383e7..7147b627a 100644 --- a/trading-bot-archetypes/basic-ta4j-archetype/src/main/resources/archetype-resources/pom.xml +++ b/trading-bot-archetypes/basic-ta4j-archetype/src/main/resources/archetype-resources/pom.xml @@ -35,7 +35,7 @@ org.hsqldb hsqldb - 2.5.1 + 2.6.0 @@ -49,7 +49,13 @@ org.knowm.xchange xchange-kucoin - 5.0.8 + 5.0.9 + + + org.knowm.xchange + xchange-simulated + 5.0.9 + test diff --git a/trading-bot-archetypes/basic-ta4j-archetype/src/main/resources/archetype-resources/src/main/java/SimpleTa4jStrategy.java b/trading-bot-archetypes/basic-ta4j-archetype/src/main/resources/archetype-resources/src/main/java/SimpleTa4jStrategy.java index 7ae730fd7..43b37e502 100644 --- a/trading-bot-archetypes/basic-ta4j-archetype/src/main/resources/archetype-resources/src/main/java/SimpleTa4jStrategy.java +++ b/trading-bot-archetypes/basic-ta4j-archetype/src/main/resources/archetype-resources/src/main/java/SimpleTa4jStrategy.java @@ -44,7 +44,6 @@ public CurrencyPairDTO getRequestedCurrencyPair() { public Optional getTradeAccount(Set accounts) { // From all the accounts retrieved by the server, we return the one we used for trading. if (accounts.size() == 1) { - // Used for Gemini integration tests. return accounts.stream().findAny(); } else { return accounts.stream() diff --git a/trading-bot-archetypes/basic-ta4j-archetype/src/main/resources/archetype-resources/src/test/java/SimpleTa4jStrategyTest.java b/trading-bot-archetypes/basic-ta4j-archetype/src/main/resources/archetype-resources/src/test/java/SimpleTa4jStrategyTest.java index 876fb5223..bc82febf0 100644 --- a/trading-bot-archetypes/basic-ta4j-archetype/src/main/resources/archetype-resources/src/test/java/SimpleTa4jStrategyTest.java +++ b/trading-bot-archetypes/basic-ta4j-archetype/src/main/resources/archetype-resources/src/test/java/SimpleTa4jStrategyTest.java @@ -1,7 +1,7 @@ #set($symbol_pound='#') - #set($symbol_dollar='$') - #set($symbol_escape='\' ) - package ${package}; +#set($symbol_dollar='$') +#set($symbol_escape='\' ) +package ${package}; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -45,18 +45,18 @@ public void gainTest() { System.out.println("Cumulated gains:"); gains.forEach((currency, gain) -> System.out.println(currency + " : " + gain.getAmount())); - System.out.println("Position closed :"); + System.out.println("Position closed:"); strategy.getPositions() .values() .stream() .filter(p -> p.getStatus().equals(CLOSED)) .forEach(p -> System.out.println(" - " + p.getDescription())); - System.out.println("Position still opened :"); + System.out.println("Position not closed:"); strategy.getPositions() .values() .stream() - .filter(p -> p.getStatus().equals(OPENED)) + .filter(p -> !p.getStatus().equals(CLOSED)) .forEach(p -> System.out.println(" - " + p.getDescription())); assertTrue(gains.get(strategy.getRequestedCurrencyPair().getQuoteCurrency()).getPercentage() > 0); diff --git a/trading-bot-archetypes/basic-ta4j-archetype/src/main/resources/archetype-resources/src/test/resources/application.properties b/trading-bot-archetypes/basic-ta4j-archetype/src/main/resources/archetype-resources/src/test/resources/application.properties index 27bf43313..d58ad0fbe 100644 --- a/trading-bot-archetypes/basic-ta4j-archetype/src/main/resources/archetype-resources/src/test/resources/application.properties +++ b/trading-bot-archetypes/basic-ta4j-archetype/src/main/resources/archetype-resources/src/test/resources/application.properties @@ -7,7 +7,7 @@ ${symbol_pound} How to do it : https://trading-bot.cassandre.tech/ressources/how ${symbol_pound} ====================================================================================================================== ${symbol_pound} ${symbol_pound} Exchange configuration. -cassandre.trading.bot.exchange.driver-class-name=kucoin +cassandre.trading.bot.exchange.driver-class-name=org.knowm.xchange.simulated.SimulatedExchange cassandre.trading.bot.exchange.username=kucoin.cassandre.test@gmail.com cassandre.trading.bot.exchange.passphrase=cassandre cassandre.trading.bot.exchange.key=6054ad25365ac6000689a998