Exciting Update: Version 1.0.0 is now available, introducing high-performance technical indicators and custom drawing tools. Read more
Version: 1.0.0

Custom Horizontal Scale

Override the time-based horizontal scale to plot arbitrary option strike prices, interest rate maturities, or yield curves.

Introduction

By default, financial charts display a chronological time scale along the horizontal axis. However, certain derivatives models (like Options charts plotting Strike Prices) or fixed-income products (like Yield Curve charts plotting maturities) require a non-linear, non-time scale.

The Backtestx charting library provides standard support to replace the time scale with arbitrary values using the registerHorizontalScale interface.

API Overview

To register a custom horizontal scale behavior, you must supply a helper object implementing the following coordinate mappings:

  • barToX(chart, index): Translates a bar index into a horizontal pixel coordinate on the canvas.
  • xToBar(chart, x): Translates a horizontal pixel coordinate on the canvas back into a bar index.
  • getVisibleRange(chart): Returns the indices of the first and last bars currently visible in the viewport.
  • drawTimeScale(chart, ctx, chartH): Main drawing method responsible for rendering gridlines and text labels on the horizontal scale.
  • drawTimeBadge(chart, ctx, chartH): Renders the text tooltip badge on the horizontal scale under the active crosshair.

Yield Curve Scale Implementation

Below is the actual implementation of the Yield Curve maturity scale from the Backtestx library:

window.YieldScale = {
  barToX: function(chart, i) {
    const slot = chart.candleWidth + chart.candleGap;
    return chart.offset + i * slot + chart.candleWidth / 2;
  },
  xToBar: function(chart, x) {
    const slot = chart.candleWidth + chart.candleGap;
    return Math.round((x - chart.offset - chart.candleWidth / 2) / slot);
  },
  getVisibleRange: function(chart) {
    const chartW = chart.logicalWidth - chart.paddingRight;
    const slot = chart.candleWidth + chart.candleGap;
    const startIndex = Math.floor((-slot - chart.offset - chart.candleWidth / 2) / slot);
    const endIndex = Math.ceil((chartW + slot - chart.offset - chart.candleWidth / 2) / slot);
    return { startIndex, endIndex };
  },
  getBarLabel: function(chart, i) {
    const bars = chart.bars;
    if (!bars || bars.length === 0) return '';
    if (i >= 0 && i < bars.length) {
      return bars[i].maturity || bars[i].x || String(i);
    }
    return '';
  },
  drawTimeScale: function(chart, ctx, chartH) {
    ctx.save();
    const { startIndex, endIndex } = window.YieldScale.getVisibleRange(chart);
    const bars = chart.bars;
    if (!bars || bars.length === 0) {
      ctx.restore();
      return;
    }
    const isLight = document.body.classList.contains('light-theme');
    const visibleCount = endIndex - startIndex + 1;
    const labelInterval = Math.max(1, Math.floor(visibleCount / 8));

    for (let i = startIndex; i <= endIndex; i++) {
      if (i >= 0 && i < bars.length) {
        if (i % labelInterval === 0) {
          const x = chart.barToX(i);
          const label = window.YieldScale.getBarLabel(chart, i);

          // Gridline
          ctx.strokeStyle = isLight ? 'rgba(0, 0, 0, 0.08)' : 'rgba(42, 46, 57, 0.4)';
          ctx.lineWidth = 0.5;
          ctx.setLineDash([4, 4]);
          ctx.beginPath();
          ctx.moveTo(x, 0);
          ctx.lineTo(x, chartH);
          ctx.stroke();

          // Label
          ctx.setLineDash([]);
          ctx.font = 'bold 10px Inter, Arial, sans-serif';
          ctx.fillStyle = isLight ? '#707584' : '#9b9ba3';
          ctx.textAlign = 'center';
          ctx.fillText(label, x, chartH + 16);
        }
      }
    }
    ctx.restore();
  }
};

Registering Scale

Once you define your custom scale layout, register it with the global ChartingAPI using:

if (window.ChartingAPI) {
  window.ChartingAPI.registerHorizontalScale('yield-curve', window.YieldScale);
}

Common Use Cases

This pattern is highly effective for:

  • Options Volatility Smiles: Plotting Implied Volatility against option Strike Prices.
  • Treasury Curves: Displaying yields across fixed maturities (1M, 3M, 6M, 1Y, 2Y, 5Y, 10Y, 30Y).
  • Rank-based Charts: Sorting assets along the X-axis based on arbitrary metrics (e.g. market cap or daily performance).