English 中文 Español Deutsch 日本語 Português
preview
Как построить советник, работающий автоматически (Часть 09): Автоматизация (I)

Как построить советник, работающий автоматически (Часть 09): Автоматизация (I)

MetaTrader 5Трейдинг | 25 апреля 2023, 08:56
837 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье Как построить советник, работающий автоматически (Часть 08): OnTradeTransaction, я рассказал, как можно использовать преимущества платформы MetaTrader 5, используя довольно интересную функцию обработки событий. Но давайте сначала рассмотрим, как построить первый уровень автоматизации для советника.

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

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


Создание механизма безубытка и трейлинг-стопа для OCO-ордеров

Для тех, кто не знаком с этим, система OCO-ордеров (One-Cancels-the-Other) - это такая система, в которой тейк-профит и стоп-лосс устанавливаются в самом ордере или позиции. Если ордер снимается или позиция закрывается, то эти ордеры тейк-профит или стоп-лосс также прекращают свое действие.

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

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

inline void TriggerBreakeven(void)
                        {
                                if (PositionSelectByTicket(m_Position.Ticket))
                                        if (PositionGetDouble(POSITION_PROFIT) >= m_Trigger)
                                                m_Position.EnableBreakEven = (ModifyPricePoints(m_Position.Ticket, m_Position.PriceOpen, m_Position.PriceOpen, m_Position.TP) ? false : true);
                        }

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

Первое, что мы сделаем - выполним вызов функции PositionSelectByTicket. Данная функция загружает всю обновленную информацию об открытой позиции. Затем мы используем функцию PositionGetDouble с аргументом POSITION_PROFIT, чтобы получить самое последнее финансовое значение, которое было загружено вызовом PositionSelectByTicket. Мы сравниваем это значение с тем значением, которое введено в вызове конструктора во время инициализации класса.

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

Эта функция довольно проста, вам не кажется? Однако её определение содержится в приватной части кода. Советник получает доступ к функции трейлинг-стопа, которую можно увидеть ниже:

inline void TriggerTrailingStop(void)
                        {
                                double price;
                                
                                if ((m_Position.Ticket == 0) || (m_Position.SL == 0)) return;
                                if (m_Position.EnableBreakEven) TriggerBreakeven(); else
                                {
                                        price = SymbolInfoDouble(_Symbol, (GetTerminalInfos().ChartMode == SYMBOL_CHART_MODE_LAST ? SYMBOL_LAST : (m_Position.IsBuy ? SYMBOL_ASK : SYMBOL_BID)));
                                        if (MathAbs(price - m_Position.SL) >= (m_Position.Gap * 2))
                                                ModifyPricePoints(m_Position.Ticket, m_Position.PriceOpen, (m_Position.SL + (m_Position.Gap * (m_Position.IsBuy ? 1 : -1))), m_Position.TP);
                                }
                        }

После срабатывания и выполнения безубытка, при следующем вызове советником функции TriggerTrailingStop, мы проверим возможность перемещения стоп-лосса.

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

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

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

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

Хорошо, мы это поняли, теперь необходимо понять кое-что еще: что будет являться обработчиком событий или функций, которые мы должны использовать для вызова вышеуказанных функций? Многие могут подумать, что обработчиком события должна быть функция OnTick, верно? ОШИБКА. Чтобы объяснить это, давайте перейдем к следующей теме.


Почему бы не использовать OnTick в качестве функции вызова триггеров?!

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

//+------------------------------------------------------------------+
int OnInit()
{
        manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04, user08);
        mouse = new C_Mouse(user05, user06, user07, user03, user02, user01);
        (*manager).CheckToleranceLevel();
        EventSetMillisecondTimer(100);

        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        delete manager;
        delete mouse;
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick() { }
//+------------------------------------------------------------------+
void OnTimer()
{
        (*manager).TriggerTrailingStop();
}
//+------------------------------------------------------------------+

