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

Examples & Presets

A library of 10 indicator presets and drawing scripts to copy directly into your custom editor.

1. Simple Moving Average (SMA 20)

Calculates a 20-period simple moving average and plots it dynamically on visible bars.

// ─── Simple Moving Average (SMA 20) Example ───
const period = 20;

if (!bars || bars.length < period) return;

ctx.beginPath();
ctx.strokeStyle = '#2962ff'; // Premium blue
ctx.lineWidth = 2.5;

let isFirst = true;
// Loop through visible bars to optimize drawing performance
for (let i = Math.max(0, Math.floor(bounds.startIndex) - 1); i <= Math.ceil(bounds.endIndex) + 1; i++) {
  if (i < period - 1) continue;
  
  // Calculate average
  let sum = 0;
  for (let j = 0; j < period; j++) {
    sum += bars[i - j].close;
  }
  const sma = sum / period;

  const x = ctx.barToX(i);
  const y = ctx.priceToY(sma);

  if (isFirst) {
    ctx.moveTo(x, y);
    isFirst = false;
  } else {
    ctx.lineTo(x, y);
  }
}
ctx.stroke();

2. Support/Resistance Band Fill

Draws a custom horizontal channel fill using dashed boundary levels.

// ─── Horizontal Support / Resistance Fill Zone ───

const resistancePrice = 120.00;
const supportPrice = 110.00;

const yRes = ctx.priceToY(resistancePrice);
const ySup = ctx.priceToY(supportPrice);

// Fill area with premium semi-transparent teal color
ctx.fillStyle = 'rgba(8, 153, 129, 0.08)';
ctx.fillRect(0, yRes, bounds.chartW, ySup - yRes);

// Draw boundaries
ctx.strokeStyle = 'rgba(8, 153, 129, 0.4)';
ctx.lineWidth = 1;
ctx.setLineDash([6, 4]); // Dotted boundaries

ctx.beginPath();
ctx.moveTo(0, yRes); ctx.lineTo(bounds.chartW, yRes);
ctx.moveTo(0, ySup); ctx.lineTo(bounds.chartW, ySup);
ctx.stroke();

ctx.setLineDash([]); // Reset line dash

3. Engulfing Signal Dots & Labels

Scans visible history to detect engulfing patterns, then draws green dots and labels below bars.

// ─── custom Buy/Sell Candle Signal Labels ───

for (let i = Math.max(0, Math.floor(bounds.startIndex) - 1); i <= Math.ceil(bounds.endIndex) + 1; i++) {
  if (i === 0) continue;
  const bar = bars[i];
  const prevBar = bars[i - 1];

  // Logic: Big Bullish Candle after Bearish Candle
  const isEngulfing = bar.close > bar.open && prevBar.close < prevBar.open && (bar.close - bar.open) > (prevBar.open - prevBar.close);

  if (isEngulfing) {
    const x = ctx.barToX(i);
    const y = ctx.priceToY(bar.low) + 16; // 16 pixels below candle low

    // Draw solid green label dot
    ctx.fillStyle = '#089981';
    ctx.beginPath();
    ctx.arc(x, y, 4, 0, 2 * Math.PI);
    ctx.fill();

    // Text label
    ctx.font = 'bold 9px Inter, sans-serif';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillStyle = '#089981';
    ctx.fillText('BULLISH', x, y + 12);
  }
}

4. Custom Curved Ribbon with Shadows

Demonstrates drop shadows, bezier curves, lineCap and lineJoin formatting.

// ─── Custom Curved Ribbon with Drop Shadows & Caps ───

if (!bars || bars.length < 5) return;

ctx.beginPath();
ctx.strokeStyle = '#ff9800'; // Amber Orange
ctx.lineWidth = 4;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';

// Configure drop shadow
ctx.shadowColor = 'rgba(255, 152, 0, 0.45)';
ctx.shadowBlur = 8;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 4;

const startX = ctx.barToX(bounds.startIndex);
const startY = ctx.priceToY(bars[bounds.startIndex].close);

const cp1x = bounds.chartW / 3;
const cp1y = ctx.priceToY(bars[Math.floor(bounds.startIndex + bounds.numVisible / 3)].close);

const cp2x = (2 * bounds.chartW) / 3;
const cp2y = ctx.priceToY(bars[Math.floor(bounds.startIndex + 2 * bounds.numVisible / 3)].close);

const endX = ctx.barToX(bounds.endIndex);
const endY = ctx.priceToY(bars[bounds.endIndex].close);

ctx.moveTo(startX, startY);
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, endX, endY);
ctx.stroke();

// IMPORTANT: Reset shadow parameters so they don't apply to subsequent indicators
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;

5. Drawing & Indicator Inspector HUD

Inspects active standard indicators and custom drawing metadata and draws an overlay panel.

