diff --git a/pom.xml b/pom.xml index f4606ec368..d8bf527184 100644 --- a/pom.xml +++ b/pom.xml @@ -1276,6 +1276,7 @@ + spring-boot-modules/spring-boot-cassandre core-java-modules/core-java-9 core-java-modules/core-java-9-improvements core-java-modules/core-java-9-jigsaw diff --git a/spring-boot-modules/spring-boot-cassandre/README.md b/spring-boot-modules/spring-boot-cassandre/README.md new file mode 100644 index 0000000000..14ffbb7d6b --- /dev/null +++ b/spring-boot-modules/spring-boot-cassandre/README.md @@ -0,0 +1,11 @@ +# Cassandre trading bot example + +This project is an example of a trading bot developed with Cassandre + +## Running the examples + +* `mvn test` - Run strategy backtesting +* `mvn spring-boot:run` - Run the bot + +## Relevant Articles +- [Build a Trading Bot with Cassandre Spring Boot Starter](https://www.baeldung.com/build-a-trading-bot-with-cassandre-spring-boot-starter/) diff --git a/spring-boot-modules/spring-boot-cassandre/pom.xml b/spring-boot-modules/spring-boot-cassandre/pom.xml new file mode 100644 index 0000000000..75163d2c2b --- /dev/null +++ b/spring-boot-modules/spring-boot-cassandre/pom.xml @@ -0,0 +1,68 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.5 + + + com.example + demo + 0.0.1-SNAPSHOT + Cassandre trading bot tutorial + Cassandre trading bot tutorial + + 11 + + + + org.springframework.boot + spring-boot-starter + + + + + tech.cassandre.trading.bot + cassandre-trading-bot-spring-boot-starter + 4.2.1 + + + org.knowm.xchange + xchange-kucoin + 5.0.7 + + + org.hsqldb + hsqldb + 2.5.1 + + + + org.springframework.boot + spring-boot-starter-test + test + + + + tech.cassandre.trading.bot + cassandre-trading-bot-spring-boot-starter-test + 4.2.1 + test + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + 2.4.5 + + + + + diff --git a/spring-boot-modules/spring-boot-cassandre/src/main/java/com/example/demo/DemoApplication.java b/spring-boot-modules/spring-boot-cassandre/src/main/java/com/example/demo/DemoApplication.java new file mode 100644 index 0000000000..094d95b93f --- /dev/null +++ b/spring-boot-modules/spring-boot-cassandre/src/main/java/com/example/demo/DemoApplication.java @@ -0,0 +1,13 @@ +package com.example.demo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class DemoApplication { + + public static void main(String[] args) { + SpringApplication.run(DemoApplication.class, args); + } + +} diff --git a/spring-boot-modules/spring-boot-cassandre/src/main/java/com/example/demo/MyFirstStrategy.java b/spring-boot-modules/spring-boot-cassandre/src/main/java/com/example/demo/MyFirstStrategy.java new file mode 100644 index 0000000000..ea8ae74aa6 --- /dev/null +++ b/spring-boot-modules/spring-boot-cassandre/src/main/java/com/example/demo/MyFirstStrategy.java @@ -0,0 +1,66 @@ +package com.example.demo; + +import static tech.cassandre.trading.bot.dto.position.PositionStatusDTO.CLOSED; +import static tech.cassandre.trading.bot.dto.position.PositionStatusDTO.OPENED; +import static tech.cassandre.trading.bot.dto.util.CurrencyDTO.BTC; +import static tech.cassandre.trading.bot.dto.util.CurrencyDTO.USDT; + +import java.math.BigDecimal; +import java.util.Optional; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import tech.cassandre.trading.bot.dto.market.TickerDTO; +import tech.cassandre.trading.bot.dto.position.PositionDTO; +import tech.cassandre.trading.bot.dto.position.PositionRulesDTO; +import tech.cassandre.trading.bot.dto.user.AccountDTO; +import tech.cassandre.trading.bot.dto.util.CurrencyPairDTO; +import tech.cassandre.trading.bot.strategy.BasicCassandreStrategy; +import tech.cassandre.trading.bot.strategy.CassandreStrategy; + +@CassandreStrategy +public class MyFirstStrategy extends BasicCassandreStrategy { + + private final Logger logger = LoggerFactory.getLogger(MyFirstStrategy.class); + + @Override + public Set getRequestedCurrencyPairs() { + return Set.of(new CurrencyPairDTO(BTC, USDT)); + } + + @Override + public Optional getTradeAccount(Set accounts) { + return accounts.stream() + .filter(a -> "trade".equals(a.getName())) + .findFirst(); + } + + @Override + public void onTickerUpdate(TickerDTO ticker) { + logger.info("Received a new ticker : {}", ticker); + + if (new BigDecimal("56000").compareTo(ticker.getLast()) == -1) { + + if (canBuy(new CurrencyPairDTO(BTC, USDT), new BigDecimal("0.01"))) { + PositionRulesDTO rules = PositionRulesDTO.builder() + .stopGainPercentage(4f) + .stopLossPercentage(25f) + .build(); + createLongPosition(new CurrencyPairDTO(BTC, USDT), new BigDecimal("0.01"), rules); + } + + } + } + + @Override + public void onPositionStatusUpdate(PositionDTO position) { + if (position.getStatus() == OPENED) { + logger.info("> New position opened : {}", position.getPositionId()); + } + if (position.getStatus() == CLOSED) { + logger.info("> Position closed : {}", position.getDescription()); + } + } + +} diff --git a/spring-boot-modules/spring-boot-cassandre/src/main/resources/application.properties b/spring-boot-modules/spring-boot-cassandre/src/main/resources/application.properties new file mode 100644 index 0000000000..5ecdabd47a --- /dev/null +++ b/spring-boot-modules/spring-boot-cassandre/src/main/resources/application.properties @@ -0,0 +1,22 @@ +# +# Exchange configuration. +cassandre.trading.bot.exchange.name=kucoin +cassandre.trading.bot.exchange.username=kucoin.cassandre.test@gmail.com +cassandre.trading.bot.exchange.passphrase=cassandre +cassandre.trading.bot.exchange.key=6054ad25365ac6000689a998 +cassandre.trading.bot.exchange.secret=af080d55-afe3-47c9-8ec1-4b479fbcc5e7 +# +# Modes +cassandre.trading.bot.exchange.modes.sandbox=true +cassandre.trading.bot.exchange.modes.dry=false +# +# Exchange API calls rates (ms or standard ISO 8601 duration like 'PT5S'). +cassandre.trading.bot.exchange.rates.account=2000 +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= diff --git a/spring-boot-modules/spring-boot-cassandre/src/test/java/com/example/demo/DemoApplicationTests.java b/spring-boot-modules/spring-boot-cassandre/src/test/java/com/example/demo/DemoApplicationTests.java new file mode 100644 index 0000000000..eaa99696e2 --- /dev/null +++ b/spring-boot-modules/spring-boot-cassandre/src/test/java/com/example/demo/DemoApplicationTests.java @@ -0,0 +1,13 @@ +package com.example.demo; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class DemoApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/spring-boot-modules/spring-boot-cassandre/src/test/java/com/example/demo/MyFirstStrategyUnitTest.java b/spring-boot-modules/spring-boot-cassandre/src/test/java/com/example/demo/MyFirstStrategyUnitTest.java new file mode 100644 index 0000000000..bf7c353821 --- /dev/null +++ b/spring-boot-modules/spring-boot-cassandre/src/test/java/com/example/demo/MyFirstStrategyUnitTest.java @@ -0,0 +1,55 @@ +package com.example.demo; + +import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static tech.cassandre.trading.bot.dto.position.PositionStatusDTO.OPENED; +import static tech.cassandre.trading.bot.dto.util.CurrencyDTO.USDT; + +import java.util.HashMap; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; + +import tech.cassandre.trading.bot.dto.util.CurrencyDTO; +import tech.cassandre.trading.bot.dto.util.GainDTO; +import tech.cassandre.trading.bot.test.mock.TickerFluxMock; + +@SpringBootTest +@Import(TickerFluxMock.class) +@DisplayName("Simple strategy test") +public class MyFirstStrategyUnitTest { + + private final Logger logger = LoggerFactory.getLogger(MyFirstStrategyUnitTest.class); + + @Autowired + private MyFirstStrategy strategy; + + @Autowired + private TickerFluxMock tickerFluxMock; + + @Test + @DisplayName("Check gains") + public void whenTickersArrives_thenCheckGains() { + await().forever().until(() -> tickerFluxMock.isFluxDone()); + + final HashMap gains = strategy.getGains(); + + logger.info("Cumulated gains:"); + gains.forEach((currency, gain) -> logger.info(currency + " : " + gain.getAmount())); + + logger.info("Position still opened :"); + strategy.getPositions() + .values() + .stream() + .filter(p -> p.getStatus().equals(OPENED)) + .forEach(p -> logger.info(" - {} " + p.getDescription())); + + assertTrue(gains.get(USDT).getPercentage() > 0); + } + +} diff --git a/spring-boot-modules/spring-boot-cassandre/src/test/resources/application.properties b/spring-boot-modules/spring-boot-cassandre/src/test/resources/application.properties new file mode 100644 index 0000000000..3d6feb09d7 --- /dev/null +++ b/spring-boot-modules/spring-boot-cassandre/src/test/resources/application.properties @@ -0,0 +1,22 @@ +# +# Exchange configuration. +cassandre.trading.bot.exchange.name=kucoin +cassandre.trading.bot.exchange.username=kucoin.cassandre.test@gmail.com +cassandre.trading.bot.exchange.passphrase=cassandre +cassandre.trading.bot.exchange.key=6054ad25365ac6000689a998 +cassandre.trading.bot.exchange.secret=af080d55-afe3-47c9-8ec1-4b479fbcc5e7 +# +# Modes +cassandre.trading.bot.exchange.modes.sandbox=true +cassandre.trading.bot.exchange.modes.dry=true +# +# Exchange API calls rates (ms or standard ISO 8601 duration like 'PT5S'). +cassandre.trading.bot.exchange.rates.account=2000 +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= diff --git a/spring-boot-modules/spring-boot-cassandre/src/test/resources/tickers-btc-usdt.tsv b/spring-boot-modules/spring-boot-cassandre/src/test/resources/tickers-btc-usdt.tsv new file mode 100644 index 0000000000..b89bd96bdc --- /dev/null +++ b/spring-boot-modules/spring-boot-cassandre/src/test/resources/tickers-btc-usdt.tsv @@ -0,0 +1,89 @@ +1612569600 38294.4 39195.5 40964.2 38217.5 3882.29460938 153897343.463150723 +1612656000 39195.4 38807.6 39623.6 37341.4 2389.96820017 91972455.834535369 +1612742400 38807.6 46373.5 46767.9 38010 4971.54731481 212132648.426718929 +1612828800 46374.6 46434.8 48139.3 44961 4330.72854712 201891604.027160303 +1612915200 46430 44812.2 47300 43687.5 4351.84907778 198189685.592028516 +1613001600 44806.1 47941.5 48647.6 44005.8 4045.91883504 188171651.974437139 +1613088000 47963.1 47310.7 48958.8 46181.2 3356.01832119 159561721.419695848 +1613174400 47305.4 47152.6 48120.5 46225.5 2740.99221759 129227867.922246174 +1613260800 47152.5 48591.9 49686.9 47026.3 3359.4690565 163299915.839307312 +1613347200 48587.2 47904.4 49003.6 42841.6 3974.98461358 188990056.26923591 +1613433600 47913.1 49147.7 50619.3 47023.9 3599.85370182 176084748.845657596 +1613520000 49147.7 52118.1 52609.6 48931.1 3356.85082847 170893567.530348564 +1613606400 52114.3 51568.9 52522.9 50906.4 2183.18379408 113272339.172174965 +1613692800 51561.1 55890.5 56317.7 50727 3749.6920105 200656740.865959032 +1613779200 55893.6 55851.5 57622.6 53463.3 3394.87226216 190744601.429330887 +1613865600 55851.4 57423 58336.3 55489.6 2514.02340013 143658132.671448082 +1613952000 57420.6 54096.6 57517.8 44160 6125.32442907 330513978.457310237 +1614038400 54085.9 48908.3 54174.2 44900 8048.96505298 389277314.445372085 +1614124800 48902.9 49685.2 51361.9 47003.2 4816.75027676 239303706.844272809 +1614211200 49676.5 47082.7 52019.6 46624.4 3701.80236678 184044004.383578525 +1614297600 47082 46289.6 48408.8 44135 5329.77125908 247604118.914146591 +1614384000 46290.2 46114.4 48381.9 44836.5 2872.64640734 134946360.020429589 +1614470400 46111.3 45141.6 46626.1 43004.3 3940.17863714 175990962.484551548 +1614556800 45136.5 49590.3 49771.3 44958.9 3548.51026561 169389196.772247159 +1614643200 49590.3 48441.2 50201.6 47052.8 2936.94454126 142575425.463057812 +1614729600 48440.6 50345.5 52623.9 48100 3177.38943911 160801620.821885745 +1614816000 50347.6 48374.5 51762.1 47505.7 3624.17683614 178873453.27515484 +1614902400 48374.5 48758.9 49450 46189.8 3697.34556922 176318969.507294567 +1614988800 48746.9 48871.9 49255.1 47001 1949.15311354 94201823.810314647 +1615075200 48885 50930.4 51424.7 48885 2444.3584982 122962479.787996993 +1615161600 50956.6 52377 52387.5 49287.5 2710.99151191 137751640.241286989 +1615248000 52376.9 54867.6 54867.6 51833.8 3070.93581512 165487483.114064122 +1615334400 54867.5 55865.5 57364 52911.4 4049.50553851 224565244.752334892 +1615420800 55863.7 57781.1 58150 54238 3403.69441456 191915265.020541521 +1615507200 57781 57238.5 58057.5 55013.7 4031.0376629 228810606.091302364 +1615593600 57220.7 61180.9 61815.3 56059.3 4394.62318443 259602986.875738328 +1615680000 61174.3 59000 61700 59000 3084.33952274 186155667.656432156 +1615766400 59000 55607.1 60632.9 54525.6 5910.33518227 338468393.188725572 +1615852800 55607.1 56880.3 56918.9 53240.3 7410.49057723 409052587.523700888 +1615939200 56880.3 58875.8 58951.8 54147.5 5828.79026943 328135601.648660052 +1616025600 58882.9 57648.9 60107.7 57000 5073.7458698 297279816.540519693 +1616112000 57648.9 58024.2 59450.1 56071 3727.09434161 217005823.723994618 +1616198400 58024.3 58113.5 59874.6 57825.6 2746.52973805 161565114.165299707 +1616284800 58113.5 57350.2 58591.6 55501 3265.35649781 186845535.507151609 +1616371200 57345.3 54096.1 58415.5 53667 4219.99501831 237141977.003568352 +1616457600 54086.8 54348.4 55823.1 52986.3 4374.34046303 239135883.538398977 +1616544000 54348.4 52307.4 57200 51499.6 6416.76024581 351202326.218690674 +1616630400 52307.1 51301.7 53239.1 50455 7242.6466396 375950351.557038048 +1616716800 51301.7 55032 55062.5 51225.3 4609.48192944 245299757.451540308 +1616803200 55031.9 55820.4 56628.6 53967.5 3634.73588532 200758048.816804103 +1616889600 55820.3 55772.9 56541 54666.6 3158.20452681 176119911.151714842 +1616976000 55772.8 57628.9 58400.5 54926.5 4413.63121553 251384747.301649587 +1617062400 57630.8 58754.6 59351.9 57072.8 3563.87315049 208118726.050535887 +1617148800 58753.2 58745.9 59800 56357.5 4921.45848213 288469053.074870873 +1617235200 58745.5 58735.7 59487.1 57879 3163.98213108 186078130.901422269 +1617321600 58735.7 58963.6 60179.1 58460.7 2553.76427314 151446539.609794648 +1617408000 58963.6 57058.3 59795 56721.2 2512.19109578 147434403.06515736 +1617494400 57052.5 58201.4 58481.2 56432.6 2069.14670128 119228330.17272614 +1617580800 58201.4 59116.2 59254.1 56501 3003.76043377 174821106.684799505 +1617667200 59116.2 57988.3 59497.5 57304.8 2964.86183859 173169186.845682699 +1617753600 57988.3 55958.2 58668.6 55439 5277.04906389 299996660.411940246 +1617840000 55958.2 58076.7 58141 55700.6 3175.60482079 181817013.517575328 +1617926400 58076.7 58131.6 58900 57666.9 3516.19104669 204849717.059779284 +1618012800 58138.2 59770.2 61350 57902.1 5533.50675561 332014577.538990658 +1618099200 59770.2 60007.6 60687.4 59247.6 3896.37426019 233158562.799039154 +1618185600 60007.6 59863.4 61270.7 59417.4 4611.409014 277430208.743380477 +1618272000 59863.4 63578.7 63759.7 59815 6906.310253 430518557.569547626 +1618358400 63578.7 62958.7 64840 61000 7696.509177 487298143.928065301 +1618444800 62954.4 63152.6 63772.1 62023.9 4709.82427144 296178401.81115496 +1618531200 63152.6 61342.6 63509.7 59930.8 8295.32523869 510423835.691643255 +1618617600 61342.7 59995.2 62497.8 59599.6 5367.42979289 328364887.709585395 +1618704000 59995.3 56150.6 60409.5 49001 11485.97101449 637797282.448645379 +1618790400 56152.7 55618.8 57583.4 54205.2 7721.306905 432634348.931871989 +1618876800 55618.7 56427.8 57061.6 53328 8677.75606016 480164200.559836543 +1618963200 56426.1 53793.6 56761.7 53602 6240.82191836 345339357.806167462 +1619049600 53793.5 51696.4 55474.7 50400 8879.16016304 475394174.249706678 +1619136000 51691.2 51102.7 52112.1 47502.1 8885.07060366 441295812.644904319 +1619222400 51109.8 50033 51157.9 48676.5 4833.41744745 241336360.887795675 +1619308800 50041.5 49086.9 50554.6 46966.2 4805.34664069 237153315.222670555 +1619395200 49069 54000.2 54336.4 48775.8 6695.12934907 353727728.269533971 +1619481600 53997.1 55014.3 55439 53240.8 4344.22291318 237020455.905144335 +1619568000 55014.2 54833.2 56399.1 53808.3 4801.04618634 262912695.604761319 +1619654400 54833.1 53558.4 55181.2 52340.1 4356.05177188 234153663.397444462 +1619740800 53558.5 57697.3 57936.4 53042.6 5000.47557303 277531927.921795199 +1619827200 57697.3 57794.7 58471.4 57006.3 3639.78966647 210179438.189007639 +1619913600 57794.7 56568.5 57903.7 56044.3 3508.52428767 199206958.05741809 +1620000000 56568.5 57159.7 58977.9 56451.3 4780.43387226 276554749.540429296 +1620086400 57159.7 53196.3 57188.2 53083.3 7079.55804728 390469293.396018923 +1620172800 53196.3 57834.5 57979.7 52888 4224.63060355 233779565.506303973 diff --git a/spring-boot-modules/spring-boot-cassandre/src/test/resources/user-trade.tsv b/spring-boot-modules/spring-boot-cassandre/src/test/resources/user-trade.tsv new file mode 100644 index 0000000000..d0fa4e9767 --- /dev/null +++ b/spring-boot-modules/spring-boot-cassandre/src/test/resources/user-trade.tsv @@ -0,0 +1,3 @@ +BTC 1 +USDT 100000 +ETH 10 \ No newline at end of file