Compiled code contains too many tokens

This compilation error indicates that a script’s compiled code is too large for the runtime system to execute.

Before a script executes on a dataset, it is compiled by the Pine Script compiler. During this process, the compiler translates the written source code into a tokenized intermediate language (IL), then generates the final compiled code from the script’s IL form. Translating a script into an IL representation enables the compiler to perform various internal transformations to optimize the script’s structure.

Pine Script limits the maximum size of each indicator, strategy, and library for resource efficiency. This limit applies to the total size of the script’s IL translation after optimization. It does not apply to the number of characters or code lines in the original written code. This behavior explains why one script might fail with the “too many tokens” error during compilation, while another with the same number of characters or lines compiles and runs successfully. It also explains why some kinds of manual code changes, such as removing comments or shortening variable names, do not affect the size of a compiled script.

The maximum possible size of a script’s IL translation is 100,256 tokens. There are several ways to reduce a translated script’s size if it exceeds this limit, depending on the original script’s structure and the kinds of code it contains.

The sections on this page explain some of the typical optimizations that the compiler performs before measuring a translated script’s size, various types of manual code changes that have little to no effect on a script’s translation, and the most common techniques that can help reduce script size and resolve this error.

Ineffective code changes

Before the compiler measures the size of a translated script, it performs several code transformations to eliminate unnecessary tokens and optimize the script’s structure. Because of these transformations, several types of source code changes a programmer might consider to resolve the “too many tokens” error have little to no impact on the size of the compiled code. Common types of ineffective code changes include the following:

The following sections explain why these common source code changes do not help resolve this error. To learn about code changes that can meaningfully reduce the size of a translated script, refer to the Effective techniques to reduce script size section below.

Revising or removing comments

Programmers might assume that reducing the length of the comments in their scripts or removing them altogether reduces the size of the compiled code. However, the compiler discards all comments during translation, because their main purpose is to provide extra information in the source code for script readers. Therefore, reducing or removing comments has no effect on the compiled code.

For example, the simple script below contains multiple comments that document what each part of the code does:

//@version=6 indicator("Removing comments demo") // Define the input length factor for the RSI and RMA calculations. int lengthInput = input.int(14, "Length", minval = 2) // Calculate the RSI. float rsi = ta.rsi(close, lengthInput) // Calculate the RMA of the RSI using the same length. float ma = ta.rma(rsi, lengthInput) // Add the RMA of the difference between the MA and the RSI for the final smoothed output. float smoothed = ma + ta.rma(rsi - ma, lengthInput) // Plot the RSI and the smoothed RSI in a separate pane. plot(rsi, "RSI", color.blue) plot(smoothed, "Smoothed RSI", color.orange)

If we remove all the comments from the script, the source code becomes visibly shorter. However, because the compiler excludes comments automatically, the version of the code below translates to the same compiled result as the previous code:

//@version=6 indicator("Removing comments demo") int lengthInput = input.int(14, "Length", minval = 2) float rsi = ta.rsi(close, lengthInput) float ma = ta.rma(rsi, lengthInput) float smoothed = ma + ta.rma(rsi - ma, lengthInput) plot(rsi, "RSI", color.blue) plot(smoothed, "Smoothed RSI", color.orange)

Removing whitespaces and merging code lines

Pine Script code uses whitespace characters, including standard spaces and line terminators, to separate expressions and statements, organize code on multiple text lines, and visually delimit code blocks. However, the IL translation of a script uses a compact format, without most whitespace delimiters, to represent the script’s structure. Therefore, removing spaces between items in the source code does not affect the script’s compiled size. Likewise, the size is not affected by merging multiple statements onto a single code line and separating them with commas.

For example, the following script lists statements across multiple lines of code. The source code includes space separators, line-wrapped expressions, and multiple blank lines:

//@version=6 indicator("Removing whitespaces and merging code lines demo", overlay = true) int lengthInput1 = input.int( 14, "Fast MA length", minval = 2 ) int lengthInput2 = input.int( 28, "Slow MA length", minval = 2 ) float ma1 = ta.sma(close, lengthInput1) float ma2 = ta.sma(close, lengthInput2) bool condition = ( ma1 > ma2 and ma1 > ma1[1] and ma2 > ma2[1] ) plot(ma1, "Fast MA", color.orange) plot(ma2, "Slow MA", color.purple) bgcolor( condition ? color.new(color.green, 80) : na, title = "Condition highlight" )

The modified script version below removes multiple space separators and blank lines, eliminates line wrapping, and groups the statements onto fewer lines. Although the code is visually shorter and more compact, its IL form is identical to that of the previous version, because the compiler already discards the spaces and line terminators automatically while translating the code:

//@version=6 indicator("Removing whitespaces and merging code lines demo",overlay=true) int lengthInput1=input.int(14,"Fast MA length",minval=2),int lengthInput2=input.int(28,"Slow MA length",minval=2) float ma1=ta.sma(close,lengthInput1),float ma2=ta.sma(close,lengthInput2) bool condition=ma1>ma2 and ma1>ma1[1] and ma2>ma2[1],plot(ma1,"Fast MA",color.orange),plot(ma2,"Slow MA",color.purple) bgcolor(condition?color.new(color.green,80):na,title="Condition highlight")

Note that:

  • Automatic whitespace removal does not apply to literal strings, because each character in a string is part of the saved value. The values of literal strings and other “string” constants are embedded directly into a script’s IL translation and do affect the total size. See the Shortening or removing constant strings section below to learn more.

Changing code identifiers

When the compiler translates a script to an IL form, it automatically replaces the identifiers (names) of declared variables, user-defined functions, function parameters, user-defined types, and type fields with compact internal identifiers based on the code’s structure. These identifiers do not vary with the variable, function, and type names specified by the programmer in the source code. Therefore, changing identifiers in a script does not affect the script’s compiled size.

Similarly, named arguments in function calls (e.g., f(x = 1)) are automatically converted to positional arguments (e.g., f(1)) when possible. Therefore, manually converting named arguments to positional arguments typically has little to no effect on the compiled code’s size.

