Algo Trading

5 Causes of Slow Pine Scripts on TradingView

By Jacob Denbrock9 min readReviewed by Christopher Downie on
5 Causes of Slow Pine Scripts on TradingView

Slow Pine scripts usually need profiling before optimization. Common causes include expensive loops, repeated work, inappropriate function usage, excessive data requests and unnecessary collections or drawings. The useful fix depends on which part of the script actually consumes time—and must preserve the intended result.

Pine runs in TradingView’s cloud environment. Script execution time is different from browser rendering, network delay, quote updates or broker execution. A faster calculation does not by itself establish faster fills or better trading performance. First identify whether the issue is a slow script, a runtime error or a chart interface problem.

CauseFirst change to investigateCorrectness check
Expensive loopsUse a matching built-in or reduce repeated iterationsCompare lookback, missing values and outputs
Repeated workReuse values within the intended executionDo not freeze a changing series with var
Misused functionsCheck context and history-dependent calculationsPreserve when and where the function executes
Too many requestsCombine expressions from the same requested contextKeep symbol, timeframe and timing assumptions unchanged
Collections and drawingsBound growth and update existing objectsRetain the history and visuals the script actually needs

Measure the Script with Pine Profiler

TradingView’s profiling and optimization guide describes using Profiler mode for an editable v6 script. Add the script to a chart, open its source in the Pine Editor and enable Profiler mode from the More menu. Inspect the significant lines and blocks with the highest time contribution.

Record the symbol, timeframe, inputs, amount of history and script version. Review both time and execution count: a modest calculation repeated across thousands of bars can matter more than one expensive operation performed once. Realtime updates can continue changing the collected results.

Profiler results are estimates. Instrumentation adds work, compiler transformations can alter what appears beside a source line, and reported regions do not account for every internal operation. Repeat comparisons under equivalent conditions rather than treating one run as an exact benchmark for all users.

1. Poor Loop Design

A loop that scans a long lookback on every bar can repeat substantial work. Nested loops multiply that cost. Look for calculations inside the loop that do not depend on its current index, and move those calculations outside the loop when doing so preserves their meaning.

For standard operations, consider an appropriate built-in such as ta.highest(). A built-in can use an implementation more efficient than a full manual scan. Verify its handling of warm-up, missing values, source series and length; two functions with similar names are not necessarily interchangeable.

TradingView’s documentation includes the following example measurements. The length is the calculation’s lookback setting, not the number of bars timed. The initial comparison ran across 20,735 script executions; these are results from that demonstration, not fixed per-call timings or promised savings.

Implementation in the documentationLength 20 runLength 200 run
Custom full-scan pineHighest()57.9 msAbout 600 ms
Custom fasterPineHighest()16.9 msAbout 75 ms
Built-in ta.highest()3.2 msAbout 5.8 ms

Keep custom loops when they implement logic a built-in does not provide. Bound inputs, avoid repeated array searches when the relevant index is already known, and break early only if remaining iterations cannot change the required answer. Do not skip historical calculations merely to lower a profiler number.

The limitations documentation sets a 500 ms limit for a loop on a single bar, with separate total-execution limits. A loop can stay under its per-bar limit and still exceed the script’s total time allowance across the full dataset.

2. Redundant Calculations

Reuse a calculated value when several outputs need the same result during an execution. This improves clarity and can avoid repeated work. But the compiler already removes some unused and redundant calculations, so a source-code reduction does not necessarily produce a runtime improvement.

A useful example is calculating a moving average once in a normal per-bar variable and using that value for plots and conditions. That differs from declaring the moving average with var and leaving it unchanged. The latter can preserve an early value, including an unavailable warm-up value, instead of tracking the market.

The variable declaration documentation distinguishes ordinary declarations, persistent var state and varip state that can persist across intrabar executions. These keywords control behavior; they are not interchangeable performance switches.

  • Use normal declarations for calculations that must update on each execution.
  • Use persistent initialization for values or objects intended to be created once, then explicitly update them when required.
  • Precompute fixed weights or other invariant inputs only when they truly remain valid for the calculation.
  • Treat varip as a deliberate intrabar-state choice, with different historical and realtime implications.

For switches or branches, separate invariant work from work that depends on the selected branch. Avoid merging two functions unless their inputs, state and evaluation timing remain equivalent. Profile the compiled result to see whether the change actually matters.

3. Misused Built-in Functions and Execution Context

A built-in is useful when it matches the task. Replacing a custom calculation with a different formula can produce a faster but incorrect indicator. Compare intermediate series on known bars and across the same configuration before judging the optimization successful.

History-dependent functions need consistent evaluation. The v6 migration guide explains how lazy evaluation of boolean conditions can skip calculations. Compute required series consistently, then apply display or event conditions to their results.

Data requests need attention to context too. The request documentation distinguishes dynamic requests from scripts that disallow them. With dynamic requests, calls can execute inside local conditionals and loops. It is therefore incorrect to claim that every request always executes regardless of its placement.

Conversely, placing a request in a function does not prove it runs only where it appears in the source. Compiler extraction and requested-context calculations can affect the profiler’s attribution. Read the relevant request-mode behavior and compare actual results rather than moving calls around based on a universal rule.

4. Too Many Data Requests

Each distinct requested context and expression can add work. Audit the symbol, timeframe, expression, gaps, lookahead and history needed by each request. Remove data the script does not use and avoid maintaining several versions of the same request without a reason.

