Create your own
Lesson illustration

Building, Backtesting, and Evaluating Pine Strategies Properly

Welcome back. In the previous lesson, you built a confirmed EMA-crossover indicator: it calculated on every bar, displayed BULL and BEAR markers only after bar close, and exposed matching alert conditions. The important boundary was clear: a marker based on a closed candle represents information that was actually available at that close.

This final Pine Script lesson changes the script’s role. Instead of merely observing a confirmed crossover, it will submit hypothetical orders to TradingView’s broker emulator and produce a Strategy Tester report. You will build a deliberately simple long-only EMA strategy, then learn to treat its results as an experiment with explicit assumptions about fills, costs, data, and robustness—not as a promise of profitability.


1. From indicator events to simulated trades

An indicator answers questions such as:

  • Is the fast EMA above the slow EMA?
  • Did a confirmed crossover occur?
  • Should a marker or alert appear?

A strategy adds a second layer: what hypothetical order should follow that event?

For this lesson, the rules are intentionally narrow:

Rule componentDecision
MarketThe chart symbol and timeframe you choose
DirectionLong only
Entry eventFast EMA crosses above slow EMA on a confirmed bar
Protective exitOptional stop-loss and target bracket
Trend exitFast EMA crosses below slow EMA on a confirmed bar
Position additionsNone
Fill assumptionDefault market orders fill on the next available tick, usually the next bar’s open in a historical test

A long-only design is a sensible first conversion for share analysis. Short selling introduces separate concerns—borrow availability, financing, and different risk characteristics—that would distract from the mechanics of strategy testing.

The distinction between a signal bar and a fill bar matters. A confirmed crossover is known at the signal bar’s close. Under TradingView’s normal historical strategy behavior, the market order created at that close is simulated at the next available tick, commonly the next bar’s open. If you see an entry marker one bar after your crossover marker, that is not an error: it is a more cautious timing model.


2. Build a basic, cost-aware strategy

Open a standard candlestick or bar chart. Do not test this on Heikin Ashi, Renko, Range, or other synthetic chart types: their prices or timing do not correspond directly to executable market prices.

For a first inspection, use a daily chart of one liquid instrument, such as a large US or UK share, a broad commodity exchange-traded product, or a major cryptocurrency pair. Keep the instrument and timeframe fixed while you validate the code.

In the Pine Editor, create a new script and replace its content with this:

//@version=6
strategy(
     "Confirmed EMA Crossover - Long Only",
     overlay = true,
     initial_capital = 10000,
     currency = currency.USD,
     default_qty_type = strategy.percent_of_equity,
     default_qty_value = 10,
     pyramiding = 1,
     commission_type = strategy.commission.percent,
     commission_value = 0.10,
     slippage = 1
)

// Inputs
fastLengthInput = input.int(20, "Fast EMA length", minval = 1, group = "Signal")
slowLengthInput = input.int(50, "Slow EMA length", minval = 1, group = "Signal")

useBracketInput = input.bool(true, "Use stop and target bracket", group = "Exits")
stopPercentInput = input.float(3.0, "Planned stop distance (%)", minval = 0.1, step = 0.1, group = "Exits")
rewardRiskInput = input.float(2.0, "Planned reward/risk ratio", minval = 0.1, step = 0.1, group = "Exits")

showEmaInput = input.bool(true, "Show EMA lines", group = "Display")

// Calculations
fastEma = ta.ema(close, fastLengthInput)
slowEma = ta.ema(close, slowLengthInput)

bullishSignal = ta.crossover(fastEma, slowEma) and barstate.isconfirmed
bearishSignal = ta.crossunder(fastEma, slowEma) and barstate.isconfirmed

// Entry and initial protective bracket
if bullishSignal and strategy.position_size <= 0
    plannedStop = close * (1 - stopPercentInput / 100)
    plannedTarget = close * (1 + (stopPercentInput * rewardRiskInput) / 100)

    strategy.entry("Long", strategy.long)

    if useBracketInput
        strategy.exit(
             "Long bracket",
             from_entry = "Long",
             stop = plannedStop,
             limit = plannedTarget
        )

// Trend-based exit
if bearishSignal and strategy.position_size > 0
    strategy.close("Long", comment = "Bearish crossover")

