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

Разработка торгового советника с нуля (Часть 19): Новая система ордеров (II)

MetaTrader 5Трейдинг | 21 июля 2022, 08:49
1 282 0
Daniel Jose
Daniel Jose

1.0 - Введение

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

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

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


2.0 - Планирование

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

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

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

Если вы считаете, что добиться этого непросто, посмотрите на следующий фрагмент кода класса C_HLineTrade:

inline void SetLineOrder(ulong ticket, double price, eHLineTrade hl, bool select)
{
        string sz0 = def_NameHLineTrade + (string)hl + (string)ticket, sz1;
                                
        ObjectCreate(Terminal.Get_ID(), sz0, OBJ_HLINE, 0, 0, 0);

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

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


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

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


3.0 - Реализация

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

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

class C_ObjectsTrade
{
//+------------------------------------------------------------------+
#define def_NameObjectsTrade 	"SMD_OT"
#define def_SeparatorInfo       '*'
#define def_IndicatorTicket0    1
//+------------------------------------------------------------------+
        protected:
                enum eIndicatorTrade {IT_NULL, IT_STOP= 65, IT_TAKE, IT_PRICE};
//+------------------------------------------------------------------+

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

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

Даже в рамках объявления protected у нас есть следующие функции:

inline double GetLimitsTake(void) const { return m_Limits.TakeProfit; }
//+------------------------------------------------------------------+
inline double GetLimitsStop(void) const { return m_Limits.StopLoss; }
//+------------------------------------------------------------------+
inline bool GetLimitsIsBuy(void) const { return m_Limits.IsBuy; }
//+------------------------------------------------------------------+
inline void SetLimits(double take, double stop, bool isbuy)
{
        m_Limits.IsBuy = isbuy;
        m_Limits.TakeProfit = (m_Limits.TakeProfit < 0 ? take : (isbuy ? (m_Limits.TakeProfit > take ? m_Limits.TakeProfit : take) : (take > m_Limits.TakeProfit ? m_Limits.TakeProfit : take)));
        m_Limits.StopLoss = (m_Limits.StopLoss < 0 ? stop : (isbuy ? (m_Limits.StopLoss < stop ? m_Limits.StopLoss : stop) : (stop < m_Limits.StopLoss ? m_Limits.StopLoss : stop)));
}
//+------------------------------------------------------------------+
inline int GetBaseFinanceLeveRange(void) const { return m_BaseFinance.Leverange; }
//+------------------------------------------------------------------+
inline int GetBaseFinanceIsDayTrade(void) const { return m_BaseFinance.IsDayTrade; }
//+------------------------------------------------------------------+
inline int GetBaseFinanceTakeProfit(void) const { return m_BaseFinance.FinanceTake; }
//+------------------------------------------------------------------+
inline int GetBaseFinanceStopLoss(void) const { return m_BaseFinance.FinanceStop; }

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

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

Теперь мы подошли к частным объявлениям.

//+------------------------------------------------------------------+
        private :
                string  m_SelectObj;
                struct st00
                {
                        double  TakeProfit,
                                StopLoss;
                        bool    IsBuy;
                }m_Limits;
                struct st01
                {
                        int     FinanceTake,
                                FinanceStop,
                                Leverange;
                        bool    IsDayTrade;
                }m_BaseFinance;
//+------------------------------------------------------------------+
                string MountName(ulong ticket, eIndicatorTrade it)
                {
                        return StringFormat("%s%c%c%c%d", def_NameObjectsTrade, def_SeparatorInfo, (char)it, def_SeparatorInfo, ticket);
                }
//+------------------------------------------------------------------+

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

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

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

inline string CreateIndicatorTrade(ulong ticket, eIndicatorTrade it, bool select)
{
        string sz0 = MountName(ticket, it);
                                
        ObjectCreate(Terminal.Get_ID(), sz0, OBJ_HLINE, 0, 0, 0);
        ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_COLOR, (it == IT_PRICE ? clrBlue : (it == IT_STOP ? clrFireBrick : clrForestGreen)));
        ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_WIDTH, 1);
        ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_STYLE, STYLE_DASHDOT);
        ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_SELECTABLE, select);
        ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_SELECTED, false);
        ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_BACK, true);
        ObjectSetString(Terminal.Get_ID(), sz0, OBJPROP_TOOLTIP, (string)ticket + " "+StringSubstr(EnumToString(it), 3, 10));
                                
        return sz0;
}

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

Затем мы перегружаем эту же функцию, что видно чуть ниже.

inline string CreateIndicatorTrade(ulong ticket, double price, eIndicatorTrade it, bool select)
{
        if (price <= 0)
        {
                RemoveIndicatorTrade(ticket, it);
                return NULL;
        }
        string sz0 = CreateIndicatorTrade(ticket, it, select);
        ObjectMove(Terminal.Get_ID(), sz0, 0, 0, price);
                                
        return sz0;
}

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

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

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

class C_Router : public C_ObjectsTrade
{

// ... Внутренний код класса ....