Обратите внимание, что в событии OnInit мы устанавливаем короткое значение для генерации события OnTime с помощью функции EventSetMillisecondTimer. Можно установить любое значение от 50 миллисекунд и больше, в зависимости от конкретного случая. Однако мы также должны вызвать функцию EventKillTimer внутри события OnDeInit. Таким образом, советник освободит OnTime, и тем самым платформа MetaTrader 5 не будет постоянно генерировать событие. Даже если платформа может решить эту проблему и перестать генерировать событие, хорошей практикой является освобождение системы. Каждые 100 миллисекунд, или примерно 10 раз в секунду, мы будем проверять наш триггер, показанный в предыдущей теме.

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

Событие OnTick запускается на каждом тике, которым торгует сервер. Если смотреть на гистограмму, даже на минутном интервале времени мы не имеем реального представления о том, что происходит. Чтобы получить точное понимание, нам придется спуститься на уровень HFT (высокочастотный трейдинг), где торгуют профессиональные роботы.

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

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

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

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

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

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

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


Трейлинг-стоп и безубыток по отложенному ордеру

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

Существует "решение" этой проблемы, которое заключается в том, чтобы использовать отложенный ордер для гарантированного выхода. Однако это "решение" не может быть реализовано в системе типа HEDGING, т.е. не может быть использовано с представленным советником. Если мы попытаемся использовать его на счете HEDGING, советник будет выбит из графика, поскольку для класса C_Manager он совершит серьезную ошибку. Однако его можно адаптировать, чтобы позволить советнику иметь две открытые позиции в противоположных направлениях на счете HEDGING. Таким образом, в случае использования счета NETTING, мы можем сделать то, что показано выше.

Однако, если задуматься, то нет смысла открывать позиции в противоположных направлениях на HEDGING-счете, потому что если сервер ордеров перехватит отложенный ордер, то для сервера будет лучше закрыть обе позиции. Это заставит советника вести себя на счете HEDGING точно так же, как и на счете типа NETTING, закрывая открытую позицию отложенным ордером.

Вам нравится эта идея? Прежде чем начинать волноваться, необходимо рассмотреть несколько деталей и вопросов.

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

Но есть и преимущество: если всё будет хорошо и связь с сервером будет потеряна, отложенный ордер в противоположном направлении с тем же объемом обеспечит закрытие позиции (счета NETTING) или фиксацию цены (счета HEDGING).

Как можно заметить, у этого метода есть свои преимущества и недостатки.

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

В этой системе не рекомендуется разрешать пользователю изменять его, т.е. после компиляции кода его не отключить от советника. Он должен всегда оставаться включенным или выключенным. Для этого мы создадим новое определение в коде класса C_Manager. Его можно увидеть ниже:

