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

History & State

History Operator

Because BacktestX Script is fully vectorized, you do not use the [n] brackets to fetch historical bar values. Instead, use the built-in ta.offset() method.

ta.offset(sourceArray, length)

Returns a new array shifted backward by length bars.

Stateful Condition Tracking

Instead of manually using for loops to track when conditions were met in the past, use these built-in stateful helpers:

ta.valuewhen

ta.valuewhen(condArray, sourceArray, occurrence)

Returns the value of sourceArray on the exact bar where condArray was true for the occurrenceth time (0 = most recent, 1 = previous).

ta.barssince

ta.barssince(condArray)

Returns the number of bars that have elapsed since the last time condArray was true.

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.