// ─── Drawing Tool & Indicator Inspector Example ───
const drawings = ctx.drawings;
const indicators = ctx.indicators;
const scripts = ctx.customScripts;

ctx.save();
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
ctx.fillRect(10, 10, 260, 95);
ctx.strokeStyle = '#2962ff';
ctx.lineWidth = 1.5;
ctx.strokeRect(10, 10, 260, 95);

ctx.fillStyle = '#ffffff';
ctx.font = '11px Inter, sans-serif';
ctx.textAlign = 'left';
ctx.fillText(`Active Drawings: ${drawings.length}`, 22, 28);
ctx.fillText(`Active Indicators: ${indicators.length}`, 22, 46);
ctx.fillText(`Custom Scripts: ${scripts.length}`, 22, 64);

if (drawings.length > 0) {
  const d = drawings[0];
  ctx.fillStyle = '#ffd54f';
  ctx.fillText(`First Drawing: ${d.type} (Points: ${d.points.length})`, 22, 84);
} else {
  ctx.fillStyle = '#ef5350';
  ctx.fillText('No drawings on chart. Try adding one!', 22, 84);
}
ctx.restore();

6. Combined EMA & RSI Signal Indicator

Combines a 20 EMA plot with RSI oversold (<30) / overbought (>70) dots on the chart canvas.

// ─── Combined EMA & RSI Signal Indicator ───
const closes = ctx.bars.map(b => b.close);

if (!closes || closes.length < 20) return;

// Calculate EMA and RSI using built-in math APIs
const ema20 = ctx.ema(closes, 20);
const rsi14 = ctx.rsi(closes, 14);

// Draw EMA 20 line
ctx.beginPath();
ctx.strokeStyle = '#9c27b0'; // Purple EMA line
ctx.lineWidth = 2.0;

let first = true;
for (let i = Math.max(0, Math.floor(bounds.startIndex) - 1); i <= Math.ceil(bounds.endIndex) + 1; i++) {
  if (ema20[i] == null || isNaN(ema20[i])) continue;
  const x = ctx.barToX(i);
  const y = ctx.priceToY(ema20[i]);
  
  if (first) {
    ctx.moveTo(x, y);
    first = false;
  } else {
    ctx.lineTo(x, y);
  }
}
ctx.stroke();

// Scan visible area to plot RSI Overbought (>70) and Oversold (<30) dots
for (let i = Math.max(0, Math.floor(bounds.startIndex) - 1); i <= Math.ceil(bounds.endIndex) + 1; i++) {
  if (rsi14[i] == null || isNaN(rsi14[i])) continue;

  const x = ctx.barToX(i);
  if (rsi14[i] < 30) {
    // Oversold Buy dot beneath candle low
    const y = ctx.priceToY(ctx.bars[i].low) + 12;
    ctx.fillStyle = '#089981'; // Green dot
    ctx.beginPath();
    ctx.arc(x, y, 4.5, 0, 2 * Math.PI);
    ctx.fill();
  } else if (rsi14[i] > 70) {
    // Overbought Sell dot above candle high
    const y = ctx.priceToY(ctx.bars[i].high) - 12;
    ctx.fillStyle = '#f23645'; // Red dot
    ctx.beginPath();
    ctx.arc(x, y, 4.5, 0, 2 * Math.PI);
    ctx.fill();
  }
}

7. Declarative Plotting & Alerts

Demonstrates ctx.plot(), ctx.plotchar(), and programmatic toast alerts.

// ─── Pine Script-style Declarative Plotting & Alerts ───
const closes = ctx.bars.map(b => b.close);

// 1. Calculate SMA 20 using built-in math helper
const smaVal = ctx.sma(closes, 20);

// 2. Draw SMA line on the chart with one call
ctx.plot(smaVal, { color: '#ff00ff', width: 2, style: 'dashed' });

// 3. Draw Buy / Sell markers using plotchar
const isGreenCandle = ctx.bars.map(b => b.close > b.open);
const isRedCandle = ctx.bars.map(b => b.close < b.open);

// Triangles below green bars, circles above red bars
ctx.plotchar(isGreenCandle, { char: '▲', location: 'belowbar', color: '#00e676', size: 14 });
ctx.plotchar(isRedCandle, { char: '▼', location: 'abovebar', color: '#ff1744', size: 14 });

// 4. Programmatic Alerts
const len = ctx.bars.length;
if (len > 1) {
  const lastBar = ctx.bars[len - 1];
  const lastSma = smaVal[len - 1];
  const prevBar = ctx.bars[len - 2];
  const prevSma = smaVal[len - 2];

  if (lastSma !== null && prevSma !== null) {
    const crossAbove = lastBar.close > lastSma && prevBar.close <= prevSma;
    ctx.alert("Price Crossed Above SMA!", crossAbove);
  }
}

8. Stats Dashboard Table

Creates and aligns a Pine Script-style summary table detailing analytics in the viewport corner.

