Files
Emerson GomesandCursor 70b53189b9 fix(patterns): stop silent degradation and untrue availability claims
Pattern detection had two recurring defects: a fallback that quietly
substituted different data than the caller asked for, and metadata that
asserted a point-in-time or guarantee the computation could not support.

Data substitution:

- Denoising on the classic/harmonic/elliott/fractal path smoothed only
  `close`, so the smoothed close routinely fell outside its own raw candle
  and ~68% of rows were reverted to raw -- preferentially at the local
  extremes pivot detection keys on. Require the full OHLC set, matching the
  candlestick path, and reject an explicitly partial column list.
- A failed denoise still echoed the requested spec with
  `preprocessing_causality: "causal"`. Report `denoise_applied: false`,
  `denoise_error` and `preprocessing_causality: "raw_prices"` instead.
- One non-finite OHLC value makes the aggregate candlestick dispatcher
  throw, leaving only the few directly-attached detectors. The failure was
  unrecorded because `dispatch_names` is the string "all", not a list. Add a
  `detector_coverage` block plus a warning, and surface names the installed
  backend does not provide (`outside` is absent from pandas-ta-classic).

Untrue claims:

- Elliott `available_at_index` was 67% irreproducible when re-running over a
  window truncated at that index, because pivot spacing and (under the
  default `scale_mode="auto"`) the swing scale are whole-window statistics.
  Report the right edge as the guarantee and demote the confirmation-derived
  figure to `earliest_possible_index_estimate` with a caveat, matching the
  harmonic contract. Also track when a min-distance choice was settled so a
  pivot surviving a later challenge is not claimed knowable at its own bar.
- The "no patterns detected in N bars" note omitted `observed_bars` outside
  candlestick mode, asserting absence over bars never fetched.
- `df.attrs["pattern_ohlc_fallback"]` was written for the API layer to
  disclose and never read, and was written after a slice so it could not
  reach the caller. Wire it into warnings and report `analyzed_bars` when a
  detector caps its own input window.
- `data_freshness` emitted the literal string "None", which is truthy and
  then broke the year parse that would have raised a staleness warning.

Silent no-ops and misattribution:

- The documented `mode="all"` namespacing (`{"harmonic": {...}}`) reported
  the section name as an invalid key and failed the fractal and harmonic
  sections. Validate a section's contents against that detector, and
  attribute an unknown top-level key to every detector that could own it
  rather than only fractal and harmonic.
- `detail="summary"` dropped `warnings`, `data_quality`, `candles` and
  `effective_window` -- exactly the fields that say not to trust a result.
- `min_strength`, `min_gap` and `robust_only` were accepted and ignored
  outside candlestick mode; reject a non-default value like the adjacent
  `engine` and `whitelist` guards do.
- Fractal `include_stale_levels` was discarded by the visibility allowlist
  unless `include_completed` was also set, and stale rows were hidden
  without being counted.
- Six Elliott config knobs were unread: the four rule/classifier blend
  weights (no blend exists; an existing test pinned that) and two additive
  unconfirmed-pattern penalties superseded by the confidence cap. Remove
  them so they become explicit unknown-key errors.

Scoring correctness:

- Candlestick geometry keyed its body-size expectation off the deprioritize
  set, so hammers, shooting stars and harami were scored as if a large body
  confirmed them, while marubozu was scored as if a small one did. Classify
  the expectation per pattern.
- A zero-range bar scored `body_ratio = 0.5` ("unknown"), making a perfect
  doji rank below an imperfect one; it has a zero body by construction.
- `min_gap` spaced candidates selected at threshold 0.0, so a weak hit could
  consume the slot and erase a qualifying signal one bar away. Space among
  rows that meet `min_strength`.
- `inside` was signed by candle colour, contradicting the harami detected on
  the identical bars and defeating the dedupe that exists to replace it.
  Report it as non-directional and let the specific form suppress it.