// Visual context
plot(
     showEmaInput ? fastEma : na,
     title = "Fast EMA",
     color = color.teal,
     linewidth = 2
)

plot(
     showEmaInput ? slowEma : na,
     title = "Slow EMA",
     color = color.orange,
     linewidth = 2
)

plotshape(
     bullishSignal,
     title = "Confirmed bullish signal",
     style = shape.triangleup,
     location = location.belowbar,
     color = color.lime,
     size = size.tiny,
     text = "Signal"
)

plotshape(
     bearishSignal,
     title = "Confirmed bearish signal",
     style = shape.triangledown,
     location = location.abovebar,
     color = color.red,
     size = size.tiny,
     text = "Exit"
)

Save the script and select Add to chart. TradingView will display entry and exit markers on the chart and open the Strategy Tester panel.

What changed from the indicator?

The main conversion is the first declaration:

indicator(...)

became:

strategy(...)

That declaration tells TradingView that this script is a strategy and supplies simulation assumptions: starting capital, currency, position sizing, commission, slippage, and pyramiding.

The order functions are the second change:

  • strategy.entry("Long", strategy.long) creates a simulated long market-entry order.
  • strategy.exit() creates price-based exit orders. Here, stop is a stop-loss price and limit is a take-profit price.
  • strategy.close("Long") creates a market order to close the entry with ID "Long".

The entry ID is not cosmetic. "Long" links the entry, the bracket exit, and the crossover exit into one coherent position-management rule.


3. Read the code as an execution model

The strategy has four layers, much like the indicator from the previous lesson.

Inputs and signal calculations

The EMA lengths remain inputs, and the script still calculates two series on every bar:

fastEma = ta.ema(close, fastLengthInput)
slowEma = ta.ema(close, slowLengthInput)

The event logic is also unchanged:

bullishSignal = ta.crossover(fastEma, slowEma) and barstate.isconfirmed
bearishSignal = ta.crossunder(fastEma, slowEma) and barstate.isconfirmed

On historical bars, barstate.isconfirmed is always true because those bars are complete. On realtime data, it protects the original bar-close intent: no order should be created from a crossover that appears temporarily during an unfinished candle.

Position-state checks

The strategy adds two checks involving strategy.position_size:

bullishSignal and strategy.position_size <= 0

and

bearishSignal and strategy.position_size > 0

They prevent repeated entries during an open long position and ensure that a bearish crossover closes a position only when there is one to close. In other words, a bullish crossover is an entry event, not a persistent “keep buying” state.

pyramiding = 1 also supports the no-additions design. It prevents a strategy from stacking repeated strategy.entry() calls in the same direction under its normal entry behavior.

The bracket: a planned loss and target

When a bullish signal occurs, the script calculates:

With a 3% planned stop and a reward-to-risk setting of 2, the target is approximately 6% above the signal close.

This is a planned reward-to-risk relationship, not a guarantee. The actual entry can occur at a different price on the next bar, particularly after a gap. A stop order can also fill worse than its nominated price in fast conditions.

The values in this code are teaching defaults, not recommended trading settings. A 3% stop means something very different for a daily UK share, a commodity product, and a volatile cryptocurrency. Position sizing must ultimately be derived from the actual monetary risk, which was the focus of the risk-planning module.

Why the bearish cross still matters

The bracket exit and the bearish-crossover exit operate together:

  • If price reaches the stop or target first, the bracket closes the trade.
  • If neither level is hit and the moving averages cross bearishly, strategy.close() submits a market exit.

A strategy is not required to use a bracket. Temporarily turn off Use stop and target bracket in settings to see how a pure “enter on bullish cross, exit on bearish cross” system behaves. Keep this as a comparison, not as parameter hunting.


4. Model costs before inspecting performance

A result without execution assumptions can be technically correct but commercially useless. The declaration includes:

commission_type = strategy.commission.percent,
commission_value = 0.10,
slippage = 1

This models:

  • Commission: 0.10% of transaction value on each filled order.
  • Slippage: one tick in the unfavorable direction on each simulated fill.

The values are deliberately illustrative. Commission depends on the broker, account type, exchange, and instrument. Slippage depends on liquidity, volatility, order type, order size, and the time you trade. Bid-ask spread is not represented as a separate universal setting here; a conservative slippage estimate is often used as a rough proxy for adverse fills and spread effects.