//+------------------------------------------------------------------+
#define def_MAX_LEVERAGE                10
#define def_ORDER_FINISH                false
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
                void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                if ((m_StaticLeverage >= def_MAX_LEVERAGE) || (m_TicketPending > 0) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                m_TicketPending = C_Orders::CreateOrder(type, Price, (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceStop), (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                        }
//+------------------------------------------------------------------+  
                void ToMarket(const ENUM_ORDER_TYPE type)
                        {
                                ulong tmp;
                                
                                if ((m_StaticLeverage >= def_MAX_LEVERAGE) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                tmp = C_Orders::ToMarket(type, (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceStop), (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                m_Position.Ticket = (m_bAccountHedging ? tmp : (m_Position.Ticket > 0 ? m_Position.Ticket : tmp));
                        }
//+------------------------------------------------------------------+

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

                void PendingToPosition(void)
                        {
                                ResetLastError();
                                if ((m_bAccountHedging) && (m_Position.Ticket > 0))
                                {
                                        if (def_ORDER_FINISH)
                                        {
                                                if (ClosePosition(m_Position.Ticket)) ZeroMemory(m_Position.Ticket);
                                                ClosePosition(m_TicketPending);                                         
                                        }else SetUserError(ERR_Unknown);
                                }else m_Position.Ticket = (m_Position.Ticket == 0 ? m_TicketPending : m_Position.Ticket);
                                m_TicketPending = 0;
                                if (_LastError != ERR_SUCCESS) UpdatePosition(m_Position.Ticket);
                                CheckToleranceLevel();
                        }

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

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

Рисунок 01

Рисунок 01 - Точка, где мы видим отложенные ордера и открытые позиции

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

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

                void UpdatePosition(const ulong ticket)
                        {
                                int ret;
                                double price;
                                
                                if ((ticket == 0) || (ticket != m_Position.Ticket)) return;
                                if (PositionSelectByTicket(m_Position.Ticket))
                                {
                                        ret = SetInfoPositions();
                                        if (def_ORDER_FINISH)
                                        {
                                                price = m_Position.PriceOpen + (FinanceToPoints(m_InfosManager.FinanceStop, m_Position.Leverage) * (m_Position.IsBuy ? -1 : 1));
                                                if (m_TicketPending > 0) if (OrderSelect(m_TicketPending))
                                                {
                                                        price = OrderGetDouble(ORDER_PRICE_OPEN);
                                                        C_Orders::RemoveOrderPendent(m_TicketPending);
                                                }
                                                m_TicketPending = C_Orders::CreateOrder(m_Position.IsBuy ? ORDER_TYPE_SELL : ORDER_TYPE_BUY, price, 0, 0, m_Position.Leverage, m_InfosManager.IsDayTrade);
                                        }
                                        m_StaticLeverage += (ret > 0 ? ret : 0);
                                }else
				{
					ZeroMemory(m_Position);
                                	if (def_ORDER_FINISH)
					{
						RemoveOrderPendent(m_TicketPending);
						m_TicketPending = 0;
					}
				}
                                ResetLastError();
                        }

Сначала мы проверяем, есть ли у системы ордеров значение true. Если это условие выполняется, мы рассчитываем ценовую точку для начала сборки и используем отложенный ордер в качестве стопа позиции. Затем мы проверяем, существует ли уже выставленный ордер. Если так и есть, мы фиксируем цену и удаляем ордер из книги. В любом случае, мы попытаемся создать новый отложенный ордер. Если всё правильно, у нас будет отложенный ордер в книге заявок, который будет служить в качестве стоп-лосса.

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

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

inline int SetInfoPositions(void)
                        {
                                double v1, v2;
                                int tmp = m_Position.Leverage;
                                
                                m_Position.Leverage = (int)(PositionGetDouble(POSITION_VOLUME) / GetTerminalInfos().VolMinimal);
                                m_Position.IsBuy = ((ENUM_POSITION_TYPE) PositionGetInteger(POSITION_TYPE)) == POSITION_TYPE_BUY;
                                m_Position.TP = PositionGetDouble(POSITION_TP);
                                v1 = m_Position.SL = PositionGetDouble(POSITION_SL);
                                v2 = m_Position.PriceOpen = PositionGetDouble(POSITION_PRICE_OPEN);
                                if (def_ORDER_FINISH) if (m_TicketPending > 0) if (OrderSelect(m_TicketPending)) v1 = OrderGetDouble(ORDER_PRICE_OPEN);
                                m_Position.EnableBreakEven = (def_ORDER_FINISH ? m_TicketPending == 0 : m_Position.EnableBreakEven) || (m_Position.IsBuy ? (v1 < v2) : (v1 > v2));
                                m_Position.Gap = FinanceToPoints(m_Trigger, m_Position.Leverage);

                                return m_Position.Leverage - tmp;
                        }

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

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

inline void TriggerBreakeven(void)
                        {
                                double price;
                                
                                if (PositionSelectByTicket(m_Position.Ticket))
                                        if (PositionGetDouble(POSITION_PROFIT) >= m_Trigger)
                                        {
                                                price = m_Position.PriceOpen + (GetTerminalInfos().PointPerTick * (m_Position.IsBuy ? 1 : -1));
                                                if (def_ORDER_FINISH)
                                                {
                                                        if (m_TicketPending > 0) m_Position.EnableBreakEven = !ModifyPricePoints(m_TicketPending, price, 0, 0);
                                                }else m_Position.EnableBreakEven = !ModifyPricePoints(m_Position.Ticket, m_Position.PriceOpen, price, m_Position.TP);
                                        }
                        }

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

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

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

Теперь давайте посмотрим, как работает функция трейлинг-стопа в этом случае:

inline void TriggerTrailingStop(void)
                        {
                                double price, v1;
                                
                                if ((m_Position.Ticket == 0) || (def_ORDER_FINISH ? m_TicketPending == 0 : m_Position.SL == 0)) return;
                                if (m_Position.EnableBreakEven) TriggerBreakeven(); else
                                {
                                        price = SymbolInfoDouble(_Symbol, (GetTerminalInfos().ChartMode == SYMBOL_CHART_MODE_LAST ? SYMBOL_LAST : (m_Position.IsBuy ? SYMBOL_ASK : SYMBOL_BID)));
                                        v1 = m_Position.SL;
                                        if (def_ORDER_FINISH) if (OrderSelect(m_TicketPending)) v1 = OrderGetDouble(ORDER_PRICE_OPEN);
                                        if (v1 > 0) if (MathAbs(price - v1) >= (m_Position.Gap * 2)) 
                                        {
                                                price = v1 + (m_Position.Gap * (m_Position.IsBuy ? 1 : -1));
                                                if (def_ORDER_FINISH) ModifyPricePoints(m_TicketPending, price, 0, 0);
                                                else ModifyPricePoints(m_Position.Ticket, m_Position.PriceOpen, price, m_Position.TP);
                                        }
                                }
                        }

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

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

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

Видео 01 - Демонстрация системы остановки отложенного ордера.


Заключение

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

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

Теперь у нас есть минимальная автоматизация, необходимая для реализации остальных шагов и примеров, а в следующей статье мы рассмотрим, как создать 100% автоматический советник. Успехов в обучении!

Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/11281

Прикрепленные файлы |
Эксперименты с нейросетями (Часть 6): Перцептрон как самодостаточное средство предсказания цены Эксперименты с нейросетями (Часть 6): Перцептрон как самодостаточное средство предсказания цены
Пример использования перцептрона как самодостаточного средства предсказания цены. В статье даются общие понятия, представлен простейший готовый советник и результаты его оптимизации.
Нейросети — это просто (Часть 38): Исследование с самоконтролем через несогласие (Self-Supervised Exploration via Disagreement) Нейросети — это просто (Часть 38): Исследование с самоконтролем через несогласие (Self-Supervised Exploration via Disagreement)
Одной из основных проблем обучения с подкреплением является исследование окружающей среды. Ранее мы уже познакомились с методом исследования на базе внутреннего любопытства. Сегодня я предлагаю посмотреть на ещё один алгоритм — исследование через несогласие.
Как построить советник, работающий автоматически (Часть 10): Автоматизация (II) Как построить советник, работающий автоматически (Часть 10): Автоматизация (II)
Автоматизация ничего не значит, если вы не можете контролировать расписание его работы. Ни один работник не может быть эффективным при работе 24 часа в сутки. Несмотря на этот факт, многие считают, что автоматизированная система должна работать 24 часа в сутки. Хорошо всегда иметь возможность задавать временной интервал для эксперта. В этой статье мы обсудим, как правильно установить такой временной интервал.
Теория категорий в MQL5 (Часть 3) Теория категорий в MQL5 (Часть 3)
Теория категорий представляет собой разнообразный и расширяющийся раздел математики, который пока относительно не освещен в MQL5-сообществе. Эта серия статей призвана осветить некоторые из ее концепций для создания открытой библиотеки и дальнейшему использованию этого замечательного раздела в создании торговых стратегий.