Skip to main content
An indicator is a function that maps a bar index to a value. Indicators are the primary analytical building blocks in ta4j: they read from a BarSeries, compute a result, and return it on demand. They compose freely — any indicator can accept another indicator as its input.

The Indicator interface

Every indicator implements Indicator<T>, where T is the type of value returned:
public interface Indicator<T> {
    T getValue(int index);
    int getCountOfUnstableBars();
    BarSeries getBarSeries();
}
  • getValue(int index) — returns the computed value at the given bar index.
  • getCountOfUnstableBars() — returns how many leading bars produce unreliable output (the warm-up period).
  • getBarSeries() — returns the backing series.
Most indicators you will use return Num (i.e., Indicator<Num>), but ta4j also has Indicator<Boolean>, Indicator<Bar>, and other specialized types.

Categories of built-in indicators

Ta4j ships with 190+ indicators organized by analytical category:
CategoryExamples
TrendEMAIndicator, SMAIndicator, WMAIndicator, MACDIndicator, IchimokuIndicator, AroonIndicator
MomentumRSIIndicator, StochasticOscillatorKIndicator, CCIIndicator, ROCIndicator, MOMIndicator
VolatilityATRIndicator, BollingerBandsUpperIndicator, BollingerBandsLowerIndicator, StandardDeviationIndicator
VolumeVolumeIndicator, VWAPIndicator, OBVIndicator, MFIIndicator, ChaikinMoneyFlowIndicator
Price helpersClosePriceIndicator, OpenPriceIndicator, HighPriceIndicator, LowPriceIndicator, TypicalPriceIndicator
PatternBullishEngulfingIndicator, MorningStarIndicator, DojiIndicator, and many more candlestick patterns

Creating basic indicators

All indicators that work on price data start from a price helper. The most common starting point is ClosePriceIndicator:
import org.ta4j.core.indicators.helpers.ClosePriceIndicator;
import org.ta4j.core.indicators.EMAIndicator;
import org.ta4j.core.indicators.RSIIndicator;
import org.ta4j.core.indicators.MACDIndicator;

BarSeries series = /* your series */;

// Price extractor — the root of most indicator chains
ClosePriceIndicator close = new ClosePriceIndicator(series);

// 12-period and 26-period EMA
EMAIndicator fastEma = new EMAIndicator(close, 12);
EMAIndicator slowEma = new EMAIndicator(close, 26);

// 14-period RSI
RSIIndicator rsi = new RSIIndicator(close, 14);

// MACD line: difference between two EMAs
MACDIndicator macd = new MACDIndicator(close, 12, 26);
Read a value at any bar index:
int lastIndex = series.getEndIndex();
System.out.println("Close:   " + close.getValue(lastIndex));
System.out.println("EMA(12): " + fastEma.getValue(lastIndex));
System.out.println("RSI(14): " + rsi.getValue(lastIndex));

Composing indicators

Indicators are composable: pass any Indicator<Num> where an input indicator is expected. This lets you build complex chains without writing custom calculation code.
import org.ta4j.core.indicators.SMAIndicator;
import org.ta4j.core.indicators.RSIIndicator;

// RSI of the close price
RSIIndicator rsi = new RSIIndicator(close, 14);

// 5-period SMA smoothed over the RSI values
SMAIndicator smoothedRsi = new SMAIndicator(rsi, 5);
You can also operate on multiple indicator outputs using arithmetic helpers:
import org.ta4j.core.indicators.helpers.DifferenceIndicator;
import org.ta4j.core.indicators.helpers.SumIndicator;

// MACD histogram = MACD line – signal line
EMAIndicator signalLine = new EMAIndicator(macd, 9);
DifferenceIndicator histogram = new DifferenceIndicator(macd, signalLine);

CachedIndicator and performance

