Полезные и экзотические приемы для автоматической торговли

Evgeniy Ilin | 22 февраля, 2021

Содержание


Введение

Существует множество торговых приемов, которые по мнению создателей или общественности должны приносить прибыль. В данной статье я не буду рассматривать эти приемы, поскольку информации о них очень много на самых разнообразных ресурсах, я не смогу привнести ничего нового и интересного с таким подходом. Вместо этого я решил сделать статью, в которой будет собрано несколько полезных и нестандартных приемов которые были бы полезны как в теоретическом, так и в практическом плане. Часть из этих приемов возможно кому-то знакома, кому-то — нет, но я постараюсь привести самые интересные методы и объяснить, почему стоит ими пользоваться и, самое главное, покажу на практике, что они могут.


Алгоритм частичного закрытия позиций, использующий движение последнего бара

Теория:

Данный вопрос я хочу осветить как полезный торговый прием в том случае, если мы открылись и не уверены в направлении дальнейшего движения цены. Грамотное закрытие позиции по частям может скомпенсировать убытки от спредов, а при хорошей оптимизации — даже дать прибыль.

Начать, я думаю, стоит вот с таких соображений, что, предположим, мы не знаем где выходить, но мы уже вошли в рынок. Неважно в каком направлении мы вошли, нам все-равно придется когда-то выходить из позиции. Некоторые игроки, конечно, могут держать свои позиции годами, но мы предполагаем, что робот должен интенсивно торговать и обеспечивать высокую частоту трейдов. Можно выйти сразу всем объемом, а можно по чуть-чуть. Мы знаем, что структура рынка флетовая, а простым человеческим языком это значит, что цена стремится к возврату до начала текущей полуволны. Иначе говоря, движение вверх говорит о том, что вероятность продолжения всегда меньше 50%, а это в свою очередь значит, что вероятность противоположного движения больше 50%. Если использовать полное закрытие, то мы можем пропустить все волны и не снять с них прибыль, ведь мы не знаем какова будущая амплитуда волн. Частичное закрытие может избавить нас от этой мысли. Мы сможем снять максимум прибыли исходя из того факта, что нам неизвестен характер будущих волн.

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

Таких алгоритмов может быть много, но здесь я покажу самый простой и, на мой взгляд, самый действенный. Он эксплуатирует предположение о том, что чем больше и мощнее движение произошло на предыдущей полностью сформированной свече, то тем вероятнее, что будет довольно сильное откатное движение. Вообще говоря, зачем нужен этот механизм? Все элементарно. Для того чтобы повысить прибыль и уменьшить убытки. Импульсы на самом деле это тоже волны. Только волны эти гораздо более полезные, чем обычные классические волны в нашем понимании. Все дело в том, что достовернее те волны, которые ближе всего к рынку, и чем мощнее импульс и чем за менее короткое время он произошел, тем большая вероятность отката в ближайшее время и тем большая величина этого отката ожидается. Смысл в том, что нужно сделать так, чтобы с повышением мощности импульса частичное закрытие позиции было бы еще сильнее. Изображу сказанное на рисунке:

Partial Closing

Для того чтобы вычислить объем части позиции, которую нужно закрыть, можно использовать любую функцию. Я приведу 2 функции. Одна линейная, а вторая степенная:

Если внимательно посмотреть, то линейная функция является частным случаем степенной при "D" = 1, поэтому можно ее не рассматривать, я ее просто привел для примера, с чего начинается логика размышлений. Размышления сначала всегда простые, но потом, обдумав, мы получаем гораздо более универсальные инструменты, просто всегда нужно начинать с чего-то простого.

Для того чтобы напрямую не задавать эти коэффициенты и потом не думать, как же они повлияют, введем несколько управляющих параметров, которые и будут определять наши коэффициенты:

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

  1. { Lc( ) = StartLotsToOnePoint }
  2. { Lc( PointsForEndLots ) = EndLotsToOnePoint }

Вот и все наши уравнения готовы, теперь напишем их в развернутом виде и начнем решать, постепенно преобразуя их:

  1. { C * Pow(1 , D) = StartLotsToOnePoint }
  2. { C * Pow( PointsForEndLots  , D) = EndLotsToOnePoint }

Из первого уравнения сразу можно найти "C", учитывая что единица в любой степени равна сама себе:

Разделив обе части второго уравнения на "C" и после чего взяв логарифм от обеих частей уравнения по основанию "PointsForEndLots", получим следующее:

Учитывая, что логарифм от основания того же самого логарифма, возведенного в любую степень равен этой самой степени, теперь можем разрешить наше уравнение относительно искомой степени:

Второй неизвестный коэффициент мы нашли, но это еще не все, потому что в MQL4 и MQL5 нет базовой функции, которая реализует логарифм по любому основанию, которое нам нужно, существует только натуральный логарифм. Придется сменить основание нашего логарифма на натуральный логарифм (натуральный логарифм — это логарифм с основанием в виде числа Эйлера). Само по себе отсутствие других логарифмов в языке это не проблема, ведь всегда можно выразить любой логарифм через натуральный логарифм. Для человека, который хотя бы немного разбирается в математике, это не будет представлять никаких проблем. После смены основания формула для нашего искомого коэффициента приобретет вот такой вид:

Подставив туда уже известный коэффициент "C", получим:

Теперь можно подставить оба коэффициента в шаблон нашей функции и получить ее окончательный вид:

Плюсом данной функции будет являться то, что степень может быть как равна единице, так и быть больше единицы или меньше единицы, тем самым обеспечивая максимальную гибкость подстройки под любой рынок и торговый инструмент. В случае, если D = 1, мы получаем нашу линейную функцию, если D > 1, то функция настроена на предположение о том, что все волны масштабируемы и количество волн определенной амплитуды обратно пропорционально амплитуде (то есть если взять и посчитать количество волн скажем на M5 и H1 за один и тот же временной отрезок, то окажется, что на H1 таких волн в 12 раз меньше просто потому, что часовых свечей в 12 раз меньше чем пятиминутных). Если  D < 1, мы рассчитываем на то, что у нас больше амплитудных волн, чем маленьких, а когда D > 1, мы предполагаем, что волны преимущественно низкоамплитудные. 

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

Код:

В коде данная функция будет выглядеть вот так:

double CalcCloseLots(double orderlots0,double X)
   {
   double functionvalue;
   double correctedlots;
   if ( X < 0.0 ) return 0.0;
   functionvalue=StartLotsToOnePoint*MathPow(X ,MathLog(EndLotsToOnePoint/StartLotsToOnePoint)/MathLog(PointsForEndLots));
   correctedlots=GetLotAniError(functionvalue);
   if ( correctedlots > orderlots0 ) return orderlots0;
   else return correctedlots;
   }

Пурпурным выделена функция, корректирующая лоты, чтобы они принимали только корректное значение, ее внутренности смысла показывать нет. Сама функция вычисляется под оператором выделенным зеленым цветом, но является частью более общей функции, которую будем вызывать вот здесь:

void PartialCloseType()//частично закрыть ордер
   {
   bool ord;
   double ValidLot;
   MqlTick TickS;
   SymbolInfoTick(_Symbol,TickS);
            
   for ( int i=0; i<OrdersTotal(); i++ )
      {
      ord=OrderSelect( i, SELECT_BY_POS, MODE_TRADES );
                            
      if ( ord && OrderMagicNumber() == MagicF && OrderSymbol() == _Symbol )
         {
         if ( OrderType() == OP_BUY )
            {
            ValidLot=CalcCloseLots(OrderLots(),(Open[0]-Open[1])/_Point);
            if ( ValidLot > 0.0 ) ord=OrderClose(OrderTicket(),ValidLot,TickS.bid,MathAbs(SlippageMaxClose),Green);
            }
         if ( OrderType() == OP_SELL )
            {
            ValidLot=CalcCloseLots(OrderLots(),(Open[1]-Open[0])/_Point);
            if ( ValidLot > 0.0 ) ord=OrderClose(OrderTicket(),ValidLot,TickS.ask,MathAbs(SlippageMaxClose),Red);
            }         
         break;
         }
      }

Это скомпилируется и в MQL5, учитывая что мы будем использовать библиотеку MT4Orders. Хочу только уточнить, что данные функции годятся для теста, а для реальной торговли нужно будет тщательно исследовать их и дорабатывать на предмет ошибок и неучтенных случаев, но для проверки в тестере стратегий это все вполне пригодно.

Тестирование советника:

Чтобы продемонстрировать работу, я написал версию советника для MetaTrader 4, потому как при тестировании в MetaTrader 5 спред скроет все, что мы хотим увидеть. Вместо этого я взял спред "1" и открытие позиций в случайном направлении:

USDCHF M5 2000-2021 Backtest

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


Алгоритм гибридной вариации лота

Еще в начале исследования рынка у меня была мысль о том, как сдвинуть профит-фактор любой системы в сторону плюсовых значений. Ведь по сути это будет означать, что любая стратегия, которая не так сильно зависит от спреда, получит очень большое преимущество по сравнению с любыми другими, которые имеют профит фактор близкий к единице. Почему именно профит-фактор нужно усиливать? Вопрос очень прост — потому что от этого показателя зависит как относительная просадка всей системы в целом, так и прибыльность системы. Подобный механизм существует, я вам его покажу. Но сначала давайте быстро пробежимся по самым известным торговым приемам, которые могут использовать манипуляции  с лотами. Существуют следующие мнимые приемы усиления показателей профит фактора:

На самом деле все сводится к двум приемам, которые все до сих пор мусолят, но так и не могут получить ничего, кроме разочарования. Все дело в очень простом математическом принципе, который я приводил в статье "Сетка и мартингейл: что это такое и как их использовать?". Там описаны не столько все принципы, сколько то общее, что есть во всех них. Общее — это либо понижение, либо повышение лота в зависимости от каких-то условий. Если условия не имеют ничего общего с характером ценообразования, то такая система заведомо убыточна.

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

Hybrid variaton

Теория:

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

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

Как же вычислять лоты в данном случае? Ситуация, на первый взгляд, непонятная. Очевидно, что лоты могут дойти до одной из границ нашего канала и никогда не выйти оттуда, для этого мы должны предусмотреть механизм возврата к первоначальному лоту, для того чтобы получались стабильные колебания возле среднего значения лотов. Очевидным и единственным минусом подобной системы является непереносимость больших амплитудных волн. На "пиле" наш механизм будет отрабатывать идеально и радовать глаз. Я решил данную задачу, организовав функцию подсчета следующего лота так, что она сама выталкивает лоты к середине тем сильнее, чем ближе лоты предыдущего закрытого ордера к одной из границ. Сейчас я проиллюстрирую сказанное на рисунке:

Lots

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

Входные переменные для управления гибридной вариацией лота

Вспомогательные переменные

Функция для случая когда лоты выше или ниже средней линии

Довольно сложно переварить эти соотношения у себя в голове, поскольку они тяжело читаются, но в данном случае, если пытаться все объединить в непрерывную функцию, то получится такая сложная конструкция, с которой невозможно будет работать, если вообще это получится. Ниже я покажу, как это все будет выглядеть в коде и все станет понятнее. Я приведу реализацию кода в стиле MQL4, но если использовать одну очень удобную и знаменитую библиотеку MT4Orders, то данный код скомпилируется и будет работать в MQL5:

Код:

double CalcMultiplierLot()
   {
   bool ord;
   double templot=(MaxLot+MinLot)/2.0;
   for ( int i=OrdersHistoryTotal()-1; i>=0; i-- )
      {
      ord=OrderSelect( i, SELECT_BY_POS, MODE_HISTORY );
      if ( ord && OrderSymbol() == CurrentSymbol &&  OrderMagicNumber() == MagicF )
         {
         double PointsProfit=(OrderProfit()+OrderCommission()+OrderSwap())/(OrderLots()*MarketInfo(CurrentSymbol,MODE_TICKVALUE));
         if ( OrderLots() >= (MaxLot+MinLot)/2.0 )
            {
            if ( PointsProfit < 0.0 ) templot=OrderLots()-(LotForMultiplier*(PointsProfit/ProfitForMultiplier))*((MaxLot-OrderLots())/((MaxLot-MinLot)/2.0));
            if ( PointsProfit > 0.0 ) templot=OrderLots()-(LotForMultiplier*(PointsProfit/ProfitForMultiplier))/((MaxLot-OrderLots())/((MaxLot-MinLot)/2.0)) ;
            if ( PointsProfit == 0.0 ) templot=OrderLots();
            break;
            }
         else
            {
            if ( PointsProfit > 0.0 ) templot=OrderLots()-(LotForMultiplier*(PointsProfit/ProfitForMultiplier))*((OrderLots()-MinLot)/((MaxLot-MinLot)/2.0));
            if ( PointsProfit < 0.0 ) templot=OrderLots()-(LotForMultiplier*(PointsProfit/ProfitForMultiplier))/((Orderlots()-MinLot)/((MaxLot-MinLot)/2.0));
            if ( PointsProfit == 0.0 ) templot=OrderLots();
            break;
            }
         }
      }
   if ( templot <= MinLot ) templot=(MaxLot+MinLot)/2.0;
   if ( templot >= MaxLot ) templot=(MaxLot+MinLot)/2.0;
         
   return templot;
   }

Последнее время распробовал эту библиотеку, особенно она полезна там, где так нужно контролировать каждый открытый ордер. Вот собственно и все премудрости данного подхода. Желтым выделены входные переменные алгоритма.

Тестирование советника:

Этот советник я тоже сделал для MetaTrader 4 по той же причине, что и предыдущий советник, потому что проще будет увидеть улучшение профит-фактора:

USDCHF M5 2020-2021 Backtest

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


Принцип усреднения

Принцип усреднения — это довольно интересный алгоритм, хоть и убыточный как сетка мартингейл и пирамидинг, но в нем есть одна очень интересная особенность, которая может помочь в волновой торговле. Если известно, что движение продолжится до определенного уровня и потом обязательно будет отскок на определенное количество пунктов, то в данном случае можно применять усреднение с мартингейлом. Особенно хорошо этот прием работает именно с мартингейлом, потому что мартингейл позволяет уменьшить величину требуемого отката, при этом сохраняя профит-фактор больше, чем 1.0. 

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

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

Пример для Buy серии ордеров:

Buy

Пример для Sell серии ордеров:

Sell

Сейчас я покажу, как считается прибыль или убыток всех ордеров и как, исходя из имеющейся ситуации, посчитать лот следующего ордера, исходя из требуемого отката. Для того чтобы закрыть веер позиций, необходимо знать, зачем вообще строить этот веер и какое условие делает его полезным. Я бы использовал "профит-фактор". Если веер в плюсе, то и его профит-фактор в плюсе. Глупо было бы закрывать его, как только суммарная прибыль всех ордеров будет плюсовой, ведь любое колебание цены на сервере или проскальзывание может сдвинуть прибыль в отрицательную сторону, а тогда какой смысл вообще строить такие веера, не правда ли. Кроме того, профит-фактор цикла даст возможность дополнительной регулировки агрессивности нашего усреднения, которую можно будет использовать при оптимизации автоматической стратегии, либо ручном поиске настроек.

Предположим, что есть ограничение на максимальное количество ордеров в серии, и после этого предположим, что мы попали на какую-то неизвестную ситуацию, где у нас уже открыто "k" ордеров, их индексы начинаются с "0", как и в любых коллекциях или массивах. Получается, что индекс последнего ордера в серии будет "k-1", соответственно, когда мы откроем следующий ордер его индекс будет "k" и количество ордеров станет "k+1". Полностью формализуем наши умозаключения во входные данные задачи:

Дополнительно можно сказать, что языки MQL4 и MQL5 дают нам следующие данные при обращении к конкретному ордеру, которые понадобятся нам для того, чтобы описать все варианты расчета:

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

Если первый ордер закрылся в плюс, то ничего не делаем и в любой момент, который считаем нужным, открываем новый ордер в нужном направлении. Если цена не дошла до мишени и пошла в противоположную сторону, то мы должны решить, сколько пунктов цена должна уехать в убыточном направлении, чтобы открыть следующий ордер (S[k-1]). Если цена доехала до этого уровня, то мы должны решить, какой откат нам подходит (D[k]). После этого мы должны определить лот ордера, который сейчас откроем так, чтобы при запланированном откате получить требуемый профит-фактор всех ордеров больше, чем выбранное нами значение (ProfitFactorMax). Для этого сначала нужно посчитать, какую прибыль дадут уже открытые ордера, которые уже висят, а также какой убыток и суммарное их значение, для чего объясню после того, как напишу соответствующие формулы:

Сначала введем величину профита в будущем для каждого конкретного ордера, она складывается из текущего профита и прибавки, которую получит ордер при условии, что мы получим требуемый откат:

В первом случае величина P[i] известна и мы можем получить ее с помощью встроенных функций языка, а можем и посчитать сами. Как посчитать, я покажу в конце как дополнение, поскольку средства языка позволяют обойтись и без этого. Что касается последнего ордера в серии, который мы хотим открыть, по идее туда нужно добавить комиссию и своп, но комиссию можно получить только у того ордера, который уже открыт, а мы только собираемся его открыть. Да и величину свопа тоже не сможем определить, так как не знаем перешагнет ли вообще позиция через 0:00 или нет. Погрешности всегда будут. Можно выбирать только те пары, у которых есть положительные свопы 

После чего мы сможем использовать данный прогнозируемый профит для того, чтобы разделить ордера с прибылью и убытком, потому что только так можно посчитать профит фактор, который они дадут в итоге:

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

Теперь нужно написать условие закрытия цикла:

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

Подставив эту величину в уравнение получим:

Теперь разрешив это уравнение относительно PF[k] получим:

Учитывая, что у нас уже есть формула для величины  PF[k], то мы можем подставить это выражение сюда и получить:

Теперь мы можем разрешить это уравнение относительно L[k] и получить наконец формулу для вычисления искомого объема позиции, которую мы хотим открыть:

Вот собственно и все. Теперь рассмотрим как же вычислить величину P[i], если это делать без использования встроенных функций.

Вычисление величины отката

Теперь рассмотрим способы вычисления отката. Я использую 2 способа. В целом их можно придумать великое множество, но я приведу два самых полезных, на мой взгляд. Откат вычисляется относительно того, насколько сильно цена ушла не в нашу сторону. То есть D[i] и X[i] могут быть связаны любой функцией, которая положительна на всей положительной оси "X":

Я использую две функции для вычисления отката. Первая — это линейная. Вторая степенная. Вторая сложнее, но интереснее. Таков вид этих функций:

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

  1. { K } - коэффициент отката для линейной функции (он же используется как входной параметр для автоматической системы) 
  2. { DMin } - минимально допустимый откат (D >= DMin, в случае степенной функции)
  3. { С } - коэффициент масштабирования степенной функции
  4. { S } - основание возведения в степень

Строго говоря "C", можно внести в степень, но, я думаю все согласятся, что в таком виде функция более читаема и удобна для использования. Следует отметить также, что величины  "K" и "DMin" являются входными параметрами и эти коэффициенты известны нам сразу. Не понятно только, как вычислить два оставшихся коэффициента. Этим сейчас и займемся. Для того чтобы найти 2 неизвестных, требуется как минимум система из двух уравнений. Решив систему, получим искомые коэффициенты. Чтобы написать такую систему сначала нужно определиться, как мы будем управлять видом нашей функции. На самом деле текущий вид степенной функции был выбран мной как раз исходя из того, как бы проще и удобнее сделать плавное уменьшение отката. Выбор пал в итоге на степенную функцию. Соображения были следующие:

  1. { HalfX } - движение цены для половины добавочного отката (дополнительный входной параметр для управления функцией)
  2. { D(0) = DMinK*DMin }
  3. { D(HalfX) = DMin K*DMin/2 }

Таким образом мы получаем искомую систему уравнений, которую и будем решать. Иначе говоря, мы задаем движение цены в сторону убытка относительно первого открытого ордера, при котором добавка к откату принимает половину значения при старте. А на старте эта добавка имеет максимальное значение. В итоге мы получаем функцию, которая не может принимать значение меньшее минимального отката, и при стремлении к бесконечности она стремится к минимальному откату. Математически это все запишется так:

Теперь можно приступать к решению нашей системы уравнений, но сначала перепишем ее в полном виде:

  1. DMin + C * Pow(S , 0) = DMin K*DMin
  2. { DMin + C * Pow(S , HalfX) =  DMin K*DMin/2 }

Из первого уравнения можно сразу же найти "C", учитывая тот факт что любое число в нулевой степени превращается в единицу, тем самым исключается переменная "S". Все что нам остается, это разрешить уравнение относительно переменной "C"

Теперь когда у нас есть "C", мы можем найти оставшуюся неизвестную "S", просто подставив предыдущее выражение вместо переменной "C":

Для того чтобы избавиться от степени, мы должны возвести обе части уравнения в степень, обратную величине "HalfX", и получим в итоге вот такое простое выражение, которое и будет являться искомым коэффициентом:

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

Вот так эта функция будет выглядеть в коде:

Код:

double D(double D0,double K0,double H0,double X)
   {
   return D0+(D0*K0)*MathPow(MathPow(0.5,1.0/H0),X);
   }

Приведу еще несколько важных функций, которые будут в советнике для проверки теории. Начну с функции, которая определяет расстояние в пунктах от цены до ближайшего открытого ордера серии:

double CalculateTranslation()
   {
   bool ord;
   bool bStartDirection;
   bool bFind;
   double ExtremumPrice=0.0;
   
   for ( int i=0; i<OrdersTotal(); i++ )
      {
      ord=OrderSelect( i, SELECT_BY_POS, MODE_TRADES );
      if ( ord && OrderMagicNumber() == MagicF && OrderSymbol() == Symbol() )
         {
         if ( OrderType() == OP_SELL ) bStartDirection=false;
         if ( OrderType() == OP_BUY ) bStartDirection=true;
         ExtremumPrice=OrderOpenPrice();
         bFind=true;
         break;
         }
      }      
   for ( int i=0; i<OrdersTotal(); i++ )
      {
      ord=OrderSelect( i, SELECT_BY_POS, MODE_TRADES );
                        
      if ( ord && OrderMagicNumber() == MagicF && OrderSymbol() == Symbol() )
         {
         if ( OrderType() == OP_SELL && OrderOpenPrice() > ExtremumPrice ) ExtremumPrice=OrderOpenPrice();
         if ( OrderType() == OP_BUY && OrderOpenPrice() < ExtremumPrice ) ExtremumPrice=OrderOpenPrice();
         }
      }
   if ( bFind )
      {
      if ( bStartDirection ) return (ExtremumPrice-Close[0])/_Point;
      else return (Close[0]-ExtremumPrice)/_Point;      
      }   
   else return -1.0;
   }

Эта функция будет нужна для того, чтобы соблюсти требуемый шаг между ордерами серии, он у нас будет фиксированный, при этом нужно не забыть что "Close[]" массив не реализован в MQL5 и его нужно реализовать самостоятельно, как я показывал в других своих статьях. Не думаю что у кого-то с этим возникнут какие то проблемы.,

Для того чтобы вычислить текущие "X" и "D", будем использовать следующую функцию, которая как все следующие за ней не будут иметь возвращаемых значений, а будут записывать результат в глобальные переменные (поскольку с ордерами лучше работать по минимуму и лишний раз не вызывать те функции, которые работают с историей):

double Xx;//смещение X
double Dd;//желаемый откат D
void CalcXD()//посчитать текущий X и D
   {
   bool ord;
   bool bStartDirection=false;
   bool bFind=false;
   double ExtremumPrice=0.0;
   
   for ( int i=0; i<OrdersTotal(); i++ )
      {
      ord=OrderSelect( i, SELECT_BY_POS, MODE_TRADES );
      if ( ord && OrderMagicNumber() == MagicF && OrderSymbol() == Symbol() )
         {
         if ( OrderType() == OP_SELL ) bStartDirection=false;
         if ( OrderType() == OP_BUY ) bStartDirection=true;
         ExtremumPrice=OrderOpenPrice();
         bFind=true;
         break;
         }
      }   
   
   for ( int i=0; i<OrdersTotal(); i++ )
      {
      ord=OrderSelect( i, SELECT_BY_POS, MODE_TRADES );
                        
      if ( OrderMagicNumber() == MagicF && OrderSymbol() == Symbol() )
         {
         if ( OrderType() == OP_SELL && OrderOpenPrice() < ExtremumPrice ) ExtremumPrice=OrderOpenPrice();
         if ( OrderType() == OP_BUY && OrderOpenPrice() > ExtremumPrice ) ExtremumPrice=OrderOpenPrice();
         }
      }
   Xx=0.0;
   Dd=0.0;   
   if ( bFind )
      {
      if ( !bStartDirection ) Xx=(Close[0]-ExtremumPrice)/_Point;
      if ( bStartDirection ) Xx=(ExtremumPrice-Close[0])/_Point;
      if ( MODEE==MODE_SINGULARITY ) Dd=D(DE,KE,XE,Xx);
      else Dd=Xx*KE;
      }      
   }

Опять же, весь этот код находится в полном согласии с библиотекой "MT4Orders" и скомпилируется в MQL5 без проблем, как и те функции, которые будут приведены ниже. Желтым выделены входные переменные алгоритма.

Для вычисления текущего и прогнозируемого профита будем использовать 3 переменные:

double TotalProfitPoints=0.0;   
double TotalProfit=0;
double TotalLoss=0;   

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

Вот так будут выглядеть две функции, одна пригодится для условия закрытия, а вторая для вычисления прогнозируемого профита уже висящих позиций:

void CalcLP()//посчитать убытки или прибыль всех имеющихся ордеров
   {
   bool ord;
   double TempProfit=0.0;
   TotalProfit=0.0;   
   TotalLoss=0.0;    
   TotalProfitPoints=0.0;
   
   for ( int i=0; i<OrdersTotal(); i++ )
      {
      ord=OrderSelect( i, SELECT_BY_POS, MODE_TRADES );
                            
      if ( ord && OrderMagicNumber() == MagicF && OrderSymbol() == Symbol() )
         {
         TempProfit=OrderProfit()+OrderCommission()+OrderSwap();
         TotalProfitPoints+=(OrderProfit()+OrderCommission()+OrderSwap())/(OrderLots()*SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_VALUE));
         if ( TempProfit >= 0.0 ) TotalProfit+=TempProfit;
         else TotalLoss-=TempProfit;
         }
      }
   }
   
