Руководство · Тестер стратегий

Консоль бэктеста: тестирование торговых стратегий кодом

На каждом графике есть кнопка Стратегия. Она открывает консоль, которая прогоняет стратегию по прошлым свечам и показывает каждую сделку на графике, прибыль, просадку, долю выигрышей и кривую капитала в сравнении с «купи и держи». Начните со встроенной стратегии, затем напишите свою в несколько строк JavaScript.

Открыть консоль на EURUSD К примерам

Безопасно по устройству. Ваш скрипт выполняется только в вашем браузере, в песочнице без доступа к интернету: он может читать переданные ему свечи и ничего больше (ни ваш аккаунт, ни этот сайт, ни другие сайты). Ничего из написанного вами не выполняется на наших серверах.

Начало за минуту

  1. Откройте любой график, например EURUSD, и выберите таймфрейм для теста (H1, D1…).
  2. Нажмите Стратегия на панели инструментов графика. Консоль откроется под графиком.
  3. Выберите встроенную стратегию, например Пересечение скользящих средних. Она запускается сразу: стрелки на графике отмечают каждый вход и выход.
  4. Измените параметр (например, медленную среднюю с 30 на 50): тест перезапустится сам.
  5. Смотрите Обзор для итогов, Сделки для каждой сделки, Кривая капитала для счёта во времени.
  6. Нажмите Редактировать копию, чтобы превратить её в свой скрипт, измените и нажмите Запустить тест (или Ctrl+Enter).

Консоль по частям

ЧастьЧто делает
Список стратегийВстроенные: шесть готовых стратегий (пересечение скользящих средних, разворот RSI, возврат к среднему по полосам Боллинджера, пересечение сигнальной линии MACD, пробой Дончиана, SuperTrend). Мои стратегии: сохранённые вами (нужен вход). Новый скрипт… создаёт пустой.
Посмотреть код / Редактировать копиюПосмотреть код показывает, что именно делает встроенная стратегия. Редактировать копию копирует её в редактор как ваш собственный скрипт, готовый к изменению.
ПараметрыКаждый input(…) в скрипте становится здесь полем (числом или списком). Изменение перезапускает тест. По умолчанию возвращает исходные значения.
НастройкиКапитал, размер ордера, издержки, число свечей и разрешены ли короткие сделки (ниже).
РедакторJavaScript стратегии. Tab делает отступ, Ctrl/⌘+Enter запускает. Ошибка показывает сообщение и номер строки.
НомерКраткий список всего, что может использовать скрипт, прямо в консоли. Эта страница — подробная версия.
Сохранить / Сохранить как…Сохраняет скрипт, его название, параметры и настройки в вашем аккаунте (точка на Сохранить • означает несохранённые изменения). Ваши стратегии перечислены на каждом графике.
Вкладки результатовОбзор, Сделки (с Скачать CSV), Кривая капитала и Log. Сделки и нарисованные линии также отображаются на графике; нажмите на сделку, чтобы перейти к ней.

Настройки

НастройкаЗначениеХорошее начальное значение
Стартовый капиталСчёт, с которого начинается тест, в валюте цены (для EURUSD — доллары).10,000
Размер ордера% капитала: каждая сделка использует эту долю счёта на момент входа (100% = весь счёт, без плеча). единицы: фиксированное количество (для форекса — единицы базовой валюты: 100 000 = один стандартный лот). Скрипт может задать свой объём для каждого ордера через { qty }.100% капитала или фиксированный лот
Комиссия % за сторонуКомиссия в процентах от суммы сделки, берётся при входе и ещё раз при выходе.0,002–0,01 для форекса, 0,05–0,1 для акций и криптовалют
Проскальзывание / спред (цена)Добавляется против вас при каждом исполнении, в единицах цены. Свечи — это цены bid, поэтому укажите здесь типичный спред, чтобы покупки были реалистичными.EURUSD 0,00010 (1 пипс), золото 0,30
СвечиНа графике: свечи, загруженные на экране. После входа выберите До 5 000 … 100 000, чтобы загрузить больше истории того же таймфрейма (этот сайт допускает до 20,000).Как можно больше: больше свечей, больше сделок, надёжнее цифры
Разрешить короткие сделкиВыкл.: short() только закрывает лонг и никогда не открывает шорт. Удобно, чтобы протестировать только длинную сторону, не меняя скрипт.Включено для форекса, выключено для инвестиций только в лонг

