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. Setctx.params, gọictx.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 viactx.buy_market/buy_limit/buy_stop/sell_*/close_all. Đọc position state quactx.position(). Modify SL/TP runtime quactx.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
| Field | Type | Ý nghĩa |
|---|---|---|
long_condition | bool | Bar này signal LONG. Khi đang SHORT → đặt reverse order; khi flat → đặt order vào. |
short_condition | bool | Signal SHORT (đối xứng). |
entry_long | number | Giá entry tuyệt đối. 0=market (đóng opposite + vào ngay); < close=buy limit (pullback); > close=buy stop (breakout). |
entry_short | number | 0=market; > close=sell limit (pullback up); < close=sell stop (breakdown). |
sl_long / sl_short | number | Initial stop loss tuyệt đối. Risk-based sizing cần SL set. |
tp_long / tp_short | number | Take profit (optional). |
trailing_long + trailing_long_condition | number + bool | Update 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_condition | number + bool | Đối xứng cho SHORT. |
cancel_long / cancel_short | bool | Hủy pending limit/stop side đó. |
close_long / close_short | bool | Force đóng position ngay tại bar.close. Skip wait reverse fill. |
size_override | number | Override size (skip risk-based sizing). |
risk_pct_override | number | Per-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_X | Engine làm |
|---|---|
0 | Market 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ùngctx.declare+ctx.array("name")[t]thayctx.htf()multi-TF → dùngctx.htf_array(tf, funcName, params)trả LTF-mapped Float64Arrayctx.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_conditionvàshort_conditioncùngtrue→ 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 (
pendingTTLconfig). Re-emit signal mỗi bar để refresh hoặccancel_Xchủ động. - Trailing strict: cần CẢ
trailing_long(number) ANDtrailing_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_conditionvàshort_conditionmỗ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ùngsize_overridenếu skip. - Warmup: skip bars đầu (
continuehoặc emitnull) đến khi indicator dài nhất ready.