- A hikkake confirmation bar (magnitude 200) was given the three-bar setup
  span, putting `start_index` inside the setup.
- Candlestick `recency` used the reported bar count while `end_index` indexes
  the warmup-inclusive frame, clamping `bars_ago` to 0 for every pattern in
  the warmup span and biasing the cross-detector ranking.
- Volume-confirmation warmup ignored pattern span, so a multi-bar pattern on
  the first visible bar had a short baseline.

Verified: ruff clean, 10115 backend tests pass, schemas 83/83.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-01 13:54:32 -05:00

19 KiB

Pattern detection and similarity search

Audience: User

Two related ideas:

  1. Pattern detection — flag known candlestick and chart shapes
  2. Analog / similarity search — find historical windows that look like now, then study what happened next

Use patterns as context, not automatic entry rules. Confirm with regime, volatility, and risk tools.

Related: Forecasting · Indicators · Glossary · Levels


Pattern detection (patterns_detect)

Identifies visual patterns traders often watch for structure and timing context.

mode=all runs five detector families for each selected timeframe. If only some detector/timeframe pairs fail, the response sets partial_failure: true and includes requested, succeeded, and failed counts plus failed_items. Partial findings remain usable by default; pass --allow-partial false for a strict scan that returns success: false on any omission.

Candlestick Patterns

Single or multi-bar patterns with historical significance.

mtdata-cli patterns_detect EURUSD --timeframe H1 --mode candlestick --lookback 200

Output:

{
  "success": true,
  "symbol": "EURUSD",
  "timeframe": "H1",
  "lookback": 200,
  "mode": "candlestick",
  "n_patterns": 29,
  "patterns_shown": 3,
  "pattern_status": "bullish",
  "pattern_confidence": 0.84,
  "top_patterns": [
    {
      "name": "Bullish Engulfing",
      "direction": "bullish",
      "match_score": 0.84,
      "time": "2025-12-22 10:00"
    }
  ]
}

Compact output ranks the recent candidates in top_patterns and reports the aggregate pattern_status. The exact rows and optional status fields depend on the detector result. Use --detail full when you need every surviving pattern row rather than the compact preview.

Filter to the curated robust pattern subset:

mtdata-cli patterns_detect EURUSD --mode candlestick --robust-only true

Common patterns detected:

Pattern Meaning
Engulfing Current candle completely covers previous (reversal)
Doji Open ≈ Close (indecision)
Hammer/Hanging Man Small body, long lower wick
Inside Current bar inside previous bar's range
Harami Small body inside previous large body
Morning/Evening Star Three-bar reversal pattern

Classic Chart Patterns

Larger geometric patterns formed over multiple bars.

mtdata-cli patterns_detect EURUSD --timeframe H1 --mode classic --lookback 500

The default classic detector evaluates patterns at the right edge of the input window and excludes pivots that do not yet have the configured right-hand confirmation gap. Results report available_at_index, available_at_time, pivot_confirmation_bars, and detection_scope. Set config.scan_historical=true to run the slower causal prefix scan when you need older patterns labeled at their first detection window.

Patterns detected:

Pattern Description
Head and Shoulders Three peaks, middle highest (bearish reversal)
Inverse H&S Three troughs, middle lowest (bullish reversal)
Double Top/Bottom Two peaks/troughs at similar level
Triangle Converging trendlines (breakout setup)
Wedge Rising or falling wedge
Rectangle Horizontal consolidation

Harmonic Patterns

Fibonacci-ratio patterns built from alternating pivot legs.

The harmonic detector reports both forming and completed XABCD/ABCD candidates. Those lifecycle states are both primary harmonic findings and are therefore returned even when the shared --include-completed flag is false. That flag continues to control historical visibility for classic, Elliott, and fractal modes.