Как читать результаты

ЧислоО чём это говорит
Чистая прибыльИтоговый капитал минус начальный, после комиссии и проскальзывания; также в % от капитала.
Купить и держатьСколько принесла бы простая покупка на первой свече и удержание до последней. Стратегия, которая этого не превосходит или превосходит лишь с гораздо более глубокой просадкой, мало что даёт.
Максимальная просадкаНаибольшее падение капитала от пика, в деньгах и %. Боль, которую пришлось бы вытерпеть. Многие трейдеры смотрят на чистую прибыль ÷ максимальную просадку.
Сделки, % прибыльныхСколько сделок закрыто и доля прибыльных. Низкая доля выигрышей допустима, если прибыльные сделки намного крупнее убыточных.
Профит-факторВаловая прибыль ÷ валовой убыток. Выше 1 — стратегия зарабатывает; 1,3–2 типично для надёжных стратегий; очень высокие значения при малом числе сделок обычно — везение.
Средняя сделка, средняя прибыль, средний убытокСредняя прибыль на сделку и типичный размер прибыльных и убыточных сделок. Сравните среднюю сделку со своими издержками.
Крупнейшая прибыль / убыток, убыточные сделки подрядКрайние значения. Одна огромная прибыльная сделка, на которой держится весь результат, — тревожный знак.
Коэффициент ШарпаДоходность на единицу волатильности, в годовом выражении по изменениям капитала от свечи к свече. Выше 1 — хорошо, выше 2 — очень хорошо (и редко).
Время в рынкеДоля свечей с открытой позицией. Меньше времени при той же прибыли — меньше риска.
Лонг / шортСделки, прибыль и доля выигрышей каждой стороны отдельно: часто всю работу делает одна сторона.

Вкладка Сделки перечисляет каждую сделку с направлением, временем и ценой входа и выхода, объёмом, прибылью в деньгах и %, длительностью в свечах, причиной закрытия (сигнал, стоп-лосс, тейк-профит, разворот, ещё открыта в конце) и вашим комментарием. Скачать CSV экспортирует её в таблицу. Кривая капитала показывает счёт в сравнении с «купи и держи». Log показывает, что ваш скрипт записал через log().

Как исполняются ордера

  • Никакого заглядывания вперёд. Ордер, выставленный в onBar(i), исполняется по открытию следующей свечи (i + 1). Стратегия никогда не может торговать по цене, которую не могла знать.
  • Стопы внутри свечи. Стоп-лосс и тейк-профит проверяются по максимуму и минимуму каждой свечи. Если оба могли быть достигнуты в одной свече, засчитывается стоп (осторожное допущение). Гэп через стоп исполняется по открытию, как на реальных рынках.
  • Одна позиция за раз. long() при открытом шорте закрывает шорт и открывает лонг («разворот»); long(), когда лонг уже открыт, только обновляет переданные стопы.
  • Издержки на каждом исполнении. Проскальзывание сдвигает каждую цену исполнения против вас; комиссия берётся при входе и выходе.
  • Конец. Сделка, открытая после последней свечи, закрывается по последнему закрытию и помечается как «ещё открыта в конце».
onBar(i): long() filled at this open candle i + 1

Пишем свою стратегию

Стратегия — это короткая программа на JavaScript из двух частей:

  1. Начало выполняется один раз: считайте параметры через input(), рассчитайте целые ряды через ta.* или ind() и нарисуйте их через plot().
  2. function onBar(i) { … } выполняется для каждой свечи, от старых к новым. i — номер свечи (0 = самая старая). Решайте здесь: long(), short(), exit() или ничего.
// 1. once: inputs and series
const len = input("Average length", 50, { min: 5, max: 400 });
const avg = ta.sma(close, len);           // one value per candle
plot(avg, "SMA " + len, "#2F80ED");       // drawn on the price chart

