— ready —
▸ STRATEGY LIBRARY
📝 STRATEGY CODE Tab=autocomplete · Enter=auto-indent · 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 – TREND PULLBACK

// regime filter + dip entry + chandelier trail (core)
trend = ema(close, 50);          // regime line
up = close > trend && ema(close,9) > trend;
if (up && rsi(7) < 35 && pos.side !== 'long') {
    strategy.entry('long', { ratio: 0.6, sl: close - 2.5*atr(14) });
    this.hh = close;             // trail anchor
}
// manual chandelier: sl is FIXED at entry — trail by closing
if (pos.side === 'long') {
    this.hh = Math.max(this.hh || close, close);
    if (close < this.hh - 3*atr(14)) strategy.close('long');
}

三层结构:EMA(50) 趋势过滤(只顺势开仓)→ RSI(7) 回调择时(趋势内逢低进场)→ 吊灯式移动止盈(从最高点回吐 3·ATR 离场)。SL 在下单后固定不动,所以移动止损必须自己用 this.* + close() 实现——这是本例的核心技巧。

📚 EX 2 – TURTLE (DONCHIAN)

// 20/10 channel + ATR unit pyramiding (core)
if (pos.side === 'none' && high > highest(20)) {   // touch trigger
    strategy.entry('long', { ratio: 0.25, sl: close - 2*a });
    this.units = 1; this.lastAdd = close;
}
// add a unit per 0.5·ATR move, ratchet the shared stop
if (bar_changed && this.units > 0 && this.units < 4 &&
    close >= this.lastAdd + 0.5*a) {
    this.stop = close - 2*a;
    strategy.add('long', { ratio: 0.25, sl: this.stop });
    this.units++; this.lastAdd = close;
}
if (pos.side === 'long' && low < lowest(10)) strategy.closeAll();

经典海龟系统:盘中触及 20 棒通道极值即入场(用 high/low 与通道做同类比较——close 对 high 通道几乎永远够不着),每向有利方向走 0.5·ATR 加一个 unit(每个 25% 仓,封顶 4 个),每次加仓把共享止损棘轮上移到新价 −2·ATR,触及 10 棒反向通道离场。this.units / lastAdd / stop 三个状态变量构成完整的单元化仓位管理。

📚 EX 3 – SQUEEZE BREAKOUT

// bandwidth squeeze → directional break (core)
b  = bb(20, 2);
bw = (b.upper - b.lower) / b.mid;     // normalized bandwidth
this.bws.push(bw);                    // rolling 120-bar window
if (this.bws.length > 120) this.bws.shift();
lo = Math.min.apply(null, this.bws);
hi = Math.max.apply(null, this.bws);
if (bw <= lo + 0.2*(hi - lo)) this.armed = true;   // squeeze!
if (this.armed && close > b.upper) {
    strategy.entry('long', { ratio: 0.7, sl: b.mid,
        tp: close + 2.5*(close - b.mid) });
    this.armed = false;
}

波动率压缩检测:布林带宽落到自身 120 棒区间的最低 20% 分位视为挤压,挂起触发器,随后向哪边破带就做哪边;带宽放大却没破则解除武装。核心技巧:用 this.bws 数组维护滚动统计窗口(内置指标做不到的自定义统计)。

📚 EX 4 – CUSTOM MACD

// hand-rolled MACD: EMA of a DERIVED series (core)
macd = ema(close, 12) - ema(close, 26);
k = 2 / (9 + 1);                      // signal = EMA(9) of macd
this.signal = this.signal === undefined
    ? macd : this.signal + k*(macd - this.signal);
hist = macd - this.signal;
// manual zero-cross (avoids conditional crossover() calls)
prevH = this.prevHist === undefined ? hist : this.prevHist;
this.prevHist = hist;
if (prevH <= 0 && hist > 0 && macd > 0)
    strategy.entry('long', { ratio: 0.8,
        sl: close - 2*atr(14), tp: close + 4*atr(14) });

没有内置 macd()——自己造:信号线是「macd 这条派生序列的 EMA」,内置函数只能算收盘价的 EMA,所以用 this.signal 跨 tick 增量递推。这个模式可以推广到任何自定义指标。出场用「柱体连续两棒衰减」的动能枯竭判断。

📚 EX 5 – Z-SCORE REVERT

// statistical mean reversion, scaled entries (core)
b  = bb(20, 1);                  // mult=1 → upper-mid = 1σ
z  = (close - b.mid) / (b.upper - b.mid);
want = z <= -3.4 ? 3 : z <= -2.7 ? 2 : z <= -2 ? 1 : 0;
if (bar_changed && want > this.legs && this.legs < 3) {
    if (this.legs === 0) strategy.entry('long', { ratio: 0.25 });
    else                 strategy.add('long',   { ratio: 0.25 });
    this.legs++;
}
if (pos.side === 'long' && z >= 0)  strategy.close('long');  // mean touch
if (pos.side === 'long' && z < -4.5) strategy.close('long'); // regime break

把价格偏离标准化成 z 分数(巧用 bb(20,1):mult=1 时上轨−中轨恰为 1σ),在 −2σ/−2.7σ/−3.4σ 三档分批接刀,回到均值止盈,−4.5σ 认输(统计失效)。刻意只做多:在有正漂移的市场里做空均值回归是负期望。

📚 EX 6 – VOL-TARGETED TREND

// volatility targeting: size by risk budget (core)
atrPct = atr(14) / close;            // realized vol proxy
TARGET = 0.012;                      // per-trade risk budget
size = Math.max(0.15, Math.min(0.9, TARGET / atrPct));
upX   = crossover(ema(close,20), ema(close,60));  // unconditional!
downX = crossunder(ema(close,20), ema(close,60));
if (upX) {
    strategy.entry('long', { ratio: size, sl: close - 2.2*atr(14) });
    this.peak = close;
}
// ratchet trail: 2·ATR giveback from the running peak
if (pos.side === 'long' && close < (this.peak = Math.max(this.peak||close, close)) - 2*atr(14))
    strategy.close('long');

机构常用的波动率目标化仓位:仓位比例 = 风险预算 ÷ 当前 ATR%(截断在 15%–90%)——市场越疯狂仓越轻,平静趋势满仓干。入场是普通的 EMA 金叉,但仓位本身成为策略的一部分。注意两个 cross 函数在顶部无条件调用(见下方 gotcha)。

📌 QUICK REFERENCE

Editor shortcuts

Ctrl+S — check syntax  ·  Ctrl+Enter — apply to trader

Tab — autocomplete / indent  ·  ▶ APPLY — send without closing editor

Common gotchas

crossover()/crossunder() track state per call-site in call order. Always call them unconditionally at the top (upX = crossover(a,b);) and use the variable — a call skipped by && short-circuit or an if branch shifts the state slots and corrupts every later cross.

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