                void UpdatePosition(int iAdjust = -1)
                        {

// ... Внутренний код функции ...

                                for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
                                {
                                        ul = PositionGetInteger(POSITION_TICKET);
                                        m_bContainsPosition = true;
                                        CreateIndicatorTrade(ul, PositionGetDouble(POSITION_PRICE_OPEN), IT_PRICE, false);
                                        CreateIndicatorTrade(ul, take = PositionGetDouble(POSITION_TP), IT_TAKE, true);
                                        CreateIndicatorTrade(ul, stop = PositionGetDouble(POSITION_SL), IT_STOP, true);

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

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

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

class C_OrderView : public C_Router
{
        private  :
//+------------------------------------------------------------------+
        public   :
//+------------------------------------------------------------------+
                void InitBaseFinance(int nContracts, int FinanceTake, int FinanceStop, bool b1)
                        {                       
                                SetBaseFinance(nContracts, FinanceTake, FinanceStop, b1);
                                CreateIndicatorTrade(def_IndicatorTicket0, IT_PRICE, false);
                                CreateIndicatorTrade(def_IndicatorTicket0, IT_TAKE, false);
                                CreateIndicatorTrade(def_IndicatorTicket0, IT_STOP, false);
                        }
//+------------------------------------------------------------------+

// ... Остальной код ....
class C_Router : public C_ObjectsTrade
{

// ... Код класса ...

                void UpdatePosition(int iAdjust = -1)
                        {
// ... Код функции ....
                                for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
                                {
                                        ul = PositionGetInteger(POSITION_TICKET);
                                        m_bContainsPosition = true;
                                        CreateIndicatorTrade(ul, PositionGetDouble(POSITION_PRICE_OPEN), IT_PRICE, false);

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

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

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

bool GetInfosOrder(const string &sparam, ulong &ticket, double &price, eIndicatorTrade &it)
{
        string szRet[];
        char szInfo[];
                                
        if (StringSplit(sparam, def_SeparatorInfo, szRet) < 2) return false;
        if (szRet[0] != def_NameObjectsTrade) return false;
        StringToCharArray(szRet[1], szInfo);
        it = (eIndicatorTrade)szInfo[0];
        ticket = (ulong) StringToInteger(szRet[2]);
        price = ObjectGetDouble(Terminal.Get_ID(), sparam, OBJPROP_PRICE);
                                
        return true;
}

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

Обратите внимание на выделенный код, в частности на функцию StringSplit. Если бы ее не было в MQL5, нам пришлось бы ее кодировать, но, к счастью, в MQL5 она есть, поэтому мы будем использовать эту функцию по полной. Что она делает, так это разбивает название объекта на данные, которые нам важны. Когда название объекта создается, оно моделируется очень специфическим образом, и по этой причине мы также можем отменить это моделирование кодирования, потому что то, что делает функция StringFormat будет отменено функцией StringSplit.

Как видите, остальная часть функции заключается в том, чтобы захватить данные, присутствующие в названии объекта, чтобы мы могли протестировать их и использовать позже. Т.е. MetaTrader 5 генерирует данные за нас, а мы разбиваем их так, чтобы понять, что произошло, а затем сообщаем MetaTrader 5, какие решения принять. Наша работа будет заключаться в том, чтобы MetaTrader 5 работал на нас, я не хочу создавать платформу с НУЛЯ, а моделировать интерфейс и советник с НУЛЯ. Для этого мы должны использовать преимущества каждой минимальной поддержки, которую нам предлагает MetaTrader 5, прежде чем искать внешнее решение.

В коде ниже мы сделаем что-то очень похожее на то, что сделали выше:

inline void RemoveAllsIndicatorTrade(bool bFull)
{
        string sz0, szRet[];
        int i0 = StringLen(def_NameObjectsTrade);
                                
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
        for (int c0 = ObjectsTotal(Terminal.Get_ID(), -1, -1); c0 >= 0; c0--)
        {
                sz0 = ObjectName(Terminal.Get_ID(), c0, -1, -1);
                if (StringSubstr(sz0, 0, i0) == def_NameObjectsTrade)
                {
                        if (!bFull)
                        {
                                StringSplit(sz0, def_SeparatorInfo, szRet);
                                if (StringToInteger(szRet[2]) == def_IndicatorTicket0) continue;
                        }
                }else continue;                                         
                ObjectDelete(Terminal.Get_ID(), sz0);
        }
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
}

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

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

inline void RemoveIndicatorTrade(ulong ticket, eIndicatorTrade it = IT_NULL)
{
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
        if ((it != NULL) && (it != IT_PRICE))
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, it));
        else
        {
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, IT_PRICE));
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, IT_TAKE));
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, IT_STOP));
        }
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
}

Ниже можно увидеть следующую появившуюся подпрограмму:

inline void PositionAxlePrice(double price, ulong ticket, eIndicatorTrade it, int FinanceTake, int FinanceStop, int Leverange, bool isBuy)
{
        double ad = Terminal.GetAdjustToTrade() / (Leverange * Terminal.GetVolumeMinimal());
        ObjectMove(Terminal.Get_ID(), MountName(ticket, it), 0, 0, price);
        if (it == IT_PRICE)
        {
                ObjectMove(Terminal.Get_ID(), MountName(ticket, IT_TAKE), 0, 0, price + Terminal.AdjustPrice(FinanceTake * (isBuy ? ad : (-ad))));
                ObjectMove(Terminal.Get_ID(), MountName(ticket, IT_STOP), 0, 0, price + Terminal.AdjustPrice(FinanceStop * (isBuy ? (-ad) : ad)));
        }
}

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

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

Далее, у нас есть последняя важная функция в классе C_ObjectsTrade, ее можно видеть в следующем коде:

inline double GetDisplacement(const bool IsBuy, const double Vol, eIndicatorTrade it) const
{
        int i0 = (it == IT_TAKE ? m_BaseFinance.FinanceTake : m_BaseFinance.FinanceStop),
            i1 = (it == IT_TAKE ? (IsBuy ? 1 : -1) : (IsBuy ? -1 : 1));
        return (Terminal.AdjustPrice(i0 * (Vol / m_BaseFinance.Leverange) * Terminal.GetAdjustToTrade() / Vol) * i1);
}

Эта функция произведет преобразование между указанными в Chart Trader значениями для ордера, который останется отложенным, или для позиции, которая будет открыта как рыночная.

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

Во-первых, это функция инициализации данных, поступающих от Chart Trader.

void InitBaseFinance(int nContracts, int FinanceTake, int FinanceStop, bool b1)
{                       
        SetBaseFinance(nContracts, FinanceTake, FinanceStop, b1);
        CreateIndicatorTrade(def_IndicatorTicket0, IT_PRICE, false);
        CreateIndicatorTrade(def_IndicatorTicket0, IT_TAKE, false);
        CreateIndicatorTrade(def_IndicatorTicket0, IT_STOP, false);
}

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

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

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

inline void MoveTo(uint Key)
{
        static double local = 0;
        datetime dt;
        bool    bEClick, bKeyBuy, bKeySell, bCheck;
        double  take = 0, stop = 0, price;
                                
        bEClick  = (Key & 0x01) == 0x01;    //Клик по левой кнопке
        bKeyBuy  = (Key & 0x04) == 0x04;    //Нажатый SHIFT
        bKeySell = (Key & 0x08) == 0x08;    //Нажатый CTRL                          
        Mouse.GetPositionDP(dt, price);
        if (bKeyBuy != bKeySell)
        {
                Mouse.Hide();
                bCheck = CheckLimits(price);
        } else Mouse.Show();
        PositionAxlePrice((bKeyBuy != bKeySell ? price : 0), def_IndicatorTicket0, IT_PRICE, (bCheck ? 0 : GetBaseFinanceTakeProfit()), (bCheck ? 0 : GetBaseFinanceStopLoss()), GetBaseFinanceLeveRange(), bKeyBuy);
        if((bEClick) && (bKeyBuy != bKeySell) && (local == 0)) CreateOrderPendent(bKeyBuy, local = price);
        local = (local != price ? 0 : local);
}

А причина в том, что теперь в системе будет новое правило, поэтому функция «похудела» и стала более компактной.


4.0 - Заключение

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

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

Прикрепленные файлы |
EA.zip (12023.87 KB)
Разработка торгового советника с нуля (Часть 20): Новая система ордеров (III) Разработка торгового советника с нуля (Часть 20): Новая система ордеров (III)
Продолжим внедрение новой системы ордеров. Создание такой системы требует хорошего владения MQL5, а также понимания того, как на самом деле работает платформа MetaTrader 5 и какие ресурсы она нам предоставляет.
Нейросети — это просто (Часть 21): Вариационные автоэнкодеры (VAE) Нейросети — это просто (Часть 21): Вариационные автоэнкодеры (VAE)
В прошлой статье мы познакомились с алгоритмом работы автоэнкодера. Как и любой другой алгоритм, он имеет свои достоинства и недостатки. В оригинальной реализации автоэнкодер выполняет задачу максимально разделить объекты из обучающей выборки. А о том, как бороться с некоторыми его недостатками мы поговорим в этой статье.
DoEasy. Элементы управления (Часть 12): Базовый объект-список, WinForms-объекты ListBox и ButtonListBox DoEasy. Элементы управления (Часть 12): Базовый объект-список, WinForms-объекты ListBox и ButtonListBox
В статье создадим базовый объект списков WinForms-объектов и два новых объекта: ListBox и ButtonListBox.
Разработка торговой системы на основе индикатора Parabolic SAR Разработка торговой системы на основе индикатора Parabolic SAR
Это продолжение серии статей, в которых мы учимся строить торговые системы с использованием самых популярных индикаторов. В этой статье мы будем изучать индикатор Parabolic SAR. Также мы разработаем торговую систему для работы в платформе MetaTrader 5, используя несколько простых стратегий.