// 2. every candle
function onBar(i) {
  if (avg[i] == null) return;             // the first len - 1 values are null (warming up)
  if (position === 0 && close[i] > avg[i]) long();
  if (position > 0 && close[i] < avg[i]) exit("below the average");
}

Что стоит знать с самого начала:

  • Ряды — это массивы, по одному значению на свечу, от старых к новым: close[i] — закрытие этой свечи, close[i - 1] — предыдущей. Рассчитанные ряды равны null, пока свечей недостаточно; проверяйте это перед использованием.
  • Внутри onBar(i) можно читать свечи только до i. close[i + 1] останавливает тест с ошибкой: это было бы заглядыванием в будущее.
  • Используйте const и let как обычно; ваши переменные сохраняют значения между свечами (удобно для трейлинг-стопа).
  • Цены в единицах графика (для EURUSD 1,0850, для золота 2650,40); время — секунды UTC.

Номер

Свечи и текущее состояние

НазваниеЧто это
open, high, low, close, volumeМассивы значений свечей, от старых к новым (объём — тиковый).
timeВремя открытия каждой свечи в секундах UTC. new Date(time[i] * 1000).getUTCHours() даёт час.
bars, symbol, timeframeЧисло свечей, тикер (например, "EURUSD") и таймфрейм (например, "H1").
positionВнутри onBar: 1 — лонг, -1 — шорт, 0 — без позиции.
positionSize, entryPrice, entryBarОбъём в единицах, цена исполнения и номер свечи открытой сделки (null без позиции). i - entryBar = удерживаемые свечи.
equity, cashСтоимость счёта на закрытии этой свечи (с открытой сделкой) и денежные средства без неё.

Параметры

ВызовРезультат
input("Period", 14, { min: 2, max: 100, step: 1 })Числовое поле (в пределах min и max). Возвращает введённое значение или 14.
input("Average", "EMA", { options: ["SMA", "EMA"] })Список. Возвращает выбранный текст.

До 30 параметров. Их значения сохраняются вместе со стратегией и передаются с ней при публикации в идее.

Расчёты (ta.*)

ФункцияВозвращает
ta.sma(src, n), ta.ema(src, n), ta.wma(src, n), ta.rma(src, n)Простая, экспоненциальная, взвешенная и уайлдеровская скользящие средние любого ряда.
ta.rsi(src, 14)RSI (0–100).
ta.atr(14), ta.tr()Average True Range (простое среднее, как на графике) и True Range каждой свечи.
ta.stdev(src, n)Стандартное отклонение за n свечей.
ta.highest(src, n), ta.lowest(src, n)Максимальное / минимальное значение за последние n свечей, включая текущую. Для пробоя сравнивайте с [i - 1].
ta.change(src, n = 1), ta.roc(src, n = 1)Разница с n свечами назад, и то же в %.
ta.macd(src, 12, 26, 9){ macd, signal, hist }.
ta.bb(src, 20, 2)Полосы Боллинджера { upper, middle, lower }.
ta.stoch(14, 3, 3)Стохастик { k, d } (период, сглаживание %D, замедление).
ta.supertrend(10, 3){ line, dir }: dir равен 1 в восходящем тренде и -1 в нисходящем.

src — любой ряд: close, high, другой результат или ваш собственный массив. Каждая функция возвращает новый массив; свечи ничто не изменяет.

Сигналы

ФункцияИстинно, когда…
ta.crossover(a, b, i)a пересекает b снизу вверх на свече i (на i - 1 было на уровне или ниже). b может быть числом: ta.crossover(rsi, 30, i).
ta.crossunder(a, b, i)a пересекает b сверху вниз.
ta.cross(a, b, i)Подойдёт любой.
ta.rising(s, n, i), ta.falling(s, n, i)s рос (падал) на каждой из последних n свечей.

Ордера

