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

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

MetaTrader 5Примеры | 30 мая 2023, 09:21
1 190 0
Daniel Jose
Daniel Jose

Введение

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

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

Тема на самом деле довольно длинная, так что давайте перейдем к практическим примерам. 


Первый пример автоматизации: 9-периодная экспоненциальная скользящая средняя

В этом примере используется код советника, который вы найдете в приложении под именем EA_v1.mq5. Начнем с конструктора класса, который можно увидеть в следующем фрагменте:

                C_Automaton(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage,
                            bool IsDayTrade, double Trailing, const ENUM_TIMEFRAMES iPeriod,
                            const double OverBought = 70, const double OverSold = 30, const int iShift = 1)
                        :C_Manager(magic, 0, 0, Leverage, IsDayTrade, 0, false, 10),
                         m_TF(iPeriod),
                         m_Handle(INVALID_HANDLE)
                        {
                                m_Infos.Shift      = iShift;
                                m_Infos.OverBought = OverBought;
                                m_Infos.OverSold   = OverSold;
                                ArraySetAsSeries(m_Buff, true);
                                m_nBars  = iBars(NULL, m_TF);
                                m_Handle = iMA(NULL, m_TF, 9, 0, MODE_EMA, PRICE_CLOSE);
                        }

Давайте посмотрим, что мы имеем здесь, в отличие от системы по умолчанию:

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

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

inline eTrigger CheckTrigger(void)
                        {
                                int iRet;
                                        
                                if (((iRet = iBars(NULL, m_TF)) > m_nBars) && (m_Handle != INVALID_HANDLE))
                                {
                                        if (CopyBuffer(m_Handle, 0, 0, m_Infos.Shift + 1, m_Buff) < m_Infos.Shift + 1) return TRIGGER_NONE;
                                        m_nBars = iRet;
                                        if (m_Buff[0] > m_Buff[m_Infos.Shift]) return TRIGGER_BUY;
                                        if (m_Buff[0] < m_Buff[m_Infos.Shift]) return TRIGGER_SELL;
                                };
                                return TRIGGER_NONE;
                        }

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

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

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

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

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

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

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

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

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

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

inline virtual void Triggers(void) final
                        {
                                if (!CtrlTimeIsPassed()) ClosePosition(); else switch (CheckTrigger())
                                {
                                        case TRIGGER_BUY:
                                                if (m_Memory == TRIGGER_SELL) ClosePosition();
                                                if (GetVolumeInPosition() == 0)
                                                {
                                                        DestroyOrderPendent();
                                                        CreateOrder(ORDER_TYPE_BUY, iHigh(NULL, m_TF, 0));
                                                }
                                                m_Memory = TRIGGER_BUY;
                                                break;
                                        case TRIGGER_SELL:
                                                if (m_Memory == TRIGGER_BUY) ClosePosition();
                                                if (GetVolumeInPosition() == 0)
                                                {
                                                        DestroyOrderPendent();
                                                        CreateOrder(ORDER_TYPE_SELL, iLow(NULL, m_TF, 0));
                                                }
                                                m_Memory = TRIGGER_SELL;
                                                break;
                                }
                        };

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

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


Второй пример автоматизации: Использование индикатора RSI или IFR

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

Код эксперта в данном случае будет представлен в приложении как EA_v2.mq5. Начнем с конструктора, который видно ниже:

                C_Automaton(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage,
                            bool IsDayTrade, double Trailing, const ENUM_TIMEFRAMES iPeriod,
                            const double OverBought = 70, const double OverSold = 30, const int iShift = 1)
                        :C_Manager(magic, FinanceStop, 0, Leverage, IsDayTrade, Trailing, true, 10),
                         m_TF(iPeriod),
                         m_Handle(INVALID_HANDLE)
                        {
                                m_Infos.Shift      = iShift;
                                m_Infos.OverBought = OverBought;
                                m_Infos.OverSold   = OverSold;
                                ArraySetAsSeries(m_Buff, true);
                                m_nBars = iBars(NULL, m_TF);
                                m_Handle = iRSI(NULL, m_TF, 14, PRICE_CLOSE);

                        }

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

  • Устанавливаем значение, которое будем использовать в качестве стоп-лосса после открытия позиции;
  • Устанавливаем значение, которое будем использовать в качестве безубытка, трейлинг-стопа;
  • В качестве точки остановки мы объявим отложенный ордер;
  • Указываем, что будем использовать индикатор RSI с периодом 14, основываясь на цене закрытия.
  • Значения перекупленности и перепроданности сообщаются и сохраняются для дальнейшего использования.

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

