Getting Started
This guide takes you from a fresh checkout to a working strategy. For execution-path selection and full onboarding routing, use canonical pages:
Prerequisites
- JDK 25 or newer
- Maven, Gradle, or an IDE that can import Maven projects
- Basic Java familiarity
If technical analysis is new to you, skim Wikipedia or Investopedia for terminology.
Install ta4j
Use the canonical dependency snippets in the repository README:
- ta4j README install section for latest stable and snapshot versions
If you need current master APIs that are not yet on Maven Central, install locally first:
mvn -pl ta4j-core -am install
Prefer to inspect the code? Clone ta4j and import the root Maven project. ta4j-core and ta4j-examples live side by side.
Verify your environment
- Run
mvn -pl ta4j-examples test - Launch
ta4jexamples.Quickstart - Confirm that you see backtest output, and a chart window when running with a GUI
Understand The Building Blocks
| Concept | What it does | Learn more |
|---|---|---|
BarSeries |
Holds the ordered bars used by indicators and strategies | Bar Series & Bars |
Indicator<T extends Num> |
Derives values lazily from bars or other indicators | Technical Indicators |
Rule and Strategy |
Decide when to enter, exit, or stay flat | Trading Strategies |
BaseTradingRecord |
Unified trading state for backtests, live trading, and paper trading | Backtesting, Live Trading |
BarSeriesManager |
Runs one strategy over a series | Backtesting |
BacktestExecutor |
Runs many strategies and ranks the results | Backtesting |
ConcurrentBarSeries |
Thread-safe series for multi-threaded ingestion and evaluation | Live Trading |
The important mental model is that ta4j no longer needs a split “backtest record” versus “live record” story for new code. BaseTradingRecord already covers both.
Walkthrough: build your first strategy
We will:
- Create a bar series
- Build indicators and rules
- Run a backtest
- Inspect metrics
- See the live-style variant
1. Build a bar series
BarSeries series = new BaseBarSeriesBuilder()
.withName("btc-usd-demo")
.build();
series.barBuilder()
.timePeriod(Duration.ofMinutes(5))
.endTime(Instant.parse("2025-01-01T00:05:00Z"))
.openPrice(42000)
.highPrice(42150)
.lowPrice(41980)
.closePrice(42100)
.volume(12.4)
.add();
If you already have bar data, call series.addBar(...). If you are aggregating raw trades, use the appropriate bar builder or a ConcurrentBarSeries in live flows.
2. Create indicators and rules
ClosePriceIndicator closePrice = new ClosePriceIndicator(series);
SMAIndicator fastSma = new SMAIndicator(closePrice, 5);
SMAIndicator slowSma = new SMAIndicator(closePrice, 30);
Rule entryRule = new CrossedUpIndicatorRule(fastSma, slowSma);
Rule exitRule = new CrossedDownIndicatorRule(fastSma, slowSma)
.or(new StopLossRule(closePrice, series.numFactory().numOf(3)))
.or(new StopGainRule(closePrice, series.numFactory().numOf(5)));
Strategy strategy = new BaseStrategy("SMA crossover", entryRule, exitRule);
strategy.setUnstableBars(30);
3. Run your first backtest
For execution-path decisions, use Execution Decision Matrix. For the common starter case, run one strategy with BarSeriesManager:
BarSeriesManager manager = new BarSeriesManager(series);
TradingRecord record = manager.run(strategy);
System.out.printf("Closed positions: %d%n", record.getPositionCount());
System.out.printf("Current position open? %s%n", record.getCurrentPosition().isOpened());
If you need a specific record configuration, provide your own BaseTradingRecord:
BaseTradingRecord record = new BaseTradingRecord(
strategy.getStartingType(),
ExecutionMatchPolicy.FIFO,
new ZeroCostModel(),
new ZeroCostModel(),
series.getBeginIndex(),
series.getEndIndex());
manager.run(strategy, record, series.numFactory().one(), series.getBeginIndex(), series.getEndIndex());
4. Inspect metrics
AnalysisCriterion netReturn = new NetReturnCriterion();
AnalysisCriterion romad = new ReturnOverMaxDrawdownCriterion();
AnalysisCriterion openCostBasis = new OpenPositionCostBasisCriterion();
System.out.println("Net return: " + netReturn.calculate(series, record));
System.out.println("Return over max drawdown: " + romad.calculate(series, record));
System.out.println("Open position cost basis: " + openCostBasis.calculate(series, record));
Useful follow-up metrics:
TotalFeesCriterionCommissionsImpactPercentageCriterionOpenPositionUnrealizedProfitCriterionMaxConsecutiveProfitCriterionMaxConsecutiveLossCriterionBaseTradingStatement
If you want to compare many parameter combinations instead of one strategy, move next to BacktestExecutor and SimpleMovingAverageRangeBacktest, which show the current weighted shortlist flow with WeightedCriterion.of(...) and getTopStrategiesWeighted(...).
5. Live-style loop with confirmed fills
When your orders are filled asynchronously, do not mutate the record at signal time. Emit the order intent first, then update the record from the confirmed fill:
BaseTradingRecord liveRecord = new BaseTradingRecord(strategy.getStartingType());
int endIndex = series.getEndIndex();
Num lastPrice = series.getBar(endIndex).getClosePrice();
Num amount = series.numFactory().one();
if (strategy.shouldEnter(endIndex, liveRecord)) {
orderRouter.submitBuy(lastPrice, amount);
}
TradeFill fill = new TradeFill(
endIndex,
Instant.now(),
lastPrice,
amount,
series.numFactory().zero(),
ExecutionSide.BUY,
"order-123",
"decision-123");
liveRecord.operate(fill);
If your exchange hands you the full partial-fill batch for one logical order, group it with Trade.fromFills(...) and pass that through operate(...) instead. Either way, the pattern is the same: ta4j strategies decide, brokers execute, then BaseTradingRecord is updated from confirmed fills.
Next Steps
| Goal | Where to go next |
|---|---|
| Learn data-loading and streaming patterns | Bar Series & Bars |
| Explore more indicators | Technical Indicators |
| Run larger backtests | Backtesting |
| Build a real bot loop | Live Trading |
| Run maintained examples | Usage Examples |
Compatibility Note
LiveTradingRecord and ExecutionFill still exist in the 0.22.x line so older adapters can migrate gradually, but new code should use BaseTradingRecord and TradeFill.