ВызовДействие (по открытию следующей свечи)
long(options) (также buy)Открывает длинную позицию; сначала закрывает короткую.
short(options) (также sell)Открывает короткую позицию; сначала закрывает длинную. При выключенных шортах только закрывает лонг.
exit("reason")Закрывает открытую позицию. Причина отображается в списке сделок.
stopLoss(price), takeProfit(price)Сразу переносит стоп / цель открытой сделки (null удаляет). Используйте для трейлинг-стопов.
ПараметрЗначение
{ sl: 1.0820, tp: 1.0950 }Стоп-лосс и тейк-профит в виде цен.
{ slPct: 2, tpPct: 4 }Стоп и цель в % от цены исполнения.
{ qty: 50000 }Объём в единицах для этого ордера (вместо настройки «Размер ордера»).
{ comment: "breakout" }Примечание, показываемое вместе со сделкой.

Вывод

ВызовДействие
plot(series, "Name", "#2F80ED")Рисует линию на ценовом графике (до 12).
log("text", value, …)Пишет строку во вкладку Log со временем свечи (до 200 строк).

Все 71 индикатор графика в коде: ind()

ind(id, inputs) рассчитывает любой индикатор графика точно так же, как график. Возвращает его первую линию как ряд; все линии — в .lines, их названия — в .names. Пропущенные параметры принимают указанное значение по умолчанию. method — один из SMA, EMA, SMMA, LWMA; price — один из close, open, high, low, median, typical, weighted.

const st = ind("stoch", { k: 14, d: 3, slowing: 3 });   // st = %K, st.lines[1] = %D
const ich = ind("ichimoku");                            // ich.names: Tenkan, Kijun, Span A, Span B, Chikou
const kijun = ich.lines[1];
idИндикаторПараметры (по умолчанию)Линии
maMoving Averageperiod: 20, method: SMA, price: close, shift: 0MA
amaAdaptive Moving Averageperiod: 9, fast: 2, slow: 30, price: closeAMA
adxAverage Directional Movement Indexperiod: 14ADX, +DI, −DI
adxwADX Wilderperiod: 14ADX, +DI, −DI
bbBollinger Bandsperiod: 20, dev: 2, price: close, shift: 0Middle, Upper, Lower
demaDouble Exponential Moving Averageperiod: 14, price: closeDEMA
envEnvelopesperiod: 14, method: SMA, dev: 0.1, price: closeUpper, Lower
framaFractal Adaptive Moving Averageperiod: 14, price: closeFrAMA
ichimokuIchimoku Kinko Hyotenkan: 9, kijun: 26, senkou: 52Tenkan, Kijun, Span A, Span B, Chikou
sarParabolic SARstep: 0.02, max: 0.2SAR
stddevStandard Deviationperiod: 20, method: SMA, price: closeStdDev
temaTriple Exponential Moving Averageperiod: 14, price: closeTEMA
vidyaVariable Index Dynamic Averagecmo: 9, ema: 12, price: closeVIDYA
atrAverage True Rangeperiod: 14ATR
bearsBears Powerperiod: 13Bears
bullsBulls Powerperiod: 13Bulls
cciCommodity Channel Indexperiod: 14, price: typicalCCI
chaikinChaikin Oscillatorfast: 3, slow: 10, method: EMACHO
demarkerDeMarkerperiod: 14DeMarker
forceForce Indexperiod: 13, method: SMAForce
macdMACDfast: 12, slow: 26, signal: 9, price: closeMACD, Signal
momentumMomentumperiod: 14, price: closeMomentum
osmaMoving Average of Oscillatorfast: 12, slow: 26, signal: 9, price: closeOsMA
rsiRelative Strength Indexperiod: 14, price: closeRSI
rviRelative Vigor Indexperiod: 10RVI, Signal
stochStochastic Oscillatork: 5, d: 3, slowing: 3, method: SMA%K, %D
trixTriple Exponential Averageperiod: 14, price: closeTRIX
wprWilliams' Percent Rangeperiod: 14%R
adAccumulation/DistributionнетA/D
mfiMoney Flow Indexperiod: 14MFI
obvOn Balance VolumeнетOBV
volumesVolumesнетVolume
acAccelerator OscillatorнетAC
alligatorAlligatorjaw: 13, jawShift: 8, teeth: 8, teethShift: 5, lips: 5, lipsShift: 3Jaw, Teeth, Lips
aoAwesome OscillatorнетAO
fractalsFractalsнетUp, Down
gatorGator Oscillatorjaw: 13, teeth: 8, lips: 5Upper, Lower
bwmfiMarket Facilitation IndexнетBW MFI
zigzagZigZagdepth: 12, dev: 0.3ZigZag
vwapVWAP (volume-weighted average price)нетVWAP
supertrendSuperTrendperiod: 10, mult: 3Up trend, Down trend
keltnerKeltner Channelsperiod: 20, atr: 10, mult: 2Middle, Upper, Lower
donchianDonchian Channelsperiod: 20Upper, Lower, Middle
hmaHull Moving Averageperiod: 20, price: closeHMA
pivotsPivot Points (classic)нетP, R1, S1, R2, S2, R3, S3
stochrsiStochastic RSIrsi: 14, stoch: 14, k: 3, d: 3%K, %D
aroonAroonperiod: 25Aroon up, Aroon down
cmfChaikin Money Flowperiod: 20CMF
uoUltimate Oscillatorp1: 7, p2: 14, p3: 28UO
chopChoppiness Indexperiod: 14CHOP
vwmaVolume-Weighted Moving Averageperiod: 20VWMA
zlemaZero-Lag Exponential Moving Averageperiod: 21, price: closeZLEMA
almaArnaud Legoux Moving Averageperiod: 9, offset: 0.85, sigma: 6, price: closeALMA
mcginleyMcGinley Dynamicperiod: 14McGinley
lsmaLeast Squares Moving Averageperiod: 25, price: closeLSMA
chandelierChandelier Exitperiod: 22, mult: 3Long exit, Short exit
rocRate of Changeperiod: 12ROC
cmoChande Momentum Oscillatorperiod: 9CMO
tsiTrue Strength Indexlong: 25, short: 13, signal: 13TSI, Signal
ppoPercentage Price Oscillatorfast: 12, slow: 26, signal: 9Histogram, PPO, Signal
dpoDetrended Price Oscillatorperiod: 20DPO
kstKnow Sure Thingsignal: 9KST, Signal
vortexVortex Indicatorperiod: 14VI+, VI−
eomEase of Movementperiod: 14EOM
massMass Indexema: 9, sum: 25Mass Index
coppockCoppock Curvewma: 10, long: 14, short: 11Coppock
hvHistorical Volatilityperiod: 20HV %
fisherFisher Transformperiod: 9Fisher, Trigger
squeezeSqueeze Momentumbb: 20, bbk: 2, kc: 20, kck: 1.5Momentum, Squeeze on, Squeeze off
crsiConnors RSIrsi: 3, streak: 2, rank: 100CRSI
pvtPrice Volume TrendнетPVT

