# 02 · Price-Structure Patterns

Patterns that come from the **shape of price** itself: harmonic (Fibonacci)
patterns, classical chart patterns, and candlestick patterns. Indicator-based
signals (RSI, MACD, moving averages, Bollinger, etc.) live in
[`03-indicators.md`](03-indicators.md).

Every detector here implements the [`Detector`](01-architecture.md#3-the-detector-interface-build-every-pattern-to-this)
interface and emits the shared [`Signal`](01-architecture.md#22-signal-schema).

---

## 0. Shared primitive: swing (pivot) detection

Harmonic and chart patterns are built from **swing highs and swing lows**
(pivots). Build this once; every structural detector consumes it.

**Definition.** A *swing high* at index `i` (depth `d`) is a bar whose high is
strictly greater than the highs of the `d` bars on each side; a *swing low* is
the mirror. Use fractal depth `d = 2..5` (bigger `d` ⇒ fewer, more significant
pivots). ZigZag with an ATR/percentage threshold is the more robust variant and
is recommended for harmonic detection.

```
function swings(candles, depth=3, atrMult=0):
  pivots = []
  for i in depth .. len-1-depth:
    isHigh = all(candles[i].high >  candles[i±k].high for k in 1..depth)
    isLow  = all(candles[i].low  <  candles[i±k].low  for k in 1..depth)
    if isHigh: pivots.push({type:'H', idx:i, ts, price:candles[i].high})
    if isLow:  pivots.push({type:'L', idx:i, ts, price:candles[i].low})
  # collapse consecutive same-type pivots, keeping the more extreme one
  # (optional) filter legs smaller than atrMult * ATR to cut noise
  return alternating(pivots)     // guaranteed H,L,H,L,… sequence
```

Helper used throughout: `retrace(A,B,C) = |C - B| / |B - A|` — the Fibonacci
ratio of leg `BC` relative to leg `AB`.

---

## 1. Harmonic Patterns

Harmonic patterns are **5-point (XABCD)** or **4-point (ABCD)** structures
whose legs sit at specific Fibonacci ratios. When price reaches the final `D`
point (the **PRZ — Potential Reversal Zone**), the pattern predicts a reversal.

**The five ratios that define each pattern** (bullish naming; mirror for
bearish). Let legs be `XA, AB, BC, CD`, and `AD` measured against `XA`:

| Pattern | B retrace of XA | BC of AB | CD of BC (or XA) | D vs X | Bias at D |
|---------|-----------------|----------|-------------------|--------|-----------|
| **ABCD** (no X) | — | BC = 0.618–0.786 of AB | CD = 1.272–1.618 of BC (AB≈CD) | — | reversal |
| **Gartley** | 0.618 | 0.382–0.886 | 1.13–1.618 | **D = 0.786 of XA** | reversal, D inside XA |
| **Bat** | 0.382–0.500 | 0.382–0.886 | 1.618–2.618 | **D = 0.886 of XA** | reversal, D inside XA |
| **Butterfly** | 0.786 | 0.382–0.886 | 1.618–2.240 | **D = 1.272–1.618 of XA** | reversal, D **beyond** X |
| **Crab** | 0.382–0.618 | 0.382–0.886 | 2.618–3.618 | **D = 1.618 of XA** | reversal, deepest D |
| **Deep Crab** | 0.886 | 0.382–0.886 | 2.240–3.618 | **D = 1.618 of XA** | reversal |
| **Cypher** | 0.382–0.618 | C = 1.272–1.414 of XA | — | **D = 0.786 of XC** | reversal |
| **Shark** | — | B in 1.13–1.618 of prior | C = 1.13 | **D = 0.886–1.13 of XC** | reversal (5-0 kin) |
| **3-Drives** | 3 symmetric drives, each 1.272–1.618 ext, retraces 0.618 | | | | exhaustion reversal |

> These are the widely published Scott Carney / harmonic-trading ratios. Keep
> each ratio as a **[min,max] tolerance band** (± ~0.05, or ± the value in the
> table) — real charts are never exact.

### 1.1 Reference detector — Gartley (the template for all XABCD)

**What it is.** A retracement/continuation reversal. In a *bullish* Gartley the
market makes X(low)→A(high)→B(low)→C(high)→D(low), and D sits at the 0.786
retracement of XA. You **buy at D**, stop below X, target Fib extensions of AD.

**Detection algorithm**

```
detect_gartley(candles, ind, params):
  P = ind.swings(depth=params.depth)          // alternating pivots
  if len(P) < 5: return []
  # take the 5 most recent alternating pivots as X,A,B,C,D
  [X,A,B,C,D] = P[-5:]
  bull = (X.type=='L' and A.type=='H' and B.type=='L' and C.type=='H' and D.type=='L')
  bear = (X.type=='H' and A.type=='L' and B.type=='H' and C.type=='L' and D.type=='H')
  if not (bull or bear): return []

  XA = abs(A.price - X.price)
  AB = abs(B.price - A.price); BC = abs(C.price - B.price); CD = abs(D.price - C.price)
  ab_xa = AB / XA                             // want ≈ 0.618
  bc_ab = BC / AB                             // want 0.382..0.886
  cd_bc = CD / BC                             // want 1.13..1.618
  ad_xa = abs(D.price - A.price) / XA         // want ≈ 0.786 (D retrace of XA)

  if within(ab_xa,0.618,tol) and inRange(bc_ab,0.382,0.886)
     and inRange(cd_bc,1.13,1.618) and within(ad_xa,0.786,tol):
     side = bull ? 'long' : 'short'
     entry = D.price
     stop  = bull ? X.price - k*ATR : X.price + k*ATR   // just beyond X
     t1 = D.price ± 0.382*|A-D|; t2 = D.price ± 0.618*|A-D|   // Fib of AD
     strength = 1 - avgAbsDeviationFromIdealRatios          // 0..1
     return [Signal{detector:'harmonic.gartley', side, action:'entry',
                    price:D.price, strength,
                    suggested:{entry,stop,targets:[t1,t2],rr:(t1-entry)/(entry-stop)},
                    evidence:{points:{X,A,B,C,D},
                              ratios:{'AB/XA':ab_xa,'BC/AB':bc_ab,'CD/BC':cd_bc,'AD/XA':ad_xa},
                              note:`${side} Gartley: D at ${ad_xa.toFixed(3)} of XA`}}]
  return []
```

**Confirmation (recommended).** Don't fire the instant price *touches* D. Wait
for a **reversal candle** in the PRZ (bullish/bearish engulfing or a close back
through a short EMA) — this cuts false positives dramatically. Combine with
RSI at oversold/overbought for a two-factor entry.

### 1.2 Butterfly (the one in the prompt)

Same engine as Gartley, different ratio bands: **B = 0.786 of XA**,
**CD = 1.618–2.24 of BC**, and critically **D is a 1.272–1.618 *extension* of
XA — i.e. D is *beyond* X.** For a *bullish* butterfly, `D` prints **below** the
starting `X`; you **buy at D**, stop a little below D, target retracements of
CD/AD. (This is exactly the behaviour described in the original request.)

### 1.3 Bat / Crab / Deep Crab / Cypher / Shark

Reuse the Gartley/XABCD engine; only the ratio table changes. Implement one
generic `detectHarmonic(spec)` where `spec` is the row from the table above:

```ts
type HarmonicSpec = {
  id: string;                       // "harmonic.crab"
  B_of_XA?: [number, number];       // allowed retrace band for B
  BC_of_AB: [number, number];
  CD_of_BC?: [number, number];
  D_of_XA: [number, number];        // PRZ location
  extension?: boolean;              // true ⇒ D beyond X (Butterfly/Crab)
};
```

Then `harmonic.gartley`, `harmonic.butterfly`, `harmonic.bat`, `harmonic.crab`,
`harmonic.deep_crab`, `harmonic.cypher`, `harmonic.shark` are all one function
+ seven specs. **This is the payoff of the `Detector` contract.**

### 1.4 ABCD (4-point, no X)

The simplest harmonic. `A→B→C→D` where `BC` retraces 0.618–0.786 of `AB` and
`CD` = 1.272–1.618 of `BC`, with **AB ≈ CD in both price and time** (the
"AB=CD" symmetry). Buy/sell at `D`. Use it as the beginner detector and as a
*sub-pattern confirmation* inside the larger XABCD legs (the CD leg of a Gartley
is itself often an AB=CD).

### 1.5 Three Drives

Three consecutive symmetric pushes (drives), each an ~1.272–1.618 extension
separated by ~0.618 retracements. Signals **exhaustion** at the third drive —
reverse. Detect by finding three equal-extension legs in the pivot series.

---

## 2. Classical Chart Patterns

Geometric formations on the pivot/price series. Unlike harmonics they don't
need exact ratios — they need **structure + a confirmed break**. **The break is
the signal**, not the shape.

### 2.1 Head & Shoulders (and Inverse) — reversal

**Structure (bearish top).** Left shoulder → higher **Head** → lower right
shoulder, with the two intervening lows forming the **neckline**. **Sell when
price closes below the neckline.** Inverse H&S is the mirror (buy on close above
neckline).

```
detect_hns(P):                     // P = recent pivots
  find L(sh) H(head) L H(rsh) with:
     head.price > leftShoulder.price and head.price > rightShoulder.price
     leftShoulder.price ≈ rightShoulder.price (± tol)     // symmetry
  neckline = line through the two lows between the peaks
  on the CURRENT bar: if close < neckline(now):           // confirmation
     entry = close
     stop  = rightShoulder.price
     target= entry - (head.price - neckline_at_head)       // measured move
     emit Signal(short, 'chart.head_shoulders', evidence:{points, note})
```

**Measured-move target** = neckline break price ± the height from head to
neckline. This "height projection" rule is shared by most chart patterns.

### 2.2 Double / Triple Top & Bottom — reversal

Two (or three) peaks at ~equal price with a trough between (top), or the
mirror (bottom). **Trigger:** close beyond the intervening trough/peak
("confirmation line"). Target = pattern height projected from the break.
`chart.double_top`, `chart.double_bottom`, `chart.triple_top/bottom`.

### 2.3 Triangles — continuation (usually) / breakout

- **Ascending:** flat resistance + rising support ⇒ bullish bias; buy on close
  above resistance.
- **Descending:** flat support + falling resistance ⇒ bearish; sell on break
  below support.
- **Symmetrical:** converging trendlines; trade the **direction of the
  breakout** (either side) with volume confirmation.

Detect by fitting two trendlines to alternating pivot highs and lows over the
last *N* pivots and checking convergence + slope signs. Target = triangle base
height from breakout. `chart.triangle_asc/desc/sym`.

### 2.4 Wedges — reversal / continuation

Both trendlines slope the **same** direction and converge. **Rising wedge** ⇒
bearish (break down). **Falling wedge** ⇒ bullish (break up). Same fitting logic
as triangles; the tell is that both lines share slope sign.

### 2.5 Flags & Pennants — continuation

A sharp move (**flagpole**) then a small counter-trend consolidation (parallel
channel = flag, small triangle = pennant). **Trade the resumption:** break in
the flagpole's direction. Target = flagpole length projected from the breakout.
Great on intraday timeframes. `chart.bull_flag/bear_flag/pennant`.

### 2.6 Cup & Handle — continuation (bullish)

Rounded "cup" (U-shape) then a small downward "handle" drift; buy on close above
the handle's resistance. Target = cup depth projected up. `chart.cup_handle`.

### 2.7 Rectangle / Channel / Range — breakout

Price bounded by roughly horizontal support and resistance. Two ways to trade:
**range** (buy support, sell resistance) or **breakout** (trade the close
outside the box). Detect via clustered pivot highs/lows at similar prices.
`chart.rectangle`.

> **Rounding bottom, Diamond, Broadening** and other rarer forms follow the
> same recipe: *define the geometry from pivots → wait for the confirmed
> close-through → project the measured move.* Add them as extra detectors when
> needed.

---

## 3. Candlestick Patterns

Single- or multi-bar formations read from OHLC. They are **weak alone** — use
them as **confirmation** at a level (a harmonic D, a support line, an oversold
RSI). Define helpers once:

```
body(c)      = abs(c.close - c.open)
range(c)     = c.high - c.low
upperWick(c) = c.high - max(c.open,c.close)
lowerWick(c) = min(c.open,c.close) - c.low
bullish(c)   = c.close > c.open
```

### 3.1 Single-bar

| Pattern | Rule (bullish reversal shown) | Detector id |
|---------|-------------------------------|-------------|
| **Hammer** | small body near top, `lowerWick ≥ 2·body`, tiny upperWick, in a downtrend | `candle.hammer` |
| **Hanging Man** | same shape as hammer but in an **up**trend ⇒ bearish | `candle.hanging_man` |
| **Inverted Hammer** | small body near bottom, `upperWick ≥ 2·body`, downtrend | `candle.inverted_hammer` |
| **Shooting Star** | inverted-hammer shape in an **up**trend ⇒ bearish | `candle.shooting_star` |
| **Doji** | `body ≤ 0.1·range` ⇒ indecision (dragonfly/gravestone variants) | `candle.doji` |
| **Marubozu** | full body, `wicks ≈ 0` ⇒ strong continuation | `candle.marubozu` |

### 3.2 Two-bar

| Pattern | Rule | Detector id |
|---------|------|-------------|
| **Bullish Engulfing** | prev bearish; current bullish; current body **engulfs** prev body (`open<prev.close`, `close>prev.open`) | `candle.bull_engulfing` |
| **Bearish Engulfing** | mirror of above | `candle.bear_engulfing` |
| **Bullish Harami** | large bearish bar, then small bar inside its body | `candle.bull_harami` |
| **Piercing Line** | bearish bar, then bullish bar closing **> 50%** into it | `candle.piercing` |
| **Dark Cloud Cover** | mirror of piercing (bearish) | `candle.dark_cloud` |
| **Tweezer Bottom/Top** | two bars sharing near-equal lows (bottom) / highs (top) | `candle.tweezer` |

### 3.3 Three-bar

| Pattern | Rule | Detector id |
|---------|------|-------------|
| **Morning Star** | bearish bar → small-body/doji → strong bullish bar closing into bar 1's body | `candle.morning_star` |
| **Evening Star** | mirror ⇒ bearish top | `candle.evening_star` |
| **Three White Soldiers** | three rising bullish bars, each opening within prior body | `candle.three_soldiers` |
| **Three Black Crows** | mirror ⇒ bearish | `candle.three_crows` |

**Trend context is mandatory.** A hammer is only bullish *after a decline*.
Gate every candlestick detector with a trend filter (e.g. price below EMA50, or
a recent swing-low sequence) so it fires in the right context. Set
`strength` from body/wick ratios and the strength of the preceding trend.

---

## 4. Per-family "Claude task" prompts

Paste these to implement each family. Each assumes doc 01's types exist.

> **Claude task — Swings + harmonic engine**
> Create `engine/indicators/swings.ts` implementing `swings(candles, depth, atrMult)`
> per doc 02 §0 (alternating pivot list). Then create
> `engine/detectors/harmonic.ts` exporting a generic `makeHarmonic(spec: HarmonicSpec)`
> factory and the seven concrete detectors (gartley, butterfly, bat, crab,
> deep_crab, cypher, shark) plus `abcd` and `three_drives`, all implementing
> `Detector`. Ratios and tolerances per the table in §1. Require a PRZ
> reversal-candle confirmation before emitting. **Acceptance:** unit tests feed
> synthetic XABCD series with known ratios and assert the right detector fires
> with the right `side` and populated `evidence.ratios`; a series that violates
> one ratio must emit nothing.

> **Claude task — Chart patterns**
> Create `engine/detectors/chart.ts` with detectors for head_shoulders (+inverse),
> double/triple top & bottom, triangle (asc/desc/sym), wedge (rising/falling),
> flag/pennant, cup_handle, rectangle. Each fits lines/levels to pivots and
> emits **only on a confirmed close-through** with a measured-move target.
> **Acceptance:** fixture series for each pattern fire exactly once on the break
> bar; `suggested.stop` and `suggested.targets` follow the height-projection
> rules in §2.

> **Claude task — Candlesticks**
> Create `engine/detectors/candles.ts` with the single/two/three-bar detectors
> in §3, each gated by a trend-context filter and emitting `strength` from
> body/wick geometry. **Acceptance:** hand-built OHLC fixtures for each pattern
> pass; the same shape in the *wrong* trend context does **not** emit.

Continue to indicator signals in [`03-indicators.md`](03-indicators.md).
