Skip to content
ChartDeck ChartScript docs Account Open the terminal

Profiling

Find the slow part of a ChartScript script with the editor's profiler, and with compile times, time-limit messages, work counters and switches.

When a script is slow, guessing which part is to blame wastes time. This page shows what ChartScript tells you about speed, and a step-by-step method to find the expensive part of a script and confirm that a change made it faster.

#The profiler

The editor's Profile tab measures where a script spends its time.

  1. Open the script in the editor and select the chart you want to measure on (the market, the timeframe and the number of bars all change the answer).
  2. Open the Profile tab and press Run profile.
  3. The script runs once on that chart with timers, and the tab lists its lines, slowest first.

Each row is one line of the script:

ColumnWhat it says
LineThe line number. Click it to put the cursor on that line.
TimeThe time spent in that line over the whole run, in milliseconds.
ShareThat time as a part of the time of all the script's lines.
RunsHow many times the line ran. This number is exact.
CodeThe line itself.

A line that runs a great many times, such as the body of a loop, is not timed by itself: reading the clock that often would cost more than the line does and would bend the result. It is listed, indented, under the line it is counted in (its loop, or the line that calls its function), with its exact number of runs. So a row like "Line 5 · 31 ms" followed by "Line 6 · in line 5 · 960,000" reads: the loop on line 5 took 31 ms, and its body ran 960,000 times.

Under the lines, Functions lists your own functions with the time of their lines and how often they were called.

The summary above the table gives the time of the whole run, the part spent in the script's lines, the part outside them (ChartDeck's own work between bars), and what the timers themselves cost; that cost is already taken off the lines.

Things to know:

  • The profiler is off until you press the button. Nothing is measured while you edit, and the script on your chart is never the timed one: the profile is a separate run that is removed when it ends. If Preview on chart is on, the preview is used for the run and put back afterwards.
  • A profiled run is a little slower than a normal one (about 1.3 times on a loop-heavy script in our tests), because it counts every line and reads the clock for the lines it times.
  • A run that only takes a few milliseconds gives rough times; the tab says so. The run counts are exact whatever the speed, and for most slow scripts the counts already tell the story.
  • If the run is stopped for taking too long (CS5401), the tab shows the numbers up to the stop: that is the case the profiler is for.
  • Time spent computing other timeframes and markets for request.* is not split by line; it shows in the part outside the script's lines.
  • A library cannot be profiled on its own. Profile a script that imports it.
  • After you change the script, the tab notes that its numbers belong to the older text. Run it again.

The rest of this page is what to do with the answer, and how to dig further by hand.

#What ChartScript tells you

#Compile time

Every compile writes one line to the editor's Console tab: "Compiled in 14 ms · 120 lines". This is how long the compiler took to check and translate your script, not how long the script takes to run on the chart. It is useful for one thing only: a very large script (thousands of lines, or a big library) compiles noticeably slower, and you feel it while typing. It says nothing about loops or drawings.

#Run time, when it is too long

The script's run time is checked against its limits on every run (see Performance). You hear about it when a limit is crossed:

  • CS5401 "Stopped: the script took too long (2612 ms; the limit is 2500 ms)." The numbers tell you how far over the full run went, which tells you how much you need to save.
  • CS5402 for one live update over 50 ms.
  • "Updating on bar close only (too slow per tick)" in the chart legend: live updates were slow twice in a row.

#Why timenow will not time your code

timenow is the current time, but it is taken once when a run starts and is the same on every bar of that run. Subtracting two readings inside one run always gives 0, so it cannot measure parts of a script. Use it for what it is meant for: comparing the clock with a bar's time.

#Count the work instead of timing it

Time measurements are noisy: they change with the device, the other tabs and whatever the browser is doing. Counting is exact and repeatable. The cost of almost every slow script comes from one of a few things you can count:

  • loop passes per bar,
  • drawings created,
  • bars the script runs on,
  • data contexts requested.

Add state counters around the suspicious parts and log the totals once, on the last bar:

ChartScriptOpen in editor
//@chartscript 1
indicator("Count the work", { overlay: true })
const len = input.int(100, "Look back", { minval: 1, maxval: 1000 })
const useScan = input.bool(true, "Part A: level scan")
const useBands = input.bool(true, "Part B: bands")

// work counters (debug only)
state scanPasses: int = 0
state barsRun: int = 0
barsRun += 1

// Part A: the nearest recent high above the close
let nearest: float = na
if (useScan) {
    for (let i = 1; i <= len; i++) {
        scanPasses += 1
        const lvl = high[i]
        if (lvl > close && (na(nearest) || lvl < nearest)) {
            nearest = lvl
        }
    }
}
plot(nearest, "Nearest high above", color.down, { style: plot.style_linebr })

// Part B: bands
const mid = ta.sma(close, 20)
const dev = ta.stdev(close, 20)
plot(useBands ? mid + 2 * dev : na, "Upper", color.muted)
plot(useBands ? mid - 2 * dev : na, "Lower", color.muted)

if (barstate.islast) {
    label.new(
        bar_index,
        high,
        str.format("{0} bars, {1} scan passes ({2} per bar)", barsRun, scanPasses, scanPasses / barsRun),
        { style: label.style_label_down, color: color.new(color.gray, 20), textcolor: color.white }
    )
}

Turn on Preview on chart and read the label on the last bar. (A log.info line in the Console works too; the label keeps the number on the chart where you look.) On 10,000 bars with a look-back of 100, the label says about a million scan passes. That one number explains the cost, and it tells you what to change: look back less, scan only on the last bar, or keep the levels in an array as they appear instead of searching for them on every bar.

#Find the slow part: a method

  1. Reproduce it on a known chart. Use the same market, timeframe and inputs every time, so your comparisons are fair. Long histories make the problem bigger and easier to see: a lower timeframe gives more bars.
  2. Put each suspect behind a switch. Wrap each large part of the script in an input.bool, as Part A and Part B above. Then you can turn parts off from the settings dialog without editing code.
  3. Turn parts off one at a time and watch for the change: the CS5401 time drops below the limit, the bar-close-only note goes away after a reload, or the chart redraws noticeably faster after an input change. The part whose removal makes the difference is the one to work on.
  4. Count inside that part. Add counters as above to see whether the cost is loop passes, drawings or requests.
  5. Change one thing and count again. A rewrite that cuts the count by ten will cut the time by about ten.
  6. Scale the bars to check. Set calc_bars_count in indicator(...) to a smaller number (say 1,000) and then back. If the full run's cost scales with the number of bars, the expensive part runs on every bar; if it barely changes, it is in the last-bar work or the live updates.
  7. Remove the counters and switches you do not want members to see before you publish.

#Typical findings

You findUsually meansFix
Loop passes per bar in the hundreds or thousandsA scan back over history on every barUse a ta.* built-in, keep state in a state array, or scan only under if (barstate.islast).
The count grows with the bar numberA state array that is never trimmed, scanned on every barCap it: remove the oldest element when it is full.
Drawings created on many barsA new label or line per barCreate once in a state variable and move it; or use plotshape.
Cost drops a lot when one request is offA lower-timeframe request, or many contextsRequest a higher timeframe, return a tuple from one request, share pairs.
Full run fine, live updates slowHeavy work that runs on every price update of the newest barDo it only when the bar closes (if (barstate.isconfirmed)).

See Performance for these patterns with examples.

#See also

The ChartScript documentation is in English for now; translations will follow.