English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
preview
Разработка торгового советника с нуля (Часть 25): Обеспечиваем надежность системы (II)

Разработка торгового советника с нуля (Часть 25): Обеспечиваем надежность системы (II)

MetaTrader 5Примеры | 14 октября 2022, 16:43
1 414 0
Daniel Jose
Daniel Jose

1.0. Введение

В предыдущей статье "Обеспечиваем надежность системы (I)" мы показали, как изменить некоторые части советника, чтобы сделать систему более надежной и прочной.

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

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

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

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

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

Сервисы помогают различными способами, и в этой серии статей "Доступ к данным в Интернете (II)" и "Доступ к данным в Интернете (III)" мы использовали сервисы для предоставления нам данных очень интересным способом. На самом деле мы могли бы сделать это непосредственно в советнике, но это не самый подходящий метод, как я уже объяснял в других статьях.

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

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

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

Итак, давайте начнем.


2.0. Реализация

2.0.1. Удаление фона советника

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

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Auxiliar\C_Wallpaper.mqh>
//+------------------------------------------------------------------+
input string                    user10 = "Wallpaper_01";        //Использованный BitMap
input char                      user11 = 60;                    //Прозрачность (от 0 до 100)
input C_WallPaper::eTypeImage   user12 = C_WallPaper::IMAGEM;   //Тип фонового изображения
//+------------------------------------------------------------------+
C_Terminal      Terminal;
C_WallPaper WallPaper;
//+------------------------------------------------------------------+
int OnInit()
{
        IndicatorSetString(INDICATOR_SHORTNAME, "WallPaper");
        Terminal.Init();
        WallPaper.Init(user10, user12, user11);

        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        WallPaper.Resize();
        break;
        }
        ChartRedraw();
}
//+------------------------------------------------------------------+

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

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