inline eTrigger CheckTrigger(void)
                        {
                                int iRet;
                                        
                                if (((iRet = iBars(NULL, m_TF)) > m_nBars) && (m_Handle != INVALID_HANDLE))
                                {
                                        if (CopyBuffer(m_Handle, 0, 0, m_Infos.Shift + 1, m_Buff) < m_Infos.Shift + 1) return TRIGGER_NONE;
                                        m_nBars = iRet;
                                        if ((m_Buff[0] > m_Buff[m_Infos.Shift]) && (m_Buff[0] > m_Infos.OverSold) && (m_Buff[m_Infos.Shift] < m_Infos.OverSold)) return TRIGGER_BUY;
                                        if ((m_Buff[0] < m_Buff[m_Infos.Shift]) && (m_Buff[0] < m_Infos.OverBought) && (m_Buff[m_Infos.Shift] > m_Infos.OverBought)) return TRIGGER_SELL;
                                };
                                return TRIGGER_NONE;
                        }

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

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

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

inline virtual void Triggers(void) final
                        {
                                if (!CtrlTimeIsPassed()) ClosePosition(); else switch (CheckTrigger())
                                {
                                        case TRIGGER_BUY:
                                                if (m_Memory == TRIGGER_SELL) ClosePosition();
                                                if (m_Memory != TRIGGER_BUY) ToMarket(ORDER_TYPE_BUY);
                                                m_Memory = TRIGGER_BUY;
                                                break;
                                        case TRIGGER_SELL:
                                                if (m_Memory == TRIGGER_BUY) ClosePosition();
                                                if (m_Memory != TRIGGER_SELL) ToMarket(ORDER_TYPE_SELL);
                                                m_Memory = TRIGGER_SELL;
                                                break;
                                }
                                TriggerTrailingStop();
                        };

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

Теперь рассмотрим еще один пример для закрепления идей.


Третий пример автоматизации: Пересечение скользящей средней

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

const bool IsBuyPosition(void) const
                        {
                                return m_Position.IsBuy;
                        }

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

                void LockStopInPrice(const double Price)
                        {
                                if (m_InfosManager.IsOrderFinish)
                                {
                                        if (m_Pending.Ticket == 0) return;
                                        if ((m_Pending.PriceOpen > Price) && (m_Position.IsBuy)) return;
                                        if ((m_Pending.PriceOpen < Price) && (!m_Position.IsBuy)) return;
                                        ModifyPricePoints(m_Pending.Ticket, m_Pending.PriceOpen = Price, m_Pending.SL = 0, m_Pending.TP = 0);
                                }else
                                {
                                        if (m_Position.SL == 0) return;
                                        if ((m_Position.SL > Price) && (m_Position.IsBuy)) return;
                                        if ((m_Position.SL < Price) && (!m_Position.IsBuy)) return;
                                        ModifyPricePoints(m_Position.Ticket, m_Position.PriceOpen, Price, m_Position.TP);
                                }
                        }

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

Исходя из этого, у нас теперь есть следующий код для функции трейлинг-стопа:

inline void TriggerTrailingStop(void)
                        {
                                double price, v1;
                                
                                if ((m_Position.Ticket == 0) || (m_InfosManager.IsOrderFinish ? m_Pending.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)));
                                        v1 = (m_InfosManager.IsOrderFinish ? m_Pending.PriceOpen : m_Position.SL);
                                        if (v1 > 0) if (MathAbs(price - v1) >= (m_Position.Gap * 2)) 
                                                LockStopInPrice(v1 + (m_Position.Gap * (m_Position.IsBuy ? 1 : -1)));
                                        {                                               
                                                price = v1 + (m_Position.Gap * (m_Position.IsBuy ? 1 : -1));
                                                if (m_InfosManager.IsOrderFinish) ModifyPricePoints(m_Pending.Ticket, m_Pending.PriceOpen = price, m_Pending.SL = 0, m_Pending.TP = 0);
                                                else    ModifyPricePoints(m_Position.Ticket, m_Position.PriceOpen, price, m_Position.TP);
                                        }
                                }
                        }

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

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

