Download Latest Version ta4j-core-0.26.0-javadoc.jar (6.4 MB) Google Add to Preferred Sources
Home / 0.26.0
Name Modified Size InfoDownloads / Week
Parent folder
ta4j-core-0.26.0-javadoc.jar 2026-10-05 6.4 MB
ta4j-core-0.26.0-sources.jar 2026-10-05 1.5 MB
ta4j-core-0.26.0-tests.jar 2026-10-05 3.9 MB
ta4j-core-0.26.0.jar 2026-10-05 2.6 MB
ta4j-examples-0.26.0-javadoc.jar 2026-10-05 1.0 MB
ta4j-examples-0.26.0-sources.jar 2026-10-05 3.0 MB
ta4j-examples-0.26.0.jar 2026-10-05 3.5 MB
0.26.0 source code.tar.gz 2026-10-05 7.0 MB
0.26.0 source code.zip 2026-10-05 8.2 MB
README.md 2026-10-05 17.4 kB
Totals: 10 Items   37.2 MB 1

0.26.0 (2026-10-05)

Upgrading from 0.25.0: backtest and analysis results change, and backtesting a live series that changes mid-run now throws. Runs no longer use prices from after their window, walk-forward folds close their open positions (paying the exit cost), and mark-to-market curves follow period-return conventions for holding costs and for positions opened before the window. Backtests and analyses of a ConcurrentBarSeries that changes underneath them now throw IllegalStateException instead of silently mixing old and new bars, and they no longer lock the series while strategies run, which removes a deadlock with live feeds. Read Breaking before upgrading.

Breaking

  • Backtests of a changing series fail loudly instead of returning mixed results. BacktestExecutor and walk-forward runs capture the series window when they start and throw IllegalStateException if a bar inside it is replaced, updated in place (including the forming last bar) or evicted before they finish; bars appended after the window are ignored. Previously such changes were silently mixed into the results. A rolling ConcurrentBarSeries with a maximum bar count evicts on every append, so a backtest over it fails whenever a bar arrives mid-run. What to do: pause writes while backtesting, or build the strategies on a stable copy such as series.getSubSeries(series.getBeginIndex(), series.getEndIndex()), which also leaves out the forming bar.
  • No lookahead past a run's window. A run no longer keeps evaluating the strategy on bars after its end to close a position, and a next-open fill for a signal on the last bar no longer lands on the bar after the run (for walk-forward folds, the next fold's first bar). A position still open at the end is marked at the window's last close or ignored, per the criterion's OpenPositionHandling, and a position that closes after an analysis window is charged holding cost only up to the window end. Why it matters: those results used prices the run should not have seen. Execution models and position sizers now receive the series through a read-only view that ends at the run's last bar.
  • Walk-forward folds end flat. A position still open at a fold's last bar is exited at that close and charged the transaction cost, instead of vanishing when the next fold starts flat. Fold criteria now include that exit cost, and fold records can be chained into one out-of-sample history. Plain BarSeriesManager and BacktestExecutor runs still leave the position open unless you wrap their execution model in the new ExitOnRunEndModel.
  • Mark-to-market curves follow period-return conventions. CashFlow, CumulativePnL, Returns and every criterion built on them (drawdowns, Calmar, Sharpe, Sortino, Omega, VaR and more) change in three cases: holding costs now accrue at every held bar, so the curve ends at the realized value instead of charging one period's average per mark; a position entered before the analysed window (for example on a rolling series) is valued from the window's first close, like EnterAndHoldCriterion's benchmark, instead of crediting pre-window gains to the first bar and skewing volatility and drawdown; and a position opened and closed on the first bar now reports its return there. REALIZED curves keep the entry price as cost basis.
  • Analyses of a changing series fail loudly too. Analysis curves retry their capture when bars they read change meanwhile and throw IllegalStateException after 8 attempts; adding a position with calculatePosition to a curve whose bars changed since it was built also throws. Build a new curve, or analyse a stable copy.