2.0.2. Преобразуем Volume At Price в индикатор

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

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Tape Reading\C_VolumeAtPrice.mqh>
//+------------------------------------------------------------------+
input color             user0   = clrBlack;                     //Цвет баров
input   char            user1   = 20;                                   //Прозрачность (от 0 до 100 )
input color     user2 = clrForestGreen; //Покупка по рынку
input color     user3 = clrFireBrick;   //Продажа по рынку
//+------------------------------------------------------------------+
C_Terminal                      Terminal;
C_VolumeAtPrice VolumeAtPrice;
//+------------------------------------------------------------------+
int OnInit()
{
        Terminal.Init();
        VolumeAtPrice.Init(user2, user3, user0, user1);
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+
void OnTimer()
{
        VolumeAtPrice.Update();
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        VolumeAtPrice.DispatchMessage(id, sparam);
        ChartRedraw();
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+

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

Видите, мы начали с чего-то простого, затем немного усложнили, но теперь всё становится еще сложнее, ведь мы собираемся удалить Times & Trade из советника.


2.0.3. Преобразуем Times & Trade в индикатор

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

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

Это именно то, что ожидает трейдер, а не тот беспорядок, который виден на картинке выше.

Давайте посмотрим, как выглядит код индикатора Times & Trade, его можно увидеть ниже полностью:

#property copyright "Daniel Jose"
#property version   "1.00"
#property indicator_separate_window
#property indicator_plots 0
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Tape Reading\C_TimesAndTrade.mqh>
//+------------------------------------------------------------------+
C_Terminal        Terminal;
C_TimesAndTrade   TimesAndTrade;
//+------------------------------------------------------------------+
input int     user1 = 2;      //Шкала
//+------------------------------------------------------------------+
bool isConnecting = false;
int SubWin;
//+------------------------------------------------------------------+
int OnInit()
{
        IndicatorSetString(INDICATOR_SHORTNAME, "Times & Trade");
        SubWin = ChartWindowFind();
        Terminal.Init();
        TimesAndTrade.Init(user1);
        EventSetTimer(1);
                
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        if (isConnecting)
                TimesAndTrade.Update();
        return rates_total;
}
//+------------------------------------------------------------------+
void OnTimer()
{
        if (TimesAndTrade.Connect())
        {
                isConnecting = true;
                EventKillTimer();
        }
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        TimesAndTrade.Resize();
        break;
        }
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+

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

У опытных программистов есть правило: Мы что-то удаляем только тогда, когда это точно не работает, в противном случае мы оставляем фрагменты, даже если они на самом деле не скомпилированы. Но как это сделать в линейном коде, где пишутся функции, и мы хотим, чтобы они всегда работали?! Затем возникает первый вопрос: знаете ли вы, как указать компилятору, что нужно скомпилировать, а что нет? Если ответ "нет", то ничего страшного, я не знал, как это сделать, когда начинал, но это очень помогает. Поэтому давайте узнаем, как можно это сделать.

В некоторых языках есть так называемые директивы компиляции, которые могут называться препроцессором, в зависимости от автора, но идея одна и та же - указать компилятору, что надо скомпилировать и как должна быть выполнена компиляция. Пока я думаю, что это легко понять, но есть очень специфический тип директив, который помогает нам изолировать код намеренно, чтобы мы могли тестировать конкретные вещи, а именно директивы условной компиляции. Если они хорошо используются, они позволяют нам делать что-то очень интересное, компилировать один и тот же код разными способами, и это именно то, что сделано здесь на примере Times & Trade. Мы выбираем, кто будет отвечать за генерацию условной компиляции, будет ли это советник или индикатор?! После определения этого параметра, мы создаем директиву определения #define, а затем с помощью условной директивы #ifdef #else #endif сообщаем компилятору, как будет скомпилирован код.

Возможно, это сложно для понимания, но давайте посмотрим на практике, как это работает на самом деле.

В коде советника мы определяем и добавляем строки, выделенные ниже:

#define def_INTEGRATION_WITH_EA
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Trade\Control\C_IndicatorTradeView.mqh>
#ifdef def_INTEGRATION_WITH_EA
        #include <NanoEA-SIMD\Auxiliar\C_Wallpaper.mqh>
        #include <NanoEA-SIMD\Tape Reading\C_VolumeAtPrice.mqh>
        #include <NanoEA-SIMD\Tape Reading\C_TimesAndTrade.mqh>
#endif
//+------------------------------------------------------------------+

Происходит следующее: Если мы хотим скомпилировать советника с классами, которые находятся в файлах MQH, мы должны оставлять директиву #ifdefine def_INTEGRATION_WIT_EA, которая определена в советнике. Это заставит советник содержать все классы, которые мы берем и вставляем в индикаторы. Но если мы хотим удалить эти индикаторы сразу, нам не нужно удалять код и строки, а единственное, что необходимо сделать - это закомментировать определение. Это можно сделать просто преобразовав строку, где объявлена директива, в строку комментария. Вот так просто, поскольку компилятор не найдет директиву, она будет выдана как несуществующая, а поскольку она не существует, каждый раз, когда встречается условная директива #ifdef def_INTEGRATION_WITH_EA, она будет полностью проигнорирована, а код между ней и частью #endif не будет скомпилирован, как видно из примера выше.

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

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include <NanoEA-SIMD\Auxiliar\C_Canvas.mqh>
#ifdef def_INTEGRATION_WITH_EA

#include <NanoEA-SIMD\Auxiliar\C_FnSubWin.mqh>

class C_TimesAndTrade : private C_FnSubWin

#else

class C_TimesAndTrade

#endif
{
//+------------------------------------------------------------------+
#define def_SizeBuff 2048
#define macro_Limits(A) (A & 0xFF)
#define def_MaxInfos 257
#define def_ObjectName "TimesAndTrade"
//+------------------------------------------------------------------+
        private :
                string  m_szCustomSymbol;

// ... Остальной код класса ....

}

Здесь видно, что у нас очень странный код для тех, кто не использует директивы компиляции. Директива def_INTEGRATION_WITH_EA объявляется в советнике, не забывайте об этом. Тогда происходит следующее: когда компилятор будет генерировать объектный код из этого файла, он примет следующее отношение: Если компилируемый файл является советником и имеет объявленную директиву, компилятор сгенерирует объектный код с частями, которые находятся между условными директивами #ifdef def_INTEGRATION_WITH_EA и #else. Обычно в таких случаях мы используем директиву #else, а в случае, если компилируется другой файл, например, указатель чья директива def_INTEGRATION_WITH_EA не определена, то будет компилироваться всё, что находится между директивами #else y #endif. Вот так это работает.

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


2.0.4. Ускоряем работу советника

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

void OnTick()
{
        Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, TradeView.SecureChannelPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]);
#ifdef def_INTEGRATION_WITH_EA
        TimesAndTrade.Update();
#endif 
}

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