Most indicators in ta4j extend CachedIndicator<T>, which stores computed values in an internal ring buffer. When getValue(index) is called multiple times for the same index, only the first call triggers calculate(int) — subsequent calls return the cached result immediately. This matters most when you compose indicators:
  • An EMA(12) composes over close.
  • A MACD indicator calls EMA(12).getValue(i) and EMA(26).getValue(i) for every bar.
  • Because both EMAs are cached, the MACD never recomputes an EMA value that was already calculated.
RecursiveCachedIndicator is a variant designed for indicators whose value at index i depends on their own value at index i-1 (e.g., EMA itself). It manages the recursive dependency safely and without stack overflow.
The cache is a ring buffer sized to series.getMaximumBarCount(). When bars are evicted from the series, corresponding cached values are also evicted. This keeps memory usage bounded during live trading.

The unstable bars concept (warm-up period)

Many indicators need a minimum number of bars before their output is mathematically meaningful. This warm-up period is reported by getCountOfUnstableBars().
RSIIndicator rsi = new RSIIndicator(close, 14);
System.out.println(rsi.getCountOfUnstableBars()); // 14

// Check whether the series has enough bars for a stable result
if (rsi.isStable()) {
    Num value = rsi.getValue(series.getEndIndex());
}
The isStable() default implementation checks that series.getBarCount() >= getCountOfUnstableBars(). Values returned during the unstable period may be NaN or mathematically incorrect — do not trade on them. When composing indicators, unstable periods are additive for sequential pipelines:
// EMA(26) needs 26 bars
EMAIndicator ema26 = new EMAIndicator(close, 26);

// SMA(9) of EMA(26) needs 26 + 9 - 1 = 34 bars
SMAIndicator signal = new SMAIndicator(ema26, 9);
System.out.println(signal.getCountOfUnstableBars()); // 34

A complete indicator chain example

import org.ta4j.core.*;
import org.ta4j.core.indicators.*;
import org.ta4j.core.indicators.helpers.*;

BarSeries series = /* your series */;

// Step 1: Extract close prices
ClosePriceIndicator close = new ClosePriceIndicator(series);

// Step 2: Trend indicators
EMAIndicator ema12 = new EMAIndicator(close, 12);
EMAIndicator ema26 = new EMAIndicator(close, 26);
MACDIndicator macd = new MACDIndicator(close, 12, 26);

// Step 3: Signal line = 9-period EMA of MACD
EMAIndicator signal = new EMAIndicator(macd, 9);

// Step 4: Momentum indicator
RSIIndicator rsi = new RSIIndicator(close, 14);

// Step 5: Volatility
ATRIndicator atr = new ATRIndicator(series, 14);

// Read values at the last bar
int i = series.getEndIndex();
System.out.printf("EMA(12)=%.2f  EMA(26)=%.2f%n",
        ema12.getValue(i).doubleValue(),
        ema26.getValue(i).doubleValue());
System.out.printf("MACD=%.4f  Signal=%.4f%n",
        macd.getValue(i).doubleValue(),
        signal.getValue(i).doubleValue());
System.out.printf("RSI(14)=%.2f  ATR(14)=%.4f%n",
        rsi.getValue(i).doubleValue(),
        atr.getValue(i).doubleValue());

Streaming over all values

Indicator exposes a stream() method that returns a Stream<T> over every bar in the series. This is convenient for logging, analysis, or collecting values into a list:
List<Double> rsiValues = rsi.stream()
        .map(Num::doubleValue)
        .toList();

JSON serialization

Indicators can be serialized to and restored from JSON. This is useful for saving strategy configurations or sharing indicator setups:
// Serialize
String json = rsi.toJson();
// {"type":"RSIIndicator","parameters":{"barCount":14},"components":[{"type":"ClosePriceIndicator"}]}

// Restore
Indicator<?> restored = Indicator.fromJson(series, json);
Custom indicator classes must be in the org.ta4j.core.indicators package (or a sub-package) for the default class resolver to find them during deserialization.