void CalcLPFuture()//посчитать убытки или прибыль всех имеющихся ордеров в будущем
   {
   bool ord;
   double TempProfit=0;
   TotalProfit=0;   
   TotalLoss=0;    
    
   for ( int i=0; i<OrdersTotal(); i++ )
      {
      ord=OrderSelect( i, SELECT_BY_POS, MODE_TRADES );
                            
      if ( ord && OrderMagicNumber() == MagicF && OrderSymbol() == Symbol() )
         {
         TempProfit=OrderProfit()+OrderCommission()+OrderSwap()+(OrderLots()*SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_VALUE)*Dd);
         if ( TempProfit >= 0.0 ) TotalProfit+=TempProfit;
         else TotalLoss-=TempProfit;
         }
      }
   }

Также стоит показать функцию-предикат для условия закрытия серии ордеров:

bool bClose()
   {
   CalcLP();
   if ( TotalLoss != 0.0 && TotalProfit/TotalLoss >= ProfitFactorMin ) return true;
   if ( TotalLoss == 0.0 && TotalProfitPoints >= DE*KE ) return true;
   return false;
   }

Тестирование советника:

Советник по этому принципу я сделал для MetaTrader 5, поскольку данный принцип при грамотном применении может перебороть практически любые спреды, комиссии и свопы. При тестировании советника с усреднением получается примерно вот такая картинка:

USDJPY M1 2019-2020 Backtest

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


Прямые и косвенные признаки состоятельности торгового приема

Думаю, в рамках данной статьи полезно было бы рассмотреть, какие же математические показатели бектеста могут нам сигнализировать о том, что прием, который мы применили, усиливает нашу стратегию, либо убыточную стратегию превращают в прибыльную. Вопрос очень важен, поскольку при неправильной интерпретации результатов бектеста будет зависеть,  выбросим ли мы на самом деле рабочий прием или оставим ложный. При разработке новых торговых приемов я всегда руководствуюсь двумя правилами, которые создают необходимое и достаточное условие для максимально вероятного обнаружения рабочих приемов и максимально вероятного откидывания ложных:

  1. Размер выборки данных, как и количество трейдов в бектесте, для статистической оценки должен быть максимален
  2. Даже незначительное изменение показателей бектеста могут сигнализировать о положительных изменениях

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

  1. Такие торговые приемы работают на любом торговом инструменте
  2. Приемы можно комбинировать и делать гибриды
  3. При правильном применении ваши шансы на заработок становятся больше, чем на убыток даже при наличии спреда

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

Обратить прежде всего стоит на такие показатели как:

Дополнительными более точными показателями, которые могли бы помочь, но которых нет в отчетах тестера стратегий, являются:

Почему же эти показатели так точно показывают, насколько безопасна и прибыльна система? Все дело в том, что для любого бектеста существуют следующие математические тождества, как собственно и у любой торговой системы, которая торгует на демо или реальном счете:

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

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

Входные данные для синтетических показателей:

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

Синтетические покaзатели:

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


Использование волн баланса и эквити

Теория:

На самом деле данную механику можно использовать не только на мартингейле, но и на любых других стратегиях, у которых имеется достаточно большая частота трейдов. В данном примере буду использовать показатель на основе просадки по балансу. Потому что все, что связано с балансом, считается проще. Разделим график баланса на растущие и падающие сегменты. Два соседних сегмента образуют полуволну. Количество полуволн стремится к бесконечности при стремлении количества сделок к бесконечности. Но нам хватит и конечной выборки для того, чтобы сделать мартингейл чуточку прибыльнее. Изображу мысли на схеме:

Balance waves

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

Для реализации нам понадобятся два дополнительных входных параметра для нашего мартингейла:

Эти два параметра позволят нам обеспечить увеличенные лоты для безопасных групп полуволн и уменьшенные лоты для опасных групп полуволн, что должно в теории повысить те показатели, которые я определил выше.

В советник-мартингейл понадобится ввести вот такой код, который считает стартовый лот следующего цикла, а также сбрасывает его при необходимости:

Код:

double DecreasedLot=Lot;//лот для уменьшения
double CalcSmartLot()//вычисление предыдущего цикла
   {
   bool ord;
   int PrevCycleDeals=0;
   HistorySelect(TimeCurrent()-HistoryDaysLoadI*86400,TimeCurrent());
   for ( int i=HistoryDealsTotal()-1; i>=0; i-- )
      {
      ulong ticket=HistoryDealGetTicket(i);
      ord=HistoryDealSelect(ticket);
      if ( ord && HistoryDealGetString(ticket,DEAL_SYMBOL) == _Symbol 
      && HistoryDealGetInteger(ticket,DEAL_MAGIC) == MagicC 
      && HistoryDealGetInteger(ticket,DEAL_ENTRY) == DEAL_ENTRY_OUT )
         {
         if ( HistoryDealGetDouble(ticket,DEAL_PROFIT) > 0 )
            {
            for ( int j=i+1; j>=0; j-- )//раз нашли прибыльную сделку то за ней убыточные ( посчитаем их количество)
                {
                ticket=HistoryDealGetTicket(j);
                ord=HistoryDealSelect(ticket);
                if ( ord && HistoryDealGetString(ticket,DEAL_SYMBOL) == _Symbol 
                && HistoryDealGetInteger(ticket,DEAL_MAGIC) == MagicC 
                && HistoryDealGetInteger(ticket,DEAL_ENTRY) == DEAL_ENTRY_OUT )
                   {
                   if ( HistoryDealGetDouble(ticket,DEAL_PROFIT) < 0 )
                      {
                      PrevCycleDeals++;
                      }
                   else
                      {
                      break;
                      }
                   }                
                }
            break;    
            }
         else
            {
            break;
            }
         }
      }
      
   if ( PrevCycleDeals < DealsMinusToBreak ) DecreasedLot-=LotDecrease;
   else DecreasedLot=Lot;
   if ( DecreasedLot <= 0.0 ) DecreasedLot=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);

   return DecreasedLot;
   }

