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

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

MetaTrader 5Торговые системы | 7 июня 2022, 09:12
1 630 0
Daniel Jose
Daniel Jose

Введение

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

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


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


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


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

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

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


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


Реализация

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

void CurrentSymbol(void)
{
        MqlDateTime mdt1;
        string sz0, sz1, sz2;
        datetime dt = TimeLocal();
                
        sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3);
        m_Infos.szFullSymbol = _Symbol;
        m_Infos.TypeSymbol = ((sz0 == "WDO") || (sz0 == "DOL") ? WDO : ((sz0 == "WIN") || (sz0 == "IND") ? WIN : OTHER));
        if ((sz0 != "WDO") && (sz0 != "DOL") && (sz0 != "WIN") && (sz0 != "IND")) return;
        sz2 = (sz0 == "WDO" ? "DOL" : (sz0 == "WIN" ? "IND" : sz0));
        sz1 = (sz2 == "DOL" ? "FGHJKMNQUVXZ" : "GJMQVZ");
        TimeToStruct(TimeLocal(), mdt1);
        for (int i0 = 0, i1 = mdt1.year - 2000;;)
        {
                m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1);
                m_Infos.szFullSymbol = StringFormat("%s%s%d", sz2, StringSubstr(sz1, i0, 1), i1);
                if (i0 < StringLen(sz1)) i0++; else
                {
                        i0 = 0;
                        i1++;
                }
                if (macroGetDate(dt) < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_EXPIRATION_TIME))) break;
        }
}

// ... Código da classe ...

inline string GetFullSymbol(void) const { return m_Infos.szFullSymbol; }

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

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