For harmonic and Elliott results, available_at_index is the right edge of the requested window, reported as available_at_index_basis: "input_window_right_edge". That is the only index at which the result is guaranteed reproducible: pivot prominence, min-distance spacing and (for Elliott under the default scale_mode="auto") the swing scale itself are all measured across the whole window, so a later bar can add, remove or move a pivot. Each row also carries earliest_possible_index_estimate, a heuristic derived from per-pivot price confirmation, alongside earliest_possible_index_caveat. Do not use the estimate for backtest entry timing; re-run over a truncated window to establish genuine point-in-time availability.

mtdata-cli patterns_detect EURUSD --timeframe H1 --mode harmonic --lookback 500

Patterns detected:

Pattern Description
ABCD Four-point measured-move completion
Gartley XABCD retracement pattern with 0.786 XA completion
Bat / Alternate Bat XABCD patterns with deeper D-point completion
Butterfly XABCD extension beyond X
Crab / Deep Crab Extended XABCD completion patterns
Shark, Cypher, 5-0 Additional Fibonacci-ratio reversal structures

Useful harmonic config:

mtdata-cli patterns_detect EURUSD --timeframe H1 --mode harmonic --config "pattern_types=gartley,bat,crab ratio_tolerance=0.06 min_confidence=0.45"

Common output fields:

Field Meaning
entry_price D-point completion price
target_price, target_price_1, target_price_2 CD retracement targets
invalidation_price Pattern invalidation level with configured buffer
lifecycle Post-completion state vs later OHLC: forming, active, target_reached, expired, or historical
bias_scope current only while a completed pattern is still an active setup
price_levels Entry, targets, invalidation, and PRZ levels
details.ratios Measured Fibonacci ratios for the candidate

A completed bullish harmonic is not a long_setup after price has already reached the first target or broken invalidation. Compact output still keeps target_price and invalidation_price for an active current setup.

Fractal Patterns

Bill Williams-style bullish and bearish fractal levels with confirmation and breakout context.

mtdata-cli patterns_detect EURUSD --timeframe H1 --mode fractal --lookback 300

Useful fractal config:

mtdata-cli patterns_detect EURUSD --timeframe H1 --mode fractal --config "left_bars=2 right_bars=2 breakout_basis=high_low"

Common output fields:

Field Meaning
level_price Confirmed fractal high/low level
status Level lifecycle: active or broken (not pattern completion)
level_state active when unbroken, broken after price breaches the level
confirmation_date When the fractal became knowable after the right-side bars closed
breakout_direction Direction of the later level break (bullish or bearish)
breakout_date When the breakout occurred, if any

Active levels are informational support/resistance context and have neutral signal bias. A broken level takes the breakout direction as its bias. By default, active levels are returned while broken and stale levels are hidden; --include-completed true adds both, and --config '{"include_stale_levels": true}' adds only the stale ones. Whatever is withheld is counted in broken_levels_hidden and stale_levels_hidden.

Parameters

Parameter Default Description
--mode candlestick Pattern type: all, candlestick, classic, harmonic, fractal, elliott
--lookback 150 Historical bars fetched for pattern analysis. mode=all requires at least 150 and caps each timeframe to roughly one year of history, so the weekly leg fetches far fewer bars than requested. Detectors additionally cap their own input window (max_bars, 1500 for classic and harmonic); when that binds, the response reports analyzed_bars and warns.
--robust-only false Restrict detection to a curated subset of established multi-bar candlestick types. This is a name preset, not a confidence threshold. Candlestick and all modes only.
--whitelist — Comma-separated list of specific patterns
--min-strength 0.70 Minimum OHLC-geometry and pattern-reliability strength score (0.0-1.0). Candlestick and all modes only; other modes reject a non-default value rather than ignoring it, and apply their own confidence rules via --config.
--min-gap 3 Minimum bars between returned candlestick detections. Candlestick and all modes only. Spacing is applied among detections that meet --min-strength.
--top-k 3 Candidate/collision budget and compact, summary, or standard row cap; it is not a global cap for --detail full
--last-n-bars — Candlestick-only recency window for returned detections; use it to bound a full scan
--config — Detector-specific overrides. Fractals support left_bars, right_bars, breakout_basis, min_prominence_pct, and confidence_prominence_cap_pct. Harmonics support pattern_types, ratio_tolerance, min_confidence, and pivot controls.

