Руководство · Тестер стратегий
Консоль бэктеста: тестирование торговых стратегий кодом
На каждом графике есть кнопка Стратегия. Она открывает консоль, которая прогоняет стратегию по прошлым свечам и показывает каждую сделку на графике, прибыль, просадку, долю выигрышей и кривую капитала в сравнении с «купи и держи». Начните со встроенной стратегии, затем напишите свою в несколько строк JavaScript.
Безопасно по устройству. Ваш скрипт выполняется только в вашем браузере, в песочнице без доступа к интернету: он может читать переданные ему свечи и ничего больше (ни ваш аккаунт, ни этот сайт, ни другие сайты). Ничего из написанного вами не выполняется на наших серверах.
Начало за минуту
- Откройте любой график, например EURUSD, и выберите таймфрейм для теста (H1, D1…).
- Нажмите Стратегия на панели инструментов графика. Консоль откроется под графиком.
- Выберите встроенную стратегию, например Пересечение скользящих средних. Она запускается сразу: стрелки на графике отмечают каждый вход и выход.
- Измените параметр (например, медленную среднюю с 30 на 50): тест перезапустится сам.
- Смотрите Обзор для итогов, Сделки для каждой сделки, Кривая капитала для счёта во времени.
- Нажмите Редактировать копию, чтобы превратить её в свой скрипт, измените и нажмите Запустить тест (или 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(), когда лонг уже открыт, только обновляет переданные стопы. - Издержки на каждом исполнении. Проскальзывание сдвигает каждую цену исполнения против вас; комиссия берётся при входе и выходе.
- Конец. Сделка, открытая после последней свечи, закрывается по последнему закрытию и помечается как «ещё открыта в конце».
Пишем свою стратегию
Стратегия — это короткая программа на JavaScript из двух частей:
- Начало выполняется один раз: считайте параметры через
input(), рассчитайте целые ряды черезta.*илиind()и нарисуйте их черезplot(). 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 | Индикатор | Параметры (по умолчанию) | Линии |
|---|---|---|---|
ma | Moving Average | period: 20, method: SMA, price: close, shift: 0 | MA |
ama | Adaptive Moving Average | period: 9, fast: 2, slow: 30, price: close | AMA |
adx | Average Directional Movement Index | period: 14 | ADX, +DI, −DI |
adxw | ADX Wilder | period: 14 | ADX, +DI, −DI |
bb | Bollinger Bands | period: 20, dev: 2, price: close, shift: 0 | Middle, Upper, Lower |
dema | Double Exponential Moving Average | period: 14, price: close | DEMA |
env | Envelopes | period: 14, method: SMA, dev: 0.1, price: close | Upper, Lower |
frama | Fractal Adaptive Moving Average | period: 14, price: close | FrAMA |
ichimoku | Ichimoku Kinko Hyo | tenkan: 9, kijun: 26, senkou: 52 | Tenkan, Kijun, Span A, Span B, Chikou |
sar | Parabolic SAR | step: 0.02, max: 0.2 | SAR |
stddev | Standard Deviation | period: 20, method: SMA, price: close | StdDev |
tema | Triple Exponential Moving Average | period: 14, price: close | TEMA |
vidya | Variable Index Dynamic Average | cmo: 9, ema: 12, price: close | VIDYA |
atr | Average True Range | period: 14 | ATR |
bears | Bears Power | period: 13 | Bears |
bulls | Bulls Power | period: 13 | Bulls |
cci | Commodity Channel Index | period: 14, price: typical | CCI |
chaikin | Chaikin Oscillator | fast: 3, slow: 10, method: EMA | CHO |
demarker | DeMarker | period: 14 | DeMarker |
force | Force Index | period: 13, method: SMA | Force |
macd | MACD | fast: 12, slow: 26, signal: 9, price: close | MACD, Signal |
momentum | Momentum | period: 14, price: close | Momentum |
osma | Moving Average of Oscillator | fast: 12, slow: 26, signal: 9, price: close | OsMA |
rsi | Relative Strength Index | period: 14, price: close | RSI |
rvi | Relative Vigor Index | period: 10 | RVI, Signal |
stoch | Stochastic Oscillator | k: 5, d: 3, slowing: 3, method: SMA | %K, %D |
trix | Triple Exponential Average | period: 14, price: close | TRIX |
wpr | Williams' Percent Range | period: 14 | %R |
ad | Accumulation/Distribution | нет | A/D |
mfi | Money Flow Index | period: 14 | MFI |
obv | On Balance Volume | нет | OBV |
volumes | Volumes | нет | Volume |
ac | Accelerator Oscillator | нет | AC |
alligator | Alligator | jaw: 13, jawShift: 8, teeth: 8, teethShift: 5, lips: 5, lipsShift: 3 | Jaw, Teeth, Lips |
ao | Awesome Oscillator | нет | AO |
fractals | Fractals | нет | Up, Down |
gator | Gator Oscillator | jaw: 13, teeth: 8, lips: 5 | Upper, Lower |
bwmfi | Market Facilitation Index | нет | BW MFI |
zigzag | ZigZag | depth: 12, dev: 0.3 | ZigZag |
vwap | VWAP (volume-weighted average price) | нет | VWAP |
supertrend | SuperTrend | period: 10, mult: 3 | Up trend, Down trend |
keltner | Keltner Channels | period: 20, atr: 10, mult: 2 | Middle, Upper, Lower |
donchian | Donchian Channels | period: 20 | Upper, Lower, Middle |
hma | Hull Moving Average | period: 20, price: close | HMA |
pivots | Pivot Points (classic) | нет | P, R1, S1, R2, S2, R3, S3 |
stochrsi | Stochastic RSI | rsi: 14, stoch: 14, k: 3, d: 3 | %K, %D |
aroon | Aroon | period: 25 | Aroon up, Aroon down |
cmf | Chaikin Money Flow | period: 20 | CMF |
uo | Ultimate Oscillator | p1: 7, p2: 14, p3: 28 | UO |
chop | Choppiness Index | period: 14 | CHOP |
vwma | Volume-Weighted Moving Average | period: 20 | VWMA |
zlema | Zero-Lag Exponential Moving Average | period: 21, price: close | ZLEMA |
alma | Arnaud Legoux Moving Average | period: 9, offset: 0.85, sigma: 6, price: close | ALMA |
mcginley | McGinley Dynamic | period: 14 | McGinley |
lsma | Least Squares Moving Average | period: 25, price: close | LSMA |
chandelier | Chandelier Exit | period: 22, mult: 3 | Long exit, Short exit |
roc | Rate of Change | period: 12 | ROC |
cmo | Chande Momentum Oscillator | period: 9 | CMO |
tsi | True Strength Index | long: 25, short: 13, signal: 13 | TSI, Signal |
ppo | Percentage Price Oscillator | fast: 12, slow: 26, signal: 9 | Histogram, PPO, Signal |
dpo | Detrended Price Oscillator | period: 20 | DPO |
kst | Know Sure Thing | signal: 9 | KST, Signal |
vortex | Vortex Indicator | period: 14 | VI+, VI− |
eom | Ease of Movement | period: 14 | EOM |
mass | Mass Index | ema: 9, sum: 25 | Mass Index |
coppock | Coppock Curve | wma: 10, long: 14, short: 11 | Coppock |
hv | Historical Volatility | period: 20 | HV % |
fisher | Fisher Transform | period: 9 | Fisher, Trigger |
squeeze | Squeeze Momentum | bb: 20, bbk: 2, kc: 20, kck: 1.5 | Momentum, Squeeze on, Squeeze off |
crsi | Connors RSI | rsi: 3, streak: 2, rank: 100 | CRSI |
pvt | Price 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 или средние цены, часы сессии брокера), исполнение по следующему открытию, а не по закрытию, издержки и правило «стоп раньше цели» — всё это меняет результаты. Сравнивайте сопоставимое.
Мой код приватен?
Да. Он выполняется в вашем браузере; в аккаунте хранится, только если вы нажмёте «Сохранить», а другие видят его, только если вы опубликуете его в идее.