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

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

MetaTrader 5Трейдинг | 28 апреля 2023, 12:34
973 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье Как построить советник, работающий автоматически (Часть 11): Автоматизация (III), мы рассмотрели, как можно создать надежную систему, способную свести к минимуму сбои и лазейки, которые могут повлиять на любую программу.

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

Если вы следили за этой серией статей об автоматизации советников, то вы, возможно, заметили, что создать советник для ручного использования относительно быстро и легко. Однако для 100% автоматизированного советника всё не так просто. Я никогда не намекал на то, что мы создадим 100% надежную систему, которую можно будет использовать без присмотра. На самом деле, я думаю, что стало ясно - многие обманывают себя, думая, что они могут включить автоматизированный советник и оставить его работать, не понимая, что работает.

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

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

На текущем этапе разработки наш советник для ручного и полуавтоматического использования (с использованием безубытка и трейлинг-стопа) не имеет такого разрушительного бага, как Blockbuster (герой из вселенной DC). Однако, если мы используем его на 100% автоматически, ситуация меняется и возникает риск потенциально опасного сбоя.

В предыдущей статье я рассмотрел эту проблему и попросил читателей определить, где была ошибка и как она могла вызывать проблемы, так что автоматизировать наш советник на 100% всё равно было бы невозможно. Если ответ был «нет», это нормально. Поверьте мне, не каждый человек может определить ошибку, просто взглянув на код и при его ручном или при полуавтоматическом использовании. Однако, если вы попытаетесь автоматизировать код, то могут появиться серьезные проблемы. Безусловно, эту ошибку легче всего выявить, но не так просто исправить для использования в полностью автоматизированном советнике.

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


Понимание проблемы

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

Для наглядности предположим, что объём в 100 раз превышает минимальный объем. Это означает, что советник сможет совершать столько сделок, сколько возможно, пока не достигнет этого предела или пока последнее правило, добавленное в класс C_Manager, не позволит объему превысить 100.

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

inline bool IsPossible(const bool IsPending)
                        {
                                if (!CtrlTimeIsPassed()) return false;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_bAccountHedging && (m_Position.Ticket > 0))) return false;
                                if ((IsPending) && (m_TicketPending > 0)) return false;
                                if (m_StaticLeverage + m_InfosManager.Leverage > m_InfosManager.MaxLeverage)
                                {
                                        Print("Request denied, as it would violate the maximum volume allowed for the EA.");
                                        return false;
                                }
                                
                                return true;
                        }

Приведенный фрагмент кода предотвращает превышение объема, но как это происходит на самом деле?

Допустим, трейдер настроил советник на работу с плечом в 3 раза больше требуемого минимального объема, а максимальный объем, установленный в коде советника (при компиляции), равен 100-кратному - как объяснялось в предыдущих статьях. После 33 сделок советник достигнет 99-кратного минимального объема торговли, а это означает, что мы можем совершить еще одну сделку. Однако из-за выделенной выше строки в коде трейдер должен изменить объем на 1 раз, чтобы достичь максимального лимита. В противном случае советник не сможет совершить транзакцию.

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

Может быть, вы подумаете: «Я не вижу никаких недостатков в этом коде». На самом деле багов в этом коде нет, а использующие его функции (что можно увидеть ниже) умудряются ограничивать объем, торгуемый советником, до установленного максимального лимита.

//+------------------------------------------------------------------+
                void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                if (!IsPossible(true)) return;
                                m_TicketPending = C_Orders::CreateOrder(type, Price, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                        }
