MMTMMT Docs

Swing levels

Turn confirmed pivots into support and resistance rays with pooled lines and labels.

This example tracks the most recent confirmed swing high and swing low, extends each as a level until price trades through it, and tags it with a formatted price label. It covers entity pools, pivot confirmation lag, and str.format.

Pivot-based support and resistance rays with price labels rendered from Binance Futures BTC/USD market data
script "Swing Levels"

data chart = subscribe(data.ohlcv)

input (
  pivotLen        = input.int(10, title: "Pivot Strength", min: 1, max: 50)
  resistanceColor = input.color(color.red, title: "Resistance")
  supportColor    = input.color(color.green, title: "Support")
)

state (
  lines  = entities.linePool(max: 2)
  labels = entities.labelPool(max: 2)
  highLevel: float? = null
  highStart: time?  = null
  lowLevel:  float? = null
  lowStart:  time?  = null
)

fn drawLevel(key: string, start: time, end: time, level: float, labelY: float, tag: string, levelColor: color) {
  lines.get(key).set(
    x1: start,
    y1: level,
    x2: end,
    y2: level,
    color: levelColor,
    width: 1.5,
    style: linestyle.dashed,
  )
  labels.get(key).set(
    x: start,
    y: labelY,
    text: str.format("{0} {1:.1}", tag, level),
    color: levelColor,
    size: 12.0,
  )
}

on chart.update {
  let now = (chart.time ?? time.fromUnixSeconds(0))
  let last = (chart.close ?? 0.0)
  // Lift labels off the level line by a volatility-scaled offset so they
  // stay readable at any price scale.
  let pad = (chart.atr(14) ?? 0.0) * 0.8

  // A pivot confirms pivotLen bars after it happened, so anchor the level
  // back at the bar that actually set it.
  let pivotHigh = ta.pivotHigh(chart.high, pivotLen, pivotLen)
  if pivotHigh != null {
    highLevel = pivotHigh
    highStart = now - time.bars(pivotLen)
  }

  let pivotLow = ta.pivotLow(chart.low, pivotLen, pivotLen)
  if pivotLow != null {
    lowLevel = pivotLow
    lowStart = now - time.bars(pivotLen)
  }

  // Extend each level to the current bar until price trades through it.
  if highLevel != null && last > (highLevel ?? 0.0) {
    lines.delete("high")
    labels.delete("high")
    highLevel = null
  } else if highLevel != null {
    drawLevel("high", (highStart ?? now), now, (highLevel ?? 0.0), (highLevel ?? 0.0) + pad, "R", resistanceColor)
  }

  if lowLevel != null && last < (lowLevel ?? 0.0) {
    lines.delete("low")
    labels.delete("low")
    lowLevel = null
  } else if lowLevel != null {
    drawLevel("low", (lowStart ?? now), now, (lowLevel ?? 0.0), (lowLevel ?? 0.0) - pad, "S", supportColor)
  }
}

Pivots confirm late

ta.pivotHigh(chart.high, 10, 10) only returns a value once ten bars have printed on each side of the candidate. The level is real from the pivot bar onward, so the script rewinds the anchor with now - time.bars(pivotLen) instead of starting the ray at the confirmation bar.

Entities are keyed and finite

Pools are declared with a hard max and addressed by key. lines.get("high") returns the same line every bar, so re-issuing set extends it in place. Deleting the key when price breaks the level removes the ray instead of leaving a stale artifact.

Factor the drawing, not the logic

Both sides share one drawLevel function that positions a line and its label together. The break/extend decision stays inline per side, where the asymmetry (break above vs. break below) is easiest to read.

On this page