A Java library that provides a variety of technical indicators for time series data, such as OHLC bars.
For build.gradle.kts:
implementation("trade.invision", "indicators", "1.2.0")For build.gradle:
implementation group: 'trade.invision', name: 'indicators', version: '1.2.0'For pom.xml:
<dependency>
<groupId>trade.invision</groupId>
<artifactId>indicators</artifactId>
<version>1.2.0</version>
</dependency>There are two base classes in this library to be aware of:
Series and
Indicator.
Series is a class that provides a simple interface for storing and retrieving values from a series. Values
can only be added to the Series, except for the last value, which can be replaced with a new value. When the
number of values stored in the Series exceed a configurable maximum upon adding a new value, then values at
the beginning of the Series are removed. This emulates the behavior of a bar/candlestick chart that displays
historical bars along with a continuously-updating end bar that reflects live trade data. There are two types of
Series this library provides: BarSeries
(for a series of Bars) and
NumSeries (for a series of
NumDatapoints).
Indicator is an abstract class for performing calculations on a Series or another Indicator, with optional result
caching. An Indicator will implement some formula/algorithm and provide the result of the calculation at a given
index. Some Indicator implementations and consumer access patterns may retrieve calculated results at
previously-calculated indices. So, in order to prevent redundantly recalculating the results at the same index, the
Indicator can cache its results in memory, with the cache size being the same as the Series maximum length. Result
caching is disabled by default since the typical access pattern is to continually calculate the Indicator value at the
end index of a Series and perform some action based on that result. The cache should be enabled when consumers of an
Indicator use the calculated values of non-ending indices, as opposed to only using the calculated value of the end
index. Indicator implementations that utilize recursion should extend
RecursiveIndicator, which forces caching
and prevents
StackOverflowError
exceptions. Typically, an Indicator will be one of the following three types:
Num (decimal number),
Boolean (true/false), or
Instant (timestamp). Each
Indicator implementation provides a public static method for consumers to acquire an instance reference to the
Indicator, as the constructor for Indicator implementations is protected and cannot be instantiated directly.
Check out the Javadoc for all classes and method signatures, but here's a simple example:
Imports
import trade.invision.indicators.indicators.Indicator;
import trade.invision.indicators.indicators.barprice.Hlc3;
import trade.invision.indicators.indicators.barprice.Ohlc4;
import trade.invision.indicators.indicators.bullishbearish.global.GlobalBullishPercentage;
import trade.invision.indicators.indicators.crossed.Crossed;
import trade.invision.indicators.indicators.ma.sma.SimpleMovingAverage;
import trade.invision.indicators.indicators.statistical.StandardDeviation;
import trade.invision.indicators.series.bar.Bar;
import trade.invision.indicators.series.bar.BarSeries;
import trade.invision.num.Num;
import java.io.InputStream;
import java.net.URI;
import java.time.Instant;
import static java.lang.Long.parseLong;
import static java.time.Instant.ofEpochMilli;
import static java.time.temporal.ChronoUnit.DAYS;
import static trade.invision.indicators.indicators.bar.Close.close;
import static trade.invision.indicators.indicators.barprice.Hlc3.typicalPrice;
import static trade.invision.indicators.indicators.barprice.Ohlc4.ohlc4;
import static trade.invision.indicators.indicators.bullishbearish.global.GlobalBullishPercentage.globalBullishPercentage;
import static trade.invision.indicators.indicators.crossed.Crossed.crossed;
import static trade.invision.indicators.indicators.ma.MovingAverageSupplier.wwmaSupplier;
import static trade.invision.indicators.indicators.ma.sma.SimpleMovingAverage.simpleMovingAverage;
import static trade.invision.indicators.indicators.rsi.RelativeStrengthIndex.rsi;
import static trade.invision.indicators.indicators.statistical.StandardDeviation.stddev;final BarSeries barSeries = new BarSeries(20); // Store a maximum of 20 bars in memory
// Get AAPL 1D bar data from 2025-01-01 to 2025-02-01.
try (final InputStream inputStream = URI.create("https://pastebin.com/raw/dnLSgw44").toURL().openStream()) {
for (String barDataLine : new String(inputStream.readAllBytes()).split("\n")) {
final String[] barDataCsv = barDataLine.trim().split(",");
final Instant start = ofEpochMilli(parseLong(barDataCsv[5]));
final Instant end = start.plus(1, DAYS);
final Num open = barSeries.numOf(barDataCsv[1]);
final Num high = barSeries.numOf(barDataCsv[3]);
final Num low = barSeries.numOf(barDataCsv[4]);
final Num close = barSeries.numOf(barDataCsv[2]);
final Num volume = barSeries.numOf(barDataCsv[0]);
final Num tradeCount = barSeries.numOf(barDataCsv[6]);
final Bar bar = new Bar(start, end, open, high, low, close, volume, tradeCount);
barSeries.add(bar);
System.out.println("Added: " + bar);
}
}
// Calculate the Simple Moving Average (SMA) using the bar average price.
final Ohlc4 averagePrice = ohlc4(barSeries);
final SimpleMovingAverage sma = simpleMovingAverage(averagePrice, 20);
for (long index = barSeries.getStartIndex(); index <= barSeries.getEndIndex(); index++) {
System.out.printf("OHLC4 20 SMA at %d: %s\n", index, sma.getValue(index));
}
// Calculate the Standard Deviation (stddev) using the bar typical price.
final Hlc3 typicalPrice = typicalPrice(barSeries);
final StandardDeviation stddev = stddev(typicalPrice, 5, false);
for (long index = barSeries.getStartIndex(); index <= barSeries.getEndIndex(); index++) {
System.out.printf("HLC3 5 STDDEV at %d: %s\n", index, stddev.getValue(index));
}
// Calculate the Relative Strength Index (RSI) using the bar close price and Welles Wilder Moving Average (WWMA).
final Indicator<Num> close = close(barSeries);
final Indicator<Num> rsi = rsi(close, 10, wwmaSupplier());
for (long index = barSeries.getStartIndex(); index <= barSeries.getEndIndex(); index++) {
System.out.printf("C 10 RSI at %d: %s\n", index, rsi.getValue(index));
}
// Calculate the percentage of bullish bars.
final GlobalBullishPercentage globalBullishPercentage = globalBullishPercentage(barSeries);
for (long index = barSeries.getStartIndex(); index <= barSeries.getEndIndex(); index++) {
System.out.printf("Bullish %% at %d: %s%%\n", index,
globalBullishPercentage.getValue(index).multiply(100).round());
}
// Calculate SMA crossovers with bar close price.
final Crossed crossed = crossed(sma, close);
for (long index = barSeries.getStartIndex(); index <= barSeries.getEndIndex(); index++) {
System.out.printf("OHLC4 20 SMA crossed C at %d: %s\n", index, crossed.getValue(index));
}This library was inspired by the excellent ta4j library. There are several improvements and additions that this library provides:
- Usage of improved
Numinterface via the Num library. - Configurable
Indicatorresult caching. Indicatorinstance caching.- Several optimizations to reduce memory consumption and improve execution time for common access patterns (e.g.
consecutive indices, identical indices, caching and reusing
Indicatorinstances with identical configurations). - Configurable moving average types for
Indicatorimplementations that utilize moving averages. - Configurable epsilon per
Series. - Generic
Seriesinterface, allowing forNumSeriesand such. - Documentation improvements.
- Improved package structure.
- Several more helper/utility
Indicatorimplementations.
Contributions are welcome. Use existing Indicator class implementations as a reference for new implementations. A few
guidelines for creating Indicator class implementations are as follows:
- There should only be one constructor and it should use the
protectedaccess modifier. - Create public static methods for consumers to use for instantiation or retrieval from a weak-valued instance cache. The method name should be the full indicator name. Add overloads for acronyms.
- All implementations should implement a weak-valued instance cache, except for implementations that are subclasses of
CachelessIndicatororNum/Boolean/Instantoperations (e.g.UnaryOperation,BinaryOperation, andTernaryOperation). - The class Javadoc should include an
@seewebsite reference for the indicator formula/algorithm. - Avoid declarative calculations via the
UnaryOperation,BinaryOperation, orTernaryOperationindicators. Prefer to imperatively calculate results in thecalculatemethod. - Avoid creating anonymous
Indicatorinstances. - Prefer to use
Num,Boolean, orInstantas the type for theIndicator. UseNumeven in cases where anIndicatoronly uses integer values. This greatly improves interoperability betweenIndicatorimplementations and is more convenient.
This project is maintained by Invision. Invision enables you to automate and test your investment and trading strategies across billions of data points in seconds using quantitative finance and algorithmic trading programs you build on our code or no-code platform.