Skip to main content
The Wyckoff method analyses price and volume to identify the four canonical market phases:
  • Accumulation — smart money builds positions quietly in a trading range before markup.
  • Markup — price trends upward after a successful accumulation.
  • Distribution — smart money unwinds positions in a new trading range before markdown.
  • Markdown — price trends downward after distribution.
Within each accumulation or distribution range, Wyckoff further subdivides the activity into phases A through E, capturing the stopping action (A), cause-building (B), test (C), trend confirmation (D), and trend in motion (E). ta4j exposes Wyckoff analysis through two entry points:
  • WyckoffCycleFacade — indicator-style, per-bar access. Use this in strategy rules and chart overlays.
  • WyckoffCycleAnalysisRunner — one-shot snapshot across one or more configuration degrees.

WyckoffCycleFacade

WyckoffCycleFacade wires the WyckoffPhaseIndicator and its supporting components from a single fluent builder. The facade gives you phase values, trading-range boundaries, and phase transition tracking at any bar index.

Creating a facade

// All parameters use library defaults
WyckoffCycleFacade facade = WyckoffCycleFacade.of(series);

Reading phase values

int begin = series.getBeginIndex() + facade.unstableBars();

for (int i = begin; i <= series.getEndIndex(); i++) {
    WyckoffPhase phase = facade.phase(i);

    // Skip bars with no determined cycle
    if (phase.cycleType() == WyckoffCycleType.UNKNOWN) {
        continue;
    }

    // Print only at phase transition bars
    if (facade.lastPhaseTransitionIndex(i) != i) {
        continue;
    }

    Num rangeLow  = facade.tradingRangeLow(i);
    Num rangeHigh = facade.tradingRangeHigh(i);

    System.out.printf("%s -> %s %s (confidence=%.2f, range=[%s, %s])%n",
            series.getBar(i).getEndTime(),
            phase.cycleType(),
            phase.phaseType(),
            phase.confidence(),
            rangeLow,
            rangeHigh);
}

Facade API reference

MethodReturn typeDescription
facade.phase()WyckoffPhaseIndicatorPhase indicator for use in rules
facade.phase(int)WyckoffPhasePhase value at the given bar index
facade.tradingRangeHigh(int)NumUpper bound of the current trading range, or NaN
facade.tradingRangeLow(int)NumLower bound of the current trading range, or NaN
facade.lastPhaseTransitionIndex(int)intBar index of the most recent phase transition, or -1
facade.unstableBars()intWarm-up bar count before indicators are stable

WyckoffPhase

Each WyckoffPhase value carries:
  • cycleType()WyckoffCycleType.ACCUMULATION, DISTRIBUTION, or UNKNOWN.
  • phaseType()WyckoffPhaseType.PHASE_A through PHASE_E.
  • confidence() — a [0.0, 1.0] score indicating detection strength.
WyckoffPhase phase = facade.phase(index);
WyckoffCycleType cycle = phase.cycleType();  // ACCUMULATION, DISTRIBUTION, UNKNOWN
WyckoffPhaseType letter = phase.phaseType(); // PHASE_A through PHASE_E
double confidence = phase.confidence();

Builder parameters

MethodDefaultDescription
.withSwingConfiguration(int, int, int)3, 3, 1Bars preceding a pivot, bars following, allowed equal bars
.withVolumeWindows(int, int)5, 20Short and long volume SMA window lengths
.withTolerances(Num, Num)0.02, 0.05Breakout tolerance and retest tolerance as ratios
.withVolumeThresholds(Num, Num)1.6, 0.7Climax threshold ratio and dry-up threshold ratio
All swing bars and volume windows must meet the constraints: precedingSwingBars >= 1, followingSwingBars >= 0, shortWindow >= 1, and longWindow >= shortWindow. Violating any constraint throws IllegalArgumentException.

WyckoffCycleAnalysisRunner

WyckoffCycleAnalysisRunner runs a one-shot Wyckoff analysis and collects all phase transitions across the full series. It can optionally run at higher or lower configuration degree offsets — coarser swing detection at positive offsets, finer at negative offsets.
1

Build the runner

