/* * SPDX-License-Identifier: MIT */ package ta4jexamples.bots; import java.time.Duration; import java.time.Instant; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.ta4j.core.Bar; import org.ta4j.core.BarSeries; import org.ta4j.core.BaseStrategy; import org.ta4j.core.BaseTradingRecord; import org.ta4j.core.Strategy; import org.ta4j.core.Trade; import org.ta4j.core.TradingRecord; import org.ta4j.core.bars.TimeBarBuilder; import org.ta4j.core.indicators.averages.SMAIndicator; import org.ta4j.core.indicators.helpers.ClosePriceIndicator; import org.ta4j.core.num.DecimalNum; import org.ta4j.core.num.DecimalNumFactory; import org.ta4j.core.num.Num; import org.ta4j.core.rules.OverIndicatorRule; import org.ta4j.core.rules.UnderIndicatorRule; import ta4jexamples.datasources.BitStampCsvTradesFileBarSeriesDataSource; /** * Example of a trading bot running on a live, moving {@link BarSeries}. * * This example simulates a real-time trading environment: * */ public class TradingBotOnMovingBarSeries { private static final Logger LOG = LogManager.getLogger(TradingBotOnMovingBarSeries.class); /** * Close price of the last bar */ private static Num LAST_BAR_CLOSE_PRICE; /** * Builds a moving bar series (i.e. keeping only the maxBarCount last bars) * * @param maxBarCount the number of bars to keep in the bar series (at maximum) * @return a moving bar series */ private static BarSeries initMovingBarSeries(int maxBarCount) { BarSeries series = BitStampCsvTradesFileBarSeriesDataSource.loadBitstampSeries(); // Limitating the number of bars to maxBarCount series.setMaximumBarCount(maxBarCount); LAST_BAR_CLOSE_PRICE = series.getBar(series.getEndIndex()).getClosePrice(); LOG.debug("Initial bar count: {} (limited to {}), close price = {}", series.getBarCount(), maxBarCount, LAST_BAR_CLOSE_PRICE); return series; } /** * @param series a bar series * @return a dummy strategy */ private static Strategy buildStrategy(BarSeries series) { if (series == null) { throw new IllegalArgumentException("Series cannot be null"); } ClosePriceIndicator closePrice = new ClosePriceIndicator(series); SMAIndicator sma = new SMAIndicator(closePrice, 12); // Signals // Buy when SMA goes over close price // Sell when close price goes over SMA Strategy buySellSignals = new BaseStrategy(new OverIndicatorRule(sma, closePrice), new UnderIndicatorRule(sma, closePrice)); return buySellSignals; } /** * Generates a random decimal number between min and max. * * @param min the minimum bound * @param max the maximum bound * @return a random decimal number between min and max */ private static Num randDecimal(Num min, Num max) { Num randomDecimal = null; if (min != null && max != null && min.isLessThan(max)) { Num range = max.minus(min); Num position = range.multipliedBy(DecimalNum.valueOf(Math.random())); randomDecimal = min.plus(position); } return randomDecimal; } /** * Generates a random bar. * * @return a random bar */ private static Bar generateRandomBar() { final Num maxRange = DecimalNum.valueOf("0.03"); // 3.0% Num openPrice = LAST_BAR_CLOSE_PRICE; Num lowPrice = openPrice.minus(maxRange.multipliedBy(DecimalNum.valueOf(Math.random()))); Num highPrice = openPrice.plus(maxRange.multipliedBy(DecimalNum.valueOf(Math.random()))); Num closePrice = randDecimal(lowPrice, highPrice); LAST_BAR_CLOSE_PRICE = closePrice; return new TimeBarBuilder(DecimalNumFactory.getInstance()).amount(1) .volume(1) .timePeriod(Duration.ofDays(1)) .endTime(Instant.now()) .openPrice(openPrice) .highPrice(highPrice) .lowPrice(lowPrice) .closePrice(closePrice) .build(); } public static void main(String[] args) throws InterruptedException { LOG.debug("********************** Initialization **********************"); // Getting the bar series BarSeries series = initMovingBarSeries(20); // Building the trading strategy Strategy strategy = buildStrategy(series); // Initializing the trading history TradingRecord tradingRecord = new BaseTradingRecord(); LOG.debug("************************************************************"); /* * We run the strategy for the 50 next bars. */ for (int i = 0; i < 50; i++) { // New bar Thread.sleep(30); // I know... Bar newBar = generateRandomBar(); LOG.debug("------------------------------------------------------\nBar {} added, close price = {}", i, newBar.getClosePrice().doubleValue()); series.addBar(newBar); int endIndex = series.getEndIndex(); if (strategy.shouldEnter(endIndex)) { // Our strategy should enter LOG.debug("Strategy should ENTER on {}", endIndex); boolean entered = tradingRecord.enter(endIndex, newBar.getClosePrice(), DecimalNum.valueOf(10)); if (entered) { Trade entry = tradingRecord.getLastEntry(); LOG.debug("Entered on {} (price={}, amount={})", entry.getIndex(), entry.getNetPrice().doubleValue(), entry.getAmount().doubleValue()); } } else if (strategy.shouldExit(endIndex)) { // Our strategy should exit LOG.debug("Strategy should EXIT on {}", endIndex); boolean exited = tradingRecord.exit(endIndex, newBar.getClosePrice(), DecimalNum.valueOf(10)); if (exited) { Trade exit = tradingRecord.getLastExit(); LOG.debug("Exited on {} (price={}, amount={})", exit.getIndex(), exit.getNetPrice().doubleValue(), exit.getAmount().doubleValue()); } } } } }