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

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

MetaTrader 5Трейдинг | 12 июля 2022, 14:26
1 520 0
Daniel Jose
Daniel Jose

Введение

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

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

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


Очень важное замечание

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

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

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

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


1.0. Планирование

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

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

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

Многие трейдеры понятия не имеют, о чем я говорю, но лучше бы им задуматься об этом. Если вы используете систему OCO-ордеров (One Cancel The Other, взаимоотменяемые ордера, когда исполнение одного ордера приводит к автоматической отмене другого) во всех сделках, то вы заметите, что происходит что-то очень любопытное и интересное каждый раз, когда вы открываете или закрываете позицию частью от общего торгуемого объема.

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

Это происходит из-за следующей проверки, присутствующей в советнике:

inline double CheckPosition(void)
{
        double Res = 0, last, sl;
        ulong ticket;
                                
        last = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_LAST);
        for (int i0 = PositionsTotal() - 1; i0 >= 0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ticket = PositionGetInteger(POSITION_TICKET);
                Res += PositionGetDouble(POSITION_PROFIT);
                sl = PositionGetDouble(POSITION_SL);
                if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                {
                        if (last < sl) ClosePosition(ticket);
                }else
                {
                        if ((last > sl) && (sl > 0)) ClosePosition(ticket);
                }
        }
        return Res;
};

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

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

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

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

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


2.0. Реализация новой системы

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

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

Для начала, внесем следующие изменения:


2.0.1. Модификация класса C_Router

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

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

//+------------------------------------------------------------------+
inline bool ExistPosition(void) const { return m_bContainsPosition; }
//+------------------------------------------------------------------+
void UpdatePosition(void)
{
        static int memPositions = 0, memOrder = 0;
        ulong ul;
        int p, o;
                                
        p = PositionsTotal();
        o = OrdersTotal();
        if ((memPositions != p) || (memOrder != o))
        {
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                RemoveAllsLines();
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                memOrder = o;
                memPositions = p;
                m_bContainsPosition = false;
        };
        for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ul = PositionGetInteger(POSITION_TICKET);
                m_bContainsPosition = true;
                SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);
                SetLineOrder(ul, PositionGetDouble(POSITION_TP), HL_TAKE, true);
                SetLineOrder(ul, PositionGetDouble(POSITION_SL), HL_STOP, true);
        }
        for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol())
        {
                SetLineOrder(ul, OrderGetDouble(ORDER_PRICE_OPEN), HL_PRICE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true);
        }
};
//+------------------------------------------------------------------+

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

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

C_Router() : m_bContainsPosition(false) {}

Теперь изменяем функцию размещения отложенных ордеров следующим образом:

ulong CreateOrderPendent(const bool IsBuy, const double Volume, const double Price, const double Take, const double Stop, const bool DayTrade = true)
{
        double last = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_LAST);
                                
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        TradeRequest.action             = TRADE_ACTION_PENDING;
        TradeRequest.symbol             = Terminal.GetSymbol();
        TradeRequest.volume             = Volume;
        TradeRequest.type               = (IsBuy ? (last >= Price ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_BUY_STOP) : (last < Price ? ORDER_TYPE_SELL_LIMIT : ORDER_TYPE_SELL_STOP));
        TradeRequest.price              = NormalizeDouble(Price, Terminal.GetDigits());
        TradeRequest.sl                 = NormalizeDouble((m_bContainsPosition ? 0 : Stop), Terminal.GetDigits());
        TradeRequest.tp                 = NormalizeDouble((m_bContainsPosition ? 0 : Take), Terminal.GetDigits());
        TradeRequest.type_time          = (DayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
        TradeRequest.stoplimit          = 0;
        TradeRequest.expiration         = 0;
        TradeRequest.type_filling       = ORDER_FILLING_RETURN;
        TradeRequest.deviation          = 1000;
        TradeRequest.comment            = "Order Generated by Experts Advisor.";
        if (!Send()) return 0;
                                                                
        return TradeResult.order;
};

Выделенные части — это изменения, которые необходимо реализовать.

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