Примеры

Каждый пример работает как есть: нажмите Копировать, откройте консоль, выберите Новый скрипт…, вставьте и нажмите Запустить тест. Результаты зависят от тикера, таймфрейма и периода; примеры учат технике и не являются рекомендациями.

1. Your first strategy: buy above a moving average

The smallest useful script. It is long while the close is above its 50-candle average and flat otherwise. The top of the script runs once; onBar(i) runs for every candle.

const len = input("Average length", 50, { min: 5, max: 400 });
const avg = ta.sma(close, len);
plot(avg, "SMA " + len, "#2F80ED");

function onBar(i) {
  if (avg[i] == null) return;                 // still warming up
  if (position === 0 && close[i] > avg[i]) long({ comment: "close above the average" });
  if (position > 0 && close[i] < avg[i]) exit("close below the average");
}

2. Moving average crossover, long and short

Always in the market: long() while short reverses the position (and short() while long). Untick Allow short trades in the settings to make it long-only without changing the code.

const fast = ta.ema(close, input("Fast EMA", 12, { min: 2, max: 200 }));
const slow = ta.ema(close, input("Slow EMA", 26, { min: 3, max: 400 }));
plot(fast, "Fast", "#2F80ED");
plot(slow, "Slow", "#E39B2D");

function onBar(i) {
  if (ta.crossover(fast, slow, i)) long();
  if (ta.crossunder(fast, slow, i)) short();
}