inline void Update(void)
{
        MqlTick Tick[];
        MqlRates Rates[def_SizeBuff];
        int i0, p1, p2 = 0;
        int iflag;
        long lg1;
        static int nSwap = 0;
        static long lTime = 0;

        if (m_ConnectionStatus < 3) return;
        if ((i0 = CopyTicks(Terminal.GetFullSymbol(), Tick, COPY_TICKS_ALL, m_MemTickTime, def_SizeBuff)) > 0)
        {

// ... Остальной код ...

        }
}

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

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

Второе событие находится в событии OnTime, оно уже обновлено до приведенного ниже формата:

#ifdef def_INTEGRATION_WITH_EA
void OnTimer()
{
        VolumeAtPrice.Update();
        TimesAndTrade.Connect();
}
#endif 

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

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

inline virtual void Update(void)
{
        MqlTick Tick[];
        int i1, p1;

        if (macroCheckUsing == false) return;
        if ((i1 = CopyTicksRange(Terminal.GetSymbol(), Tick, COPY_TICKS_TRADE, m_Infos.memTimeTick)) > 0)
        {
                if (m_Infos.CountInfos == 0)
                {
                        macroSetInteger(OBJPROP_TIME, m_Infos.StartTime = macroRemoveSec(Tick[0].time));
                        m_Infos.FirstPrice = Tick[0].last;
                }                                               
                for (p1 = 0; (p1 < i1) && (Tick[p1].time_msc == m_Infos.memTimeTick); p1++);
                for (int c0 = p1; c0 < i1; c0++) SetMatrix(Tick[c0]);
                if (p1 == i1) return;
                m_Infos.memTimeTick = Tick[i1 - 1].time_msc;
                m_Infos.CurrentTime = macroRemoveSec(Tick[i1 - 1].time);
                Redraw();
        };      
};

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

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

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

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

void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
{
#define def_IsBuy(A) ((A == ORDER_TYPE_BUY_LIMIT) || (A == ORDER_TYPE_BUY_STOP) || (A == ORDER_TYPE_BUY_STOP_LIMIT) || (A == ORDER_TYPE_BUY))

        ulong ticket;
        
        if (trans.symbol == Terminal.GetSymbol()) switch (trans.type)
        {
                case TRADE_TRANSACTION_DEAL_ADD:
                case TRADE_TRANSACTION_ORDER_ADD:
                        ticket = trans.order;
                        ticket = (ticket == 0 ? trans.position : ticket);
                        TradeView.IndicatorInfosAdd(ticket);
                        TradeView.UpdateInfosIndicators(0, ticket, trans.price, trans.price_tp, trans.price_sl, trans.volume, (trans.position > 0 ? trans.deal_type == DEAL_TYPE_BUY : def_IsBuy(trans.order_type)));
                        break;
                case TRADE_TRANSACTION_ORDER_DELETE:
                         if (trans.order != trans.position) TradeView.RemoveIndicator(trans.order);
                         else TradeView.UpdateInfosIndicators(0, trans.position, trans.price, trans.price_tp, trans.price_sl, trans.volume, trans.deal_type == DEAL_TYPE_BUY);
                         if (!PositionSelectByTicket(trans.position)) TradeView.RemoveIndicator(trans.position);
                        break;
                case TRADE_TRANSACTION_ORDER_UPDATE:
                        TradeView.UpdateInfosIndicators(0, trans.order, trans.price, trans.price_tp, trans.price_sl, trans.volume, def_IsBuy(trans.order_type));
                        break;
                case TRADE_TRANSACTION_POSITION:
                        TradeView.UpdateInfosIndicators(0, trans.position, trans.price, trans.price_tp, trans.price_sl, trans.volume, trans.deal_type == DEAL_TYPE_BUY);
                        break;
        }
                
#undef def_IsBuy
}

