Стратегия приводит к нереалистично хорошим результатам путём заглядывания в будущее

Одна из наших основных целей при разработке языка Pine - предоставить пользователям как можно больше полезных инструментов. Эти инструменты могут иметь множество различных применений, и при определенных манипуляциях некоторые индикаторы и типы графиков позволяют получать в коде данные будущих баров или сделок (относительно текущего обрабатываемого бара). Поскольку трейдер не может получать эти данные в реальной торговле, стратегии, построенные при помощи таких методов, могут давать нереалистично прибыльные результаты при тестировании на исторических данных, тогда как при торговле в реальном времени эти сделки будут убыточными.

Некоторые пользователи TradingView по незнанию или со злым умыслом создают идеи и публикуют сценарии, используя эти методы. TradingView не может удалить их, потому что в некоторых случаях данные функции могут быть полезными, но в то же время мы стремимся предупреждать пользователей о таком поведении.

Стратегии на графиках с Японскими типами свечей 

Очень распространенной причиной такого поведения является тестирование стратегии на графиках с японскими типами свечей (Ренко, Каги и т. Д.). Проблема возникает из-за того, что механизм тестирования стратегии рассматривает каждый бар как 4 транзакции с ценами открытия, максимума, минимума и закрытия (как в случае с обычным свечным графиком). Из-за этого на графике Ренко механизм тестирования стратегии может входить/выходить из позиции по цене, которой в действительности не существовало. Кроме того, если вы установите значение Box Size меньше, чем минтик символа, можно проверить, будет ли следующая цена выше или ниже текущей, и войти / выйти из позиции заранее, прежде чем механизм тестирования на истории обработает реальную цену.

//@version=4
strategy("My Strategy", overlay=true)
if close < close[1]
    strategy.entry("ShortEntryId", strategy.short)
strategy.close("ShortEntryId", when = close > close[1])

if close > close[1]
    strategy.entry("LongEntryId", strategy.long)
strategy.close("LongEntryId", when = close < close[1])

Как видно на скриншоте, эта простая стратегия позволяет заключать сделки по ценам, очень близким к максимальным/минимальным.

Стратегии с использованием параметра calc_on_order_fills = true

Если в функции стратегии указан параметр calc_on_order_fills = true, механизм расчета стратегии выполняет дополнительный расчет внутри бара после исполнения ордера (в отличие от обычной ситуации, когда стратегия рассчитывается только при закрытии бара). При этом в процессе расчета стратегия получает доступ ко многим дополнительным параметрам бара, например, максимальным и минимальным значениям. Это позволяет вам написать стратегию, которая покажет отличную производительность при тестировании на истории:

//@version=4
strategy("CalcOnOrderFillsStrategy", overlay=true, calc_on_order_fills=true)

// a variable is used to prevent double entry on the same bar
var lastTimeEntry = 0

longCondition = close > sma(close, 14)  and lastTimeEntry != time
if longCondition
    strategy.entry("LongEntryId", strategy.long)

strategy.exit("exitId", "LongEntryId", limit=high)
lastTimeEntry := time

На скриншоте видно, что вход происходит по цене открытия бара, а выход происходит по максимуму того же бара. То есть во время расчета после исполнения ордера мы устанавливаем лимитную цену strategy.exit равной верхнему уровню текущего бара, чего мы не можем сделать в реальной торговле.

Параметр lookahead = barmerge.lookahead_on в функции security() и все security() до Pine v3

Функция security в Pine позволяет запрашивать данные с других символов и/или таймфреймов. В зависимости от реализации это может позволить стратегии получать данные из будущего: если кто-то, например, запрашивает закрытие или максимум дневного бара, при тестировании на истории стратегия может знать эти значения прямо в начале дня.


До версии 3 функция security() возвращала значение из старшего таймфрейма еще до того, как оно должно было быть доступно. В версии 3 это поведение было исправлено, но для совместимости в функцию security() был добавлен параметр lookahead. По умолчанию он имеет значение false (т.е. заглядывание в будущее выключено), но вы можете включить его, установив значение параметра lookahead равным barmerge.lookahead_on).


Пример прибыльной стратегии, построенной с использованием этой функции:

//@version=4
strategy("My Strategy", overlay=true)
dayStart = security(syminfo.tickerid, "1D", time, lookahead=barmerge.lookahead_on)
dayHigh = security(syminfo.tickerid, "1D", high, lookahead=barmerge.lookahead_on)
dayLow = security(syminfo.tickerid, "1D", low, lookahead=barmerge.lookahead_on)

// entry at first bar of a day
if time == dayStart
    // distance to daily high is further, so we can earn more
    if abs(open - dayHigh) > abs(open - dayLow)
        strategy.entry("LongEntryId", strategy.long)
        strategy.exit("exitLongId", "LongEntryId", limit=dayHigh)
    else
        strategy.entry("ShortEntryId", strategy.short)
        strategy.exit("exitShortId", "ShortEntryId", limit=dayLow)
        
plot(dayHigh)
plot(dayLow)

На первом баре мы анализируем, будет ли цена двигаться дальше вверх или вниз относительно цены открытия, и на основании этого мы открываем длинную или короткую позицию, а затем выходим по максимальной или минимальной цене дня соответственно.

Обратите внимание, что не во всех случаях, когда security() имеет аргумент barmerge.lookahead_on, стратегия заглядывает в будущее: например, если бы мы изменили приведенный выше код, заменив time/high/low внутри security() на time[1]/ high[1]/low[1] соответственно, мы бы получили значения для уже закрытых баров. Это часто используется опытными кодерами для получения данных из security() без какого-либо риска заглядывания в будущее.

На данный момент это все известные способы стратегии заглянуть в будущее. Мы надеемся, что это описание позволит вам создавать стратегии, не имеющие этих недостатков, а также избегать опубликованных стратегий, авторы которых используют эти особенности в своих идеях.