3. Fixed stop loss and take profit in percent

slPct and tpPct are measured from the fill price. The stop and the target are checked inside every candle with its high and low; if both could have been hit in one candle, the stop counts.

const r = ta.rsi(close, 14);
const sl = input("Stop loss %", 1.5, { min: 0.1, max: 20, step: 0.1 });
const tp = input("Take profit %", 3, { min: 0.1, max: 50, step: 0.1 });

function onBar(i) {
  if (position !== 0) return;                 // one trade at a time
  if (ta.crossover(r, 30, i)) long({ slPct: sl, tpPct: tp, comment: "RSI back above 30" });
  if (ta.crossunder(r, 70, i)) short({ slPct: sl, tpPct: tp, comment: "RSI back below 70" });
}

4. ATR stop and a 2:1 target in prices

sl and tp take prices. Here the stop sits 2 ATR from the close and the target twice as far (risk/reward 1:2). The fill is at the next open, so the real distance can differ slightly.

const a = ta.atr(14);
const hi = ta.highest(high, 20);
const mult = input("Stop (ATR multiple)", 2, { min: 0.5, max: 6, step: 0.5 });

function onBar(i) {
  if (i < 1 || a[i] == null || hi[i - 1] == null || position !== 0) return;
  if (close[i] > hi[i - 1]) {                  // a new 20-candle high
    const stop = close[i] - mult * a[i];
    const target = close[i] + 2 * mult * a[i];
    long({ sl: stop, tp: target, comment: "breakout, risk " + (mult * a[i]).toFixed(5) });
  }
}

5. A trailing stop that follows the price

stopLoss(price) moves the stop of the open trade. This one trails 3 ATR under the highest close since the entry and never moves down.

const a = ta.atr(14);
const fast = ta.ema(close, 20), slow = ta.ema(close, 50);
let stop = null;

function onBar(i) {
  if (a[i] == null || slow[i] == null) return;
  if (position === 0) {
    stop = null;
    if (ta.crossover(fast, slow, i)) long({ comment: "trend up" });
    return;
  }
  const candidate = close[i] - 3 * a[i];
  if (stop === null || candidate > stop) {     // only ever raise it
    stop = candidate;
    stopLoss(stop);
  }
}

6. Any chart indicator with ind(): ADX trend filter

ind(id, inputs) gives the first line of any of the chart's 71 indicators; the other lines are in .lines (their names in .names). Here: trade the MACD cross only when ADX says the market is trending.

const adx = ind("adx", { period: 14 });      // .lines[0] ADX, [1] +DI, [2] -DI
const plusDI = adx.lines[1], minusDI = adx.lines[2];
const m = ta.macd(close, 12, 26, 9);
const minAdx = input("Minimum ADX", 25, { min: 10, max: 50 });

function onBar(i) {
  if (adx[i] == null || adx[i] < minAdx) return;
  if (ta.crossover(m.macd, m.signal, i) && plusDI[i] > minusDI[i]) long();
  if (ta.crossunder(m.macd, m.signal, i) && minusDI[i] > plusDI[i]) short();
}

7. Mean reversion with Bollinger Bands

Buys a close under the lower band, sells a close over the upper band, and exits at the middle band. A classic for ranging markets; it suffers in strong trends.

const b = ta.bb(close, input("Period", 20, { min: 5, max: 200 }), input("Width", 2, { min: 0.5, max: 4, step: 0.1 }));
plot(b.upper, "Upper", "#8E44AD");
plot(b.lower, "Lower", "#8E44AD");

function onBar(i) {
  if (b.middle[i] == null) return;
  if (position === 0 && close[i] < b.lower[i]) long({ comment: "below the lower band" });
  else if (position === 0 && close[i] > b.upper[i]) short({ comment: "above the upper band" });
  else if (position > 0 && close[i] >= b.middle[i]) exit("middle band");
  else if (position < 0 && close[i] <= b.middle[i]) exit("middle band");
}

8. Trade only at certain hours (time filter)

