QuantScript — example-paper.qs
// SMA Crossover + RSI Filter — ETHUSD 1m Forward Test
//
// SMA(10)/SMA(20) crossover defines trend direction.
// RSI(14) filters entries to avoid overbought/oversold.
// Exits on trend flip. One position at a time (flat → entry → exit → entry).
// Hedging-safe: always closes opposing side before opening new direction.
//
// Forwardtest mode: forward-test with simulated broker. Same runtime as live,
// but no real orders are placed.

mode({ mode: "forwardtest", script_name: "SMA RSI ETHUSD-1m Forwardtest" });

candles({ symbol: "ETHUSD", resolution: "1m", bars: 40, adapter: "demotrader.net" });

broker({ adapter: "mybrokeraccount1" });

risk({ max_position_pct: 20, daily_loss_limit: 3 });

const fastLen  = input.int(10, "Fast SMA", 5, 50, null, null, null, "Trend");
const slowLen  = input.int(20, "Slow SMA", 10, 100, null, null, null, "Trend");
const rsiLen   = input.int(14, "RSI Length", 5, 50, null, null, null, "Filter");
const rsiOB    = input.float(65.0, "RSI Overbought", 50.0, 90.0, 1.0, null, null, "Filter");
const rsiOS    = input.float(35.0, "RSI Oversold", 10.0, 50.0, 1.0, null, null, "Filter");
const lotSize  = input.float(0.1, "Lot Size", 0.01, null, 0.01, null, null, "Trade");

event.onCandle((candle) => {
  if (candleIndex < Math.max(slowLen, rsiLen)) return;

  let close   = candle.close;
  let smaFast = ta.sma(close, fastLen);
  let smaSlow = ta.sma(close, slowLen);
  let rsiVal  = ta.rsi(close, rsiLen);
  let bullish = smaFast > smaSlow;
  let bearish = smaFast < smaSlow;

  chart.plot(smaFast, "Fast SMA", "#2196F3");
  chart.plot(smaSlow, "Slow SMA", "#FF9800");
  chart.pane("oscillator");
  chart.plot(rsiVal, "RSI", "#9C27B0");
  chart.hline(rsiOB, "Overbought", "#f85149");
  chart.hline(rsiOS, "Oversold", "#3fb950");

  // Close all positions matching a given side
  let closeBySide = (side) => {
    for (const pos of strategy.()) {
      if (pos.side == side)
        trade.closePosition({ positionId: pos.id });
    }
  };

  // ── Trade logic: only on last confirmed candle ──────────────────────
  if (candle.isLive) {
    let symbol = frame.symbol ?? "ETHUSD";

    // Position detection via introspection
    let hasLong = false, hasShort = false;
    let positions = strategy.();
    for (const pos of positions) {
      if (pos.side == "long")  hasLong  = true;
      if (pos.side == "short") hasShort = true;
    }

    console.log("[Trade] close=" + close + " rsi=" + rsiVal + " pos=" + positions.length + " L=" + hasLong + " S=" + hasShort);

    // Determine desired direction from trend + RSI filter
    let wantLong  = bullish && rsiVal < rsiOB;
    let wantShort = bearish && rsiVal > rsiOS;

    // ── Exit: close any position that conflicts with desired direction ──
    if (wantLong && hasShort) {
      closeBySide("short");
      console.log("  → EXIT short (trend flipped bullish)");
    }
    if (wantShort && hasLong) {
      closeBySide("long");
      console.log("  → EXIT long (trend flipped bearish)");
    }

    // ── Entry: only when flat on the desired side ─────────────────────
    if (wantLong && !hasLong) {
      trade.buy({
        symbol, qty: lotSize, comment: "Long",
        reason: "Bullish SMA crossover + RSI (" + rsiVal + ") not overbought",
        context: { rsi: rsiVal, close: close, direction: "long" },
        tags: ["trend", "sma_crossover"]
      });
      console.log("  → LONG ENTRY @ " + close);
    }
    if (wantShort && !hasShort) {
      trade.sell({
        symbol, qty: lotSize, comment: "Short",
        reason: "Bearish SMA crossover + RSI (" + rsiVal + ") not oversold",
        context: { rsi: rsiVal, close: close, direction: "short" },
        tags: ["trend", "sma_crossover"]
      });
      console.log("  → SHORT ENTRY @ " + close);
    }
  }
});

← Back to all examples