The following example script contains a user-defined function with multiple verbose identifiers in its signature and body. The script calls this function and the plot() function using named arguments. It also declares multiple variables with lengthy identifiers:

//@version=6 indicator("Changing code identifiers demo") calculateExponentialMovingAverage(float sourceSeries, int lengthFactor) => float smoothingFactor = 2.0 / (lengthFactor + 1.0) float exponentialMovingAverage = na exponentialMovingAverage := ( nz(exponentialMovingAverage[1], sourceSeries) * (1 - smoothingFactor) + sourceSeries * smoothingFactor ) float fastExponentialMovingAverage = calculateExponentialMovingAverage(sourceSeries = close, lengthFactor = 12) float slowExponentialMovingAverage = calculateExponentialMovingAverage(sourceSeries = close, lengthFactor = 26) float movingAverageConvergenceDivergence = fastExponentialMovingAverage - slowExponentialMovingAverage float signalMovingAverage = calculateExponentialMovingAverage(movingAverageConvergenceDivergence, lengthFactor = 9) float histogramLineValue = movingAverageConvergenceDivergence - signalMovingAverage plot(series = movingAverageConvergenceDivergence, title = "MACD", color = color.blue) plot(series = signalMovingAverage, title = "Signal", color = color.orange) plot( series = histogramLineValue, title = "Histogram", color = histogramLineValue > 0 ? color.green : color.red, style = plot.style_columns )

The script version below reduces the verbosity of all the script’s identifiers and changes the function calls to use positional arguments instead of named arguments. Although the code is now visibly more compact, it still translates to the same IL form as the previous version:

//@version=6 indicator("Changing code identifiers demo") ema(src, len) => sf = 2.0 / (len + 1.0) float ma = na ma := nz(ma[1], src) * (1 - sf) + src * sf maFast = ema(src = close, len = 12) maSlow = ema(src = close, len = 26) macd = maFast - maSlow sig = ema(macd, len = 9) hist = macd - sig plot(macd, "MACD", color.blue) plot(sig, "Signal", color.orange) plot(hist, "Histogram", hist > 0 ? color.green : color.red, style = plot.style_columns)

Note that:

  • We also removed type keywords from most of the variable and parameter declarations. If a declaration does not include a type keyword, the compiler automatically infers the appropriate type based on how the script uses the variable or parameter. Therefore, omitting type keywords typically does not affect the size of the IL translation.

Swapping constant expressions and values

When possible, the compiler automatically evaluates expressions that return constant values during translation. Rather than including tokens to represent each part of a constant expression in the script’s IL form, the compiler embeds the expression’s final value into the translated code. This optimization prevents the script from repeatedly recalculating the result at runtime. Therefore, replacing a constant expression with a literal value in the source code typically does not affect the size of the compiled script.

Similarly, if an if or switch structure or a conditional expression relies on a constant true or false condition, the compiler often simplifies the logic by replacing it with tokens for only the calculations that the script consistently executes. Therefore, manually simplifying such logic has little to no effect on the compiled script’s size.

The following example script assigns an expression that returns a “const int” value to a variable. It then checks the expression fixedValue == 6 in an if structure and adds 10 to the variable’s value if the expression evaluates to true:

//@version=6 indicator("Constant expressions demo") int fixedValue = (1 + 2) * 10 / 5 if fixedValue == 6 fixedValue += 10 else fixedValue := 0 plot(fixedValue, "16")

The above script plots a constant value of 16. The fixedValue variable is initially assigned a value of 6, so the fixedValue == 6 condition always evaluates to true, thus causing the variable’s final value to change to 16 on each bar. The compiler recognizes this pattern in the code and simplifies the logic to fixedValue = 16. Therefore, the script has the same IL translation as the script below:

//@version=6 indicator("Constant expressions demo") int fixedValue = 16 plot(fixedValue, "16")

Reducing identical expressions and functions

If the compiler detects that multiple expressions are structurally identical and consistently evaluate to the same value, it automatically reduces the duplicate expressions to avoid repetitive calculations when possible. Rather than including tokens for each repetition, the compiler assigns the translated expression to an internal variable, then uses that variable in all code that requires the expression’s result. This optimization helps conserve compiled tokens and potentially reduce the script’s runtime.

Therefore, manually reducing the repetition of expressions that the compiler can automatically replace has little to no effect on the size of a compiled script.

The following script shows a scenario in which removing repeated expressions does not affect the size of the compiled code. The script defines a posPercent() function, which calculates the percentage of positive values in a series over a specified length. The script plots the results of seven calls to the function, each using identical arguments:

//@version=6 indicator("Identical expressions demo") //@function Calculates the percentage of positive values in the `source` series over a specified length. posPercent(float source, int length) => float posSum = math.sum(source > 0 ? 1 : 0, length) float result = 100 * posSum / length // Plot the result of the same `posPercent()` call seven times. plot(posPercent((close - open) / (high - low), 10), color = color.red, linewidth = 7) plot(posPercent((close - open) / (high - low), 10), color = color.orange, linewidth = 6) plot(posPercent((close - open) / (high - low), 10), color = color.yellow, linewidth = 5) plot(posPercent((close - open) / (high - low), 10), color = color.green, linewidth = 4) plot(posPercent((close - open) / (high - low), 10), color = color.teal, linewidth = 3) plot(posPercent((close - open) / (high - low), 10), color = color.blue, linewidth = 2) plot(posPercent((close - open) / (high - low), 10), color = color.purple, linewidth = 1)

As the compiler translates the script, it verifies that all seven posPercent() expressions use identical value-type arguments, perform operations in the same order, and return the same result. It then restructures the script to execute only one call to the function and reuse the result in all the plot() calls. Therefore, the script has the same IL translation as the following script, which assigns one posPercent() call to a single variable and plots the variable’s value seven times:

//@version=6 indicator("Identical expressions demo") //@function Calculates the percentage of `source` values that are positive over a specified length. posPercent(float source, int length) => float posSum = math.sum(source > 0 ? 1 : 0, length) float result = 100 * posSum / length //@variable The percentage of positive body-to-wick ratios over 10 bars. float percentPositive = posPercent((close - open) / (high - low), 10) // Plot the `percentPositive` value seven times. plot(percentPositive, color = color.red, linewidth = 7) plot(percentPositive, color = color.orange, linewidth = 6) plot(percentPositive, color = color.yellow, linewidth = 5) plot(percentPositive, color = color.green, linewidth = 4) plot(percentPositive, color = color.teal, linewidth = 3) plot(percentPositive, color = color.blue, linewidth = 2) plot(percentPositive, color = color.purple, linewidth = 1)

Similar to the above, if the compiler identifies user-defined functions or methods that are structurally identical, have the same parameter types, and consistently return matching results when passed the same arguments, it automatically removes the duplicate function definitions from the translated code rather than including tokens for each one, when possible. The compiled script then treats each call to a duplicate function as an alias for a call to the first function. Therefore, manually removing such duplicate functions from the code does not typically affect the compiled script’s size.

To illustrate this behavior, the following script includes the posPercent() function from our previous example, along with a separate function named f() that follows the same structure and differs only in its identifiers. The script executes one posPercent() call and one f() call, then plots the results:

//@version=6 indicator("Removing identical functions demo") //@function Calculates the percentage of positive values in the `source` series over a specified length. posPercent(float source, int length) => float posSum = math.sum(source > 0 ? 1 : 0, length) log.info(str.tostring(posSum)) float result = 100 * posSum / length //@function A copy of `posPercent()`. Follows an identical structure, but uses different identifiers. f(float s, int l) => float ps = math.sum(s > 0 ? 1 : 0, l) log.info(str.tostring(ps)) float r = 100 * ps / l //@variable The ratio of the bar's body range to its wick range. float barRatio = (close - open) / (high - low) // Plot the results from calls to both functions. plot(posPercent(barRatio, 10)) plot(f(barRatio, 20)) // The compiled script treats this `f()` call as a call to `posPercent()`.

Aside from differences in identifiers, both functions in the script are identical. They have the same required parameters, rely on the same value types, perform calculations in the same order, and return matching values when passed the same arguments. The compiler recognizes this pattern and includes tokens for only the first function definition in the IL translation. After this transformation, the f() call is treated as a call to posPercent(). Therefore, the above script has the same compiled form as the script below:

//@version=6 indicator("Removing identical functions demo") //@function Calculates the percentage of positive values in the `source` series over a specified length. posPercent(float source, int length) => float posSum = math.sum(source > 0 ? 1 : 0, length) log.info(str.tostring(posSum)) float result = 100 * posSum / length //@variable The ratio of the bar's body range to its wick range. float barRatio = (close - open) / (high - low) // Plot the results from two calls to the function. plot(posPercent(barRatio, 10)) plot(posPercent(barRatio, 20))

Note that:

  • If we profile the previous script that contains an f() call, the Pine Profiler does not display any performance data next to the lines in that function’s definition. Instead, it shows combined performance results for both function calls next to the posPercent() definition, confirming that the duplicate function is removed during translation.

Removing unused code

As the compiler translates a script, it verifies which parts of the code affect the script’s outputs, such as plots, drawings, strategy orders, alerts, and Pine Logs. If the script contains code that does not affect any outputs, the compiler recognizes that the code is unused and automatically discards tokens for it in the IL translation. Therefore, manually removing unused code does not reduce a script’s compiled size.

The following example script contains a user-defined function that creates an array of simple moving averages over a range of specified lengths. It calls the function to create an array containing averages of close values over 2 to 50 bars, then uses the results to calculate a custom oscillator. However, the script does not use this code in any outputs; the only output from this script is a single plot(close) call:

//@version=6 indicator("Removing unused code demo") //#region // None of the code in this region contributes to any script output, such as a plot, drawing, or log. // Therefore, the compiler automatically *discards* it while translating the script. //@function Calculates simple moving averages of a `source` series over a range of lengths. //@param source The series of values to process. //@param minLength The length of the shortest SMA. //@param maxLength The length of the longest SMA. //@returns The ID of an array containing each calculated SMA, from shortest to longest. calcSMAs(float source, float minLength, float maxLength) => var array<float> result = array.new<float>() float total = ta.cum(source) result.clear() for length = minLength to maxLength float sum = total - total[length] float sma = sum / length result.push(sma) result //@variable References an array of SMAs over 2 to 50 bars. array<float> smas = calcSMAs(close, 2, 50) //@variable The average of all SMA values in the array. float avgSMA = smas.avg() //@variable The standard deviation of all values in the array. float dev = smas.stdev() //@variable The distance from the shortest SMA to the average SMA, relative to the standard deviation. float osc = (smas.first() - avgSMA) / dev //#endregion // This `plot()` call is the *only* code that generates an output, and it does not depend on the code above. plot(close)

The compiler recognizes that the user-defined function and all related calculations are unused by the script’s outputs, so it excludes tokens for that code in the IL translation. Consequently, after this transformation, the script above has the same compiled result as the following script:

//@version=6 indicator("Removing unused code demo") plot(close)

Effective techniques to reduce script size

The following sections list some of the most common types of code changes that can help meaningfully reduce the size of a compiled script. These techniques are effective because they involve code revisions that the compiler cannot perform automatically during translation. The techniques covered below include the following:

Shrinking or removing embedded data

One of the most common causes of the “too many tokens” error is a large quantity of embedded data in the translated code. A typical cause of large embedded data is the use of numerous constant values in calls to functions that support arbitrary numbers of arguments, such as array.from().

The Pine Script compiler embeds every literal “int”, “float”, “bool”, and “string” value used in a source code into the script’s IL translation. Likewise, it automatically evaluates constant expressions of these types, then embeds their results in the translated code to prevent recalculation at runtime. Additionally, if a script uses constant variables or expressions in function or method calls, the compiler inlines their values directly into each call site.

