Documentation, examples and further information of the ta4j project
This project is maintained by ta4j Organization
A comprehensive guide to using ta4j’s Elliott Wave analysis tools, from basic concepts to advanced trading strategies.
Elliott Wave Theory, developed by Ralph Nelson Elliott in the 1930s, is a form of technical analysis that identifies recurring patterns in market price movements. The theory posits that market prices move in predictable wave patterns that reflect investor psychology.
Elliott Wave patterns consist of two main types of waves:
A fundamental challenge in Elliott Wave analysis is that any given price action can often support multiple valid wave interpretations. Two experienced analysts looking at the same chart may arrive at different counts—and both may be technically valid according to Elliott Wave rules.
This ambiguity is not a flaw in the theory; it reflects the inherent uncertainty in market forecasting. Ta4j addresses this challenge directly through:
ta4j provides a comprehensive suite of indicators for Elliott Wave analysis, designed to work seamlessly with the rest of the ta4j ecosystem. The implementation follows Elliott Wave principles while providing flexibility for different trading styles and timeframes.
The Elliott Wave indicators in ta4j are built on several key principles:
NaN values rather than throwing exceptions.The Elliott Wave system in ta4j follows a layered architecture:
BarSeries
↓
Swing Detection (Fractal/ZigZag)
↓
ElliottSwingIndicator (alternating swings)
↓
┌─────────────────────────────────────────────────────────┐
│ Deterministic Analysis Layer │
├─────────────────────────────────────────────────────────┤
│ • ElliottPhaseIndicator (single phase output) │
│ • ElliottRatioIndicator (Fibonacci ratios) │
│ • ElliottWaveCountIndicator (swing counting) │
│ • ElliottConfluenceIndicator (signal aggregation) │
│ • ElliottChannelIndicator (price channels) │
│ • ElliottInvalidationIndicator (boolean invalidation) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Scenario-Based Analysis Layer │
├─────────────────────────────────────────────────────────┤
│ • ElliottScenarioIndicator (ranked alternatives) │
│ • ElliottConfidenceScorer (confidence calculation) │
│ • ElliottScenarioGenerator (scenario exploration) │
│ • ElliottProjectionIndicator (Fibonacci targets) │
│ • ElliottInvalidationLevelIndicator (price levels) │
└─────────────────────────────────────────────────────────┘
Use the Deterministic Analysis Layer when:
Use the Scenario-Based Analysis Layer when:
Immutable representation of a single swing between two pivots:
ElliottSwing swing = swings.get(0);
int startBar = swing.fromIndex(); // Bar index where swing starts
int endBar = swing.toIndex(); // Bar index where swing ends
Num startPrice = swing.fromPrice(); // Price at swing start
Num endPrice = swing.toPrice(); // Price at swing end
ElliottDegree degree = swing.degree(); // Wave degree (MINOR, INTERMEDIATE, etc.)
boolean rising = swing.isRising(); // true if toPrice >= fromPrice
Num amplitude = swing.amplitude(); // Absolute price displacement
int barCount = swing.length(); // Number of bars covered
Utility class providing a validated snapshot of swing statistics:
List<ElliottSwing> swings = swingIndicator.getValue(index);
ElliottSwingMetadata metadata = ElliottSwingMetadata.of(swings, series.numFactory());
// Check validity
if (metadata.isValid()) {
// Access swing statistics
int count = metadata.size();
Num highest = metadata.highestPrice(); // Highest price across all swings
Num lowest = metadata.lowestPrice(); // Lowest price across all swings
// Access swing subsets
List<ElliottSwing> first5 = metadata.leading(5); // First 5 swings
List<ElliottSwing> last3 = metadata.trailing(3); // Last 3 swings
List<ElliottSwing> middle = metadata.subList(2, 5); // Swings 2-4
// Access individual swings
ElliottSwing first = metadata.swing(0);
}
ElliottSwingMetadata validates that all swings contain valid (non-null, non-NaN) prices and provides convenient methods for accessing swing subsets and statistics.
Enumeration of wave phases with helper methods:
ElliottPhase phase = ElliottPhase.WAVE3;
phase.isImpulse(); // true for WAVE1-WAVE5
phase.isCorrective(); // true for CORRECTIVE_A/B/C
phase.completesStructure(); // true for WAVE5 and CORRECTIVE_C
phase.impulseIndex(); // 1-5 for impulse waves, -1 otherwise
phase.correctiveIndex(); // 1-3 for corrective waves, -1 otherwise
Captures Fibonacci-style ratios between swings:
ElliottRatio ratio = ratioIndicator.getValue(index);
if (ratio.isValid()) {
Num value = ratio.value(); // The calculated ratio (e.g., 0.618)
RatioType type = ratio.type(); // RETRACEMENT, EXTENSION, or NONE
}
Projected price channel with support/resistance boundaries:
ElliottChannel channel = channelIndicator.getValue(index);
if (channel.isValid()) {
Num upper = channel.upper(); // Resistance boundary
Num lower = channel.lower(); // Support boundary
Num mid = channel.median(); // Channel midline
Num width = channel.width(); // Channel width
// Check if price is within channel (with optional tolerance)
boolean inChannel = channel.contains(price, tolerance);
}
The foundation of all Elliott Wave analysis. Detects alternating swing highs and lows:
// Fractal-based detection (fixed window)
ElliottSwingIndicator swings = new ElliottSwingIndicator(series, 5, ElliottDegree.INTERMEDIATE);
// ZigZag-based detection (volatility-adaptive)
ElliottSwingIndicator swings = ElliottSwingIndicator.zigZag(series, ElliottDegree.INTERMEDIATE);
// Get swings up to current bar
List<ElliottSwing> swingList = swings.getValue(index);
Tracks the current wave phase by analyzing swing patterns:
ElliottPhaseIndicator phase = new ElliottPhaseIndicator(swingIndicator);
ElliottPhase currentPhase = phase.getValue(index);
// Check structure confirmation
if (phase.isImpulseConfirmed(index)) {
List<ElliottSwing> impulseSwings = phase.impulseSwings(index);
}
if (phase.isCorrectiveConfirmed(index)) {
List<ElliottSwing> correctiveSwings = phase.correctiveSwings(index);
}
Calculates Fibonacci ratios between consecutive swings:
ElliottRatioIndicator ratios = new ElliottRatioIndicator(swingIndicator);
ElliottRatio ratio = ratios.getValue(index);
// Check proximity to key Fibonacci levels
Num target = series.numFactory().numOf(0.618);
Num tolerance = series.numFactory().numOf(0.02);
if (ratios.isNearLevel(index, target, tolerance)) {
// Near golden ratio retracement
}
Projects price channels based on recent swing structure:
ElliottChannelIndicator channels = new ElliottChannelIndicator(swingIndicator);
ElliottChannel channel = channels.getValue(index);
Aggregates multiple Elliott Wave signals into a confluence score:
ElliottConfluenceIndicator confluence = new ElliottConfluenceIndicator(
priceIndicator, ratioIndicator, channelIndicator);
Num score = confluence.getValue(index); // Score from 0 to 5
// Check if minimum confluence threshold is met
if (confluence.isConfluent(index)) {
// Multiple indicators align
}
ta4j provides several example classes that demonstrate Elliott Wave analysis with real market data:
The main example class that demonstrates comprehensive Elliott Wave analysis with chart visualization. It can be used in two ways:
Command-line usage:
# Load default dataset (ossified BTC-USD data)
java ElliottWaveAnalysis
# Load from external data source (4-6 arguments)
java ElliottWaveAnalysis [dataSource] [ticker] [barDuration] [startEpoch] [endEpoch]
java ElliottWaveAnalysis [dataSource] [ticker] [barDuration] [degree] [startEpoch] [endEpoch]
Arguments:
dataSource: “YahooFinance” or “Coinbase” (case-insensitive)ticker: Symbol (e.g., “BTC-USD”, “AAPL”, “^GSPC”)barDuration: ISO-8601 duration (e.g., “PT1D” for daily, “PT4H” for 4-hour, “PT5M” for 5-minute)degree: Elliott degree (optional; if omitted, auto-selected based on bar duration and count)startEpoch: Start time as Unix epoch secondsendEpoch: End time as Unix epoch seconds (optional, defaults to now)Programmatic usage:
BarSeries series = // ... load your bar series
ElliottWaveAnalysis analysis = new ElliottWaveAnalysis();
analysis.analyze(series, ElliottDegree.PRIMARY, 0.25);
The analyze() method performs complete Elliott Wave analysis including:
Charts are saved to temp/charts/ and displayed if running in a non-headless environment.
For quick analysis of specific assets, use the dedicated example classes:
BTCUSDElliottWaveAnalysis - Bitcoin (BTC-USD) analysis using Coinbase data:
java BTCUSDElliottWaveAnalysis
Loads 365 days of daily Bitcoin data from Coinbase and performs analysis using the PRIMARY degree.
ETHUSDElliottWaveAnalysis - Ethereum (ETH-USD) analysis using Coinbase data:
java ETHUSDElliottWaveAnalysis
Loads daily Ethereum data from Coinbase starting from a specific timestamp.
SP500ElliottWaveAnalysis - S&P 500 Index (^GSPC) analysis using Yahoo Finance data:
java SP500ElliottWaveAnalysis
Loads 365 days of daily S&P 500 index data from Yahoo Finance with auto-selected degree.
These example classes demonstrate simple usage patterns and can be modified to analyze different time periods or use different degrees.
The simplest way to get started is with ElliottWaveFacade, which creates and coordinates all Elliott Wave indicators with a shared swing source:
BarSeries series = // your bar series (minimum ~60 bars recommended)
// Create a complete Elliott Wave analysis facade
ElliottWaveFacade facade = ElliottWaveFacade.fractal(series, 5, ElliottDegree.INTERMEDIATE);
int index = series.getEndIndex();
// Deterministic analysis
ElliottPhase phase = facade.phase().getValue(index);
ElliottRatio ratio = facade.ratio().getValue(index);
ElliottChannel channel = facade.channel().getValue(index);
boolean confluent = facade.confluence().isConfluent(index);
// Wave counting
int waveCount = facade.waveCount().getValue(index); // All swings
int filteredCount = facade.filteredWaveCount().getValue(index); // Filtered (if compressor configured)
// Scenario-based analysis
Optional<ElliottScenario> baseCase = facade.primaryScenario(index);
List<ElliottScenario> alternatives = facade.alternativeScenarios(index);
String summary = facade.scenarioSummary(index); // Human-readable summary
// Price projections and invalidation
Num projection = facade.projection().getValue(index);
Num invalidationPrice = facade.invalidationLevel().getValue(index);
// Consensus and confidence
boolean hasConsensus = facade.hasScenarioConsensus(index);
ElliottPhase consensusPhase = facade.scenarioConsensus(index);
Num confidence = facade.confidenceForPhase(index, ElliottPhase.WAVE3);
ElliottWaveFacade provides several factory methods for different swing detection approaches:
// Fractal-based with symmetric window (5 bars before and after pivot)
ElliottWaveFacade facade = ElliottWaveFacade.fractal(series, 5, ElliottDegree.INTERMEDIATE);
// Fractal-based with asymmetric window
ElliottWaveFacade facade = ElliottWaveFacade.fractal(series, 3, 5, ElliottDegree.INTERMEDIATE);
// ZigZag-based with ATR(14) reversal threshold
ElliottWaveFacade facade = ElliottWaveFacade.zigZag(series, ElliottDegree.INTERMEDIATE);
// From custom swing indicator
ElliottWaveFacade facade = ElliottWaveFacade.from(customSwingIndicator, closePriceIndicator);
All factory methods support optional custom configuration:
// Custom Fibonacci tolerance and swing compressor
Num customTolerance = series.numFactory().numOf(0.25);
ElliottSwingCompressor compressor = new ElliottSwingCompressor(series); // 1% of price, 2 bars minimum
// Fractal with custom configuration
ElliottWaveFacade facade = ElliottWaveFacade.fractal(
series, 5, ElliottDegree.INTERMEDIATE,
Optional.of(customTolerance),
Optional.of(compressor)
);
// ZigZag with custom configuration
ElliottWaveFacade facade = ElliottWaveFacade.zigZag(
series, ElliottDegree.INTERMEDIATE,
Optional.of(customTolerance),
Optional.of(compressor)
);
// From custom swing indicator with configuration
ElliottWaveFacade facade = ElliottWaveFacade.from(
customSwingIndicator,
closePriceIndicator,
Optional.of(customTolerance),
Optional.of(compressor)
);
When a custom Fibonacci tolerance is provided, the phase indicator uses a custom ElliottFibonacciValidator with that tolerance instead of the default (0.05). When a compressor is provided, filteredWaveCount() uses it to filter swings before counting; otherwise, filteredWaveCount() returns the same as waveCount().
The ElliottScenarioIndicator returns an ElliottScenarioSet for each bar, containing all plausible wave interpretations ranked by confidence:
ElliottScenarioIndicator scenarios = facade.scenarios();
ElliottScenarioSet scenarioSet = scenarios.getValue(index);
// Check if scenarios exist
if (scenarioSet.isEmpty()) {
// Not enough data for analysis
return;
}
// Get the base case (highest confidence) scenario
Optional<ElliottScenario> baseCase = scenarioSet.base();
// Get all alternatives (excluding base case)
List<ElliottScenario> alternatives = scenarioSet.alternatives();
// Get all scenarios including base case
List<ElliottScenario> all = scenarioSet.all();
// Get scenario counts
int total = scenarioSet.size();
int highConfidence = scenarioSet.highConfidenceCount(); // >= 0.7 confidence
int lowConfidence = scenarioSet.lowConfidenceCount(); // < 0.3 confidence
You can filter scenarios by phase or pattern type:
ElliottScenarioSet scenarioSet = scenarios.getValue(index);
// Get only impulse scenarios
ElliottScenarioSet impulseScenarios = scenarioSet.byType(ScenarioType.IMPULSE);
// Get scenarios predicting a specific phase
ElliottScenarioSet wave3Scenarios = scenarioSet.byPhase(ElliottPhase.WAVE3);
// Get scenarios that remain valid at a given price
Num testPrice = series.numFactory().numOf(100.0);
ElliottScenarioSet validScenarios = scenarioSet.validAt(testPrice);
// Get scenarios that would be invalidated at a given price
List<ElliottScenario> invalidated = scenarioSet.invalidatedBy(testPrice);
Check whether multiple interpretations agree:
ElliottScenarioSet scenarioSet = scenarios.getValue(index);
// Get consensus phase (agreed upon by all high-confidence scenarios)
ElliottPhase consensus = scenarioSet.consensus();
if (consensus != ElliottPhase.NONE) {
System.out.println("All high-confidence scenarios agree: " + consensus);
}
// Check for strong consensus
if (scenarioSet.hasStrongConsensus()) {
// Either single high-confidence scenario or large confidence spread
System.out.println("High conviction in base case scenario");
}
// Calculate confidence spread between base case and secondary
double spread = scenarioSet.confidenceSpread();
if (spread > 0.3) {
System.out.println("Base case scenario is significantly more confident");
}
Each scenario contains comprehensive information about a wave interpretation:
ElliottScenario scenario = baseCase.get();
// Identity and classification
String id = scenario.id(); // Unique identifier
ElliottPhase phase = scenario.currentPhase(); // Current wave (WAVE3, CORRECTIVE_B, etc.)
ScenarioType type = scenario.type(); // IMPULSE, CORRECTIVE_ZIGZAG, etc.
ElliottDegree degree = scenario.degree(); // Wave degree
// Structure information
List<ElliottSwing> swings = scenario.swings(); // The swings backing this interpretation
int waveCount = scenario.waveCount(); // Number of waves identified
int startIndex = scenario.startIndex(); // Bar where this structure begins
// Confidence metrics
ElliottConfidence confidence = scenario.confidence();
Num score = scenario.confidenceScore(); // Aggregate score (0.0 - 1.0)
boolean highConf = scenario.isHighConfidence(); // >= 0.7
boolean lowConf = scenario.isLowConfidence(); // < 0.3
// Trading information
Num invalidation = scenario.invalidationPrice(); // Price that invalidates this count
Num target = scenario.primaryTarget(); // Primary Fibonacci target
List<Num> targets = scenario.fibonacciTargets(); // All calculated targets
// Direction
boolean hasDirection = scenario.hasKnownDirection(); // Check if direction is known
if (hasDirection) {
boolean bullish = scenario.isBullish(); // Based on first wave direction
boolean bearish = scenario.isBearish();
}
// Check if price would invalidate this scenario
if (scenario.isInvalidatedBy(currentPrice)) {
System.out.println("This wave count is no longer valid");
}
// Check if current phase expects completion soon
if (scenario.expectsCompletion()) {
System.out.println("Wave structure approaching completion");
}
<|tool▁calls▁begin|><|tool▁call▁begin|> read_file
The ScenarioType enum classifies the pattern structure:
| Type | Description | Expected Waves |
|---|---|---|
IMPULSE |
Five-wave motive structure (1-2-3-4-5) | 5 |
CORRECTIVE_ZIGZAG |
Sharp A-B-C where C exceeds A | 3 |
CORRECTIVE_FLAT |
A-B-C where B retraces most of A | 3 |
CORRECTIVE_TRIANGLE |
Five-wave contracting/expanding pattern (A-B-C-D-E) | 5 |
CORRECTIVE_COMPLEX |
Double/triple combinations | Varies |
UNKNOWN |
Pattern could not be determined | 0 |
ScenarioType type = scenario.type();
if (type.isImpulse()) {
// Trading with the trend
} else if (type.isCorrective()) {
// Counter-trend or consolidation
}
int expected = type.expectedWaveCount(); // 5 for impulse, 3 for zigzag/flat
The ElliottConfidenceScorer evaluates each scenario against five weighted factors:
| Factor | Weight | Description |
|---|---|---|
| Fibonacci Proximity | 35% | How closely swing ratios match canonical Fibonacci levels (0.382, 0.618, 1.618, etc.) |
| Time Proportions | 20% | Whether wave durations follow expected relationships (e.g., wave 3 typically longest) |
| Alternation Quality | 15% | Degree of pattern/depth alternation between waves 2 and 4 |
| Channel Adherence | 15% | Whether price stays within projected channel boundaries |
| Structure Completeness | 15% | How many expected waves are confirmed |
Each scenario includes detailed confidence breakdown:
ElliottConfidence confidence = scenario.confidence();
// Overall score (weighted combination)
Num overall = confidence.overall();
double percentage = confidence.asPercentage(); // 0-100
// Individual factor scores (each 0.0 - 1.0)
Num fibScore = confidence.fibonacciScore(); // Fibonacci alignment
Num timeScore = confidence.timeProportionScore(); // Time relationships
Num altScore = confidence.alternationScore(); // Wave 2/4 alternation
Num chanScore = confidence.channelScore(); // Channel adherence
Num compScore = confidence.completenessScore(); // Structure completeness
// Primary reason for the confidence level
String reason = confidence.primaryReason(); // e.g., "Strong Fibonacci conformance"
// Identify the weakest factor
String weakness = confidence.weakestFactor(); // e.g., "Wave alternation"
// Threshold checks
boolean valid = confidence.isValid(); // Non-null and not NaN
boolean high = confidence.isHighConfidence(); // >= 0.7
boolean low = confidence.isLowConfidence(); // < 0.3
boolean meetsThreshold = confidence.isAboveThreshold(0.5); // Custom threshold
| Confidence | Interpretation | Recommended Action |
|---|---|---|
| 0.7 - 1.0 | High confidence | Trade with conviction; use tighter stops |
| 0.5 - 0.7 | Moderate confidence | Wait for confirmation or reduce position size |
| 0.3 - 0.5 | Low confidence | Consider alternative scenarios; wide stops |
| 0.0 - 0.3 | Very low confidence | Avoid trading; wave structure is unclear |
You can create a scorer with custom weights to emphasize factors important to your strategy:
// Create custom scorer with different emphasis
ElliottConfidenceScorer customScorer = new ElliottConfidenceScorer(
series.numFactory(),
0.50, // fibonacciWeight - emphasize Fibonacci alignment
0.15, // timeWeight
0.10, // alternationWeight
0.15, // channelWeight
0.10 // completenessWeight
);
// Use custom scorer with generator
ElliottScenarioGenerator generator = new ElliottScenarioGenerator(
series.numFactory(),
0.15, // minConfidence threshold
5 // maxScenarios to return
);
In practice, you’ll often encounter situations where price action supports multiple valid interpretations. For example:
The scenario-based system helps you:
ElliottWaveFacade facade = ElliottWaveFacade.fractal(series, 5, ElliottDegree.INTERMEDIATE);
int index = series.getEndIndex();
ElliottScenarioSet scenarioSet = facade.scenarios().getValue(index);
System.out.println(scenarioSet.summary());
// Example output: "3 scenario(s): Base case=WAVE3 (72.5%), 2 alternative(s), consensus=WAVE3"
Optional<ElliottScenario> baseCase = scenarioSet.base();
if (baseCase.isPresent()) {
ElliottScenario bc = baseCase.get();
System.out.println("Base case interpretation:");
System.out.println(" Phase: " + bc.currentPhase());
System.out.println(" Type: " + bc.type());
System.out.println(" Confidence: " + String.format("%.1f%%", bc.confidence().asPercentage()));
System.out.println(" Invalidation: " + bc.invalidationPrice());
System.out.println(" Primary target: " + bc.primaryTarget());
}
// Consider alternatives
List<ElliottScenario> alternatives = scenarioSet.alternatives();
if (!alternatives.isEmpty()) {
System.out.println("\nAlternative interpretations:");
for (ElliottScenario alt : alternatives) {
System.out.println(" " + alt.type() + " " + alt.currentPhase() +
" (" + String.format("%.1f%%", alt.confidence().asPercentage()) + ")");
}
}
// Check consensus
if (scenarioSet.hasStrongConsensus()) {
System.out.println("\nStrong consensus - high conviction trade opportunity");
} else if (scenarioSet.consensus() != ElliottPhase.NONE) {
System.out.println("\nModerate consensus on phase: " + scenarioSet.consensus());
} else {
System.out.println("\nNo consensus - market structure is ambiguous");
}
Track how scenarios evolve as new bars arrive:
// Track the evolution of scenarios
ElliottScenarioIndicator scenarios = facade.scenarios();
for (int i = 0; i < series.getBarCount(); i++) {
ElliottScenarioSet set = scenarios.getValue(i);
Optional<ElliottScenario> baseCase = set.base();
if (baseCase.isPresent()) {
ElliottScenario bc = baseCase.get();
System.out.println(String.format("Bar %d: %s (%.1f%%) - %d alternatives",
i,
bc.currentPhase(),
bc.confidence().asPercentage(),
set.alternatives().size()));
}
}
The projection indicator calculates Fibonacci-based price targets for the base case scenario:
ElliottProjectionIndicator projection = facade.projection();
// Get base case target for current bar
Num baseCaseTarget = projection.getValue(index);
if (Num.isValid(baseCaseTarget)) {
System.out.println("Base case target: " + baseCaseTarget);
}
// Get all Fibonacci targets for the base case scenario
List<Num> allTargets = projection.allTargets(index);
for (Num target : allTargets) {
System.out.println(" Target: " + target);
}
// Calculate targets for a specific swing sequence and phase
ElliottScenario scenario = scenarioSet.base().get();
List<ElliottSwing> swings = scenario.swings();
ElliottPhase phase = scenario.currentPhase();
List<Num> customTargets = projection.calculateTargets(swings, phase);
Targets are calculated based on the current wave phase:
Impulse Wave Projections:
| Current Phase | Target Wave | Calculation |
|---|---|---|
| WAVE2 | Wave 3 | Wave 2 end + (Wave 1 amplitude × 1.0/1.618/2.618) |
| WAVE4 | Wave 5 | Wave 4 end + (Wave 1 amplitude × 0.618/1.0/1.618) |
Corrective Wave Projections:
| Current Phase | Target Wave | Calculation |
|---|---|---|
| CORRECTIVE_B | Wave C | Wave B end ± (Wave A amplitude × 0.618/1.0/1.618) |
You can also calculate projections for any swing sequence and phase:
ElliottProjectionIndicator projection = facade.projection();
// Get swings from a specific scenario
ElliottScenario scenario = scenarioSet.base().get();
List<ElliottSwing> swings = scenario.swings();
ElliottPhase phase = scenario.currentPhase();
// Calculate targets for this specific structure
List<Num> targets = projection.calculateTargets(swings, phase);
Every Elliott Wave count has specific price levels that would invalidate it. Knowing these levels is crucial for:
ElliottInvalidationLevelIndicator invalidation = facade.invalidationLevel();
// Get invalidation price for base case scenario
Num invalidationPrice = invalidation.getValue(index);
if (Num.isValid(invalidationPrice)) {
System.out.println("Base case invalidation at: " + invalidationPrice);
}
The indicator supports three modes for different risk tolerances:
import org.ta4j.core.indicators.elliott.ElliottInvalidationLevelIndicator.InvalidationMode;
// PRIMARY mode (default): Use base case scenario's invalidation
ElliottInvalidationLevelIndicator primary =
new ElliottInvalidationLevelIndicator(scenarioIndicator, InvalidationMode.PRIMARY);
// CONSERVATIVE mode: Use tightest invalidation across high-confidence scenarios
// (First scenario invalidated = consider exiting)
ElliottInvalidationLevelIndicator conservative =
new ElliottInvalidationLevelIndicator(scenarioIndicator, InvalidationMode.CONSERVATIVE);
// AGGRESSIVE mode: Use widest invalidation across all scenarios
// (All scenarios must be invalidated before exiting)
ElliottInvalidationLevelIndicator aggressive =
new ElliottInvalidationLevelIndicator(scenarioIndicator, InvalidationMode.AGGRESSIVE);
| Mode | Description | Use Case |
|---|---|---|
PRIMARY |
Invalidation level from the highest-confidence scenario | Standard trading with single scenario focus |
CONSERVATIVE |
Tightest stop across high-confidence scenarios | Protective trading when multiple valid counts exist |
AGGRESSIVE |
Widest stop (all scenarios must fail) | Position trades where you want maximum room |
ElliottInvalidationLevelIndicator invalidation = facade.invalidationLevel();
Num currentPrice = series.getBar(index).getClosePrice();
// Check if current price invalidates the base case scenario
boolean isInvalid = invalidation.isInvalidated(index, currentPrice);
// Get distance from current price to invalidation
Num distance = invalidation.distanceToInvalidation(index, currentPrice);
// Positive = still valid, Negative = invalidated
For simple true/false invalidation checking without price levels:
ElliottInvalidationIndicator boolInvalidation = facade.invalidation();
if (boolInvalidation.getValue(index)) {
System.out.println("Current wave structure is invalidated");
// Reassess wave count
}
The ElliottInvalidationIndicator checks if the current wave structure violates Elliott Wave rules (e.g., Wave 2 retracing beyond Wave 1 start, Wave 4 overlapping with Wave 1, etc.).
Analyze multiple timeframes simultaneously:
// Primary trend (larger degree)
ElliottWaveFacade primaryFacade =
ElliottWaveFacade.fractal(series, 10, ElliottDegree.PRIMARY);
// Intermediate trend (smaller degree)
ElliottWaveFacade intermediateFacade =
ElliottWaveFacade.fractal(series, 3, ElliottDegree.INTERMEDIATE);
int index = series.getEndIndex();
// Get interpretations at both degrees
Optional<ElliottScenario> primaryBaseCase = primaryFacade.primaryScenario(index);
Optional<ElliottScenario> intermediateBaseCase = intermediateFacade.primaryScenario(index);
// Trade when degrees align
if (primaryBaseCase.isPresent() && intermediateBaseCase.isPresent()) {
ElliottScenario primaryBase = primaryBaseCase.get();
ElliottScenario intermediateBase = intermediateBaseCase.get();
if (primaryBase.isBullish() && intermediateBase.currentPhase() == ElliottPhase.WAVE3) {
// Strong bullish alignment: wave 3 at intermediate degree within bullish base case
System.out.println("Aligned bullish setup");
}
}
Use custom price sources or swing detectors:
// Use close prices instead of high/low
ClosePriceIndicator close = new ClosePriceIndicator(series);
ElliottSwingIndicator swings = new ElliottSwingIndicator(close, 3, 3, ElliottDegree.INTERMEDIATE);
// Create facade from custom swing indicator
ElliottWaveFacade facade = ElliottWaveFacade.from(swings, close);
// Or with custom configuration
Num fibTolerance = series.numFactory().numOf(0.25);
ElliottSwingCompressor compressor = new ElliottSwingCompressor(series);
ElliottWaveFacade facade = ElliottWaveFacade.from(
swings,
close,
Optional.of(fibTolerance),
Optional.of(compressor)
);
Filter out small swings before analysis using ElliottSwingCompressor:
ElliottSwingIndicator rawSwings = new ElliottSwingIndicator(series, 3, ElliottDegree.MINOR);
// Option 1: No filtering (all swings retained)
ElliottSwingCompressor noFilter = new ElliottSwingCompressor();
// Option 2: Absolute thresholds (explicit price and bar length)
Num minimumAmplitude = series.numFactory().numOf(5.0); // Minimum price move
int minimumLength = 2; // Minimum bars between pivots
ElliottSwingCompressor compressor = new ElliottSwingCompressor(minimumAmplitude, minimumLength);
// Option 3: Relative price-based (percentage of current price)
ClosePriceIndicator closePrice = new ClosePriceIndicator(series);
ElliottSwingCompressor relativeCompressor = new ElliottSwingCompressor(
closePrice,
0.01, // 1% of current price
2 // Minimum 2 bars
);
// Option 4: Convenience constructor (1% of current price, 2 bars minimum)
ElliottSwingCompressor defaultCompressor = new ElliottSwingCompressor(series);
// Use compressor with wave count indicator
ElliottWaveCountIndicator counter = new ElliottWaveCountIndicator(rawSwings, compressor);
// Get filtered swings
List<ElliottSwing> compressed = compressor.compress(rawSwings.getValue(index));
The compressor filters swings that don’t meet both the minimum amplitude and minimum bar length thresholds. This is useful for removing noise and focusing on significant price movements.
Navigate between wave degrees programmatically:
ElliottDegree degree = ElliottDegree.INTERMEDIATE;
ElliottDegree higher = degree.higherDegree(); // Returns PRIMARY
ElliottDegree lower = degree.lowerDegree(); // Returns MINOR
// Check degree relationships
if (degree.isHigherOrEqual(ElliottDegree.MINOR)) {
// This degree is significant enough for swing trading
}
// Iterate through degrees
for (ElliottDegree d : ElliottDegree.values()) {
System.out.println(d.name());
}
The ElliottDegree class provides recommendations based on bar duration and history length:
Duration barDuration = Duration.ofDays(1); // Daily bars
int barCount = 365; // One year of data
// Get recommended degrees (ordered by best fit)
List<ElliottDegree> recommendations = ElliottDegree.getRecommendedDegrees(barDuration, barCount);
// Use the first recommendation (best fit)
ElliottDegree selected = recommendations.get(0);
// Or iterate through all recommendations
for (ElliottDegree degree : recommendations) {
System.out.println("Recommended: " + degree);
}
The recommendation system considers:
Typical ranges (rule of thumb):
The ElliottWaveAnalysis class automatically uses degree recommendations when no explicit degree is provided via command-line arguments.
Fine-tune how scenarios are generated:
// Create generator with custom thresholds
ElliottScenarioGenerator generator = new ElliottScenarioGenerator(
series.numFactory(),
0.10, // minConfidence: lower threshold keeps more scenarios
10 // maxScenarios: maximum to return
);
// Create scenario indicator with custom generator
ElliottScenarioIndicator scenarios = new ElliottScenarioIndicator(
swingIndicator, channelIndicator, generator);
Track scenario evolution for a trading journal:
StringBuilder journal = new StringBuilder();
for (int i = 50; i < series.getBarCount(); i++) {
ElliottScenarioSet set = facade.scenarios().getValue(i);
journal.append(String.format("Bar %d: %s%n", i, set.summary()));
// Track scenario changes
if (i > 50) {
ElliottScenarioSet previous = facade.scenarios().getValue(i - 1);
Optional<ElliottScenario> prevBaseCase = previous.base();
Optional<ElliottScenario> currBaseCase = set.base();
if (prevBaseCase.isPresent() && currBaseCase.isPresent()) {
if (prevBaseCase.get().currentPhase() != currBaseCase.get().currentPhase()) {
journal.append(" ** Phase change detected **\n");
}
}
}
}
Elliott Wave analysis works well with momentum and volume indicators:
ElliottWaveFacade facade = ElliottWaveFacade.fractal(series, 5, ElliottDegree.INTERMEDIATE);
RSIIndicator rsi = new RSIIndicator(new ClosePriceIndicator(series), 14);
int index = series.getEndIndex();
Optional<ElliottScenario> baseCase = facade.primaryScenario(index);
Num rsiValue = rsi.getValue(index);
if (baseCase.isPresent()) {
ElliottScenario scenario = baseCase.get();
// Wave 2 + oversold RSI = potential wave 3 entry
if (scenario.currentPhase() == ElliottPhase.WAVE2
&& rsiValue.isLessThan(series.numFactory().numOf(30))) {
System.out.println("Wave 3 entry setup with RSI confirmation");
}
// Wave 5 + overbought RSI + bearish divergence = potential exit
if (scenario.currentPhase() == ElliottPhase.WAVE5
&& rsiValue.isGreaterThan(series.numFactory().numOf(70))) {
System.out.println("Potential wave 5 exhaustion");
}
}
All Elliott Wave indicators extend CachedIndicator, so values are cached after first calculation:
// First call calculates and caches
ElliottScenarioSet set1 = scenarios.getValue(index);
// Second call returns cached value (no recalculation)
ElliottScenarioSet set2 = scenarios.getValue(index);
// Get unstable bar count (values before this index may not be reliable)
int unstable = scenarios.getCountOfUnstableBars();
Always check for valid data before using values:
// Check Num values
Num price = indicator.getValue(index);
if (Num.isValid(price)) {
// Safe to use
}
// Check Optional scenarios
Optional<ElliottScenario> baseCase = facade.primaryScenario(index);
if (baseCase.isPresent() && baseCase.get().isHighConfidence()) {
// Safe to trade
}
// Check confidence validity
ElliottConfidence conf = scenario.confidence();
if (conf.isValid()) {
// Safe to use confidence values
}
// Check scenario set
ElliottScenarioSet set = scenarios.getValue(index);
if (!set.isEmpty()) {
// Safe to access scenarios
}
ElliottWaveFacade facade = ElliottWaveFacade.fractal(series, 5, ElliottDegree.INTERMEDIATE);
// Enter long when high-confidence wave 3 begins
Rule entryRule = new Rule() {
@Override
public boolean isSatisfied(int index, TradingRecord tradingRecord) {
Optional<ElliottScenario> baseCase = facade.primaryScenario(index);
return baseCase.isPresent()
&& baseCase.get().currentPhase() == ElliottPhase.WAVE3
&& baseCase.get().isHighConfidence()
&& facade.hasScenarioConsensus(index);
}
};
// Exit at wave 5 completion or invalidation
Rule exitRule = new Rule() {
@Override
public boolean isSatisfied(int index, TradingRecord tradingRecord) {
Optional<ElliottScenario> baseCase = facade.primaryScenario(index);
if (baseCase.isEmpty()) return true; // Exit if no scenarios
ElliottScenario scenario = baseCase.get();
Num currentPrice = series.getBar(index).getClosePrice();
// Exit conditions
boolean wave5Complete = scenario.currentPhase() == ElliottPhase.WAVE5
&& scenario.expectsCompletion();
boolean invalidated = scenario.isInvalidatedBy(currentPrice);
return wave5Complete || invalidated;
}
};
Note: The expectsCompletion() method on ElliottScenario indicates whether the current phase is expected to complete soon (e.g., Wave 5 or Corrective C).
Strategy strategy = new BaseStrategy(entryRule, exitRule);
### Confidence-Based Position Sizing
```java
public double calculatePositionSize(ElliottScenario scenario, double baseSize) {
ElliottConfidence conf = scenario.confidence();
if (conf.isHighConfidence()) {
return baseSize * 1.0; // Full size
} else if (conf.isAboveThreshold(0.5)) {
return baseSize * 0.5; // Half size
} else {
return baseSize * 0.25; // Quarter size or skip
}
}
public Num calculateStopLevel(ElliottScenarioSet scenarioSet, boolean conservative) {
InvalidationMode mode = conservative
? InvalidationMode.CONSERVATIVE
: InvalidationMode.PRIMARY;
ElliottInvalidationLevelIndicator invalidation =
new ElliottInvalidationLevelIndicator(scenarioIndicator, mode);
return invalidation.getValue(scenarioSet.barIndex());
}
// Trade when primary and alternatives agree on direction
public boolean shouldEnterLong(int index) {
ElliottScenarioSet set = facade.scenarios().getValue(index);
// Require at least 2 scenarios
if (set.size() < 2) return false;
// Check if base case is bullish
Optional<ElliottScenario> baseCase = set.base();
if (baseCase.isEmpty() || !baseCase.get().isBullish()) return false;
// Check if majority of alternatives also bullish
List<ElliottScenario> alternatives = set.alternatives();
long bullishAlternatives = alternatives.stream()
.filter(ElliottScenario::isBullish)
.count();
double bullishRatio = (double)(bullishAlternatives + 1) / set.size();
return bullishRatio >= 0.6; // 60% of scenarios are bullish
}
ta4j’s Elliott Wave indicators provide a sophisticated toolkit for wave-based technical analysis that acknowledges the inherent uncertainty in wave counting:
// Create facade
ElliottWaveFacade facade = ElliottWaveFacade.fractal(series, 5, ElliottDegree.INTERMEDIATE);
int index = series.getEndIndex();
// Get base case interpretation with confidence
Optional<ElliottScenario> baseCase = facade.primaryScenario(index);
if (baseCase.isPresent()) {
ElliottScenario bc = baseCase.get();
System.out.println("Phase: " + p.currentPhase());
System.out.println("Confidence: " + String.format("%.1f%%", p.confidence().asPercentage()));
System.out.println("Invalidation: " + p.invalidationPrice());
System.out.println("Target: " + p.primaryTarget());
}
// Check for consensus
if (facade.hasScenarioConsensus(index)) {
System.out.println("Strong consensus: " + facade.scenarioConsensus(index));
}
// Get alternatives
List<ElliottScenario> alts = facade.alternativeScenarios(index);
System.out.println("Alternative counts: " + alts.size());
For the fastest way to see Elliott Wave analysis in action, use the provided example classes:
# Analyze Bitcoin with default settings
java BTCUSDElliottWaveAnalysis
# Analyze Ethereum
java ETHUSDElliottWaveAnalysis
# Analyze S&P 500
java SP500ElliottWaveAnalysis
# Custom analysis with command-line arguments
java ElliottWaveAnalysis Coinbase BTC-USD PT1D PRIMARY 1686960000 1697040000
All examples generate charts with wave pivot labels, scenario analysis, and confidence scores. Charts are saved to temp/charts/ and displayed if running in a non-headless environment.
The modular design allows you to build custom workflows that match your trading style, whether you prefer a single deterministic count or a full scenario-based approach with confidence weighting.