Free lesson · Charts & patterns
Trends, ranges, breakouts and chart structures
Open interactive lessonPractice calculationsExplore labs
Start with the idea
Market structure describes relationships between price regions over time. Start with a trailing high and low before fitting complicated shapes. A useful rule has an explicit time when it becomes observable.
Symbols, units & horizon
- Uₜ: upper channel boundary from n prior highs
- L*ₜ: lower channel boundary from n prior lows, not current candle low
- Hₜ,Lₜ,Cₜ: high, low and close of bar t
- n: trailing window length in bars
- j: prior-bar offset
- 1{condition}: indicator equal to 1 when true and 0 otherwise
- i: candidate pivot index
- k: required bars on each side
- t_known: earliest confirmation index
- b: optional volatility-buffer multiple
- ATR: average true range in price units
When and why to use this
Use causal channels for breakout research and confirmed pivots for swing-based stop or pattern definitions. Test false breakouts and gaps as part of the strategy, not as data to discard.
A trend is a sequence at a chosen scale. Higher confirmed highs and higher confirmed lows form one operational definition of an uptrend. A range is a region with repeated tests of boundaries and no sustained progression under the chosen rule. Support and resistance are zones inferred from past observations, not exact walls that must hold.
Construct a boundary from completed history
- At t with n=3, expand the upper maximum as max(Hₜ₋₁,Hₜ₋₂,Hₜ₋₃). The current high Hₜ is deliberately absent.
- An indicator is 1 if the condition holds and 0 otherwise. With prior highs 101,102,100, the boundary is 102; close 103 gives 1. A next-bar entry waits until after that close.
A buffer can be added as Cₜ>Uₜ+b·ATRₜ₋₁. Define b before testing; both the channel and volatility input are available before the current close.
A channel breakout compares today’s close with a boundary formed before today’s bar. Including today’s high in the upper boundary makes a strict close-above-high rule impossible. Requiring a close above a band adds confirmation but changes the entry price and delay. A retest is a later revisit to the old boundary; specify zone width, deadline and invalidation.
| Structure | Operational definition to write | Failure to test |
|---|---|---|
| Double top / bottom | Two confirmed extrema within a price tolerance, minimum spacing, intervening neckline and a later neckline break | Pattern is labelled before the second extreme is confirmed |
| Head and shoulders / inverse | Three confirmed extrema with a more extreme middle peak/trough, shoulder tolerance and neckline rule | Many subjective choices create invisible optimisation |
| Triangle / wedge | Converging fitted boundaries using only already confirmed pivots | Retrofitting lines after seeing the breakout |
| Flag / pullback | Prior impulse plus a bounded retracement/consolidation and a resumption trigger | Calling every pause continuation; ignoring failed impulses |
| Range / channel | Trailing boundaries, minimum touches and a permitted slope | Treating a trending boundary as stable support |
Account for confirmation delay
- For k=2 and candidate bar i=10, compare highs at 8,9,11,12 against H₁₀. Bar 12 must finish before the comparison can be completed.
- Therefore known time is i+k=12. With next-open execution, earliest bar entry is 13.
A marker drawn at bar 10 must not create a bar-10 trade. This example uses strict extrema; equal highs do not qualify.
A pivot detector that compares k bars on each side of a high needs the k later bars before the pivot is confirmed. Plotting the marker back at the high is acceptable only if the interface also records its later confirmation time. A backtest must act at confirmation, not at the earlier visually attractive high. Zigzag tools that revise their last segment have the same issue.
Python implementation
Self-contained teaching example. Python 3.10+; dependencies and input conventions are shown in the code and notation. Run in your own Python environment.
def previous_channel(bars, index, window):
if window<1 or index<window or index>=len(bars):
return None
history=bars[index-window:index] # excludes current bar
upper=max(b['h'] for b in history)
lower=min(b['l'] for b in history)
return upper,lower,bars[index]['c']>upper
def confirmed_highs(visible_bars, flank=2):
if flank<1: raise ValueError("Positive flank required")
result=[]
for i in range(flank,len(visible_bars)-flank):
neighbours=visible_bars[i-flank:i]+visible_bars[i+1:i+flank+1]
if all(visible_bars[i]['h']>b['h'] for b in neighbours):
result.append({"pivot":i,"known_at":i+flank})
return resultContinue learning
Candles, Structures & Pattern Research — all lessons- First principles: what a candle actually records
- Doji, hammer, shooting star and long-body bars
- Engulfing, inside bars and multi-candle sequences
- Trends, ranges, breakouts and chart structures
- Indicators as arithmetic: ATR, moving averages, RSI and bands
- Known strategy families: from chart idea to complete rules
- Pattern recognition: rules, features, shapelets and image models
- Quantitative recognition I: build a causal candle feature table
- Quantitative recognition II: shapelets and constrained dynamic time warping
- Quantitative recognition III: causal encoders and contrastive learning
- Quantitative recognition IV: GAF images, CNNs, transformers and visual-model audits
- Quantitative recognition V: calibrate, abstain and test the complete strategy
- Research review: what the evidence does and does not establish
Quantitative finance and development glossary · Python resources and libraries · Research sources and limitations