Therefore, hard-coding numerous constants into a script can significantly increase its compiled size, especially if the script uses them as arguments in function calls. For instance, a single array.from() call with thousands of constant arguments can consume the entire size budget on its own, depending on the saved values.

To reduce the amount of embedded data in a compiled script:

  • Swap large constants with concise expressions that compute the same values at runtime where possible.
  • Replace function calls and other expressions that use long lists of constants with logic that yields equivalent results at runtime. For instance, rather than defining an array.from() call with numerous constant arguments, create a loop that iteratively calculates the elements and inserts them into the array using functions such as array.set() or array.push().
  • If using a lengthy function call or other expression with many hard-coded values is unavoidable, consider moving the call into a user-defined function and exporting that function from a separate library. See the Splitting the script into parts section to learn more.

The following example script calculates a weighted moving average with custom precomputed weights. The script uses a single array.from() call with multiple literal “float” arguments to create an array of weights. It populates a single-row matrix with the array’s elements, calls the matrix.mult() method to calculate a weighted sum, then divides the result by the sum of the weights to compute the moving average. The array.from() call in this script significantly increases the size of the compiled code, because each literal value used as an argument is embedded directly into the IL translation. Adding multiple similar calls to this script can easily cause the “too many tokens” error:

//@version=6 indicator("Shrinking embedded data demo", overlay = true) //@variable References an array of precomputed weights. Each weight is a square number. // Every literal value defined in this `array.from()` call is *embedded* into the compiled code. var array<float> fixedWeights = array.from( 1.0, 4.0, 9.0, 16.0, 25.0, 36.0, 49.0, 64.0, 81.0, 100.0, 121.0, 144.0, 169.0, 196.0, 225.0, 256.0, 289.0, 324.0, 361.0, 400.0, 441.0, 484.0, 529.0, 576.0, 625.0, 676.0, 729.0, 784.0, 841.0, 900.0, 961.0, 1024.0, 1089.0, 1156.0, 1225.0, 1296.0, 1369.0, 1444.0, 1521.0, 1600.0, 1681.0, 1764.0, 1849.0, 1936.0, 2025.0, 2116.0, 2209.0, 2304.0, 2401.0, 2500.0, 2601.0, 2704.0, 2809.0, 2916.0, 3025.0, 3136.0, 3249.0, 3364.0, 3481.0, 3600.0, 3721.0, 3844.0, 3969.0, 4096.0, 4225.0, 4356.0, 4489.0, 4624.0, 4761.0, 4900.0, 5041.0, 5184.0, 5329.0, 5476.0, 5625.0, 5776.0, 5929.0, 6084.0, 6241.0, 6400.0, 6561.0, 6724.0, 6889.0, 7056.0, 7225.0, 7396.0, 7569.0, 7744.0, 7921.0, 8100.0, 8281.0, 8464.0, 8649.0, 8836.0, 9025.0, 9216.0, 9409.0, 9604.0, 9801.0, 10000.0, 10201.0, 10404.0, 10609.0, 10816.0, 11025.0, 11236.0, 11449.0, 11664.0, 11881.0, 12100.0, 12321.0, 12544.0, 12769.0, 12996.0, 13225.0, 13456.0, 13689.0, 13924.0, 14161.0, 14400.0, 14641.0, 14884.0, 15129.0, 15376.0, 15625.0, 15876.0, 16129.0, 16384.0, 16641.0, 16900.0, 17161.0, 17424.0, 17689.0, 17956.0, 18225.0, 18496.0, 18769.0, 19044.0, 19321.0, 19600.0, 19881.0, 20164.0, 20449.0, 20736.0, 21025.0, 21316.0, 21609.0, 21904.0, 22201.0, 22500.0, 22801.0, 23104.0, 23409.0, 23716.0, 24025.0, 24336.0, 24649.0, 24964.0, 25281.0, 25600.0, 25921.0, 26244.0, 26569.0, 26896.0, 27225.0, 27556.0, 27889.0, 28224.0, 28561.0, 28900.0, 29241.0, 29584.0, 29929.0, 30276.0, 30625.0, 30976.0, 31329.0, 31684.0, 32041.0, 32400.0, 32761.0, 33124.0, 33489.0, 33856.0, 34225.0, 34596.0, 34969.0, 35344.0, 35721.0, 36100.0, 36481.0, 36864.0, 37249.0, 37636.0, 38025.0, 38416.0, 38809.0, 39204.0, 39601.0, 40000.0, 40401.0, 40804.0, 41209.0, 41616.0, 42025.0, 42436.0, 42849.0, 43264.0, 43681.0, 44100.0, 44521.0, 44944.0, 45369.0, 45796.0, 46225.0, 46656.0, 47089.0, 47524.0, 47961.0, 48400.0, 48841.0, 49284.0, 49729.0, 50176.0, 50625.0, 51076.0, 51529.0, 51984.0, 52441.0, 52900.0, 53361.0, 53824.0, 54289.0, 54756.0, 55225.0, 55696.0, 56169.0, 56644.0, 57121.0, 57600.0, 58081.0, 58564.0, 59049.0, 59536.0, 60025.0, 60516.0, 61009.0, 61504.0, 62001.0, 62500.0, 63001.0, 63504.0, 64009.0, 64516.0, 65025.0, 65536.0, 66049.0, 66564.0, 67081.0, 67600.0, 68121.0, 68644.0, 69169.0, 69696.0, 70225.0, 70756.0, 71289.0, 71824.0, 72361.0, 72900.0, 73441.0, 73984.0, 74529.0, 75076.0, 75625.0, 76176.0, 76729.0, 77284.0, 77841.0, 78400.0, 78961.0, 79524.0, 80089.0, 80656.0, 81225.0, 81796.0, 82369.0, 82944.0, 83521.0, 84100.0, 84681.0, 85264.0, 85849.0, 86436.0, 87025.0, 87616.0, 88209.0, 88804.0, 89401.0, 90000.0, 90601.0, 91204.0, 91809.0, 92416.0, 93025.0, 93636.0, 94249.0, 94864.0, 95481.0, 96100.0, 96721.0, 97344.0, 97969.0, 98596.0, 99225.0, 99856.0, 100489.0, 101124.0, 101761.0, 102400.0, 103041.0, 103684.0, 104329.0, 104976.0, 105625.0, 106276.0, 106929.0, 107584.0, 108241.0, 108900.0, 109561.0, 110224.0, 110889.0, 111556.0, 112225.0, 112896.0, 113569.0, 114244.0, 114921.0, 115600.0, 116281.0, 116964.0, 117649.0, 118336.0, 119025.0, 119716.0, 120409.0, 121104.0, 121801.0, 122500.0, 123201.0, 123904.0, 124609.0, 125316.0, 126025.0, 126736.0, 127449.0, 128164.0, 128881.0, 129600.0, 130321.0, 131044.0, 131769.0, 132496.0, 133225.0, 133956.0, 134689.0, 135424.0, 136161.0, 136900.0, 137641.0, 138384.0, 139129.0, 139876.0, 140625.0, 141376.0, 142129.0, 142884.0, 143641.0, 144400.0, 145161.0, 145924.0, 146689.0, 147456.0, 148225.0, 148996.0, 149769.0, 150544.0, 151321.0, 152100.0, 152881.0, 153664.0, 154449.0, 155236.0, 156025.0, 156816.0, 157609.0, 158404.0, 159201.0, 160000.0, 160801.0, 161604.0, 162409.0, 163216.0, 164025.0, 164836.0, 165649.0, 166464.0, 167281.0, 168100.0, 168921.0, 169744.0, 170569.0, 171396.0, 172225.0, 173056.0, 173889.0, 174724.0, 175561.0, 176400.0, 177241.0, 178084.0, 178929.0, 179776.0, 180625.0, 181476.0, 182329.0, 183184.0, 184041.0, 184900.0, 185761.0, 186624.0, 187489.0, 188356.0, 189225.0, 190096.0, 190969.0, 191844.0, 192721.0, 193600.0, 194481.0, 195364.0, 196249.0, 197136.0, 198025.0, 198916.0, 199809.0, 200704.0, 201601.0, 202500.0, 203401.0, 204304.0, 205209.0, 206116.0, 207025.0, 207936.0, 208849.0, 209764.0, 210681.0, 211600.0, 212521.0, 213444.0, 214369.0, 215296.0, 216225.0, 217156.0, 218089.0, 219024.0, 219961.0, 220900.0, 221841.0, 222784.0, 223729.0, 224676.0, 225625.0, 226576.0, 227529.0, 228484.0, 229441.0, 230400.0, 231361.0, 232324.0, 233289.0, 234256.0, 235225.0, 236196.0, 237169.0, 238144.0, 239121.0, 240100.0, 241081.0, 242064.0, 243049.0, 244036.0, 245025.0, 246016.0, 247009.0, 248004.0, 249001.0, 250000.0, 251001.0, 252004.0, 253009.0, 254016.0, 255025.0, 256036.0, 257049.0, 258064.0, 259081.0, 260100.0, 261121.0, 262144.0, 263169.0, 264196.0, 265225.0, 266256.0, 267289.0, 268324.0, 269361.0, 270400.0, 271441.0, 272484.0, 273529.0, 274576.0, 275625.0, 276676.0, 277729.0, 278784.0, 279841.0, 280900.0, 281961.0, 283024.0, 284089.0, 285156.0, 286225.0, 287296.0, 288369.0, 289444.0, 290521.0, 291600.0, 292681.0, 293764.0, 294849.0, 295936.0, 297025.0, 298116.0, 299209.0, 300304.0, 301401.0, 302500.0, 303601.0, 304704.0, 305809.0, 306916.0, 308025.0, 309136.0, 310249.0, 311364.0, 312481.0, 313600.0, 314721.0, 315844.0, 316969.0, 318096.0, 319225.0, 320356.0, 321489.0, 322624.0, 323761.0, 324900.0, 326041.0, 327184.0, 328329.0, 329476.0, 330625.0, 331776.0, 332929.0, 334084.0, 335241.0, 336400.0, 337561.0, 338724.0, 339889.0, 341056.0, 342225.0, 343396.0, 344569.0, 345744.0, 346921.0, 348100.0, 349281.0, 350464.0, 351649.0, 352836.0, 354025.0, 355216.0, 356409.0, 357604.0, 358801.0, 360000.0, 361201.0, 362404.0, 363609.0, 364816.0, 366025.0, 367236.0, 368449.0, 369664.0, 370881.0, 372100.0, 373321.0, 374544.0, 375769.0, 376996.0, 378225.0, 379456.0, 380689.0, 381924.0, 383161.0, 384400.0, 385641.0, 386884.0, 388129.0, 389376.0, 390625.0, 391876.0, 393129.0, 394384.0, 395641.0, 396900.0, 398161.0, 399424.0, 400689.0, 401956.0, 403225.0, 404496.0, 405769.0, 407044.0, 408321.0, 409600.0, 410881.0, 412164.0, 413449.0, 414736.0, 416025.0, 417316.0, 418609.0, 419904.0, 421201.0, 422500.0, 423801.0, 425104.0, 426409.0, 427716.0, 429025.0, 430336.0, 431649.0, 432964.0, 434281.0, 435600.0, 436921.0, 438244.0, 439569.0, 440896.0, 442225.0, 443556.0, 444889.0, 446224.0, 447561.0, 448900.0, 450241.0, 451584.0, 452929.0, 454276.0, 455625.0, 456976.0, 458329.0, 459684.0, 461041.0, 462400.0, 463761.0, 465124.0, 466489.0, 467856.0, 469225.0, 470596.0, 471969.0, 473344.0, 474721.0, 476100.0, 477481.0, 478864.0, 480249.0, 481636.0, 483025.0, 484416.0, 485809.0, 487204.0, 488601.0, 490000.0, 491401.0, 492804.0, 494209.0, 495616.0, 497025.0, 498436.0, 499849.0, 501264.0, 502681.0, 504100.0, 505521.0, 506944.0, 508369.0, 509796.0, 511225.0, 512656.0, 514089.0, 515524.0, 516961.0, 518400.0, 519841.0, 521284.0, 522729.0, 524176.0, 525625.0, 527076.0, 528529.0, 529984.0, 531441.0, 532900.0, 534361.0, 535824.0, 537289.0, 538756.0, 540225.0, 541696.0, 543169.0, 544644.0, 546121.0, 547600.0, 549081.0, 550564.0, 552049.0, 553536.0, 555025.0, 556516.0, 558009.0, 559504.0, 561001.0, 562500.0, 564001.0, 565504.0, 567009.0, 568516.0, 570025.0, 571536.0, 573049.0, 574564.0, 576081.0, 577600.0, 579121.0, 580644.0, 582169.0, 583696.0, 585225.0, 586756.0, 588289.0, 589824.0, 591361.0, 592900.0, 594441.0, 595984.0, 597529.0, 599076.0, 600625.0, 602176.0, 603729.0, 605284.0, 606841.0, 608400.0, 609961.0, 611524.0, 613089.0, 614656.0, 616225.0, 617796.0, 619369.0, 620944.0, 622521.0, 624100.0, 625681.0, 627264.0, 628849.0, 630436.0, 632025.0, 633616.0, 635209.0, 636804.0, 638401.0, 640000.0 ) //@variable References a single-row matrix containing the elements of the `fixedWeights` array. // The script uses this matrix to calculate the dot product for the average's numerator. var matrix<float> weightMatrix = matrix.new<float>() // Add the `fixedWeights` array's elements to the matrix on the first bar. if barstate.isfirst weightMatrix.add_row(0, fixedWeights) //@variable References an array of consecutive `close` values. var array<float> prices = array.new<float>(fixedWeights.size()) // Queue the current `close` value into the `prices` array on each bar. prices.push(close) prices.shift() // Calculate and plot the weighted average. float weightedAvg = weightMatrix.mult(prices).first() / fixedWeights.sum() plot(weightedAvg, "Weighted average")