void UpdatePosition(void)
{
        static int memPositions = 0, memOrder = 0;
        ulong ul;
        int p, o;
                                
        p = PositionsTotal();
        o = OrdersTotal();
        if ((memPositions != p) || (memOrder != o))
        {
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                RemoveAllsLines();
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                memOrder = o;
                memPositions = p;
                m_bContainsPosition = false;
        };
        for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ul = PositionGetInteger(POSITION_TICKET);
                m_bContainsPosition = true;
                SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);
                SetLineOrder(ul, PositionGetDouble(POSITION_TP), HL_TAKE, true);
                SetLineOrder(ul, PositionGetDouble(POSITION_SL), HL_STOP, true);
        }
        for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol())
        {
                if (m_bContainsPosition)
                {
                        ModifyOrderPendent(ul, OrderGetDouble(ORDER_PRICE_OPEN), 0, 0);
                        (OrderSelect(ul) ? 0 : 0);
                }
                SetLineOrder(ul, OrderGetDouble(ORDER_PRICE_OPEN), HL_PRICE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true);
        }
};

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

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

ulong ExecuteOrderInMarket(const bool IsBuy, const double Volume, const double Price, const double Take, const double Stop, const bool DayTrade = true)
{
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        TradeRequest.action             = TRADE_ACTION_DEAL;
        TradeRequest.symbol             = Terminal.GetSymbol();
        TradeRequest.volume             = Volume;
        TradeRequest.type               = (IsBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL);
        TradeRequest.price              = NormalizeDouble(Price, Terminal.GetDigits());
        TradeRequest.sl                 = NormalizeDouble((m_bContainsPosition ? 0 : Stop), Terminal.GetDigits());
        TradeRequest.tp                 = NormalizeDouble((m_bContainsPosition ? 0 : Take), Terminal.GetDigits());
        TradeRequest.type_time          = (DayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
        TradeRequest.stoplimit          = 0;
        TradeRequest.expiration         = 0;
        TradeRequest.type_filling       = ORDER_FILLING_RETURN;
        TradeRequest.deviation          = 1000;
        TradeRequest.comment            = "[ Order Market ] Generated by Experts Advisor.";
        if (!Send()) return 0;
                                                
        return TradeResult.order;
};

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

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

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

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

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


2.0.2. Работа в рамках лимитов

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


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

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


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

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

class C_Router : public C_HLineTrade
{
        protected:
                MqlTradeRequest TradeRequest;
                MqlTradeResult  TradeResult;
        private  :
                bool            m_bContainsPosition;
                struct st00
                {
                        double  TakeProfit,
                                StopLoss;
                        bool    IsBuy;
                }m_Limits;

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

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

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

//+------------------------------------------------------------------+
#define macro_MAX(A, B) (A > B ? A : B)
#define macro_MIN(A, B) (A < B ? A : B)
void UpdatePosition(void)
{
        static int      memPositions = 0, memOrder = 0;
        ulong           ul;
        int             p, o;
        double          price;
        bool            bTest;
                                
        p = PositionsTotal();
        o = OrdersTotal();
        if ((memPositions != p) || (memOrder != o))
        {
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                RemoveAllsLines();
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                memOrder = o;
                memPositions = p;
                m_bContainsPosition = false;
                m_Limits.StopLoss = -1;
                m_Limits.TakeProfit = -1;
                m_Limits.IsBuy = false;
        };
        for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ul = PositionGetInteger(POSITION_TICKET);
                m_bContainsPosition = true;
                SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);
                SetLineOrder(ul, m_Limits.TakeProfit = PositionGetDouble(POSITION_TP), HL_TAKE, true);
                SetLineOrder(ul, m_Limits.StopLoss = PositionGetDouble(POSITION_SL), HL_STOP, true);
                m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;
        }
        for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol())
        {
                price = OrderGetDouble(ORDER_PRICE_OPEN);
                if ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1)) bTest = false; else
                {
                        bTest = ((!m_Limits.IsBuy) && (m_Limits.StopLoss > price));
                        bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price));
                        bTest = (bTest ? bTest : ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price)));
                }
                if ((m_bContainsPosition) && (bTest))
                {
                        ModifyOrderPendent(ul, price, 0, 0);
                        (OrderSelect(ul) ? 0 : 0);
                }
                SetLineOrder(ul, price, HL_PRICE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true);
        }
};
#undef macro_MAX
#undef macro_MIN
//+------------------------------------------------------------------+

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

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

