Getting Started#

QuantScript is a domain-specific language for creating, testing, and deploying automated trading strategies. You can start writing and running QuantScript code directly in your browser at quantscript.com/playground — no installation required.

Execution Channels & Modes#

QuantScript code can be executed in two channels, each suited to a different stage of your strategy's lifecycle. Every channel supports two execution semantics — Run1 and Run — and each mode has specific rules about where it can run.

The Two Channels

ChannelDescriptionWhen to use
PlaygroundBrowser-based IDE at quantscript.com/playground with live chart renderingDevelopment, debugging, visual inspection, backtesting
Cloud DeploymentCloud deployment via the QuantScript platformContinuous forwardtest/live trading 24/7

Run1 vs Run

Run1Run
BehaviourExecute the script once, then exitLoop: execute, wait for next candle close, execute again
LifecycleParse → execute → output → exitParse → execute → poll/wait → execute → poll/wait → ...
Use caseBacktests, indicator calculations, one-shot tradesContinuous forwardtest or live trading
Playground✅ "Run" button✅ "Run (Live Loop)" button
Cloud❌ Not applicable✅ Continuous deployment

Modes and Where They Are Valid

QuantScript has four execution modes, declared with mode():

ModeDeclarationRun1RunCloud
Indicatormode("indicator") or omitted
Backtestmode("backtest")
Forwardtestmode("forwardtest")
Livemode("live")✅ (auto-downgrades to forwardtest)✅ (connects to real broker)✅ (real broker)

Cloud Deployment

Cloud deployment is available via the QuantScript platform. Each deployed strategy runs in its own isolated environment with persistent storage.

  • Only mode("forwardtest") and mode("live") are accepted for cloud deployment.
  • mode("indicator") and mode("backtest") are rejected at deploy time.
  • Each deployment wakes on its own resolution_type schedule.
  • State persists across executions via runtime snapshots.

Hello World#

Create a file called hello.qs in the Playground:

QuantScript
console.log("Hello, World!");

Click "Run" to execute. Output:

text
── QuantScript Indicator ──
Hello, World!
Execution complete.

Running Your First Script#

Script Structure

QuantScript
// 1. Mode declaration (optional — defaults to indicator mode)
mode(mode_type: "indicator", script_name: "My Script Name");

// 2. Global code (executed once)
let myVariable = 10;

// 3. DataFrame iteration (executed per bar)
event.onCandle((candle) => {
    // Process each bar
});

Variables and Math

QuantScript
mode(mode_type: "indicator", script_name: "Math Example");

let x = 10;
const PI = 3.14159;

let sum = x + 5;
let product = x * 2;
let power = 2 ** 8;

console.log("Sum: " + sum);
console.log("Product: " + product);
console.log("Power: " + power);

JavaScript Standard Library#

QuantScript includes the standard JavaScript utility namespaces and instance methods with full ES6 parity. If you know JavaScript, you already know these:

  • Math.*Math.abs(), Math.max(), Math.sqrt(), Math.round(), etc.
  • JSON.*JSON.parse(text), JSON.stringify(value)
  • Date.*Date.now(), Date.parse(str), Date.UTC(y, m, d)
  • String methods"hello".toUpperCase(), .includes(), .split(), .replace(), etc.
  • Array methodsarr.push(), arr.map(fn), arr.filter(fn), arr.reduce(fn, init), etc.

See the API Reference for the full list, or consult any JavaScript reference (e.g. MDN) — the behaviour is identical.

Working with OHLCV Data#

Accessing Bar Data

QuantScript
event.onCandle((candle) => {
    let openPrice = candle.open;
    let highPrice = candle.high;
    let lowPrice = candle.low;
    let closePrice = candle.close;
    let volume = candle.volume;
});

Derived Bar Variables

QuantScript
event.onCandle((candle) => {
    let midpoint = candle.hl2;     // (high + low) / 2
    let avgThree = candle.hlc3;    // (high + low + close) / 3
    let avgFour  = candle.ohlc4;   // (open + high + low + close) / 4
});

Historical Data Access

QuantScript
let currentClose = candle.close;
let previousClose = close[1];  // 1 bar ago
let twoBarsAgo = close[2];     // 2 bars ago

Building a Simple Indicator#

Simple Moving Average

QuantScript
mode(mode_type: "indicator", script_name: "SMA Indicator");

const SMA_LENGTH = 20;

event.onCandle((candle) => {
    let sma = ta.sma(candle.close, SMA_LENGTH);
    chart.plot(sma, "SMA", "#FF0000");
});

Multiple Indicators

