QuantScript — example-backtest.qs
// ─────────────────────────────────────────────────────────────────────────────
// SMA Trend Pullback Strategy — XAUUSD 1H (Backtest Mode)
// ─────────────────────────────────────────────────────────────────────────────
//
// Backtests a trend-following strategy on XAUUSD 1-hour candles from
// June 1, 2026 to July 1, 2026 using the DemoTrader data provider.
//
// Strategy logic:
//   1. 50-period SMA defines the trend direction.
//   2. RSI(14) identifies pullback entries within the trend.
//   3. Long when price is above SMA and RSI crosses above 50 (bullish midline cross).
//   4. Short when price is below SMA and RSI crosses below 50 (bearish midline cross).
//   5. ATR-based stop loss and take profit for risk management.
//
// ─────────────────────────────────────────────────────────────────────────────

mode("backtest", "XAUUSD SMA Pullback");

data("XAUUSD", timeframe: "1h", bars: 720, from: 1780272000, to: 1782864000, provider: "demotrader");

broker("sim", {
  initial_capital: 100000,
  commission: 0.05,
  commission_type: "percent"
});

broker("mybrokeraccount1");

risk(max_position_pct: 25, daily_loss_limit: 3);

// ── Indicator Parameters ───────────────────────────────────────────────────
const smaLen    = input.int(50, "SMA Length", minval: 10, maxval: 200, group: "Trend");
const rsiLen    = input.int(14, "RSI Length", minval: 5, maxval: 50, group: "Entry");
const atrLen    = input.int(14, "ATR Length", minval: 5, maxval: 50, group: "Risk");
const rsiBullZone  = input.float(50.0, "RSI Bullish Cross Level", group: "Entry");
const rsiBearZone  = input.float(50.0, "RSI Bearish Cross Level", group: "Entry");
const slMult    = input.float(1.5, "Stop Loss × ATR", minval: 0.5, maxval: 5.0, step: 0.1, group: "Risk");
const tpMult    = input.float(2.5, "Take Profit × ATR", minval: 1.0, maxval: 10.0, step: 0.5, group: "Risk");

// ── State ──────────────────────────────────────────────────────────────────
var prevRsi = 50.0;

event.onCandle((candle) => {
  // ── Warmup ────────────────────────────────────────────────────────────
  if (candleIndex < Math.max(smaLen, rsiLen, atrLen)) { return; }

  let close = candle.close;
  let high  = candle.high;
  let low   = candle.low;

  // ── Indicators ────────────────────────────────────────────────────────
  let smaVal = ta.sma(close, smaLen);
  let rsiVal = ta.rsi(close, rsiLen);
  let atrVal = ta.atr(high, low, close, atrLen);

  // ── Debug: log indicator values every 50 bars ─────────────────────────
  if (candleIndex % 50 == 0) {
    console.log("[bar " + candleIndex + "] close=" + close + " sma=" + smaVal + " rsi=" + rsiVal + " prevRsi=" + prevRsi + " atr=" + atrVal);
  }

  // ── Chart Plots ───────────────────────────────────────────────────────
  chart.plot(smaVal, "SMA(" + smaLen + ")", "#FF9800");

  // ── Entry Signals ─────────────────────────────────────────────────────
  let bullTrend  = close > smaVal;
  let rsiRecover = rsiVal > rsiBullZone && prevRsi <= rsiBullZone;

  let bearTrend  = close < smaVal;
  let rsiDrop    = rsiVal < rsiBearZone && prevRsi >= rsiBearZone;

  // ── Long Entry ────────────────────────────────────────────────────────
  if (bullTrend && rsiRecover && trade.positions().length < 2) {
    let sl = close - atrVal * slMult;
    let tp = close + atrVal * tpMult;
    trade.buy({
      symbol: "XAUUSD", qty: 1, comment: "Long",
      reason: "SMA trend long: RSI crossed above " + rsiBullZone,
      context: { close: close, rsi: rsiVal, sma: smaVal },
      tags: ["trend", "long_entry"]
    });
    for (const pos of trade.positions()) {
      if (pos.comment == "Long" && pos.isOpen) {
        trade.modifyPosition({ positionId: pos.id, stopLoss: sl, takeProfit: tp });
      }
    }
  }

  // ── Short Entry ───────────────────────────────────────────────────────
  if (bearTrend && rsiDrop && trade.positions().length < 2) {
    let sl = close + atrVal * slMult;
    let tp = close - atrVal * tpMult;
    trade.sell({
      symbol: "XAUUSD", qty: 1, comment: "Short",
      reason: "SMA trend short: RSI crossed below " + rsiBearZone,
      context: { close: close, rsi: rsiVal, sma: smaVal },
      tags: ["trend", "short_entry"]
    });
    for (const pos of trade.positions()) {
      if (pos.comment == "Short" && pos.isOpen) {
        trade.modifyPosition({ positionId: pos.id, stopLoss: sl, takeProfit: tp });
      }
    }
  }

  // ── Exit on SMA cross ─────────────────────────────────────────────────
  if (close < smaVal) {
    for (const pos of trade.positions()) {
      if (pos.comment == "Long" && pos.isOpen) {
        trade.closePosition({ positionId: pos.id });
      }
    }
  }

  if (close > smaVal) {
    for (const pos of trade.positions()) {
      if (pos.comment == "Short" && pos.isOpen) {
        trade.closePosition({ positionId: pos.id });
      }
    }
  }

  prevRsi = rsiVal;
});

← Back to all examples