m_Limits.StopLoss = -1;
m_Limits.TakeProfit = -1;
m_Limits.IsBuy = false;

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

SetLineOrder(ul, m_Limits.TakeProfit = PositionGetDouble(POSITION_TP), HL_TAKE, true);
SetLineOrder(ul, m_Limits.StopLoss = PositionGetDouble(POSITION_SL), HL_STOP, true);
m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;

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

 

Если мы «ставим» на продажу, то стоп-лосс укажет на максимум...

 

Если мы делаем ставку на покупку, стоп-лосс укажет на минимум...

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

 

Отложенные ордера за пределами лимитов... типичный случай...

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

price = OrderGetDouble(ORDER_PRICE_OPEN);
if ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1)) bTest = false; else
{
        bTest = ((!m_Limits.IsBuy) && (m_Limits.StopLoss > price));
        bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price));
        bTest = (bTest ? bTest : ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price)));
}

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

  1. Если мы продаем, то цена, по которой будет выставляться отложенный ордер, должна быть больше значения стоп-лосса открытой позиции;
  2. Если мы покупаем, то цена, по которой будет выставляться отложенный ордер, должна быть ниже значения стоп-лосса открытой позиции;
  3. Если система продолжает принимать ордер как OCO-ордер, мы сделаем последнюю проверку, которая будет заключаться в том, чтобы проверить, не выходит ли ордер за пределы позиции.

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

void UpdatePosition(void)
{

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

        for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ul = PositionGetInteger(POSITION_TICKET);
                m_bContainsPosition = true;
                SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);
                SetLineOrder(ul, m_Limits.TakeProfit = PositionGetDouble(POSITION_TP), HL_TAKE, true);
                SetLineOrder(ul, m_Limits.StopLoss = PositionGetDouble(POSITION_SL), HL_STOP, true);
                m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;
        }
        if (AccountInfoInteger(ACCOUNT_TRADE_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
                m_bContainsPosition = false;
        for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol())
        {
                price = OrderGetDouble(ORDER_PRICE_OPEN);
                if (m_bContainsPosition)
                {
                        if ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1)) bTest = false; else
                        {
                                bTest = ((!m_Limits.IsBuy) && (m_Limits.StopLoss > price));
                                bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price));
                                bTest = (bTest ? bTest : ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price)));
                        }
                        if (bTest)
                        {
                                ModifyOrderPendent(ul, price, 0, 0);
                                (OrderSelect(ul) ? 0 : 0);
                        }
                }
                SetLineOrder(ul, price, HL_PRICE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true);
        }
};

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


2.1.0. Настройка системы позиционирования

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

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

Хотя заманчиво делать так всё время, есть вещи, о которых нельзя забывать, когда принимается такое решение, но «давайте пойдем по частям», как сказал бы Джек Потрошитель. В основном, единственное реальное изменение, которое нам нужно будет внести в класс C_OrderView показано ниже в коде:


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;    //Left click
        bKeyBuy  = (Key & 0x04) == 0x04;    //SHIFT pressed
        bKeySell = (Key & 0x08) == 0x08;    //CTRL pressed                          
        Mouse.GetPositionDP(dt, price);
        if (bKeyBuy != bKeySell)
        {
                Mouse.Hide();
                bCheck = CheckLimits(price);
        } else Mouse.Show();
        ObjectMove(Terminal.Get_ID(), m_Infos.szHLinePrice, 0, 0, price = (bKeyBuy != bKeySell ? price : 0));
        ObjectMove(Terminal.Get_ID(), m_Infos.szHLineTake, 0, 0, take = (bCheck ? 0 : price + (m_Infos.TakeProfit * (bKeyBuy ? 1 : -1))));
        ObjectMove(Terminal.Get_ID(), m_Infos.szHLineStop, 0, 0, stop = (bCheck ? 0 : price + (m_Infos.StopLoss * (bKeyBuy ? -1 : 1))));
        if((bEClick) && (bKeyBuy != bKeySell) && (local == 0)) CreateOrderPendent(bKeyBuy, m_Infos.Volume, local = price, take, stop, m_Infos.IsDayTrade);
        local = (local != price ? 0 : local);                           
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLinePrice, OBJPROP_COLOR, (bKeyBuy != bKeySell ? m_Infos.cPrice : clrNONE));
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLineTake, OBJPROP_COLOR, (take > 0 ? m_Infos.cTake : clrNONE));
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLineStop, OBJPROP_COLOR, (stop > 0 ? m_Infos.cStop : clrNONE));
};