The array in this example contains a series of square numbers, where the first element is 1 (1 * 1), the second is 4 (2 * 2), and so on. In other words, each element is the square of i + 1, where i refers to the element’s index. Therefore, we can reduce our script’s size by calculating the array’s values within a loop rather than defining them directly in the code.

The script version below creates a persistent array using an array.new<float>() call, then uses a loop to populate it on the first bar. On each iteration, the script calculates a square value, then calls the array.push() function to push the value into the array. This version calculates the same results as the previous script, but its compiled size is substantially smaller because we avoided defining a long list of literal values. Additionally, this source code is visibly shorter and simpler to maintain:

//@version=6 indicator("Shrinking embedded data demo", overlay = true) //@variable References an array of weights calculated on the first bar. Each weight is a square number. var array<float> fixedWeights = array.new<float>() //@variable References a single-row matrix containing the elements of the `fixedWeights` array. // The script uses this matrix to calculate the dot product for the average's numerator. var matrix<float> weightMatrix = matrix.new<float>() // Populate the weight array and matrix on the first bar. if barstate.isfirst // Although this loop executes over a fixed range and uses consistent operations, // the compiler does not embed all the calculated values into the translated code. // It includes only necessary tokens for the loop's logic, so the compiled size is much smaller. for i = 1 to 800 float weight = i * i fixedWeights.push(weight) weightMatrix.add_row(0, fixedWeights) //@variable References an array of consecutive `close` values. var array<float> prices = array.new<float>(fixedWeights.size()) // Queue the current `close` value into the `prices` array on each bar. prices.push(close) prices.shift() // Calculate and plot the weighted average. float weightedAvg = weightMatrix.mult(prices).first() / fixedWeights.sum() plot(weightedAvg, "Weighted average")

