QuantScript paper trading example: SMA Crossover + RSI Filter for ETHUSD 1-minute. Forward-test with simulated broker — same runtime as live, but no real orders.
// 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);
}
}
});