Pine Script: What It Is, How It Works, and How to Get Started
Pine Script is TradingView's built-in language for indicators and strategies. What it does, how to get started, and the mistakes that cost real money.
11 min read
In this article ▾
Most traders discover Pine Script the same way. They find a public indicator on TradingView, paste it onto a chart, and start wondering what would happen if they changed the moving average period from 14 to 21. They open the source code, change a number, click "Add to chart," and something works. That's the moment the language recruits another user.
Pine Script is TradingView's built-in scripting language. It runs entirely on TradingView's servers, inside the browser, with zero setup on your end. No IDE, no compiler, no terminal window. You write code in the Pine Editor, press save, and the result appears on your chart. That instant feedback loop is why most retail traders who try algorithmic trading start here.
Why Pine Script instead of Python or MQL5
The argument against Pine Script goes like this: it's proprietary, it only works inside TradingView, and it won't help you get a developer job. All true. Python is a general-purpose language with a real job market. MQL5 runs directly on MetaTrader, where your broker account lives. So why not skip Pine Script entirely?
Two reasons. First, Pine Script is the fastest path from "I have a trading idea" to "I can see whether it works." Writing a moving average crossover in Python means choosing a data provider, setting up a backtest framework (Backtrader, Zipline, or a custom pandas pipeline), configuring a charting library, and debugging dataframes for a weekend before you see a single result. In Pine Script, it's about 15 lines of code, and the chart is already there. You see your entries and exits overlaid on real price data the moment you save.
Second, TradingView's chart is not a nice-to-have. It's the development surface. The visual output (seeing exactly where your strategy entered, where it exited, where the stop would have been hit) is how most traders think about their ideas.
When you write a strategy in Python, you get a P&L curve and maybe a table of trades. In Pine Script, you get those trades painted on the chart you already know how to read. You spot problems by eye that a spreadsheet would bury.
That said, Pine Script is a starting point, not a destination. There are real reasons to eventually convert Pine Script to MQL5. Pine Script can't place trades or talk to your broker directly. If your end goal is a fully autonomous system running on MetaTrader, you'll need MQL5.
The same logic applies to Python. Access to machine learning libraries, external data feeds, and portfolio-level logic drives traders who convert Pine Script to Python once they outgrow TradingView's built-in tools. But for the first version of a strategy, the one where you're testing whether the core logic even makes money, Pine Script gets you there in an afternoon.
Indicators, strategies, and alerts: how Pine Script builds things
This is where most beginners lose time. Pine Script creates three distinct types of scripts, and mixing them up leads to weeks of frustration.
An indicator draws on the chart. Moving averages, RSI, Bollinger Bands, custom oscillators. Indicators calculate values and plot them, but they don't simulate trades or backtest. If you copy a public script that starts with indicator(), it will never generate a buy or sell signal on its own and will never show you a performance report.
A strategy simulates trades. It starts with strategy() and places orders using functions like strategy.entry() and strategy.close(). TradingView runs the strategy on historical data and produces a performance report: net profit, win rate, max drawdown, and a trade list. This is what most people actually want when they say, "I want to build a trading bot."
An alert is how TradingView talks to the outside world. When a condition fires (either from a strategy or an indicator), an alert can send a webhook: an HTTP request to an external URL. That webhook is the only exit door, because Pine Script itself can't call APIs, open network connections, or place orders at a broker. All of TradingView's automated trading capabilities depend on this one mechanism. The alert is the piece that makes it all happen.
If your goal is automated trading, you need all three concepts working together: a strategy that generates the signal, an alert that fires when the signal triggers, and something on the other end of the webhook that actually executes the trade.
How Pine Script runs your code
This section is short but worth understanding, because the execution model is behind most of the confusion beginners hit.
Pine Script doesn't run in a loop. TradingView calls your script once per bar. On a 15-minute chart, your code runs when each 15-minute candle closes. It receives all the historical bars first (calculating from bar 0 to the current bar), then it runs on each new bar as it forms.
This matters for one specific reason: the value of close differs between a historical bar and a live bar. On a historical bar, close is the final closing price. On a live bar, close is whatever price just ticked. Your strategy might fire an entry based on a close value that changes 200 times before the bar actually closes.
The function barstate.isconfirmed exists to handle this. It returns true only after the bar has closed and won't change again. If your strategy logic should only act on confirmed bars (and for most strategies, it should), wrap your conditions with this check. Otherwise, you'll get alerts firing mid-bar that retrigger or contradict themselves by the close of the bar.
If you set calc_on_every_tick=true in your strategy declaration, the script recalculates on every price update instead of once per bar close. This is important for live trading accuracy, but it means your backtest runs slower and your results will look different (usually worse, and usually more honest).
Writing your first Pine Script strategy
Open any chart on TradingView. Click "Pine Editor" at the bottom. Delete the template code. Start here:
//@version=5
strategy("My First Strategy", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=10)
fast = ta.sma(close, 9)
slow = ta.sma(close, 21)
if ta.crossover(fast, slow)
strategy.entry("Long", strategy.long)
if ta.crossunder(fast, slow)
strategy.close("Long")
plot(fast, color=color.green)
plot(slow, color=color.red)Save it. Add it to your chart. You'll see two moving averages and blue/red arrows marking entries and exits. Click "Strategy Tester" at the bottom to see the performance report.
That's a working strategy. Not a good one: SMA crossovers on their own have been losing money since approximately 2008. But it backtests, generates alerts, and can be extended with real logic.
A few things to notice in the code. strategy.entry("Long", strategy.long) uses a string ID ("Long") that you'll see in the trade list. ta.crossover and ta.crossunder are built-in functions that detect when one series crosses above or below another. The overlay=true parameter tells TradingView to draw the strategy directly on the price chart instead of in a separate pane.
The TradingView Pine Script reference manual is the authoritative source for all functions and parameters. Keep it open. Community scripts are useful for seeing patterns, but they vary wildly in quality, and many are pinned to older versions.
Pine Script best practices
Once you've written your first strategy, these habits keep you from debugging problems that shouldn't exist.
Pin your version. Always start with //@version=5. TradingView occasionally changes default behavior between versions, and a script without a version pin will silently adopt the defaults of the current version. Pin it.
Use strategy.entry, not strategy.order. strategy.entry manages position direction and sizing for you. strategy.order doesn't. It will happily stack multiple positions on top of each other if you're not careful. Use strategy.order only when you specifically need that behavior and understand what it does.
Set realistic backtest assumptions. In the strategy() declaration, set commission_value, slippage, and default_qty_type to something that reflects your actual broker. A backtest with zero commission and zero slippage on XAUUSD is fiction. IC Markets charges roughly $3.50 per lot per side on gold. Put that number in, or your equity curve is lying to you.
Name your orders. Every strategy.entry("Long", ...) call uses a string ID. Name them clearly: "Long_MA_Cross", "Short_RSI_Reversal." When you're debugging why a position closed early, the order names in the strategy tester's trade list are your first clue. Tracking your trading metrics across strategies becomes easier when every entry and exit can be traced back to a specific line of code.
Match your backtest to your live conditions. If your live alerts fire on real-time price, set calc_on_every_tick=true in the strategy declaration. The backtest results will look worse. They'll also match what your broker actually sees.
Your plumbing matters more than your indicators. Most useful algo trading tips have nothing to do with finding a magic setup. They're about making sure the plumbing around your strategy doesn't quietly destroy whatever edge the logic has.
Pine Script mistakes that cost real money
Syntax errors crash your script and tell you what went wrong. The expensive mistakes compile fine and produce garbage quietly.
Trusting a repainting indicator. Some indicators change their past values as new data arrives. The signal that looked perfect on yesterday's chart didn't actually exist yesterday. If your strategy relies on a repainting indicator, the backtest is showing trades that were impossible to take. Classic offenders: any script using request.security() with barmerge.lookahead_on, or custom indicators referencing close on the current bar as though it's final. When your TradingView indicators aren't working the way you expected, repainting is the first thing to rule out.
Ignoring barstate.isconfirmed. If your alert fires on every tick, it fires multiple times per bar. On a 5-minute EURUSD chart, that can mean dozens of redundant signals per candle. Wrap your alert condition in if barstate.isconfirmed to fire once, after the bar closes. Skip this only if you've designed your strategy to handle intrabar signals and actually tested that behavior.
Overfitting to historical data. Add enough conditions, and any strategy looks profitable over the past 5 years of EURUSD. That's not a strategy. It's a curve fit. If yours has more than 5 or 6 parameters and you've optimized all of them against the same dataset, you've built a script that perfectly describes the past and predicts nothing about the future. Test on out-of-sample data. Test on a different symbol. If the edge disappears, it was never there.
Expecting backtest fills in live markets. TradingView fills your backtest orders at the bar's close price when the condition triggers. Your broker fills at whatever price is available when the order arrives, plus spread and slippage. On a daily XAUUSD bar, the gap between bar close and actual fill can be 30+ pips. On a 1-minute chart, it's usually a few points. But it's never zero, and across hundreds of trades, the difference between your backtest equity curve and your live account grows into a number that's hard to ignore.
Where Pine Script stops, and execution begins
Pine Script can't place a trade. Can't read your broker's balance. Can't verify whether a previous order actually filled. It runs inside TradingView, computes values on chart data, fires alerts, and stops. That's where its authority ends.
The gap between "my strategy fired an alert" and "the correct trade appeared on my broker" is where most automation quietly falls apart. Alerts duplicate. Webhooks retry and deliver the same signal twice. A reversal fires as two separate alerts (close the long, open the short), and they arrive at the broker in the wrong order. Stop losses end up at a different price than the script calculated because the broker requoted or filled late.
These are not exotic edge cases. They're the normal failure modes of connecting two systems that were never designed to work together.
This is the problem FillEdge was built for. FillEdge is a webhook bridge between TradingView and your broker. Your Pine Script fires an alert, FillEdge receives it, validates the signal, and delivers it to your trading terminal. The delivery is the easy part. The part that matters is what happens after.
Every trade is reconciled against the original signal. FillEdge checks whether the fill matches what your strategy actually called: right price, right size, right stop-loss placement. If something doesn't agree, you see it immediately with a clear status:
✓MATCHED— signal and fill agree.🎯LOCKED— your stop-loss landed at the exact price your script calculated, even if the entry slipped.👻CAUGHT— a phantom signal was intercepted before it reached the broker.🛡️BLOCKED— a duplicate execution attempt was stopped.🔀REORDERED— reversal alerts that arrived out of sequence were delivered in the correct order.💀EXPIRED— a stale signal was discarded rather than executed late.
That distinction matters. Anyone weighing copy trading and bot trading will notice that the "bot" side is really Pine Script plus a bridge. The strategy runs in TradingView. The execution runs somewhere else. The question is whether that somewhere else actually confirms the trade happened the way your code described, or just forwards the webhook and hopes.
The path from idea to live execution is shorter than it used to be. If you plan to connect MT5 to TradingView or any other broker, it starts in the Pine Editor and ends with a bridge that doesn't invent trades, drop signals, or leave you guessing which leg of the pipeline broke.
Pine Script handles the logic. The bridge handles the rest.
FAQ
Is Pine Script a real programming language?
Yes, but it's a domain-specific one. Pine Script is designed to do one thing well: express trading logic on TradingView chart data. It has variables, functions, conditionals, loops, and a type system, but it can't build a web app, talk to a database, or run outside TradingView's servers. Think of it as closer to SQL than to Python: real code, narrow purpose.
What is the difference between an indicator and a strategy in Pine Script?
An indicator calculates and plots values on your chart (moving averages, RSI, custom oscillators) but can't simulate trades or produce a performance report. A strategy uses strategy.entry() and strategy.close() to simulate orders on historical data, giving you a backtest with win rate, drawdown, and a trade list. If you want to test whether a trading idea actually makes money, you need a strategy, not an indicator.
Is Pine Script hard to learn?
It's one of the easiest ways into algorithmic trading. If you can read a basic if/then statement, you can write a Pine Script strategy in an afternoon. The language handles most of the hard parts (data feeds, chart rendering, order simulation) so you focus on the logic. The learning curve gets steeper when you hit execution-model details like repainting and barstate.isconfirmed, but a working first strategy takes 15 lines of code and about 20 minutes.
Does Pine Script work with MetaTrader?
Not directly. Pine Script runs on TradingView's servers and has no way to connect to MetaTrader or any broker on its own. The link between the two is the TradingView alert system: your strategy fires an alert, the alert sends a webhook to a bridge like FillEdge, and the bridge delivers the trade to your MetaTrader terminal. Pine Script handles the signal. The bridge handles the execution.
More from FillEdge