Detector config is strict. Candlestick mode accepts use_volume_confirmation, volume_confirm_breakout_bars, volume_confirm_lookback_bars, volume_confirm_min_ratio, volume_confirm_bonus, volume_confirm_penalty, use_regime_context, regime_alignment_bonus, and regime_countertrend_penalty; other keys return unknown_config_key instead of being ignored.

In mode=all, un-namespaced config keys apply to every detector that has the field. Nest under a section name to retune one detector, for example --config '{"harmonic": {"min_confidence": 0.7}}'. An unknown key inside a section is reported as section.key against that section alone; an unknown top-level key is reported against every detector that could have owned it.

Denoising

Pattern detection smooths all four OHLC columns together, because pivot geometry is read from the open, high, low and close as a set. If you omit denoise.columns the tool substitutes ohlc; if you pass a column list that lacks any of them the request is rejected rather than analyzing a series that mixes smoothed and raw prices. When denoising fails, the response reports denoise_applied: false with denoise_error and preprocessing_causality: "raw_prices" — it never echoes the requested spec as if it had been applied. Zero-phase denoising sets denoise_lookahead_bias: true and rewrites every row's status_basis, because such results may repaint. See DENOISING.md.

Pattern names listed in this guide describe detector coverage, not a promise that every pattern is returned at the default threshold. robust_only=true restricts which candlestick detectors run based on pattern name, while min_strength independently filters their conviction scores. Lower-strength and deprioritized formations such as many dojis may be absent by default. The score uses body/range geometry, directional close location, range expansion, pattern span, and the curated reliability tier. Raw detector magnitudes remain in raw_signal but do not alter strength because pandas-ta backends use different native signal scales.

Named whitelists are resolved against both individual backend methods and an aggregate pattern dispatcher. Full results report requested_detectors, detectors_evaluated, and any unsupported_detectors, so a zero-hit detector is distinguishable from one the active backend could not run.

Most detectors run through the aggregate dispatcher, so a single non-finite OHLC value can stop nearly all of them at once. When fewer detectors run than were selected, the response adds a detector_coverage block with the expected and evaluated counts, the names that did not run, any aggregate_dispatcher_error, and any names the installed backend does not provide. Treat "no patterns found" as inconclusive whenever that block is present.

For candlesticks, top_k also resolves collisions when several detector types fire on the same bar. Compact and summary use it as a preview budget, and standard uses it as a returned-row cap. Full detail deliberately keeps every surviving row and reports that scope in top_k_contract (candlestick mode only); combine full detail with --last-n-bars when the complete row set must remain small.

--detail trades away per-row diagnostics, never the fields that tell you not to trust a result: warnings, data_quality, candles and effective_window are present at every detail level, including summary.

Filtering Patterns

Classic detector config values max_pattern_age_bars and max_pattern_span_bars bound all detector results, including completed patterns. --include-completed true adds completed structures that remain inside those detection bounds; it does not request an unbounded historical scan.

By name:

mtdata-cli patterns_detect EURUSD --mode candlestick --whitelist "ENGULFING,HAMMER,DOJI"

By confidence:

mtdata-cli patterns_detect EURUSD --mode candlestick --min-strength 0.85

Analog Forecasting

Finds historical windows that "look like" the current market and uses them to predict what happens next.

Concept

"History doesn't repeat, but it rhymes."

  1. Take the last N bars (the "query window")
  2. Search through historical data for similar patterns
  3. Look at what happened after those patterns
  4. Average/aggregate those future moves into a forecast

Basic Usage

mtdata-cli forecast_generate EURUSD --timeframe H1 --horizon 12 --method analog --params "window_size=64 top_k=20"

Parameters

