Pine Script v5 Fundamentals for Systematic Traders

📘 Educational — not financial advice. This post explains Pine Script v5 mechanics and shows working examples. It is not investment advice, a trade signal, or a claim that any specific script or strategy will be profitable. Backtested results — including any shown in this series — do not guarantee future performance. See full Disclaimer.

"Pine Script looked, from a distance, like a shorthand for TradingView charts. When I actually opened the editor, it turned out to be a small language with sharp edges — the kind of edges an engineer notices first and a discretionary trader stumbles into last."

I opened the Pine Editor on TradingView for the first time in 2018, mostly out of curiosity. I wanted to see if I could translate a checklist I was already running by eye into something a computer could plot for me. Two hours later I had a script that painted crossovers on a chart, and a strong feeling that I had missed several things about how the runtime worked.

This post is the introduction I wish someone had handed me back then. It is not a full language reference — TradingView already has one and it is good. It is a systematic-trader-shaped tour: what Pine Script v5 is really doing when it runs, where the sharp edges are, and where the free TradingView tier stops being enough. That last part is why this is a series, not a single post.

📋 What we will look at

  1. Why Pine Script v5 specifically, from an engineer's angle
  2. Indicator vs strategy — two very different things that look alike
  3. The anatomy of a working v5 script — an SMA cross end-to-end
  4. Why free TradingView isn't enough — three walls you hit
  5. What this series will build (instead of just explain)

1. Why Pine Script v5 Specifically, From an Engineer's Angle

Pine Script is TradingView's in-chart scripting language. v5 has been the current major version for several years and is what almost all new material — including TradingView's own Pine Script documentation — is written against. If a tutorial you find online uses study() instead of indicator(), it is v3 or v4, and you should be careful.

Three properties of the language are worth flagging up front, because they explain a lot of the beginner errors I see later:

  • It is a bar-by-bar language, not a tick-by-tick one. Your script is re-executed on every completed bar, and on every intra-bar update for the currently forming bar. That last part is the source of most repainting bugs.
  • Series are the default type. When you write close, you are not referring to one number — you are referring to a series indexed by bar. The value on the current bar is close. The value on the previous bar is close[1]. This is the piece that turns "just write the formula" into a semantic minefield the first time you compare bars.
  • The runtime is single-symbol and single-timeframe by default. Cross-symbol or higher-timeframe data comes in through request.security(), which has its own set of subtle rules about when the requested value has actually formed. This is where the second wave of repainting bugs comes from.

For an engineer, none of this is exotic. It is a domain-specific language with a well-defined execution model and a well-defined set of gotchas. The only thing that makes it feel foreign at first is that "just write the formula" doesn't work, because time-series semantics keep leaking through the syntax.

2. Indicator vs Strategy — Two Very Different Things That Look Alike

The first design decision you make in Pine Script is which top-level declaration to use — indicator() or strategy(). They compile to superficially similar code. They are not the same tool.

Indicator — draws on a chart. Plots lines, shapes, boxes, tables. Can raise alerts. Cannot enter or exit positions and cannot access strategy.* functions or the Strategy Tester report. Use this when the point is to see a condition.

Strategy — everything an indicator does, plus strategy.entry(), strategy.close(), strategy.exit(), and the Strategy Tester panel that reports Net Profit, Profit Factor, drawdown, and a trade list. Use this when the point is to simulate a rule set.

The trap is that beginners often build strategies when they really wanted indicators, because strategy() gives them a Strategy Tester report that looks like a backtest. It is a backtest — of the exact rules encoded in that script, on the exact symbol and timeframe currently loaded on the chart, using TradingView's fill assumptions. It is not a backtest of "my trading system", because most trading systems are broader than what a single Pine strategy encodes.

My own working rule: I build indicators for anything a human is still making the entry/exit decision on, and I build strategies only when I want to see the machine's version of my rules land somewhere I can measure. The two shapes of script serve very different questions. Mixing them up is where most "why doesn't my Pine Script match my TradingView Premium backtest?" questions on Reddit begin.

3. The Anatomy of a Working v5 Script — SMA Cross End-to-End

TradingView Pine Editor screenshot in dark theme, showing the complete SMA(50)/SMA(200) crossover indicator script from lines 1 to 20 with the script name SMA Cross Coder Trader displayed in the editor header, proper syntax highlighting on the indicator declaration, ta.sma calls, plot statements with inline blue and red color swatches, plotshape markers, and alertcondition lines, plus a bottom console pane showing three timestamped log entries reading Compiling, Compiled, and Added to chart at 13:12:20 that together confirm the script compiled cleanly and was successfully applied to the currently loaded chart, framed to make the two-space indentation and Pine Script v5 keyword coloring clearly readable for a systematic trader who is copying the script into their own editor for the first time and needs to verify that the version 5 pragma, the ta.crossover call, and the plotshape offsets all match what they typed.

🔼 Figure 1: the SMA crossover indicator in the Pine Editor. Version pragma on line 1, indicator declaration on line 2, plots and shapes on the bottom half. The console pane confirms the compile succeeded and the script attached to the chart — reading it top-to-bottom is the same as reading how the runtime processes each bar.