#property copyright "Daniel Jose 07-02-2022 (A)"
#property version   "1.00"
#property description "Este archivo sólo sirve como soporte de indicadores en SubWin"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
input string user01 = "SubSupport";             //Short Name
//+------------------------------------------------------------------+
int OnInit()
{
        IndicatorSetString(INDICATOR_SHORTNAME, user01);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+

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

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Terminal.mqh"
//+------------------------------------------------------------------+
class C_FnSubWin
{
        private :
                string  m_szIndicator;
                int             m_SubWin;
//+------------------------------------------------------------------+
                void Create(const string szIndicator)
                        {
                                int i0;
                                m_szIndicator = szIndicator;
                                if ((i0 = ChartWindowFind(Terminal.Get_ID(), szIndicator)) == -1)
                                        ChartIndicatorAdd(Terminal.Get_ID(), i0 = (int)ChartGetInteger(Terminal.Get_ID(), CHART_WINDOWS_TOTAL), iCustom(NULL, 0, "::" + def_Resource, szIndicator));
                                m_SubWin = i0;
                        }
//+------------------------------------------------------------------+
        public  :
//+------------------------------------------------------------------+
                C_FnSubWin()
                        {
                                m_szIndicator = NULL;
                                m_SubWin = -1;
                        }
//+------------------------------------------------------------------+
                ~C_FnSubWin()
                        {
                                Close();
                        }
//+------------------------------------------------------------------+
                void Close(void)
                        {
                                if (m_SubWin >= 0) ChartIndicatorDelete(Terminal.Get_ID(), m_SubWin, m_szIndicator);
                                m_SubWin = -1;
                        }
//+------------------------------------------------------------------+
inline int GetIdSubWinEA(const string szIndicator = NULL)
                        {
                                if ((szIndicator != NULL) && (m_SubWin < 0)) Create(szIndicator);
                                return m_SubWin;
                        }
//+------------------------------------------------------------------+
inline bool ExistSubWin(void) const { return m_SubWin >= 0; }
//+------------------------------------------------------------------+
};
//+------------------------------------------------------------------+

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

#include "C_ChartFloating.mqh"
#include <NanoEA-SIMD\Auxiliar\C_FnSubWin.mqh>
//+------------------------------------------------------------------+
class C_SubWindow : public C_ChartFloating
{
//+------------------------------------------------------------------+
        private :
                C_FnSubWin      m_fnSubWin;
//+------------------------------------------------------------------+
        public  :
//+------------------------------------------------------------------+
                ~C_SubWindow()
                        {
                                Close();
                        }       
//+------------------------------------------------------------------+
                void Close(void)
                        {
                                m_fnSubWin.Close();
                                CloseAlls();
                        }
//+------------------------------------------------------------------+
inline int GetIdSubWinEA(void)
                        {
                                return m_fnSubWin.GetIdSubWinEA("SubWinSupport");
                        }
//+------------------------------------------------------------------+
inline bool ExistSubWin(void) const { return m_fnSubWin.ExistSubWin(); }
//+------------------------------------------------------------------+
};
//+------------------------------------------------------------------+

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

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


Класс C_TimesAndTrade

Объектный класс C_TimesAndTrade состоит из нескольких небольших частей, каждая из которых отвечает за что-то конкретное. Приведенный ниже код - это первое, что советник вызывает для этого класса:

void Init(const int iScale = 2)
{
        if (!ExistSubWin())
        {
                CreateCustomSymbol();
                CreateChart();
        }
        ObjectSetInteger(Terminal.Get_ID(), m_szObjName, OBJPROP_CHART_SCALE, (iScale > 5 ? 5 : (iScale < 0 ? 0 : iScale)));
}

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

inline void CreateCustomSymbol(void)
{
        m_szCustomSymbol = "_" + Terminal.GetFullSymbol();
        SymbolSelect(Terminal.GetFullSymbol(), true);
        SymbolSelect(m_szCustomSymbol, false);
        CustomSymbolDelete(m_szCustomSymbol);
        CustomSymbolCreate(m_szCustomSymbol, StringFormat("Custom\\Robot\\%s", m_szCustomSymbol), Terminal.GetFullSymbol());
        CustomRatesDelete(m_szCustomSymbol, 0, LONG_MAX);
        CustomTicksDelete(m_szCustomSymbol, 0, LONG_MAX);
        SymbolSelect(m_szCustomSymbol, true);
};

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

SymbolSelect(m_szCustomSymbol, true);

Пользовательский символ будет создан в следующем месте Custom\Robot <Имя символа>. и его начальные данные будут предоставлены исходным символом, это достигается с помощью следующего фрагмента кода:

CustomSymbolCreate(m_szCustomSymbol, StringFormat("Custom\\Robot\\%s", m_szCustomSymbol), Terminal.GetFullSymbol());

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

// ... Код советника

#include <NanoEA-SIMD\Tape Reading\C_TimesAndTrade.mqh>

// ... Код советника

input group "Times & Trade"
input   int     user041 = 2;    //Escala
//+------------------------------------------------------------------+
C_TemplateChart Chart;
C_WallPaper     WallPaper;
C_VolumeAtPrice VolumeAtPrice;
C_TimesAndTrade TimesAndTrade;
//+------------------------------------------------------------------+
int OnInit()
{
// ... Код советника

        TimesAndTrade.Init(user041);
        
        OnTrade();
        EventSetTimer(1);
   
        return INIT_SUCCEEDED;
}

Получаем следующее изображение:


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

inline void Connect(void)
{
        switch (m_ConnectionStatus)
        {
                case 0:
                        if (!TerminalInfoInteger(TERMINAL_CONNECTED)) return; else m_ConnectionStatus = 1;
                case 1:
                        if (!SymbolIsSynchronized(Terminal.GetFullSymbol())) return; else m_ConnectionStatus = 2;
                case 2:
                        m_LastTime = TimeLocal();
                        m_MemTickTime = macroMinusMinutes(60, m_LastTime) * 1000;
                        m_ConnectionStatus = 3;
                default:
                        break;
        }
}

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

void Init(const int iScale = 2)
{
        if (!ExistSubWin())
        {
                CreateCustomSymbol();
                CreateChart();
                m_ConnectionStatus = 0;
        }
        ObjectSetInteger(Terminal.Get_ID(), m_szObjName, OBJPROP_CHART_SCALE, (iScale > 5 ? 5 : (iScale < 0 ? 0 : iScale)));
}

Как только это будет сделано, мы можем видеть функцию захвата.

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

        if (m_ConnectionStatus < 3) return;
        if ((i0 = CopyTicks(Terminal.GetFullSymbol(), Tick, COPY_TICKS_ALL, m_MemTickTime, def_SizeBuff)) > 0)
        {
                for (p1 = 0, p2 = 0; (p1 < i0) && (Tick[p1].time_msc == m_MemTickTime); p1++);
                for (int c0 = p1, c1 = 0; c0 < i0; c0++)
                {
                        if (Tick[c0].volume == 0) continue;
                        iflag = 0;
                        iflag += ((Tick[c0].flags & TICK_FLAG_BUY) == TICK_FLAG_BUY ? 1 : 0);
                        iflag -= ((Tick[c0].flags & TICK_FLAG_SELL) == TICK_FLAG_SELL ? 1 : 0);
                        if (iflag == 0) continue;
                        Rates[c1].high = Tick[c0].ask;
                        Rates[c1].low = Tick[c0].bid;
                        Rates[c1].open = Tick[c0].last;
                        Rates[c1].close = Tick[c0].last + ((Tick[c0].volume > 200 ? 200 : Tick[c0].volume) * (Terminal.GetTypeSymbol() == C_Terminal::WDO ? 0.02 : 1.0) * iflag);
                        Rates[c1].time = m_LastTime;
                        p2++;
                        c1++;
                        m_LastTime += 60;
                }
                CustomRatesUpdate(m_szCustomSymbol, Rates, p2);
                m_MemTickTime = Tick[i0 - 1].time_msc;
        }
}

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

