# Trend candles

> Repaint the chart's candles by trend state, fade them when the trend is stretched thin, and mark every trend flip.

Colouring candles by trend turns a judgement into something you see at a glance. This script draws its own candles on top of the price pane, using the same open, high, low and close as the chart, and colours them by a trend state built from two independent reads: a Supertrend-style ATR trailing line for direction and an EMA slope for momentum. When both agree, candles take the up or down colour. When they disagree, candles turn grey. The colour also gets stronger the further price is from its EMA, measured in ATRs, so you see when a trend is extended.

A marker appears on the first bar of each new trend, and an optional line shows where the trend would flip.

```flowscope title="trend-candles.fs"
script "Trend candles"

input (
  factor = input.float(3.0, title: "Trailing line (x ATR)", min: 0.5, max: 10.0)
  atrLen = input.int(10, title: "ATR length", min: 2, max: 100)
  emaLen = input.int(34, title: "EMA length", min: 2, max: 300)
  slopeBars = input.int(3, title: "Slope lookback (bars)", min: 1, max: 20)
  maxStretch = input.float(3.0, title: "Full colour at (x ATR from EMA)", min: 0.5, max: 10.0)
  showLine = input.bool(true, title: "Show trailing line")
)

data chart = subscribe(data.ohlcv)

type Flip {
  trend: string
  price: float
}

state (
  lowerBand: float? = null
  upperBand: float? = null
  direction = 1
  regime = 0
  tone = color.gray
)

plot (
  candles = plot.candle(title: "Trend candles")
  trail = plot.line(title: "Trailing line", width: 2, style: linestyle.step)
  flipUp = plot.marker(title: "Uptrend starts", color: color.green, shape: shape.up, size: 5.0)
  flipDown = plot.marker(title: "Downtrend starts", color: color.red, shape: shape.down, size: 5.0)
)

alert flip = alert(title: "Trend flip", onClose: true, payload: Flip)

// The forming candle takes the colour of the last confirmed state.
on chart.update {
  candles.plot(chart.open, chart.high, chart.low, chart.close, bodyColor: tone, wickColor: tone, borderColor: tone)
}

on chart.close {
  let h = chart.high
  let l = chart.low
  let c = chart.close
  let prevClose = chart.close[1]
  let atr = chart.atr(atrLen)
  let ema = ta.ema(c, emaLen)
  let slope = ta.change(ema, slopeBars)
  if h == null || l == null || c == null || atr == null {
    return
  }

  // An ATR band around the bar's middle that only ever tightens while the
  // close stays on its side; crossing the active band flips the direction.
  let mid = (h + l) / 2.0
  let lb = lowerBand
  let ub = upperBand
  let lower = lb != null && prevClose != null && prevClose > lb ? math.max(mid - factor * atr, lb) : mid - factor * atr
  let upper = ub != null && prevClose != null && prevClose < ub ? math.min(mid + factor * atr, ub) : mid + factor * atr
  if direction == 1 && c < lower {
    direction = -1
  } else if direction == -1 && c > upper {
    direction = 1
  }
  lowerBand = lower
  upperBand = upper
  let line = direction == 1 ? lower : upper
  trail.plot(showLine ? line : null, color: direction == 1 ? color.green : color.red)

  // Direction from the trailing line, momentum from the EMA slope.
  let rising = (slope ?? 0.0) > 0.0
  let falling = (slope ?? 0.0) < 0.0
  let next = direction == 1 && rising ? 1 : direction == -1 && falling ? -1 : 0

  // Stretch: distance from the EMA in ATRs, 0 .. maxStretch.
  let stretch = ema != null && atr > 0.0 ? math.abs(c - ema) / atr : 0.0
  let base = match next {
    1 => color.green
    -1 => color.red
    _ => color.gray
  }
  tone = color.gradient(stretch, 0.0, maxStretch, color.withAlpha(base, 110), base)
  candles.plot(chart.open, h, l, c, bodyColor: tone, wickColor: tone, borderColor: tone)

  // A flip is the first bar of a new non-neutral regime.
  let isFlipUp = next == 1 && regime != 1
  let isFlipDown = next == -1 && regime != -1
  regime = next
  flipUp.plot(isFlipUp ? l : null)
  flipDown.plot(isFlipDown ? h : null)
  if isFlipUp || isFlipDown {
    flip.trigger(Flip { trend: isFlipUp ? "up" : "down", price: c })
  }
}
```

## How it works

### Your own candles

`plot.candle` draws a full candle per bar from four values. Fed with the chart's own open, high, low and close, it sits exactly on top of the chart's candles. To see only the trend candles, hide the chart's own candles in the chart settings; the script does not need them.

The trend state is decided on confirmed bars in `on chart.close`. `on chart.update` paints the forming candle in the colour of the last confirmed state, so a colour never changes after the fact; when the bar closes, the close handler paints it again with its own state.

### A trailing line from state

The trailing line is what a Supertrend computes: a band `factor` ATRs below the bar's middle while the trend is up, above it while the trend is down. The band only tightens while the previous close stays on its side, so it trails price. When a close crosses the active band, the direction flips. The two bands and the direction live in `state`, because each bar needs the previous bar's values; they are copied to locals (`lb`, `ub`) so null checks narrow them.

### Two reads, one state

The EMA slope over `slopeBars` bars, from `ta.change`, adds momentum. The regime is `+1` only when both say up, `-1` only when both say down, and `0` in between. Grey candles are a reminder that the two disagree, often in a range or a turn.

All `ta.*` calls happen at the top of the handler, before the early `return`, so each keeps a consistent history.

### Intensity from the stretch

`color.gradient(value, bottom, top, start, end)` blends between two colours. The script blends from a translucent version of the trend colour at the EMA to the full colour at `maxStretch` ATRs away. Values outside the range are clamped, so anything beyond `maxStretch` gets the full colour.

### Flips

A flip is the first bar of a new up or down regime: the regime of the previous bar is kept in `state` and compared. A move from up to neutral and back to up counts as a new flip; that is deliberate, because the neutral bars mean the two reads disagreed for a while. Because everything runs on confirmed bars, a marker never appears and disappears on the live bar; see [repainting](/docs/scripting/guides/repainting).

## Variations

- **Order-flow trend.** Replace the EMA slope with the sign of a rolling sum of volume delta from `data.vd`, so candles turn green only when buyers are net aggressive.
- **Higher-timeframe colouring.** Compute the state in `on htf.close` for a 1h subscription and colour 5-minute candles by the hourly trend. See [HTF trend filter](/docs/scripting/examples/htf-trend-filter).
- **Bars instead of candles.** `plot.bar` takes the same four values and draws OHLC bars.
- **Heikin-Ashi.** Compute Heikin-Ashi values in state and feed them to `plot.candle` in a separate pane.

## Related

- [Colors and gradients](/docs/scripting/guides/colors-and-gradients)
- [`plot` reference](/docs/scripting/reference/catalog/plot), [`color` reference](/docs/scripting/reference/catalog/color)