QuantScript
event.onCandle((candle) => {
    let fastSMA = ta.sma(candle.close, 10);
    let slowSMA = ta.sma(candle.close, 20);
    let ema = ta.ema(candle.close, 20);

    chart.plot(fastSMA, "Fast SMA", "#00FF00");
    chart.plot(slowSMA, "Slow SMA", "#FF0000");
    chart.plot(ema, "EMA", "#0000FF");

    if (ta.crossover(fastSMA, slowSMA)) {
        console.log("BULLISH CROSSOVER");
    }
});

Creating a Trading Strategy#

SMA Crossover Strategy

QuantScript
candles(symbol: "BTCUSD", resolution_type: "1h", bars: 500);
mode(mode_type: "backtest", script_name: "SMA Crossover");
broker(adapter: "sim", config: { initial_capital: 100000 });

event.onCandle((candle) => {
    let fastSMA = ta.sma(candle.close, 10);
    let slowSMA = ta.sma(candle.close, 20);

    if (ta.crossover(fastSMA, slowSMA)) {
        trade.buy({ symbol: "BTCUSD", qty: 1 });
    }
    if (ta.crossunder(fastSMA, slowSMA)) {
        trade.closePosition({});
    }
});

Strategy with Exit Orders

QuantScript
candles(symbol: "BTCUSD", resolution_type: "1h", bars: 500);
mode(mode_type: "backtest", script_name: "Strategy with Exits");
broker(adapter: "sim", config: { initial_capital: 100000 });

event.onCandle((candle) => {
    let sma = ta.sma(candle.close, 20);
    let crossUp = ta.crossover(candle.close, sma);

    if (crossUp && strategy.position_size == 0) {
        trade.buy({ symbol: "BTCUSD", qty: 1, stopLoss: 2.0, takeProfit: 4.0 });
    }
});

Trade Sizing

Instead of specifying a raw qty, you can use the size object for more intuitive position sizing across different asset classes:

QuantScript
// Trade 0.5 lots of EURUSD
trade.buy({ symbol: "EURUSD", size: { lots: 0.5 } });

// Crypto: spend exactly $500
trade.buy({ symbol: "BTCUSDT", size: { cash: 500 } });

// Risk 2% of account equity
trade.buy({ symbol: "AAPL", size: { equity_percent: 2 } });

Available sizing modes: units (direct quantity), lots (standardized lot units), cash (quote-currency amount), equity_percent (% of equity). Exactly one must be set. See the API Reference for details.

Multi-Timeframe Analysis#

QuantScript supports analyzing multiple symbols and resolutions in a single script using data.loadCandles() with a callback. This evaluates indicators on a secondary resolution's native bars, then aligns results back to your primary chart — like Pine Script's request.security().

Basic Example

QuantScript
mode(mode_type: "indicator", script_name: "Multi-TF");
candles(symbol: "BTCUSD", resolution_type: "1h", bars: 300, adapter: "demotrader.net");

// ETH 4h SMA computed on native 4h bars
var ethSma = data.loadCandles(adapter: "demotrader.net", symbol: "ETHUSD", resolution_type: "4h", callback: (c) => ta.sma(c.close, 50));

event.onCandle((candle) => {
    chart.plot(candle.close, "BTC Close");
    chart.plot(ethSma, "ETH 4h SMA", "#FF9800");
});

Cross-Timeframe Strategy

Use a higher timeframe for trend filtering and a lower timeframe for entry timing:

QuantScript
mode(mode_type: "forwardtest", script_name: "Cross-TF Strategy");
candles(symbol: "ETHUSD", resolution_type: "15m", bars: 200, adapter: "demotrader.net");
broker(adapter: "sim", config: { initial_capital: 10000 });

// Daily SMA as trend filter
var dailySma = data.loadCandles(adapter: "demotrader.net", symbol: "ETHUSD", resolution_type: "1D", callback: (c) => ta.sma(c.close, 50));

event.onCandle((candle) => {
    const rsi = ta.rsi(candle.close, 14);
    if (dailySma !== null && candle.close > dailySma && rsi !== null && rsi < 35) {
        trade.buy({ symbol: "ETHUSD", qty: 0.1 });
    }
});

Results support [n] historical lookback and are forward-filled to align with your primary chart.

Pine Script to QuantScript#

Key Translation Notes

  1. Frame iteration: Pine iterates bar-by-bar automatically. QuantScript requires explicit event.onCandle((candle) => { ... }).
  2. Variable access: Pine uses close directly. QuantScript uses candle.close inside the event handler.
  3. Historical references: Both use close[1] syntax.
  4. Colors: QuantScript uses hex strings: "#0000FF".
  5. Function namespaces: Both use ta. for technical analysis.
  6. Semicolons: Required, just like JavaScript.