The current limit is up to 40 unique request.*() calls, or 64 on TradingView’s Ultimate plan. Identical calls can reuse data and do not necessarily count as additional unique requests. A loop can still produce many unique requests when its context changes; the number of written call sites is not the whole picture.

When several values come from the same context, combine them in one tuple request. For example, close and a moving average from the same higher timeframe can share a request if their timing assumptions match. Do not combine unrelated contexts in a way that changes which series is being calculated.

TradingView’s profiler guide shows one example changing nine requests into a tuple request, with measured runtime falling from 340.8 ms to 228.3 ms in those runs. That demonstrates a possible improvement, not a fixed percentage for every script. Preserve the outputs and benchmark your own configuration.

The combined tuple-element limit across requests is 127. User-defined types can structure larger requested results where appropriate, but they do not remove other resource limits. Keep the requested data small enough for the actual task.

Use calc_bars_count only after determining the required history. Too small a value can omit needed warm-up or earlier observations and change results. Confirm both the initial valid value and behavior over the intended testing period after reducing requested history.

5. Unnecessary Memory and Drawing Work

Persistent arrays that grow on every bar can retain much more information than the script needs. Define the required window or capacity and remove or overwrite old values deliberately. Preallocating a fixed collection can make its intended size clear, but allocation alone does not prevent later growth.

TradingView documents a maximum of 100,000 elements for collections; a map uses two elements per key-value pair. This is a collection constraint, not a usable megabyte allowance for every script. Compiled size, variables, requests, history and drawings have their own limits and should not be collapsed into a single invented memory budget.

Avoid repeatedly copying large collections across requested contexts when only a few summary values are needed. Return the required calculation rather than a full history whenever that preserves the intended behavior. Review whether repeated object creation or retained references are necessary.

For drawings, consider updating an existing line, label, box or table through its setters instead of deleting and recreating it on each execution. Render only the history that serves the user’s purpose. Last-bar updates may suit a current-status table, but are inappropriate for historical calculations the table depends on.

Use documented profiler output and runtime diagnostics to investigate resource problems. Do not rely on a supposed memory.log() API or unsupported plan-specific megabyte figures. If the script hits a limit, isolate the relevant collection, request or drawing operation and verify the reduced version.

Keep Speed Improvements Reproducible

  • Save the baseline code and record the full chart configuration.
  • Profile the original and identify the highest-impact region.
  • Make one change that preserves the intended calculation.
  • Compare outputs, warm-up and realtime behavior with the baseline.
  • Repeat profiling on the same configuration and a larger supported input.
  • Keep the change only if its benefit and behavior are understood.

A smaller dataset or disabled feature can reduce execution time by doing less work. That may be a valid product choice, but document the changed coverage rather than presenting it as an equivalent optimization. Review the script again when its inputs, data requirements or features grow.

Use LuxAlgo to Organize Related Research

Begin chart-based research with LuxAlgo’s native charts and documented data coverage. Keep the instrument, source and timeframe consistent when comparing a hypothesis across charts.

Compare related chart experiments with consistent data and timeframe assumptions.

Ask Quant, our coding agent to implement or revise clear indicator logic. Inspect the generated code and run it yourself. Explain which outputs must remain unchanged and identify missing inputs before assessing the result.

Example prompt: “Review this indicator for repeated work and unnecessary state. Explain each proposed change, preserve the intended outputs and identify any timing or data differences. Keep the code inspectable so I can review it, run it and compare the same configuration.”

Organize related charts and indicator experiments in a LuxAlgo workspace.

If the indicator supports a strategy, use native strategy testing with realistic assumptions and review compatible recorded trades in the native journal. Calculation speed and the quality of a trading hypothesis are separate questions.

LuxAlgo native journal dashboard for reviewing recorded trades
Review recorded outcomes when an indicator forms part of a tested trading process.

For a Pine Script® running on TradingView, use TradingView’s runtime and profiler to verify Pine-specific performance. On LuxAlgo, Pine Script® runs through PineTS, and Quant can repair a slow script and backtest it against years of history; neither replaces profiling arbitrary Pine code.

Video: A Pine Profiler Walkthrough

The original The Art of Trading video from May 31, 2024 introduces the Pine Profiler and promotes the creator’s educational resources. It predates Pine v6, so use it for the profiling approach and consult the current documentation for version-specific behavior and limits.

Frequently Asked Questions

How do I identify the slowest part of a Pine script?

Use Pine Profiler on an editable supported script and inspect the significant lines and blocks with the highest time contribution. Record the configuration and compare repeated runs, because profiling adds overhead and produces estimates.

Should I use var for every expensive calculation?

No. It preserves initialized state until reassignment and can freeze a value that should change. Use persistence only when it matches the intended behavior.

Is the request limit always 40 written calls?

No. TradingView documents unique requests, with up to 40 or 64 on its Ultimate plan. Identical requests may reuse data, while changing contexts can create additional unique requests.

Does reducing calc_bars_count always preserve results?

No. It can remove needed history or warm-up observations. Determine the required history and compare outputs after changing it.

Does a faster Pine script guarantee faster trades?

No. Script runtime is separate from data updates, network delay, broker execution and fills. Profiling helps improve code efficiency, not establish trading performance.

Learn to trade smarter.

Market analysis and techniques that build your edge, one email a week.

Don’t worry, no spam here. See our privacy policy for more info.

Jacob Denbrock
Jacob Denbrock

CCO at LuxAlgo. 20 years of content creation experience, Jacob runs LuxAlgo's content team, brand growth, and hosts live shows showcasing his expertise in trading & LuxAlgo tools.

Read next