QuantScript forwardtest 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 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);
}
}
});