//+------------------------------------------------------------------+  
                void ToMarket(const ENUM_ORDER_TYPE type)
                        {
                                ulong tmp;
                                
                                if (!IsPossible(false)) return;
                                tmp = C_Orders::ToMarket(type, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                m_Position.Ticket = (m_bAccountHedging ? tmp : (m_Position.Ticket > 0 ? m_Position.Ticket : tmp));
                        }
//+------------------------------------------------------------------+

Но тогда где ошибка? На самом деле, это не так просто для понимания. Чтобы понять проблему, мы должны подумать о том, как советник может работать не только в плане отправки ордеров, потому что класс C_Manager уже не позволяет советнику их выставлять. Проблема возникает, когда мы комбинируем разные виды ордеров, чтобы получить триггер ЯВНЫХ ошибок. Есть несколько способов ограничить или предотвратить наличие этого вида триггера, эти способы мы рассмотрим ниже:

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

Теперь, уже зная, что проблема возникает, когда у нас есть комбинация ордеров, давайте глубже разберем этот вопрос. Возвращаясь к примеру с плечом 3x, где уже было 33 сделки с дневным лимитом 100x, всё было бы под контролем, если бы советник работал только на рыночных ордерах. Однако, если по какой-то причине у нас есть отложенный ордер на торговом сервере, это может измениться. Если этот отложенный ордер добавит еще 3 единицы к минимальному объему, то как только он будет исполнен, мы превысим максимальный объем, предусмотренный советником.

Вы можете подумать, что те 2 единицы объема, которые превысили предел 100, это не так уж и много, однако оно может стать серьезной проблемой. Например, давайте представим, что советник настроен на подсчет объема в 50 единиц. Мы можем выполнить 2 рыночных ордера, не нарушая правило на 100. Но если отправить отложенный ордер на увеличение объема позиции, мы уже достигнем объема в 100 единиц. Однако этот отложенный ордер еще не выполнен, так как он находится в состоянии ожидания. В какой-то момент советник может решить отправить еще один рыночный ордер, и в этом случае объем вернется к 50, но лимит уже будет превышен.

Тогда класс C_Manager сразу же заметит, что советник достиг дневного лимита, учитывая сделку на 100. И хотя он знает о существовании отложенного ордера, класс не понимает, как с ним обращаться. Если ордер будет исполнен, советник превысит правило на 100, достигнув объема сделки на 150 единиц. Вы ведь понимаете в чем проблема? Некоторое время назад была установлена блокировка, чтобы предотвратить советник от зависания слишком большого количества ордеров в книге заявок или открытия слишком большого количества позиций с большим объемом. Эта блокировка была нарушена из-за такого простого факта, что триггер, который автоматизирует советник, не предвидел такой возможности. Предполагалась, что класс C_Manager мог предотвратить превышение советником установленного максимального объема, но этого не произошло из-за смешения рыночных и отложенных ордеров. 

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

Хотя описанную выше проблему можно наблюдать даже при ручном использовании системы, вероятность того, что трейдер допустит эту ошибку, значительно меньше. А всё потому что в этом будет виноват сам трейдер, а не система защиты. Однако для 100% автоматической системы это совершенно неприемлемо. По этой причине и еще несколько, НИКОГДА, НИ ПРИ КАКИХ ОБСТОЯТЕЛЬСТВАХ, не оставляйте без присмотра советника, работающего на 100% автоматически, даже если вы его запрограммировали. Даже если вы отличный программист, не рекомендуется оставлять его без присмотра, пока он функционирует.

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


Исправление ошибки

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

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

                struct st01
                {
                        ulong   Ticket;
                        double  SL,
                                TP,
                                PriceOpen,
                                Gap;
                        bool    EnableBreakEven,
                                IsBuy;
                        uint    Leverage;
                }m_Position, m_Pending;
                ulong   m_TicketPending;

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

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

inline void SetInfoPending(void)
                        {
                                ENUM_ORDER_TYPE eLocal = (ENUM_ORDER_TYPE) OrderGetInteger(ORDER_TYPE);
                                
                                m_Pending.Leverage = (uint)(OrderGetDouble(ORDER_VOLUME_CURRENT) / GetTerminalInfos().VolMinimal);
                                m_Pending.IsBuy = ((eLocal == ORDER_TYPE_BUY) || (eLocal == ORDER_TYPE_BUY_LIMIT) || (eLocal == ORDER_TYPE_BUY_STOP) || (eLocal == ORDER_TYPE_BUY_STOP_LIMIT));
                                m_Pending.PriceOpen = OrderGetDouble(ORDER_PRICE_OPEN);
                                m_Pending.SL = OrderGetDouble(ORDER_SL);
                                m_Pending.TP = OrderGetDouble(ORDER_TP);
                        }

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

Нам также нужна новая процедура:

                void UpdatePending(const ulong ticket)
                        {
                                if ((ticket == 0) || (ticket != m_Pending.Ticket) || (m_Pending.Ticket == 0)) return;
                                if (OrderSelect(m_Pending.Ticket)) SetInfoPending();
                        }

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

Чтобы советник выполнил указанный выше вызов, нам нужно добавить новое событие в функцию обработки OnTradeTransaction:

void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
{
        switch (trans.type)
        {
                case TRADE_TRANSACTION_POSITION:
                        manager.UpdatePosition(trans.position);
                        break;
                case TRADE_TRANSACTION_ORDER_DELETE:
                        if (trans.order == trans.position) (*manager).PendingToPosition();
                        else (*manager).UpdatePosition(trans.position);
                        break;
                case TRADE_TRANSACTION_ORDER_UPDATE:
                        (*manager).UpdatePending(trans.order);
                        break;
                case TRADE_TRANSACTION_REQUEST: if ((request.symbol == _Symbol) && (result.retcode == TRADE_RETCODE_DONE) && (request.magic == def_MAGIC_NUMBER)) switch (request.action)
                        {
                                case TRADE_ACTION_DEAL:
                                        (*manager).UpdatePosition(request.order);
                                        break;
                                case TRADE_ACTION_SLTP:
                                        (*manager).UpdatePosition(trans.position);
                                        break;
                                case TRADE_ACTION_REMOVE:
                                        (*manager).EraseTicketPending(request.order);
                                        break;
                        }
                        break;
        }
}

Именно выделенная выше строка будет вызывать класс C_Manager.

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

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

inline void LoadPositionValid(void)
                        {
                                ulong value;
                                
                                for (int c0 = PositionsTotal() - 1; (c0 >= 0) && (_LastError == ERR_SUCCESS); c0--)
                                {
                                        if ((value = PositionGetTicket(c0)) == 0) continue;
                                        if (PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
                                        if (PositionGetInteger(POSITION_MAGIC) != GetMagicNumber()) continue;
                                        if ((m_bAccountHedging) && (m_TicketPending > 0))
                                        {
                                                C_Orders::ClosePosition(value);
                                                continue;
                                        }
                                        if (m_Position.Ticket > 0) SetUserError(ERR_Unknown); else
                                        {
                                                m_Position.Ticket = value;
                                                SetInfoPositions();
                                                m_StaticLeverage = m_Position.Leverage;
                                        }
                                }
                        }

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

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

//+------------------------------------------------------------------+
                bool CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                bool bRet = false;
                                
                                if (!IsPossible(true)) return bRet;
                                m_Pending.Ticket = C_Orders::CreateOrder(type, Price, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                if (m_Pending.Ticket > 0) bRet = OrderSelect(m_Pending.Ticket);
                                if (bRet) SetInfoPending();
                                
                                return bRet;
                        }
//+------------------------------------------------------------------+  
                bool ToMarket(const ENUM_ORDER_TYPE type)
                        {
                                ulong tmp;
                                bool bRet = false;
                                
                                if (!IsPossible(false)) return bRet;
                                tmp = C_Orders::ToMarket(type, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                m_Position.Ticket = (m_bAccountHedging ? tmp : (m_Position.Ticket > 0 ? m_Position.Ticket : tmp));
                                if (m_Position.Ticket > 0) bRet = PositionSelectByTicket(m_Position.Ticket);
                                if (!bRet) ZeroMemory(m_Position);
                                
                                return bRet;
                        }
//+------------------------------------------------------------------+

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

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

const uint GetVolumeInPosition(void) const
                        {
                                return m_Position.Leverage;
                        }

Приведенный выше код достаточен для выполнения такого действия. Однако в классе кода нет реального способа изменить позицию.

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

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

Новый код для вызовов можно увидеть ниже:

//+------------------------------------------------------------------+
                bool CreateOrder(const ENUM_ORDER_TYPE type, const double Price, const uint LeverageArg = 0)
                        {
                                bool bRet = false;
                                
                                if (!IsPossible(type, (LeverageArg > 0 ? LeverageArg : m_InfosManager.Leverage))) return bRet;
                                m_Pending.Ticket = C_Orders::CreateOrder(type, Price, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                if (m_Pending.Ticket > 0) bRet = OrderSelect(m_Pending.Ticket);
                                if (bRet) SetInfoPending();
                                
                                return bRet;
                        }
//+------------------------------------------------------------------+  
                bool ToMarket(const ENUM_ORDER_TYPE type, const uint LeverageArg = 0)
                        {
                                ulong tmp;
                                bool bRet = false;
                                
                                if (!IsPossible(type, (LeverageArg > 0 ? LeverageArg : m_InfosManager.Leverage))) return bRet;
                                tmp = C_Orders::ToMarket(type, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                m_Position.Ticket = (m_bAccountHedging ? tmp : (m_Position.Ticket > 0 ? m_Position.Ticket : tmp));
                                if (m_Position.Ticket > 0) bRet = PositionSelectByTicket(m_Position.Ticket);
                                if (!bRet) ZeroMemory(m_Position);
                                
                                return bRet;
                        }
//+------------------------------------------------------------------+

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

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

inline bool IsPossible(const ENUM_ORDER_TYPE type, const uint Leverage)
                        {
                                int i0, i1;
                                
                                if (!CtrlTimeIsPassed()) return false;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_bAccountHedging && (m_Position.Ticket > 0))) return false;
                                if ((m_Pending.Ticket > 0) || (Leverage > INT_MAX)) return false;
                                i0 = (int)(m_Position.Ticket == 0 ? 0 : m_Position.Leverage) * (m_Position.IsBuy ? 1 : -1);
                                i1 = i0 + ((int)(Leverage * (type == ORDER_TYPE_BUY ? 1 : -1)));
                                if (((i1 < i0) && (i1 >= 0) && (i0 > 0)) || ((i1 > i0) && (i1 <= 0) && (i0 < 0))) return true;
                                if ((m_StaticLeverage + MathAbs(i1)) > m_InfosManager.MaxLeverage)
                                {
                                        Print("Request denied, as it would violate the maximum volume allowed for the EA.");
                                        return false;
                                }
                                
                                return true;
                        }

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

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

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

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

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

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

Пример 1:

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

Пример 2:

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

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

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

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

inline int SetInfoPositions(void)
                        {
                                double v1, v2;
                                uint tmp = m_Position.Leverage;
                                bool tBuy = m_Position.IsBuy;
                                
                                m_Position.Leverage = (uint)(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 (m_InfosManager.IsOrderFinish) if (m_Pending.Ticket > 0) v1 = m_Pending.PriceOpen;
                                m_Position.EnableBreakEven = (m_InfosManager.IsOrderFinish ? m_Pending.Ticket == 0 : m_Position.EnableBreakEven) || (m_Position.IsBuy ? (v1 < v2) : (v1 > v2));
                                m_Position.Gap = FinanceToPoints(m_Trigger, m_Position.Leverage);

                                return (int)(tBuy == m_Position.IsBuy ? m_Position.Leverage - tmp : m_Position.Leverage);
                        }

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


Конечные выводы

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

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

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

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

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

int OnInit()
{
        string szInfo;
        
        manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04, user08, false, 10);
        mouse = new C_Mouse(user05, user06, user07, user03, user02, user01);
        for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++)

По умолчанию это значение установлено на 10, но может возникнуть необходимость использовать его с более высоким значением, особенно на рынке Forex. Многие люди склонны использовать значение 100. Чтобы внести это изменение, просто замените значение 10 на нужное значение, например, 1000, если вы хотите использовать объем в десять раз больше обычного 100.

int OnInit()
{
        string szInfo;
        
        manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04, user08, false, 1000);
        mouse = new C_Mouse(user05, user06, user07, user03, user02, user01);
        for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++)

Таким образом, вы сможете отправить до 10 ордеров с указанным объемом, прежде чем советник заблокирует отправку новых ордеров.

В следующем видео можно увидеть систему, работающую в текущей конфигурации.



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

Прикрепленные файлы |
Нейросети — это просто (Часть 39): Go-Explore — иной подход к исследованию Нейросети — это просто (Часть 39): Go-Explore — иной подход к исследованию
Продолжаем тему исследования окружающей среды в моделях обучения с подкреплением. И данной статье мы рассмотрим ещё один алгоритм Go-Explore, который позволяет эффективно исследовать окружающую среду на стадии обучения модели.
Как построить советник, работающий автоматически (Часть 11): Автоматизация (III) Как построить советник, работающий автоматически (Часть 11): Автоматизация (III)
Автоматизированная система без соответствующей безопасности не будет успешной. Однако безопасность не будет обеспечена без хорошего понимания некоторых вещей. В этой статье мы разберемся с тем, почему достижение максимальной безопасности в автоматизированных системах является такой сложной задачей.
Алгоритм докупки: математическая модель увеличения эффективности Алгоритм докупки: математическая модель увеличения эффективности
В данной статье мы будем использовать алгоритм докупки, как путеводитель в мир более глубокого понимания эффективности торговых систем и начнем работать над общими принципами усиления эффективности торговли с помощью математики и логики а также применим самые нестандартные методы увеличения эффективности в контексте использования абсолютно любой торговой системы.
Как построить советник, работающий автоматически (Часть 10): Автоматизация (II) Как построить советник, работающий автоматически (Часть 10): Автоматизация (II)
Автоматизация ничего не значит, если вы не можете контролировать расписание его работы. Ни один работник не может быть эффективным при работе 24 часа в сутки. Несмотря на этот факт, многие считают, что автоматизированная система должна работать 24 часа в сутки. Хорошо всегда иметь возможность задавать временной интервал для эксперта. В этой статье мы обсудим, как правильно установить такой временной интервал.