Начнем с объявления переменных:

class C_Automaton : public C_Manager
{
        protected:
                enum eTrigger {TRIGGER_NONE, TRIGGER_BUY, TRIGGER_SELL};
        private :
                enum eSelectMedia {MEDIA_FAST, MEDIA_SLOW};
                struct st00
                {
                        int     Shift,
                                nBars;
                        double  OverBought,
                                OverSold;
                }m_Infos;
                struct st01
                {
                        double  Buff[];
                        int     Handle;
                }m_Op[sizeof(eSelectMedia) + 1];
                int     m_nBars;
                ENUM_TIMEFRAMES m_TF;

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

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

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

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

                C_Automaton(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage,
                                                bool IsDayTrade, double Trailing, const ENUM_TIMEFRAMES iPeriod,
                                                const double OverBought = 70, const double OverSold = 30, const int iShift = 1)
                        :C_Manager(magic, FinanceStop, FinanceTake, Leverage, IsDayTrade, Trailing, true, 10),
                         m_TF(iPeriod)
                        {
                                for (int c0 = sizeof(eSelectMedia); c0 <= 0; c0--)
                                {
                                        m_Op[c0].Handle = INVALID_HANDLE;
                                        ArraySetAsSeries(m_Op[c0].Buff, true);
                                }
                                m_Infos.Shift      = (iShift < 3 ? 3 : iShift);
                                m_Infos.OverBought = OverBought;
                                m_Infos.OverSold   = OverSold;
                                m_nBars = iBars(NULL, m_TF);
                                m_Op[MEDIA_FAST].Handle = iMA(NULL, m_TF, 9, 0, MODE_EMA, PRICE_CLOSE);
                                m_Op[MEDIA_SLOW].Handle = iMA(NULL, m_TF, 20, 0, MODE_SMA, PRICE_CLOSE);
                        }

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

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

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

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

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

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

inline eTrigger CheckTrigger(void)
                        {
                                int iRet;
                                bool bOk = false;
                                        
                                if (iRet = iBars(NULL, m_TF)) > m_nBars)
                                {
                                        for (int c0 = sizeof(eSelectMedia); c0 <= 0; c0--)
                                        {
                                                if (m_Op[c0].Handle == INVALID_HANDLE) return TRIGGER_NONE;
                                                if (CopyBuffer(m_Op[c0].Handle, 0, 0, m_Infos.Shift + 1, m_Op[c0].Buff) < m_Infos.Shift + 1) return TRIGGER_NONE;
                                                bOk = true;
                                        }
                                        if (!bOk) return TRIGGER_NONE; else m_nBars = iRet;
                                        if ((m_Op[MEDIA_FAST].Buff[1] > m_Op[MEDIA_SLOW].Buff[1]) && (m_Op[MEDIA_FAST].Buff[m_Infos.Shift] < m_Op[MEDIA_SLOW].Buff[m_Infos.Shift])) return TRIGGER_BUY;
                                        if ((m_Op[MEDIA_FAST].Buff[1] < m_Op[MEDIA_SLOW].Buff[1]) && (m_Op[MEDIA_FAST].Buff[m_Infos.Shift] > m_Op[MEDIA_SLOW].Buff[m_Infos.Shift])) return TRIGGER_SELL;
                                };
                                return TRIGGER_NONE;
                        }

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

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

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

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

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

Чтобы завершить эту последнюю модель, давайте посмотрим кое-что еще о системе чуть ниже:

inline virtual void Triggers(void) final
                        {
#define def_HILO 20
                                if (!CtrlTimeIsPassed()) ClosePosition(); else switch (CheckTrigger())
                                {
                                        case TRIGGER_BUY:
                                                if (m_Memory == TRIGGER_SELL) ClosePosition();
                                                if (m_Memory != TRIGGER_BUY) ToMarket(ORDER_TYPE_BUY);
                                                m_Memory = TRIGGER_BUY;
                                                break;
                                        case TRIGGER_SELL:
                                                if (m_Memory == TRIGGER_BUY) ClosePosition();
                                                if (m_Memory != TRIGGER_SELL) ToMarket(ORDER_TYPE_SELL);
                                                m_Memory = TRIGGER_SELL;
                                                break;
                                }
                                LockStopInPrice(IsBuyPosition() ?  iLow(NULL, m_TF, iLowest(NULL, m_TF, MODE_LOW, def_HILO, 0)) : iHigh(NULL, m_TF, iHighest(NULL, m_TF, MODE_HIGH, def_HILO, 0)));
#undef def_HILO
                        };

Большим достоинством данной функции является именно этот код, в котором мы имеем очень любопытный трейлинг-стоп. Таким образом, ордер, или стоп-цена, в зависимости от ситуации, будет находиться на максимуме или минимуме, в зависимости, от того, покупаем мы или продаем. Используемое значение очень похоже на индикатор, известный многим трейдерам B3, под названием HILO. Этот индикатор, для тех, кто с ним не знаком, ищет максимум или минимум цены в пределах определенного количества баров. За это отвечает именно данный код: Здесь мы ищем значение LO, и тут значение HI, в обоих случаях HILO равно 20.

На этом мы закончили с третьим примером.


Заключительные итоги

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

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

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

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

Кроме того, я показал, как создать определенные условия для эффективной работы вашей системы, даже если в MQL5 или MetaTrader 5 нет индикатора, который вы хотели бы использовать. Это мы показали в примере номер 3, где показано, как создать внутренний индикатора HILO. Однако крайне важно, чтобы система была должным образом реализована и протестирована. Плохо выполненная система, как бы хорошо мы ее ни разработали, может привести скорее к убыткам, чем к прибыли.

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

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


ВАЖНОЕ ЗАМЕЧАНИЕ: Не используйте советники, имеющиеся в приложении, без соответствующих знаний. Такие советники предназначены только для демонстративных целей.

Если вы собираетесь использовать их на РЕАЛЬНОМ счете, то придется сделать это на свой страх и риск, поскольку они могут принести значительные потери вашему депозиту.


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

Прикрепленные файлы |
Учимся у проп-трейдинговых компаний (Часть 1) — Введение Учимся у проп-трейдинговых компаний (Часть 1) — Введение
В этой вводной статье я расскажу о нескольких уроках, которые можно извлечь из испытаний, которые применяют проп-трейдинговые компании. Это особенно актуально для новичков и тех, кто изо всех сил пытается найти свое место в мире трейдинга. В следующей статье будет рассмотрена реализация кода.
Нейросети — это просто (Часть 43): Освоение навыков без функции вознаграждения Нейросети — это просто (Часть 43): Освоение навыков без функции вознаграждения
Проблема обучения с подкреплением заключается в необходимости определения функции вознаграждения, которая может быть сложной или затруднительной для формализации, и для решения этой проблемы исследуются подходы, основанные на разнообразии действий и исследовании окружения, которые позволяют обучаться навыкам без явной функции вознаграждения.
Алгоритм докупки: симуляция мультивалютной торговли Алгоритм докупки: симуляция мультивалютной торговли
В данной статье мы создадим математическую модель для симуляции мультивалютного ценообразования и завершим исследование принципа диверсификации в рамках поиска механизмов увеличения эффективности торговли, которое я начал в предыдущей статье с теоретических выкладок.
Разработка торговой системы на основе индикатора Fibonacci Разработка торговой системы на основе индикатора Fibonacci
Это продолжение серии статей, в которых мы учимся строить торговые системы на основе самых популярных индикаторов. Очередным техническим инструментом станет индикатор Фибоначчи. Давайте разберем, как написать программу по сигналам этого индикатора.