Here is the SMA(50)/SMA(200) crossover as a Pine Script v5 indicator. It draws the two moving averages and marks crossovers with small triangles. It does not enter or exit positions.

//@version=5
indicator("SMA Cross — Coder Trader", overlay=true)

fastLen = input.int(50,  "Fast MA length", minval=1)
slowLen = input.int(200, "Slow MA length", minval=1)

fastMA = ta.sma(close, fastLen)
slowMA = ta.sma(close, slowLen)

crossUp   = ta.crossover(fastMA, slowMA)
crossDown = ta.crossunder(fastMA, slowMA)

plot(fastMA, color=color.new(color.blue, 0), title="Fast MA")
plot(slowMA, color=color.new(color.red,  0), title="Slow MA")

plotshape(crossUp,   title="Cross Up",   style=shape.triangleup,   location=location.belowbar, color=color.new(color.green, 0), size=size.small)
plotshape(crossDown, title="Cross Down", style=shape.triangledown, location=location.abovebar, color=color.new(color.red,   0), size=size.small)

alertcondition(crossUp,   title="Fast crossed above Slow", message="SMA Cross Up")
alertcondition(crossDown, title="Fast crossed below Slow", message="SMA Cross Down")

Five things to notice, in the order the runtime cares about them:

  1. Line 1 is the version pragma. If you omit it, the script compiles as v1 and almost nothing will work.
  2. input.int() parameters expose fastLen and slowLen in the settings dialog, so you can change them without editing code. This is how you keep a script honest — no hardcoded magic numbers that quietly drift.
  3. ta.sma() operates on a series. You are not asking "what is the SMA right now" — you are declaring "there is a series called fastMA that is the SMA of close over fastLen bars, evaluated bar by bar." The Pine Editor is very picky about this being clear at every step.
  4. ta.crossover() and ta.crossunder() are boolean series that are only true on the specific bar the crossover happens on. Not the bar before, not the bar after. This matters when we translate the same idea into a strategy in a moment.
  5. plotshape() draws relative to the current bar, not to some crossover event that occurred earlier. The mistake I made most often at the start was assuming the shape would draw at the crossover bar; it does, because the boolean series is only true on that specific bar. If you want it drawn on the bar after, you have to shift the boolean explicitly.

Turning this indicator into a strategy means replacing the plotshape alerts with actual entries and exits. Here is the minimal version, with realistic fee and slippage assumptions rather than the TradingView defaults:

//@version=5
strategy(
     "SMA Cross Strategy — Coder Trader",
     overlay=true,
     initial_capital=50000,
     default_qty_type=strategy.percent_of_equity,
     default_qty_value=100,
     commission_type=strategy.commission.cash_per_contract,
     commission_value=2.0,
     slippage=1)

fastLen = input.int(50,  "Fast MA length", minval=1)
slowLen = input.int(200, "Slow MA length", minval=1)

fastMA = ta.sma(close, fastLen)
slowMA = ta.sma(close, slowLen)

if ta.crossover(fastMA, slowMA)
    strategy.entry("Long", strategy.long)

if ta.crossunder(fastMA, slowMA)
    strategy.close("Long")

plot(fastMA, color=color.blue)
plot(slowMA, color=color.red)
TradingView chart in dark theme showing Micro E-mini S&P 500 futures MES1! on a daily timeframe covering approximately mid-2019 to mid-2026, with the SMA Cross Coder Trader indicator applied so the blue Fast MA (50-period) and red Slow MA (200-period) lines are clearly visible over the price action, with green upward triangles below the bars where fast crosses above slow and red downward triangles above the bars where fast crosses below slow, six such crossover markers scattered across the window covering the COVID-19 dislocation in early 2020, the 2022 bear market top, and the 2023 to 2025 recovery, with the indicator name and its 50 and 200 parameters pinned in the top-left corner of the chart to confirm the script is running, a small volume subpanel at the bottom, and the TradingView watermark preserved in the lower left corner, framed as the standard TradingView view a systematic trader would see after loading the indicator from the Pine Editor to visually verify that crossover markers land on the correct bar across multiple market regimes.

🔼 Figure 2: the same script rendered on Micro E-mini S&P 500 futures 1D. Six crossovers across seven years — including the COVID-19 dislocation of early 2020, the 2022 bear-market top, and the 2023–25 recovery. Several are the kind of whipsaw that makes the raw signal a bad idea to trade as-is, which is exactly what confluence-based frameworks exist to filter.

Two things are different in the strategy version. First, strategy.entry() replaces the crossover marker with an actual simulated long. Second, the strategy declaration itself sets commission, slippage, and sizing explicitly. TradingView's defaults are lenient — no commission, no slippage, and a full-equity allocation — which produces backtest curves that look good and translate to nothing. Setting realistic numbers up front is not an optimisation, it is a truth adjustment.

4. Why Free TradingView Isn't Enough — The Three Walls You Hit

