Pine strategy pre-flight before a prop-firm evaluation

Seven rule checks you can run on a Pine strategy's source before an evaluation, each with a rule id, why it matters, and a do and don't. Cites TradingView's own docs.

By Matthew Hendricks, from TradingView's own Pine documentation. Sources read . No affiliate links (about this guide).

If a prop firm reruns your strategy, it has to load and it has to give the same answer each time. These seven checks read only your script’s source, so you can run them before you spend an evaluation attempt.

  1. pine/future-leak: no look-ahead on a higher timeframe without a one-bar offset.
  2. pine/size-limits: stay under TradingView’s script and collection caps.
  3. pine/request-budget: keep request calls few.
  4. pine/per-tick-work: use built-ins instead of loops, and guard tick logic.
  5. pine/release-drawings: update drawings instead of deleting and recreating them.
  6. pine/price-equality: compare prices within a tick instead of with ==. (My check.)
  7. pine/loss-limit-input: the strategy states its own loss limits. (My check.)

Each has a rule id you can cite in a code review, why it matters, and a do and a don’t. Rules 1 to 5 trace to TradingView’s own Pine documentation, which I read on 2026-11-05. Rules 6 and 7 are my own checks, and the section says so.

This page is a checklist, not trading advice. A script that passes every check has not shown how it trades or that it follows a firm’s rules. The last section lists what a source check cannot answer.

pine/future-leak: no look-ahead on a higher-timeframe request without a one-bar offset

When request.security() asks for a higher timeframe with lookahead = barmerge.lookahead_on and no [1] offset on the series, it “will return data from the future on historical bars, which is dangerously misleading”S1. The chart shows prices before they could be known. In realtime that future does not exist, so the same script behaves differently live.

TradingView’s recommended way to get confirmed higher-timeframe values is an expression that only references past bars, such as close[1], together with lookahead_onS6. The docs add that the [1] offset and lookahead_on depend on each other, and that neither can be dropped without breaking the guaranteeS1.

Don’t:

htfClose = request.security(
     syminfo.tickerid, "D", close,
     lookahead = barmerge.lookahead_on)

Do:

htfClose = request.security(
     syminfo.tickerid, "D", close[1],
     lookahead = barmerge.lookahead_on)

The offset applies to higher-timeframe requests only. For the other two cases, TradingView says:

  • Same timeframe as the chart (timeframe.period): the docs list lookahead_on as acceptable, so no offset is neededS6.
  • Lower timeframe than the chart: an offset is not the fix. request.security() returns one intrabar per chart bar. With lookahead_on it returns the first intrabar, which “will generally work as expected on historical bars”S6. On realtime bars it tracks only the most recent intrabar, so it repaintsS6. The Repainting page says to avoid request.security() at lower timeframes unless you understand the subtletiesS1. It says to use request.security_lower_tf() for more reliable lower-timeframe dataS1.

Why it matters for an evaluation: a rerun on a firm’s side can only match a result that does not depend on data from the future or on realtime-only values.

pine/size-limits: stay under the script and collection limits

TradingView’s Limitations page sets hard caps: the compiled form of a script can hold at most 100,256 tokens, a script can have at most 64 plot counts, and an array, matrix or map can hold at most 100,000 elementsS2. A script over a cap does not load. A collection that grows on every bar reaches its cap on a long history.

Don’t:

var float[] closes = array.new<float>()
array.push(closes, close)

Do:

var float[] closes = array.new<float>()
array.push(closes, close)
if array.size(closes) > 500
    array.shift(closes)

Keep functions short and few in parameters too. That is my readability limit, not a TradingView rule: a function over about 40 lines or with more than 4 parameters is a candidate to split.

pine/request-budget: few request.*() calls

A script can use up to 40 unique request.*() calls, or 64 on the Ultimate plan, and “a subsequent call to the same request.*() function with the same arguments is not typically unique”S2. The Profiling page also treats data requests as a cost to keep downS3. Stay well below the cap, and fetch related values in one call.

Don’t:

htfHigh = request.security(
     syminfo.tickerid, "60", high[1],
     lookahead = barmerge.lookahead_on)
htfLow = request.security(
     syminfo.tickerid, "60", low[1],
     lookahead = barmerge.lookahead_on)

Do:

[htfHigh, htfLow] = request.security(
     syminfo.tickerid, "60", [high[1], low[1]],
     lookahead = barmerge.lookahead_on)

pine/per-tick-work: no loops where a built-in exists, no unguarded tick logic

The Limitations page caps any loop on a single bar at 500 milliseconds, and the outer loop of nested loops times out firstS2. The Profiling page shows how to replace a loop with a loop-free expression or a built-inS3.

Per-tick logic is a second source of cost and of surprises. The Strategies page says changing a strategy’s script execution settings, such as calc_on_every_tick, “can cause repainting”S4. A strategy that places orders on unconfirmed realtime ticks can act on a bar that later changes.

Don’t:

float total = 0.0
for i = 0 to 49
    total += close[i]
avg = total / 50

Do:

avg = ta.sma(close, 50)

if barstate.isconfirmed and longCondition
    strategy.entry("Long", strategy.long)

pine/release-drawings: update drawings instead of deleting and recreating them

By default a script shows its last 50 lines, boxes, polylines and labels, and the declaration can raise the cap to 500 for lines, boxes and labels and 100 for polylinesS2. The Profiling page shows two versions of the same drawing code and says updating a drawing’s properties is more efficient than deleting it and creating a new one. It also says drawings that change across historical bars are only seen in their final state, so the updates can be limited to the last barS3.