var runner = WyckoffCycleAnalysisRunner.builder()
        .withSwingConfiguration(3, 3, 1)
        .withVolumeWindows(5, 20)
        .withTolerances(0.02, 0.05)
        .withVolumeThresholds(1.5, 0.7)
        .higherDegrees(1)  // also analyze with coarser parameters (+1 offset)
        .lowerDegrees(0)
        .build();
2

Run analysis

WyckoffCycleAnalysisResult result = runner.analyze(series);
3

Inspect the result

// Base-degree analysis (degree offset = 0)
result.baseAnalysis().ifPresent(base -> {
    WyckoffCycleAnalysisResult.CycleSnapshot snapshot = base.cycleSnapshot();
    System.out.println("Final phase: " + snapshot.finalPhase().cycleType());
    System.out.println("Transitions: " + snapshot.transitions().size());

    // Walk all detected transitions
    for (WyckoffCycleAnalysisResult.PhaseTransition t : snapshot.transitions()) {
        System.out.printf("  bar=%d phase=%s %s range=[%s,%s]%n",
                t.barIndex(), t.phase().cycleType(), t.phase().phaseType(),
                t.rangeLow(), t.rangeHigh());
    }
});

Degree offsets

Unlike Elliott Wave, Wyckoff analysis in ta4j does not use a canonical degree enum. Instead, WyckoffCycleAnalysisRunner expresses “degrees” as integer offsets from the base configuration (offset 0). Positive offsets coarsen the swing detection and lengthen volume windows; negative offsets tighten them. The default DegreeConfigurationProvider scales precedingSwingBars, followingSwingBars, volumeShortWindow, and volumeLongWindow linearly by the degree offset. Provide a custom DegreeConfigurationProvider for full control.

Example class

The WyckoffCycleIndicatorSuiteDemo in ta4jexamples.wyckoff demonstrates both entry points:
# Linux/macOS
./mvnw -pl ta4j-examples exec:java \
  -Dexec.mainClass=ta4jexamples.wyckoff.WyckoffCycleIndicatorSuiteDemo

# Windows CMD
mvnw.cmd -pl ta4j-examples exec:java "-Dexec.mainClass=ta4jexamples.wyckoff.WyckoffCycleIndicatorSuiteDemo"
The demo loads a CSV-backed BarSeries, builds a facade with custom tolerances, iterates the series printing phase transitions, then runs WyckoffCycleAnalysisRunner for a one-shot snapshot.

Complete demo code

BarSeries series = CsvFileBarSeriesDataSource.loadSeriesFromFile();
var numFactory = series.numFactory();

// Per-bar access via facade
WyckoffCycleFacade facade = WyckoffCycleFacade.builder(series)
        .withSwingConfiguration(3, 3, 1)
        .withVolumeWindows(5, 20)
        .withTolerances(numFactory.numOf(0.02), numFactory.numOf(0.05))
        .withVolumeThresholds(numFactory.numOf(1.5), numFactory.numOf(0.7))
        .build();

int begin = series.getBeginIndex() + facade.unstableBars();
for (int i = begin; i <= series.getEndIndex(); i++) {
    WyckoffPhase phase = facade.phase(i);
    if (phase.cycleType() == WyckoffCycleType.UNKNOWN) continue;
    if (facade.lastPhaseTransitionIndex(i) != i) continue;
    System.out.printf("%s -> %s %s (confidence=%.2f, range=[%s, %s])%n",
            series.getBar(i).getEndTime(), phase.cycleType(), phase.phaseType(),
            phase.confidence(), facade.tradingRangeLow(i), facade.tradingRangeHigh(i));
}

// One-shot analysis
var analysis = WyckoffCycleAnalysisRunner.builder()
        .withSwingConfiguration(3, 3, 1)
        .withVolumeWindows(5, 20)
        .withTolerances(0.02, 0.05)
        .withVolumeThresholds(1.5, 0.7)
        .build();

WyckoffCycleAnalysisResult result = analysis.analyze(series);
result.baseAnalysis().ifPresent(base ->
        System.out.println("Transitions: " + base.cycleSnapshot().transitions().size()));
WyckoffPhaseIndicator requires a warm-up period. Always start iteration at series.getBeginIndex() + facade.unstableBars() to avoid reading undefined values during the warm-up window.