Shortening or removing constant strings

As explained in the section Shrinking or removing embedded data above, the compiler embeds constant values, including strings, into a script’s IL translation where possible. Consequently, using lengthy constant strings in a script can significantly increase the size of the compiled code, especially if the script passes the strings as multiple arguments in function calls.

If a script with lengthy or multiple constant strings causes the “too many tokens” error, try any of the following to reduce the script’s compiled size:

  • Remove all lengthy constant strings that the script’s logic does not absolutely require, and try to reduce the length of all others.
  • Avoid using a long constant string for multiple arguments in function calls. Each instance of the argument’s value is inlined at the call site when possible. Therefore, using the value for multiple arguments can multiply its effect on the compiled code’s size.
  • Check for repetitive expressions that use the same constant string. If possible, replace the repeated expressions with a single expression that executes in a loop. For instance, if the script contains multiple str.format() calls with the same formatting string, replace them with a loop that executes a single str.format() call multiple times.
  • Shorten the titles of enum members used by the script’s logic. The titles of enum members are embedded into parts of the code that require their values. By default, the title of each member is the “const string” representation of its name. Therefore, if an enum member does not have a specified title, shorten the member’s name or assign it a short title.
  • If the script requires a specific lengthy string, but does not require it to be a constant, create a user-defined function that returns a “simple” or “series” version of the value, or the ID of a collection that contains the value, then export the function from a separate library. See the Splitting the script into parts section to learn more.

Replacing unrolled statements with loops

Loop unrolling is a common technique in which the programmer writes a set of repetitive statements and expressions to perform a sequence of calculations without a loop. This technique can marginally improve a script’s runtime in some cases. However, each instance of repeated code that the compiler cannot reduce adds tokens to the script’s IL translation. Consequently, writing several pieces of unrolled code can impact the size of a compiled script.

Therefore, if a script contains multiple unrolled statements or expressions, defining an equivalent, concise loop to execute the necessary calculations can help reduce the script’s compiled size.

For example, the following script calculates the number of close values that are greater than the current value over the past 50 bars. The script calculates the value using 50 written statements that perform the same operations using different values. All of the statements are unique and cannot be reduced by the compiler. Therefore, each one adds tokens to the script’s IL translation:

//@version=6 indicator("Replacing unrolled code with loops demo") //@variable The number of past `close` values, over the latest 50 bars, that are greater than the current `close` value. int higherCloses = 0 // Add 1 to the `higherCloses` value for each past `close` value that is greater than the current value. // All of these statements add separate tokens to the translated code. higherCloses += close[1] > close ? 1 : 0 higherCloses += close[2] > close ? 1 : 0 higherCloses += close[3] > close ? 1 : 0 higherCloses += close[4] > close ? 1 : 0 higherCloses += close[5] > close ? 1 : 0 higherCloses += close[6] > close ? 1 : 0 higherCloses += close[7] > close ? 1 : 0 higherCloses += close[8] > close ? 1 : 0 higherCloses += close[9] > close ? 1 : 0 higherCloses += close[10] > close ? 1 : 0 higherCloses += close[21] > close ? 1 : 0 higherCloses += close[22] > close ? 1 : 0 higherCloses += close[23] > close ? 1 : 0 higherCloses += close[24] > close ? 1 : 0 higherCloses += close[25] > close ? 1 : 0 higherCloses += close[26] > close ? 1 : 0 higherCloses += close[27] > close ? 1 : 0 higherCloses += close[28] > close ? 1 : 0 higherCloses += close[29] > close ? 1 : 0 higherCloses += close[30] > close ? 1 : 0 higherCloses += close[31] > close ? 1 : 0 higherCloses += close[32] > close ? 1 : 0 higherCloses += close[33] > close ? 1 : 0 higherCloses += close[34] > close ? 1 : 0 higherCloses += close[35] > close ? 1 : 0 higherCloses += close[36] > close ? 1 : 0 higherCloses += close[37] > close ? 1 : 0 higherCloses += close[38] > close ? 1 : 0 higherCloses += close[39] > close ? 1 : 0 higherCloses += close[40] > close ? 1 : 0 higherCloses += close[41] > close ? 1 : 0 higherCloses += close[42] > close ? 1 : 0 higherCloses += close[43] > close ? 1 : 0 higherCloses += close[44] > close ? 1 : 0 higherCloses += close[45] > close ? 1 : 0 higherCloses += close[46] > close ? 1 : 0 higherCloses += close[47] > close ? 1 : 0 higherCloses += close[48] > close ? 1 : 0 higherCloses += close[49] > close ? 1 : 0 higherCloses += close[50] > close ? 1 : 0 plot(higherCloses)

We can reduce our script’s token count by replacing all the repetitive statements with a single loop that executes the required logic iteratively. The script version below uses a for loop that increments its counter variable (i) from 1 to 50. The single statement in the loop’s local block increments the higherCloses value on each iteration where the value of close[i] is greater than the close value on the current bar. This version of the script achieves the same result as the previous version, but its compiled size is significantly smaller:

//@version=6 indicator("Replacing unrolled code with loops demo") //@variable The number of past `close` values, over the latest 50 bars, that are greater than the current `close` value. int higherCloses = 0 // Add 1 to the `higherCloses` value for each past `close` value that is greater than the current value. // This loop consumes fewer tokens than an equivalent set of unrolled statements, and it is easier to maintain. for i = 1 to 50 higherCloses += close[i] > close ? 1 : 0 plot(higherCloses)

Encapsulating code in functions

Function calls in Pine Script are not inlined, meaning the compiler does not replace each function call with a copy of the function’s body during translation, unlike the compilers for some other languages. However, the compiler does typically include tokens for each argument in a function call, including the function’s default arguments. Therefore, the size that each function call contributes to a compiled script often varies with the number of parameters.

Calls to functions with few parameters typically have low impact on the size of the compiled code, unless the arguments are lengthy constant strings. By contrast, calls to functions with many parameters, or to functions that support an arbitrary number of arguments, can significantly impact a compiled script’s size in some cases. See the Shrinking or removing embedded data section above for an example.

If a script contains multiple large, repetitive statements or expressions, or calls to functions with many parameters, encapsulating that code in a separate user-defined function containing as few parameters as possible can help reduce the size of a compiled script.

The following example script calculates an average Efficiency Ratio across several periods that are powers of two. The script declares a variable with an initial value of 0, then uses nine addition assignment operations that use the same expressions but different length values. It then divides the value by 9 and plots the result. Each of the repetitive statements in this script contributes multiple tokens to the script’s IL translation. We cannot simplify the script by replacing the statements with a loop, because the ta.change() and math.sum() calls require one execution on every bar for correct results:

//@version=6 indicator("Encapsulating code in functions demo") //@variable The average Efficiency Ratio measured across multiple periods that are powers of 2. float avgEfficiency = 0.0 // Calculate each period's value as the ratio of absolute change to the total bar-by-bar change over the period, // and add it to the total. Each repetitive expression here contributes to the script's compiled size. avgEfficiency += math.abs(ta.change(close, 8)) / math.sum(math.abs(close - close[1]), 8) avgEfficiency += math.abs(ta.change(close, 16)) / math.sum(math.abs(close - close[1]), 16) avgEfficiency += math.abs(ta.change(close, 32)) / math.sum(math.abs(close - close[1]), 32) avgEfficiency += math.abs(ta.change(close, 64)) / math.sum(math.abs(close - close[1]), 64) avgEfficiency += math.abs(ta.change(close, 128)) / math.sum(math.abs(close - close[1]), 128) avgEfficiency += math.abs(ta.change(close, 256)) / math.sum(math.abs(close - close[1]), 256) avgEfficiency += math.abs(ta.change(close, 512)) / math.sum(math.abs(close - close[1]), 512) avgEfficiency += math.abs(ta.change(close, 1024)) / math.sum(math.abs(close - close[1]), 1024) avgEfficiency += math.abs(ta.change(close, 2048)) / math.sum(math.abs(close - close[1]), 2048) // Divide the result by 9 to calculate the average. avgEfficiency /= 9 plot(avgEfficiency, "Average ER")