Added

  • Portfolio backtesting (CF-148): backtest a basket of instruments held at target weights with the same workflow as a single-series backtest. PortfolioSeries aligns several bar series by bar end time, PortfolioAllocation sets long-only target weights (whatever is left stays in cash, so Map.of("SPY", 0.6) works), and PortfolioSeriesManager.run(...) buys and holds or rebalances on a RebalancePolicy: every bar, every nth bar, on chosen indexes, on the first bar of a calendar period, or when weights drift past a threshold, combinable with or/and. Trades are sized after costs, so cash never goes negative and fixed fees don't cause churn. Each rebalance is reported in a PortfolioSnapshot (holdings, achieved weights, turnover, costs, and whether it completed, partially filled or was skipped), and PortfolioExecutionResult exports a value series that works with the existing criteria. See the StaticPortfolioBacktest example.
  • Portfolio correlations and minimum-variance weights: see how a portfolio's assets move together and let ta4j pick lower-risk weights. PortfolioCorrelations returns price, simple-return and log-return correlation matrices with hierarchical clustering that matches SciPy's complete linkage. MinimumVarianceOptimizer turns a PortfolioSeries into a long-only minimum-variance PortfolioAllocation, optionally capping each asset (new MinimumVarianceOptimizer(series, 0.25)), ready to run through PortfolioSeriesManager. It shrinks the covariance estimate by default (Ledoit-Wolf, as in scikit-learn's LedoitWolf), so short histories or many assets don't pile the weight into a few names; withCovarianceEstimator(CovarianceEstimator.SAMPLE) uses the plain sample covariance. The DiversifiedPortfolioAnalysis example loads adjusted Yahoo prices and writes correlation heatmaps, dendrograms, CSV tables and an HTML report, without new dependencies.
  • Close open positions when a run ends: wrap any execution model in ExitOnRunEndModel, for example new ExitOnRunEndModel(new TradeOnNextOpenModel()), and a position still open at the end of a run is closed at the last close, paying the transaction cost, instead of being left open for criteria to mark to market. Walk-forward folds do this by default. Custom execution models can use the new TradeExecutionModel.onRunEnd overload that receives the run's series.
  • Consistent reads from a live series: BarSeries.withReadLock(...) reads a ConcurrentBarSeries' bounds and bars as one consistent snapshot while a feed keeps writing (on other series it just runs the action). Keep it to short, bar-only reads; evaluating indicators inside it can deadlock. Analysis curves report the window they captured through PerformanceIndicator.getBeginIndex()/getEndIndex(), and BarSeriesUtils.deltaYears(Instant, Instant, NumFactory) computes year fractions from bar times you captured.
  • Cap the threads a backtest batch uses: new BacktestExecutor.executeWithRuntimeReport(..., parallelism) overloads, for fixed amounts or a PositionSizer, run a batch on at most parallelism platform threads without nested parallel streams, so a backtest inside a bounded ForkJoinPool or thread budget stays within it.
  • pow in numeric indicator chains: NumericIndicator.pow(Number) raises any composed indicator to a constant exponent, so chains like NumericIndicator.of(relativeVolume).max(0.25).min(4).pow(0.5) no longer need UnaryOperationIndicator.pow.
  • Adaptive Kalman noise example: AdaptiveKalmanNoiseExample and the adaptive-kalman-noise.md walkthrough show a Kalman filter that adapts to volatility and volume: ATR-based process noise and relative-volume measurement noise fed into KinematicKalmanFilterIndicator through KalmanNoiseIndicator, with same-bar or lagged (--lag-noise) noise, explicit handling of missing or zero volume, and MAE/RMSE comparisons against fixed-noise and last-close baselines. Library defaults are unchanged.
  • Experimental Elliott wave pattern research (CF-525): package-private groundwork, not yet public API, for recognizing Elliott structures (5-wave motive, 3-wave corrective and full 5-3 cycles) from confirmed pivots, with explicit forming, complete, ambiguous and invalidated outcomes, plus a reproducible internal study harness. It changes no public behavior.

Changed

  • Backtests run on your series, with its indexes, without locking it: BarSeriesManager, position-sizing contexts and the Elliott/Wyckoff facades use the series you pass instead of an index-resetting copy, so strategies keyed to absolute indexes (including Integer.MAX_VALUE ranges) fire on offset and rolling series. No series lock is held while strategies run, so a live ConcurrentBarSeries keeps accepting writes, and parallel scheduling applies to every series type. Each execution reports the window it ran on as an immutable, index-preserving barSeries(); executeWithWalkForward shares one window across both phases.
  • Leaner bar change tracking: bars that no series holds no longer carry change-tracking state, and bars held by one series use a compact form; only bars shared by several series keep a full registry. Every series holding a bar now refreshes its indicator caches when the bar changes, including when a price update fails halfway or a BaseBar subclass skips the superclass notification.
  • For contributors: faster test runs: Surefire runs test classes in four bounded JVM forks (ta4j.test.forkCount); the benchmark and analysis-demo lanes stay on one fork so their timings and shared outputs don't interfere.

Fixed

  • Fresh swing and forecast results on revisionless series (CF-638): AbstractRecentSwingIndicator, ProminenceSwingDetector, AdaptiveZigZagSwingDetector and EwmaReturnForecastStateIndicator revalidate retained bar values for series without history revisions. Repeated queries, appends after interior edits and mutations during calculation reset stale swing or EWMA state, including registered SMA/ATR inputs. EWMA refreshes shared return caches before accepting appended ranges, and direct detector-backed price reads refresh cached fallback sources. Each fallback history comparison costs O(retained bars); an initial or replayed detector-backed recent-swing scan can perform O(retained bars²) validation work across its historical queries, in addition to the underlying swing algorithm's cost. Revision-aware series keep constant-time history checks. Queries retry until the observed history is stable; continuous mutation can delay completion.

  • Deadlocks between backtests, indicators and live feeds: indicator caches lock themselves before reading bars, so code that evaluated indicators or user cost models while holding the series read lock could deadlock with another reader once a feed writer was waiting. Backtests, walk-forward runs, analysis curves, Calmar, Omega, return over max drawdown, InvestedInterval and the Elliott/Wyckoff indicators and facades now read bars in short scopes and run everything else outside the lock. Bars mutated directly (for example bar.addPrice(...)) can be seen before the series publishes the change; update them through the series when readers must never see that (a stricter fix is tracked in [#1643]).

  • No deadlocks when concurrent series share bars: inside ConcurrentBarSeries.withWriteLock(...), change notifications for bars shared with other series are delivered once the outermost lock is released, so two series sharing bars can no longer deadlock each other; bars that survive a head eviction are rechecked.
  • Elliott swing detection on a live series: FractalSwingDetector reads the series' revision, bounds and bars in one consistent step, replays without blocking the feed, and rebuilds when bars change, including custom Bar classes. Series without revision tracking revalidate every retained bar a query already read, so an earlier bar mutated in place can no longer leave pivots built from mixed bar states. It throws IllegalStateException after 8 changed attempts instead of returning pivots from mixed data.
  • Indicators refresh after a failed realtime-bar update: when a trade update to a BaseRealtimeBar fails partway through aggregating side or liquidity, the series holding it is still told the bar changed, so its indicators don't keep serving values from before the partial update.
  • Kalman filters start at the first usable bar: KalmanFilterIndicator and the kinematic Kalman indicators and forecasts no longer start from a zero placeholder when the first bars have no usable price or noise, for example during an ATR warm-up. They start at the first valid observation with zero velocity, so early estimates aren't dragged toward zero and show no invented momentum. Data that is valid from the first bar gives the same results as before.
  • Analyses measure exactly their window and one bar history: Calmar and Omega stop at an explicitly bounded record's end instead of scoring the flat tail after it; drawdowns use each curve's own window and measure lengths from its start; MonteCarloMaximumDrawdownCriterion no longer iterates from entries before the window, which could exhaust memory near Integer.MAX_VALUE; curves keep the window they captured when a live series rolls, check the bars they read by value (covering custom Bar classes and series without revision tracking) and ignore bars after a bounded finalIndex; ExcessReturns (Sharpe, Sortino) and Calmar take bar times from the same history as their returns; and backtest verification checks only the bars a run can read.
  • Faster rule and strategy copies: copying rules (getEntryRule()/getExitRule(), composite rule accessors and position-sizing strategy snapshots) no longer scans the logging backend's JVM-wide logger registry, a cost that grew with every logger created in the JVM.
  • Security: safer JSON loading of rules and strategies: deserializing a Rule or Strategy no longer initializes arbitrary classes named in its type field, and unknown or non-rule types fail with the same Unknown rule/strategy type error. ComponentDescriptor#getTypeClass resolves org.ta4j.core simple names.
  • Edge cases: VaR and Expected Shortfall return NaN when a return in the distribution is undefined instead of sorting NaN into the tail; return over max drawdown returns its neutral value for a null record, like Calmar and Omega; a backtest that started on an empty series no longer fails when bars are appended; curves no longer evaluate holding-cost models for positions outside their window; and loops over windows ending at Integer.MAX_VALUE no longer wrap around.
  • Bounded backtests and Omega returns honor their exact start: execution-series views hide bars before a delegate's logical begin and report that hidden prefix as removed, so indicators such as ParabolicSarIndicator no longer index before it; ExitOnRunEndModel does not force-close positions when a run processes no bars; backtest results reject a logical end that contracted during the run; and Omega retains a closed return seeded at the bounded start even when open-position marks are ignored. CashFlow keeps same-bar realized exits at the bounded analysis end. Curves on a pruned or rolling series now carry the realized result of positions closed before the retained begin instead of restarting at 1/0, while positions before an explicit record or requested start stay excluded; curves recheck every bar field (not only close and end time) before trusting user cost models; position-based drawdown criteria treat an exit after the analysed end as open at that end, per the configured open-position handling; and drawdowns of custom PerformanceIndicator curves stop at an explicitly bounded record's end.
  • Criteria judge positions as of the analysed end: a position (or lot) that exits after the analysed end counts as open there, so open unrealized profit and open cost basis sum every lot active at that end (and skip open lots entered after it), NumberOfPositionsCriterion counts it as open, windowed analyses mark it to market (or drop it under OpenPositionHandling.IGNORE), and Sharpe/Sortino trade samples respect IGNORE for it. Sharpe and Sortino sample only the captured window and its captured bar times, so bars appended or replaced mid-analysis no longer add artificial returns. Return over max drawdown and Monte Carlo drawdown no longer count realized P&L carried in from before a rolling series' retained begin as a return of the window.
  • Window edges are measured, not guessed: curves verify the bars before their window that a holding-cost model may read, so a bar replaced there mid-analysis triggers a recapture instead of a stale borrow or funding cost. InvestedInterval, InPositionPercentageCriterion, NumberOfBarsCriterion and PositionDurationCriterion stop at the record's logical end and honor OpenPositionHandling.IGNORE, so a risk-free cash leg no longer skews Sharpe and Sortino. Return over max drawdown, Calmar, the drawdown criteria and Monte Carlo drawdown start from the equity entering the window, new PerformanceIndicator#getBaselineValue(), so a same-bar round trip or a loss realized on a window's first bar counts instead of cancelling out.
  • Faster DecimalNum fractional powers: DecimalNum.pow(Num) computes the whole-number part of the exponent at working precision plus guard digits instead of exactly, so large exponents (such as annualizing over years of bars) no longer take seconds; negative fractional exponents are supported too, and values using an unlimited MathContext stay exact.
Source: README.md, updated 2026-10-05