EMA in Pine Script: Calculation, Crossovers and Timeframe Behavior
An exponential moving average summarizes a price series using exponentially declining weights. In Pine Script, ta.ema(source, length) calculates that series. Choosing an EMA length defines a smoothing rule; it does not establish a profitable trading rule.

EMA calculation and interpretation
For length N, the smoothing factor is alpha = 2 / (N + 1). After initialization, the recurrence is EMA_current = alpha * source_current + (1 - alpha) * EMA_previous. With N = 9, the new observation receives a weight of 0.2 in that update. This is an arithmetic example, not a recommended period.
An N-bar SMA equally weights its N observations. An EMA gives more weight to recent observations and retains a declining contribution from older history. Neither is universally earlier or more accurate at detecting a turn; the price path, initialization and signal definition matter. Shorter smoothing can also produce more reversals of the signal.
Plot a basic EMA
Open the Pine Editor using the Pine icon on the right toolbar. Add one complete example at a time. The existing code examples below are retained for inspection; this article does not report a chart execution or a profitability test.
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Pineify
//======================================================================//
// ____ _ _ __ //
// | _ \(_)_ __ ___(_)/ _|_ _ //
// | |_) | | '_ \ / _ \ | |_| | | | //
// | __/| | | | | __/ | _| |_| | //
// |_| |_|_| |_|\___|_|_| \__, | //
// |___/ //
//======================================================================//
//@version=6
indicator("My First EMA", overlay=true)
length = input(14, title="EMA Period")
ema_line = ta.ema(close, length)
plot(ema_line, color=color.blue, title="EMA")
length is an input and close is the source. Keep the length positive when editing the settings. The Pine reference defines the function's input and return types.
Expose appearance settings
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Pineify
//======================================================================//
// ____ _ _ __ //
// | _ \(_)_ __ ___(_)/ _|_ _ //
// | |_) | | '_ \ / _ \ | |_| | | | //
// | __/| | | | | __/ | _| |_| | //
// |_| |_|_| |_|\___|_|_| \__, | //
// |___/ //
//======================================================================//
//@version=6
indicator("Custom EMA Indicator", overlay=true)
length = input(21, title="EMA Period")
line_color = input(color.red, title="EMA Color")
line_thickness = input(2, title="Line Thickness")
ema_value = ta.ema(close, length)
plot(ema_value, color=line_color, title="Custom EMA", linewidth=line_thickness)
This adds color and line-width inputs. A color change affects the plot, not the EMA calculation. Use a valid positive line width and compare the plotted value in the Data Window with TradingView's built-in EMA using the same source and length.
Compare two EMA series
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Pineify
//======================================================================//
// ____ _ _ __ //
// | _ \(_)_ __ ___(_)/ _|_ _ //
// | |_) | | '_ \ / _ \ | |_| | | | //
// | __/| | | | | __/ | _| |_| | //
// |_| |_|_| |_|\___|_|_| \__, | //
// |___/ //
//======================================================================//
//@version=6
indicator("Fast and Slow EMA System", overlay=true)
fast_period = input(12, title="Fast EMA Period")
slow_period = input(26, title="Slow EMA Period")
fast_ema = ta.ema(close, fast_period)
slow_ema = ta.ema(close, slow_period)
plot(fast_ema, color=color.red, title="Fast EMA", linewidth=2)
plot(slow_ema, color=color.blue, title="Slow EMA", linewidth=2)
// Color the area between EMAs
fill(plot(fast_ema), plot(slow_ema), color=fast_ema > slow_ema ? color.new(color.green, 80) : color.new(color.red, 80))
The fill is green when the fast EMA is above the slow EMA and red otherwise. Equality therefore uses the red branch in this example. The script plots each EMA twice because its fill() call creates additional plot IDs; it is a display example, not an optimized implementation.
A crossover means that the first series is above the second on the current bar and was at or below it on the previous bar. A crossunder reverses those comparisons. On an open bar, a crossover can appear and disappear as close changes. A bar-close decision needs an explicit timing rule.
Inspect the strategy's order behavior
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Pineify
//======================================================================//
// ____ _ _ __ //
// | _ \(_)_ __ ___(_)/ _|_ _ //
// | |_) | | '_ \ / _ \ | |_| | | | //
// | __/| | | | | __/ | _| |_| | //
// |_| |_|_| |_|\___|_|_| \__, | //
// |___/ //
//======================================================================//
//@version=6
strategy("EMA Crossover Strategy", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=10)
// Input settings
fast_length = input(9, title="Fast EMA Length")
slow_length = input(21, title="Slow EMA Length")
// Calculate EMAs
fast_ema = ta.ema(close, fast_length)
slow_ema = ta.ema(close, slow_length)
// Plot EMAs
plot(fast_ema, color=color.red, title="Fast EMA")
plot(slow_ema, color=color.blue, title="Slow EMA")
// Trading logic
long_condition = ta.crossover(fast_ema, slow_ema)
short_condition = ta.crossunder(fast_ema, slow_ema)
// Execute trades
if (long_condition)
strategy.entry("Long", strategy.long)
if (short_condition)
strategy.entry("Short", strategy.short)
This is a broker-emulator strategy. An opposite strategy.entry() normally reverses an existing position; the short entry is not simply an instruction to close a long position. Its configured size is 10% of equity for the resulting entry, not a 10% maximum loss. There is no stop-loss in this example, and reversal order quantity can include the quantity required to close the previous position.
Under default strategy processing, orders created at a bar's close generally fill at the next available tick, commonly the next bar's open in historical tests. Commission and slippage are not specified in this source. Set them in strategy properties before interpreting results, and inspect the List of Trades rather than assuming a crossing price is a fill price. These mechanics are documented in TradingView's strategy guide.
Understand the multi-timeframe example
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Pineify
//======================================================================//
// ____ _ _ __ //
// | _ \(_)_ __ ___(_)/ _|_ _ //
// | |_) | | '_ \ / _ \ | |_| | | | //
// | __/| | | | | __/ | _| |_| | //
// |_| |_|_| |_|\___|_|_| \__, | //
// |___/ //
//======================================================================//
//@version=6
indicator("Multi-Timeframe EMA", overlay=true)
ema_length = input(50, title="EMA Length")
higher_tf = input.timeframe("1D", title="Higher Timeframe")
// Get EMA from higher timeframe
htf_ema = request.security(syminfo.tickerid, higher_tf, ta.ema(close, ema_length))
// Plot both current timeframe and higher timeframe EMAs
current_ema = ta.ema(close, ema_length)
plot(current_ema, color=color.blue, title="Current TF EMA")
plot(htf_ema, color=color.orange, linewidth=3, title="Higher TF EMA")
With the default 1D input on an intraday chart, the requested expression calculates a daily EMA. The blue line uses chart bars instead, so the two 50-period series summarize different durations.
The orange line requests the developing daily value. It can change before the daily bar closes and can differ after reloading the chart. This source does not enforce a higher timeframe: selecting a lower timeframe changes the request's meaning. For a confirmed higher-timeframe design, use TradingView's documented previous-bar expression and lookahead pairing, with a guard requiring the requested timeframe to exceed the chart timeframe. Do not add lookahead alone: that can leak future information into historical results. See other timeframes and data.
Choose periods through a defined experiment
A 9/21 pair, a 12/26 pair and a single 50-period EMA are different hypotheses, not ranked settings. A 50-bar EMA on a five-minute chart is not a 50-day EMA.
- Fix the instrument, exchange feed, session, chart type and timeframe.
- Write the entry, exit, position-size and bar-confirmation rules before testing lengths.
- Separate a development interval from a later evaluation interval. Record every parameter combination tried.
- Include trading costs and examine drawdown, trade count, exposure and average trade alongside win rate.
- Compare neighboring settings and different market regimes. A sharp peak at one setting may reflect selection noise.
A volume threshold or three-bar confirmation adds another hypothesis and changes entry timing. It should be tested as a separate rule, not presented as a universally reliable filter. This article supplies no measured win rate or ticker-specific return.
Build an EMA configuration visually
Pineify can generate Pine Script from configured indicator components, including EMA. Review the generated source, add it to TradingView and compare the values with the equivalent built-in indicator.
Combining calculations into one script can change how many separate scripts you need on a chart. It does not remove TradingView's plot, execution, request or account limits. The Pine limitations documentation applies to generated scripts too.



