Elliott Wave Indicators
A comprehensive guide to using ta4j’s Elliott Wave analysis tools, from basic concepts to advanced trading strategies.
For a concise entry path, use Elliott Wave Quickstart. For a shareable daily macro-cycle report, see Run a live five-outlook snapshot. This page is the deep reference and advanced guide.
Table of Contents
- Introduction to Elliott Wave Theory
- Overview of ta4j’s Elliott Wave Implementation
- Core Components
- Getting Started
- Scenario-Based Analysis
- ElliottWaveAnalysisRunner and One-Shot Analysis
- ElliottTrendBias and ElliottTrendBiasIndicator
- Pluggable Swing Detection
- Confidence Scoring
- Working with Alternative Wave Counts
- Price Projections and Targets
- Invalidation Levels
- Intermediate Usage Patterns
- Advanced Topics
- Best Practices
- Integration with Trading Strategies
Introduction to Elliott Wave Theory
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.
Basic Wave Structure
Elliott Wave patterns consist of two main types of waves:
- Impulsive Waves (1-2-3-4-5): These waves move in the direction of the main trend. In a bull market, waves 1, 3, and 5 move upward, while waves 2 and 4 are corrective downward movements.
- Corrective Waves (A-B-C): These waves move against the main trend, representing temporary reversals or consolidations.
Key Principles
- Wave Degrees: Elliott waves exist at multiple timeframes simultaneously, from grand supercycle (decades) down to sub-minuette (minutes). Each degree is labeled using a specific notation system.
- Fibonacci Relationships: Wave amplitudes often relate to each other through Fibonacci ratios (e.g., 0.382, 0.618, 1.618), providing targets for retracements and extensions.
- Wave Rules: Specific rules govern wave formation:
- Wave 2 cannot retrace more than 100% of Wave 1
- Wave 3 cannot be the shortest of the three impulse waves (1, 3, 5)
- Wave 4 cannot overlap with Wave 1 (except in diagonal triangles)
- Wave 5 must exceed Wave 3’s extreme (in standard impulses)
The Problem of Multiple Wave Counts
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:
- Scenario-based analysis: Generating multiple plausible wave counts simultaneously
- Confidence scoring: Quantifying how well each interpretation aligns with Elliott Wave principles
- Consensus detection: Identifying when multiple interpretations agree on market direction
- Invalidation tracking: Providing specific price levels that would disprove each count
Overview of ta4j’s Elliott Wave Implementation
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.
Design Philosophy
The Elliott Wave indicators in ta4j are built on several key principles:
- Swing-Based Foundation: All Elliott Wave analysis starts with swing detection—identifying alternating pivot highs and lows in price action.
- Modular Architecture: Components are designed to work independently or together, allowing you to build custom analysis workflows.
- Scenario-Aware: Rather than outputting a single “correct” wave count, the system generates multiple ranked alternatives with confidence scores.
- Validation-Driven: Wave identification includes validation against Elliott Wave rules and Fibonacci relationships.
- Degree-Aware: The system supports multiple wave degrees, allowing analysis across different timeframes.
- Resilient to Edge Cases: Indicators gracefully handle insufficient data, returning
NaNvalues rather than throwing exceptions.
Internal Implementation Boundary
Some Elliott package classes are internal implementation details (for example, ElliottScenarioRuleSupport and generator/scoring helpers used to build ranked alternatives).
- Public strategy rules live in
org.ta4j.core.rules.elliottand are documented below. - Package-private abstract indicator bases are omitted from Indicators Inventory.
Component Hierarchy
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) │
│ • ElliottTrendBiasIndicator (aggregate directional bias)│
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Pluggable Analysis Pipeline (0.22.4+) │
├─────────────────────────────────────────────────────────┤
│ • ElliottWaveAnalysisRunner (one-shot analysis) │
│ • SwingDetector (pluggable swing detection) │
│ • Fractal / ZigZag; SlopeChange / multi-scale (0.23.0+) │
│ • ConfidenceModel / ConfidenceProfile (pluggable weights)│
└─────────────────────────────────────────────────────────┘
When to Use Each Layer
Use the Deterministic Analysis Layer when:
- You need a single, definitive wave count for automated trading rules
- Simplicity is preferred over nuance
- You’re building indicators that feed into other calculations
Use the Scenario-Based Analysis Layer when:
- You want to understand the range of plausible interpretations
- You need confidence metrics for risk management
- You want to track multiple wave counts simultaneously
- You need explicit invalidation price levels for stop placement
Change tracking relevant to this guide
- Commit
bebb758e(2026-02-12, #1435) is the baseline for the current confidence/trend-bias model used here (ElliottTrendBias,ElliottTrendBiasIndicator,ElliottConfidenceScorer, andelliott.confidencefactors). - Commit
279d9056(2026-03-05, #1448) expanded the Elliott workflow with automatic multi-timeframe analysis (for exampleElliottWaveAnalysisRunner,ElliottWaveAnalysisResult, and updatedElliottWaveFacadeintegration). - Confidence scoring uses soft penalties outside preferred extension ranges (instead of hard pass/fail), so mildly overextended wave-3/wave-5 structures are down-weighted rather than discarded.
- For release-by-release history, see Release Notes.
Core Components
Data Model Records
ElliottSwing
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
ElliottSwingMetadata
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.
ElliottPhase
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
ElliottRatio
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
}
ElliottChannel
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);
}
Core Indicators
ElliottSwingIndicator
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);
ElliottPhaseIndicator
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);
}
ElliottRatioIndicator
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
}
ElliottChannelIndicator
Projects price channels based on recent swing structure:
ElliottChannelIndicator channels = new ElliottChannelIndicator(swingIndicator);
ElliottChannel channel = channels.getValue(index);
ElliottConfluenceIndicator
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
}
Getting Started
Quickstart: Facade vs Runner
This quickstart shows the minimal entry points into the Elliott Wave suite. Start with the facade for indicator-style access, or the runner for one-shot pipeline analysis.
Option 1: Indicator-style access (recommended entry point)
Use ElliottWaveFacade when you want per-bar indicator values that plug into rules or charts.
BarSeries series = ...;
int index = series.getEndIndex();
ElliottWaveFacade facade = ElliottWaveFacade.fractal(series, 5, ElliottDegree.INTERMEDIATE);
ElliottPhase phase = facade.phase().getValue(index);
ElliottRatio ratio = facade.ratio().getValue(index);
ElliottScenarioSet scenarios = facade.scenarios().getValue(index);
Num invalidation = facade.invalidationLevel().getValue(index);
Option 2: One-shot analysis pipeline
Use ElliottWaveAnalysisRunner when you want a single analysis result (for reporting, charting, or batch processing) and you need to plug in custom swing detectors, filters, confidence profiles, or supporting degrees.
BarSeries series = ...;
ElliottWaveAnalysisRunner runner = ElliottWaveAnalysisRunner.builder()
.swingDetector(SwingDetectors.fractal(5))
.degree(ElliottDegree.INTERMEDIATE)
.build();
ElliottWaveAnalysisResult result = runner.analyze(series);
ElliottWaveAnalysisResult.BaseScenarioAssessment best =
result.recommendedScenario().orElseThrow();
ElliottScenario scenario = best.scenario();
ElliottTrendBias trendBias = result.analysisFor(ElliottDegree.INTERMEDIATE)
.map(ElliottWaveAnalysisResult.DegreeAnalysis::analysis)
.map(ElliottAnalysisResult::trendBias)
.orElse(ElliottTrendBias.unknown());
Next steps:
- Use
ElliottScenarioIndicatorfor multiple interpretations per bar. - Use
ElliottProjectionIndicatorfor targets andElliottInvalidationLevelIndicatorfor invalidation prices. - Customize confidence scoring with
ConfidenceProfilesandScenarioTypeConfidenceModel. - Explore detectors under
org.ta4j.core.analysis.elliott.swingfor fractal, ZigZag, or adaptive ZigZag swing detection.
Example Classes
ta4j-examples currently groups Elliott Wave examples under ta4jexamples.analysis.elliottwave:
ElliottWaveIndicatorSuiteDemo
The main suite demo runs swing detection, phase identification, Fibonacci ratio validation, channels, confluence, invalidation checks, scenario scoring, chart labels, and optional chart output.
Command-line usage:
# Load the default ossified BTC-USD dataset
java ta4jexamples.analysis.elliottwave.ElliottWaveIndicatorSuiteDemo
# Load from an external data source
java ta4jexamples.analysis.elliottwave.ElliottWaveIndicatorSuiteDemo [dataSource] [ticker] [barDuration] [startEpoch] [endEpoch]
java ta4jexamples.analysis.elliottwave.ElliottWaveIndicatorSuiteDemo [dataSource] [ticker] [barDuration] [degree] [startEpoch] [endEpoch]
Arguments:
dataSource:YahooFinanceorCoinbaseticker: symbol such asBTC-USD,AAPL, or^GSPCbarDuration: ISO-8601 duration such asPT1D,PT4H, orPT5Mdegree: optional Elliott degree; if omitted, the suite auto-selects from bar duration and history lengthstartEpoch/endEpoch: Unix epoch seconds;endEpochis optional
The suite can also be called programmatically with analyze(BarSeries, ElliottDegree, double) and returns structured report data through ElliottWaveAnalysisReport.
Preset and focused demos
- ElliottWavePresetDemo - Consolidated launcher for
ossified <btc|eth|sp500>datasets orlive <Coinbase|YahooFinance> <ticker> [barDuration] [lookbackDays] [degree]. - ElliottWaveAdaptiveSwingAnalysis - Composite fractal/adaptive-ZigZag swing detection with confidence-breakdown logging.
- ElliottWaveMultiDegreeAnalysisDemo - Multi-degree analysis with cross-degree scenario matching and a recommended base scenario.
- ElliottWavePatternProfileDemo - Compares default and pattern-aware confidence profiles.
- ElliottWaveTrendBacktest - Backtests and walk-forward tests Elliott Wave trend-bias predictions across ossified datasets.
- HighRewardElliottWaveBacktest - Runs the
HighRewardElliottWaveStrategythrough several parameter profiles. - HighRewardElliottWaveStrategy - Named strategy that trades high-confidence impulse scenarios with risk/reward, trend, and momentum gates. See Usage Examples for links.
Older thin wrappers such as BTCUSDElliottWaveAnalysis, ETHUSDElliottWaveAnalysis, and SP500ElliottWaveAnalysis have been replaced by ElliottWavePresetDemo.
Using ElliottWaveFacade (Recommended)
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);
Factory Methods
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, both phase() and scenarios() share the same ElliottFibonacciValidator instance (default tolerance is 0.05). This keeps phase validation and scenario ranking aligned on the same Fibonacci bands. You can also construct ElliottFibonacciValidator directly when building custom generators or tests:
Num tolerance = series.numFactory().numOf(0.25);
ElliottFibonacciValidator validator = new ElliottFibonacciValidator(series.numFactory(), tolerance);
When a compressor is provided, filteredWaveCount() uses it to filter swings before counting; otherwise, filteredWaveCount() returns the same as waveCount().
Scenario-Based Analysis
Understanding ElliottScenarioSet
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
Filtering Scenarios
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);
Consensus Detection
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");
}
The ElliottScenario Record
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");
}
Scenario Types
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
ElliottWaveAnalysisRunner and One-Shot Analysis
The ElliottWaveAnalysisRunner (0.22.4+) orchestrates a full Elliott Wave analysis run with pluggable swing detectors, confidence profiles, and optional higher/lower-degree validation. It returns an ElliottWaveAnalysisResult containing per-degree ElliottAnalysisResult snapshots plus ranked base-scenario assessments—ideal for one-shot analysis, reports, or custom pipelines.
Using the runner
import org.ta4j.core.indicators.elliott.ElliottAnalysisResult;
import org.ta4j.core.indicators.elliott.ElliottDegree;
import org.ta4j.core.indicators.elliott.ElliottLogicProfile;
import org.ta4j.core.indicators.elliott.ElliottScenario;
import org.ta4j.core.analysis.elliott.swing.AdaptiveZigZagConfig;
import org.ta4j.core.analysis.elliott.swing.SwingDetector;
import org.ta4j.core.analysis.elliott.swing.SwingDetectors;
import org.ta4j.core.indicators.elliott.ElliottWaveAnalysisResult;
import org.ta4j.core.indicators.elliott.ElliottWaveAnalysisRunner;
// Build runner with fractal swing detection and supporting degrees
SwingDetector detector = SwingDetectors.fractal(5);
ElliottWaveAnalysisRunner runner = ElliottWaveAnalysisRunner.builder()
.swingDetector(detector)
.degree(ElliottDegree.INTERMEDIATE)
.logicProfile(ElliottLogicProfile.ORTHODOX_CLASSICAL) // optional 0.22.7 preset
.higherDegrees(1)
.lowerDegrees(1)
.minConfidence(0.15)
.maxScenarios(5)
.build();
ElliottWaveAnalysisResult result = runner.analyze(series);
ElliottWaveAnalysisResult.BaseScenarioAssessment recommended =
result.recommendedScenario().orElseThrow();
ElliottScenario scenario = recommended.scenario();
ElliottAnalysisResult base = result.analysisFor(ElliottDegree.INTERMEDIATE)
.map(ElliottWaveAnalysisResult.DegreeAnalysis::analysis)
.orElseThrow();
int index = base.index();
ElliottScenarioSet scenarios = base.scenarios();
ElliottTrendBias trendBias = base.trendBias();
ElliottChannel channel = base.channel();
// Per-scenario confidence breakdown
base.breakdownFor(scenario).ifPresent(System.out::println);
Live intraday analysis (1-minute and 5-minute bars)
Version: The APIs in this section are pending for ta4j 0.23.1. They are not available in the published 0.23.0 or 0.22.x artifacts.
Use INTRADAY_LIVE for low-duration live series. It selects causal,
ATR-scaled ZigZag detection and bypasses the macro-oriented
percentage-of-history and percentage-of-price post-filters that can remove
ordinary minute-bar swings.
ElliottWaveAnalysisRunner liveRunner = ElliottWaveAnalysisRunner.builder()
.degree(ElliottDegree.SUB_MINUETTE)
.logicProfile(ElliottLogicProfile.INTRADAY_LIVE)
.build();
ElliottAnalysisResult live = liveRunner.analyze(series)
.analysisFor(ElliottDegree.SUB_MINUETTE)
.orElseThrow()
.analysis();
int confirmedWaves = live.waveCount().confirmed();
int wavesIncludingForming = live.waveCount().includingProvisional();
boolean baseCaseUsesFormingWave = live.scenarios()
.base()
.map(live::usesProvisionalTerminal)
.orElse(false);
The compatible default includes one forming terminal swing when current price extends beyond the last confirmed detector pivot. This is useful for live charting and provisional counts. For trading rules that must evaluate confirmed pivots only, disable that projection before building the runner:
ElliottWaveAnalysisRunner tradingRunner = ElliottWaveAnalysisRunner.builder()
.degree(ElliottDegree.SUB_MINUETTE)
.logicProfile(ElliottLogicProfile.INTRADAY_LIVE)
.includeProvisionalTerminalSwing(false)
.build();
Custom ATR sensitivity remains composable with the live profile. The built-in profile uses ATR period 14, multiplier 1.0, no absolute clamps, and smoothing period 3. Override the detector when the instrument needs different sensitivity:
AdaptiveZigZagConfig config = new AdaptiveZigZagConfig(10, 1.25, 0.0, 0.0, 2);
ElliottWaveAnalysisRunner tuned = ElliottWaveAnalysisRunner.builder()
.degree(ElliottDegree.SUB_MINUETTE)
.logicProfile(ElliottLogicProfile.INTRADAY_LIVE)
.swingDetector(SwingDetectors.adaptiveZigZag(config))
.includeProvisionalTerminalSwing(false)
.build();
includeProvisionalTerminalSwing(...) applies to the built-in analysis
pipeline. A custom analysisRunner(...) owns its complete terminal-wave
semantics.
ElliottWaveAnalysisResult and ElliottAnalysisResult
ElliottWaveAnalysisResult is the top-level one-shot output. It includes the base degree, one DegreeAnalysis per analyzed degree, ranked base-scenario assessments, and diagnostic notes. Use recommendedScenario() for the highest composite-score scenario and analysisFor(ElliottDegree) to retrieve the per-degree snapshot.
Each per-degree ElliottAnalysisResult includes:
| Field | Description |
|---|---|
degree |
Elliott degree used for analysis |
index |
Bar index evaluated |
rawSwings |
Swings from the detector (before filter/compressor) |
processedSwings |
Windowed swings used for scenario generation, including the optional terminal projection |
scenarios |
Ranked scenario set (base case + alternatives) |
confidenceBreakdowns |
Per-scenario confidence breakdown (keyed by scenario id) |
channel |
Projected Elliott channel at this index |
trendBias |
Aggregate directional bias across scenarios |
waveCount |
Confirmed and provisional counts before scenario-window clipping |
Use breakdownFor(ElliottScenario) to get the confidence breakdown for a
specific scenario, provisionalTerminalSwing() to inspect the forming tail, and
usesProvisionalTerminal(ElliottScenario) before acting on a scenario that may
depend on that tail.
ElliottLogicProfile presets (0.22.7)
ElliottLogicProfile packages reusable runner defaults (hierarchical swing sensitivity, enabled scenario families, and confidence-model style). Apply a preset with ElliottWaveAnalysisRunner.Builder#logicProfile(...) and override individual builder settings afterward when needed. Presets include ORTHODOX_CLASSICAL, HIERARCHICAL_SWING, BTC_RELAXED_IMPULSE, BTC_RELAXED_CORRECTIVE, ANCHOR_FIRST_HYBRID, and the volatility-scaled INTRADAY_LIVE profile.
Walk-forward adapters
Commit 279d9056 also added Elliott-specific adapters for the generic walk-forward framework:
ElliottWavePredictionProviderruns a configuredElliottWaveAnalysisRunneron each decision prefix and maps ranked base scenarios to walk-forward predictions.ElliottWaveOutcomeLabelerlabels fixed-horizon outcomes as target-first, invalidation-first, or neither, with coarse phase progression and realized return.ElliottWaveWalkForwardContextcarries the runner, prefix selector, max predictions, and metadata for repeatable runs.ElliottWaveWalkForwardProfilesexposes a tuned baseline context and split geometry for cross-run comparisons.
ElliottTrendBias and ElliottTrendBiasIndicator
The ElliottTrendBiasIndicator aggregates directional bias across all Elliott wave scenarios at each bar. It produces an ElliottTrendBias value with a signed score (-1.0 to 1.0), bullish/bearish/neutral classification, and consensus flag.
ElliottTrendBias record
| Field | Description |
|---|---|
direction |
BULLISH, BEARISH, or NEUTRAL |
score |
Signed bias score; positive = bullish dominance, negative = bearish |
bullishWeight / bearishWeight |
Sum of confidence weights for bullish/bearish scenarios |
bullishCount / bearishCount / unknownCount |
Scenario counts by direction |
consensus |
True when high-confidence scenarios agree on direction |
Helper methods: isBullish(), isBearish(), isNeutral().
Using the indicator
ElliottTrendBiasIndicator trendBias = new ElliottTrendBiasIndicator(scenarioIndicator);
// Optional: custom neutral threshold (default 0.15)
ElliottTrendBiasIndicator trendBias = new ElliottTrendBiasIndicator(scenarioIndicator, 0.20);
ElliottTrendBias bias = trendBias.getValue(index);
if (bias.isBullish() && bias.consensus()) {
// High-confidence scenarios agree on bullish direction
}
Use trend bias for entry filters (e.g. only enter long when bias is bullish and consensus is true) or for reporting.
Pluggable Swing Detection
Elliott Wave analysis can use different swing detection backends via the SwingDetector interface (package org.ta4j.core.analysis.elliott.swing). The runner accepts any SwingDetector and optional SwingFilter and ElliottSwingCompressor.
Built-in detectors
| Detector | Description |
|---|---|
| FractalSwingDetector | Fixed lookback/lookforward window (fractal-style pivots). |
| ZigZagSwingDetector | ZigZag state with fixed or ATR-based reversal threshold. |
| AdaptiveZigZagSwingDetector | ZigZag with volatility-adaptive threshold (ATR period, multiplier, min/max clamp). |
| SlopeChangeSwingDetector | Causal rolling-regression slope reversal suited to rounded turns. Its window-only constructor supplies balanced two-window persistence and half-ATR filtering; SlopeChangeConfig enables full control. |
| CompositeSwingDetector | Combines detectors with exact AND/OR agreement or tolerant same-type pivot clustering and a configurable quorum. |
Use SwingDetectors for factory methods:
// Fractal with symmetric window
SwingDetector fractal = SwingDetectors.fractal(5);
// Fractal with explicit lookback/lookforward
SwingDetector fractal = SwingDetectors.fractal(3, 5, 1);
// Adaptive ZigZag (ATR-based)
AdaptiveZigZagConfig config = new AdaptiveZigZagConfig(14, 2.0, 0, 0, 1);
SwingDetector adaptive = SwingDetectors.adaptiveZigZag(config);
// Rounded turns with balanced two-window and half-ATR defaults
SwingDetector slope = SwingDetectors.slopeChange(5);
// Full control over persistence and magnitude filtering
SwingDetector filteredSlope = SwingDetectors.slopeChange(
new SlopeChangeConfig(5, 2, 14, 0.0, 0.5));
// Tolerant consensus: require two votes within a two-bar cluster
SwingDetector consensus = SwingDetectors.consensus(
2, 2, fractal, adaptive, filteredSlope);
Choose by turn shape rather than treating one detector as universally superior: ZigZag is strongest for sharp price-distance reversals, fractals provide deterministic fixed-window extrema, and slope-change detection captures gradual turns. Tolerant consensus trades earliest confirmation for more stable pivots when detectors disagree by a few bars. Every built-in detector is causal at the requested index.
SwingFilter and MinMagnitudeSwingFilter
SwingFilter post-processes swing lists (e.g. remove noise). MinMagnitudeSwingFilter drops swings whose magnitude is below a fraction of the largest swing:
SwingFilter filter = new MinMagnitudeSwingFilter(0.10); // keep swings >= 10% of largest
ElliottWaveAnalysisRunner runner = ElliottWaveAnalysisRunner.builder()
.swingDetector(detector)
.swingFilter(filter)
.degree(ElliottDegree.INTERMEDIATE)
.build();
Data types
- SwingDetectorResult: Pivots and derived swings for a bar index.
- SwingPivot / SwingPivotType: Single pivot (index, price, high/low).
- AdaptiveZigZagConfig: ATR period, multiplier, min/max threshold, smoothing for adaptive ZigZag.
- SlopeChangeConfig: regression window, persistence count, ATR period, minimum slope change, and minimum ATR reversal.
defaults(window)returns the balanced profile used bySwingDetectors.slopeChange(window).
See Indicators Inventory for the full list of swing detector support types.
Confidence Scoring
How Confidence is Calculated
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 |
Implementation note:
- The default scoring is continuous, not binary. In particular, extension ratios are down-weighted when they drift outside preferred wave-3/wave-5 zones, which improves ranking separation between “acceptable” and “best-fit” scenarios.
The ElliottConfidence Record
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
Interpreting Confidence Levels
| 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 |
Custom Confidence Weights
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
);
Working with Alternative Wave Counts
Why Multiple Counts Matter
In practice, you’ll often encounter situations where price action supports multiple valid interpretations. For example:
- Impulse vs. Correction: Is this sharp move wave 3 of an impulse, or wave C of a correction?
- Degree Uncertainty: Is this a minor wave 4 or an intermediate wave 2?
- Pattern Classification: Is this a zigzag or a flat correction?
The scenario-based system helps you:
- See all plausible interpretations at once
- Understand which interpretation is most likely (base case)
- Plan for alternative outcomes
- Know when the market will resolve the ambiguity (via invalidation levels)
Practical Example: Handling Ambiguity
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");
}
Monitoring Alternative Scenarios Over Time
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()));
}
}
Price Projections and Targets
Using ElliottProjectionIndicator
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);
Understanding Projection Logic
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) |
Calculating Custom Projections
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);
Invalidation Levels
The Importance of Invalidation
Every Elliott Wave count has specific price levels that would invalidate it. Knowing these levels is crucial for:
- Stop placement: Setting stops just beyond invalidation levels
- Scenario transition: Knowing when to switch from primary to alternative count
- Risk management: Understanding exactly where the analysis is wrong
Using ElliottInvalidationLevelIndicator
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);
}
Invalidation Modes
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 |
Checking Invalidation Status
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
Boolean Invalidation Indicator
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.).
Intermediate Usage Patterns
Multi-Degree Analysis
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");
}
}
Custom Swing Detection
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)
);
Swing Compression
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.
Wave Degree Navigation
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());
}
Auto-Selecting Wave Degrees
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:
- Bar duration: Filters out degrees that are too fine for the timeframe
- History length: Matches total history (duration × count) to typical ranges for each degree
- Score ranking: Returns degrees ordered by how well they fit the available data
Typical ranges (rule of thumb):
- Daily bars: PRIMARY through MINUTE degrees
- Weekly bars: CYCLE, PRIMARY, INTERMEDIATE degrees
- Hourly bars: INTERMEDIATE through MINUETTE degrees
- 5-15 minute bars: MINOR through SUB_MINUETTE degrees
ElliottWaveIndicatorSuiteDemo and ElliottWavePresetDemo automatically use degree recommendations when no explicit degree is provided via command-line arguments.
Advanced Topics
Scenario Generator Configuration
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);
Working with Scenario History
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");
}
}
}
}
Combining with Other Indicators
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");
}
}
Performance Considerations
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();
Best Practices
Swing Detection Configuration
- Match Window Size to Timeframe:
- Intraday: 3-5 bars
- Daily: 5-10 bars
- Weekly: 10-20 bars
- Consider Market Volatility:
- High volatility: Use ZigZag with ATR-based thresholds
- Low volatility: Fractal windows work well
Confidence Interpretation
- Don’t Chase High Confidence: A 90% confidence scenario isn’t “right”—it just fits the current data well
- Watch for Confidence Decay: If confidence drops bar-over-bar, the count may be becoming less valid
- Value Consensus Over Confidence: When multiple scenarios agree on direction, that’s often more actionable than a single high-confidence count
Managing Multiple Counts
- Plan for Alternatives: Before entering a trade, identify what price level would shift you to the alternative count
- Use Conservative Invalidation: When uncertain, use the conservative invalidation mode
- Accept Ambiguity: Some market conditions genuinely have multiple valid interpretations—don’t force a single count
Error Handling
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
}
Integration with Trading Strategies
Built-in scenario rules (org.ta4j.core.rules.elliott)
ta4j ships public Rule implementations that read the base case from an ElliottScenarioIndicator (typically facade.scenarios()). All rules evaluate the ranked base scenario unless noted otherwise.
| Rule | Purpose |
|---|---|
ElliottScenarioValidRule |
Base scenario exists and reference price is finite |
ElliottScenarioConfidenceRule |
Base confidence meets a minimum threshold |
ElliottScenarioDirectionRule |
Base scenario is bullish or bearish |
ElliottImpulsePhaseRule |
Base scenario is impulse-typed and in allowed ElliottPhase values |
ElliottScenarioAlternationRule |
Wave 2/4 alternation meets a minimum duration ratio |
ElliottScenarioCompletionRule |
Base scenario expectsCompletion() is true |
ElliottScenarioRiskRewardRule |
Base scenario risk/reward meets a minimum |
ElliottScenarioTargetReachedRule |
Price has reached the scenario primary target |
ElliottScenarioStopViolationRule |
Price breached the corrective stop |
ElliottScenarioInvalidationRule |
Price invalidated the base scenario (useful as an exit trigger) |
ElliottScenarioTimeStopRule |
Trade exceeded the maximum wave-duration window |
ElliottTrendBiasRule |
Aggregated ElliottTrendBias direction and strength thresholds |
Example wiring with a tolerance-aware facade:
Num fibTolerance = series.numFactory().numOf(0.25);
ElliottWaveFacade facade = ElliottWaveFacade.fractal(
series, 5, ElliottDegree.INTERMEDIATE, Optional.of(fibTolerance), Optional.empty());
ElliottScenarioIndicator scenarios = facade.scenarios();
ClosePriceIndicator close = new ClosePriceIndicator(series);
Rule entryRule = new ElliottScenarioValidRule(scenarios, close)
.and(new ElliottScenarioConfidenceRule(scenarios, 0.6))
.and(new ElliottImpulsePhaseRule(scenarios, ElliottPhase.WAVE3))
.and(new ElliottScenarioDirectionRule(scenarios, true))
.and(new ElliottTrendBiasRule(scenarios, true, 0.5));
Rule exitRule = new ElliottScenarioInvalidationRule(scenarios, close)
.or(new ElliottScenarioTargetReachedRule(scenarios, close, true))
.or(new ElliottScenarioTimeStopRule(scenarios, 2.0));
Prefer these rules over ad-hoc anonymous Rule implementations when the built-in semantics match your strategy.
Basic Scenario-Aware Rule
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
}
}
Dynamic Stop Based on Invalidation
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());
}
Multi-Scenario Trading Logic
// 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
}
Summary
ta4j’s Elliott Wave indicators provide a sophisticated toolkit for wave-based technical analysis that acknowledges the inherent uncertainty in wave counting:
Quick Start with Scenarios
// 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());
Quick Start with Example Classes
For the fastest way to see Elliott Wave analysis in action, use the provided example classes:
# Analyze ossified Bitcoin with default settings
java ta4jexamples.analysis.elliottwave.ElliottWavePresetDemo ossified btc
# Analyze ossified Ethereum
java ta4jexamples.analysis.elliottwave.ElliottWavePresetDemo ossified eth
# Analyze ossified S&P 500
java ta4jexamples.analysis.elliottwave.ElliottWavePresetDemo ossified sp500
# Custom live-style analysis with command-line arguments
java ta4jexamples.analysis.elliottwave.ElliottWavePresetDemo live Coinbase BTC-USD PT1D 1825 PRIMARY
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.
Key Takeaways
- Embrace Uncertainty: Multiple valid wave counts are normal, not a problem
- Use Confidence Wisely: High confidence means good fit to rules, not prediction accuracy
- Plan for Alternatives: Know what would change your view before entering trades
- Set Invalidation Stops: Use explicit invalidation levels for disciplined risk management
- Watch for Consensus: When scenarios agree, conviction can be higher
- Leverage Examples: Start with the provided example classes to understand usage patterns
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.