Documentation, examples and further information of the ta4j project
This project is maintained by ta4j Organization
Pearson correlation answers one question: do two series move together in the same direction right now? Trading signals ask harder questions:
ta4j’s correlation and event-analysis surface (0.24.2+) answers all four.
The four lenses ship in two phases and complement each other; this guide shows
when to use which, with the exact API.
Association, not causation. Every tool on this page measures association between series or events. None of them proves that one signal causes another. Use them to describe and filter relationships, then validate causality with controlled experiments on out-of-sample data.
| Question | Tool | Package |
|---|---|---|
| Which lag best relates two continuous indicators, and how strong is it? | LeadLagCorrelationIndicator |
org.ta4j.core.indicators.statistics |
| How similar are two waves when timing may be distorted? | DynamicTimeWarpingDistanceIndicator |
org.ta4j.core.indicators.statistics |
| How well do predicted events match reference events one-to-one? | EventSynchronizationIndicator |
org.ta4j.core.indicators.statistics.event |
| How much does a continuous state reduce uncertainty about a future event? | EventMutualInformationEvaluator |
org.ta4j.core.analysis.event |
All four are deterministic and reproducible, and each ships a committed-data example (see End-to-end demos).
LeadLagCorrelationIndicator scans a bounded lag range as a rolling
indicator and reports, for every bar, the signed correlation at each lag plus
one deterministic “best” lag.
LeadLagCorrelationIndicator leadLag = new LeadLagCorrelationIndicator(
momentum, close, 32, 20, LagSelectionPolicy.MAXIMUM_ABSOLUTE_CORRELATION);
LeadLagCorrelationIndicator.Profile profile = leadLag.getProfile(lastIndex);
LeadLagCorrelationIndicator(first, second, barCount, minimumLag,
maximumLag, selectionPolicy) — explicit inclusive lag range.LeadLagCorrelationIndicator(first, second, barCount, maximumLag,
selectionPolicy) — symmetric convenience; searches
[-maximumLag, maximumLag].The sign convention matches LaggedCorrelationIndicator: a positive lag
means the first indicator leads the second.
LagSelectionPolicy has two values:
MAXIMUM_CORRELATION — the lag with the highest signed correlation.MAXIMUM_ABSOLUTE_CORRELATION — the lag with the highest absolute
correlation; the reported correlation keeps its original sign, so a strong
negative relationship is still reported as negative.getProfile(index) returns an immutable Profile anchored at index:
| Accessor | Meaning |
|---|---|
points() |
One Point per lag in ascending lag order — undefined lags are retained (correlation NaN, sampleCount 0), so the profile is never silently trimmed |
bestLags() |
Every lag tying for the best score under the policy, ascending; empty when no lag is defined |
selectedLag() |
One deterministic pick: smallest absolute lag, then smallest signed lag (a symmetric [-k, k] tie resolves to -k) |
selectedCorrelation() |
The signed correlation at the selected lag |
Each Point carries lag, correlation (Pearson), and sampleCount. A
defined correlation is guaranteed finite and within [-1, 1] up to rounding
tolerance. getValue(index) returns the selected lag’s correlation directly
when you only need the scalar.
When to prefer the profile: when the best lag changes over time or you want the whole correlation curve, read
points(). ThegetValuescalar is the fastest way to feed a best-lag correlation into a strategy rule.
DynamicTimeWarpingDistanceIndicator computes the minimum-cost monotonic
alignment between two rolling windows — the classic way to compare shape
when level, scale, and timing differ.
DynamicTimeWarpingDistanceIndicator dtw = new DynamicTimeWarpingDistanceIndicator(
momentum, close, 32, DynamicTimeWarpingDistanceIndicator.Config.shapeComparison(5));
Num distance = dtw.getValue(lastIndex);
Config.shapeComparison(radius) bundles the baseline shape-comparison setup:
SequenceNormalization.Z_SCORE — removes level and scale, so the distance
measures shape only (two constant sequences have zero shape distance
regardless of their levels);LocalDistance.SQUARED — penalizes large local deviations more strongly;WarpingWindow.sakoeChiba(radius) — a bounded Sakoe–Chiba band;PathCostNormalization.BY_PATH_LENGTH — divides the accumulated cost by the
path length so paths of different lengths stay comparable.If absolute level matters, use SequenceNormalization.NONE;
LocalDistance.ABSOLUTE and PathCostNormalization.NONE are available for
raw-cost semantics.
WarpingWindow.sakoeChiba(radius) — path cells must stay within
radius of the diagonal. Radius 0 forces diagonal alignment, so the
warped distance is the pointwise-cost reduction of the configured
normalization: the sum of local costs under PathCostNormalization.NONE,
or their mean under PathCostNormalization.BY_PATH_LENGTH (the
Config.shapeComparison default).WarpingWindow.unconstrained() — explicit opt-in for full warping at
O(W²) time; prefer a band unless you need it.Complexity is O(W * min(W, 2r + 1)) time and O(W) memory for window W
and radius r. The value is NaN until the window is fully available.
When events are sparse and near-coincident rather than timestamp-identical,
Pearson-style correlation is a poor measure. EventSynchronizationIndicator
scores two Boolean event streams over the same series with deterministic
one-to-one matching:
EventSynchronizationIndicator sync = new EventSynchronizationIndicator(
belowZeroCrosses, swingHighConfirmation, barCount, 12, 12);
Num f1 = sync.getValue(lastIndex); // cached scalar
EventSynchronizationIndicator.Result r = sync.getResult(lastIndex); // full diagnostics
EventSynchronizationIndicator(predicted, reference, barCount,
toleranceBars) — symmetric tolerance window.EventSynchronizationIndicator(predicted, reference, barCount, maxLeadBars,
maxLagBars) — asymmetric; a prediction may lead by at most maxLeadBars
and lag by at most maxLagBars.Only Boolean.TRUE counts as an event; false and null do not. At bar
index, the indicator evaluates the closed trailing window
[index - barCount + 1, index].
Predicted and reference events are matched with a lexicographic objective:
A pair is eligible when -maxLagBars <= offset <= maxLeadBars, where
offset = referenceIndex - predictedIndex — a positive offset means the
prediction leads the reference.
precision, recall, and f1Score follow the usual definitions from the
matched counts:
NaN;NaN and F1 is 0;maxLeadBars would permit it — the correct causal behavior for a
rolling indicator, and what keeps training and validation windows isolated.NaN until the complete window is available: the index must
pass the combined unstable-bar boundary and the whole window must lie inside
the series’ current domain. Windows are never silently truncated.[startIndex, endIndex] range is the
terminal window of an indicator with barCount = endIndex - startIndex + 1.getResult(index) (recomputed per call — use getValue for the cached
scalar) exposes Result:
predictedCount, referenceCount, matchedCount,
falsePositives, falseNegatives;precision, recall, f1Score;matches() — chronological Match records with
predictedIndex, referenceIndex, offsetBars;
unmatchedPredictedIndexes, unmatchedReferenceIndexes (ascending);exactMatchCount, meanSignedOffset, meanAbsoluteOffset,
medianSignedOffset, minSignedOffset, maxSignedOffset;windowAvailable() — whether the window was actually evaluated.The matcher has a hard capacity: a window whose alignment problem needs more
than 8 million cells — (predicted events + 1) * (reference events + 1) >
8_000_000 — throws IllegalArgumentException from getResult/getValue,
and so does a source whose cached event history exceeds the same limit. Sparse
streams stay far below this bound; a dense stream firing on most bars inside a
large window can hit it. Keep windows sparse enough, or handle the exception.
EventMutualInformationEvaluator measures how much a continuous predictor
state reduces uncertainty about whether a target event occurs in an
explicit bar window ahead of the sample:
EventMutualInformationConfig config = new EventMutualInformationConfig(
0, 3, 8, BinningStrategy.EQUAL_FREQUENCY); // current-or-next-3-bars window, 8 bins
EventMutualInformationEvaluator evaluator = new EventMutualInformationEvaluator();
EventMutualInformationResult mi = evaluator.evaluate(
momentum, swingHighConfirmation, 0, lastIndex, config);
mi.mutualInformationNats(); // raw MI in nats
mi.targetEntropyNats(); // H(Y)
mi.normalizedMutualInformation(); // MI / H(Y), always in [0, 1]
mi.sampleCount(); // samples actually evaluated
mi.positiveTargetCount(); // samples with >= 1 target event in window
mi.positiveTargetRate(); // positiveTargetCount / sampleCount
mi.effectiveBinCount(); // bins actually formed
A predictor sample at index i is labeled positive when at least one target
event occurs in the inclusive bar window
[i + targetWindowStartBars, i + targetWindowEndBars]:
(0, 0) labels the sample’s own bar;(1, 3) labels the next three bars — a future-only window.Both offsets are non-negative, and every target index must lie inside the evaluation range: samples whose target window would cross the range boundary are excluded. Target windows never cross the evaluation partition boundary — no look-ahead into validation data.
predictorBinCount (at least 2, capped at MAX_PREDICTOR_BIN_COUNT) requests
bins, then BinningStrategy decides how:
EQUAL_WIDTH — divides the value range into equal-width bins, matching
MutualInformationIndicator. Simple, but skewed distributions can crowd
most samples into a few bins.EQUAL_FREQUENCY — divides the sorted samples into approximately equal
counts. Identical predictor values are never split across bins, so the
effective bin count may be smaller than requested; it is reported in
effectiveBinCount().mutualInformationNats — raw MI in natural units. A constant predictor
(one effective bin) or a constant target carries exactly zero MI.normalizedMutualInformation — MI / H(Y), bounded [0, 1]; 1 means the
predictor state fully determines the event.NaN metrics
with effectiveBinCount 0, while the factual counts stay reported. Data is
never silently dropped.[0, 1]) are rejected at construction, so deserialized or
hand-built results are as trustworthy as evaluator output.EventMutualInformationEvaluator is a one-shot evaluator over an explicit
range. For a rolling Indicator<Num> of equal-width mutual information, see
MutualInformationIndicator; for regime-conditioned correlation,
RegimeSegmentedCorrelationIndicator.
Both examples run on a committed, ossified daily BTC dataset from the examples classpath — fully reproducible, no network access:
ta4jexamples.analysis.EventSynchronizationExample — scores Net Momentum
zero crossings against causal ZigZag swing-high/swing-low confirmation
events with EventSynchronizationIndicator, printing counts, precision,
recall, F1, and the full match diagnostics with signed offsets.ta4jexamples.analysis.LeadLagDtwEventAnalysisExample — runs all three
Phase-2 lenses on the same fixture: the TLCC profile of Net Momentum versus
close price (window 32, lags [-20, 20]), the bounded DTW shape distance
(Config.shapeComparison(5), window 32), and event MI of momentum state
against current-or-future swing confirmations ((0, 3, 8, EQUAL_FREQUENCY)).Confirmation-time semantics (both demos): the ZigZag Boolean indicators are
trueat the bar where a prior pivot becomes confirmed — the first bar at which the reversal is causally known. The historical pivot bar may be several bars earlier. Matching and MI operate on the confirmation indexes; projecting a confirmation back to its pivot index is look-ahead information and must never enter a fitness calculation.
The event-relationship program ships in phases. Phase 1 delivered event synchronization (ta4j PR #1602); Phase 2 delivered the lead/lag profile, DTW shape distance, and event-aware mutual information (ta4j PR #1603).
Phase 3 (Linear CF-455, in Backlog) integrates all four lenses into
ta4j’s unified parameter research workflow (ParameterResearch, from
PR #1542), so the metrics become directly optimizable objectives:
Grid, genetic, and particle-swarm search become interchangeable search plans behind one public seam — switching algorithms is a one-line change — with seeded reproducibility, exact unique-evaluation budgets, and independent holdout/walk-forward validation of the training-selected top K. This page will grow a tuning guide when CF-455 lands.