📚 Quant Backtester — API Reference

← Backtester Docs
🔒 Code chạy 100% trên trình duyệt — strategy được compile và backtest ngay client-side, không gửi về server. Strategy đã lưu chỉ nằm trong localStorage của trình duyệt, không ai khác đọc được.

Strategy contract

Mỗi strategy define 2 function:

  • function onInit(ctx) { ... } — chạy 1 lần trước backtest. Set ctx.params, gọi ctx.declare("name", "indicator", {...}) register indicators (cache + auto-plot).
  • function onBar(bar, ctx) { ... } — chạy mỗi bar (event-driven). bar = {time, open, high, low, close, volume}. Gọi order via ctx.buy_market/buy_limit/buy_stop/sell_*/close_all. Đọc position state qua ctx.position(). Modify SL/TP runtime qua ctx.set_sl/set_tp.

⌨️ Editor: gõ ctx. → autocomplete dropdown. Hover function → inline tooltip. API reference đầy đủ list bên dưới.

Ví dụ — minimal onBar strategy

function onInit(ctx) {
  ctx.params.fast = 10;
  ctx.params.slow = 30;
  ctx.params.risk_pct = 0.01;
  ctx.declare("fast", "sma", { period: ctx.params.fast, plot: true, color: "#60a5fa" });
  ctx.declare("slow", "sma", { period: ctx.params.slow, plot: true, color: "#facc15" });
}

function onBar(bar, ctx) {
  const fast = ctx.value("fast");
  const slow = ctx.value("slow");
  if (!isFinite(fast) || !isFinite(slow)) return;
  const atr = ctx.ta.atr(14);
  if (!isFinite(atr)) return;

  const pos = ctx.position();

  if (fast > slow) {
    if (pos === null || pos.side === -1) {
      const sl = bar.close - 2 * atr;
      const size = ctx.risk_size(sl, ctx.params.risk_pct, bar.close);
      if (size > 0) ctx.buy_market(size, sl, bar.close + 4 * atr);
    }
  } else if (fast < slow) {
    if (pos === null || pos.side === 1) {
      const sl = bar.close + 2 * atr;
      const size = ctx.risk_size(sl, ctx.params.risk_pct, bar.close);
      if (size > 0) ctx.sell_market(size, sl, bar.close - 4 * atr);
    }
  }
}

Event-driven path đủ build mọi chiến lược — entry, exit, scale-in, partial close, trailing SL runtime, conditional cancel. Xem chi tiết từng function trong các category bên dưới.

Advanced — Vectorized fast path (evaluateBatch)

Path thay thế khi cần tốc độ (vectorized signal pre-compute). Chạy nhanh 5-10x so với onBar nhưng giới hạn về linh hoạt (không có position state trong loop). Đa số chiến lược dùng onBar là đủ — chỉ chọn evaluateBatch khi sweep nhiều params hoặc backtest dataset lớn.

Define function evaluateBatch(bars, ctx) { ... } thay onBar. Trả array Array(bars.length) — mỗi element là null (skip bar) hoặc object output dưới đây. Đọc indicator full-array qua ctx.array("name").

Output schema

FieldTypeÝ nghĩa
long_conditionboolBar này signal LONG. Khi đang SHORT → đặt reverse order; khi flat → đặt order vào.
short_conditionboolSignal SHORT (đối xứng).
entry_longnumberGiá entry tuyệt đối. 0=market (đóng opposite + vào ngay); < close=buy limit (pullback); > close=buy stop (breakout).
entry_shortnumber0=market; > close=sell limit (pullback up); < close=sell stop (breakdown).
sl_long / sl_shortnumberInitial stop loss tuyệt đối. Risk-based sizing cần SL set.
tp_long / tp_shortnumberTake profit (optional).
trailing_long + trailing_long_conditionnumber + boolUpdate position.sl. Apply CHỈ khi trailing_long_condition === true AND value present. Cả 2 phải có. Bool gate cho strategy tự quyết khi nào trail (vd: chỉ trail sau breakeven, chỉ trail khi structure đổi).
trailing_short + trailing_short_conditionnumber + boolĐối xứng cho SHORT.
cancel_long / cancel_shortboolHủy pending limit/stop side đó.
close_long / close_shortboolForce đóng position ngay tại bar.close. Skip wait reverse fill.
size_overridenumberOverride size (skip risk-based sizing).
risk_pct_overridenumberPer-bar risk_pct (scale equity-relative size). Skipped khi size_override đã set. Dùng cho confidence-weighted sizing: risk_pct_override = base × confidence_weight.