Once you have written and run a few Pine strategies on the free tier, the same walls show up. They are the reason the rest of this series exists.

⚠️ FOUR WAYS PINE SCRIPT V5 GOES WRONG (AND ONE WAY THE FREE TIER DOES)

① Repainting via unclosed bars

The current bar is not closed until it closes. If your script fires a signal based on close during an intra-bar update and then the bar reverses, the signal disappears on the next re-execution. Rule: only take signals on confirmed bars, or guard with barstate.isconfirmed. Skipping this is where 40–45% of beginner Pine strategies quietly overstate their backtest edge.

② The request.security() lookahead trap

Requesting a higher-timeframe value without the correct lookahead and offset settings can leak future information into current-bar logic. TradingView's Pine Script v5 docs are explicit about the safe form; the unsafe form still compiles.

strategy.entry() called every bar

If the entry condition remains true for several bars, strategy.entry() will re-fire and pyramid unless you set the pyramiding parameter or gate the call on "no open position." This is why some strategies show 3× the trade count you expected.

④ Free tier: the bar-count and metrics ceiling

The free tier caps historical bars available to strategies well below what you want for multi-year testing on intraday timeframes, and the Strategy Tester surfaces a limited metrics set. There is no walk-forward, no parameter sweep, no per-regime slicing. This is the wall this series exists to route around — not by paying for Premium, but by building the equivalent evaluation harness in Python.

The first three are language mistakes and are fixed by writing safer Pine. The fourth is a platform limit, and it does not have a fix inside TradingView. It has a fix outside of TradingView — a small, Python-native backtester that reads the same OHLCV data, runs the same rule set, and returns the same shape of report. Building that harness is what posts 2 through 5 of this series do.

5. What This Series Will Build (Instead of Just Explain)

The rest of this series is closer to a project than a set of tutorials. The plan, across the next nine posts, is to:

  • Post 2 — Design the Python backtester that mirrors TradingView Strategy Tester semantics: bar-by-bar iteration, next-open fills, commission, slippage, equity curve, trade list. Publish the architecture.
  • Posts 3–5 — Build it. A historical-data pipeline on top of yfinance and ccxt, a strategy engine, and a metrics-and-report layer. By post 5 there is a working v0.1 harness that can run a Pine-shaped idea in Python and produce the same overview a TradingView Strategy Tester would.
  • Posts 6–8 — Backtest the popular stuff. OBV divergence, CMF plus 200-EMA, Supertrend plus RSI. All three are common enough that the results — the honest results — are useful in their own right.
  • Posts 9–10 — Publish two of my own indicators. A five-layer confluence scanner and a Grade A/B/C setup classifier, both derived from frameworks earlier posts on this blog established. Full Pine Script source, full Python backtest, no hidden knobs.

The engineering discipline running underneath is the same one I described in Reading Charts After a Decade of Reading Code: version everything, test the boring parts, and refuse to trust a chart-only backtest curve you cannot reproduce with code you can read.

A Note on Code Availability

All Pine Script and Python source used in this series will live in the pine-script-strategies repository on my GitHub, under a backtester/ subfolder that grows one directory per post. The GitHub link that normally appears in the author box below this post is temporarily removed while the account is under a routine review — GitHub Support is aware and I expect the review to close in the ordinary course. As soon as the repository is public, this post will be edited to include the direct link to basics/01_sma_cross.pine. In the meantime, everything you need to reproduce the two scripts above is inline in section 3.

If you spot an error in the code above, or you have a specific Pine v5 pattern you would like the Python backtester to reproduce, hello@codertrader.com is the fastest way to reach me. I keep a small list of reader-suggested patterns and try to fold them into later posts.


⚠️ Educational Disclaimer

This post is an educational explainer of Pine Script v5 mechanics and includes example scripts. It is not investment advice, financial advice, trading advice, or a recommendation to trade any specific instrument or strategy.

Any script shown here is illustrative only. Backtested results — including any produced later in this series using the Python harness — describe past behaviour on historical data and do not guarantee future performance. Trading involves substantial risk of loss and is not suitable for every investor.

Always do your own research, consult a qualified financial advisor licensed in your jurisdiction, and never risk capital you cannot afford to lose. See our full Disclaimer and Privacy Policy.

About the Author

Dongmin Park — Coder Trader author profile photo

Dongmin Park is a software engineer with over 15 years in embedded systems (automotive and defense industries) and 10+ years of active trading across Korean equities, US options, MNQ futures, and crypto. He started trading on a Kiwoom Securities account in Seoul in 2016 and now lives in Ingolstadt, Germany, after relocating in 2022.

Coder Trader is an ongoing project to document where systematic engineering discipline meets discretionary trading. Say hi on X, or email hello@codertrader.com.

Comments

Popular posts from this blog

Cumulative Volume Delta (CVD): 6 Institutional Patterns Every Trader Must Know (2 Real MNQ Examples)

Chaikin Money Flow + 200 EMA: How to Spot Institutional Accumulation (2 Real Trade Examples)

Ultimate Volume Trading Checklist: A 5-Layer Rule-Based Framework