Parameter Default Description
window_size 64 Length of pattern to match
search_depth 5000 How far back to search
top_k 20 Number of similar patterns to use
metric euclidean Initial distance: euclidean, cosine, correlation
scale zscore Normalization: zscore, minmax, none
refine_metric dtw Refinement: dtw, softdtw, affine, ncc, none
search_engine ckdtree Search algorithm

Scaling Options

Scale Description When to Use
zscore Standardize to mean=0, std=1 Default, handles varying volatility
minmax Scale to [0,1] When range matters more than volatility
none No scaling When absolute levels matter

Distance Metrics

The metric parameter controls the initial candidate search (must be fast — Euclidean-family):

Metric Description
euclidean Standard L2 distance (default, fastest)
cosine Cosine distance after vector normalization
correlation Correlation distance after centering and normalization

The refine_metric parameter re-ranks candidates using a slower, more precise metric:

Refine Metric Description
dtw Dynamic Time Warping (handles time warping)
softdtw Differentiable DTW
ncc Normalized cross-correlation
affine Affine-invariant distance

Example with refinement:

mtdata-cli forecast_generate EURUSD --horizon 12 --method analog --params "window_size=64 metric=euclidean refine_metric=dtw"

This first finds candidates with fast Euclidean distance, then refines ranking using DTW.

Search Engines

Engine Description
ckdtree Scipy KD-tree (default, fast)
hnsw Approximate nearest neighbor (scalable hnswlib backend; included in [all], or add to lean installs through the source-build path in ../SETUP.md)
matrix_profile STUMPY-based (specialized for time series)
mass Mueen's MASS algorithm

matrix_profile and mass require metric=euclidean with scale=zscore. Unsupported values and incompatible combinations are rejected rather than silently replaced with another similarity algorithm.


Practical Applications

Pattern-Based Entry Filter

Use pattern detection as a confirmation signal:

# Check for reversal patterns at support
mtdata-cli patterns_detect EURUSD --mode candlestick --robust-only true

# If bullish pattern detected at support level → consider long entry

Analog-Based Targets

Use analog forecasts to set price targets:

# Find similar historical patterns
mtdata-cli forecast_generate EURUSD --method analog --params "window_size=64 top_k=20" --json

# Use forecast percentiles for TP levels

Combining with Technical Analysis

# Get patterns and indicators together
mtdata-cli data_fetch_candles EURUSD --limit 200 --indicators "ema(20),rsi(14)"

# Then check patterns
mtdata-cli patterns_detect EURUSD --mode candlestick --robust-only true

# Look for pattern + indicator confluence

Interpreting Results

Pattern Detection

data[5]{time,pattern}:
    "2025-12-19 05:00",Bearish ENGULFING
    "2025-12-22 10:00",Bearish ENGULFING

Interpretation:

  • Pattern occurred at specific times
  • "Bearish" suggests potential downward move
  • Combine with other analysis (support/resistance, indicators)

Analog Forecast

{
  "forecast": [1.1755, 1.1758, 1.1762, ...],
  "lower": [1.1740, 1.1738, ...],
  "upper": [1.1770, 1.1778, ...],
  "analogs_found": 20
}

Interpretation:

  • forecast: Median of analog outcomes
  • lower/upper: Spread of analog outcomes
  • Wide spread = diverse outcomes in similar historical patterns

Quick Reference

Task Command
Candlestick patterns mtdata-cli patterns_detect EURUSD --mode candlestick
Curated candlestick subset mtdata-cli patterns_detect EURUSD --mode candlestick --robust-only true
Chart patterns mtdata-cli patterns_detect EURUSD --mode classic
Harmonic patterns mtdata-cli patterns_detect EURUSD --mode harmonic
Fractal levels and breakouts mtdata-cli patterns_detect EURUSD --mode fractal
Analog forecast mtdata-cli forecast_generate EURUSD --method analog --params "window_size=64 top_k=20"
Analog with DTW mtdata-cli forecast_generate EURUSD --method analog --params "refine_metric=dtw"

See Also