Ta4j Wiki

Documentation, examples and further information of the ta4j project

View the Wiki On GitHub

This project is maintained by ta4j Organization

The Num Interface

What is Num?

Num is ta4j’s numeric abstraction. Every indicator, criterion, and BarSeries uses it so you can swap numeric backends without rewriting trading logic. A NumFactory is tied to each BarSeries; it produces instances of the chosen implementation and guarantees consistent arithmetic throughout the series.

Built-in implementations:

Take a look at the usage examples to see Num and BaseBarSeries in action.

Choosing the right Num

Every calculation faces the precision vs. speed trade-off:

Pick DecimalNum when you care about exact decimals (crypto pairs, high-value assets, long test runs). Pick DoubleNum when latency matters more than an ulp of precision. You can also add your own implementation by extending Num and providing a NumFactory.

BarSeries lightningFast = new BaseBarSeriesBuilder()
        .withName("fast_series")
        .withNumFactory(DoubleNumFactory.getInstance())
        .build();

Configuring DecimalNum precision

DecimalNum uses 16 digits of precision with HALF_UP rounding by default (reduced from 32 digits in 0.19 for improved performance). You can configure precision in several ways:

Configure global default precision (affects all new DecimalNum instances):

// Set global default to 32-digit precision (preserves HALF_UP rounding)
DecimalNum.configureDefaultPrecision(32);

// Or configure both precision and rounding mode globally
DecimalNum.configureDefaultMathContext(new MathContext(32, RoundingMode.HALF_EVEN));

// Reset to library defaults (16 digits, HALF_UP)
DecimalNum.resetDefaultPrecision();

Use a custom factory for a specific series (doesn’t affect global defaults):

// Create a series with custom precision/rounding for this series only
BarSeries highPrecisionSeries = new BaseBarSeriesBuilder()
        .withName("btc_usdt")
        .withNumFactory(DecimalNumFactory.getInstance(new MathContext(40, RoundingMode.HALF_EVEN)))
        .build();

Check current default precision:

int currentPrecision = DecimalNum.getDefaultPrecision(); // returns 16 by default
MathContext currentContext = DecimalNum.getDefaultMathContext();

Increasing precision protects against rounding errors when you chain many indicators, but every arithmetic call becomes more expensive. Benchmark both settings if you’re pushing millions of bars or large strategy grids. The default 16 digits provides sufficient accuracy for most trading calculations while maintaining good performance.

Performance characteristics

The ta4j-examples module includes a benchmark (DecimalNumPrecisionPerformanceTest) that quantifies the precision vs. performance trade-off. The benchmark measures execution time for common indicator calculations (EMA, mean, variance, volatility) across different precision settings.

General performance patterns

Precision vs. speed trade-off:

Performance scaling:

Accuracy considerations:

Benchmarking your workload

To measure precision impact on your specific use case, run the benchmark:

// Run the precision performance benchmark
ta4jexamples.num.DecimalNumPrecisionPerformanceTest.main(new String[0]);

The benchmark outputs CSV data showing:

Interpreting results:

Real-world guidance:

Handling Num from your code

Every BarSeries exposes its factory via series.numFactory(). Use it whenever you need a literal so the value matches the series’ numeric type:

Num three = series.numFactory().numOf(3);
series.addTrade(three, series.numFactory().numOf(10.5)); // accepts Number and converts internally

Create bars with the series’ barBuilder(); all numeric inputs are converted using the same factory:

Bar bar = series.barBuilder()
        .timePeriod(Duration.ofMinutes(1))
        .endTime(Instant.now())
        .openPrice(100.0)
        .highPrice(101.0)
        .lowPrice(99.5)
        .closePrice(100.7)
        .volume(42)
        .build();
series.addBar(bar);

Important: A BarSeries has a fixed numeric backend. Mixing different Num types on the same series (e.g., creating a DoubleNum literal and feeding it into a DecimalNum series) will throw or produce undefined behavior—always go through numFactory().

Implementing your own Num

If you need a custom numeric type (decimal128, fixed-point integers, GPU-backed tensors, etc.), implement the Num interface plus a matching NumFactory. As long as the factory can produce zero(), one(), and numOf(...), the rest of ta4j will treat it like any other Num.