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 indexi(from0tobars.length - 1) to its respective X pixel coordinate on the canvas.ctx.priceToY(p): Converts a numeric price valuep(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 asctx.sma(closes, 20),ctx.rsi(closes, 14), orctx.macd(closes)) to calculate indicators dynamically. - Active Indicators Array: Inspect
ctx.indicatorsto read pre-calculated arrays for active indicators running on the chart. Every indicator object contains a populated.valuesproperty. - Accessing Volume: Retrieve raw bar volume using
ctx.bars[i].volume, or get the entire history array viactx.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)toMath.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
letorconstto 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.