Reverse-via-limit semantic

long_condition/short_condition = "đặt order theo hướng đó", KHÔNG phải "close position cũ ngay" (match sàn thật). Khi đang trong position ngược + signal flip:

entry_XEngine làm
0Market order ở next bar open → đóng opposite + mở new cùng fill (instant flip).
≠ 0Đặt limit/stop. Position cũ giữ nguyên đến khi pending fill — fill price tốt hơn close hiện tại. Pending TTL = 5 bars.

Muốn force exit không đợi fill → dùng close_long/close_short.

3 pattern phổ biến

A — Breakout entry (Turtle/Donchian style)
function evaluateBatch(bars, ctx) {
  const atr = ctx.array("atr");
  const hh20 = ctx.array("hh20");
  const ll20 = ctx.array("ll20");
  const outputs = new Array(bars.length).fill(null);

  for (let t = 20; t < bars.length; t++) {
    const a = atr[t];
    if (!(a > 0)) continue;
    const c = bars.close[t];
    const out = {};

    if (bars.high[t] >= hh20[t - 1]) {
      out.long_condition = true;
      out.entry_long = 0;                  // market — breakout đã xảy ra
      out.sl_long = ll20[t - 1] - 2 * a;
      out.cancel_short = true;
    }
    if (bars.low[t] <= ll20[t - 1]) {
      out.short_condition = true;
      out.entry_short = 0;
      out.sl_short = hh20[t - 1] + 2 * a;
      out.cancel_long = true;
    }
    if (Object.keys(out).length) outputs[t] = out;
  }
  return outputs;
}
B — Pullback limit (mean-reversion / structure)
// Vào limit BELOW current close khi long, đợi pullback xuống.
// Reverse signal → đặt limit ở opposite, KHÔNG đóng ngay.
if (bars.high[t] >= hh20[t - 1]) {
  out.long_condition = true;
  out.entry_long = c - a;                 // limit dưới close 1 ATR
  out.sl_long = ll20[t - 1] - 2 * a;
}
if (bars.low[t] <= ll20[t - 1]) {
  out.short_condition = true;
  out.entry_short = c + a;                // limit trên close 1 ATR
  out.sl_short = hh20[t - 1] + 2 * a;
}
C — Trailing với strict bool gate
// Trail SL chỉ khi structure đổi (Donchian low mới cao hơn). Skip default.
const dc_low = ctx.array("dc_l");
if (dc_low[t] > dc_low[t - 1]) {
  out.trailing_long = dc_low[t] - a;
  out.trailing_long_condition = true;     // bắt buộc bool=true mới apply
}
// Force exit khi price cross regime threshold
if (bars.close[t] < ctx.array("sma200")[t]) {
  out.close_long = true;
}

Fast mode limitations

Các API scalar-at-current-bar không work trong evaluateBatch (vì uses curIdx → return value tại last bar, không per-loop-t):

  • ctx.ta.* ad-hoc per-bar → dùng ctx.declare + ctx.array("name")[t] thay
  • ctx.htf() multi-TF → dùng ctx.htf_array(tf, funcName, params) trả LTF-mapped Float64Array
  • ctx.regime(), ctx.size_vol_target(), ctx.set_triple_barrier() → no Fast equivalent (use onBar)
  • ctx.position() inline state → no Fast equivalent. Workaround: track via heuristic close-vs-mean hoặc dùng output schema flow (engine guard).

Gotchas

  • Conflict same bar: cả long_conditionshort_condition cùng true → engine xử lý cả 2 (place 2 order). Strategy tự resolve trước nếu không muốn.
  • Pending TTL: limit/stop hết hạn sau 5 bars (pendingTTL config). Re-emit signal mỗi bar để refresh hoặc cancel_X chủ động.
  • Trailing strict: cần CẢ trailing_long (number) AND trailing_long_condition: true. Thiếu 1 trong 2 → skip.
  • Trailing only-tighten: engine không auto-monotonic. Nếu strategy emit raw level mỗi bar (vd trailing_long = ll[t]), SL có thể loosen khi structure đi ngược. Track last_emitted state để guard.
  • Dual-signal flip: emit cả long_conditionshort_condition mỗi bar khi đang có position → opposite limit fill = reverse-via-netting. Mean-reversion strategies cần emit 1 hướng/bar (heuristic close-vs-mean).
  • SL gate sizing: risk-based sizing cần sl_X. Không có SL → size=0. Dùng size_override nếu skip.
  • Warmup: skip bars đầu (continue hoặc emit null) đến khi indicator dài nhất ready.