📘 INSTRUCTION
🔹 SYNTAX RULES
Code is valid JavaScript executed every tick inside a with(env){...} sandbox. All built-in names below are injected automatically — no imports needed.
• Statements should end with ;
• if blocks require () and {}
• Variables: just assign — fast = ema(close, 9);
• Comments: // line or /* block */
• Standard JS: Math.abs, Math.max, Math.min, Math.floor, Math.sqrt etc. all work
📊 BUILT-IN VARIABLES
| Name | Type | Description |
|---|---|---|
open | number | Current candle open price |
high | number | Current candle high (updates each tick) |
low | number | Current candle low (updates each tick) |
close | number | Latest tick price — use in all calculations |
volume | number | Relative volume this candle |
bar_index | number | Completed bar count from 0 — use for frequency control |
bar_changed | boolean | true only when a new candle just opened. In CRYPTO mode this is true once per candle interval (1m/5m…). In SIM mode always true. Use to guard strategy.add so it fires once per candle, not every 200ms tick. |
// bar_changed: guard strategy.add to fire once per candle (essential in CRYPTO mode)
if (bar_changed) {
strategy.add('long', { ratio: 0.1, sl: slow * 0.97 });
}
// bar_index: run logic only every 5 bars
if (bar_index % 5 === 0) {
// rebalance or re-evaluate less frequently
}
🧮 INDICATOR FUNCTIONS
All are stateless — call any time, no this.xxx setup needed.
| Function | Returns | Description |
|---|---|---|
sma(src, len) | number | Simple moving average of close (len bars) |
ema(src, len) | number | Exponential moving average of close (len bars) |
rsi(len=14) | 0–100 | Wilder RSI — 70+ overbought, 30− oversold |
atr(len=14) | number | Average True Range — volatility unit for SL sizing |
highest(len=20) | number | Highest high of last len completed bars |
lowest(len=20) | number | Lowest low of last len completed bars |
bb(len=20, mult=2) | object | Bollinger Bands → { upper, mid, lower } |
stoch(kLen=14, dLen=3) | object | Stochastic Oscillator → { k, d } both 0–100 |
crossover(a, b) | boolean | true only on the one tick when a crosses above b |
crossunder(a, b) | boolean | true only on the one tick when a crosses below b |
Note sma/ema always use close price. The src argument is accepted for compatibility but ignored.
💡 INDICATOR USAGE PATTERNS
ATR — volatility-scaled stop loss
a = atr(14);
strategy.entry('long', {
ratio: 1.0,
sl: close - 2 * a, // 2 ATR below entry
tp: close + 4 * a // 4 ATR above (2:1 R/R)
});
Bollinger Bands — mean-reversion + squeeze
bands = bb(20, 2);
// Long when price touches lower band
if (close < bands.lower) {
strategy.entry('long', { ratio: 0.5,
sl: bands.lower * 0.98, tp: bands.mid });
}
// Short when price touches upper band
if (close > bands.upper) {
strategy.entry('short', { ratio: 0.5,
sl: bands.upper * 1.02, tp: bands.mid });
}
Stochastic — overbought / oversold filter
s = stoch(14, 3);
fast = ema(close, 9);
slow = ema(close, 21);
// Only enter long if stoch is not overbought
if (crossover(fast, slow) && s.k < 80) {
strategy.entry('long', { ratio: 1.0,
sl: slow * 0.98, tp: close * 1.08 });
}
if (crossunder(fast, slow) && s.k > 20) {
strategy.entry('short', { ratio: 1.0,
sl: slow * 1.02, tp: close * 0.92 });
}
RSI + EMA trend filter
r = rsi(14);
trend = ema(close, 50);
// Only buy dips in an uptrend
if (close > trend && r < 35) {
strategy.entry('long', { ratio: 0.6,
sl: close - 2*atr(14), tp: close * 1.10 });
}
if (close < trend && r > 65) {
strategy.entry('short', { ratio: 0.6,
sl: close + 2*atr(14), tp: close * 0.90 });
}
🎯 STRATEGY COMMANDS
| Command | Behaviour |
|---|---|
strategy.entry('long'/'short', opts?) | Idempotent — closes opposite side, opens this side. No-op if already on same side. |
strategy.add('long'/'short', opts?) | Always opens a new order on that side. Use for pyramiding / scaling in. |
strategy.close('long'/'short') | Close all orders on that side. |
strategy.closeAll() | Close every open order regardless of direction. |
opts — all fields optional, any combination:
| Field | Type | Notes |
|---|---|---|
ratio | 0–1 | Fraction of free cash. Default 1.0. Overridden by qty. |
qty | number | Fixed share count. Takes priority over ratio. |
sl | price | Stop-loss. Long: must be < entry. Short: must be > entry. |
tp | price | Take-profit. Long: must be > entry. Short: must be < entry. |
strategy.entry('long', { ratio: 0.5, sl: close-2*atr(14), tp: close*1.10 });
strategy.entry('short', { qty: 20, sl: close*1.03 });
// Pyramiding: add 20% when price moves in our favour
if (close > this.lastAdd * 1.03) {
strategy.add('long', { ratio: 0.2, sl: ema(close,21)*0.98 });
this.lastAdd = close;
}
📡 POSITION STATE
strategy.position returns a snapshot of current holdings:
| Field | Type | Value |
|---|---|---|
.side | string | 'long' / 'short' / 'none' |
.qty | number | Total shares on that side |
.avgPrice | number | Volume-weighted average entry price |
.unrealized | number | Floating P&L in $ at current price |
pos = strategy.position;
// Gate: only add if already long and profitable
if (pos.side === 'long' && pos.unrealized > 0) {
strategy.add('long', { ratio: 0.2 });
}
// Rolling: close and reopen to compound gains
if (pos.side === 'long' && close > pos.avgPrice * 1.05) {
strategy.close('long');
strategy.entry('long', { ratio: 1.0,
sl: ema(close,21)*0.975, tp: close*1.10 });
}
// Emergency exit: close all on large drawdown
if (pos.unrealized < -200) { strategy.closeAll(); }
Note position reflects state before any commands on the current tick. After strategy.close(), the next tick will show side: 'none'.
💾 PERSISTENT VARIABLES
Use this.myVar to store custom state between ticks. Always guard with === undefined on first tick.
// Track last add price for pyramiding frequency control
if (this.lastAdd === undefined) this.lastAdd = close;
if (close > this.lastAdd * 1.03) {
strategy.add('long', { ratio: 0.2 });
this.lastAdd = close;
}
// Previous value comparison (when crossover() isn't enough)
if (this.prevRsi === undefined) this.prevRsi = rsi(14);
r = rsi(14);
if (this.prevRsi > 50 && r < 50) { /* RSI crossed below 50 */ }
this.prevRsi = r;
When to use Custom rolling sums, counters, previous arbitrary values, cooldown timers. For standard crossovers/indicators, use the built-in functions instead.
📚 EX 1 – MA CROSS + PYRAMID
// MA Cross + Pyramid — enter on crossover, add 10% each tick while trend holds
fast = ema(close, 9);
slow = ema(close, 21);
pos = strategy.position;
if (crossover(fast, slow)) {
strategy.entry('long', { ratio: 0.1, sl: slow * 0.97, tp: close * 1.10 });
} else if (fast > slow && pos.side === 'long') {
strategy.add('long', { ratio: 0.1, sl: slow * 0.97 });
}
if (crossunder(fast, slow)) {
strategy.entry('short', { ratio: 0.1, sl: slow * 1.03, tp: close * 0.90 });
} else if (fast < slow && pos.side === 'short') {
strategy.add('short', { ratio: 0.1, sl: slow * 1.03 });
}
入场用 entry(10%),之后每 tick 只要趋势保持就用 add 继续加仓 10%。资金耗尽时自动停止加仓。
📚 EX 2 – RSI + PYRAMID
// RSI Mean Reversion + Pyramid — add 10% while signal holds
r = rsi(14);
a = atr(14);
pos = strategy.position;
if (r > 70) {
strategy.close('long');
} else if (r < 30) {
strategy.entry('long', { ratio: 0.1, sl: close - 2 * a, tp: close * 1.08 });
} else if (r < 40 && pos.side === 'long') {
strategy.add('long', { ratio: 0.1, sl: close - 2 * a });
}
RSI < 30 触发入场,RSI 在 30–40 之间继续加仓(每次 10%),RSI > 70 全部平多。ATR stop 动态止损。
📚 EX 3 – BREAKOUT + PYRAMID
// Two-sided Breakout + Pyramid — add 10% while price extends the breakout
h20 = highest(20);
l20 = lowest(20);
pos = strategy.position;
if (close > h20) {
strategy.close('short');
strategy.entry('long', { ratio: 0.1, sl: l20, tp: close * 1.10 });
} else if (pos.side === 'long' && close > pos.avgPrice) {
strategy.add('long', { ratio: 0.1, sl: l20 });
}
if (close < l20) {
strategy.close('long');
strategy.entry('short', { ratio: 0.1, sl: h20, tp: close * 0.90 });
} else if (pos.side === 'short' && close < pos.avgPrice) {
strategy.add('short', { ratio: 0.1, sl: h20 });
}
突破入场后,只要价格继续向有利方向(多仓高于均价 / 空仓低于均价)就每 tick 加仓 10%。SL 锁定在区间极值。
📚 EX 4 – MANUAL CROSS + PYRAMID
// Manual MA Cross + Pyramid — first-tick safe, add 10% while trend holds
fast = ema(close, 9);
slow = ema(close, 21);
pos = strategy.position;
if (this.prevFast === undefined) {
this.prevFast = fast; this.prevSlow = slow;
} else {
if (this.prevFast <= this.prevSlow && fast > slow) {
strategy.close('short');
strategy.entry('long', { ratio: 0.1, sl: slow * 0.98, tp: close * 1.08 });
} else if (fast > slow && pos.side === 'long') {
strategy.add('long', { ratio: 0.1, sl: slow * 0.98 });
}
if (this.prevFast >= this.prevSlow && fast < slow) {
strategy.close('long');
strategy.entry('short', { ratio: 0.1, sl: slow * 1.02, tp: close * 0.92 });
} else if (fast < slow && pos.side === 'short') {
strategy.add('short', { ratio: 0.1, sl: slow * 1.02 });
}
this.prevFast = fast; this.prevSlow = slow;
}
首 tick 安全初始化,金叉/死叉触发入场(10%),之后每 tick 均线方向不变就继续加仓。this.prevFast/Slow 跨 tick 持久化。
📚 EX 5 – EMA ROLL + PYRAMID
// EMA Roll + Pyramid — add 10%/tick, compound on every +3% gain
fast = ema(close, 9);
slow = ema(close, 21);
pos = strategy.position;
if (fast > slow) {
if (pos.side !== 'long') {
strategy.entry('long', { ratio: 0.1, sl: slow * 0.975 });
} else if (close > pos.avgPrice * 1.03) {
strategy.close('long');
strategy.entry('long', { ratio: 0.1, sl: slow * 0.975 });
} else {
strategy.add('long', { ratio: 0.1, sl: slow * 0.975 });
}
} else if (fast < slow) {
strategy.closeAll();
}
趋势持续时每 tick 加仓 10%。当均价盈利 +3% 时滚仓(平仓 + 重开),以复利方式扩大本金。均线反转时全平。
📌 QUICK REFERENCE
Editor shortcuts
• Ctrl+S — check syntax · Ctrl+Enter — apply to trader
• Tab — autocomplete / indent · ▶ APPLY — send without closing editor
Common gotchas
• strategy.entry is idempotent — won't stack. Use strategy.add for pyramiding.
• strategy.add fires every tick unless guarded. In CRYPTO mode use if (bar_changed) { strategy.add(...) } so it fires once per candle (1m/5m…), not every 200ms.
• SL/TP are fixed at the moment the order is placed — they don't trail automatically.
• SL/TP with wrong-side prices are silently rejected with a toast warning.
• ratio and qty: if both set, qty wins. Omit both → 100% of free cash.
• bb() and stoch() return objects — assign to a variable first: b = bb(20); b.upper.
• All names are case-sensitive: close ✓ Close ✗
Choosing the right tool
| Need | Use |
|---|---|
| Standard indicator | rsi() atr() bb() etc — no state needed |
| Previous bar value | this.prev = value pattern |
| One entry per signal | strategy.entry() |
| Scale into position | if (bar_changed) { strategy.add() } — once per candle |
| Compound gains (roll) | strategy.close() then strategy.entry() |
| Emergency exit | strategy.closeAll() |
| Check if in position | strategy.position.side |
| Limit trade frequency | bar_index % N === 0 or this.cooldown |