Создание многомодульных советников
Введение
На сегодняшний день существует несколько подходов в программировании: модульный, объектно-ориентированный и структурный. В статье речь пойдёт о модульном программировании применительно к торговым роботам.
Модульное программирование — метод разработки программ, предполагающий разделение программы на независимые модули.
Основное правило модульного программирования — принцип "разделяй и властвуй". Удобство использования модульной архитектуры заключается в возможности обновления (замены) модуля, без необходимости изменения остальной системы.
В основе модульного программирования лежат три основных концепции.
- Принцип утаивания информации Парнаса. Модуль служит для сокрытия информации и алгоритма решения конкретной задачи. Модуль в дальнейшем может быть заменён на другой.
- Аксиома модульности Коуэна. Модуль — независимая программная единица, служащая для выполнения определённой функции программы.
- Сборочное программирование Цейтина. Модули — это программные "кирпичи", из которых строится программа.
Единственная альтернатива модульности — монолитная программа. Она не совсем удобна: если возникает необходимость изменить или дополнить какие-либо функции программы, то надо редактировать код советника, а это в большинстве случаев может сделать либо автор кода, либо другой опытный программист. А если монолитная программа вдобавок ещё и скомпилирована, то редактировать такой продукт может исключительно собственник авторских прав, и не факт, что вы с ним договоритесь. На этом фоне намного удобнее выглядит возможность изменения важных функций программы, не привлекая ее автора, а используя собственный потенциал или услуги сторонних разработчиков.
Рис. 1. Абстрактная схема модульного торгового робота
Принцип многомодульности
Модульное программирование — это искусство разбиения задачи на некоторое число подзадач, реализуемых в виде отдельных модулей (файлов). В общем случае программный модуль — это отдельная программа, или функционально законченная автономно компилируемая программная единица, некоторым образом идентифицируемая и объединяемая с вызываемым модулем. Другими словами, модуль — функционально законченный фрагмент программы, оформленный в виде отдельного скомпилированного файла, предназначенный для использования в других программах.
При определении набора модулей, реализующих функции конкретного алгоритма, необходимо учитывать следующее:
- каждый модуль вызывается на выполнение вышестоящим модулем и, закончив работу, возвращает управление вызвавшему его модулю;
- принятие основных решений в алгоритме выносится на максимально "высокий" по иерархии уровень;
- модули независимы по данным друг от друга;
- модули не зависят от предыстории обращения к ним.
Итак, резюмируя всё вышесказанное, модульная программа — это такая программа, в которой любую часть логической структуры можно изменить, не вызывая изменений в остальных частях.
Основные характеристики модуля:
- один вход и один выход — на входе программный модуль получает определённый набор исходных данных, обрабатывает их и возвращает один набор результирующих данных, т.е. реализуется принцип IPO (Input-Process-Output);
- функциональная завершённость — чтобы выполнить отдельную функцию, модуль выполняет полный перечень регламентированных операций, достаточный для завершения начатой обработки;
- логическая независимость — результат работы программного модуля зависит только от исходных данных, но не зависит от работы других модулей;
- слабые информационные связи с другими программными модулями — обмен информацией между модулями должен быть по возможности минимизирован.
На языке MQL5 можно написать три вида программ: эксперт, индикатор или скрипт. Для главного модуля лучше всего подойдёт формат эксперта, в котором будет организовано управление всеми модулями и разместятся торговые функции. А вот остальные модули могут быть реализованы, например, в виде индикаторов. Индикаторы действительно идеально подходят для формирования модуля: данные, рассчитанные по заданному алгоритму, можно сохранить в индикаторных буферах и передать при необходимости в многомодульный советник. Эксперт, в свою очередь, может использовать или игнорировать эти данные, в зависимости от поставленной задачи. В каких-то проектах, возможно, будет оправдано использование экспертов в качестве внешних модулей, но при этом нужно детально продумать механизм обмена данными.
Наверняка вы не раз использовали в своих торговых советниках модульные технологии: например, пользовательские индикаторы в качестве модулей генерации и фильтрации торговых сигналов.
Самое рациональное решение, на мой взгляд, выглядит так: весь базовый функционал сосредоточен в главном модуле и не требует участия внешних модулей. В свою очередь, внешние модули нужны для адаптации к различным рыночным условиям и улучшения торговой стратегии. Набор функций программы определяет не автор кода или стратегии, а трейдер-пользователь торгового робота. При этом важно отметить, что никто не нарушает законных прав друг друга.
Главный модуль — эксперт
Главный модуль, в котором сосредоточено управление всем проектом, — самый важный в иерархии советника. В нём обязательно должны быть размещены торговые функции, без которых любая торговая стратегия бессмысленна.
Рассмотрим в статье создание многомодульного советника на конкретном примере, взятом из CodeBase. Исходный советник торгует фиксированным лотом в канале индикатора iBands с разворотом позиции на границах канала. Эксперт полностью самодостаточен и не требует каких-либо внешних программ.
Не каждый советник может быть многомодульным, а только лишь тот, в котором заложена такая возможность.
Так что же надо добавить в код, чтобы превратить его в модульный проект?
- Объявить внешние модули (индикаторы), которые в дальнейшем может применять пользователь.
- Добавить необходимый функционал для их интеграции.
- Подготовить документацию для разработчиков внешних модулей (включить функцию генерации документации в отдельном файле). Так, для разработки внешних модулей понадобится информация о структуре данных, которые могут быть корректно использованы главным модулем. Например, в рассматриваемом примере модуль управления капиталом должен транслировать в советник размер лота, а модуль сопровождения позиции — расстояние в пунктах от текущей цены.
В итоге преобразований получился модульный советник, в который можно интегрировать до семи внешних модулей.
- Модуль №1 — модуль управления капиталом. На выходе он дает размер лота.
- Модуль №2 — модуль сопровождения позиции и установки SL. На выходе он дает расстояние до стоп-лосса в пунктах от цены открытия позиции.
- Модуль №3 — модуль сопровождения позиции и установки TP. На выходе он дает расстояние до тейк-профита в пунктах от цены открытия позиции.
- Модуль №4 — модуль сопровождения позиции и установки трейлинг-стопа. На выходе он дает расстояние до стоп-лосса в пунктах от текущей цены.
- Модуль №5 — модуль генерации торговых сигналов. На выходе он дает значение сигнала.
- Модуль №6 — модуль фильтрации торговых сигналов. На выходе он дает значение фильтра.
- Модуль №7 — модуль сопровождения позиции и установки уровня безубытка. На выходе он дает расстояние до стоп-лосса от цены открытия позиции.
Рис. 2. Функция OnInit() и инициализация внешних модулей
Рис. 3. Функция OnTick() и чтение данных из внешних модулей
Рис. 4. Функция OnTrade() и чтение данных из внешних модулей
Рис. 5. Функция генерации торговых сигналов и чтение данных из внешних модулей
//****** project (module expert): test_module_exp.mq5 //+------------------------------------------------------------------+ //| The program code is generated Modular project generator | //| Copyright 2010-2017, Sergey Pavlov (DC2008) | //| http://www.mql5.com/ru/users/dc2008 | //+------------------------------------------------------------------+ #property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)" #property link "http://www.mql5.com/ru/users/dc2008" #property link "1.00" #property link "Example of a multimodule expert: project TEST Main module." #property link "The project uses 7 external modules." //--- #include <Trade\Trade.mqh> //--- MqlTick last_tick; CTrade trade; //--- input int e_bands_period=80; // Moving average period int e_bands_shift=0; // shift input double e_deviation=3.0; // Number of standard deviations input ENUM_APPLIED_PRICE e_applied_price=PRICE_CLOSE; // Price type input bool on_module=false; // whether or not to use plug-ins //--- double lot=0.01; // Fixed lot double min_lot=0.01; // Minimum allowable lot bool on_trade=false; // Trade function flag //--- Variable for storing the indicator iBands handle int handle_Bands; //--- module 1 bool on_lot=false; int handle_m1; //--- module 2 bool on_SL=false; int handle_m2; //--- module 3 bool on_TP=false; int handle_m3; //--- module 4 bool on_Trail=false; int handle_m4; //--- module 5 bool on_signals=false; int handle_m5; //--- module 6 bool on_Filter=false; int handle_m6; //--- module 7 bool on_Breakeven=false; int handle_m7; //+------------------------------------------------------------------+ //| Structure of trading signals | //+------------------------------------------------------------------+ struct sSignal { bool Buy; // Buy signal bool Sell; // Sell signal }; //+------------------------------------------------------------------+ //| Trading signals generator | //+------------------------------------------------------------------+ sSignal Buy_or_Sell() { sSignal res={false,false}; //--- MODULE 5 if(on_signals) { // If there is an additional module double buffer_m5[]; ArraySetAsSeries(buffer_m5,true); if(CopyBuffer(handle_m5,0,0,1,buffer_m5)<0) return(res); if(buffer_m5[0]<-1) res.Sell=true; if(buffer_m5[0]>1) res.Buy=true; } //--- MODULE 6 if(on_Filter) { // If there is an additional module double buffer_m6[]; ArraySetAsSeries(buffer_m6,true); if(CopyBuffer(handle_m6,0,0,1,buffer_m6)<0) return(res); lot=buffer_m6[0]; if(buffer_m6[0]<1) res.Buy=false; if(buffer_m6[0]>-1) res.Sell=false; } //--- //--- Indicator buffers double UpperBuffer[]; double LowerBuffer[]; double MiddleBuffer[]; ArraySetAsSeries(MiddleBuffer,true); CopyBuffer(handle_Bands,0,0,1,MiddleBuffer); ArraySetAsSeries(UpperBuffer,true); CopyBuffer(handle_Bands,1,0,1,UpperBuffer); ArraySetAsSeries(LowerBuffer,true); CopyBuffer(handle_Bands,2,0,1,LowerBuffer); //--- Timeseries double L[]; double H[]; ArraySetAsSeries(L,true); CopyLow(_Symbol,_Period,0,1,L); ArraySetAsSeries(H,true); CopyHigh(_Symbol,_Period,0,1,H); if(H[0]>UpperBuffer[0]&& L[0]>MiddleBuffer[0]) res.Sell=true; if(L[0]<LowerBuffer[0] && H[0]<MiddleBuffer[0]) res.Buy=true; //--- return(res); } //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Create the indicator handle handle_Bands=iBands(_Symbol,_Period,e_bands_period,e_bands_shift,e_deviation,e_applied_price); if(handle_Bands==INVALID_HANDLE) return(INIT_FAILED); else on_trade=true; if(on_module) { //--- MODULE 1 //--- check: whether there is an external module? handle_m1=iCustom(NULL,0,"Market\\test_module_MM"); if(handle_m1!=INVALID_HANDLE) on_lot=true; //--- MODULE 2 //--- check: whether there is an external module? handle_m2=iCustom(NULL,0,"Market\\test_module_SL"); if(handle_m2!=INVALID_HANDLE) on_SL=true; //--- MODULE 3 //--- check: whether there is an external moduleь? handle_m3=iCustom(NULL,0,"Market\\test_module_TP"); if(handle_m3!=INVALID_HANDLE) on_TP=true; //--- MODULE 4 //--- check: whether there is an external module? handle_m4=iCustom(NULL,0,"Market\\test_module_Trail"); if(handle_m4!=INVALID_HANDLE) on_Trail=true; //--- MODULE 5 //--- check: whether there is an external module? handle_m5=iCustom(NULL,0,"Market\\test_module_signals"); if(handle_m5!=INVALID_HANDLE) on_signals=true; //--- MODULE 6 //--- check: whether there is an external module? handle_m6=iCustom(NULL,0,"Market\\test_module_Filter"); if(handle_m6!=INVALID_HANDLE) on_Filter=true; //--- MODULE 7 //--- check: whether there is an external module? handle_m7=iCustom(NULL,0,"Market\\test_module_Breakeven"); if(handle_m7!=INVALID_HANDLE) on_Breakeven=true; } //--- Minimum allowable volume for trading operationsn min_lot=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { double Equity=AccountInfoDouble(ACCOUNT_EQUITY); //--- MODULE 1 if(on_lot) { // If there is an additional module double buffer_m1[]; ArraySetAsSeries(buffer_m1,true); if(CopyBuffer(handle_m1,0,0,1,buffer_m1)<0) return; lot=buffer_m1[0]; } //--- MODULE 4 if(on_Trail) if(PositionSelect(_Symbol)) if(PositionGetDouble(POSITION_PROFIT)>0) { // If there is an additional module double buffer_m4[]; ArraySetAsSeries(buffer_m4,true); if(CopyBuffer(handle_m4,0,0,1,buffer_m4)<0) return; double TR=buffer_m4[0]; if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) if(PositionGetDouble(POSITION_PRICE_CURRENT)-TR*_Point>PositionGetDouble(POSITION_PRICE_OPEN)) { double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)-TR*_Point; if(price_SL>PositionGetDouble(POSITION_SL)) trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),0); } if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) if(PositionGetDouble(POSITION_PRICE_CURRENT)+TR*_Point<PositionGetDouble(POSITION_PRICE_OPEN)) { double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)+TR*_Point; if(price_SL<PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL)==NULL) trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),0); } } //--- MODULE 7 if(on_Breakeven) if(PositionSelect(_Symbol)) if(PositionGetDouble(POSITION_PROFIT)>0) { // If there is an additional module double buffer_m7[]; ArraySetAsSeries(buffer_m7,true); if(CopyBuffer(handle_m7,0,0,1,buffer_m7)<0) return; double TRB=buffer_m7[0]; if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) if(PositionGetDouble(POSITION_PRICE_CURRENT)-TRB*_Point>PositionGetDouble(POSITION_PRICE_OPEN)) { double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)-5*_Point; if(price_SL>PositionGetDouble(POSITION_SL)) trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),PositionGetDouble(POSITION_TP)); } if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) if(PositionGetDouble(POSITION_PRICE_CURRENT)+TRB*_Point<PositionGetDouble(POSITION_PRICE_OPEN)) { double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)+5*_Point; if(price_SL<PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL)==NULL) trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),PositionGetDouble(POSITION_TP)); } } //--- if(lot<min_lot) lot=min_lot; //--- if(on_trade) { sSignal signal=Buy_or_Sell(); //--- The value of the required and free margin double margin,free_margin=AccountInfoDouble(ACCOUNT_MARGIN_FREE); //--- BUY if(signal.Buy) { if(!PositionSelect(_Symbol)) { SymbolInfoTick(_Symbol,last_tick); if(OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,NormalizeDouble(lot,2),last_tick.ask,margin)) if(margin<Equity) trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,NormalizeDouble(lot,2),last_tick.ask,0,0,"BUY: new position"); } else { if(PositionGetDouble(POSITION_PROFIT)<0) return; if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) { trade.PositionClose(_Symbol); SymbolInfoTick(_Symbol,last_tick); if(OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,NormalizeDouble(lot,2),last_tick.ask,margin)) if(margin<Equity) trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,NormalizeDouble(lot,2),last_tick.ask,0,0,"BUY: reversal"); } } } //--- SELL if(signal.Sell) { if(!PositionSelect(_Symbol)) { SymbolInfoTick(_Symbol,last_tick); if(OrderCalcMargin(ORDER_TYPE_SELL,_Symbol,NormalizeDouble(lot,2),last_tick.bid,margin)) if(margin<Equity) trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,NormalizeDouble(lot,2),last_tick.bid,0,0,"SELL: new position"); } else { if(PositionGetDouble(POSITION_PROFIT)<0) return; if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) { trade.PositionClose(_Symbol); SymbolInfoTick(_Symbol,last_tick); if(OrderCalcMargin(ORDER_TYPE_SELL,_Symbol,NormalizeDouble(lot,2),last_tick.bid,margin)) if(margin<Equity) trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,NormalizeDouble(lot,2),last_tick.bid,0,0,"SELL: reversal"); } } } } } //+------------------------------------------------------------------+ //| Trade function | //+------------------------------------------------------------------+ void OnTrade() { if(on_SL && on_TP) // If there is an additional module { //--- MODULE 2 double buffer_m2[]; ArraySetAsSeries(buffer_m2,true); if(CopyBuffer(handle_m2,0,0,1,buffer_m2)<0) return; double SL=buffer_m2[0]; //--- MODULE 3 double buffer_m3[]; ArraySetAsSeries(buffer_m3,true); if(CopyBuffer(handle_m3,0,0,1,buffer_m3)<0) return; double TP=buffer_m3[0]; //--- Position modification if(PositionSelect(_Symbol)) if(PositionGetDouble(POSITION_SL)==0) { //--- BUY if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) { double priceTP=PositionGetDouble(POSITION_PRICE_OPEN)+TP*_Point; double priceSL=PositionGetDouble(POSITION_PRICE_OPEN)-SL*_Point; trade.PositionModify(_Symbol,NormalizeDouble(priceSL,Digits()),NormalizeDouble(priceTP,Digits())); } //--- SELL if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) { double priceTP=PositionGetDouble(POSITION_PRICE_OPEN)-TP*_Point; double priceSL=PositionGetDouble(POSITION_PRICE_OPEN)+SL*_Point; trade.PositionModify(_Symbol,NormalizeDouble(priceSL,Digits()),NormalizeDouble(priceTP,Digits())); } } } }
Если в каталоге, указанном в программе, не окажется необходимых модулей (файлов), то торговый робот будет использовать функционал, заложенный по умолчанию. Таким образом, отсутствие внешних модулей не окажет критического влияния на работоспособность советника.
Наиболее важные модули
Как из монолитной программы можно сделать многомодульный советник? Модульный проект начинается с анализа общей задачи и выделения функционально замкнутых фрагментов, которые в дальнейшем можно оформить в виде скомпилированных модулей. При этом необходимо выбрать наиболее типичные функции, которые могут существенно изменить работу советника и базируются на различных алгоритмах. Не секрет, что в большинстве советников применяются одни и те же процедуры:
- модуль управления капиталом (рисками);
- модуль сопровождения позиций (SL и TP);
- модуль трейлинг-стопа;
- модуль генерации торговых сигналов;
- модуль фильтрации сигналов.
Есть множество вариантов реализации каждого из перечисленных модулей. В этой статье покажем самые примитивные решения, поскольку для нас сейчас важнее сама технология модульного программирования, чем многострочный функционал.
Технология создания вспомогательных модулей
Вспомогательный (внешний) модуль — это индикатор, который выполняет определенную функцию и размещает выходные данные в индикаторных буферах. Главный модуль при необходимости использует эти данные. Таким образом советник советник адаптируется под требования трейдера, который использует данную торговую стратегию. Один и тот же исходный советник может быть пересобран под каждый конкретный финансовый инструмент или брокера. Фактически, в руках у трейдера появляется конструктор, из которого можно собрать неограниченное количество торговых роботов.
Программирование — это трудоёмкий процесс. Несмотря на наличие творческой составляющей, он содержит очень много рутинных и однотипных операций, которые лучше автоматизировать. Автоматизация, кроме всего прочего, повышает производительность программирования и сокращает количество ошибок.
К статье приложен генератор модулей, с помощью которого можно за несколько секунд сформировать до 8 файлов, связанных в один многомодульный проект. Это значительно упрощает и ускоряет процес разработки и сборки (см. видео).
Видео 1. Панель управления генератором многомодульных проектов
В панели управления можно указать, какие модули нужно сгенерировать для создаваемого проекта. В нашем примере создаётся проект "test". В зависимости от выбранной комбинации модулей, генератор автоматически сформирует код, в котором не будет ненужных блоков и файлов.
Сгенерированные файлы помещаются в папку "Files" (см. рис. 2). Имя главного модуля "test_module_exp.mq5" состоит из имени проекта "test" и приставки "_module_exp.mq5". Его необходимо переместить в папку "Experts", а остальные файлы внешних модулей — в папку "Indicators\Market".
Рис. 6. Сгенерированные файлы проекта "test".
После этого скомпилируем все файлы и приступим к тестированию многомодульного проекта.
Видео 2. Компиляция сгенерированных файлов проекта "test"
На создание аналогичного проекта вручную уйдёт не один час, и начинать, очевидно, придётся с главного модуля. После определения внешних модулей, которые впоследствии можно будет подключить к проекту, переходим к проектированию и программированию. Главное, что необходимо контролировать — это значения выходных данных вспомогательных модулей, которые ожидает главный модуль. Поскольку модули — это индикаторы, а индикаторные буферы содержат значения исключительно вещественного типа, то в главном модуле нужно предусмотреть конвертацию переменных из вещественного типа в тип, соответствующий алгоритму.
Внешние модули желательно спроектировать так, чтобы их можно было вызывать в главном модуле без входных параметров, т.е. по умолчанию. Такой механизм вызова упрощает проектирование системы управления внешними данными.
Рассмотрим подробнее, какие внешние модули наиболее востребованы в торговых стратегиях.
Пример 1: модуль управления капиталом
Этот внешний модуль рассчитывает объём лота для открытия очередного ордера. Ниже реализован самый простой вариант расчёта торгового объёма (в процентах от доступных средств на депозите):
//****** project (module MM): test_module_MM_ind.mq5 //+------------------------------------------------------------------+ //| The program code is generated Modular project generator | //| Copyright 2010-2017, Sergey Pavlov (DC2008) | //| http://www.mql5.com/ru/users/dc2008 | //+------------------------------------------------------------------+ #property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)" #property link "http://www.mql5.com/ru/users/dc2008" #property link "1.00" #property link "Example of a multimodule expert: project test module MM" //--- Display indicator in the chart window #property indicator_chart_window //--- Number of buffers to calculate the indicator #property indicator_buffers 1 //--- Number of graphic series in the indicator #property indicator_plots 1 //--- input double lot_perc=0.1; // Percentage of Equity value double Buffer1[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- ArraySetAsSeries(Buffer1,true); SetIndexBuffer(0,Buffer1,INDICATOR_DATA); return(INIT_SUCCEEDED); }; //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate (const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], const double& high[], const double& low[], const double& close[], const long& tick_volume[], const long& volume[], const int &spread[]) { double Equity=AccountInfoDouble(ACCOUNT_EQUITY); //--- calculation of the lot of equity Buffer1[0]=NormalizeDouble(Equity*lot_perc/1000.0,2); // Lot size determination function if(Buffer1[0]<0.01) Buffer1[0]=0.01; return(rates_total); };
Если вызвать этот модуль по умолчанию (без указания значений входных параметров), то главный модуль получит размер разрешённого лота для совершения сделки в размере 0,1% от доступных средств. Пример вызова этого модуля из главной программы:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { double Equity=AccountInfoDouble(ACCOUNT_EQUITY); //--- MODULE 1 if(on_lot) { // If there is an additional module double buffer_m1[]; ArraySetAsSeries(buffer_m1,true); if(CopyBuffer(handle_m1,0,0,1,buffer_m1)<0) return; lot=buffer_m1[0]; } ... }
Пример 2: модуль сопровождения позиции (SL, TP и трейлинг)
Установка стоп-лосса (SL) и тейк-профита (TP) — это один из способов сопровождения открытой позиции. Поскольку в разных торговых стратегиях используются различные комбинации расчёта и выставления SL и TP, то напрашивается вариант с разделением на два модуля: отдельно для SL и TP. Но если мы всё-таки решаем объединить SL и TP в одном модуле, то их значения следует разместить в разных индикаторных буферах.
Модуль установки SL:
//****** project (module SL): test_module_SL_ind.mq5 //+------------------------------------------------------------------+ //| The program code is generated Modular project generator | //| Copyright 2010-2017, Sergey Pavlov (DC2008) | //| http://www.mql5.com/ru/users/dc2008 | //+------------------------------------------------------------------+ #property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)" #property link "http://www.mql5.com/ru/users/dc2008" #property link "1.00" #property link "Example of a multimodule expert: project test module SL" //--- Display indicator in the chart window #property indicator_chart_window //--- Number of buffers to calculate the indicator #property indicator_buffers 1 //--- Number of graphic series in the indicator #property indicator_plots 1 //--- double Buffer1[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- ArraySetAsSeries(Buffer1,true); SetIndexBuffer(0,Buffer1,INDICATOR_DATA); return(INIT_SUCCEEDED); }; //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate (const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], const double& high[], const double& low[], const double& close[], const long& tick_volume[], const long& volume[], const int &spread[]) { double SL=100; // SL in points //--- calculation of the SL Buffer1[0]=SL; return(rates_total); };
Модуль установки TP:
//****** project (module TP): test_module_TP_ind.mq5 //+------------------------------------------------------------------+ //| The program code is generated Modular project generator | //| Copyright 2010-2017, Sergey Pavlov (DC2008) | //| http://www.mql5.com/ru/users/dc2008 | //+------------------------------------------------------------------+ #property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)" #property link "http://www.mql5.com/ru/users/dc2008" #property link "1.00" #property link "Example of a multimodule expert: project test module TP" //--- Display indicator in the chart window #property indicator_chart_window //--- Number of buffers to calculate the indicator #property indicator_buffers 1 //--- Number of graphic series in the indicator #property indicator_plots 1 //--- double Buffer1[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- ArraySetAsSeries(Buffer1,true); SetIndexBuffer(0,Buffer1,INDICATOR_DATA); return(INIT_SUCCEEDED); }; //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate (const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], const double& high[], const double& low[], const double& close[], const long& tick_volume[], const long& volume[], const int &spread[]) { double TP=100; // TP in points //--- calculation of the TP Buffer1[0]=TP; return(rates_total); };
В приведённых кодах показан самый наглядный вариант для расчёта значений SL и TP — в пунктах. Вернее сказать, он не расчитывается, а задаётся константой. При этом значения указаны прямо в программе, а не во входных параметрах. Это сделано, чтобы продемонстрировать вариант реализации внешних модулей без входных данных. Такой код "на коленке" сможет написать любой начинающий программист.
Вызов рассмотренных модулей я рекомендую размещать в функции OnTrade. Выглядит это примерно вот так:
//+------------------------------------------------------------------+ //| Trade function | //+------------------------------------------------------------------+ void OnTrade() { if(on_SL && on_TP) // If there is an additional module { //--- MODULE 2 double buffer_m2[]; ArraySetAsSeries(buffer_m2,true); if(CopyBuffer(handle_m2,0,0,1,buffer_m2)<0) return; double SL=buffer_m2[0]; //--- MODULE 3 double buffer_m3[]; ArraySetAsSeries(buffer_m3,true); if(CopyBuffer(handle_m3,0,0,1,buffer_m3)<0) return; double TP=buffer_m3[0]; //--- Position modification if(PositionSelect(_Symbol)) if(PositionGetDouble(POSITION_SL)==0) { //--- BUY if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) { double priceTP=PositionGetDouble(POSITION_PRICE_OPEN)+TP*_Point; double priceSL=PositionGetDouble(POSITION_PRICE_OPEN)-SL*_Point; trade.PositionModify(_Symbol,NormalizeDouble(priceSL,Digits()),NormalizeDouble(priceTP,Digits())); } //--- SELL if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) { double priceTP=PositionGetDouble(POSITION_PRICE_OPEN)-TP*_Point; double priceSL=PositionGetDouble(POSITION_PRICE_OPEN)+SL*_Point; trade.PositionModify(_Symbol,NormalizeDouble(priceSL,Digits()),NormalizeDouble(priceTP,Digits())); } } } }
Кроме статичных значений SL и TP, установленных сразу после открытия позиции, часто используется трейлинг-стоп, или плавающий SL. Чаще всего его выставляют после того, как позиция становится прибыльной. Посмотрите самый очевидный вариант реализации: мы задаём в пунктах отстояние SL от текущей цены.
//****** project (module Trail): test_module_Trail_ind.mq5 //+------------------------------------------------------------------+ //| The program code is generated Modular project generator | //| Copyright 2010-2017, Sergey Pavlov (DC2008) | //| http://www.mql5.com/ru/users/dc2008 | //+------------------------------------------------------------------+ #property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)" #property link "http://www.mql5.com/ru/users/dc2008" #property link "1.00" #property link "Example of a multimodule expert: project test module Trail" //--- Display indicator in the chart window #property indicator_chart_window //--- Number of buffers to calculate the indicator #property indicator_buffers 1 //--- Number of graphic series in the indicator #property indicator_plots 1 //--- double Buffer1[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- ArraySetAsSeries(Buffer1,true); SetIndexBuffer(0,Buffer1,INDICATOR_DATA); return(INIT_SUCCEEDED); }; //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate (const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], const double& high[], const double& low[], const double& close[], const long& tick_volume[], const long& volume[], const int &spread[]) { double TR=50; // Trail in points //--- calculation of Trail Buffer1[0]=TR; return(rates_total); };
Здесь, как и в предыдущих кодах для SL и TP, отстояние для расчёта трейлинг-стопа задано константой для упрощения программы и чтения.
Вызов модуля трейлинг-стопа нужно реализовать в функции OnTick, потому что текущая цена изменяется на каждом тике и контролировать уровень стопа необходимо постоянно. А вот изменять его или нет — это уже решает главный модуль. Получив значение отступа в пунктах, советник модифицирует позицию и передвигает уровень SL в сторону роста прибыли.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { ... //--- MODULE 4 if(on_Trail) if(PositionSelect(_Symbol)) if(PositionGetDouble(POSITION_PROFIT)>0) { // If there is an additional module double buffer_m4[]; ArraySetAsSeries(buffer_m4,true); if(CopyBuffer(handle_m4,0,0,1,buffer_m4)<0) return; double TR=buffer_m4[0]; if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) if(PositionGetDouble(POSITION_PRICE_CURRENT)-TR*_Point>PositionGetDouble(POSITION_PRICE_OPEN)) { double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)-TR*_Point; if(price_SL>PositionGetDouble(POSITION_SL)) trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),0); } if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) if(PositionGetDouble(POSITION_PRICE_CURRENT)+TR*_Point<PositionGetDouble(POSITION_PRICE_OPEN)) { double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)+TR*_Point; if(price_SL<PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL)==NULL) trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),0); } } ... }
Есть и еще один метод сопровождения позиции — выставление SL в точке "безубытка". При срабатывании SL позиция закроется с нулевым результатом или с заранее заданной прибылью. Модуль может выглядеть примерно так:
//****** project (module Breakeven): test_module_Breakeven_ind.mq5 //+------------------------------------------------------------------+ //| The program code is generated Modular project generator | //| Copyright 2010-2017, Sergey Pavlov (DC2008) | //| http://www.mql5.com/ru/users/dc2008 | //+------------------------------------------------------------------+ #property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)" #property link "http://www.mql5.com/ru/users/dc2008" #property link "1.00" #property link "Example of a multimodule expert: project test module Breakeven" //--- Display indicator in the chart window #property indicator_chart_window //--- Number of buffers to calculate the indicator #property indicator_buffers 1 //--- Number of graphic series in the indicator #property indicator_plots 1 //--- double Buffer1[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- ArraySetAsSeries(Buffer1,true); SetIndexBuffer(0,Buffer1,INDICATOR_DATA); return(INIT_SUCCEEDED); }; //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate (const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], const double& high[], const double& low[], const double& close[], const long& tick_volume[], const long& volume[], const int &spread[]) { double Breakeven=100; // Breakeven in points //--- calculation of the Breakeven Buffer1[0]=Breakeven; return(rates_total); };
В этом модуле задаётся отступ текущей цены от цены открытия позиции в пунктах для выставления SL в точку "безубытка". Вызов модуля безубыточности также нужно разместить в функции OnTick.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { ... //--- MODULE 7 if(on_Breakeven) if(PositionSelect(_Symbol)) if(PositionGetDouble(POSITION_PROFIT)>0) { // If there is an additional module double buffer_m7[]; ArraySetAsSeries(buffer_m7,true); if(CopyBuffer(handle_m7,0,0,1,buffer_m7)<0) return; double TRB=buffer_m7[0]; if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) if(PositionGetDouble(POSITION_PRICE_CURRENT)-TRB*_Point>PositionGetDouble(POSITION_PRICE_OPEN)) { double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)-5*_Point; if(price_SL>PositionGetDouble(POSITION_SL)) trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),PositionGetDouble(POSITION_TP)); } if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) if(PositionGetDouble(POSITION_PRICE_CURRENT)+TRB*_Point<PositionGetDouble(POSITION_PRICE_OPEN)) { double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)+5*_Point; if(price_SL<PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL)==NULL) trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),PositionGetDouble(POSITION_TP)); } } ... }
Пример 3: модуль генерации торговых сигналов
Пожалуй, это самый сложный в реализации модуль. От него должны приходить сигналы на совершение торговых операций: выставление ордеров, закрытие позиций и т.п. Сложность его проектирования заключается в том, что почти все индикаторы должны быть адаптированы под торговые условия. Практически не существует индикаторов с одними и теми же входными параметрами, которые генерируют рабочие сигналы для разных финансовых инструментов.
Очевидно, что главная программа не должна настраивать сигнальные модули самостоятельно, поскольку их можно насоздавать столько, что работа модульного проекта будет парализована. Следовательно, индикаторы, генерирующие торговые сигналы, должны быть подготовлены заранее, еще до подключения к общему проекту. Поговорим об этом чуть позже, в разделе, посвященном оптимизации многомодульных экспертов. А сейчас посмотрите на код модуля торговых сигналов:
//****** project (module signals): test_module_signals_ind.mq5 //+------------------------------------------------------------------+ //| The program code is generated Modular project generator | //| Copyright 2010-2017, Sergey Pavlov (DC2008) | //| http://www.mql5.com/ru/users/dc2008 | //+------------------------------------------------------------------+ #property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)" #property link "http://www.mql5.com/ru/users/dc2008" #property link "1.00" #property link "Example of a multimodule expert: project test module Trading signals" //--- Display indicator in the chart window #property indicator_chart_window //--- Number of buffers to calculate the indicator #property indicator_buffers 1 //--- Number of graphic series in the indicator #property indicator_plots 1 //--- double Buffer1[]; //--- Variable for storing the indicator iBands handle int handle_Bands; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { handle_Bands=iBands(_Symbol,_Period,20,0,3.5,PRICE_CLOSE); //--- ArraySetAsSeries(Buffer1,true); SetIndexBuffer(0,Buffer1,INDICATOR_DATA); return(INIT_SUCCEEDED); }; //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate (const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], const double& high[], const double& low[], const double& close[], const long& tick_volume[], const long& volume[], const int &spread[]) { double signal=0.0; ArraySetAsSeries(high,true); ArraySetAsSeries(low,true); //--- Indicator buffers double UpperBuffer[]; double LowerBuffer[]; ArraySetAsSeries(UpperBuffer,true); CopyBuffer(handle_Bands,1,0,1,UpperBuffer); ArraySetAsSeries(LowerBuffer,true); CopyBuffer(handle_Bands,2,0,1,LowerBuffer); //--- calculation of the Trading signals if(high[0]>UpperBuffer[0]) signal=-2.0; if(low[0]<LowerBuffer[0]) signal=2.0; Buffer1[0]=signal; return(rates_total); };
В индикаторный буфер сигнального модуля записываются значения:
- 2.0 — если сформировался сигнал BUY;
- 0.0 — если нет торговых сигналов;
- -2.0 — если сформировался сигнал SELL.
Использовать полученные значения модуля торговых сигналов лучше всего в специальной функции главного модуля — например, вот так:
//+------------------------------------------------------------------+ //| Trading signals generator | //+------------------------------------------------------------------+ sSignal Buy_or_Sell() { sSignal res={false,false}; //--- MODULE 5 if(on_signals) { // If there is an additional module double buffer_m5[]; ArraySetAsSeries(buffer_m5,true); if(CopyBuffer(handle_m5,0,0,1,buffer_m5)<0) return(res); if(buffer_m5[0]<-1) res.Sell=true; if(buffer_m5[0]>1) res.Buy=true; } //--- MODULE 6 if(on_Filter) { // If there is an additional module double buffer_m6[]; ArraySetAsSeries(buffer_m6,true); if(CopyBuffer(handle_m6,0,0,1,buffer_m6)<0) return(res); lot=buffer_m6[0]; if(buffer_m6[0]<1) res.Buy=false; if(buffer_m6[0]>-1) res.Sell=false; } //--- //--- Indicator buffers double UpperBuffer[]; double LowerBuffer[]; double MiddleBuffer[]; ArraySetAsSeries(MiddleBuffer,true); CopyBuffer(handle_Bands,0,0,1,MiddleBuffer); ArraySetAsSeries(UpperBuffer,true); CopyBuffer(handle_Bands,1,0,1,UpperBuffer); ArraySetAsSeries(LowerBuffer,true); CopyBuffer(handle_Bands,2,0,1,LowerBuffer); //--- Timeseries double L[]; double H[]; ArraySetAsSeries(L,true); CopyLow(_Symbol,_Period,0,1,L); ArraySetAsSeries(H,true); CopyHigh(_Symbol,_Period,0,1,H); if(H[0]>UpperBuffer[0]&& L[0]>MiddleBuffer[0]) res.Sell=true; if(L[0]<LowerBuffer[0] && H[0]<MiddleBuffer[0]) res.Buy=true; //--- return(res); }
Существует огромное количество торговых стратегий, и у каждой из которых свои сигналы. Поэтому нужно так организовать работу модуля торговых сигналов, чтобы они вписывались в заданную стратегию. В документации к советнику должна быть описана торговая стратегия, чтобы разработчики сигнальных модулей действовали в соответствии с техническими требованиями модульного проекта.
Пример 4: модуль фильтра сигналов
Чтобы повысить прибыльность торговых роботов, часто используют фильтры торговых сигналов. Это могут быть, например, учёт тренда, времени торговли, новостей, дополнительных сигнальных индикаторов и т.д.
//****** project (module Filter): test_module_Filter_ind.mq5 //+------------------------------------------------------------------+ //| The program code is generated Modular project generator | //| Copyright 2010-2017, Sergey Pavlov (DC2008) | //| http://www.mql5.com/ru/users/dc2008 | //+------------------------------------------------------------------+ #property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)" #property link "http://www.mql5.com/ru/users/dc2008" #property link "1.00" #property link "Example of a multimodule expert: project test module Filter" //--- Display indicator in the chart window #property indicator_chart_window //--- Number of buffers to calculate the indicator #property indicator_buffers 1 //--- Number of graphic series in the indicator #property indicator_plots 1 //--- double Buffer1[]; //--- Variable for storing the indicator iBands handle int handle_Bands; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { handle_Bands=iBands(_Symbol,_Period,35,0,4.1,PRICE_CLOSE); //--- ArraySetAsSeries(Buffer1,true); SetIndexBuffer(0,Buffer1,INDICATOR_DATA); return(INIT_SUCCEEDED); }; //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate (const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], const double& high[], const double& low[], const double& close[], const long& tick_volume[], const long& volume[], const int &spread[]) { double filtr=0.0; ArraySetAsSeries(high,true); ArraySetAsSeries(low,true); //--- Indicator buffers double MiddleBuffer[]; ArraySetAsSeries(MiddleBuffer,true); CopyBuffer(handle_Bands,0,0,1,MiddleBuffer); //--- calculation of the filter if(high[0]<MiddleBuffer[0]) filtr=2.0; if(low[0]>MiddleBuffer[0]) filtr=-2.0; Buffer1[0]=filtr; return(rates_total); };
Итак, мы рассмотрели варианты реализации внешних модулей и принцип их интеграции в модульный советник.
Оптимизация многомодульных экспертов
Оптимизация многомодульных советников — пожалуй, один из краеугольных вопросов. Действительно, как оптимизировать входные параметры внешних модулей в тестере стратегий? Если в главном модуле они не прописаны, то получается, что никак (или почти никак). Можно попробовать дискретно задавать входные данные внешних модулей и затем тестировать советник. Но эта кропотливая и, скорее всего, бессмысленная работа нам не подходит. Тогда как же быть?
Вот один из возможных вариантов: использовать в качестве внешних модулей самооптимизирующиеся индикаторы. На тему автооптимизации написано много статей и примеров. Внесу и я свой вклад в развитие этой темы. Воспользуемся идеями из статьи "Визуальное тестирование прибыльности индикаторов и сигналов". Ее автор предлагает использовать в качестве цены совершения виртуальной сделки максимальное значение свечи для открытия позиции BUY, и минимальное — для SELL. Следовательно, выбираются худшие торговые условия, и при таком подходе к ценам происходит оптимизация входных параметров. Предполагается, что при полученных оптимальных значениях в реальной торговле результат будет не хуже (на том же участке исторических данных). В реальной же торговле гарантировать прибыль нельзя после любой оптимизации.
Стратегия нашего советника основана на торговле внутри канала индикатора Bollinger с разворотом позиции на его границах. Давайте заменим этот индикатор, а канал построим на индикаторе Envelope: от индикатора скользящей средней сформируем границы, равноудаленные от МА на фиксированное расстояние. Новый сигнальный индикатор перед использованием будет автоматически самооптимизироваться. В качестве входных параметров будут использоваться оптимальные значения, при которых была показана максимальная прибыль. Для оптимизации выбраны два параметра — период МА и отстояние границ от скользящей средней.
Алгоритм создания сигнального индикатора с функцией автооптимизации:
- Определяем параметры и критерий оптимизации. В нашем случае параметрами будут период МА и дистанция смещения границ, а критерием — максимальная прибыль.
- Создаём блок оптимизации в индикаторе. В предлагаемом примере происходит полный перебор входных данных в заданном диапазоне с фиксированным шагом. Период скользящей средней от 10 до 100 с шагом 10, а для смещения значения перебираются в диапазоне от 1000 до 10000 с шагом 1000.
//+------------------------------------------------------------------+ //| Copyright 2018, Sergey Pavlov (DC2008) | //| http://www.mql5.com/ru/users/dc2008 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, Sergey Pavlov (DC2008)" #property link "http://www.mql5.com/ru/users/dc2008" #property link "1.00" #property link "Example of a multimodule expert: project test module Trading signals" //--- #include <MovingAverages.mqh> //--- Display indicator in the chart window #property indicator_chart_window //--- Number of buffers to calculate the indicator #property indicator_buffers 1 //--- Number of graphic series in the indicator #property indicator_plots 1 //+------------------------------------------------------------------+ //| Структура результатов оптимизации | //+------------------------------------------------------------------+ struct Opt { int var1; // оптимальное значение 1 параметра int var2; // оптимальное значение 2 параметра double profit; // прибыль }; //--- double Buffer1[]; bool optimum=false; Opt test={NULL,NULL,NULL}; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { ArraySetAsSeries(Buffer1,true); SetIndexBuffer(0,Buffer1,INDICATOR_DATA); optimum=false; return(INIT_SUCCEEDED); }; //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { double signal=0.0; Buffer1[0]=signal; //--- optimization of input parameters if(!optimum) { ArraySetAsSeries(close,false); int count=rates_total; int total=0; int total_profit=0; for(int d=1000;d<10001;d+=1000) for(int j=10;j<101;j+=10) { double shift=d*_Point; bool open_buy=false; bool open_sell=false; double price_buy=0; double price_sell=0; double profit=0; int order=0; for(int i=j+1;i<count;i++) { double ma=SimpleMA(i,j,close); double sell=ma+shift; double buy=ma-shift; //--- BUY if(buy>close[i] && !open_buy) { price_buy=high[i]+spread[i]*_Point; if(order==0) profit=0; else profit+=price_sell-price_buy; order++; open_buy=true; open_sell=false; } //--- SELL if(sell<close[i] && !open_sell) { price_sell=low[i]-spread[i]*_Point; if(order==0) profit=0; else profit+=price_sell-price_buy; order++; open_sell=true; open_buy=false; } //--- } if(profit>0) if(profit>test.profit) { test.var1=j; test.var2=d; test.profit=profit; total_profit++; } //--- Comment("Оптимизация входных параметров..."," проходов=",total," // прибыльных =",total_profit); total++; } //--- Print(" Оптимизация завершена: ",test.var1," ",test.var2); Comment("Оптимизация завершена"); optimum=true; } //--- if(optimum) if(test.profit>0) { ArraySetAsSeries(close,true); double ma=SimpleMA(0,test.var1,close); double sell=ma+test.var2*_Period; double buy=ma-test.var2*_Period; //--- calculation of the Trading signals if(buy>close[0]) signal=2.0; if(sell<close[0]) signal=-2.0; } //--- Indicator buffers Buffer1[0]=signal; return(rates_total); };
Естественно, оптимизация займёт какое-то время, в течение которого советник не сможет совершать торговые операции. Если модульный торговый робот работает круглосуточно, то задержка на автооптимизацию не должна оказать существенного влияния на общее время торговли.
Заключение
- Написать многомодульный советник на MQL5 возможно, а в некоторых случаях — целесообразно и даже коммерчески выгодно.
- В статье показан примтивный концепт торгового робота с внешними модулями. Тем не менее, технология модульного программирования позволяет создавать достаточно сложные проекты, под которые можно привлекать сторонних разработчиков. При создании модулей они вольны не раскрывать их код и таким образом сохранять свои авторские права на алгоритмы.
- Остаётся открытым вопрос оптимизации модульных проектов. Самооптимизация сигнальных индикаторов, используемых в качестве модулей сигналов или фильтров, — тема, которая требует развития.
Примечание: Приложенный к статье файл позволяет сгенерировать исходные коды модульного проекта в нужной конфигурации.
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Важно различать:
- модульность программы (когда одна простая модель разбивается на модули)
- и модульность сложной модели (где каждый модуль - это независимая модель).
Любой традиционный индикатор - это простая модель, а нужна сложная модель,
так как простой модели недостаточно для получения достаточной точности
отображения процесса.
Вот допустим, я не знаю, что такое модульность сложной модели, то из Вашего комментария мои знания не увеличились.
Приведите пример сложной модели заложенной в индикаторе.
Мне, как не проффесианалу было позновательно. Хорошая статья для начинающих.
Но вот по поводу модульности, я считаю пример не законченым. Мне кажется, что если б в том же примере, каждую функцию главного модуля написать в отдельном файле, а после с помощю условной компиляции и инклюдов обьеденить в один файл - тогда это модульное написание. В этом случае каждый отдельный кусочек может быть переписан, не затрагивая главного кода.
Как пример, вместо кода:
код:
P.S. Имена модульных индикаторов в главном файле, не совпадают с названиями файлов. В главном модуле нужно довавить "_ind". Например, должно быть: "Module_training_module_Filter_ind" , а не "Module_training_module_Filter"
Многомодульность торговой системы определяется прежде всего природой процесса (для финансовых рынков движение цен - это нестационарный процесс).
А моделирование такого сложного процесса (ведь разработка советника - это всегда моделирование процесса) невозможно при помощи единого, даже очень сложного алгоритма входа. Чтобы приблизиться к процессу с точки зрения точности, необходим набор алгоритмов (модулей), каждый их которых является независимой моделью.
Ну и какого рожна я должен чего-нибудь моделировать, чтобы что-нибудь купить/продать? И при чем тут технологические моменты? Зачем о стационарности гутарим?
Мальчик, код выложи, или ТЗ, или еще что, а после я обращу внимание на твои суждения.
Хотел бы выразить благодарность автору данной статьи. Спасибо за такие труды. Мне как начинающему чайнику в освоении ООП и в конкретике mql5, данная статья очень помогает осваивать язык в целом. Господа которые видят недостатки, реализации концепции в выше изложенном в статье, хотелось бы сказать, что совершенствам предела нет нигде, совершенствоваться возможно я думаю и Ваших трудах есть еще куда ....
Статья данная скорее ориентирована для начинающих свой путь в изучении языка...