Most traders eventually hit the same wall. The built-in indicators are fine, but they never quite match the setup you actually trade. You want a moving average crossover that only fires above a certain volume, or a colored background that tells you at a glance whether the trend is on your side. Paying a developer feels excessive for something so small, and learning a full programming language feels like a detour from trading. Pine Script exists precisely for this gap.
This guide walks you through building a working custom indicator on TradingView even if you have never written a line of code. By the end you will have an EMA crossover indicator with adjustable settings, visual buy and sell markers, alerts, and a backtestable strategy version. More importantly, you will understand the structure well enough to keep going on your own.
What Pine Script Is and Why TradingView Created It
Pine Script is a lightweight programming language designed for one job: writing indicators and strategies that run directly on TradingView charts. It was created because general-purpose languages like Python or C++ ask far too much of a trader who simply wants to plot a line. You would need to fetch price data, manage a chart library, handle time zones, and deal with a hundred details unrelated to your trading idea.
Pine Script removes all of that. Price data is already loaded. Timeframes are already handled. When you write close, the language knows you mean the closing price of the current bar on whatever symbol and timeframe you are viewing. A script that would take two hundred lines in Python takes ten lines in Pine. The language runs bar by bar, from the oldest candle to the newest, executing your code once per bar. That single mental model explains most of how Pine behaves, so keep it in mind as you read.
The current version is Pine Script v6. Older versions still work, but new scripts should always start with v6 because the syntax is cleaner and the documentation assumes it.
Opening the Pine Editor
Open any chart and look at the bottom of the screen. You will see a tab labeled Pine Editor. Click it and a code panel slides up beneath the chart. You do not need a paid plan to write and run your own scripts; a free account is enough to follow everything in this article.
Inside the editor, click Open and choose New indicator. The editor fills the panel with a tiny template that plots the closing price. Click Add to chart and you will see a line appear in a pane below the candles. Congratulations, you have technically already run a Pine Script. Now let us understand what you are looking at.
Anatomy of a Pine Script
Every indicator, no matter how complex, follows the same five-part structure. Once you can recognize these parts, reading other people’s scripts becomes far less intimidating.
1. The version annotation
The very first line is //@version=6. It looks like a comment, and technically it is, but the compiler reads it to decide which version of the language rules to apply. Leave it out and the compiler assumes an old version, which causes confusing errors later. Always start with this line.
2. The declaration statement
The second line tells the compiler what kind of script this is. For an indicator you write indicator("My Script", overlay=true). The first argument is the name that appears on the chart. The overlay parameter decides whether your plots draw on top of the price candles (true) or in a separate pane underneath (false). Moving averages belong on the price chart, so we use overlay=true. An RSI would use overlay=false.
3. Inputs
Inputs are the settings a user can change from the gear icon without editing code. Functions like input.int(), input.float(), and input.bool() create these fields. Hard-coding a number works, but inputs make your script flexible and reusable, so it is worth learning them from day one.
4. Calculations
This is where the logic lives. Pine ships with a large library of technical functions under the ta namespace: ta.ema(), ta.sma(), ta.rsi(), ta.crossover(), and many more. You combine these with price variables like open, high, low, close, and volume.
5. Outputs
Finally you display something. The plot() function draws a line. Other output functions include bgcolor() for shading the background, plotshape() for markers, and alertcondition() for alerts. A script with no output compiles but shows nothing, which is a common source of confusion for beginners who forget this step.
Building an EMA Crossover Indicator Step by Step
The exponential moving average crossover is a classic trend-following signal. When a fast EMA crosses above a slow EMA, momentum is shifting upward. When it crosses below, momentum is shifting downward. It is simple enough to build in a few minutes and rich enough to teach every core concept.
Step 1: Declare the script
Delete the template code and start fresh with the version line and an indicator declaration. Because EMAs sit on the price chart, set overlay=true.
Step 2: Add inputs for the EMA lengths
Rather than typing 9 and 21 directly into the calculations, create two integer inputs. The minval=1 argument prevents a user from entering zero or a negative number, which would break the calculation.
Step 3: Calculate the two EMAs
Call ta.ema() twice, passing the closing price and each length. Store each result in a variable so you can reuse it.
Step 4: Detect the crossover
Pine has dedicated functions for this. ta.crossover(a, b) returns true on the exact bar where a moves from below b to above it. ta.crossunder() does the reverse. These return a boolean, meaning true or false, which is exactly what you need to drive signals and alerts.
Step 5: Plot the lines
Two plot() calls put both EMAs on the chart with different colors so you can tell them apart.
Adding Inputs, Signals, Background Color, and Alerts
With the core working, three additions turn a bare indicator into something genuinely useful. First, shade the background green when the fast EMA is above the slow EMA and red when it is below. The color.new() function takes a color and a transparency from 0 to 100; a value around 90 keeps the shading subtle. Second, use plotshape() to draw a triangle under the bar on a bullish cross and above the bar on a bearish cross. Third, register two alert conditions so you can receive a notification when either event occurs.
Here is the complete indicator in Pine Script v6:
//@version=6
indicator("EMA Crossover", overlay=true)
fastLen = input.int(9, "Fast EMA Length", minval=1)
slowLen = input.int(21, "Slow EMA Length", minval=1)
fastEma = ta.ema(close, fastLen)
slowEma = ta.ema(close, slowLen)
bullCross = ta.crossover(fastEma, slowEma)
bearCross = ta.crossunder(fastEma, slowEma)
plot(fastEma, "Fast EMA", color=color.orange)
plot(slowEma, "Slow EMA", color=color.blue)
bgcolor(fastEma > slowEma ? color.new(color.green, 90) : color.new(color.red, 90))
plotshape(bullCross, "Buy", shape.triangleup, location.belowbar, color.green, size=size.small)
plotshape(bearCross, "Sell", shape.triangledown, location.abovebar, color.red, size=size.small)
alertcondition(bullCross, "EMA Bull Cross", "Fast EMA crossed above slow EMA")
alertcondition(bearCross, "EMA Bear Cross", "Fast EMA crossed below slow EMA")
Paste this into the Pine Editor, click Add to chart, and you should see two moving averages, a tinted background, and triangles at each crossover. Open the settings gear on the indicator and you will find the two length inputs ready to adjust. Change them and the chart updates instantly.
A note on how alertcondition() works, because it trips up nearly everyone. Adding the function to your script does not create an alert by itself. It registers a condition that becomes available in the TradingView alert dialog. To actually receive notifications, right-click the chart, choose Add alert, select your indicator in the Condition dropdown, and pick the condition name you defined. The third argument to alertcondition() becomes the default message text. You need to create the alert once per condition, and if you later edit the script you may need to recreate the alert so it picks up the new code.
Turning the Indicator Into a Strategy
An indicator shows you signals. A strategy acts on them and keeps score. Converting one to the other requires surprisingly few changes. Replace indicator() with strategy(), then tell Pine when to enter and exit using strategy.entry() and strategy.close().
//@version=6
strategy("EMA Crossover Strategy", overlay=true, initial_capital=10000)
fastEma = ta.ema(close, input.int(9, "Fast"))
slowEma = ta.ema(close, input.int(21, "Slow"))
if ta.crossover(fastEma, slowEma)
strategy.entry("Long", strategy.long)
if ta.crossunder(fastEma, slowEma)
strategy.close("Long")
Add this to the chart and a new tab called Strategy Tester appears at the bottom of TradingView next to the Pine Editor. It shows net profit, win rate, maximum drawdown, the total number of trades, and a full list of every entry and exit the strategy would have taken on the visible history. Blue and red arrows appear on the chart marking each simulated trade.
The Strategy Tester is a powerful reality check. A crossover that looks brilliant during a strong trend often bleeds money in choppy markets, and the equity curve makes that obvious within seconds. Before trusting any backtest, set realistic commission and slippage in the strategy settings, test on more than one symbol and timeframe, and be skeptical of results that look too good. TradingView also lets you set default_qty_type and default_qty_value to control position sizing, which matters a great deal for how the numbers come out.
Publishing Your Script to the Community
Once your script works, you can share it. Click Publish script in the Pine Editor and TradingView asks you to choose a visibility level. There are three options, and the choice is permanent for that publication, so think it through.
VisibilityWho can use itWho can read the codeBest for Open-sourceEveryoneEveryoneLearning, reputation, giving back ProtectedEveryoneOnly youSharing a tool while keeping logic private Invite-onlyUsers you approveOnly youPaid or private distribution Open-source scripts are the backbone of the TradingView community and the best way to learn, since you can open any of them and read exactly how they work. Protected scripts let anyone add your indicator but hide the source. Invite-only scripts require you to grant access to individual users, and TradingView requires vendors who sell invite-only scripts to follow its House Rules, including providing a clear description of what the script does. Whichever level you choose, your publication needs a meaningful title and a description that explains the concept in plain language. Scripts that just say “buy low sell high” get flagged or ignored.
Common Beginner Errors and How to Fix Them
Every Pine programmer runs into the same handful of mistakes. Knowing them ahead of time saves hours.
- Missing or wrong version line. If you omit
//@version=6, functions liketa.ema()throw undefined errors because older versions use different names. Always put the version annotation on line one. - Indentation inside if blocks. Pine uses indentation to define code blocks. The line under an
ifstatement must be indented with four spaces or a tab. Mixing the two, or forgetting to indent, produces a “line continuation” error. - Type mismatches. Passing a string where a number is expected, or comparing a boolean to a float, will not compile. The error message names the expected type, so read it carefully rather than guessing.
- Plotting inside a conditional block. Functions like
plot()andalertcondition()must be at the global scope, not inside anif. If you need a conditional plot, use a ternary expression such asplot(cond ? value : na). - Repainting. Using
request.security()to pull higher-timeframe data without care can produce signals that change after the fact. Beginners should stick to the chart timeframe until they understand lookahead behavior. - Forgetting that the script runs bar by bar. A variable declared normally resets on every bar. If you need a value to persist across bars, declare it with the
varkeyword. - Too many plots or drawings. The platform limits the number of plots, labels, and lines per script. If you hit the limit, the compiler tells you the exact count.
When an error appears, the Pine Editor highlights the offending line and prints a message in the console below. Fix the first error only, then recompile. Later errors are often just consequences of the first one.
Where to Learn More
The official Pine Script reference manual is the definitive source. Every function, variable, and keyword is documented with its parameters and a short example. Keep it open in a second tab while you write; searching for a function name takes seconds and answers most questions. Alongside the reference, the Pine Script User Manual walks through concepts like execution model, types, and scopes in a more narrative style. It is worth reading once from start to finish even if some of it does not sink in immediately.
The second best teacher is other people’s code. The Community Scripts section on TradingView contains tens of thousands of open-source indicators. Find one that does something close to what you want, open its source, and trace through it line by line using the anatomy you learned above. Editors’ Picks highlight well-written, clearly documented scripts that model good practice. Modifying an existing script is often a faster path to a working result than starting from a blank editor, and it teaches idioms you would not discover on your own.
Finally, the TradingView blog announces new Pine features as they ship, and the PineCoders group publishes FAQs and style guides. Combined with the built-in autocomplete in the Pine Editor, which shows function signatures as you type, you have everything you need to move from your first EMA crossover to genuinely original tools.
Final Thoughts
Pine Script rewards small, incremental steps. Start with the twenty-line indicator above, change one thing, and observe what happens. Add an RSI filter. Swap the EMA for a Hull moving average. Require that the bullish cross happens above a 200-period average. Each modification teaches a new function and reinforces the five-part structure. Within a few sessions you will stop thinking of yourself as someone who cannot code and start thinking of TradingView as a canvas for your own ideas. That shift, more than any single indicator, is what makes learning Pine worthwhile.
Frequently Asked Questions
Do I need a paid TradingView plan to use Pine Script?
No. The Pine Editor, custom indicators, and the Strategy Tester are available on free accounts. Paid plans raise the number of indicators you can add to a single chart at once and unlock more alerts, but writing and running scripts costs nothing.
Can Pine Script place real trades automatically?
Not directly. Pine strategies simulate trades in the Strategy Tester. To automate execution you would connect TradingView alerts to a broker through webhooks or a supported integration. Treat that as an advanced step to take only after a strategy has been thoroughly backtested and forward-tested.
Why do my indicator’s signals look different on another timeframe?
Pine calculates everything using the bars of the chart you are viewing. A 9-period EMA on a daily chart uses nine days; on a five-minute chart it uses forty-five minutes. The logic is identical, but the inputs represent different amounts of time, so the signals naturally change.
How long does it take to become comfortable with Pine Script?
Most traders can build simple indicators like the one in this guide within an afternoon. Becoming comfortable with strategies, multi-timeframe data, and drawing objects usually takes a few weeks of regular practice. Reading community scripts alongside the reference manual accelerates the process considerably.