Хотя приведенный выше код работает, он, мягко говоря, УЖАСНЫЙ; количество бесполезных вызовов, генерируемых приведенным выше кодом, граничит с безумием, ничто не сможет улучшить советник в плане стабильности и надежности, если приведенный выше код не может быть исправлен.

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

void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
{
#define def_IsBuy(A) ((A == ORDER_TYPE_BUY_LIMIT) || (A == ORDER_TYPE_BUY_STOP) || (A == ORDER_TYPE_BUY_STOP_LIMIT) || (A == ORDER_TYPE_BUY))

        if (trans.type == TRADE_TRANSACTION_HISTORY_ADD) if (trans.symbol == Terminal.GetSymbol()) TradeView.RemoveIndicator(trans.position);
        if (trans.type == TRADE_TRANSACTION_REQUEST) if ((request.symbol == Terminal.GetSymbol()) && (result.retcode == TRADE_RETCODE_DONE)) switch (request.action)
        {
                case TRADE_ACTION_PENDING:
                        TradeView.IndicatorAdd(request.order);
                        break;
                case TRADE_ACTION_SLTP:
                        TradeView.UpdateIndicators(request.position, request.tp, request.sl, request.volume, def_IsBuy(request.type));
                        break;
                case TRADE_ACTION_DEAL:
                        TradeView.RemoveIndicator(request.position);
                        break;
                case TRADE_ACTION_REMOVE:
                        TradeView.RemoveIndicator(request.order);
                        break;
                case TRADE_ACTION_MODIFY:
                        TradeView.UpdateIndicators(request.order, request.tp, request.sl, request.volume, def_IsBuy(request.type));
                        break;
        }
                        
#undef def_IsBuy
}

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

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

if (trans.type == TRADE_TRANSACTION_HISTORY_ADD) if (trans.symbol == Terminal.GetSymbol()) TradeView.RemoveIndicator(trans.position);
if (trans.type == TRADE_TRANSACTION_REQUEST) if ((request.symbol == Terminal.GetSymbol()) && (result.retcode == TRADE_RETCODE_DONE)) switch (request.action)
{

//... внутренний код ...

}

Строка, выделенная ЗЕЛЕНЫМ, будет проверять каждый раз, когда в истории происходит сделка, совпадает ли актив, о котором сообщается, с активом, наблюдаемым советником. Если это так, то классу C_IndicatorTradeView будет отослана команда удалить индикатор с графика, но эта сделка может произойти в 2 момента: когда ордер становится позицией или когда позиция закрывается. Нужно помнить, что я использую только режим NETTING, а не HEDGING. Таким образом, что бы ни случилось, индикатор будет удален с графика.

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

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

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


2.0.5. Сокращаем количество объектов, создаваемых классом C_IndicatorTradeView

