Exciting Update: Version 1.0.1 is now available, introducing the high-performance BacktestX Custom Script Editor. Read more

Overview

Introduction

The Script Editor allows you to write custom indicator overlays directly in JavaScript that execute dynamically during the chart rendering loop. Indicators have direct access to standard 2D canvas drawing APIs, mouse interactions, indicator feeds, and viewport boundaries.

Execution Flow

Whenever the chart renders due to user panning, zooming, resizing, or incoming ticks, your custom indicator code is executed from start to finish. The script engine evaluates standard JavaScript immediately, passing a sandboxed proxy parameter named ctx.

Safety & Sandboxing boundaries

To prevent layouts and drawing corruption, script execution is wrapped inside a safe evaluation shell. Drawings are automatically clipped to the main price chart grid so they cannot bleed over the horizontal time scale or vertical price scale axes.

Coordinate Mapping

Drawing on a canvas requires pixel coordinates. The system provides two primary helper conversion methods on the ctx context:

  • ctx.barToX(i): Converts a bar index i (from 0 to bars.length - 1) to its respective X pixel coordinate on the canvas.
  • ctx.priceToY(p): Converts a numeric price value p (e.g. 1.2345) to its respective Y pixel coordinate on the canvas.

State Preservation

Because scripts execute repeatedly on every UI draw call, understanding scope is critical:

  • Persistent State (var): Declaring variables in the global block scope (which translates to outer declarations in the transpiler) preserves their values across render cycles. Use these to store rolling totals, win/loss stats, or active drawing references.
  • Loop State: Variables defined inside the main bar execution loop are re-initialized on each bar iteration and do not persist across bars or render cycles.

Exposing Technical Indicators & Volume

Your custom indicator scripts have access to standard technical indicator values in two distinct ways:

  • Built-in Math Helpers: Call standard math functions directly from ctx (such as ctx.sma(closes, 20), ctx.rsi(closes, 14), or ctx.macd(closes)) to calculate indicators dynamically.
  • Active Indicators Array: Inspect ctx.indicators to read pre-calculated arrays for active indicators running on the chart. Every indicator object contains a populated .values property.
  • Accessing Volume: Retrieve raw bar volume using ctx.bars[i].volume, or get the entire history array via ctx.vol(ctx.bars).

Rendering Optimization & 60 FPS Guidelines

To ensure high-performance scripting (60 FPS rendering without lag or stutter):

  • Clamp rendering loops: Restrict canvas rendering iterations from Math.max(0, Math.floor(bounds.startIndex) - 1) to Math.ceil(bounds.endIndex) + 1. This padding ensures indicators pan smoothly without abruptly disappearing. Avoid looping through the entire historical dataset.
  • Function Hoisting (CRITICAL): Never declare helper functions inside loops or main rendering callbacks. Hoist all user-defined functions to the global scope to prevent heavy memory allocation.
  • Local Scoping: Scope loop-local variables with block-level let or const to prevent global namespace lookups and scope leaks.
  • Map Callback Dereferencing: Avoid allocating new array references or parsing objects inside map callbacks. Dereference precalculated indicator arrays directly using index keys.
  • Execution Limits: Scripts taking longer than 12ms to execute will trigger a performance warning in the console.

Mouse Interactions

You can query the real-time mouse position and action states using the ctx.mouse object. This allows you to build interactive hover overlays, highlight target coordinates, or reveal tooltip stats under the cursor.