
Zigzag Pattern Classifier
- 57 installs
- 62 repo stars
- Updated August 3, 2026
- terrylica/cc-skills
Helps with ai & agent building tasks.
About
zigzag-pattern-classifier is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- zigzag-pattern-classifier
- AI & Agent Building
- AI-coding skill
Zigzag Pattern Classifier by the numbers
- 57 all-time installs (skills.sh)
- +1 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #6,592 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/terrylica/cc-skills --skill zigzag-pattern-classifierAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 57 |
|---|---|
| repo stars | ★ 62 |
| Last updated | August 3, 2026 |
| Repository | terrylica/cc-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
ZigZag Swing Pattern Classifier
Complete taxonomy for classifying ZigZag swing patterns by structure and market regime. Every confirmed ZigZag sequence maps to exactly one variant — no gaps, no overlaps.
Self-Evolving Skill: This skill improves through use. If instructions are wrong, parameters drifted, or a workaround was needed — fix this file immediately, don't defer. Only update for real, reproducible issues.
When to Use
- Classifying a confirmed L₀→H₁→L₂ (two-pivot) or L₀→H₁→L₂→H₃ (three-pivot) swing
- Looking up market regime implications of a specific pattern
- Answering "how many distinct patterns exist?" and proving exhaustiveness
- Implementing pattern labeling in code (Rust
qtacrate, Python pipelines) - Understanding the epsilon tolerance band that defines "equal"
- Applying Freedman-Diaconis binning for sub-classification depth
Notation (Single Source of Truth)
| Symbol | Definition | Example |
|---|---|---|
| L₀ | Initial low (first confirmed pivot) | 1.0800 |
| H₁ | Swing high (reversal peak) | 1.0850 |
| L₂ | Second low (retracement) | 1.0810 |
| H₃ | Second high (three-pivot only) | 1.0870 |
| W | Swing magnitude: H₁ − L₀ | 0.0050 |
| z | Normalized retracement: (L₂ − L₀) / (H₁ − L₀) | 0.20 |
| o | Volatility-normalized overshoot: (L₀ − L₂) / ATR₁₄ | 0.35 |
| ε | Tolerance band for "equal" classification | 5 pips |
| τ | ZigZag reversal threshold | 6.6 pips |
Price Level Comparisons
| Code | Meaning | Condition |
|---|---|---|
| HL | Higher Low | L₂ > L₀ + ε |
| EL | Equal Low | \ |
| LL | Lower Low | L₂ < L₀ − ε |
| HH | Higher High | H₃ > H₁ + ε |
| EH | Equal High | \ |
| LH | Lower High | H₃ < H₁ − ε |
---
Part 1: Two-Pivot Patterns (UP-DOWN)
Pattern: L₀ → H₁ → L₂ (one up-leg, one down-leg)
Base Classification (3 Classes)
Every UP-DOWN triplet falls into exactly one:
| Class | Condition | Meaning | Frequency |
|---|---|---|---|
| EL | \ | L₂ − L₀\ | ≤ ε |
| HL | L₂ > L₀ + ε | Higher Low (pullback) | ~50–60% |
| LL | L₂ < L₀ − ε | Lower Low (undercut) | ~10–20% |
Granular Classification: 9 FD-Binned Variants
HL and LL each decompose into 4 sub-classes using Freedman-Diaconis binning on normalized coordinates:
- HL bins use z = (L₂ − L₀) / (H₁ − L₀), ranging 0 to 1
- LL bins use o = (L₀ − L₂) / ATR₁₄, ranging 0 to ∞
| Variant | z or o Range | Market Regime | Probability |
|---|---|---|---|
| EL | \ | z\ | ≤ ε_r |
| HL-FD1 | 0.75 < z < 1.0 | Shallow pullback — buyers in control | 20–25% |
| HL-FD2 | 0.50 < z ≤ 0.75 | Moderate pullback — Fib 38–50% | 15–20% |
| HL-FD3 | 0.25 < z ≤ 0.50 | Deep pullback — Fib 61.8% | 10–15% |
| HL-FD4 | 0 < z ≤ 0.25 | Near-complete retrace — barely held | 5–10% |
| LL-FD1 | o ≤ q₂₀ | Micro undercut — brief false break | 8–12% |
| LL-FD2 | q₂₀ < o ≤ q₄₀ | Shallow undercut — moderate panic | 4–8% |
| LL-FD3 | q₄₀ < o ≤ q₆₀ | Deep undercut — structural break | 2–5% |
| LL-FD4 | o > q₆₀ | Extreme undercut — tail event | <2% |
Trading Implications
| Variant | Entry Signal | Stop Loss | Target |
|---|---|---|---|
| EL | Long from L₀+ε | L₀−ε | H₁ + ΔH |
| HL-FD1 | Long from L₂ | L₂−ε | H₁ + ΔH |
| HL-FD2 | Long at confirmation | L₂−ε | Prior H + ΔH |
| HL-FD3 | Reduced size; wait | L₂−ε | Support + ΔH |
| HL-FD4 | Extreme risk; avoid | L₂−ε | Critical |
| LL-FD1 | Short spike trade | L₂+spike | L₀ |
| LL-FD2 | Wait for reversal | Breakout | L₀ |
| LL-FD3 | Short continuation | Reversal | New lows |
| LL-FD4 | Crisis mode; hedge | Capitulation | TBD |
Optional Sub-Classification Flags
Attach to any variant for richer context:
| Flag | Meaning | Signal |
|---|---|---|
| +C | Any close < L₀ between H₁→L₂ | Stronger bearish commitment |
| +S | L₂ occurs in single bar (spike) | Sharp reversal; mean reversion |
| +X | Wicks below L₀ only, no close | Liquidity grab; false break |
Example labels: HL-FD2+C, LL-FD4+S+C, EL+X
---
Part 2: Three-Pivot Patterns (UP-DOWN-UP)
Pattern: L₀ → H₁ → L₂ → H₃ (up-leg, down-leg, up-leg)
9 Exhaustive Variants
Two independent dimensions × 3 values each = 3×3 = 9 mutually exclusive, collectively exhaustive variants.
Dimension 1 — L₂ vs L₀: {HL, EL, LL} Dimension 2 — H₃ vs H₁: {HH, EH, LH}
| # | L₂ vs L₀ | H₃ vs H₁ | Name | Market Regime |
|---|---|---|---|---|
| 1 | HL | HH | Continuation impulse | Bull trend continuation |
| 2 | HL | EH | Double-top test | Range, bullish bias |
| 3 | HL | LH | Triangle compression | Neutral consolidation |
| 4 | EL | HH | Range break up | Bullish transition |
| 5 | EL | EH | Rectangle | Balanced range |
| 6 | EL | LH | Lower-high at flat base | Range, bearish bias |
| 7 | LL | HH | V-reversal / spring | Bullish reversal |
| 8 | LL | EH | Undercut then stall | Volatile range |
| 9 | LL | LH | Rally failure | Bear trend continuation |
Exhaustiveness Proof
All variants satisfy these mandatory constraints:
- L₀ < H₁ (first uptrend exists)
- L₂ < H₁ (retracement doesn't exceed peak)
- H₃ > L₂ (second uptrend exists)
L₂ has exactly 3 relationships to L₀ (higher, equal, lower). H₃ has exactly 3 relationships to H₁ (higher, equal, lower). These dimensions are independent — no constraint eliminates any combination. Therefore 3 × 3 = 9 variants, all feasible, none missing.
Natural Groupings
- Bullish (4): HL+HH, EL+HH, HL+EH, LL+HH
- Neutral (3): HL+LH, EL+EH, LL+EH
- Bearish (2): EL+LH, LL+LH
Regime Assignments
| Regime | Variants |
|---|---|
| Trend Continuation | HL+HH (bull), LL+LH (bear) |
| Range Consolidation | HL+LH, EL+EH, LL+EH |
| Bullish Transition | EL+HH, HL+EH |
| Bearish Transition | EL+LH |
| Reversal | LL+HH |
---
Part 3: 27-Way Extension
Adding H₃ vs L₀ as a third independent dimension:
- L₂ vs L₀: {HL, EL, LL}
- H₃ vs H₁: {HH, EH, LH}
- H₃ vs L₀: {Above, Equal, Below}
This yields 3×3×3 = 27 sub-variants. Some are mathematically impossible due to constraints (e.g., HL+LH+H₃<L₀ requires H₃ < L₀ < L₂ < H₃, a contradiction).
Analysts often simplify this third dimension to a binary: "reclaims L₀" vs "fails to reclaim L₀".
---
The Epsilon Tolerance Band
The tolerance band ε determines what "equal" means. It adapts to volatility and microstructure.
Core Formula
ε = min(ε_max, max(ε_min, √[(a·S)² + (b·ATR₁₄)²]))Where:
- S = rolling median spread (bid-ask)
- ATR₁₄ = 14-bar Average True Range
- a = 2.0 (spread scaling)
- b = 0.05 (M5-M30) or 0.07 (H1-D1)
Bounds (EURUSD defaults)
ε_min = max(3 ticks, 1×S) = max(0.00003, S)
ε_max = min(5 pips, 0.20 × swing) = min(0.00050, 0.20 × W)Classification Using ε
ε_r = ε / W (relative tolerance)
EL if |z| ≤ ε_r
HL if z > ε_r
LL if z < −ε_rPractical Fallbacks
If bid-ask spread unavailable:
- ATR-only: ε = 0.05 × ATR₁₄ (M5-M30), 0.07 × ATR₁₄ (H1-D1)
- Fixed band: ε = 3 pips (intraday), 5 pips (swing), 10 pips (daily)
For complete worked examples with sensitivity analysis, read references/epsilon-tolerance-detail.md.
---
Freedman-Diaconis Binning
The FD rule computes statistically optimal bin edges from data:
h = 2 × IQR(X) × n^(-1/3) (bin width)
K = clip(⌈(max−min) / h⌉, 3, 6) (number of bins)
edges = linspace(min, max, K+1)Procedure for HL Patterns
1. Collect 2-3 years of UP-DOWN triplets 2. Filter to HL (z > ε_r) 3. Winsorize z at 0.5%-99.5% 4. Compute FD bin edges on z ∈ (ε_r, 1) 5. Label: HL-FD1 (shallowest) through HL-FDK (deepest)
Procedure for LL Patterns
1. Filter to LL (z < −ε_r) 2. Compute o = (L₀ − L₂) / ATR₁₄ 3. Winsorize o at 0.5%-99.5% 4. Compute FD bin edges on o ∈ (ε_r, ∞) 5. Label: LL-FD1 (micro) through LL-FDK (extreme)
Recompute bin edges monthly or quarterly to track regime drift. Fall back to quantile binning if sample < 400.
---
Implementation Reference
The qta Rust crate (crates/qta/) implements the core ZigZag state machine that produces the pivots consumed by this classification framework:
ZigZagConfig::new(reversal_depth, epsilon_multiplier, bar_threshold_dbps)ZigZagState::process_bar(&bar) → ZigZagOutputwithcompleted_segmentcontainingbase_class(EL/HL/LL) andzscore- The crate computes τ and ε dynamically per pivot from
bar_threshold_dbps
The classification framework in this skill extends the crate's output with FD-binning, three-pivot analysis, and market regime labeling.
---
Deep Reference Files
For ASCII visualizations, worked examples, and implementation pseudocode, read these reference files as needed:
| File | Contents |
|---|---|
references/notation-definitions.md | Single source of truth: all symbols, formulas, abbreviations, pattern classes |
references/two-pivot-variants.md | All 9 two-pivot ASCII diagrams, trading rules, flag examples |
references/three-pivot-variants.md | All 9 three-pivot ASCII diagrams, 27-way extension, HL+LH granular sub-variants |
references/epsilon-tolerance-detail.md | Complete ε formula, worked calculation, sensitivity analysis, pseudocode |
references/binning-methodology.md | Freedman-Diaconis algorithm details, FD vs quantile comparison, worked example |
references/data-pipeline.md | 11-step end-to-end pipeline: raw quotes → OHLC → ATR → pivots → classify → output |
references/eurusd-validation-scenarios.md | 3 worked market scenarios (Normal, Volatile, Crash) validating ε and τ |
Post-Execution Reflection
After this skill completes, check before closing:
1. Did the command succeed? — If not, fix the instruction or error table that caused the failure. 2. Did parameters or output change? — If the underlying tool's interface drifted, update Usage examples and Parameters table to match. 3. Was a workaround needed? — If you had to improvise (different flags, extra steps), update this SKILL.md so the next invocation doesn't need the same workaround.
Only update if the issue is real and reproducible — not speculative.
Binning Methodology: Freedman–Diaconis for FD-Binned Variants
Complete specification for computing statistically grounded bin edges using the Freedman–Diaconis rule, enabling granular UP–DOWN pattern classification.
Why Freedman–Diaconis?
| Criterion | FD | Fixed Quantiles | Manual Thresholds |
|---|---|---|---|
| Automatic | ✓ | ✓ | ✗ (requires expert tuning) |
| Robust to outliers | ✓ (IQR-based) | ✓ | ✗ (sensitive to extremes) |
| Stable counts | ✓ | ✓ | ✗ (volatile bin sizes) |
| Interpretable | ✓ | ✓ | ✓ |
| Theoretically grounded | ✓ (asymptotic convergence) | ✗ | ✗ |
| No external parameters | ✓ | ✗ (K fixed) | ✗ (manual) |
Recommendation: Use FD as primary; fall back to quantiles if sample < 400.
---
Freedman–Diaconis Rule
Bin Width Formula
h = 2 × IQR(X) × n^(-1/3)Where:
- IQR(X) = Interquartile range (Q₃ − Q₁)
- n = Sample size
- h = Optimal bin width
Number of Bins
K = ⌈(max(X) - min(X)) / h⌉Clamp to reasonable range:
K ∈ [3, 6] (typically 3–5 for trading data)Bin Edges
Create uniform edges over observed range:
edges = linspace(min(X), max(X), K+1)
bins = [min:edges[1], edges[1]:edges[2], ..., edges[K-1]:max]---
Application: UP–DOWN Variants
For HL (Higher Low) Patterns
Normalized coordinate:
z = (L₂ - L₀) / (H₁ - L₀) ∈ (0, 1)Reference: `notation-definitions.md#normalized-retracement-coordinate`.
Procedure:
1. Collect all UP–DOWN triplets from rolling history (2–3 years). 2. Classify as HL if z > ε_r. 3. Extract subset Z = {z_i : z_i > ε_r}. 4. Winsorize Z at 0.5%–99.5% to remove extreme outliers. 5. Compute IQR(Z_winsor). 6. Calculate h = 2 × IQR(Z) × |Z|^(-1/3). 7. Determine K = clip(⌈(max Z − min Z) / h⌉, 3, 6). 8. Create uniform edges on [min Z, max Z] with K bins. 9. Label bins HL-FD1 (shallowest), HL-FD2, ..., HL-FDK (deepest).
For LL (Lower Low) Patterns
Normalized coordinate (overshoot depth):
o = -z = (L₀ - L₂) / ATR₁₄ ∈ (0, ∞)Reference: `notation-definitions.md#volatility-normalized-overshoot`.
Procedure:
1. Collect all UP–DOWN triplets. 2. Classify as LL if z < −ε_r (equivalently, o > ε_r). 3. Extract subset O = {o_i : o_i > ε_r}. 4. Winsorize O at 0.5%–99.5%. 5. Compute IQR(O_winsor). 6. Calculate h = 2 × IQR(O) × |O|^(-1/3). 7. Determine K = clip(⌈(max O − min O) / h⌉, 3, 6). 8. Create uniform edges on [min O, max O] with K bins. 9. Label bins LL-FD1 (micro undercut), LL-FD2, ..., LL-FDK (extreme undercut).
For EL (Equal Low) Patterns
No binning: Single class if |z| ≤ ε_r.
---
Example Calculation
Scenario: H1 EURUSD, Rolling 3-Year Window
Input Data:
- 1,500 UP–DOWN triplets collected over 3 years of H1 data
- ε_r computed for each triplet (see epsilon-tolerance.md)
- Roughly 60% are HL, 20% are EL, 20% are LL
HL Binning
Step 1: Extract HL subset
Z_raw = {z_i for all triplets where z_i > ε_r_i}
|Z_raw| = 900 tripletsStep 2: Winsorize
Z_0.5% ≈ 0.05 (5% retrace)
Z_99.5% ≈ 0.95 (95% retrace)
Z_winsor = clip(Z_raw, 0.05, 0.95)
|Z_winsor| = 880 triplets (20 removed as outliers)Step 3: Calculate IQR
Q1 = 0.20 (25th percentile: 20% retrace)
Q3 = 0.60 (75th percentile: 60% retrace)
IQR(Z) = 0.60 − 0.20 = 0.40Step 4: Compute bin width
h = 2 × 0.40 × 880^(-1/3)
= 0.80 × 0.0968
≈ 0.0774Step 5: Determine K
Range = max(Z) − min(Z) ≈ 0.95 − 0.05 = 0.90
K_raw = ⌈0.90 / 0.0774⌉ = ⌈11.6⌉ = 12
K = clip(12, 3, 6) = 6 (cap at 6 for readability)Step 6: Create edges
edges = linspace(0.05, 0.95, K+1)
= [0.05, 0.20, 0.35, 0.50, 0.65, 0.80, 0.95]Step 7: Bin labels
HL-FD1: z ∈ [0.80, 0.95] (shallow; 20–25% of HL)
HL-FD2: z ∈ [0.65, 0.80] (mid-upper; 18–22% of HL)
HL-FD3: z ∈ [0.50, 0.65] (mid-lower; 17–21% of HL)
HL-FD4: z ∈ [0.35, 0.50] (deep; 15–20% of HL)
HL-FD5: z ∈ [0.20, 0.35] (deep; 12–17% of HL)
HL-FD6: z ∈ [0.05, 0.20] (nearly complete; 8–12% of HL)LL Binning
Step 1: Extract LL subset
O_raw = {−z_i for all triplets where z_i < −ε_r_i}
|O_raw| = 300 tripletsStep 2: Winsorize
O_0.5% ≈ 0.05 (depth of 5% normalized by ATR)
O_99.5% ≈ 2.50 (depth of 250% normalized by ATR, extreme)
O_winsor = clip(O_raw, 0.05, 2.50)
|O_winsor| = 290 triplets (10 removed as outliers)Step 3: Calculate IQR
Q1 = 0.15 (25th percentile)
Q3 = 0.60 (75th percentile)
IQR(O) = 0.60 − 0.15 = 0.45Step 4: Compute bin width
h = 2 × 0.45 × 290^(-1/3)
= 0.90 × 0.0675
≈ 0.0608Step 5: Determine K
Range = max(O) − min(O) ≈ 2.50 − 0.05 = 2.45
K_raw = ⌈2.45 / 0.0608⌉ = ⌈40.3⌉ = 40
K = clip(40, 3, 6) = 6 (cap at 6)Step 6: Create edges
edges = linspace(0.05, 2.50, K+1)
= [0.05, 0.45, 0.85, 1.25, 1.65, 2.05, 2.50]Step 7: Bin labels
LL-FD1: o ∈ [0.05, 0.45] (micro; 40–50% of LL)
LL-FD2: o ∈ [0.45, 0.85] (shallow; 20–25% of LL)
LL-FD3: o ∈ [0.85, 1.25] (deep; 10–15% of LL)
LL-FD4: o ∈ [1.25, 1.65] (deeper; 5–10% of LL)
LL-FD5: o ∈ [1.65, 2.05] (extreme; 2–5% of LL)
LL-FD6: o ∈ [2.05, 2.50] (catastrophic; <2% of LL)---
Practical Considerations
Sample Size Requirements
| Minimum N | Confidence | Recommendation |
|---|---|---|
| < 100 | Low | Use quantiles or fixed bins |
| 100–400 | Moderate | FD works but use K ∈ [3,4] |
| 400–2000 | Good | FD recommended |
| > 2000 | Excellent | FD reliable; can use K ∈ [5,6] |
For EURUSD: 2–3 years of daily to hourly data typically yields > 1000 triplets per timeframe → FD is appropriate.
Winsorization Strategy
Clip extreme outliers at 0.5%–99.5% to:
- Reduce FD sensitivity to tail events
- Maintain reasonable bin widths
- Preserve representativeness of typical market conditions
Refitting Schedule
Recompute bin edges periodically to track regime drift:
| Timeframe | Refit Frequency | Reason |
|---|---|---|
| M5–M15 | Monthly | Higher turnover; faster regime change |
| M30–H4 | Quarterly | Moderate drift |
| D1 | Quarterly–Semi-annually | Slower regime change |
Stability Test
If refit produces significantly different K or edges, investigate:
1. Market regime shift (trending → ranging or vice versa) 2. Volatility regime change 3. Seasonality effects 4. Data quality issues
---
Fallback: Quantile-Based Binning
If FD results are unstable or sample is small, use fixed quantiles:
HL quantiles: 20%, 40%, 60%, 80% → 4 bins (HL-Q1, HL-Q2, HL-Q3, HL-Q4)
LL quantiles: 20%, 40%, 60%, 80% → 4 bins (LL-Q1, LL-Q2, LL-Q3, LL-Q4)Each quantile bin contains ~25% of observations; simpler but less optimal than FD.
---
Implementation Pseudocode
def compute_fd_bins(X, min_K=3, max_K=6):
"""
Compute Freedman–Diaconis bin edges.
Args:
X: Array of normalized values (e.g., z for HL or o for LL)
min_K, max_K: Bounds on bin count
Returns:
edges: Array of bin edges [min, edge1, edge2, ..., max]
K: Number of bins
"""
X_array = np.array(X)
n = len(X_array)
# Winsorize at 0.5% and 99.5%
X_winsor = np.clip(X_array, np.percentile(X_array, 0.5),
np.percentile(X_array, 99.5))
# Compute IQR and bin width
Q1, Q3 = np.percentile(X_winsor, [25, 75])
IQR = Q3 - Q1
if IQR == 0:
# All values are equal; use quantile bins
edges = np.percentile(X_array, [0, 20, 40, 60, 80, 100])
return edges, min(5, max_K)
h = 2 * IQR * (n ** (-1/3))
K_raw = int(np.ceil((np.max(X_winsor) - np.min(X_winsor)) / h))
K = np.clip(K_raw, min_K, max_K)
# Create uniform edges
edges = np.linspace(np.min(X_array), np.max(X_array), K + 1)
return edges, K
def classify_triplet(L0, H1, L2, epsilon_r, fd_edges_hl, fd_edges_ll):
"""
Classify an UP–DOWN triplet into FD-binned variant.
Args:
L0, H1, L2: Pivot prices
epsilon_r: Relative tolerance band
fd_edges_hl: FD bin edges for HL
fd_edges_ll: FD bin edges for LL
Returns:
variant: String label (e.g., 'HL-FD2', 'LL-FD3', 'EL')
"""
z = (L2 - L0) / (H1 - L0) if H1 > L0 else 0.0
if abs(z) <= epsilon_r:
return 'EL'
elif z > epsilon_r:
# HL: find which bin
for i, (low, high) in enumerate(zip(fd_edges_hl[:-1], fd_edges_hl[1:])):
if low <= z < high or (i == len(fd_edges_hl) - 2 and z == high):
return f'HL-FD{i+1}'
return 'HL-FD1' # Fallback
else:
# LL: compute overshoot and find bin
o = -z
for i, (low, high) in enumerate(zip(fd_edges_ll[:-1], fd_edges_ll[1:])):
if low <= o < high or (i == len(fd_edges_ll) - 2 and o == high):
return f'LL-FD{i+1}'
return 'LL-FD1' # Fallback---
Validation Checklist
- [ ] Sample size N ≥ 400 for each (HL, LL)
- [ ] IQR > 0 (avoid degenerate case of zero variance)
- [ ] Winsorization removes <5% of data (outliers only)
- [ ] K ∈ [3, 6] (readable bin count)
- [ ] Bin edges monotonically increasing
- [ ] Each bin contains ≥5 observations
- [ ] Refit monthly; compare edges to prior month (should be stable)
- [ ] Backtest: returns per bin should show monotonic trend
---
References
- Freedman, D. & Diaconis, P. (1981). "On the histogram as a density estimator." _Zeitschrift für Wahrscheinlichkeitstheorie und Verwandte Gebiete_.
- Sturges' Rule (alternative): K ≈ ⌈log₂(n)⌉; coarser bins, simpler formula.
- Scott's Rule (alternative): h = 3.49 × σ × n^(-1/3); sensitive to σ estimate.
---
Last Updated: 2025-10-22 Context: UP–DOWN pattern binning; 2–3 year rolling history; EURUSD typical.
Data Pipeline: End-to-End ZigZag Classification System
Complete specification of the data pipeline from rolling historical EURUSD quotes to ZigZag-derived regime labels and pattern classifications.
Pipeline Overview
Raw Quotes (bid/ask)
↓
[1] Clean & Align
↓
[2] Build OHLC & Compute Spreads
↓
[3] Compute Volatility (ATR) & Noise Stats
↓
[4] Determine ZigZag Thresholds (τ)
↓
[5] Detect ZigZag Pivots
↓
[6] Form UP–DOWN Triplets
↓
[7] Compute Tolerance Band (ε)
↓
[8] Classify: EL / HL / LL (base classes)
↓
[9] Compute FD Bin Edges (rolling window)
↓
[10] Assign FD-Binned Variant Labels
↓
[11] Output: Labeled Segments + Features---
Detailed Pipeline Steps
[1] Clean & Align Quotes
Input: Raw bid/ask time series with timestamps (microsecond precision preferred).
Operations:
- Drop records with NaN, Inf, or missing values.
- Remove crossed quotes (ask < bid).
- De-duplicate timestamps; keep first occurrence.
- Forward-fill micro gaps (≤500 ms) if needed.
- Verify no large timestamp jumps (detect session breaks).
Output: Clean, aligned quote stream {t, bid_t, ask_t}.
def clean_quotes(quotes_df):
"""Remove bad ticks, align timestamps."""
df = quotes_df.dropna()
df = df[df['ask'] >= df['bid']]
df = df.drop_duplicates(subset=['timestamp'], keep='first')
df['mid'] = (df['bid'] + df['ask']) / 2
df['spread'] = df['ask'] - df['bid']
return df.sort_values('timestamp')---
[2] Build OHLC & Compute Spreads
Input: Clean quote stream.
Operations:
- Resample quotes to target timeframes (M5, M15, M30, H1, H4, D1).
- For each bar:
- High = max(ask) over bar
- Low = min(bid) over bar
- Open/Close = first/last mid price
- Volume = tick count (if available)
- Spread = median(spread) over bar
Output: OHLCV with spread per timeframe.
def build_ohlc(quotes_df, timeframe='H1'):
"""Resample quotes to OHLC bars."""
quotes_df['time'] = quotes_df['timestamp'].dt.floor(timeframe)
ohlc = quotes_df.groupby('time').agg({
'ask': 'max', # High
'bid': 'min', # Low
'mid': ['first', 'last'], # Open, Close
'spread': 'median'
})
ohlc.columns = ['high', 'low', 'open', 'close', 'spread']
return ohlc---
[3] Compute Volatility & Noise Stats
Input: OHLC bars per timeframe.
Operations:
- Calculate ATR₁₄ (14-bar Average True Range).
- Calculate rolling median spread over ±k bars (k=3–5).
- Identify swing filter threshold: keep only swings Δ ≥ 5×S.
Output: ATR, local spread, volatility-adjusted thresholds.
def compute_volatility(ohlc):
"""Calculate ATR and rolling spread."""
ohlc['tr'] = np.maximum(
ohlc['high'] - ohlc['low'],
np.maximum(
abs(ohlc['high'] - ohlc['close'].shift()),
abs(ohlc['low'] - ohlc['close'].shift())
)
)
ohlc['atr14'] = ohlc['tr'].rolling(14).mean()
ohlc['spread_rolling'] = ohlc['spread'].rolling(5, center=True).median()
return ohlc---
[4] Determine ZigZag Thresholds
Input: ATR, rolling spreads, swing magnitudes.
Operations: Calculate dynamic ZigZag reversal threshold τ:
τ = min(τ_max, max(τ_min, c_S × S, c_A × ATR₁₄))Constants (EURUSD):
- c_S = 3 (spread scaling)
- c_A = 0.9 (intraday), 1.1 (H1 and higher)
- τ_min = 0.00030 (3 pips)
- τ_max = min(0.0030, 0.35 × swing)
Output: Dynamic threshold τ per bar.
def compute_zigzag_threshold(atr14, spread_rolling, c_S=3, c_A=0.9):
"""Compute reversal threshold τ."""
tau_base = np.maximum(c_S * spread_rolling, c_A * atr14)
tau_min = 0.00030
tau_max = 0.0030
tau = np.clip(tau_base, tau_min, tau_max)
return tau---
[5] Detect ZigZag Pivots
Input: OHLC highs/lows, threshold τ.
Operations:
- Initialize from first bar; track current leg (up/down).
- For each new bar:
- If in uptrend: update running max H; if price reverses by ≥τ from H, confirm pivot.
- If in downtrend: update running min L; if price reverses by ≥τ from L, confirm pivot.
- Confirm pivot only when reversal is confirmed (no repainting).
Output: Sequence of pivots {t_i, p_i, type_i ∈ {L, H}}.
def detect_zigzag_pivots(ohlc, tau_col='tau'):
"""Identify ZigZag pivots (no repainting)."""
pivots = []
leg_type = 'UP' # Start with UP
leg_extreme = ohlc['high'].iloc[0]
leg_extreme_idx = 0
for i in range(1, len(ohlc)):
if leg_type == 'UP':
# Uptrend: track high
if ohlc['high'].iloc[i] > leg_extreme:
leg_extreme = ohlc['high'].iloc[i]
leg_extreme_idx = i
# Check for reversal
elif leg_extreme - ohlc['low'].iloc[i] >= ohlc[tau_col].iloc[i]:
pivots.append({'time': ohlc.index[leg_extreme_idx],
'price': leg_extreme, 'type': 'H'})
leg_type = 'DOWN'
leg_extreme = ohlc['low'].iloc[i]
leg_extreme_idx = i
else:
# Downtrend: track low
if ohlc['low'].iloc[i] < leg_extreme:
leg_extreme = ohlc['low'].iloc[i]
leg_extreme_idx = i
# Check for reversal
elif ohlc['high'].iloc[i] - leg_extreme >= ohlc[tau_col].iloc[i]:
pivots.append({'time': ohlc.index[leg_extreme_idx],
'price': leg_extreme, 'type': 'L'})
leg_type = 'UP'
leg_extreme = ohlc['high'].iloc[i]
leg_extreme_idx = i
return pd.DataFrame(pivots)---
[6] Form UP–DOWN Triplets
Input: Pivot sequence from step [5].
Operations:
- Slide a 3-element window over pivots with pattern L-H-L.
- Extract features:
- Prices: L₀, H₁, L₂
- Times: t_L₀, t_H₁, t_L₂
- Swing: W = H₁ − L₀
- Spreads at pivots: S_L₀, S_H₁, S_L₂
- ATR₁₄ at H₁ and L₂
- Durations: t_up = t_H₁ − t_L₀, t_down = t_L₂ − t_H₁
- Flags: C (close<L₀?), S (spike?)
Output: Triplet records with features.
def form_triplets(pivots, ohlc_indexed):
"""Form L0-H1-L2 triplets."""
triplets = []
for i in range(len(pivots) - 2):
if pivots['type'].iloc[i] == 'L' and \
pivots['type'].iloc[i+1] == 'H' and \
pivots['type'].iloc[i+2] == 'L':
L0 = pivots['price'].iloc[i]
H1 = pivots['price'].iloc[i+1]
L2 = pivots['price'].iloc[i+2]
t_L0 = pivots['time'].iloc[i]
t_H1 = pivots['time'].iloc[i+1]
t_L2 = pivots['time'].iloc[i+2]
# Features
W = H1 - L0
t_up = (t_H1 - t_L0).total_seconds()
t_down = (t_L2 - t_H1).total_seconds()
# Spreads and ATR
S_L0 = ohlc_indexed.loc[t_L0, 'spread_rolling']
S_H1 = ohlc_indexed.loc[t_H1, 'spread_rolling']
S_L2 = ohlc_indexed.loc[t_L2, 'spread_rolling']
atr_H1 = ohlc_indexed.loc[t_H1, 'atr14']
atr_L2 = ohlc_indexed.loc[t_L2, 'atr14']
triplets.append({
'segment_id': f"{t_L0.date()}-{i}",
't_L0': t_L0, 't_H1': t_H1, 't_L2': t_L2,
'L0': L0, 'H1': H1, 'L2': L2,
'W': W, 't_up': t_up, 't_down': t_down,
'S_L0': S_L0, 'S_H1': S_H1, 'S_L2': S_L2,
'atr_H1': atr_H1, 'atr_L2': atr_L2
})
return pd.DataFrame(triplets)---
[7] Compute Tolerance Band (ε)
Input: Triplet features (spreads, ATR, swing).
Operations: Apply ε formula per triplet.
See [epsilon-tolerance.md](epsilon-tolerance.md) for complete formula specification, EURUSD defaults, and examples.
See [notation-definitions.md#tolerance-band-ε](notation-definitions.md#tolerance-band-ε) for quick reference.
Output: ε and ε_r per triplet.
def compute_epsilon(triplets, b=0.05):
"""Compute tolerance band."""
S = triplets[['S_L0', 'S_H1', 'S_L2']].median(axis=1)
atr = triplets['atr_H1']
eps_raw = np.sqrt((2 * S)**2 + (b * atr)**2)
eps_min = np.maximum(0.00003, S)
eps_max = np.minimum(0.00050, 0.20 * triplets['W'])
triplets['eps'] = np.clip(eps_raw, eps_min, eps_max)
triplets['eps_r'] = triplets['eps'] / triplets['W']
return triplets---
[8] Classify Base Classes (EL / HL / LL)
Input: Triplets with ε, ε_r.
Operations: For each triplet, compute normalized coordinate:
z = (L₂ - L₀) / (H₁ - L₀)See `notation-definitions.md#normalized-retracement-coordinate` for canonical definition.
Classify:
- EL if |z| ≤ ε_r
- HL if z > ε_r
- LL if z < −ε_r
Output: Base class label per triplet.
def classify_base(triplets):
"""Classify EL / HL / LL."""
z = (triplets['L2'] - triplets['L0']) / triplets['W']
triplets['z'] = z
triplets['base_class'] = 'UNDEFINED'
triplets.loc[np.abs(z) <= triplets['eps_r'], 'base_class'] = 'EL'
triplets.loc[z > triplets['eps_r'], 'base_class'] = 'HL'
triplets.loc[z < -triplets['eps_r'], 'base_class'] = 'LL'
return triplets---
[9] Compute FD Bin Edges (Rolling)
Input: Historical triplets with z values and base_class labels.
Operations: Per timeframe, on rolling 2–3 year window:
1. Separate HL and LL populations:
- Z_HL = {z_i : base_class_i = 'HL'}
- Z_LL = {z_i : base_class_i = 'LL'}
2. For each population:
- Winsorize at 0.5%–99.5%
- Compute FD bin width h
- Determine K ∈ [3, 6]
- Create uniform edges
3. Store edges for scoring new triplets.
Output: Bin edge arrays edges_hl, edges_ll per timeframe.
Implementation: See binning-methodology.md § Implementation Pseudocode for the complete compute_fd_bins() function with detailed documentation.
---
[10] Assign FD-Binned Labels
Input: Triplets with z values; FD edges from step [9].
Operations: For each triplet:
- If base_class = 'EL': label = 'EL'
- If base_class = 'HL': find bin index of z in edges_hl → label = f'HL-FD{k}'
- If base_class = 'LL':
- Compute o = -z / ATR₁₄
- Find bin index of o in edges_ll → label = f'LL-FD{j}'
Add optional flags:
- +C if any close < L₀ between H₁ → L₂
- +S if L₂ occurs in single bar (spike)
Output: FD-binned variant label per triplet.
def assign_fd_labels(triplets, edges_hl, edges_ll):
"""Assign FD-binned variant labels."""
labels = []
for idx, row in triplets.iterrows():
if row['base_class'] == 'EL':
label = 'EL'
elif row['base_class'] == 'HL':
for k, (lo, hi) in enumerate(zip(edges_hl[:-1], edges_hl[1:])):
if lo <= row['z'] <= hi:
label = f'HL-FD{k+1}'
break
elif row['base_class'] == 'LL':
o = -row['z']
for j, (lo, hi) in enumerate(zip(edges_ll[:-1], edges_ll[1:])):
if lo <= o <= hi:
label = f'LL-FD{j+1}'
break
# Add flags
if row.get('close_below_L0', False):
label += '+C'
if row.get('spike', False):
label += '+S'
labels.append(label)
triplets['variant'] = labels
return triplets---
[11] Output: Labeled Segments
Input: Triplets with all features, classes, variants.
Operations:
- Deduplicate segment_id
- Sort by time
- Add derived fields:
regime= regime label (Retest, Pullback, Undercut, ...)retrace_pct= 1 − z (% of swing retraced)overshot_pct= o × ATR₁₄ / W (% overshooting L₀)
Output: Segments table ready for analysis/backtest.
def finalize_segments(triplets):
"""Finalize output table."""
segments = triplets[[
'segment_id', 't_L0', 't_H1', 't_L2',
'L0', 'H1', 'L2', 'W',
'S_L0', 'S_H1', 'S_L2', 'eps', 'eps_r',
'z', 'atr_H1', 'atr_L2',
't_up', 't_down',
'base_class', 'variant'
]].copy()
segments['retrace_pct'] = 1 - segments['z']
segments['regime'] = segments['variant'].apply(infer_regime)
return segments.sort_values('t_L0')
def infer_regime(variant_label):
"""Map variant to regime."""
if 'EL' in variant_label:
return 'Retest'
elif 'HL' in variant_label:
return 'Pullback'
elif 'LL' in variant_label:
return 'Undercut'
else:
return 'Unknown'---
Complete Pipeline Pseudocode
def full_pipeline(quotes_df, timeframes=['M5', 'H1', 'D1'], lookback_years=3):
"""End-to-end ZigZag classification pipeline."""
# Step 1: Clean
quotes_clean = clean_quotes(quotes_df)
results = []
for tf in timeframes:
# Step 2: Build OHLC
ohlc = build_ohlc(quotes_clean, tf)
# Step 3: Volatility
ohlc = compute_volatility(ohlc)
# Step 4: ZigZag threshold
ohlc['tau'] = compute_zigzag_threshold(ohlc['atr14'], ohlc['spread_rolling'])
# Step 5: Detect pivots
pivots = detect_zigzag_pivots(ohlc)
# Step 6: Form triplets
triplets = form_triplets(pivots, ohlc)
# Step 7: Tolerance band
triplets = compute_epsilon(triplets)
# Step 8: Base classification
triplets = classify_base(triplets)
# Step 9: FD bins (rolling window)
# Note: compute_fd_bins() implementation in binning-methodology.md § Implementation Pseudocode
z_hl = triplets[triplets['base_class'] == 'HL']['z'].values
z_ll = -triplets[triplets['base_class'] == 'LL']['z'].values
edges_hl, K_hl = compute_fd_bins(z_hl)
edges_ll, K_ll = compute_fd_bins(z_ll)
# Step 10: FD labels
triplets = assign_fd_labels(triplets, edges_hl, edges_ll)
# Step 11: Finalize
segments = finalize_segments(triplets)
segments['timeframe'] = tf
results.append(segments)
# Combine all timeframes
all_segments = pd.concat(results, ignore_index=True)
all_segments = all_segments.sort_values('t_L0')
return all_segments---
Data Quality Checks
Before running pipeline, verify:
- [ ] Quotes cover at least 2–3 years
- [ ] No large gaps (>1 hour) in quote stream
- [ ] Bid ≤ Ask (no crossed quotes)
- [ ] Spread > 0 (no phantom liquidity)
- [ ] ATR₁₄ > 0 (adequate volatility data)
- [ ] Pivot count > 100 per triplet class (sufficient sample)
---
Output Storage
Recommended database schema:
CREATE TABLE segments (
segment_id VARCHAR(50) PRIMARY KEY,
timeframe VARCHAR(5),
t_L0 TIMESTAMP,
t_H1 TIMESTAMP,
t_L2 TIMESTAMP,
L0 FLOAT,
H1 FLOAT,
L2 FLOAT,
W FLOAT,
z FLOAT,
eps FLOAT,
eps_r FLOAT,
base_class VARCHAR(10),
variant VARCHAR(20),
regime VARCHAR(20),
atr_H1 FLOAT,
atr_L2 FLOAT,
t_up INT,
t_down INT,
retrace_pct FLOAT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);---
Performance Monitoring
Track pipeline health:
| Metric | Target | Alert |
|---|---|---|
| Pivot detection rate | 0.5–1.0 per day per TF | <0.3 or >1.5 |
| EL fraction | 20–30% | <15% or >40% |
| HL fraction | 50–65% | <40% or >75% |
| LL fraction | 10–20% | <5% or >25% |
| Avg episode duration | 1–10 bars | <0.5 or >20 |
| FD bin stability | K±1 month-to-month | Change >1 |
---
References
- See epsilon-tolerance.md for ε formula details
- See binning-methodology.md for FD edge computation
- See variants-updown.md for variant interpretation
---
Last Updated: 2025-10-22 Context: EURUSD forex; multiple timeframes; 2–3 year rolling history; no volume data required.
Epsilon Tolerance Band: "Equal" Price Level Definition
Complete specification for computing the tolerance band ε used to classify "equal" pivot levels (EL/EH) in ZigZag pattern analysis.
Motivation
Problem: When are two price levels "equal"?
- Absolute difference (e.g., 1 pip) is scale-dependent and brittle.
- Relative percentage is noisy on small swings.
- Solution: Use a volatility- and microstructure-aware band that scales to both market conditions and instrument characteristics.
Core Formula
ε = min(ε_max, max(ε_min, √[(a·S)² + (b·ATR₁₄)²]))Components
| Symbol | Meaning | Units | Notes |
|---|---|---|---|
| S | Rolling median spread (bid–ask) | price | Microstructure noise |
| ATR₁₄ | 14-bar Average True Range | price | Volatility proxy |
| a | Spread scaling constant | dimensionless | Typically 2.0 |
| b | ATR scaling constant | dimensionless | Timeframe-dependent; 0.05–0.07 |
| ε_min | Floor (minimum tolerance) | price | Prevents collapsing below noise |
| ε_max | Ceiling (maximum tolerance) | price | Prevents absorbing trend moves |
Default Parameters for EURUSD
Constants
- Tick: 0.00001 (smallest price unit)
- Pip: 0.00010 (10 ticks; standard reporting unit)
- a: 2.0 (universal)
Timeframe-Dependent Coefficients
| Timeframe | b | Rationale |
|---|---|---|
| M5–M30 | 0.05 | Tighter bands; less volatile |
| H1–H4 | 0.07 | Moderate volatility; wider bands |
| D1 | 0.07 | Daily swings; comparable to H4 |
Bounds
ε_min = max(3 ticks, 1 × S)
= max(0.00003, S)
ε_max = min(0.00050, 0.20 × swing)
= min(5 pips, 20% of swing magnitude)Where swing = |H₁ - L₀| (for high-low classification) or |H₃ - L₂| (for other legs).
Computation Procedure
Step 1: Collect Spreads
At each pivot time (t_L₀, t_H₁, t_L₂):
- Extract best-ask − best-bid for that bar.
- Compute rolling median over ±k bars (k=3–5).
- Take median of the three pivot spreads: S = median(S_L₀, S_H₁, S_L₂).
S_L0 = rolling_median(ask - bid, at t_L0, window=3)
S_H1 = rolling_median(ask - bid, at t_H1, window=3)
S_L2 = rolling_median(ask - bid, at t_L2, window=3)
S = median([S_L0, S_H1, S_L2])Step 2: Compute ATR₁₄
On the same timeframe as ZigZag (e.g., H1), calculate standard 14-bar ATR:
ATR14 = atr(high, low, close, period=14)Use the ATR value at H₁ time (or average of H₁ and L₂ if more recent).
Step 3: Calculate ε
Apply the formula:
variance = (a * S)**2 + (b * ATR14)**2
epsilon_raw = sqrt(variance)
epsilon = clip(epsilon_raw, ε_min, ε_max)Step 4: Compute Relative Tolerance
For binning, convert to relative units:
swing = H1 - L0 (or H3 - L2 for other pivots)
epsilon_r = epsilon / swingUse ε_r to classify pivots:
- EL: |L₂ - L₀| ≤ ε
- HL: (L₂ - L₀) > ε
- LL: (L₀ - L₂) > ε
---
Example Calculation
Scenario: H1 EURUSD, 2025-10-22 11:00 UTC
Input Data:
- L₀ = 1.0800 (high of prior swing)
- H₁ = 1.0850 (peak at 10:00)
- L₂ = 1.0810 (low at 11:00)
- Swing W = 1.0850 - 1.0800 = 0.0050 (50 pips)
Spreads (bid–ask):
- S_L0 (at 9:55–10:05) = median(0.0003, 0.0002, 0.0003) = 0.0003
- S_H1 (at 09:55–10:05) = median(0.0002, 0.0002, 0.0002) = 0.0002
- S_L2 (at 10:55–11:05) = median(0.0004, 0.0003, 0.0004) = 0.0004
- S = median(0.0003, 0.0002, 0.0004) = 0.0003
Volatility:
- ATR₁₄ (H1, at 11:00) = 0.0065 (65 pips over 14 bars)
Tolerance Calculation:
ε = √[(2.0 × 0.0003)² + (0.07 × 0.0065)²]
= √[(0.0006)² + (0.000455)²]
= √[3.6e-7 + 2.07e-7]
= √[5.67e-7]
≈ 0.000753 pips ≈ 0.75 basis points ≈ 7.53 pipsBounds Check:
ε_min = max(3 ticks, 0.0003) = max(0.00003, 0.0003) = 0.0003
ε_max = min(5 pips, 20% × 50 pips) = min(0.0005, 0.001) = 0.0005
ε_bounded = clip(0.000753, 0.0003, 0.0005) = 0.0005 (exceeds cap)Final ε = 0.0005 (5 pips)
Classification:
|L2 - L0| = |1.0810 - 1.0800| = 0.0010 (10 pips)
Is 0.0010 ≤ 0.0005? No.
Therefore: HL (Higher Low) — L2 is distinctly higher than L0.Relative Tolerance:
ε_r = 0.0005 / 0.005 = 0.1 (10% of swing)---
Sensitivity Analysis
What If Volatility Spikes?
If ATR₁₄ doubles to 0.013 (during news):
ε = √[(2.0 × 0.0003)² + (0.07 × 0.013)²]
= √[3.6e-7 + 8.28e-7]
= √[1.188e-6]
≈ 0.00109 (11 pips)
Bounded: ε = 0.0005 (capped at ε_max)Effect: Tolerance stays at ceiling; prevents wild swings from collapsing to near-zero.
What If Spreads Widen?
If S triples to 0.0009 (thin liquidity):
ε = √[(2.0 × 0.0009)² + (0.07 × 0.0065)²]
= √[3.24e-6 + 2.07e-7]
= √[3.447e-6]
≈ 0.00186 (18.6 pips)
Bounded: ε = 0.0005 (capped at ε_max)Effect: Wider spreads increase noise; tolerance grows but is capped.
What If Swing is Tiny?
If W = 10 pips instead of 50:
ε_max = min(5 pips, 20% × 10 pips) = min(0.0005, 0.002) = 0.0005 ✓Effect: Cap still applies; ε_max prevents "equal" from absorbing 50% of micro-moves.
---
Practical Fallbacks
If you lack real-time bid/ask (S unavailable):
Fallback 1: ATR-only (less accurate but workable)
ε = 0.05 × ATR14 (M5–M30)
ε = 0.07 × ATR14 (H1–D1)Fallback 2: Fixed band (simplest, least ideal)
ε = 3 pips (intraday, M5–M30)
ε = 5 pips (swing, H1–D1)
ε = 10 pips (daily, D1+)These are crude but often sufficient for rough classification.
---
Validation
To verify your ε formula is reasonable:
1. Sanity check: ε should be 3–10 pips for EURUSD on normal market days. 2. Stability: ε should not swing wildly between bars (use rolling median of S, not instant). 3. Regime test:
- Calm day (ATR ≈ 50 pips): ε ≈ 3–5 pips.
- Volatile day (ATR ≈ 100 pips): ε ≈ 5–10 pips.
- Crisis day (ATR ≈ 200 pips): ε ≈ ε_max (capped).
---
Implementation Pseudocode
def compute_epsilon(L0, H1, L2, S_L0, S_H1, S_L2, ATR14, b=0.05):
"""
Compute tolerance band ε for classification.
Args:
L0, H1, L2: Pivot prices
S_L0, S_H1, S_L2: Spreads at pivot times
ATR14: 14-bar ATR value at H1 time
b: Timeframe constant (0.05 for M5-M30, 0.07 for H1 and higher)
Returns:
epsilon: Tolerance band (price units)
epsilon_r: Relative tolerance (fraction of swing)
"""
# Step 1: Aggregate spread
S = median([S_L0, S_H1, S_L2])
# Step 2: Compute raw tolerance
a = 2.0
variance = (a * S)**2 + (b * ATR14)**2
epsilon_raw = sqrt(variance)
# Step 3: Apply bounds
swing = H1 - L0
epsilon_min = max(0.00003, S) # 3 ticks or 1x spread
epsilon_max = min(0.00050, 0.20 * swing) # 5 pips or 20% swing
epsilon = clip(epsilon_raw, epsilon_min, epsilon_max)
# Step 4: Relative tolerance
if swing > 0:
epsilon_r = epsilon / swing
else:
epsilon_r = 0.01 # Safe default if swing is near zero
return epsilon, epsilon_r
def classify_low(L0, L2, epsilon):
"""Classify L2 relative to L0."""
diff = abs(L2 - L0)
if diff <= epsilon:
return 'EL' # Equal Low
elif L2 > L0:
return 'HL' # Higher Low
else:
return 'LL' # Lower Low---
References
- ATR Calculation: Wilder, J.W. (1978). New Concepts in Technical Trading Systems.
- Volatility Scaling: Standard practice in options pricing (similar to volatility cones).
- Microstructure: Bid–ask spread models in high-frequency trading literature.
---
Last Updated: 2025-10-22 Context: EURUSD; no volume data assumed; OHLC high/low used for ZigZag extremes.
EURUSD Validation Scenarios: Three Market Conditions
Purpose: Provide concrete test cases for ε (tolerance band) and τ (reversal threshold) calculations across different market regimes.
Scope: H1 timeframe (4-hour data window equivalent to H1 behavior) Asset: EURUSD forex pair Validation: Parameters match observations; thresholds adjust appropriately
---
Scenario 1: Normal Market Day (Tight Spreads, Moderate Volatility)
Context: 2025-10-22 09:00–17:00 UTC, post-London session data Volatility: ATR₁₄ = 55 pips (typical) Liquidity: Good; tight spreads Market: Directionless; choppy consolidation
Input Data
| Parameter | Value | Meaning |
|---|---|---|
| L₀ | 1.0800 | Prior support level |
| H₁ | 1.0860 | Swing high |
| L₂ | 1.0815 | Current low (emerging pattern) |
| Swing W | 60 pips (0.0060) | H₁ - L₀ |
| Median Spread S | 0.3 pips (0.00003) | Bid-ask typical |
| ATR₁₄ | 55 pips (0.0055) | 14-bar range |
| c_A (H1) | 1.1 | Intraday H1 coefficient |
Calculation: ε (Tolerance Band)
ε = √[(2.0 × 0.00003)² + (0.07 × 0.0055)²]
= √[(0.00006)² + (0.000385)²]
= √[3.6e-9 + 1.48e-7]
= √[1.516e-7]
≈ 0.000389 (3.89 basis points)
≈ 3.9 pips
ε_min = max(3 ticks = 0.00003, 1 × 0.00003) = 0.00003
ε_max = min(5 pips = 0.0005, 20% × 0.0060 = 0.0012) = 0.0005
ε_final = clip(0.000389, 0.00003, 0.0005) = 0.000389 ≈ 3.9 pipsCalculation: τ (ZigZag Reversal Threshold)
τ_base = max(3 × 0.00003, 1.1 × 0.0055)
= max(0.00009, 0.00605)
= 0.00605 (6.05 pips)
τ_swing_cap = 0.35 × 0.0060 = 0.0021 (21 pips)
τ_hard_cap = 0.0030 (30 pips)
τ_max = min(0.0030, 0.0021) = 0.0021
τ = min(0.0021, max(0.00030, 0.00605)) = min(0.0021, 0.00605) = 0.0021 (21 pips)
Wait, this is capped by swing. Recalculate:
τ_base = 0.00605 = 6.05 pips (within bounds since < 21)
τ_final = 6.05 pipsClassification
| Concept | Value | Interpretation |
|---|---|---|
| z coordinate | (1.0815 - 1.0800) / (1.0860 - 1.0800) = 15/60 = 0.25 | 25% retrace |
| ε_r | 3.9 / 60 = 0.065 | 6.5% relative tolerance |
| Base Class | HL (since 0.25 > 0.065) | Higher Low (pullback) |
| Pivot Confirmation | Requires τ = 6.05 pip reversal | Not yet confirmed |
Regime Assessment
- Pattern: HL pullback in choppy market
- Confidence: Medium (normal spreads, typical volatility)
- Next Move: If L₂ holds (no reversal ≥6.05 pips down), likely bounce. If breaks by ≥6.05 pips, consider LL possibility.
---
Scenario 2: Volatile Day (Wide Spreads, High Volatility)
Context: 2025-10-21 12:00–20:00 UTC, US data release impact (FOMC statement) Volatility: ATR₁₄ = 140 pips (spike) Liquidity: Degraded; wider spreads Market: Sharp moves; trending down
Input Data
| Parameter | Value | Meaning |
|---|---|---|
| L₀ | 1.0900 | Recent support (pre-move) |
| H₁ | 1.0950 | Intraday bounce high |
| L₂ | 1.0860 | Lower low forming |
| Swing W | 50 pips (0.0050) | H₁ - L₀ |
| Median Spread S | 1.2 pips (0.00012) | Wide during event |
| ATR₁₄ | 140 pips (0.0140) | Elevated |
| c_A (H1) | 1.1 | Intraday H1 coefficient |
Calculation: ε (Tolerance Band)
ε = √[(2.0 × 0.00012)² + (0.07 × 0.0140)²]
= √[(0.00024)² + (0.00098)²]
= √[5.76e-8 + 9.604e-7]
= √[1.0160e-6]
≈ 0.001008 (10.08 basis points)
≈ 10.08 pips
ε_min = max(3 ticks = 0.00003, 1 × 0.00012) = 0.00012
ε_max = min(5 pips = 0.0005, 20% × 0.0050 = 0.001) = 0.0005
ε_final = clip(0.001008, 0.00012, 0.0005) = 0.0005 (5.0 pips)
[Note: ε exceeded ε_max, capped to 5 pips hard limit]Calculation: τ (ZigZag Reversal Threshold)
τ_base = max(3 × 0.00012, 1.1 × 0.0140)
= max(0.00036, 0.0154)
= 0.0154 (15.4 pips)
τ_swing_cap = 0.35 × 0.0050 = 0.00175 (17.5 pips)
τ_hard_cap = 0.0030 (30 pips)
τ_max = min(0.0030, 0.00175) = 0.00175 (17.5 pips)
τ = min(0.00175, max(0.00030, 0.0154)) = min(0.00175, 0.0154) = 0.00175 (17.5 pips)
[Note: τ_base (15.4) exceeds both caps; bounded to 17.5 pips swing cap]Classification
| Concept | Value | Interpretation |
|---|---|---|
| z coordinate | (1.0860 - 1.0900) / (1.0950 - 1.0900) = -40/50 = -0.8 | Below L₀ |
| ε_r | 5.0 / 50 = 0.10 | 10% relative tolerance |
| o coordinate | (1.0900 - 1.0860) / 0.0140 = 40/140 = 0.286 | 2.86× ATR undershoot |
| Base Class | LL (since -0.8 < -0.10) | Lower Low (undercut) |
| Volatility Regime | LL-FD1 (micro, based on o ≈ 0.29 < typical q₂₀) | Sharp break expected to bounce |
Regime Assessment
- Pattern: LL undercut in volatile environment
- Confidence: Medium-High (spike volatility, but coherent move)
- Next Move: Overshoot (2.86× ATR) is significant but not extreme. Expect mean-reversion bounce if τ triggers (17.5 pip reversal from L₂ high).
---
Scenario 3: Market Crash (Extreme Volatility, Liquidity Evaporation)
Context: 2025-10-18 02:00 UTC, Asian session black swan (geopolitical shock) Volatility: ATR₁₄ = 350+ pips (extreme) Liquidity: Evaporated; 5+ pip spreads common Market: Panic selling; flash crash
Input Data
| Parameter | Value | Meaning |
|---|---|---|
| L₀ | 1.0700 | Pre-shock support |
| H₁ | 1.0850 | Pre-shock high |
| L₂ | 1.0450 | Panic low |
| Swing W | 150 pips (0.0150) | H₁ - L₀ |
| Median Spread S | 6.0 pips (0.0006) | Extreme wide |
| ATR₁₄ | 350 pips (0.0350) | Tail-risk event |
| c_A (H1) | 1.1 | Intraday H1 coefficient |
Calculation: ε (Tolerance Band)
ε = √[(2.0 × 0.0006)² + (0.07 × 0.0350)²]
= √[(0.0012)² + (0.00245)²]
= √[1.44e-6 + 6.0025e-6]
= √[7.4425e-6]
≈ 0.002728 (27.28 basis points)
≈ 27.28 pips
ε_min = max(3 ticks = 0.00003, 1 × 0.0006) = 0.0006
ε_max = min(5 pips = 0.0005, 20% × 0.0150 = 0.003) = 0.0005
ε_final = clip(0.002728, 0.0006, 0.0005) = 0.0005 (5.0 pips)
[Note: ε severely constrained by hard cap; tolerance narrowed despite chaos]Calculation: τ (ZigZag Reversal Threshold)
τ_base = max(3 × 0.0006, 1.1 × 0.0350)
= max(0.0018, 0.0385)
= 0.0385 (38.5 pips)
τ_swing_cap = 0.35 × 0.0150 = 0.00525 (52.5 pips)
τ_hard_cap = 0.0030 (30 pips)
τ_max = min(0.0030, 0.00525) = 0.0030 (30 pips)
τ = min(0.0030, max(0.00030, 0.0385)) = min(0.0030, 0.0385) = 0.0030 (30 pips)
[Note: τ hard-capped to 30 pips; prevents over-aggressive threshold in chaos]Classification
| Concept | Value | Interpretation |
|---|---|---|
| z coordinate | (1.0450 - 1.0700) / (1.0850 - 1.0700) = -250/150 = -1.667 | Far below L₀ |
| ε_r | 5.0 / 150 = 0.033 | 3.3% relative tolerance |
| o coordinate | (1.0700 - 1.0450) / 0.0350 = 250/350 = 0.714 | 7.14× ATR undershoot |
| Base Class | LL (since -1.667 < -0.033) | Lower Low (severe undercut) |
| Volatility Regime | LL-FD4 (extreme, o ≈ 0.714 >> typical q₆₀) | Tail-risk crash |
| Pivot Confirmation | Requires τ = 30 pip reversal | Ultra-high bar |
Regime Assessment
- Pattern: Extreme LL (flash crash) in black swan
- Confidence: Low (crisis conditions; no normal market structure)
- Next Move: Overshoot (7.14× ATR) is catastrophic. Reversal unlikely until τ = 30 pip bounce (would require price to rise from 1.0450 to 1.0480 minimum). High probability of continued selling or circuit breaker halt.
- Risk: τ hard-capped to 30 pips prevents false pivot detection, but true reversal confirmation will lag the actual market bottom.
---
Validation Checks
ε (Tolerance Band) Behavior Across Scenarios
| Scenario | Spread | ATR | ε (raw) | ε (bounded) | Interpretation |
|---|---|---|---|---|---|
| Normal | 0.3 pip | 55 pip | 3.9 pip | 3.9 pip | ✅ Reasonable |
| Volatile | 1.2 pip | 140 pip | 10.1 pip | 5.0 pip | ✅ Capped to hard limit |
| Crash | 6.0 pip | 350 pip | 27.3 pip | 5.0 pip | ✅ Hard cap prevents explosion |
Observation: As chaos increases, ε→ε_max (5 pips hard limit). Tolerance band stabilizes despite extreme inputs. ✅
τ (Reversal Threshold) Behavior Across Scenarios
| Scenario | Swing | ATR | τ (raw) | τ (bounded) | Interpretation |
|---|---|---|---|---|---|
| Normal | 60 pip | 55 pip | 6.05 pip | 6.05 pip | ✅ Moderate |
| Volatile | 50 pip | 140 pip | 15.4 pip | 17.5 pip | ✅ Capped by swing (21 pip) |
| Crash | 150 pip | 350 pip | 38.5 pip | 30 pip | ✅ Hard-capped to 30 pip |
Observation: τ scales with volatility but hard-caps at 30 pips to prevent over-aggressive thresholds in chaos. ✅
Pattern Classification Consistency
| Scenario | L₂ vs L₀ | o Value | FD Bin | Regime |
|---|---|---|---|---|
| Normal | HL (0.25) | N/A | HL-FD2 | Pullback |
| Volatile | LL (-0.80) | 0.29 | LL-FD1 | Micro undercut |
| Crash | LL (-1.67) | 0.71 | LL-FD4 | Extreme undercut |
Observation: Classification adapts correctly to market regime (normal→pullback, volatile→micro break, crash→extreme break). ✅
---
Implementation Test Checklist
When implementing ε and τ calculations, verify against these scenarios:
- [ ] Normal scenario: ε ≈ 3.9 pips, τ ≈ 6.05 pips
- Spreads are tight (0.3 pip)
- Volatility is moderate (55 pip ATR)
- Thresholds should be responsive but not noise-driven
- [ ] Volatile scenario: ε capped to 5 pips, τ ≈ 17.5 pips
- Spreads widen (1.2 pip)
- Volatility spikes (140 pip ATR)
- ε hard-cap prevents over-sensitivity; τ follows ATR but respects swing cap
- [ ] Crash scenario: ε capped to 5 pips, τ capped to 30 pips
- Spreads evaporate (6+ pips)
- Volatility extreme (350 pips)
- Both thresholds saturate hard limits to stabilize pattern detection
- [ ] Cross-Check:
- τ > ε in all scenarios (reversal threshold > classification tolerance) ✓
- ε_max and τ_max prevent runaway values in chaos ✓
- FD binning adapts variant classification to overshoot depth ✓
---
References
- ε formula: epsilon-tolerance.md
- τ formula: notation-definitions.md#zigzag-reversal-threshold-τ
- Classification: variants-updown.md
- Implementation: data-pipeline.md
---
Last Updated: 2025-10-22 Status: ✅ Ready for implementation testing Validation Purpose: Confirm ε and τ behave correctly across three representative market conditions
Notation & Definitions: Single Source of Truth
Master reference document for all symbols, abbreviations, and key terms used throughout the ZigZag research documentation.
All other documents cross-reference this file instead of duplicating definitions.
---
Pivot Notation
| Symbol | Definition | Units | Example |
|---|---|---|---|
| L₀ | Initial low (first confirmed pivot) | price | 1.0800 |
| H₁ | Swing high (reversal peak) | price | 1.0850 |
| L₂ | Second low (continuation swing) | price | 1.0810 |
| H₃ | Second high (final pivot in 3-pivot) | price | 1.0870 |
| t_L₀, t_H₁, t_L₂, t_H₃ | Timestamp of pivot | datetime | 2025-10-22 10:00 UTC |
| W | Swing magnitude (H₁ − L₀) | price | 0.0050 |
---
Price Level Relationships
Pivot Comparisons
| Notation | Definition | Example |
|---|---|---|
| HL | Higher Low: L₂ > L₀ | 1.0810 > 1.0800 ✓ |
| EL | Equal Low: L₂ ≈ L₀ | 1.0800 ± ε |
| LL | Lower Low: L₂ < L₀ | 1.0795 < 1.0800 ✓ |
| HH | Higher High: H₃ > H₁ | 1.0870 > 1.0850 ✓ |
| EH | Equal High: H₃ ≈ H₁ | 1.0850 ± ε |
| LH | Lower High: H₃ < H₁ | 1.0840 < 1.0850 ✓ |
---
Key Formulas
Normalized Retracement Coordinate
z = (L₂ - L₀) / (H₁ - L₀) ∈ [−∞, +∞]Interpretation:
- z = 1.0: L₂ equals H₁ (rare)
- z = 0.5: L₂ retraces 50% of swing (Fibonacci level)
- z = 0.0: L₂ equals L₀ (edge case)
- z < 0: L₂ below L₀ (undershoots) — signals LL class
Usage:
- Compare patterns across different swing sizes (scale-invariant)
- Input to Freedman–Diaconis binning
- Basis for EL/HL/LL classification
---
Volatility-Normalized Overshoot
o = −z / ATR₁₄ = (L₀ − L₂) / ATR₁₄ ∈ [0, +∞)Interpretation:
- o = 0.1: Undershoot = 10% of ATR (micro undercut, bounce likely)
- o = 0.5: Undershoot = 50% of ATR (moderate panic)
- o = 1.0: Undershoot = full ATR (deep reversal)
- o > 2.0: Undershoot > 200% ATR (extreme tail event, crash)
Usage:
- Quantify "surprise" relative to recent volatility
- Identify tail-risk undercuts
- Input to FD binning for LL sub-classification
---
Tolerance Band (ε)
See [epsilon-tolerance.md](epsilon-tolerance.md) for complete specification.
Core formula:
ε = min(ε_max, max(ε_min, √[(a·S)² + (b·ATR₁₄)²]))Components:
- S: Rolling median spread (bid–ask), price units
- ATR₁₄: 14-bar Average True Range, price units
- a: Spread scaling (default: 2.0)
- b: ATR scaling (default: 0.05 for M5–M30, 0.07 for H1–D1)
Bounds (EURUSD):
- ε_min = max(3 ticks = 0.00003, 1×S)
- ε_max = min(5 pips = 0.00050, 0.2×W)
Relative tolerance:
ε_r = ε / WUse: Classify EL if |L₂ − L₀| ≤ ε; else HL or LL
---
Relative Tolerance (ε_r)
ε_r = ε / (H₁ − L₀) ∈ [0, 1]Interpretation:
- ε_r = 0.01: "Equal" means within 1% of swing (tight tolerance)
- ε_r = 0.10: "Equal" means within 10% of swing (loose tolerance)
Use: Classification threshold
- EL if |z| ≤ ε_r
- HL if z > ε_r
- LL if z < −ε_r
---
ZigZag Reversal Threshold (τ)
Purpose: Minimum price reversal magnitude required to confirm a ZigZag pivot. Adapts dynamically to market conditions (volatility and microstructure).
Dynamic formula:
τ = min(τ_max, max(τ_min, c_S × S, c_A × ATR₁₄))Components:
- S: Rolling median spread (bid–ask), price units
- ATR₁₄: 14-bar Average True Range, price units
- c_S: Spread scaling constant = 3
- c_A: ATR scaling constant (timeframe-dependent)
Constants (EURUSD):
- c_S = 3 (spread scaling – 3× microstructure noise as baseline)
- c_A = 0.9 (intraday: M5–M30), 1.1 (H1 and higher: H1–D1)
- τ_min = 0.00030 (3 pips, absolute minimum reversal size)
- τ_max = min(0.0030, 0.35 × swing)
- Hard cap: 0.0030 (30 pips maximum)
- Swing cap: 35% of current swing magnitude (prevents over-aggressive thresholds on small swings)
- Use whichever is smaller
Interpretation:
- τ = 6 pips: Pivot confirmed when price reverses ≥6 pips from current extreme
- τ = 8 pips: (higher volatility or wider spreads)
- τ = 3 pips: (tight market, low volatility)
Use: Confirm pivot when price reverses by ≥τ from leg extreme
For implementation details and pseudocode, see [data-pipeline.md § Step [4]](data-pipeline.md#4-determine-zigzag-thresholds).
Worked Example: Computing τ for H1 EURUSD
Scenario: 2025-10-22 11:30 UTC, EURUSD H1, building uptrend
Input Data:
- Median spread S = 0.2 pips (1.5 ticks) = 0.00015 price units
- ATR₁₄ = 60 pips (0.00060 price units) over past 14 bars
- Current swing = H-L = 0.0055 (55 pips) – used for τ_max cap
- Timeframe: H1 (intraday H1, so c_A = 1.1)
Calculation:
Step 1: Compute baseline threshold
τ_base = max(c_S × S, c_A × ATR₁₄)
= max(3 × 0.00015, 1.1 × 0.00060)
= max(0.00045, 0.00066)
= 0.00066 (6.6 pips)
Step 2: Apply bounds
τ_swing_cap = 0.35 × swing = 0.35 × 0.0055 = 0.001925
τ_hard_cap = 0.0030
τ_max = min(0.0030, 0.001925) = 0.001925
τ_min = 0.00030
τ = min(τ_max, max(τ_min, τ_base))
= min(0.001925, max(0.00030, 0.00066))
= min(0.001925, 0.00066)
= 0.00066 (6.6 pips)Interpretation:
- Pivot confirmation threshold: Price must reverse ≥6.6 pips from current extreme
- If in uptrend: Running high H = 1.0900. If price drops to 1.0893 or below, pivot confirmed
- If in downtrend: Running low L = 1.0850. If price rises to 1.0856 or above, pivot confirmed
Comparison with ε (tolerance band):
- τ ≈ 6.6 pips (reversal threshold for pivot confirmation)
- ε ≈ 4.5 pips (tolerance for "equal" level classification)
- Relationship: τ > ε in this scenario. τ is stricter for pivot detection; ε is looser for classification.
Sensitivity:
- If ATR₁₄ doubles to 120 pips: τ_base = max(0.00045, 1.1 × 0.0012) = 0.00132 (13.2 pips)
- If spreads widen to 1 pip: τ_base = max(3 × 0.0001, 1.1 × 0.0006) = 0.00066 (unchanged)
- If swing shrinks to 20 pips: τ_swing_cap = 0.35 × 0.002 = 0.0007 (7 pips), τ = 0.0007
---
Pattern Classes
Base Classes (3 mutually exclusive)
| Class | Condition | Interpretation | Probability |
|---|---|---|---|
| EL | \ | z\ | ≤ ε_r |
| HL | z > ε_r | Higher Low — pullback, no break | ~50–60% |
| LL | z < −ε_r | Lower Low — undercut below L₀ | ~10–20% |
---
FD-Binned Variants (9 total: UP–DOWN)
See [variants-updown.md](variants-updown.md) for detailed classification and market regimes.
EL Variants
- EL: Equal Low (no binning, single class)
HL Variants (4 bins, shallow → deep)
- HL-FD1: Shallow retrace (0.75 < z < 1.0) — minimal pullback
- HL-FD2: Mid-upper retrace (0.50 < z ≤ 0.75) — Fibonacci 38.2% / 50%
- HL-FD3: Mid-lower retrace (0.25 < z ≤ 0.50) — Fibonacci 61.8%
- HL-FD4: Deep retrace (0 < z ≤ 0.25) — nearly complete retest
LL Variants (4 bins, micro → extreme)
- LL-FD1: Micro undercut (o ≤ q₂₀) — brief spillover
- LL-FD2: Shallow undercut (q₂₀ < o ≤ q₄₀) — moderate panic
- LL-FD3: Deep undercut (q₄₀ < o ≤ q₆₀) — structural breakdown
- LL-FD4: Extreme undercut (o > q₆₀) — tail-risk crash
---
Supplementary Flags
Optional flags attach to any variant for richer classification:
| Flag | Meaning | Signal |
|---|---|---|
| +C | Close below L₀ between H₁→L₂ | Stronger bearish conviction |
| +S | L₂ occurs in single bar (spike) | Sharp reversal; mean reversion likely |
| +X | High wicks below L₀, no close | Liquidity grab; false break setup |
Example labels: HL-FD2+C, LL-FD4+S+C, EL+X
Flag Combination Examples
Example 1: HL-FD2+C — Mid-upper Retrace with Close Below Support
Variant: HL-FD2 (L₂ retraces 50–75% of swing; bullish pullback expected)
Flag +C: Close bar ends BELOW L₀ (between H₁→L₂)
Combined: HL-FD2+C
Interpretation:
- Base regime (HL-FD2): Shallow pullback; likely bounce
- +C modifier: Close below L₀ indicates commitment to bearish; weakens bullish bias
- Signal: Still a pullback pattern but with more downside risk. Caution on longs until bounce confirmed.
- Trade: Enter long near L₂ but tighten stop below recent wick. Consider waiting for close above L₀.Example 2: LL-FD1+S+C — Micro Undercut with Spike and Close Below
Variant: LL-FD1 (L₂ < L₀ by < 0.5 ATR; brief spillover, micro-crash)
Flag +S: L₂ occurs in single bar (sharp reversal spike)
Flag +C: Close bar ends BELOW L₀ (confirming bearish intent)
Combined: LL-FD1+S+C
Interpretation:
- Base regime (LL-FD1): Reversal likely; mean reversion bounce expected
- +S modifier: Sharp single-bar action; price "wicked" quickly; high conviction reversal
- +C modifier: Close below L₀ = confirmed break, not just a wick
- Signal: High-conviction short signal. Price broke support cleanly on single bar. Expect sharp bounce OR continued breakdown.
- Trade: If bounce occurs, enter long after close crosses back above L₀. Probability of bounce = 70%+ given micro nature.Example 3: EL+X — Equal Low with Liquidity Grab (False Break)
Variant: EL (L₂ ≈ L₀ within ε; retest of support)
Flag +X: High wicks below L₀ but close remains above L₀ (no close below)
Combined: EL+X
Interpretation:
- Base regime (EL): Retest of support; equilibrium pattern; slight bullish
- +X modifier: Wicks probe below L₀ but fail to close there (liquidation trap / false break)
- Signal: Liquidity grab. Shorts get stopped out by wick. Close above L₀ shows buyers returning.
- Trade: False break setup. Enter long on close above L₀. Stop loss just below wick low. Probability of upside breakout = 65%+Example 4: HL-FD4+S — Deep Retrace with Spike (Volatile Consolidation)
Variant: HL-FD4 (L₂ retraces 0–25% of swing; nearly complete retest)
Flag +S: L₂ occurs in single bar spike
Combined: HL-FD4+S
Interpretation:
- Base regime (HL-FD4): Deep retrace; pullback has burned support; uncertain continuation
- +S modifier: Occurred via sharp spike; rapid reversal execution
- Signal: Deep retest on sharp spike = high conviction selling followed by equally sharp buying. Volatile consolidation zone.
- Trade: Caution. Pullback is deep AND volatile. Wait for second bar confirmation before entry. If close above pivot, long is favored.---
Market Regimes
See [variants-updown.md](variants-updown.md#market-regime-mapping) for trading implications per variant.
| Regime | Variants | Characteristics | Entry Signal |
|---|---|---|---|
| Retest | EL | Lows re-establish support | Long from L₀+ε |
| Pullback | HL-FD1/2/3/4 | Retracement without break | Long from L₂ (varies by depth) |
| Undercut | LL-FD1/2/3/4 | Break below support | Short trade (micro) or reversal wait (extreme) |
---
Microstructure Terms
| Term | Definition | Units | Use |
|---|---|---|---|
| Spread (S) | best-ask − best-bid | price | Liquidity quality; noise floor |
| ATR₁₄ | 14-bar Average True Range | price | Volatility proxy; scales thresholds |
| Tick | Smallest price unit | price | EURUSD = 0.00001 |
| Pip | 10 ticks (standard reporting) | price | EURUSD = 0.00010 |
| Repainting | Pivot changes as new data arrives | flag | Excluded from analysis |
---
Abbreviations
| Abbreviation | Full Form |
|---|---|
| OHLC | Open, High, Low, Close |
| ATR | Average True Range |
| FD | Freedman–Diaconis (binning method) |
| IQR | Interquartile Range |
| ε | Epsilon (tolerance band) |
| τ | Tau (ZigZag threshold) |
| EL | Equal Low |
| HL | Higher Low |
| LL | Lower Low |
| HH | Higher High |
| EH | Equal High |
| LH | Lower High |
| DRY | Don't Repeat Yourself (principle) |
| UTC | Coordinated Universal Time |
---
Related Documents
Implementation & Methodology:
- [epsilon-tolerance.md](epsilon-tolerance.md) — Complete ε formula, EURUSD defaults, examples
- [binning-methodology.md](binning-methodology.md) — FD binning algorithm, worked example
- [data-pipeline.md](data-pipeline.md) — 11-step pipeline using all above terms
Pattern Analysis:
- [variants-updownup.md](variants-updownup.md) — 9 three-pivot patterns (L₀→H₁→L₂→H₃)
- [variants-updown.md](variants-updown.md) — Granular two-pivot patterns with FD bins
Development History:
- [conversation.md](conversation.md) — Full 13-part Q&A developing the framework
- [README.md](README.md) — Navigation guide and use cases
---
Document Version
| Date | Update |
|---|---|
| 2025-10-22 | Created as single source of truth; replaces scattered definitions |
---
All notation in this document is normative. Other files reference this document instead of redefining terms.
UP–DOWN–UP Variants: 9 Complete Classification
Exhaustive analysis of all 9 possible three-pivot UP–DOWN–UP ZigZag patterns: L0→H1→L2→H3.
Overview
With confirmed non-repainting pivots following the pattern UP–DOWN–UP, exactly 9 mutually exclusive, collectively exhaustive variants exist. See notation-definitions.md for pivot notation (L₀, H₁, L₂, H₃) and price level relationships (HL, EL, LL, HH, EH, LH).
They differ along two independent dimensions:
1. L2 vs L0: {HL (higher), EL (equal), LL (lower)} 2. H3 vs H1: {HH (higher), EH (equal), LH (lower)}
This yields 3×3=9 combinations, all mathematically feasible and market-relevant.
Constraint Structure
All 9 variants satisfy these mandatory inequalities:
- L0 < H1 (first uptrend)
- L2 < H1 (second low is below first high)
- H3 > L2 (second uptrend)
No variant is forbidden by these constraints.
Classification Matrix
| Variant | Relative L2 | Relative H3 | Name | Market Regime |
|---|---|---|---|---|
| 1 | L2 > L0 (HL) | H3 > H1 (HH) | Continuation impulse | Bull trend continuation |
| 2 | L2 > L0 (HL) | H3 ≈ L1 (EH) | Double-top test | Range, bullish bias |
| 3 | L2 > L0 (HL) | H3 < H1 (LH) | Triangle compression | Neutral consolidation |
| 4 | L2 ≈ L0 (EL) | H3 > H1 (HH) | Range break up | Bullish transition |
| 5 | L2 ≈ L0 (EL) | H3 ≈ H1 (EH) | Rectangle | Balanced range |
| 6 | L2 ≈ L0 (EL) | H3 < H1 (LH) | Lower-high at flat base | Range, bearish bias |
| 7 | L2 < L0 (LL) | H3 > H1 (HH) | V-reversal, spring | Bullish reversal |
| 8 | L2 < L0 (LL) | H3 ≈ H1 (EH) | Undercut then stall | Volatile range |
| 9 | L2 < L0 (LL) | H3 < H1 (LH) | Rally failure | Bear trend continuation |
ASCII Visualizations
All pivots marked with •. Levels scale: top→bottom = high→low price.
1) HL+HH — Continuation Impulse
Market regime: Bull trend continuation Interpretation: Fresh swing high; series likely extends further up.
Levels │ L0 │ H1 │ L2 │ H3 │
HH │ │ │ │ • │
H1/EH │ │ • │ │ │
LH/HL │ │ │ • │ │
L0/EL │ • │ │ │ │
LL │ │ │ │ │---
2) HL+EH — Double-Top Test from HL
Market regime: Range, bullish bias Interpretation: Second high retests first high; breakout pending above H1.
Levels │ L0 │ H1 │ L2 │ H3 │
HH │ │ │ │ │
H1/EH │ │ • │ │ • │
LH/HL │ │ │ • │ │
L0/EL │ • │ │ │ │
LL │ │ │ │ │---
3) HL+LH — Triangle Compression
Market regime: Neutral consolidation Interpretation: Highs declining, lows holding; range compression may precede directional break.
Levels │ L0 │ H1 │ L2 │ H3 │
HH │ │ │ │ │
H1/EH │ │ • │ │ │
LH/HL │ │ │ • │ • │
L0/EL │ • │ │ │ │
LL │ │ │ │ │---
4) EL+HH — Range Break Up
Market regime: Bullish transition Interpretation: Lows flat; highs new; bullish breakout confirmed.
Levels │ L0 │ H1 │ L2 │ H3 │
HH │ │ │ │ • │
H1/EH │ │ • │ │ │
LH/HL │ │ │ │ │
L0/EL │ • │ │ • │ │
LL │ │ │ │ │---
5) EL+EH — Rectangle
Market regime: Balanced range Interpretation: Both highs and lows equal; perfect box pattern; breakout imminent.
Levels │ L0 │ H1 │ L2 │ H3 │
HH │ │ │ │ │
H1/EH │ │ • │ │ • │
LH/HL │ │ │ │ │
L0/EL │ • │ │ • │ │
LL │ │ │ │ │---
6) EL+LH — Lower-High at Flat Base
Market regime: Range, bearish bias Interpretation: Lows flat; highs failing; bearish setup for downside extension.
Levels │ L0 │ H1 │ L2 │ H3 │
HH │ │ │ │ │
H1/EH │ │ • │ │ │
LH/HL │ │ │ │ • │
L0/EL │ • │ │ • │ │
LL │ │ │ │ │---
7) LL+HH — V-Reversal, Spring
Market regime: Bullish reversal Interpretation: Deep undercut followed by surge; classic reversal pattern; reversal likely confirmed.
Levels │ L0 │ H1 │ L2 │ H3 │
HH │ │ │ │ • │
H1/EH │ │ • │ │ │
LH/HL │ │ │ │ │
L0/EL │ • │ │ │ │
LL │ │ │ • │ │---
8) LL+EH — Undercut Then Stall
Market regime: Volatile range Interpretation: Strong dip tested; recovery stalls at prior high; uncertain trend.
Levels │ L0 │ H1 │ L2 │ H3 │
HH │ │ │ │ │
H1/EH │ │ • │ │ • │
LH/HL │ │ │ │ │
L0/EL │ • │ │ │ │
LL │ │ │ • │ │---
9) LL+LH — Rally Failure
Market regime: Bear trend continuation Interpretation: Lows break lower; highs decline; downtrend reinforced.
Levels │ L0 │ H1 │ L2 │ H3 │
HH │ │ │ │ │
H1/EH │ │ • │ │ │
LH/HL │ │ │ │ • │
L0/EL │ • │ │ │ │
LL │ │ │ • │ │---
Pattern Performance Notes
Each variant's market regime is defined in the Classification Matrix above (lines 27-37). The 9 variants naturally group into:
- Bullish (4): HL+HH (continuation), EL+HH (breakout), HL+EH (retest), LL+HH (reversal)
- Neutral (3): HL+LH (triangle), EL+EH (rectangle), LL+EH (volatile range)
- Bearish (2): EL+LH (lower-high), LL+LH (rally failure)
For detailed trading rules (entry signals, stop loss, targets), see variants-updown.md Market Regime Mapping which extends these concepts to FD-binned granularity
Implementation Notes
Equality Classification
Use your tolerance band ε to classify EH and EL:
- EH: |H3 - H1| ≤ ε
- EL: |L2 - L0| ≤ ε
See epsilon-tolerance.md for complete ε formula and defaults.
Granular Sub-Classification
If you want finer discrimination within each variant, apply Freedman–Diaconis binning or add a third independent relation: H₃ vs L₀.
27-Way Extension
Adding H₃ vs L₀ as a third dimension yields 3×3×3=27 sub-variants:
- L₂ vs L₀: {HL, EL, LL}
- H₃ vs H₁: {HH, EH, LH}
- H₃ vs L₀: {Above, Equal, Below}
Example: HL + LH + H₃>L₀
Pattern: L₂>L₀, H₃<H₁, H₃>L₀
Variant: HL + LH + H₃>L₀
Levels │ L₀ │ H₁ │ L₂ │ H₃ │
────────────────┼─────┼─────┼─────┼─────┤
Above H₁ │ │ │ │ │
= H₁ │ │ • │ │ │
Between H₁ & L₀ │ │ │ • │ • │
= L₀ │ • │ │ │ │
Below L₀ │ │ │ │ │Interpretations for HL + LH:
- H₃ > L₀: Triangle above initial support (most common)
- H₃ ≈ L₀: Triangle retracing to initial low (reversal risk)
- H₃ < L₀: Impossible (violates L₀<L₂<H₃<H₁ constraint for HL+LH)
Note: Some combinations are mathematically impossible due to constraints. Analysts often use "reclaims L₀" vs "fails to reclaim L₀" as a simpler binary distinction.
---
HL+LH Granular Sub-Variants (Third-Band Breakdown)
For HL+LH — Triangle Compression, segmenting L₂ and H₃ into lower/mid/upper thirds of [L₀, H₁] reveals buyer-seller balance dynamics.
Band Structure:
- Lower third: [L₀, L₀ + ⅓(H₁−L₀)]
- Mid third: [L₀ + ⅓(H₁−L₀), L₀ + ⅔(H₁−L₀)]
- Upper third: [L₀ + ⅔(H₁−L₀), H₁]
- Near H₁: Within ε-band of H₁
1) Shallow HL + Weak Bounce (H₃ near L₂)
Pattern: L₂ in lower third, H₃ in mid third Interpretation: Compression with fading momentum; sellers gaining control
Levels │ L₀ │ H₁ │ L₂ │ H₃ │
= H₁ │ │ • │ │ │
Near H₁ │ │ │ │ │
Upper │ │ │ │ │
Mid │ │ │ │ • │
Lower │ │ │ • │ │
= L₀ │ • │ │ │ │---
2) Shallow HL + Mid Bounce
Pattern: L₂ in lower third, H₃ in upper third Interpretation: Tightening triangle with mild bullish bias; consolidation before breakout
Levels │ L₀ │ H₁ │ L₂ │ H₃ │
= H₁ │ │ • │ │ │
Near H₁ │ │ │ │ │
Upper │ │ │ │ • │
Mid │ │ │ │ │
Lower │ │ │ • │ │
= L₀ │ • │ │ │ │---
3) Shallow HL + Strong Test (H₃ near H₁)
Pattern: L₂ in lower third, H₃ near H₁ Interpretation: Breakout pressure building; buyers testing resistance strongly
Levels │ L₀ │ H₁ │ L₂ │ H₃ │
= H₁ │ │ • │ │ │
Near H₁ │ │ │ │ • │
Upper │ │ │ │ │
Mid │ │ │ │ │
Lower │ │ │ • │ │
= L₀ │ • │ │ │ │---
4) Deep HL + Weak Bounce (L₂ high, H₃ just above)
Pattern: L₂ in upper third, H₃ near H₁ Interpretation: Deep pullback with weak recovery; triangle apex forming
Levels │ L₀ │ H₁ │ L₂ │ H₃ │
= H₁ │ │ • │ │ │
Near H₁ │ │ │ │ • │
Upper │ │ │ • │ │
Mid │ │ │ │ │
Lower │ │ │ │ │
= L₀ │ • │ │ │ │---
5) Deep HL + Mid Bounce
Pattern: L₂ in upper third, H₃ in upper third Interpretation: Continuation drift; both L₂ and H₃ drifting higher in tight range
Levels │ L₀ │ H₁ │ L₂ │ H₃ │
= H₁ │ │ • │ │ │
Near H₁ │ │ │ │ │
Upper │ │ │ • │ • │
Mid │ │ │ │ │
Lower │ │ │ │ │
= L₀ │ • │ │ │ │---
6) Deep HL + Strong Test
Pattern: L₂ in upper third, H₃ near H₁ Interpretation: Extreme compression; apex reached; breakout imminent
Levels │ L₀ │ H₁ │ L₂ │ H₃ │
= H₁ │ │ • │ │ │
Near H₁ │ │ │ │ • │
Upper │ │ │ • │ │
Mid │ │ │ │ │
Lower │ │ │ │ │
= L₀ │ • │ │ │ │---
Implementation Note: Use your ε-band for "Near H₁" edge detection. Thirds can be replaced with Freedman–Diaconis bins on real data for more precise demarcation.
Market Regime Assignment
Each variant receives a regime label for downstream analysis:
- Trend Continuation: HL+HH, LL+LH
- Range Consolidation: HL+LH, EL+EH, EL+EH, LL+EH
- Bullish Transition: EL+HH, HL+EH
- Bearish Transition: EL+LH
- Reversal: LL+HH
Temporal Dependencies
Consider tracking:
- Duration up (L0→H1 time): Fast impulse vs gradual accumulation
- Duration down (H1→L2 time): Quick pullback vs lengthy consolidation
- Duration final up (L2→H3 time): Weak bounce vs strong recovery
These temporal features enhance pattern prediction when combined with the 9-variant classification.
Next Steps
1. Implement: Compute all 9 variants on rolling EURUSD data 2. Backtest: Analyze returns per variant across multiple timeframes 3. Granularize: Apply FD binning for sub-variant analysis (see variants-updown.md) 4. Extend: Combine with volatility, spreads, and trend context 5. Model: Use 9-variant labels as features for ML regime classifiers
---
Reference: Mathematically complete; no variants missing. Constraints: All patterns satisfy L0 < H1, L2 < H1, H3 > L2.
UP–DOWN Variants: Granular Two-Pivot Classification with FD Binning
Comprehensive analysis of UP–DOWN (L0→H1→L2) patterns with Freedman–Diaconis binning for fine-grained regime discrimination.
Overview
Two-pivot UP–DOWN patterns provide immediate binary classification (simpler than three-pivot):
- Simple: Only 3 base classes (EL, HL, LL)
- Rich: FD binning adds statistical depth without manual thresholding
- Practical: Fewer degrees of freedom = more stable estimates per timeframe
Base Classification (3 Classes)
All UP–DOWN triplets fall into exactly one of:
| Class | Definition | Meaning | Frequency |
|---|---|---|---|
| EL | L₂ ≈ L₀ | Equal Low (retest) | ~20–30% |
| HL | L₂ > L₀ | Higher Low (pullback) | ~50–60% |
| LL | L₂ < L₀ | Lower Low (undercut) | ~10–20% |
Equality determined by tolerance band ε (see epsilon-tolerance.md).
Granular Classification: 9 FD-Binned Variants
Decompose HL and LL into 4 sub-classes each using normalized retracement depth:
z = (L2 - L0) / (H1 - L0) ∈ [0, 1] for HL
o = -z / ATR14 ∈ [0, ∞) for LLSee `notation-definitions.md#normalized-retracement-coordinate` and `notation-definitions.md#volatility-normalized-overshoot` for the single-source definitions.
Run Freedman–Diaconis separately on each; typical result: 4 bins per side + 1 EL bin = 9 variants.
Complete Variant Set
Class: EL (Equal Low)
Levels │ L0 │ H1 │ L2 │
≈H1 │ │ • │ │
HL bands │ │ │ │
= L0±ε │ • │ │ • │
Below L0 │ │ │ │Interpretation: L₂ equals L₀ within tolerance band ε. Market Regime: Retest – Market re-establishes support; neutral to slight upside. Probability: Lower (20–30% of observations). Next Move: Likely bounce from L₀; watch for breakout above H₁.
---
Class: HL-FD1 — Shallow Retrace (0.75 < z < 1.0)
Levels │ L0 │ H1 │ L2 │
≈H1 │ │ • │ │
HL-FD1 band │ │ │ • │
HL-FD2 band │ │ │ │
HL-FD3 band │ │ │ │
HL-FD4 band │ │ │ │
= L0 │ • │ │ │
Below L0 │ │ │ │Interpretation: L₂ retraces only 0–25% of swing (H₁ - L₀). Market Regime: Shallow Pullback – Minor consolidation; bullish continuation likely. Probability: Most common HL sub-class (35–40% of HL). Next Move: Quick recovery; minimal hesitation; buyers in control.
---
Class: HL-FD2 — Mid-Upper Retrace (0.50 < z ≤ 0.75)
Levels │ L0 │ H1 │ L2 │
≈H1 │ │ • │ │
HL-FD1 band │ │ │ │
HL-FD2 band │ │ │ • │
HL-FD3 band │ │ │ │
HL-FD4 band │ │ │ │
= L0 │ • │ │ │
Below L0 │ │ │ │Interpretation: L₂ retraces 25–50% of swing (Fibonacci 38.2%, 50%). Market Regime: Moderate Pullback – Standard retracement; consolidation before continuation. Probability: Second-most common HL sub-class (25–30% of HL). Next Move: May establish support; decision point; watch above H₁ for breakout signal.
---
Class: HL-FD3 — Mid-Lower Retrace (0.25 < z ≤ 0.50)
Levels │ L0 │ H1 │ L2 │
≈H1 │ │ • │ │
HL-FD1 band │ │ │ │
HL-FD2 band │ │ │ │
HL-FD3 band │ │ │ • │
HL-FD4 band │ │ │ │
= L0 │ • │ │ │
Below L0 │ │ │ │Interpretation: L₂ retraces 50–75% of swing (Fibonacci 61.8%). Market Regime: Deep Pullback – Significant retracement; sellers testing conviction. Probability: Less common HL sub-class (20–25% of HL). Next Move: Risk/reward tight; reversal risk growing; tight stops required.
---
Class: HL-FD4 — Deep Retrace (0 < z ≤ 0.25)
Levels │ L0 │ H1 │ L2 │
≈H1 │ │ • │ │
HL-FD1 band │ │ │ │
HL-FD2 band │ │ │ │
HL-FD3 band │ │ │ │
HL-FD4 band │ │ │ • │
= L0 │ • │ │ │
Below L0 │ │ │ │Interpretation: L₂ retraces 75–100% of swing (approaches L₀ but doesn't break below). Market Regime: Nearly Complete Retrace – Significant selling pressure; buyers barely hold. Probability: Least common HL sub-class (10–15% of HL). Next Move: Highest reversal risk; trendline break possible; wait for confirmation close above L₂.
---
Class: LL-FD1 — Micro Undercut (o ≤ q₂₀)
Levels │ L0 │ H1 │ L2 │
≈H1 │ │ • │ │
HL-FD bands │ │ │ │
= L0 │ • │ │ │
LL-FD1 band │ │ │ • │
LL-FD2 band │ │ │ │
LL-FD3 band │ │ │ │
LL-FD4 band │ │ │ │Interpretation: L₂ breaks below L₀ by ≤20th percentile of overshoot depth (minimal). Market Regime: Micro Undercut – Brief failure break; contained spillover; short seller test. Probability: Most common LL sub-class (40–50% of LL). Next Move: Quick reversal likely; spike play; watch for high-vol bounce.
Flags:
- +S (Spike): If undercut occurs in single bar, expect rapid reversal.
- +C (Close below L₀): Check if close is below L₀; stronger commitment if yes.
---
Class: LL-FD2 — Shallow Undercut (q₂₀ < o ≤ q₄₀)
Levels │ L0 │ H1 │ L2 │
≈H1 │ │ • │ │
HL-FD bands │ │ │ │
= L0 │ • │ │ │
LL-FD1 band │ │ │ │
LL-FD2 band │ │ │ • │
LL-FD3 band │ │ │ │
LL-FD4 band │ │ │ │Interpretation: L₂ breaks below L₀ by 20–40th percentile (small overshoot). Market Regime: Shallow Undercut – Moderate panic; test of sellers; probable reversal. Probability: Second-most common LL sub-class (25–30% of LL). Next Move: Reversal not guaranteed; may establish lower support; watch for continuation down.
---
Class: LL-FD3 — Deep Undercut (q₄₀ < o ≤ q₆₀)
Levels │ L0 │ H1 │ L2 │
≈H1 │ │ • │ │
HL-FD bands │ │ │ │
= L0 │ • │ │ │
LL-FD1 band │ │ │ │
LL-FD2 band │ │ │ │
LL-FD3 band │ │ │ • │
LL-FD4 band │ │ │ │Interpretation: L₂ breaks below L₀ by 40–60th percentile (significant overshoot). Market Regime: Deep Undercut – Major breakdown; strong selling; structural support broken. Probability: Less common LL sub-class (15–20% of LL). Next Move: Extended downtrend likely; new support needed; risk management critical.
---
Class: LL-FD4 — Extreme Undercut (o > q₆₀)
Levels │ L0 │ H1 │ L2 │
≈H1 │ │ • │ │
HL-FD bands │ │ │ │
= L0 │ • │ │ │
LL-FD1 band │ │ │ │
LL-FD2 band │ │ │ │
LL-FD3 band │ │ │ │
LL-FD4 band │ │ │ • │Interpretation: L₂ breaks below L₀ by >60th percentile (extreme overshoot, rare event). Market Regime: Extreme Undercut – Panic, shock, tail event; severe breakdown. Probability: Rare; <10% of LL. Next Move: Crisis mode; VaR breach possible; free-fall likely; watch for capitulation reversal.
Flags:
- +S (Spike): Extreme undercuts often spike; mean reversion likely if single-bar.
- +C (Close below L₀): Critical signal; shows commitment; downside extension very likely.
---
Optional Sub-Classification Flags
Attach to any variant for richer context:
| Flag | Meaning | Market Signal |
|---|---|---|
| +C | Any close < L₀ between H₁→L₂ | Stronger bearish commitment; downtrend escalates |
| +S | L₂ occurs in single bar (spike) | Likely sharp reversal; mean reversion setup; reduced persistence |
| +X | No close below L₀, high wicks only | False break; liquidity grab; setup for trap |
Example labels: HL-FD2+C, LL-FD4+S+C, EL+X.
---
Binning Edge Computation
See binning-methodology.md for complete FD procedure.
Quick summary:
1. Collect 2–3 years of UP–DOWN segments per timeframe. 2. Compute z for HL cases, o for LL cases. 3. Run Freedman–Diaconis: h = 2·IQR(x)·n^(-1/3) 4. Determine K = clip(⌈(max - min) / h⌉, 3, 6) 5. Create uniform bin edges; assign variant labels. 6. Recompute monthly or quarterly to track regime drift.
---
Market Regime Mapping
| Variant Class | Typical Market Regime | Entry Signal | Stop Loss | Target |
|---|---|---|---|---|
| EL | Retest | Long from L₀+ε | L₀-ε | H₁ + ΔH |
| HL-FD1 | Shallow Pullback | Long from L₂ | L₂-ε | H₁ + ΔH |
| HL-FD2 | Moderate Pullback | Long at confirmation | L₂-ε | Prior H + ΔH |
| HL-FD3 | Deep Pullback | Reduced size; wait | L₂-ε | Support + ΔH |
| HL-FD4 | Nearly Complete | Extreme risk; avoid | L₂-ε | Critical |
| LL-FD1 | Micro Undercut | Short spike trade | L₂+spike | L₀ |
| LL-FD2 | Shallow Undercut | Wait for reversal | Breakout | L₀ |
| LL-FD3 | Deep Undercut | Short continuation | Reversal | New lows |
| LL-FD4 | Extreme Undercut | Crisis mode; hedge | Capitulation | TBD |
---
Implementation Checklist
- [ ] Compute ε (tolerance band) per epsilon-tolerance.md
- [ ] Build UP–DOWN triplet list from ZigZag pivots
- [ ] Calculate z = (L₂ - L₀) / (H₁ - L₀) for each triplet
- [ ] Classify EL if |z| ≤ εᵣ; otherwise HL if z > 0 else LL
- [ ] For HL: run FD binning on {z ∈ (εᵣ, 1)}
- [ ] For LL: compute o = -z, run FD binning on {o > εᵣ}
- [ ] Assign labels HL-FD1..k and LL-FD1..j
- [ ] Add optional flags (+C, +S, +X)
- [ ] Store in time-series database with segment_id, timeframe, regime labels
- [ ] Backtest regime-conditional trading rules
---
Typical Distribution
On EURUSD 2–3 year history (all timeframes combined):
- EL: 20–30% (retests common but not dominant)
- HL-FD1: 20–25% (shallow pullbacks most common)
- HL-FD2: 15–20% (moderate retraces)
- HL-FD3: 10–15% (deeper pullbacks less frequent)
- HL-FD4: 5–10% (near-complete retraces rare)
- LL-FD1: 8–12% (micro undercuts most common LL)
- LL-FD2: 4–8% (shallow undershoots less frequent)
- LL-FD3: 2–5% (deep undershoots infrequent)
- LL-FD4: <2% (extreme undershoots extremely rare)
Note: Exact percentages vary by timeframe, symbol, and market regime (trending vs ranging).
---
Next Steps
1. Implement FD binning on your EURUSD data 2. Backtest returns per variant across M5, M15, M30, H1, H4, D1 3. Compare to other classification schemes (Fibonacci, quantile-based, etc.) 4. Combine variants with volume, spreads, volatility for multi-factor models 5. Extend to 3-pivot UP–DOWN–UP for richer feature set
---
Reference: Methodologically complete; 9 variants cover all UP–DOWN configurations. Basis: Freedman–Diaconis binning on volatility-normalized coordinate.