Ta4j Wiki

Documentation, examples and further information of the ta4j project

View the Wiki On GitHub

This project is maintained by ta4j Organization

Serialization and Named Shorthand

ta4j can persist and rebuild indicators, rules, strategies, and analysis criteria. The default JSON form is a canonical descriptor tree: it is explicit, stable, and intended for durable storage.

Preview status: This page documents the companion serialization shorthand feature in ta4j PR #1507, targeting ta4j 0.23.1. The named shorthand APIs are not available from the latest released ta4j artifacts or from ta4j master until that implementation PR is merged and released.

The PR #1507 implementation adds compact named shorthand for authoring configuration, presenting strategy choices in UIs, and round-tripping common graphs without hand-writing full descriptor JSON.

Use this guide when you want to store a strategy, pass a rule or indicator through a service boundary, load strategy presets from configuration, or let users choose named assets such as SMA(7,21) or SharpeRatio.

Choose the Right Form

Form Best for API
Canonical descriptor JSON Long-term storage, audit logs, exact graph persistence, and maximum compatibility toJson(), fromJson(...), describe(...)
Strategy JSON v2 Human-authored strategy configuration with named rule and indicator shorthand Strategy.fromJson(series, json)
Named expressions UI pickers, CLI arguments, compact config values, and registry extension points toExpression(...), fromExpression(...)
Compact strategy JSON Exporting a recognized strategy graph as a v2 config strategy.toCompactJson()

Canonical JSON remains the baseline. strategy.toJson() always emits the canonical descriptor form. Compact output is explicit: call strategy.toCompactJson() only when you want the opt-in v2 envelope.

Strategy Quickstart

The shortest built-in strategy macro is an SMA crossover:

String json = """
        {
          "version": 2,
          "strategy": "SMA(7,21)",
          "name": "Daily_SMA_Crossover"
        }
        """;

Strategy strategy = Strategy.fromJson(series, json);
String canonicalJson = strategy.toJson();
String compactJson = strategy.toCompactJson();

Strategy macroStrategy = Strategy.fromExpression(series, "SMA(7,21)");
String expression = macroStrategy.toExpression(); // "SMA(7,21)"

SMA(7,21) builds a BaseStrategy whose entry rule is CrossedUpIndicatorRule(SMA(7), SMA(21)) and whose exit rule is CrossedDownIndicatorRule(SMA(7), SMA(21)). The strategy’s unstable bar count is set to the slow SMA period.

If you need full control over entry and exit logic, write a v2 strategy with rule shorthand:

String json = """
        {
          "version": 2,
          "name": "Momentum_With_Risk_Stops",
          "unstableBars": 26,
          "startingType": "BUY",
          "entryRule": "And(SmaCrossUp(12,26),Under(RSI(14),70))",
          "exitRule": "Or(SmaCrossDown(12,26),StopLoss(2.5%))"
        }
        """;

Strategy strategy = Strategy.fromJson(series, json);

The same fields can be expressed as objects when that is easier for generated JSON:

{
  "version": 2,
  "name": "Momentum_With_Risk_Stops",
  "entryRule": {
    "type": "CrossedUpIndicatorRule",
    "args": ["SMA(12)", "SMA(26)"]
  },
  "exitRule": {
    "type": "OrRule",
    "rules": [
      { "type": "CrossedDownIndicatorRule", "args": ["SMA(12)", "SMA(26)"] },
      { "type": "StopLossRule", "args": ["2.5%"] }
    ]
  }
}

Strategy v2 accepts these metadata fields:

Field Meaning
version Required for v2, must be 2.
strategy Optional top-level strategy macro such as SMA(7,21). If present, entry and exit rules come from the macro; do not also provide entryRule or exitRule.
name Optional strategy name. Use null to intentionally preserve an unnamed strategy when compacting a macro-shaped strategy.
type Optional, but only BaseStrategy or org.ta4j.core.BaseStrategy is accepted by v2. Use canonical JSON or a registered macro for other strategy implementations.
unstableBars Optional non-negative integer override.
startingType Optional BUY or SELL.
entryRule, exitRule Required when strategy is absent. Values may be expression strings or rule objects.

Use Strategy.fromExpression(series, "SMA(7,21)") when the whole strategy is a single registered macro. Use Strategy.fromJson(series, json) when you need metadata overrides, explicit entry/exit rules, or a stored v2 envelope.

Indicators

Indicators can round-trip through canonical JSON:

ClosePriceIndicator close = new ClosePriceIndicator(series);
RSIIndicator rsi = new RSIIndicator(close, 14);

String json = rsi.toJson();
Indicator<?> restored = Indicator.fromJson(series, json);

For compact authoring, use expressions:

Indicator<?> close = Indicator.fromExpression(series, "ClosePrice");
Indicator<?> sma = Indicator.fromExpression(series, "SMA(21)");
Indicator<?> rsiOfSma = Indicator.fromExpression(series, "RSI(SMA(14),9)");

String expression = sma.toExpression(); // "SMA(21)"

Built-in indicator shorthand:

Expression Descriptor meaning
ClosePrice ClosePriceIndicator
ClosePriceIndicator ClosePriceIndicator, accepted for input but not preferred for compact output
SMA(barCount) SMAIndicator(ClosePriceIndicator, barCount)
SMA(indicator,barCount) SMAIndicator(indicator, barCount)
EMA(barCount) EMAIndicator(ClosePriceIndicator, barCount)
EMA(indicator,barCount) EMAIndicator(indicator, barCount)
RSI(barCount) RSIIndicator(ClosePriceIndicator, barCount)
RSI(indicator,barCount) RSIIndicator(indicator, barCount)