For more on the design philosophy, see the About QuantScript page.

Virtual Trading Accounts#

When running multiple strategies on a single broker account, each strategy needs its own isolated view of capital. Virtual Trading Accounts solve this by giving each strategy an independent balance, equity, and margin — even though they all share the same real broker account.

How It Works

Set allocated_capital in your broker() config to opt in:

QuantScript
broker(adapter: "demotrader.net", email: "...", password: "...", config: {
  allocated_capital: 10000
});

This creates a VirtualAccount for the strategy, seeded with $10,000. From there:

  • Virtual Balance tracks closed PnL and fees from the strategy’s own fills
  • Virtual Equity adds open PnL from the strategy’s open positions
  • Virtual Margin tracks margin consumed by the strategy’s positions

Risk Guard Integration

When a virtual account is active, all risk() parameters are enforced against the virtual account — not the broker-wide account:

  • min_balance and min_equity check against virtual values
  • max_position_pct and max_risk_pct_per_trade compute percentages from virtual equity
  • max_margin_pct uses virtual margin and balance

This means one strategy hitting its risk limit does not block the other strategies sharing the same broker account.

Multi-Strategy Example

Two strategies sharing one $50,000 account, each with independent capital and risk limits:

QuantScript
// Strategy A: $30,000 allocated for BTC momentum
candles(symbol: "BTCUSD", resolution_type: "1h", bars: 200);
mode(mode_type: "live", script_name: "Momentum");
broker(adapter: "demotrader.net", email: "...", password: "...", config: {
  allocated_capital: 30000
});
risk(min_equity: 25000, max_position_pct: 20);
QuantScript
// Strategy B: $20,000 allocated for ETH mean reversion
candles(symbol: "ETHUSD", resolution_type: "15m", bars: 200);
mode(mode_type: "live", script_name: "Mean Reversion");
broker(adapter: "demotrader.net", email: "...", password: "...", config: {
  allocated_capital: 20000
});
risk(min_equity: 17000, max_position_pct: 15);

Each strategy’s performance (equity curve, drawdown, ROI) is measured against its own allocated_capital, giving you accurate per-strategy metrics.

State Recovery

Virtual accounts are automatically rebuilt from your strategy’s historical fills and open positions on cold start (e.g., after a server restart). This ensures balance and equity tracking continues seamlessly without manual intervention.

Forming Candle & Live Price#

When your strategy runs in forwardtest or live mode, the runtime keeps an incomplete (forming) candle that updates with every incoming tick. This gives you access to the freshest market price between confirmed bar closes.

Confirmed vs. Forming Candle

Confirmed candle (candle.close)Forming candle (trade.last())
MutabilityImmutable — never changes after bar closeUpdates with every tick until the bar closes
Use forIndicators, signal logic, trend detectionEntry checks, SL/TP placement, position sizing
Repainting riskNoneValue changes intra-bar (by design)

Rule of thumb: Compute your signals from confirmed data, then execute using the live price.

Available Functions

  • trade.last(symbol?) — Freshest last-traded price (forming bar close, or latest quote)
  • trade.formingCandle(symbol?) — Full forming candle object (open, high, low, close, volume, incomplete)
  • trade.bid(symbol?) — Current best bid from the quote stream
  • trade.ask(symbol?) — Current best ask from the quote stream

See the API Reference for full details on each function.

The Fallback Pattern

In backtest mode no forming bar exists, so trade.last() returns 0. Always include a fallback so the same script works everywhere:

QuantScript
let livePrice = trade.last();
if (livePrice == 0) { livePrice = candle.close; }

Live Strategy Example

Signal from confirmed data, execution at live price:

QuantScript
event.onCandle((candle) => {
  let sma = ta.sma(candle.close, 20);  // signal: confirmed data

  if (candle.isLive) {
    let livePrice = trade.last();
    if (livePrice == 0) { livePrice = candle.close; }

    if (candle.close > sma) {
      let sl = livePrice * 0.995;  // SL relative to live price
      trade.buy({ symbol: "ETHUSD", size: { equity_percent: 5 }, stopLoss: sl });
    }
  }
});

Notice: the SMA is computed from candle.close (confirmed), but the stop-loss is anchored to livePrice (forming). This gives you stable signals with accurate execution.

Next Steps

  1. Try the Playground — Write and run QuantScript code in your browser
  2. Explore the Language Reference — See all available functions and syntax
  3. Browse the Examples — Run example scripts from this guide
  4. Build your own indicators and strategies
  5. Deploy your strategies to the cloud at quantscript.com