Rules define entry and exit conditions; strategies combine them into a complete trading system.
A ta4j strategy is built from two halves: an entry rule that decides when to open a position, and an exit rule that decides when to close it. Each half is a Rule — a pure boolean condition evaluated at a given bar index.
It returns true when the condition is met at the given bar index. The tradingRecord parameter is provided for rules that need to inspect current position state (e.g., stop-loss rules that compare against the entry price). For most indicator-based rules, tradingRecord is not used.A convenience overload omits the record:
boolean isSatisfied(int index); // delegates with tradingRecord = null
Rules compose with four logical operations, all returning a new Rule:
Rule a = /* ... */;Rule b = /* ... */;Rule both = a.and(b); // true when both are trueRule either = a.or(b); // true when at least one is trueRule exactly = a.xor(b); // true when exactly one is trueRule not = a.negation(); // true when a is false
These methods return fresh Rule instances (AndRule, OrRule, XorRule, NotRule) and can be chained fluently.
Entry rules describe when to open a position. Common built-in options:
CrossedUpIndicatorRule
Satisfied when the first indicator crosses above the second indicator (or a fixed threshold) from below. Commonly used for EMA/SMA golden-cross entries.
import org.ta4j.core.rules.CrossedUpIndicatorRule;// Buy when fast EMA crosses above slow EMARule entry = new CrossedUpIndicatorRule(fastEma, slowEma);// Buy when RSI crosses above 30 (exits oversold)Rule oversoldExit = new CrossedUpIndicatorRule(rsi, 30);
OverIndicatorRule
Satisfied when an indicator’s value is above another indicator or a fixed threshold.
Exit rules describe when to close a position. Common built-in options:
CrossedDownIndicatorRule
Satisfied when the first indicator crosses below the second. Commonly used for EMA/SMA death-cross exits.
import org.ta4j.core.rules.CrossedDownIndicatorRule;// Exit when fast EMA crosses below slow EMARule exit = new CrossedDownIndicatorRule(fastEma, slowEma);
StopLossRule
Satisfied when the current price falls by more than a given percentage from the entry price.
import org.ta4j.core.rules.StopLossRule;// Cut losses at -2%Rule stopLoss = new StopLossRule(close, 2.0);
StopGainRule
Satisfied when the current price rises by more than a given percentage from the entry price.
import org.ta4j.core.rules.StopGainRule;// Take profit at +5%Rule takeProfit = new StopGainRule(close, 5.0);
TrailingStopLossRule
Satisfied when the current price falls by a given percentage from the highest price seen since entry. The stop trails the price upward automatically.
import org.ta4j.core.rules.TrailingStopLossRule;// Trail stop 3% below the peakRule trailingStop = new TrailingStopLossRule(close, series.numFactory().numOf(3));
BaseStrategy is the standard Strategy implementation. Pass it an entry rule, an exit rule, and optionally a name and unstable-bar count:
import org.ta4j.core.BaseStrategy;import org.ta4j.core.Strategy;Strategy strategy = new BaseStrategy( "EMA Crossover", // human-readable name entryRule, // Rule: when to open a position exitRule, // Rule: when to close a position 26 // unstableBars: ignore signals for the first 26 bars);
The strategy exposes shouldEnter and shouldExit methods that wrap the underlying rules and automatically suppress signals during the unstable (warm-up) period:
int i = series.getEndIndex();if (strategy.shouldEnter(i)) { // Open a position}if (strategy.shouldExit(i, tradingRecord)) { // Close the current position}
Both methods skip the bar if index < unstableBars — no entry or exit signal is ever generated before the strategy is fully warmed up.
Two Strategy objects can be merged with .and() or .or():
// Enter only when both strategies agreeStrategy combined = strategyA.and(strategyB);// Enter when either strategy firesStrategy either = strategyA.or(strategyB);
The combined strategy’s unstableBars is automatically set to the maximum of the two component strategies.
import org.ta4j.core.*;import org.ta4j.core.indicators.*;import org.ta4j.core.indicators.helpers.*;import org.ta4j.core.rules.*;BarSeries series = /* your series */;ClosePriceIndicator close = new ClosePriceIndicator(series);EMAIndicator fastEma = new EMAIndicator(close, 12);EMAIndicator slowEma = new EMAIndicator(close, 26);RSIIndicator rsi = new RSIIndicator(close, 14);// Entry: golden cross AND RSI above 50 (confirms bullish momentum)Rule entry = new CrossedUpIndicatorRule(fastEma, slowEma) .and(new OverIndicatorRule(rsi, 50));// Exit: death cross OR 3% stop-loss OR 8% take-profitRule exit = new CrossedDownIndicatorRule(fastEma, slowEma) .or(new StopLossRule(close, 3.0)) .or(new StopGainRule(close, 8.0));// Ignore signals for the first 26 bars (slowEma warm-up)Strategy strategy = new BaseStrategy("Trend + Momentum", entry, exit, 26);
Set unstableBars to at least the getCountOfUnstableBars() value of your slowest indicator. This prevents the strategy from generating signals against indicator values that are still in their warm-up phase.