time[i] is the candle's open in UTC seconds. This example only opens trades between 07:00 and 16:00 UTC (the London session) and closes anything still open at 20:00. Use it on an intraday timeframe.

const from = input("Start hour (UTC)", 7, { min: 0, max: 23 });
const to = input("End hour (UTC)", 16, { min: 1, max: 24 });
const fast = ta.sma(close, 10), slow = ta.sma(close, 30);

function onBar(i) {
  const hour = new Date(time[i] * 1000).getUTCHours();
  if (position !== 0 && hour >= 20) { exit("end of the day"); return; }
  if (hour < from || hour >= to) return;
  if (ta.crossover(fast, slow, i)) long();
  if (ta.crossunder(fast, slow, i)) short();
}

9. Your own calculation: a volatility breakout

Series are plain arrays, so you can compute your own. This builds yesterday's range for every candle and buys a break above the open plus half of it. log() writes to the Log tab.

const k = input("Range multiple", 0.5, { min: 0.1, max: 2, step: 0.1 });
const range = high.map((h, i) => (i ? high[i - 1] - low[i - 1] : null));   // the previous candle's range
const trigger = open.map((o, i) => (range[i] == null ? null : o + k * range[i]));
plot(trigger, "Trigger", "#16A085");

function onBar(i) {
  if (trigger[i] == null) return;
  if (position === 0 && close[i] > trigger[i]) {
    long({ slPct: 1, comment: "close above open + " + k + " x range" });
    log("signal at", close[i], "trigger", trigger[i]);
  } else if (position > 0 && i - entryBar >= 3) {
    exit("held 3 candles");
  }
}

10. Position size from risk (1% of equity per trade)

{ qty } sets the size in units for one order, overriding the Order size setting. Here the size is chosen so that hitting the stop loses about 1% of the current equity.

const a = ta.atr(14);
const riskPct = input("Risk per trade %", 1, { min: 0.1, max: 5, step: 0.1 });
const fast = ta.ema(close, 20), slow = ta.ema(close, 50);

function onBar(i) {
  if (a[i] == null || slow[i] == null || position !== 0) return;
  if (ta.crossover(fast, slow, i)) {
    const stopDistance = 2 * a[i];
    const qty = (equity * riskPct / 100) / stopDistance;
    long({ qty: qty, sl: close[i] - stopDistance, tp: close[i] + 3 * stopDistance,
           comment: "risk " + riskPct + "%" });
  }
}

11. Two conditions in a row: pullback in an uptrend

Combines a trend filter (price above the 200 average), a pullback (RSI under 40 within the last 5 candles) and a trigger (close back above the 20 average).

const trend = ta.sma(close, 200), fast = ta.ema(close, 20), r = ta.rsi(close, 14);

function onBar(i) {
  if (trend[i] == null || i < 5) return;
  let dipped = false;
  for (let k = 0; k < 5; k++) if (r[i - k] != null && r[i - k] < 40) dipped = true;
  if (position === 0 && close[i] > trend[i] && dipped && ta.crossover(close, fast, i)) {
    long({ slPct: 2, tpPct: 4, comment: "pullback bought" });
  }
}

Ошибки и как их исправить

СообщениеПричина и решение
the script needs a function onBar(i) { … }В скрипте нет onBar, или он написан с ошибкой (onbar). Пишите function onBar(i) { … } на верхнем уровне скрипта, а не внутри другой функции.
close[124] is in the future: inside onBar(123) only candles up to 123 are knownВы прочитали более позднюю свечу. Используйте close[i] и более ранние (close[i - 1]). Для пробоя сравнивайте сегодняшнее закрытие со вчерашним наивысшим максимумом: close[i] > hi[i - 1].
close cannot be changedСвечи доступны только для чтения. Создайте свой массив: const mid = close.map((c, i) => (c + open[i]) / 2);
Cannot read properties of null / NaN в результатахВ начале ряд ещё равен null (разогрев). Начинайте onBar с if (avg[i] == null) return; и проверяйте i >= 1 перед использованием [i - 1].
unknown indicator "…"Используйте id из таблицы индикаторов (строчными буквами), например ind("adx"), а не ind("ADX").
Нет сделок: стратегия ни разу не выставила ордерУсловия ни разу не выполнились одновременно. Добавьте log("rsi", r[i]), чтобы увидеть значения, ослабьте условие или загрузите больше свечей.
Стратегия выполнялась дольше 15 секунд (бесконечный цикл?)Бесконечный цикл или тяжёлый цикл внутри onBar. Рассчитывайте ряды один раз в начале, а не перебирайте все свечи на каждой свече.
Unexpected token / SyntaxError (строка N)Опечатка в этой строке: пропущена скобка, кавычка или запятая.