Неужели это всё? Да, это всё, что нам нужно сделать. Вся остальная логика находится внутри класса C_Router, а то, что не изменилось - будет выполнено собственной системой обмена сообщениями в MetaTrader 5, так как при изменении списка ордеров (отложенные или от позиции), вызывается подпрограмма OnTrade, и когда это происходит, функция обновления сработает из класса C_Router, который внесет необходимые корректировки. Однако, в этой функции есть некий код, который вам возможно будет очень тяжело найти. На самом деле он находится внутри класса C_Router, его можно увидеть ниже:

#define macro_MAX(A, B) (A > B ? A : B)
#define macro_MIN(A, B) (A < B ? A : B)
inline bool CheckLimits(const double price)
{
        bool bTest = false;
                                
        if ((!m_bContainsPosition) || ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1))) return bTest;
        bTest = ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price));
        if (m_Limits.TakeProfit == 0)
        {
                bTest = (bTest ? bTest : (!m_Limits.IsBuy) && (m_Limits.StopLoss > price));
                bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price));
        }
        return bTest;
};
#undef macro_MAX
#undef macro_MIN

Именно этот код и был внутри функции обновления класса C_Router, его оттуда убрали, а вместо него добавили вызов...


2.2.0. Ограничивать или не ограничивать, вот в чем вопрос

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

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

Еще одна важная деталь: как должен действовать советник?! Должен ли я уведомить трейдера о том, что на активе только что появился простой ордер... Или должен просто поставить лимиты на ордер и сделать его OCO-ордером?

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

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

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

// ... Код ...

input group "Chart Trader"
input int       user20   = 1;              //Leverage factor
input int       user21   = 100;            //Take Profit (financial)
input int       user22   = 75;             //Stop Loss (financial)
input color     user23   = clrBlue;        //Price line color
input color     user24   = clrForestGreen; //Take Profit line color
input color     user25   = clrFireBrick;   //Stop Loss line color
input bool      user26   = true;           //Day Trade?
input bool      user27   = true;           //Always set loose order limits

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

void OnTrade()
{
        Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, OrderView.UpdateRoof(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eROOF_DIARY]);
        OrderView.UpdatePosition(user27);
}

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

Теперь мы должны вернуться к классу C_Router и добавить в него 3 новые функции. Их можно увидеть ниже:

//+------------------------------------------------------------------+
void SetFinance(const int Contracts, const int Take, const int Stop)
{
        m_Limits.Contract = Contracts;
        m_Limits.FinanceTake = Take;
        m_Limits.FinanceStop = Stop;
}
//+------------------------------------------------------------------+
inline double GetDisplacementTake(const bool IsBuy, const double Vol) const
{
        return (Terminal.AdjustPrice(m_Limits.FinanceTake * (Vol / m_Limits.Contract) * Terminal.GetAdjustToTrade() / Vol) * (IsBuy ? 1 : -1));
}
//+------------------------------------------------------------------+
inline double GetDisplacementStop(const bool IsBuy, const double Vol) const
{
        return (Terminal.AdjustPrice(m_Limits.FinanceStop * (Vol / m_Limits.Contract) * Terminal.GetAdjustToTrade() / Vol) * (IsBuy ? -1 : 1));
}
//+------------------------------------------------------------------+

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

Иными словами, у нас уже есть откуда взять значения, которые мы будем использовать, чтобы советник мог минимально настроить OCO-ордер при срабатывании отложенного ордера. Однако, как вы уже можете подозревать, нам придется внести новое изменение в код обновления класса C_Router, и эти изменения вы можете увидеть ниже:

void UpdatePosition(bool bAdjust)
{
        static int      memPositions = 0, memOrder = 0;
        ulong           ul;
        int             p, o;
        long            info;
        double          price, stop, take, vol;
        bool            bIsBuy, bTest;
                        
        p = PositionsTotal();
        o = OrdersTotal();
        if ((memPositions != p) || (memOrder != o))
        {
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                RemoveAllsLines();
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                memOrder = o;
                memPositions = p;
                m_bContainsPosition = false;
                m_Limits.StopLoss = -1;
                m_Limits.TakeProfit = -1;
                m_Limits.IsBuy = false;
        };
        for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ul = PositionGetInteger(POSITION_TICKET);
                m_bContainsPosition = true;
                SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);
                SetLineOrder(ul, take = PositionGetDouble(POSITION_TP), HL_TAKE, true);
                SetLineOrder(ul, stop = PositionGetDouble(POSITION_SL), HL_STOP, true);
                m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;
                m_Limits.TakeProfit = (m_Limits.TakeProfit < 0 ? take : (m_Limits.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 : (m_Limits.IsBuy ? (m_Limits.StopLoss < stop ? m_Limits.StopLoss : stop) : (stop < m_Limits.StopLoss ? m_Limits.StopLoss : stop)));
        }
        if ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
                m_bContainsPosition = false;
        for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol())
        {
                price = OrderGetDouble(ORDER_PRICE_OPEN);
                take = OrderGetDouble(ORDER_TP);
                stop = OrderGetDouble(ORDER_SL);
		bTest = CheckLimits(price);
                if ((take == 0) && (stop == 0) && (bAdjust) && (!bTest))
                {
                        info = OrderGetInteger(ORDER_TYPE);
                        vol = OrderGetDouble(ORDER_VOLUME_CURRENT);
                        bIsBuy = ((info == ORDER_TYPE_BUY_LIMIT) || (info == ORDER_TYPE_BUY_STOP) || (info == ORDER_TYPE_BUY_STOP_LIMIT) || (info == ORDER_TYPE_BUY));
                        take = price + GetDisplacementTake(bIsBuy, vol);
                        stop = price + GetDisplacementStop(bIsBuy, vol);
                        ModifyOrderPendent(ul, price, take, stop);
                }
                if ((take != 0) && (stop != 0) && (bTest))
                        ModifyOrderPendent(ul, price, take = 0, stop = 0);
                SetLineOrder(ul, price, HL_PRICE, true);
                SetLineOrder(ul, take, HL_TAKE, true);
                SetLineOrder(ul, stop, HL_STOP, true);
        }
};

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


Заключение

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

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




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

Прикрепленные файлы |
Видео: Настройка MetaTrader 5 и MQL5 для простой автоматизированной торговли Видео: Настройка MetaTrader 5 и MQL5 для простой автоматизированной торговли
В этом небольшом видеокурсе вы узнаете, как скачать, установить и настроить MetaTrader 5 для автоматизированной торговли. Вы также узнаете, как настроить график и параметры автоматизированной торговли. Вы проведете свое первое тестирование на истории и узнаете, как импортировать советника, который может самостоятельно торговать 24 часа в сутки 7 дней в неделю, избавляя вас от необходимости сидеть перед экраном.
Нейросети — это просто (Часть 20): Автоэнкодеры Нейросети — это просто (Часть 20): Автоэнкодеры
Мы продолжаем изучение алгоритмов обучения без учителя. Возможно, у читателя может возникнуть вопрос об соответствии последних публикаций теме нейронных сетей. В новой статье мы возвращаемся к использованию нейронных сетей.
DoEasy. Элементы управления (Часть 11): WinForms-объекты — группы, WinForms-объект CheckedListBox DoEasy. Элементы управления (Часть 11): WinForms-объекты — группы, WinForms-объект CheckedListBox
В статье рассмотрим группирование WinForms-объектов и создадим объект-список объектов CheckBox.
Разработка торговой системы на основе индикатора ADX Разработка торговой системы на основе индикатора ADX
Эта статья продолжает серию о построении торговых систем с использованием самых популярных индикаторов. На этот раз мы поговорим об индикаторе ADX (Average Directional Index, Индекс среднего направленного движения). Мы подробно изучим этот индикатор, чтобы понять, чем он может быть полезен в торговле. Также с помощью простых стратегий мы узнаем, как его использовать. Изучая самую суть вещей, мы можем получить больше информации и использовать это с максимальной выгодой.