Indicator expression export succeeds only when the registry recognizes the descriptor. If a graph includes an unsupported indicator, keep using canonical indicator.toJson() until you add a registry binding and formatter for that indicator.

Rules

Rules can also round-trip through canonical JSON:

Rule entry = new CrossedUpIndicatorRule(new SMAIndicator(close, 12), new SMAIndicator(close, 26));

String json = entry.toJson();
Rule restored = Rule.fromJson(series, json);

Expression form keeps common trading logic readable:

Rule entry = Rule.fromExpression(series,
        "And(SmaCrossUp(12,26),Under(RSI(14),70))");

Rule exit = Rule.fromExpression(series,
        "Or(SmaCrossDown(12,26),StopLoss(2.5%))");

Built-in rule shorthand:

Expression Descriptor meaning
SmaCrossUp(fast,slow) CrossedUpIndicatorRule(SMA(fast), SMA(slow))
SmaCrossDown(fast,slow) CrossedDownIndicatorRule(SMA(fast), SMA(slow))
CrossedUp(indicator,indicatorOrNumber) CrossedUpIndicatorRule
CrossedDown(indicator,indicatorOrNumber) CrossedDownIndicatorRule
Over(indicator,indicatorOrNumber) OverIndicatorRule
Under(indicator,indicatorOrNumber) UnderIndicatorRule
And(left,right) AndRule
Or(left,right) OrRule
StopLoss(lossPercentage) StopLossRule(ClosePriceIndicator, lossPercentage)
StopGain(gainPercentage) StopGainRule(ClosePriceIndicator, gainPercentage)

Class-style aliases such as AndRule, OrRule, OverIndicatorRule, UnderIndicatorRule, CrossedUpIndicatorRule, CrossedDownIndicatorRule, StopLossRule, and StopGainRule are accepted as input where the v2 object syntax is more natural. Compact output prefers the shorter names above when a formatter recognizes the descriptor.

Analysis Criteria

Analysis criteria serialize through canonical descriptor JSON and can also use default named aliases:

AnalysisCriterion criterion = AnalysisCriterion.fromExpression("SharpeRatio");
String json = criterion.toJson();
AnalysisCriterion restored = AnalysisCriterion.fromJson(json);
String expression = restored.toExpression(); // "SharpeRatio"

Built-in analysis criterion aliases map to their default constructors:

Expression Criterion
NetProfit NetProfitCriterion
GrossReturn GrossReturnCriterion
NetReturn NetReturnCriterion
MaximumDrawdown MaximumDrawdownCriterion
ReturnOverMaxDrawdown ReturnOverMaxDrawdownCriterion
SharpeRatio SharpeRatioCriterion
SortinoRatio SortinoRatioCriterion
TotalFees TotalFeesCriterion
NumberOfPositions NumberOfPositionsCriterion

Parameterized criteria should use canonical JSON unless you register a custom formatter that can represent every constructor argument without losing information.

Custom Shorthand

Use NamedAssetRegistry when your application has its own aliases. Registries are immutable after build, and aliases are scoped by NamedAssetKind, so SMA(7) can mean an indicator while SMA(7,21) can mean a strategy macro.

NamedAssetRegistry registry = NamedAssetRegistry.builder()
        .withDefaults()
        .registerIndicator("FastCloseSma", List.of("barCount"), args -> {
            args.requireCount(1);
            return ComponentDescriptor.builder()
                    .withType("SMAIndicator")
                    .withParameters(Map.of("barCount", args.positiveInt(0)))
                    .addComponent(ComponentDescriptor.typeOnly("ClosePriceIndicator"))
                    .build();
        })
        .build();

Indicator<?> indicator = Indicator.fromExpression(series, "FastCloseSma(5)", registry);

Factories receive typed NamedAssetRegistry.Arguments helpers:

Helper Use
requireCount(expected) Fail fast when an alias receives the wrong arity.
positiveInt(index) Read a positive bar count or lookback.
intValue(index) Read an integer that may be zero or negative.
finiteNumberText(index) Read a finite JSON-style number, accepting an optional trailing %.
stringValue(index) Read a quoted string or bare token.
indicatorDescriptor(index) Expand an argument as an indicator descriptor.
ruleDescriptor(index) Expand an argument as a rule descriptor.

Add a formatter when you also want toExpression(...) or toCompactJson(...) to emit your alias. Without a formatter, the alias is still valid for input.

If an indicator alias may be used inside numeric comparison rules such as Over(...), Under(...), CrossedUp(...), or CrossedDown(...), it must rebuild an indicator whose runtime values are Num. Boolean indicators belong behind boolean-aware rules such as BooleanIndicatorRule; v2 strategy parsing rejects non-numeric indicator aliases at the rule argument that used them.

To make application defaults automatic, implement NamedAssetProvider and register it with Java ServiceLoader:

META-INF/services/org.ta4j.core.named.NamedAssetProvider

The file should contain the fully qualified provider class name. NamedAssetRegistry.defaultRegistry() loads ta4j defaults first, then provider bindings.

Grammar and Validation

The expression grammar is intentionally strict:

When processing lists of expressions from a CLI or configuration file, do not split on commas manually. Use NamedAssetRegistry#splitTopLevel(...) so nested expressions and quoted strings stay intact:

List<String> entries = NamedAssetRegistry.defaultRegistry()
        .splitTopLevel("SMA(7),Custom(\"a,b\",SMA(21)),RSI(14)");

Round-Trip Checklist