В статье Разработка торгового советника с нуля (Часть 23) я представил довольно абстрактную, но весьма интересную концепцию смещения ордеров или лимитов. Концепция заключалась в использовании призраков или теней позиций. Они определяют и отображают на графике то, что видит торговый сервер, и используются до тех пор, пока не произойдет фактическое движение. У этой модели есть маленькая проблема: она добавляет объекты, о которых должен заботиться MetaTrader 5, а добавленные объекты в большинстве случаев не нужны, поэтому MetaTrader 5 остается со списком объектов, который зачастую полон бесполезными или редко используемыми вещами.

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

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

Это будет очень весело и интересно.

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

struct st00
{
        eIndicatorTrade it;
        bool            bIsBuy,
			bIsDayTrade;
        ulong           ticket;
        double          vol,
                        pr,
                        tp,
                        sl;
}m_Selection;

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

Таким образом, наш индикатор-призрак теперь будет иметь свой собственный индекс:

#define def_IndicatorGhost      2

И это привело к тому, что моделирование имен также изменилось на код ниже:

#define macroMountName(ticket, it, ev) StringFormat("%s%c%llu%c%c%c%c", def_NameObjectsTrade, def_SeparatorInfo,\
                                                                        ticket, def_SeparatorInfo,              \
                                                                        (char)it, def_SeparatorInfo,            \
                                                                        (char)(ticket <= def_IndicatorGhost ? ev + 32 : ev))

Это кажется мелочью, но вскоре это многое изменит... Давайте продолжим...

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

#define macroSetLinePrice(ticket, it, price) ObjectSetDouble(Terminal.Get_ID(), macroMountName(ticket, it, EV_LINE), OBJPROP_PRICE, price)
#define macroGetLinePrice(ticket, it) ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, it, EV_LINE), OBJPROP_PRICE)

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

#define macroCreateIndicator(A, B, C, D)        {                                                                       \       
                m_TradeLine.Create(ticket, sz0 = macroMountName(ticket, A, EV_LINE), C);                                \
                m_BackGround.Create(ticket, sz0 = macroMountName(ticket, A, EV_GROUND), B);                             \
                m_BackGround.Size(sz0, (A == IT_RESULT ? 84 : 92), (A == IT_RESULT ? 34 : 22));                         \
                m_EditInfo1.Create(ticket, sz0 = macroMountName(ticket, A, EV_EDIT), D, 0.0);                           \
                m_EditInfo1.Size(sz0, 60, 14);                                                                          \
                if (A != IT_RESULT)     {                                                                               \
                        m_BtnMove.Create(ticket, sz0 = macroMountName(ticket, A, EV_MOVE), "Wingdings", "u", 17, C);    \
                        m_BtnMove.Size(sz0, 21, 23);                                                                    \
                                        }else                   {                                                       \
                        m_EditInfo2.Create(ticket, sz0 = macroMountName(ticket, A, EV_PROFIT), clrNONE, 0.0);           \
                        m_EditInfo2.Size(sz0, 60, 14);  }                                                               \
                                                }

                void CreateIndicator(ulong ticket, eIndicatorTrade it)
                        {
                                string sz0;
                                
                                switch (it)
                                {
                                        case IT_TAKE    : macroCreateIndicator(it, clrForestGreen, clrDarkGreen, clrNONE); break;
                                        case IT_STOP    : macroCreateIndicator(it, clrFireBrick, clrMaroon, clrNONE); break;
                                        case IT_PENDING : macroCreateIndicator(it, clrCornflowerBlue, clrDarkGoldenrod, def_ColorVolumeEdit); break;
                                        case IT_RESULT  : macroCreateIndicator(it, clrDarkBlue, clrDarkBlue, def_ColorVolumeResult); break;
                                }
                                m_BtnClose.Create(ticket, macroMountName(ticket, it, EV_CLOSE), def_BtnClose);
                        }
#undef macroCreateIndicator

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

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

Функция IndicatorAdd была изменена, мы удалили вырезанные фрагменты...

