

How to Code Custom Pine Script V6 Indicators on TradingView
Table of Contents
- Introduction
- What Is Custom Pine Script V6 Indicator?
- Why Custom Pine Script V6 Matters for Traders and Investors
- Core Concepts
- Step‑by‑Step Guide
- Practical Tips for Better Results
- Common Mistakes to Avoid
- Frequently Asked Questions
- Conclusion
Introduction
When the EUR/USD 1‑hour chart flashed a clean 20‑period EMA crossover on Tuesday, a handful of seasoned forex traders were already placing market orders. Their edge did not come from a canned study; it stemmed from a home‑grown Pine Script that highlighted exactly those bullish bars. The same logic can be transplanted to any market—AAPL daily, the S&P 500 intraday, or a commodity futures chart—provided you know how to code a custom indicator that respects your timing, risk parameters, and visual preferences.
Most traders lean on the built‑in Moving Average or RSI, yet those tools are static snapshots. A custom Pine Script V6 indicator lets you blend several data streams, apply dynamic filters, and output shapes that match your entry criteria. Version 6 introduced stricter typing, mutable arrays, and a more modularrequest.securitycall, all of which trim bugs and boost performance on TradingView.
The following sections walk you through the entire lifecycle: declaring the script, wiring user inputs, pulling multi‑timeframe data, visualizing signals, and finally testing the logic on historical bars. By the end you will own a reusable template that can be adapted to any strategy you trade.
What Is Custom Pine Script V6 Indicator?
A custom Pine Script V6 indicator is a block of code written in TradingView’s proprietary Pine language, version 6, that produces visual cues—lines, histograms, or shapes—based on calculations you define. Unlike built‑in studies, the script lives in your personal library, can be edited at any time, and may be shared with colleagues or the broader community.
Example: A forex trader writes a script that plots a green triangle above the candle whenever the 20‑period EMA crosses above the 50‑period EMA and the 14‑period RSI exceeds 55. The script runs on the EUR/USD 1‑hour chart, but a single input change lets the same code operate on a 4‑hour timeframe. The flexibility comes from exposing the periods as inputs rather than hard‑coding them.
Why Custom Pine Script V6 Matters for Traders and Investors
Professional desks operating in CFTC‑regulated futures markets and retail accounts under SEC oversight both need signals that mirror their specific edge. A custom indicator enables you to:
* Align logic with a risk‑reward framework, for instance by signaling only when average true range (ATR) volatility exceeds a threshold. This reduces false entries during low‑liquidity windows.
* Embed proprietary data—such as a volume filter derived from an internal order‑flow model or a macro‑fundamental flag—without waiting for TradingView to add a built‑in study.
* Backtest the exact rules you intend to trade, preserving the same look‑ahead bias a strategy script would have. That alignment matters for compliance with the FCA’s best‑execution standards.
Skipping the ability to code your own indicator forces you to rely on generic signals that may not fit your position sizing, or to waste time manually scanning charts for patterns a simple script could flag instantly.
//@version=6 Declaration and Script Type — the foundation
Every Pine file begins with a version declaration. In V6 the syntax is strict: //@version=6. Directly after, you specify the script type—indicator for visual studies or strategy for backtesting. Declaring the type determines which built‑in functions are available and how TradingView treats your output.
Scenario: A swing trader wants a daily histogram that shows the difference between a 20‑period EMA and a 50‑period EMA. By starting with //@version=6 and indicator("EMA Diff", overlay=false), the script tells TradingView to render a separate pane, keeping the price chart uncluttered.
input() for User‑Configurable Parameters — flexibility in real time
The input() function creates UI controls in the script’s settings panel. You can expose numeric fields, dropdowns, or checkboxes that let the user adjust periods, thresholds, or toggle features without touching the code.
Scenario: In the EUR/USD EMA crossover example, the trader adds
pinescript
fastLen = input.int(20, "Fast EMA Length", minval=1)
slowLen = input.int(50, "Slow EMA Length", minval=1)
Changing these values on the fly lets the trader test 10‑period versus 30‑period EMAs without recompiling. The UI updates instantly, which speeds up the iteration loop.
plot() and plotshape() for Visual Output — turning numbers into signals
plot() draws continuous series like lines or histograms, while plotshape() places discrete icons (arrows, triangles) on specific bars. Both accept color, style, and size arguments that can be conditional.
Scenario: When the fast EMA crosses above the slow EMA and RSI > 55, the script executes
pinescript
plotshape(series=longSignal, location=location.abovebar,
color=color.lime, style=shape.triangleup, size=size.small)
The trader instantly sees a green triangle above the candle, eliminating the need to hunt for numeric crossovers in the data window.
request.security() for Multi‑Timeframe Data — avoiding repainting
request.security() fetches data from a different symbol or timeframe while preserving the current bar’s context. In V6 the function returns a series that respects the chart’s resolution, preventing the common repaint issue where signals shift after the bar closes.
Scenario: An equities swing trader wants a daily ATR value while operating on a 4‑hour chart. By calling
pinescript
dailyATR = request.security(syminfo.tickerid, "D", ta.atr(14))
the script obtains a stable ATR that updates only at the daily close, ensuring that entry signals on the 4‑hour chart are based on a non‑repainting volatility measure.
ta.* Library Functions — built‑in math without reinventing the wheel
The ta. namespace hosts a suite of technical‑analysis functions: moving averages, oscillators, and statistical tools. Using them guarantees optimized performance and alignment with TradingView’s calculation conventions.
Scenario: Instead of coding a custom SMA, the trader writes
pinescript
sma20 = ta.sma(close, 20)
The function automatically handles na values at the start of the series, sparing the coder from manual checks.
array.* Structures for Dynamic Data Handling — beyond static series
V6 introduced mutable arrays, enabling storage of values that evolve over the script’s lifetime. Arrays are useful for tracking recent highs/lows, counting consecutive bars, or building custom histograms.
Scenario: A volatility‑breakout indicator keeps an array of the last 10 ATR values to compute a rolling median. The code creates
pinescript
var float[] atrHistory = array.new_float(0)
and on each bar pushes ta.atr(14). The median is then derived with array.median(atrHistory), delivering a smoother volatility filter that reacts to recent market conditions.
Step‑by‑Step Guide
Below is a practical walk‑through that assembles the concepts above into a functioning indicator.
Step 1 — Set Up the Script Skeleton
Open TradingView, click Pine Editor, and start with the mandatory header:
pinescript
//@version=6
indicator("Custom EMA Crossover", overlay=true, maxbarsback=500)
overlay=true forces the plots onto the price chart. maxbarsback tells the compiler how many historical bars to retain for functions that look back, such as ta.sma. Save the file with a descriptive name; because Pine lacks built‑in version control, consider appending a date (e.g., EMA_Crossover_2024_09) to the filename.
Step 2 — Define User Inputs
Add inputs for the fast and slow EMA lengths, the RSI period, and a volume filter toggle:
pinescript
fastLen = input.int(20, "Fast EMA Length", minval=1)
slowLen = input.int(50, "Slow EMA Length", minval=1)
rsiLen = input.int(14, "RSI Length", minval=1)
volFilter = input.bool(true, "Enable Volume Filter")
volThresh = input.float(1.5, "Volume Multiplier", minval=0.1, step=0.1)
These controls appear in the script’s Settings dialog, allowing rapid experimentation without recompilation. Adjusting the multiplier lets you tighten or loosen the volume filter on the fly.
Step 3 — Compute Core Series
Use the ta. library to calculate EMAs, RSI, and a volume‑adjusted threshold:
pinescript
fastEMA = ta.ema(close, fastLen)
slowEMA = ta.ema(close, slowLen)
rsiVal = ta.rsi(close, rsiLen)
avgVol = ta.sma(volume, 20)
volOk = volume > avgVol * volThresh
volOk becomes true only when the current bar’s volume exceeds the 20‑period average by the user‑defined multiplier, filtering out low‑liquidity spikes that often generate false signals.
Step 4 — Generate Entry Signals with Conditional Logic
Combine the series into a single boolean that represents a bullish entry:
pinescript
bullCross = ta.crossover(fastEMA, slowEMA)
bullSignal = bullCross and rsiVal > 55 and (not volFilter or volOk)
If the volume filter is disabled, the or clause ensures the signal ignores volOk. This pattern keeps the code readable and prevents nested if statements that can cause repainting.
Step 5 — Plot Visual Cues on the Chart
Use plotshape() to mark bullish bars and plot() for the EMAs:
pinescript
plot(fastEMA, color=color.orange, title="Fast EMA")
plot(slowEMA, color=color.blue, title="Slow EMA")
plotshape(bullSignal, title="Bullish Entry", location=location.belowbar,
color=color.lime, style=shape.triangleup, size=size.tiny)
The triangle appears below the candle, giving a clear visual cue without obscuring price action. You can swap size.tiny for size.small if you prefer a more prominent marker.
Step 6 — Incorporate Multi‑Timeframe Confirmation (Optional)
To add robustness, fetch a higher‑timeframe trend direction using request.security():
pinescript
higherTF = request.security(syminfo.tickerid, "D", ta.ema(close, 100))
trendUp = close > higherTF
finalSignal = bullSignal and trendUp
plotshape(finalSignal, title="Confirmed Entry", location=location.belowbar,
color=color.green, style=shape.arrowup, size=size.small)
Now the script flags a bullish entry only when the daily 100‑EMA sits below the price, aligning short‑term entries with a longer‑term uptrend.
Step 7 — Test the Indicator on Historical Data
Click Add to Chart to see the indicator in action. Use the Data Window to verify that the boolean series (finalSignal) flips only on closed bars. If you notice “NaN” values, check that all series have sufficient history (respect maxbarsback) and that request.security() calls are not referencing a timeframe with fewer bars than the chart.
Step 8 — Deploy and Share
When satisfied, click Publish Script. Choose Public if you want community feedback, or Invite‑only for private distribution. Include a concise description, version number, and any required inputs. Public scripts undergo TradingView’s review process, which checks for prohibited content and ensures the code does not breach the platform’s terms of service.
Practical Tips for Better Results
- Lock inputs with
constwhen they never change; this reduces compilation time and signals to the interpreter that the value is immutable. - Use
varfor persistent arrays to avoid re‑initializing on each bar; this conserves memory during long backtests. - Prefer
ta.crossoverover manual comparisons such asfastEMA > slowEMA[1] and fastEMA[1] <= slowEMA; the built‑in function handlesnaedges cleanly. - Guard calculations with
nachecks, e.g.,if not na(fastEMA). This prevents propagation of missing values through later series. - Activate the
calc_on_every_tickflag only when you truly need intra‑bar updates; on a free plan it can trigger throttling. - Export data via
strategy.export()in strategy mode if you plan Monte Carlo simulations in Python or R. - Run the script on a low‑liquidity market—such as a thinly traded ETF—to see how the volume filter behaves under stress. Hidden edge cases often surface in these environments.
Common Mistakes to Avoid
- Hard‑coding symbol names; this prevents reuse across markets. Always reference
syminfo.tickerid. - Neglecting
maxbarsback; insufficient look‑back leads to “series not available” errors when using long‑term functions. - Using
request.security()withoutlookahead=barmerge.lookahead_on; that configuration can cause repainting if the higher‑timeframe series updates mid‑bar. - Over‑fitting inputs on a single instrument; signals that look perfect on one asset may crumble on another. Forward‑test on out‑of‑sample data.
- Skipping division‑by‑zero checks;
close / volumewhen volume is zero yieldsinfvalues that break the chart. - Choosing
size.largeforplotshapeon a crowded chart; oversized icons obscure price action and reduce readability.
How do I create a custom Pine Script V6 indicator on TradingView?
Start by opening the Pine Editor, declare //@version=6, choose indicator() as the script type, define inputs with input(), compute your series using ta. functions, and visualize with plot() or plotshape(). Save and add the script to your chart to see the results.
What are the key differences between Pine Script V5 and V6 for indicator development?
V6 introduces stricter type checking, mutable arrays (array.*), and a refined request.security() that reduces repainting. It also allows maxbarsback to be set per script, improving performance for long‑look‑back calculations.
Why does my custom indicator show “NaN” values on the chart?
“NaN” appears when a series lacks enough historical data to compute the requested look‑back, or when an input series contains missing values. Ensure maxbarsback exceeds the longest look‑back period and guard calculations with if not na(series).
When should I use request.security() versus built‑in series in a custom indicator?
Use request.security() when you need data from a different timeframe or symbol—e.g., a daily trend filter on an intraday chart. Built‑in series suffice for calculations that stay within the chart’s current resolution.
Can I backtest a custom Pine Script V6 indicator as a strategy?
Yes. Convert the script type from indicator to strategy, replace plotshape() with strategy.entry() calls, and TradingView will generate equity curves, drawdown statistics, and trade logs. Remember to set calc_on_order_fills=true for realistic order execution.
Is it possible to export Pine Script indicator data for external analysis?
In strategy mode, you can use the built‑in strategy.export() function to download a CSV of trades and equity. For pure indicators, you can write the series to the plot pane and manually copy the data via the Data Window; TradingView does not provide a direct export for indicator‑only scripts.
Conclusion
The most valuable takeaway is that a well‑structured Pine Script V6 indicator can turn a discretionary observation—like an EMA crossover—into a repeatable, testable signal that respects your risk parameters and market context. Begin by drafting the skeleton, expose the critical parameters through input(), and validate each step on live data before committing capital.
Your next move: copy the example code into the Pine Editor, adjust the EMA lengths to match your own trading horizon, and run a 30‑day forward test on a liquid instrument such as the S&P 500 E‑mini. Observe how the signals behave across different volatility regimes, and refine the volume filter as needed.
Remember, no script guarantees profit. Markets can shift, liquidity can dry up, and even a perfectly coded indicator can generate a string of losses. Treat every custom indicator as a tool—not a crutch—and always size positions to withstand the worst‑case drawdown. Happy coding, and trade responsibly.
—
This article is for educational purposes only and does not constitute investment advice. Trading and investing carry risk of loss; never invest more than you can afford to lose.
Last reviewed August 2026
Last reviewed: August 2026




















































