QuantScript — example-paper.qs
// SMA Crossover + RSI Filter — ETHUSD 1m Paper Trading
//
// 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.
//
// Paper mode: forward-test with simulated broker. Same runtime as live,
// but no real orders are placed.

mode("paper", "SMA RSI ETHUSD-1m Paper");

candles("ETHUSD", timeframe: "1m", bars: 40, provider: "demotrader");

broker("mybrokeraccount1");

risk(max_position_pct: 20, daily_loss_limit: 3);

const fastLen  = input.int(10, "Fast SMA", minval: 5, maxval: 50, group: "Trend");
const slowLen  = input.int(20, "Slow SMA", minval: 10, maxval: 100, group: "Trend");
const rsiLen   = input.int(14, "RSI Length", minval: 5, maxval: 50, group: "Filter");
const rsiOB    = input.float(65.0, "RSI Overbought", minval: 50.0, maxval: 90.0, step: 1.0, group: "Filter");
const rsiOS    = input.float(35.0, "RSI Oversold", minval: 10.0, maxval: 50.0, step: 1.0, group: "Filter");
const lotSize  = input.float(0.1, "Lot Size", minval: 0.01, step: 0.01, group: "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 trade.positions()) {
      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 = trade.positions();
    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