inline void IndicatorAdd(ulong ticket)
                        {
                                char ret;
                                
                                if (ticket == def_IndicatorTicket0) ret = -1; else
                                {
                                        if (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_PENDING, EV_LINE, false), OBJPROP_PRICE) != 0) return;
                                        if (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_RESULT, EV_LINE, false), OBJPROP_PRICE) != 0) return;
                                        if ((ret = GetInfosTradeServer(ticket)) == 0) return;
                                }
                                switch (ret)
                                {
                                        case  1:
                                                CreateIndicatorTrade(ticket, IT_RESULT);
                                                PositionAxlePrice(ticket, IT_RESULT, m_InfoSelection.pr);
                                                break;
                                        case -1:
                                                CreateIndicatorTrade(ticket, IT_PENDING);
                                                PositionAxlePrice(ticket, IT_PENDING, m_InfoSelection.pr);
                                                break;
                                }
                                ChartRedraw();
                                UpdateIndicators(ticket, m_InfoSelection.tp, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
				UpdateIndicators(ticket, m_Selection.tp, m_Selection.sl, m_Selection.vol, m_Selection.bIsBuy);
                        } 

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

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

#define macroUpdate(A, B) { if (B > 0) {                                                                \
                if (b0 = (macroGetLinePrice(ticket, A) == 0 ? true : b0)) CreateIndicator(ticket, A);   \
                PositionAxlePrice(ticket, A, B);                                                        \
                SetTextValue(ticket, A, vol, (isBuy ? B - pr : pr - B));                                \
                                        } else RemoveIndicator(ticket, A); }
                                                                        
                void UpdateIndicators(ulong ticket, double tp, double sl, double vol, bool isBuy)
                        {
                                double pr;
                                bool b0 = false;
                                
                                if (ticket == def_IndicatorGhost) pr = m_Selection.pr; else
                                {
                                        pr = macroGetLinePrice(ticket, IT_RESULT);
                                        if ((pr == 0) && (macroGetLinePrice(ticket, IT_PENDING) == 0))
                                        {
                                                CreateIndicator(ticket, IT_PENDING);
                                                PositionAxlePrice(ticket, IT_PENDING, m_Selection.pr);
                                                ChartRedraw();
                                        }
                                        pr = (pr > 0 ? pr : macroGetLinePrice(ticket, IT_PENDING));
                                        SetTextValue(ticket, IT_PENDING, vol);
                                }
                                if (m_Selection.tp > 0) macroUpdate(IT_TAKE, tp);
                                if (m_Selection.sl > 0) macroUpdate(IT_STOP, sl);
                                if (b0) ChartRedraw();
                        }
#undef macroUpdate

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

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

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{

// ... Код ....

        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:

// ... Код ....
                        }else if ((!bMounting) && (bKeyBuy == bKeySell) && (m_Selection.ticket > def_IndicatorGhost))
                        {
                                if (bEClick) SetPriceSelection(price); else MoveSelection(price);
                        }
                        break;

// ... Код ...

                case CHARTEVENT_OBJECT_CLICK:
                        if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
                        {
                                case EV_CLOSE:

// ... Код ...

                                        break;
                                case EV_MOVE:
                                        CreateGhostIndicator(ticket, it);
                                        break;
                        }
                break;
        }
}

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

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


2.0.6 - Как работает функция CreateGhostIndicator

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

CreateGhostIndicator

#define macroSwapName(A, B) ObjectSetString(Terminal.Get_ID(), macroMountName(ticket, A, B), OBJPROP_NAME, macroMountName(def_IndicatorGhost, A, B));
                void CreateGhostIndicator(ulong ticket, eIndicatorTrade it)
                        {
                                if (GetInfosTradeServer(m_Selection.ticket = ticket) != 0)
                                {
                                        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                                        macroSwapName(it, EV_LINE);
                                        macroSwapName(it, EV_GROUND);
                                        macroSwapName(it, EV_MOVE);
                                        macroSwapName(it, EV_EDIT);
                                        macroSwapName(it, EV_CLOSE);
                                        m_TradeLine.SetColor(macroMountName(def_IndicatorGhost, it, EV_LINE), def_IndicatorGhostColor);
                                        m_BackGround.SetColor(macroMountName(def_IndicatorGhost, it, EV_GROUND), def_IndicatorGhostColor);
                                        m_BtnMove.SetColor(macroMountName(def_IndicatorGhost, it, EV_MOVE), def_IndicatorGhostColor);
                                        ObjectDelete(Terminal.Get_ID(), macroMountName(def_IndicatorGhost, it, EV_CLOSE));
                                        m_TradeLine.SpotLight();
                                        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                                        m_Selection.it = it;
                                }else m_Selection.ticket = 0;
                        }