// ─── Pine Script-style Stats Dashboard Table Example ───
// 1. Initialize table with 2 columns, 4 rows in the bottom-right corner
const myTable = ctx.table.new(ctx.position.bottom_right, 2, 4, 'rgba(26, 26, 46, 0.85)', 'rgba(57, 62, 70, 0.4)', 1);

// 2. Set headers
myTable.cell(0, 0, 'Metric', { bgcolor: 'rgba(34, 40, 49, 0.95)', color: '#00adb5', size: ctx.size.normal });
myTable.cell(1, 0, 'Performance', { bgcolor: 'rgba(34, 40, 49, 0.95)', color: '#00adb5', size: ctx.size.normal });

// 3. Simple performance scan (count total and green candles)
let totalBars = ctx.bars.length;
let greenCount = 0;
for (let i = 0; i < totalBars; i++) {
  if (ctx.bars[i].close > ctx.bars[i].open) {
    greenCount++;
  }
}
let winRate = totalBars > 0 ? ((greenCount / totalBars) * 100).toFixed(1) + '%' : '0.0%';

// 4. Fill values in cells
myTable.cell(0, 1, 'Total Trades', { color: '#eeeeee', size: ctx.size.small });
myTable.cell(1, 1, String(totalBars), { color: '#eeeeee', size: ctx.size.small });

myTable.cell(0, 2, 'Profitable Trades', { color: '#00e676', size: ctx.size.small });
myTable.cell(1, 2, String(greenCount), { color: '#00e676', size: ctx.size.small });

myTable.cell(0, 3, 'Win Rate (Ratio)', { bgcolor: 'rgba(57, 62, 70, 0.6)', color: '#00adb5', size: ctx.size.normal });
myTable.cell(1, 3, winRate, { bgcolor: 'rgba(57, 62, 70, 0.6)', color: '#00adb5', size: ctx.size.normal });

9. Supply & Demand Zones (box.new)

Identifies local swing highs and lows, drawing filled support and resistance range rectangles.

// ─── Supply & Demand Zones using box.new Example ───

if (bars.length < 10) return;

// Identify local highest high and lowest low on visible bars
let highest = bars[bounds.startIndex].high;
let lowest = bars[bounds.startIndex].low;
for (let i = Math.max(0, Math.floor(bounds.startIndex) - 1); i <= Math.ceil(bounds.endIndex) + 1; i++) {
  if (bars[i].high > highest) highest = bars[i].high;
  if (bars[i].low < lowest) lowest = bars[i].low;
}

// Clear previous boxes before drawing new ones for dynamic zoning
_active_boxes.length = 0;

// Resistance/Supply Zone (Top 10% of range, semi-transparent Red fill)
box.new(
  bounds.startIndex, 
  highest, 
  bounds.endIndex, 
  highest - (highest - lowest) * 0.1, 
  color.new(color.red, 88), // 88% transparent background fill
  color.red,               // Border color
  1                        // Border width
);

// Support/Demand Zone (Bottom 10% of range, semi-transparent Green fill)
box.new(
  bounds.startIndex, 
  lowest + (highest - lowest) * 0.1, 
  bounds.endIndex, 
  lowest, 
  color.new(color.green, 88), // 88% transparent background fill
  color.green,               // Border color
  1                          // Border width
);

10. Advanced Drawings (hline, linefill, fib, plotcandle)

Demonstrates complex combinations: horizontal lines, line fills, Fibonacci levels, and price overrides.

// ─── Advanced Drawings (hline, linefill, fib, plotcandle) Example ───

if (bars.length < 10) return;

// 1. Draw horizontal boundaries
ctx.hline(120.00, { color: '#ff1744', width: 1.5, style: 'dashed' });
ctx.hline(80.00, { color: '#00e676', width: 1.5, style: 'dashed' });

// 2. Draw Fib levels dynamically on active swing
const startIdx = Math.max(0, Math.floor(bounds.startIndex));
const endIdx = Math.min(Math.ceil(bounds.endIndex), bars.length - 1);
const highest = bars[startIdx].high;
const lowest = bars[startIdx].low;
fib.new(startIdx, lowest, endIdx, highest, 'rgba(156, 39, 176, 0.4)');

// 3. Draw a custom Channel fill using linefill
const line1 = line.new(startIdx, highest, endIdx, highest * 0.95, '#2196f3', 'solid', 1.5);
const line2 = line.new(startIdx, lowest, endIdx, lowest * 1.05, '#2196f3', 'solid', 1.5);
linefill.new(line1, line2, 'rgba(33, 150, 243, 0.08)');

// 4. Overwrite candles using plotcandle
const h = bars.map(b => b.high);
const l = bars.map(b => b.low);
const c = bars.map(b => b.close);
ctx.plotcandle(o, h, l, c, { color: '#ffeb3b', wickcolor: '#ffc107' });