— ready —
EXAMPLES:
📝 STRATEGY CODE Tab=autocomplete · Ctrl+S=check · Ctrl+Enter=apply
Ready Ln 1, Col 1

📘 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

NameTypeDescription
opennumberCurrent candle open price
highnumberCurrent candle high (updates each tick)
lownumberCurrent candle low (updates each tick)
closenumberLatest tick price — use in all calculations
volumenumberRelative volume this candle
bar_indexnumberCompleted bar count from 0 — use for frequency control
bar_changedbooleantrue 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.

FunctionReturnsDescription
sma(src, len)numberSimple moving average of close (len bars)
ema(src, len)numberExponential moving average of close (len bars)
rsi(len=14)0–100Wilder RSI — 70+ overbought, 30− oversold
atr(len=14)numberAverage True Range — volatility unit for SL sizing
highest(len=20)numberHighest high of last len completed bars
lowest(len=20)numberLowest low of last len completed bars
bb(len=20, mult=2)objectBollinger Bands → { upper, mid, lower }
stoch(kLen=14, dLen=3)objectStochastic Oscillator → { k, d } both 0–100
crossover(a, b)booleantrue only on the one tick when a crosses above b
crossunder(a, b)booleantrue 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

CommandBehaviour
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:

FieldTypeNotes
ratio0–1Fraction of free cash. Default 1.0. Overridden by qty.
qtynumberFixed share count. Takes priority over ratio.
slpriceStop-loss. Long: must be < entry. Short: must be > entry.
tppriceTake-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:

FieldTypeValue
.sidestring'long' / 'short' / 'none'
.qtynumberTotal shares on that side
.avgPricenumberVolume-weighted average entry price
.unrealizednumberFloating 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

NeedUse
Standard indicatorrsi() atr() bb() etc — no state needed
Previous bar valuethis.prev = value pattern
One entry per signalstrategy.entry()
Scale into positionif (bar_changed) { strategy.add() } — once per candle
Compound gains (roll)strategy.close() then strategy.entry()
Emergency exitstrategy.closeAll()
Check if in positionstrategy.position.side
Limit trade frequencybar_index % N === 0 or this.cooldown