Тесты, которым можно доверять

  • Учитывайте издержки. Стратегия с частыми сделками может выглядеть отлично без издержек и терять деньги при реалистичном спреде. Всегда указывайте проскальзывание (спред) и комиссию.
  • Используйте достаточно сделок. При менее чем ~30 сделках цифры в основном случайны. Загрузите больше свечей или используйте меньший таймфрейм.
  • Не подгоняйте под прошлое. Перебор параметров до идеального результата находит шум этого периода, а не работающее правило. Настраивайте на одной части истории, затем проверяйте те же параметры на другом периоде и нескольких других тикерах.
  • Сравнивайте с «купи и держи» и смотрите на просадку, а не только на прибыль.
  • Следите за выбросами. Если одна-две сделки дают всю прибыль, стратегия зависит от везения.
  • Практикуйтесь, прежде чем рисковать деньгами. Прогоните стратегию вперёд в демо-торговле или с воспроизведением баров и поставьте оповещения на её сигналы.

Бэктесты используют прошлые свечи bid и не учитывают свопы, финансирование и спреды, меняющиеся вместе с рынком. Прошлые результаты не гарантируют будущих. Это не инвестиционная рекомендация.

Сохранение, публикация, отправка

  • Сохранить (после входа) хранит до 50 стратегий с параметрами и настройками; они появляются в списке на каждом графике.
  • Опубликуйте идею с графика и приложите стратегию: читатели смогут открыть ваш график и сами запустить стратегию с вашими параметрами. См. Идеи.
  • Поделитесь анализом с командой в клубе.
  • Скачать CSV во вкладке «Сделки» экспортирует сделки в таблицу.
  • Для более серьёзных исследований загрузите те же свечи через API цен (GET /bars) в Python, R или Excel.

Лимиты

Свечей на тестГости: свечи на графике. После входа: до 20,000 (тарифы Premium могут это увеличить; см. Premium).
Скрипт50 000 символов, 30 параметров, 12 линий на графике, 200 строк журнала, 5 000 сделок в списке, 15 секунд выполнения.
Сохранённые стратегии50 на аккаунт.
ПозицииОдна позиция за раз в тесте (лонг, шорт или без позиции); один тикер и таймфрейм на тест.

Вопросы

Нужно ли уметь программировать?

Нет. Встроенные стратегии работают без кода; вы меняете только их параметры. Если хочется большего, Редактировать копию даст рабочий скрипт, который можно менять по строке, а примеры выше охватывают типовые блоки.

Это похоже на Pine Script?

Идея та же (ряды, параметры, линии, ордера), язык — обычный JavaScript. Большинство простых стратегий на Pine переводятся построчно: ta.sma(close, 20) остаётся ta.sma(close, 20), strategy.entry("L", strategy.long) становится long(), а тело скрипта переходит в onBar(i) с [i] после каждого ряда.

Может ли стратегия торговать за меня?

Нет. Консоль тестирует только на прошлых свечах. Чтобы следовать стратегии вживую, используйте демо-торговлю и оповещения или вызывайте API цен из своей программы.

Почему мой результат отличается от результата на другой платформе?

Другие свечи (bid или средние цены, часы сессии брокера), исполнение по следующему открытию, а не по закрытию, издержки и правило «стоп раньше цели» — всё это меняет результаты. Сравнивайте сопоставимое.

Мой код приватен?

Да. Он выполняется в вашем браузере; в аккаунте хранится, только если вы нажмёте «Сохранить», а другие видят его, только если вы опубликуете его в идее.