What is Pine Script and Why It Matters
Pine Script is TradingView's built-in scripting language. It runs directly in your browser, compiles to JavaScript on TradingView's servers, and renders indicators and strategies on every chart you load. You do not need to install anything — no VS Code, no Python, no external compilers. Just open TradingView, open the Pine Editor, and start writing.
The language is purpose-built for one thing: quantitative analysis on price data. It has native concepts for candles, volume, time, and the drawing objects that traders expect. If you have spent time with Python or JavaScript, Pine Script will feel familiar, but it has its own quirks — particularly around how it handles series data (time-ordered arrays that update on every bar).
Most retail traders use pre-built indicators from the TradingView community. That is fine for a start, but there is a hard ceiling: you cannot customize someone else's script without understanding the source. Learning Pine Script gives you the ability to build exactly what you want — and to build once, then let it run automatically across every chart, every timeframe, indefinitely.
A custom Pine Script indicator is not just about your own analysis. It is the foundation for automated alerts, webhook triggers, and signal pipelines. TrendFlow uses proprietary Pine Scripts to generate every LONG/SHORT signal — the same logic you will write in this guide, refined and battle-tested across live crypto markets.
Pine Script Versions: v4, v5, or v6?
TradingView currently supports Pine Script v1 through v6. Versions below v5 are effectively deprecated — v1–v3 have limited features and v4 is on the way out. Write new scripts in v5 or v6.
The differences are meaningful:
- Pine Script v5: Introduced type annotations, better namespace handling, and cleaner built-in variable names. Still the most widely used version on TradingView community scripts.
- Pine Script v6: Adds newer built-in variables, minor performance improvements, and slightly cleaner syntax. Most new scripts should target v6, but v5 is still fully supported.
To set the version in your script, add this as the first line:
//@version=5 // Indicator settings indicator("My EMA Cross", overlay=true)
The //@version=5 declaration tells TradingView which compiler version to use. Skipping it or using an older version can cause unexpected behavior, especially with community scripts that rely on newer built-ins.
Anatomy of a Pine Script Indicator
Every Pine Script indicator follows the same structure:
// 1. Version declaration //@version=5 // 2. Indicator declaration — name, overlay behaviour indicator("My Script", overlay=true) // 3. Inputs — user-configurable parameters fastLen = input.int(9, title="Fast EMA Length") slowLen = input.int(21, title="Slow EMA Length") // 4. Calculations — the core logic fastEma = ta.ema(close, fastLen) slowEma = ta.ema(close, slowLen) // 5. Plotting — render on chart plot(fastEma, color=color.orange, title="Fast EMA") plot(slowEma, color=color.blue, title="Slow EMA")
That is it. indicator() declares the script, input.*() creates user-facing controls, ta.ema() computes the exponential moving average, and plot() renders it. The four sections repeat in virtually every indicator you will write.
The key concept in Pine Script is the series — a time-ordered sequence of values, one per bar. When you call ta.ema(close, 20), you are creating a series that computes the 20-period EMA on every historical bar as the script loads, and will continue computing on every new bar going forward. This is what makes Pine Script reactive: it updates in real time without any additional code.
Building Your First EMA Indicator (with Code)
An EMA crossover indicator is the classic starting point. When the fast EMA crosses above the slow EMA, it suggests bullish momentum. When it crosses below, bearish. Here is a complete, working script you can paste directly into TradingView's Pine Editor:
//@version=5 indicator("TrendFlow EMA Cross", overlay=true, format=format.price, precision=2) // — User inputs fastLen = input.int(9, title="Fast EMA") slowLen = input.int(21, title="Slow EMA") src = input.source(close, title="Source") // — Calculations fastEma = ta.ema(src, fastLen) slowEma = ta.ema(src, slowLen) // — Plot EMAs on chart pFast = plot(fastEma, color=color.orange, linewidth=2, title="Fast EMA") pSlow = plot(slowEma, color=color.blue, linewidth=2, title="Slow EMA") // — Crossover detection bullCross = ta.crossover(fastEma, slowEma) bearCross = ta.crossunder(fastEma, slowEma) // — Signal markers on chart plotshape(bullCross, title="Bull Cross", style=shape.labelup, location=location.belowbar, color=color.lime, text="LONG", textcolor=color.black) plotshape(bearCross, title="Bear Cross", style=shape.labeldown, location=location.abovebar, color=color.red, text="SHORT", textcolor=color.white) // — Alerts (add via TradingView alert dialog, not code) // Set alert on: fastEMA crosses above slowEMA (bull) // Set alert on: fastEMA crosses below slowEMA (bear)
Copy this into the Pine Editor, click "Add to Chart," and you will see the two EMAs plotted with LONG/SHORT labels at every crossover. The inputs at the top of the script (Fast EMA length, Slow EMA length) become controls on the chart — you can tweak them without changing the code.
Two things worth noting about this script:
- The alert comment — TradingView alerts are configured through the UI (right-click the script, "Add Alert"), not in code. The comment in the script is a reminder for yourself. For automated alerts that trigger webhooks, you will set these up in the Alert dialog and point them at a webhook URL.
overlay=true— This tells TradingView to render the indicator on the price chart rather than in a separate pane below. Most retail indicators useoverlay=true(RSI, MACD overlays usefalseto show in their own pane).
Debugging: Why Your Indicator Isn't Working
If you paste the script above and see nothing on the chart, or the wrong values, here are the most common causes:
Version mismatch
TradingView defaults to the latest Pine version for new scripts, but if you copied from an older source, the syntax may be incompatible. Check the //@version= at the very top of the script. If you see //@version=3 or missing entirely, switch to v5 by changing it to //@version=5.
Wrong scale / empty plot
If your indicator values are extremely large or small (e.g., showing millions on a price chart that tops out at $70,000), you probably applied format=format.volume by mistake, or used an incompatible series. For price-based indicators, use format=format.price.
"Cannot use global 'close' in local scope"
This error appears when you reference a built-in variable inside a function that expects a local context. In Pine v5, all built-in variables like close, open, high, low are available globally — you should not pass them as function arguments unless the function signature explicitly requires it.
Strategy vs. Indicator
Scripts declared with strategy() include backtesting, order execution, and performance reporting. Scripts declared with indicator() are for analysis only. If you want to test your crossover on historical data, use strategy() and add strategy.entry() and strategy.exit() calls. If you just want visual signals and alerts, use indicator().
If your script runs fine on 1-hour and 4-hour charts but shows strange values on 1-minute data, check whether your EMA length is too long for the bar count available. A 200-period EMA on a 1-minute chart needs 200 bars of data — if your chart only shows 100 bars, the indicator will appear incomplete or NaN. Increase the chart's visible bar count or shorten the period.
Next Steps and Where to Go from Here
You now have a working EMA crossover indicator. From here, the natural progression is:
- Add confirmation filters — an EMA crossover alone is noisy. Adding RSI above/below a threshold, or volume confirmation, reduces false signals significantly. TrendFlow's proprietary scripts use multi-factor confirmation across 5 indicators before generating a signal (see our EMA Crossover Strategy guide for the foundational logic).
- Add alerts — configure TradingView alerts on the crossover conditions and wire them to a webhook. That webhook can trigger external automation (covered in our TradingView Automation guide).
- Test on historical data — convert your script to a
strategy(), enable the Strategy Tester tab, and run it across your preferred market and timeframe. Most traders are surprised by how differently a strategy performs in backtesting vs. live markets. - Use pre-built signals — if building and maintaining your own scripts is not your priority, TrendFlow provides live EMA crossover signals across BTC, ETH, SOL, and other major pairs, delivered to your inbox every morning.
Continue reading: How EMA crossovers work in practice or automate your signals with webhooks.
Want live crossover signals delivered to your inbox?
TrendFlow runs 5 proprietary Pine Scripts across crypto markets 24/7. Get LONG/SHORT alerts every morning — no chart-watching required.
See Signal Plans →