#undef macroSwapName

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



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

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

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

После инициализации советника мы видим следующий поток выполнения, как показано на рисунке ниже:

Поток 1

init_ea  <<<< Поток инициализации системы

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

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

Поток 2

     <<<< Поток инициализации индикатора 0

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

Поток 3

     <<< Поток размещения отложенных ордеров

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

Этот поток довольно странный для тех, кто хотел увидеть что-то линейное.

Поток 4

     <<< Поток удаления ордера или лимитов ордера

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

Всё это прекрасно и замечательно, но всё равно это не дает ответа на вопрос, как появляются призраки, а ведь именно это и есть наша тема.

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

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

Однако, изучая документацию, я нашел информацию, которая привлекла мое внимание: функция ObjectSetString позволяет манипулировать свойством объектов, которое на первый взгляд не имеет ни малейшего смысла, свойство, о котором идет речь - это OBJPROP_NAME. Я был заинтригован этим, почему это разрешается?! Это не имеет смысла. Если объект уже создан, то какой смысл менять его имя?!

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

ObjectDelete(Terminal.Get_ID(), macroMountName(def_IndicatorGhost, it, EV_CLOSE));

Но здесь у нас есть небольшая деталь. Посмотрев документацию функции ObjectSetString, мы видим предупреждение о ее работе:

При переименовании графического объекта одновременно формируются два события, Эти события можно обработать в Expert Advisor или индикаторе посредством функции OnChartEvent():

  • событие удаления объекта со старым именем;
  • событие создания объекта с новым именем.

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

ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);

// ... Безопасный код ...

ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);

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

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

Поток 5

    <<<< Поток выполнения призрака

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


3.0. Заключение

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

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



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

Прикрепленные файлы |
Разработка торгового советника с нуля (Часть 26): Навстречу будущему (I) Разработка торгового советника с нуля (Часть 26): Навстречу будущему (I)
Сегодня мы выведем нашу систему ордеров на новый уровень, но сначала нам нужно решить несколько задач. Сейчас у нас есть разные вопросы, которые связаны с тем, как мы хотим работать и какие вещи мы делаем в течение торгового дня.
DoEasy. Элементы управления (Часть 22): SplitContainer. Изменение свойств созданного объекта DoEasy. Элементы управления (Часть 22): SplitContainer. Изменение свойств созданного объекта
В статье реализуем возможность изменять свойства и внешний вид элемента управления SplitContainer после его создания.
Разработка торгового советника с нуля (Часть 27): Навстречу будущему (II) Разработка торгового советника с нуля (Часть 27): Навстречу будущему (II)
Давайте перейдем к более полноценной системе ордеров непосредственно на графике. В этой статье я вам покажу способ исправить систему ордеров или, скорее, как сделать её более интуитивно понятной.
Популяционные алгоритмы оптимизации: Рой частиц (PSO) Популяционные алгоритмы оптимизации: Рой частиц (PSO)
В данной статье рассмотрим популярный алгоритм "Рой Частиц" (PSO — particle swarm optimisation). Ранее мы обсудили такие важные характеристики алгоритмов оптимизации как сходимость, скорость сходимости, устойчивость, масштабируемость, разработали стенд для тестирования, рассмотрели простейший алгоритм на ГСЧ.