A 0.10% commission per filled order implies that an entry and exit alone incur roughly 0.20% in commission before considering slippage. Frequent crossover systems can look materially different once this friction is present.

Concepts / Strategies

Read the relevant parts of TradingView’s official “Strategies” documentation to connect the script settings with their simulation limits. Its examples are useful because they show how apparently modest costs can substantially change a report.

In the “Simulating trading costs” section, read why costs matter, then continue through the commission example. Next, read the slippage discussion. In “Notes on testing strategies,” read backtesting and forward testing, followed by lookahead bias and overfitting. Focus on the distinction between a useful historical experiment and a claim about future returns.

After reading, open the strategy’s Settings, then the Properties tab. Confirm that its starting capital, account currency, order size, commission, slippage, and margin settings represent a plausible paper-trading scenario. Properties can override defaults declared in the code, so it is the configuration you inspect—not only the source code—that defines the tested model.


5. Read the Strategy Tester as evidence, not a score

TradingView’s Apple daily chart shows simulated trade markers and a Strategy Tester report. The report includes net profit, closed trades, percentage profitable, profit factor, maximum drawdown, average trade, and an equity curve after a commission setting has been applied.

In the Metrics tab, begin with the following set of numbers together. No single figure is sufficient.

MetricWhat it tells youWhat it cannot tell you alone
Net profitTotal simulated result after modeled costsWhether the path to that result was tolerable or repeatable
Total closed tradesAmount of observed trade dataWhether the sample spans enough market regimes
Percent profitableFraction of winning tradesWhether wins were large enough to exceed losses and costs
Average tradeTypical net result per completed tradeWhether a few extreme trades dominate the total
Profit factorGross profit relative to gross lossWhether the strategy is robust or merely fitted to this dataset
Maximum drawdownLargest peak-to-trough equity declineThe probability of a worse future drawdown
Equity curveDistribution and sequence of gains and lossesWhether the curve resulted from chance or an unrepeatable market regime

Profit factor is usually interpreted as:

A value above 1 means gross profits exceeded gross losses in this simulation. It does not negate drawdown, costs omitted from the model, or overfitting.

A sensible inspection order

  1. Check the chart markers first. Confirm that entries follow confirmed bullish crossovers and that exit markers match either the bracket or bearish crossover rule.

  2. Check the Properties tab. Record account currency, position size, costs, margin, chart symbol, timeframe, and test range. Changing any of these can change the result.

  3. Read net profit beside maximum drawdown. A profitable test with a drawdown you could not realistically sustain is not automatically useful.

  4. Compare trade count with chart history. Ten trades across many years provide limited evidence. Hundreds of trades can still be misleading if they all occurred under similar trend conditions.

  5. Open the Trades tab. Inspect a few large winners, large losses, and clusters of losses. A trend-following crossover system often performs poorly in sideways, whipsawing periods and captures a smaller number of extended trends. That behavior should be visible in the trade list and chart context.

  6. Compare against buy and hold with care. For a long-only strategy on one share, buy and hold provides useful context. A strategy may make a positive return but still trail a passive holding approach while requiring substantially more activity and cost.

The official TradingView walkthrough is useful at this point because it shows where the settings, performance data, and risk figures appear in the interface.

Strategy Tester Walkthrough: Tutorial (2025 Updated)

Watch TradingView’s “Strategy Tester Walkthrough: Tutorial (2025 Updated)” for a visual tour of the settings and report. It is especially useful for seeing why configuration belongs before interpretation.

Watch strategy properties to see initial capital, base currency, order sizing, commission, slippage, margin, and bar-detail settings. Then watch report metrics for the performance, trade-analysis, drawdown, and risk-performance views. Treat the presenter’s examples as interface guidance; your own assumptions must match the market and account you are modelling.


6. Avoid the most persuasive backtest mistakes

A clean equity curve can conceal a weak experiment. The following safeguards are more important than chasing a better-looking report.

Do not turn visual alignment into future leakage

The strategy intentionally uses confirmed signals and default next-available-tick fills. Avoid enabling settings merely because they make the report look better:

  • Do not assume the signal bar’s closing price was necessarily executable.
  • Be cautious with Fill orders on bar close or process_orders_on_close = true. A daily signal at the end of a market session may be actionable only on the next session, not retroactively at the old close.
  • Do not enable calc_on_order_fills simply to make same-bar trade logic work. On historical bars, extra recalculations can expose completed OHLC information that would not have been known at that moment.
  • Do not add higher-timeframe request.security() filters without deliberately handling confirmation. An unfinished daily candle viewed from an hourly chart can create look-ahead-like historical results.
  • Do not test on synthetic charts. Attractive entries at synthetic prices are not executable trades.