Желтым подсвечены входные параметры. Функция, конечно, тестовая и годится в таком виде только для проверки в тестере, но нам и этого будет достаточно, чтобы провести первую грубую и простейшую проверку данного предположения. В данном случае я пишу необходимый минимум ввиду специфики статьи, и не буду приводить остальной код советника, чтобы не отвлекать читателя лишними размышлениями, коих, я думаю, созрело уже немало по ходу прочтения. Это же соображение относится и к остальным советникам, что были сделаны по ходу написания статьи. Теперь давайте протестируем обычный мартингейл, а потом включим наш новый режим и посмотрим, как он повлиял на показатели. Данный советник также будет для MetaTrader 5, поскольку исходным сигналом в нем является обычный мартингейл, который демонстрирует одну и ту же картинку с разными спредами.

Тестирование советника:

EURUSD M1 2017-2021 Backtest

Если посчитать показатель "D" для исходного теста, то он примет величину "1.744", а для теста с включенным новым режимом получилось "1.758". Иначе говоря, прибыльность чуть-чуть, но сдвинулась в нужную нам сторону. Конечно, если провести еще несколько тестов, данный показатель может и упасть, но в среднем должен наблюдаться прирост показателя. Строго говоря, логика подобрана нелучшим образом, но для демонстрации достаточно.


Заключение

В данной статье я старался собрать самые интересные и полезные приемы, которые могут помочь разработчикам автоматических торговых систем. Некоторые из этих приемов при должной проработке и исследовании могут принести вам прибыль. Я надеюсь, что данный материал был увлекательным и полезным. Все эти приемы — это скорее набор инструментов, но никак не руководство по сборке "Граалей". Даже простое знакомство с подобными приемами может уберечь вас от необдуманных вложений или от критических убытков, а наиболее пытливым попробовать сделать что-то более интересное и стабильное, чем обычную торговую систему на пересечении двух индикаторов.