FRAMA and FRAMA Channels on TradingView
The Fractal Adaptive Moving Average changes its smoothing weight using a range-based estimate of price roughness. A FRAMA channel adds a separate width rule around that center. This guide explains the original calculation and defines one reproducible ATR channel extension, including its inputs, warm-up and closing-bar alerts.
Worked example and code, not a performance study. No settings or trading returns are established by this guide.
FRAMA formula: from ranges to adaptive smoothing
John Ehlers defines FRAMA using two adjacent half-windows and the full window. For an even length N, let h = N / 2. Each high-low range is divided by its own number of bars. The halves do not overlap. Read Ehlers' original paper (PDF mirror).
Nrecent = (highest high − lowest low over bars 0…h−1) / h
Nolder = (highest high − lowest low over bars h…N−1) / h
Nfull = (highest high − lowest low over bars 0…N−1) / N
D = [ln(Nrecent + Nolder) − ln(Nfull)] / ln(2)
α = clamp(exp(−4.6 × (D − 1)), 0.01, 1)
FRAMA = α × source + (1 − α) × previous FRAMA
Lower D produces a larger current-source weight; higher D produces more smoothing. At D = 1, α = 1. At D = 2, exp(−4.6) is approximately 0.010052. The 0.01 floor and 1 ceiling bound the weight. These are smoothing coefficients, not probabilities of a trend.
In this implementation, changing the source changes the filtered input only: the dimension still uses chart highs and lows. A close-based FRAMA and an hl2-based FRAMA can differ even when their dimension and alpha agree. The paper uses hl2 and length 16; this page's code keeps close and length 14 as explicit defaults.
A calculation you can reproduce
This hypothetical 16-bar window has older-half low/high 100/106 and recent-half low/high 102/108. Both halves contain 8 bars; the full range is 100 to 108. Assume the filter is warmed up, previous FRAMA is 100 and the current source is 104. These are demonstration inputs, not market observations.
| Step | Calculation | Result |
|---|---|---|
| Recent half | (108 − 102) / 8 | 0.75 |
| Older half | (106 − 100) / 8 | 0.75 |
| Full window | (108 − 100) / 16 | 0.50 |
| Dimension | ln((0.75 + 0.75) / 0.50) / ln(2) | 1.584963 |
| Alpha | exp(−4.6 × (1.584963 − 1)) | 0.067825 |
| Current FRAMA | 100 + 0.06782479 × (104 − 100) | 100.271299 |
| ATR half-width | Assumed ATR = 3; multiplier = 1.5 | 4.50 |
| Channel rails | 100.271299 ± 4.50 | 95.771299 / 104.771299 |
Using raw ranges would give ln((6 + 6) / 8) / ln(2) = 0.584963, exactly 1 below the normalized estimate. Its alpha would hit the ceiling of 1, incorrectly replacing the filter value with 104. Normalization changes the behavior, not just the units.
The assumed ATR value is a separate input to this arithmetic example. Half-window extrema alone cannot reconstruct ATR, because true range also uses each previous close. The closing price 104 is below the upper rail 104.771299; the example does not establish an upper-rail crossover.
FRAMA + ATR channel v1: the extension used here
This page defines upper = FRAMA + k × ATR and lower = FRAMA − k × ATR. The ATR rails are an added construction; they are not part of Ehlers' original center-line formula. A script named "FRAMA Channel" may instead use a smoothed high-low range, standard deviation, different sources or different initialization. Compare its code before expecting matching plots.
Our v1 uses Pine's ta.atr with a separate ATR length, default 14, and multiplier k, default 1.5. ATR measures volatility using true range, including gaps from the previous close; it does not supply direction. TradingView documents the true-range calculation and RMA smoothing.
- Center response: FRAMA length controls the ranges used for D. It is independent of the ATR lookback.
- Rail distance: multiplier zero collapses both rails onto the center. A larger multiplier increases their distance for the same ATR.
- Noise zone: "inside the channel" means price lies between this version's rails. It is not proof of random behavior. High D and high ATR can coexist.
- Alert definition: close crosses the current upper or lower rail, confirmed at bar close. This compares two moving series; it is not a cross of yesterday's fixed rail. Both current and previous bars must have ready channel values.
At α = 1 with close as the source, the center equals the close. With a positive width, close cannot lie outside the current rails on that bar. Thus a fast center can suppress a channel event even during a large move. Switching to previous-bar rails would define a different event and needs separate testing.
FRAMA Pine Script v6 with optional ATR rails
Paste this indicator into TradingView's Pine Editor, save it and add it to the chart. Disable "Show ATR channel" to inspect the FRAMA center alone. This display switch does not disable alert conditions. The Data Window exposes D, alpha and ATR for comparison.
//@version=6
indicator("FRAMA + ATR channel v1 (Pineify example)", overlay=true)
source = input.source(close, "FRAMA source")
length = input.int(14, "FRAMA length (even)", minval=4, maxval=500, step=2)
atrLength = input.int(14, "ATR length", minval=1)
multiplier = input.float(1.5, "ATR multiplier", minval=0.0, step=0.1)
showChannel = input.bool(true, "Show ATR channel")
if length % 2 != 0
runtime.error("FRAMA length must be even; odd lengths are not rounded.")
half = int(length / 2)
recentHigh = ta.highest(high, half)
recentLow = ta.lowest(low, half)
nRecent = (recentHigh - recentLow) / half
nOlder = (recentHigh[half] - recentLow[half]) / half
nFull = (ta.highest(high, length) - ta.lowest(low, length)) / length
ready = bar_index >= length
validRanges = not na(nOlder) and nRecent > 0 and nOlder > 0 and nFull > 0
// Explicit fallback: retain dimension; use 1 until the first valid estimate.
var float dimension = 1.0
if ready and validRanges
dimension := (math.log(nRecent + nOlder) - math.log(nFull)) / math.log(2.0)
alpha = math.max(0.01, math.min(1.0, math.exp(-4.6 * (dimension - 1.0))))
var float frama = na
frama := not ready or na(frama[1]) ? source : alpha * source + (1.0 - alpha) * frama[1]
// ATR rails are this example's extension, not part of the original FRAMA.
atrValue = ta.atr(atrLength)
upper = frama + multiplier * atrValue
lower = frama - multiplier * atrValue
channelReady = ready and not na(atrValue)
plot(frama, "FRAMA", color=color.blue, linewidth=2)
pUpper = plot(showChannel and channelReady ? upper : na, "Upper ATR rail", color=color.gray)
pLower = plot(showChannel and channelReady ? lower : na, "Lower ATR rail", color=color.gray)
fill(pUpper, pLower, color=color.new(color.blue, 92))
plot(ready ? dimension : na, "Dimension", display=display.data_window)
plot(ready ? alpha : na, "Alpha", display=display.data_window)
plot(atrValue, "ATR", display=display.data_window)
crossUp = ta.crossover(close, upper)
crossDown = ta.crossunder(close, lower)
upEvent = channelReady and channelReady[1] and barstate.isconfirmed and crossUp
downEvent = channelReady and channelReady[1] and barstate.isconfirmed and crossDown
alertcondition(upEvent, "Close crosses above upper rail", "Close crossed above the current FRAMA ATR upper rail at bar close.")
alertcondition(downEvent, "Close crosses below lower rail", "Close crossed below the current FRAMA ATR lower rail at bar close.")Pineify syntax validation passed for this code. Syntax validation does not verify strategy profitability or agreement with another author's implementation.
Parameters, warm-up and failure modes
| Input or condition | v1 behavior | What to verify |
|---|---|---|
| FRAMA length: 14 | Even integer from 4 to 500; odd values cause an error. | Do not truncate one half or mix lengths in denominators. |
| Source: close | High/low windows still determine D. | Match source before comparing two FRAMA lines. |
| First N bars | Center equals source; channel is hidden. | First adaptive update is bar N + 1 in the loaded dataset. |
| Zero range | Retain prior D; seed D = 1 before any valid estimate. | A retained value contains no fresh roughness measurement. |
| ATR length: 14 | Rails wait for a non-missing ATR and FRAMA readiness. | A larger ATR length can extend channel warm-up. |
| Multiplier: 1.5 | Nonnegative width; rails move with center and ATR. | Width is not a confidence interval or a stop-loss guarantee. |
Finite windows with gaps can produce D below 1: the full range may span a gap that neither half contains. The code bounds alpha instead of silently forcing D into a presumed interval. Inspect the raw dimension when the center suddenly follows the source exactly.
Small windows make the estimate sensitive to a few highs and lows. A changing extreme can alter D without a persistent trend change. D = 1.5 gives alpha about 0.100259 in this formula; it does not certify a random walk, and no universal D cutoff separates tradable trends from noise.
The live line and rails update as the current bar changes. Alerts use barstate.isconfirmed, which becomes true on the realtime bar's closing update. TradingView's bar-state reference explains that timing. Confirmation can remove a disappearing intrabar cross from the alert stream, but cannot prevent later price reversals.
A price cross, slope change or rail breakout is an observation, not a complete trading strategy. Entries still need explicit exits, position size, transaction costs and fill assumptions. Adding RSI or another price-based filter does not make the evidence independent or establish a better win rate.
Verify the values before adding trading rules
- Fix the symbol, timeframe, session and chart type. Use the same loaded data when comparing recursive filters.
- Start with the default 14-bar close version. After warm-up, inspect one completed bar's two 7-bar ranges, full range, D and alpha.
- Recalculate the center from its previous value. Then check ATR and both rails separately. Change one input at a time.
- Create an alert for the required crossing condition using a closing-bar frequency. Confirm whether the rule uses current rails, as v1 does, or a prior fixed rail.
- For a strategy experiment, specify exits, costs and position sizing before measuring results. Evaluate reserved data rather than selecting parameters from the best-looking chart.
Continue in Pineify's Pine Script AI Coding Agent
Give the agent the version contract and ask for inspectable values. For example: "Keep two equal high-low half-windows, reject odd lengths, retain D on zero range, and plot D and alpha in the Data Window. Add ATR rails with independent inputs and confirmed current-rail crossover alerts." Compare the generated output bar by bar before requesting strategy entries and exits.
Open Pine Script AI Coding AgentFRAMA questions
Sources and related indicators
Sources reviewed October 17, 2026. The numeric example and ATR channel v1 are this page's demonstrations.