Ta4j Wiki

Documentation, examples and further information of the ta4j project

View the Wiki On GitHub

This project is maintained by ta4j Organization

Getting Started

This guide takes you from a fresh checkout to a working strategy, then shows how to choose between ta4j’s three main execution styles:

  1. BarSeriesManager for straightforward historical runs
  2. BacktestExecutor for large batches
  3. Manual loops with BaseTradingRecord when fills arrive asynchronously

Prerequisites

If technical analysis is new to you, skim Wikipedia or Investopedia for terminology.

Install ta4j

Start with the latest released line unless you specifically want unreleased master APIs:

<dependency>
  <groupId>org.ta4j</groupId>
  <artifactId>ta4j-core</artifactId>
  <version>0.22.3</version>
</dependency>

If you want the newest development APIs described on this wiki, install the current snapshot locally first:

mvn -pl ta4j-core -am install

Then depend on ta4j-core with the snapshot version:

<dependency>
  <groupId>org.ta4j</groupId>
  <artifactId>ta4j-core</artifactId>
  <version>0.22.4-SNAPSHOT</version>
</dependency>
implementation "org.ta4j:ta4j-core:0.22.4-SNAPSHOT"

If you are consuming a released build from Maven Central instead, replace the version with the newest released 0.22.x number from Release Notes. If a page mentions an API you do not see in 0.22.3 yet, that is your cue to build the current snapshot locally.

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

  1. Run mvn -pl ta4j-examples test
  2. Launch ta4jexamples.Quickstart
  3. 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:

  1. Create a bar series
  2. Build indicators and rules
  3. Run a backtest
  4. Inspect metrics
  5. 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. Pick the right execution path

Goal Recommended path Why
One strategy over historical bars BarSeriesManager Minimal wiring and deterministic trade-execution models
Same backtest loop, but with a preconfigured record BarSeriesManager.run(strategy, tradingRecord, ...) Keep a specific ExecutionMatchPolicy, fee model, or record instance
Large parameter sweeps BacktestExecutor Batched execution, runtime reports, and weighted ranked statements
Live or paper execution with confirmed fills Manual loop + BaseTradingRecord Signal generation stays separate from fill recording

For the common case, start 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:

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.