Don’t:

label.delete(lbl)
lbl := label.new(bar_index, high, str.tostring(high))

Do:

var lbl = label.new(bar_index, high)
label.set_xy(lbl, bar_index, high)
label.set_text(lbl, str.tostring(high))

pine/price-equality: compare prices within a tick, not with ==

This is my check, not a TradingView rule. I found no passage in the five docs pages I read that states it. TradingView’s own examples build price offsets from syminfo.mintickS4, which is the tick size of the symbol. A price from a calculation, such as an average or a percentage level, rarely equals a market price to every decimal, so == on two prices can be false when the levels are the same to the tick.

Don’t, when the intent is to detect a touch of a level, such as for an alert:

if close == touchLevel
    alert("Close at the level")

Do:

if math.abs(close - touchLevel) < syminfo.mintick
    alert("Close within a tick of the level")

Use this only where “equal within a tick” is the real intent. Do not use it for a stop. A close that steps over the level by more than a tick would never match, and the position would stay open. A stop compares with <= or >=:

if close <= stopLevel
    strategy.close("Long")

pine/loss-limit-input: the strategy states its own loss limits

This is also my check. It asks only whether the source declares a daily loss limit and a maximum drawdown as inputs, so a reviewer can see the numbers the author chose. Pine has commands that act on such limits: strategy.risk.max_intraday_loss() stops trading for the session when losses reach a threshold, and strategy.risk.max_drawdown() stops trading after a set drawdownS4.

Don’t: leave the limits out, or bury a constant in the middle of the script.

Do:

//@version=6
strategy("Example", overlay = true)

dailyLossLimit = input.float(500.0, "Daily loss limit ($)", minval = 1.0)
maxDrawdown    = input.float(1500.0, "Max drawdown ($)", minval = 1.0)

strategy.risk.max_intraday_loss(dailyLossLimit, strategy.cash)
strategy.risk.max_drawdown(maxDrawdown, strategy.cash)

An input in the source shows the limit is declared. It does not show the limit holds. See the next section.

Planned: checks that need an evaluation run

A source check cannot answer these, because each depends on how the strategy trades through a session. I list them so a clean pre-flight is not read as more than it is. None of them is built here yet.

  • Whether the loss limits hold. Firms measure the limit on their own balance and equity series, and some move the limit on the end-of-day balance but test it on live equity. See trailing, end-of-day and static drawdown.
  • Trade frequency. Whether a strategy trades often enough to break a firm’s rule on automation or high-frequency activity. See do prop firms allow trading bots?.
  • Consistency. Whether one day’s profit is too large a share of the total. See the consistency rule calculator.
  • News windows. Whether the strategy holds or opens a position inside a firm’s blackout window. See news trading rules.
  • A firm’s own rule set. Each firm words these rules differently, which is why they are read from the firm’s pages and listed in the rules overview.

Matchstick shows what a strategy did under defined conditions. It does not predict future results or give trading advice.

Quick answers

What is a future leak in a Pine strategy?

It is a higher-timeframe request.security() call that uses lookahead = barmerge.lookahead_on on a series without a [1] offset. On historical bars the call returns values before they existed, so a result that looks good on the chart cannot be reproduced live. TradingView's own docs call this dangerously misleading. The offset is not needed when the request is for the chart's own timeframe, and it does not fix a lower-timeframe request, which can still repaint.

How many request.*() calls can a Pine script make?

TradingView's Limitations page says up to 40 unique calls, or up to 64 on its Ultimate plan, and that a repeat call with the same arguments is not typically unique. Staying well below the limit also keeps the script lighter.

Can a source check tell me whether a strategy will pass a prop-firm evaluation?

No. A source check can tell you whether a loss-limit input exists, not whether the limit holds. Whether a daily loss limit holds, how often the strategy trades and whether a consistency rule is met all need a run of the strategy against a firm's rules.

Sources

Each rule above is tied to one of these by its S number. Firms change their rules, so check the firm's page before you rely on one.

  1. S1 TradingView Pine Script docs: Repainting Read . Section 'Future leak with request.security()'
  2. S2 TradingView Pine Script docs: Limitations Read . Script size, request.*() calls, loops, plots, collections and drawings
  3. S3 TradingView Pine Script docs: Profiling and optimization Read . Loops, redrawing and reducing drawing updates
  4. S4 TradingView Pine Script docs: Strategies Read . Script execution settings and the strategy.risk.* commands
  5. S5 TradingView Pine Script docs: Style guide Read . Naming conventions; the guide does not cover price comparisons
  6. S6 TradingView Pine Script docs: Other timeframes and data Read . The lookahead parameter, higher-timeframe and lower-timeframe requests

About this guide

Compiled from TradingView's own Pine documentation by Matthew Hendricks, founder of Matchstick (about Matthew). Where a source is an archive snapshot, the Sources list says so. There are no affiliate links on these pages. Matchstick also talks to prop firms about a paid pilot (the firms page).

Matchstick Replay is a tool for replaying recorded futures market data. These pages are for futures traders who take prop-firm evaluation accounts (the test accounts you must pass first). Not about retirement accounts, banking, restaurant orders or video calls.

Hear when there is something new

One confirmation now, then one email when Replay ships something worth your time. Not a newsletter.

Email me when Replay ships