We can reduce our script’s size by encapsulating the structure of the repetitive expressions within a user-defined function, then replacing each expression with a concise function call. The script version below defines a calcER() function, which calculates the Efficiency Ratio of close values over a specified length. It then uses a call to this function instead of the repeated expressions in each addition assignment operation. Each calcER() call is not inlined; the compiler adds tokens only for the function’s reference and its single argument. Therefore, in addition to being visibly more compact, this version has a smaller compiled size:

//@version=6 indicator("Encapsulating code in functions demo") //@function Calculates the Kaufman's Efficiency Ratio for `close` values over a specified length. // The compiler does not inline this function's body into each function call. The calculations in the // function's body are defined only once in the compiled code. calcER(int length) => math.abs(ta.change(close, length)) / math.sum(math.abs(close - close[1]), length) //@variable The average Efficiency Ratio measured across multiple periods that are powers of 2. float avgEfficiency = 0.0 // Calculate each period's value as the ratio of absolute change to the total bar-by-bar change over the period, // and add it to the total. // The concise `calcER()` calls below add tokens only for the function's reference and each "int" argument. // Therefore, each repetitive statement here has a lower impact on the compiled size. avgEfficiency += calcER(8) avgEfficiency += calcER(16) avgEfficiency += calcER(32) avgEfficiency += calcER(64) avgEfficiency += calcER(128) avgEfficiency += calcER(256) avgEfficiency += calcER(512) avgEfficiency += calcER(1024) avgEfficiency += calcER(2048) // Divide the result by 9 to calculate the average. avgEfficiency /= 9 plot(avgEfficiency, "Average ER")

Replacing custom code with built-ins

Pine Script features many built-in functions and variables that help simplify code and streamline script creation. Each written call to a built-in function produces compiled tokens for only the function’s reference and the call’s arguments. Similarly, many built-in variables produce minimal tokens for referencing data calculated outside the script.

Sometimes, programmers create custom code that mirrors built-ins or yields equivalent results. Custom code implementations provide extra freedom to modify calculations as needed. However, they also typically contribute more tokens to a script’s IL translation. Therefore, if a script performs calculations that can be done with built-ins, replacing those calculations with built-ins can often help reduce a script’s compiled size.

The following example script computes a weighted moving average of hlc3 values over an input length, and it calculates the daily volume-weighted average price. The script uses custom functions to compute both values rather than using equivalent built-ins. All the code defined within those functions adds tokens to the script’s IL translation, thus increasing its size:

//@version=6 indicator("Replacing custom code with built-ins demo", overlay = true) //@variable The number of bars in the weighted moving average. int lengthInput = input.int(20, "WMA length", 2) //@function A custom function that calculates the weighted moving average of a series over a fixed length. // This function is equivalent to using `ta.wma()` with a consistent `length` argument. wma(float source, simple int length) => var float sum = 0.0 var float numerator = 0.0 var float denominator = 0.5 * length * (length + 1) sum += source - nz(source[length]) numerator += length * source - nz(sum[1]) bar_index < length ? na : numerator / denominator //@function A custom function that calculates the daily volume-weighted average price. // This function produces the same result stored by the `ta.vwap` variable. calcDailyVWAP() => var float numerator = 0.0 var float denominator = 0.0 if timeframe.change("1D") numerator := 0.0 denominator := 0.0 numerator += hlc3 * volume denominator += volume numerator / denominator //@variable The 20-bar weighted average of `hlc3` values. float wmaSeries = wma(hlc3, 20) //@variable The daily VWAP. float vwapSeries = calcDailyVWAP() // Plot both series on the chart. plot(wmaSeries, "WMA", wmaSeries > vwapSeries ? color.green : color.red, 3) plot(vwapSeries, "VWAP", color.orange)

Implementing custom functions for our script’s calculations is unnecessary. We can use the built-in ta.wma() function to calculate the weighted moving average, and use the ta.vwap variable to retrieve the daily volume-weighted average price. The script version below implements these changes. This version computes the same results, is visibly shorter, and has a smaller compiled size:

//@version=6 indicator("Replacing custom code with built-ins demo", overlay = true) //@variable The number of bars in the weighted moving average. int lengthInput = input.int(20, "WMA length", 2) //@variable The 20-bar weighted average of `hlc3` values. float wmaSeries = ta.wma(hlc3, 20) //@variable The daily VWAP. float vwapSeries = ta.vwap // Plot both series on the chart. plot(wmaSeries, "WMA", wmaSeries > vwapSeries ? color.green : color.red, 3) plot(vwapSeries, "VWAP", color.orange)

Splitting the script into parts

If a script causes the “too many tokens” error, and no manual source code revisions resolve it without compromising the script’s logic, the typical solution is to split the code into two or more smaller scripts or offload reusable code components into libraries. Distributing a script’s logic across multiple scripts directly reduces the compiled tokens from a single source code.

Programmers can try any of the following to split a large script’s logic, depending on the types of code it uses and the tasks that it performs:

  • If the script contains multiple large function or method definitions, consider exporting them from a separate library, and importing that library into the script. Likewise, if the script contains several lengthy statements, consider defining exported library functions to encapsulate those statements, if possible. Moving functions to libraries often fixes the problem, because libraries are compiled independently and their code does not directly affect the compiled size of the scripts that import them. A single script can import multiple libraries, provided their combined size does not exceed 1 million tokens. Refer to the Libraries page to learn more about library scripts and how to use them.
  • If the script performs multiple independent tasks, create independent scripts for each task instead. For example, if a script calculates multiple complex indicators for no reason other than to consolidate them into a single source code, move each indicator’s logic into a separate script to keep compiled sizes low.
  • If the script performs a lengthy set of related calculations, consider creating separate indicators for intermediate stages of those calculations, then use plots and source inputs to pass data between them. To learn more about connecting scripts to other scripts, refer to the How to apply an indicator or strategy to another indicator article in our Help Center. Note that charts allow up to 24 total indicator-on-indicator connections, and a maximum of 11 indicators connected sequentially (i.e., indicator_1 -> indicator_2 -> ... indicator_11). See this related article in our Help Center for more information.