Even this simple strategy has an intrabar limitation. A daily bar can contain both the stop and target prices, while ordinary OHLC data does not necessarily reveal which level was reached first. TradingView’s bar magnifier and lower-timeframe data can improve fill modeling where available, but they do not make historical simulation identical to real execution.

Treat parameter changes as model changes

Changing EMA lengths from 20/50 to 18/47 is not a cosmetic adjustment. It changes the strategy’s event frequency, holding periods, and exposure to different regimes. If you keep changing inputs until one past chart looks impressive, you are fitting rules to historical noise.

Use an in-sample and out-of-sample process instead:

  1. Choose a historical period for development and keep the rule deliberately simple.
  2. Set parameters and trading costs based on a stated rationale, not the best result.
  3. Freeze the configuration.
  4. Test the frozen version on a later, untouched period.
  5. Compare not only profit, but trade count, drawdown, cost sensitivity, and the character of losing periods.

Because this is code, maintain an experiment record as you would for a software change: strategy version, symbol, exchange, timeframe, tested dates, input values, execution settings, and cost assumptions. Without that record, a reported result is difficult to reproduce or compare.

Test robustness, not universality

The same EMA system need not perform identically on US stocks, UK stocks, commodity products, and cryptocurrencies. Those markets have different trading hours, volatility patterns, tick sizes, spreads, and structural drivers.

A more credible claim is modest and specific:

Under stated fills, costs, timeframe, and rules, this strategy had certain historical characteristics on these datasets.

A much less credible claim is:

This crossover configuration is profitable everywhere.

Poor performance on a relevant instrument or regime is evidence, not an inconvenient result to discard.


7. A short, defensible testing workflow

Use this workflow for the script you just built. It is designed to keep implementation validation separate from performance claims.

  1. Validate the logic with costs set to zero. This is a debugging pass only. Check every entry and exit marker against the EMA conditions.

  2. Restore plausible costs. Enter a commission estimate and a conservative slippage estimate appropriate to the instrument. Record the assumptions.

  3. Run a stress case. Increase modeled slippage and costs. If a strategy’s result collapses under a modestly more conservative assumption, its apparent edge may be too fragile.

  4. Freeze the 20/50 configuration. Do not optimize it after seeing the first report.

  5. Inspect two distinct time ranges. For example, use an earlier period as development data and a later period as untouched out-of-sample data. Do not tune after viewing the second report.

  6. Repeat on a small, diverse set. Use at least one relevant US or UK share, one commodity exposure product, and one major cryptocurrency pair if those are markets you intend to study. Keep the strategy version and test method identical.

  7. Forward-test on paper. Let new bars generate signals and hypothetical fills over time. Forward testing cannot reveal future data because the future does not yet exist, though it provides fewer observations and takes patience.

At this stage, the desired outcome is not a winning system. It is a repeatable process that can reject a weak or misleading idea before it reaches a real-money account.


Key takeaways

A Pine indicator becomes a strategy when you replace indicator() with strategy() and connect your existing boolean rules to simulated order commands such as strategy.entry(), strategy.exit(), and strategy.close().

For the confirmed EMA strategy:

  • a bullish confirmed crossover creates a long-entry order;
  • the default historical fill is normally on the next available tick, often the next bar’s open;
  • a stop/target bracket can define planned exits, while a bearish crossover provides a trend-based market exit;
  • commission and slippage must be explicitly modeled;
  • Strategy Tester metrics are useful only when read together with the chart, trade list, settings, sample size, and testing range.

Most importantly, a historical strategy report is a conditional simulation. Guard against look-ahead bias, synthetic-price tests, cherry-picked datasets, and overfitting. The course has now established a broad foundation in market instruments, fundamentals, charts, indicators, risk-controlled paper trading, and Pine Script. The appropriate next step is continued paper trading and documented forward testing of a small number of simple, rule-based hypotheses.

Can't find a good explanation? Sign up and we'll make it for you

Sign up