Rates[c1].high = Tick[c0].ask;
Rates[c1].low = Tick[c0].bid;
Rates[c1].open = Tick[c0].last;
Rates[c1].close = Tick[c0].last + ((Tick[c0].volume > 200 ? 200 : Tick[c0].volume) * (Terminal.GetTypeSymbol() == C_Terminal::WDO ? 0.02 : 1.0) * iflag);
Rates[c1].time = m_LastTime;

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

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

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

Заключительную информацию об этой системе можно видеть на рисунке ниже:

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


Заключение

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



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

Прикрепленные файлы |
EA_-_Times_e_Trade.zip (5982.95 KB)
Как прокачаться в машинном обучении (Machine Learning) Как прокачаться в машинном обучении (Machine Learning)
Представляем вашему вниманию подборку материалов, которые будут полезны трейдеру для повышения своих знаний в алготрейдинге. Время простых алгоритмов уходит в прошлое, сейчас сложно добиться успехов без использования машинного обучения и нейронных сетей.
DoEasy. Элементы управления (Часть 7): Элемент управления "Текстовая метка" DoEasy. Элементы управления (Часть 7): Элемент управления "Текстовая метка"
В статье создадим класс объекта элемента управления WinForms "Текстовая метка". Такой объект будет иметь возможность позиционирования в любом месте своего контейнера, а его собственный функционал будет повторять некоторый функционал текстовой метки MS Visual Studio — мы сможем задать для выводимого текста параметры шрифта.
Модель движения цены и ее основные положения (Часть 1): Простейший вариант  модели  и его  приложения Модель движения цены и ее основные положения (Часть 1): Простейший вариант модели и его приложения
Представлены основы математически строгой теории движения цены и функционирования рынка. Строгой математической теории движения цены до настоящего момента еще не было создано, а имелся только ряд неподкрепленных ни статистикой, ни теорией предположений типа, что после таких-то паттернов цена движется так-то.
Разработка торгового советника с нуля (Часть 11): Система кросс-ордеров Разработка торгового советника с нуля (Часть 11): Система кросс-ордеров
Создание системы кросс-ордеров. Есть один вид активов, который очень усложняет жизнь трейдерам — это активы фьючерсных контрактов. Но почему они усложняют жизнь трейдеру?