English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Передача данных между индикаторами - простое решение наболевшей проблемы

Передача данных между индикаторами - простое решение наболевшей проблемы

MetaTrader 5Примеры | 15 января 2010, 10:35
11 456 21
Alexey Subbotin
Alexey Subbotin

Введение

Ценность новичков – не только в том, что они задают вопросы, упорно не желая пользоваться поиском и тем самым побуждая остальных к созданию бросающихся в глаза разделов с названиями вроде «FAQ», «Новичкам сюда» или «Тот, кто задаст вопрос из этого списка, будет гореть в аду». Их истинное предназначение – задавать вопросы, начинающиеся с фразы «А как… ?» и «Можно ли… ?» и получать ответы «Никак» и «Нельзя».

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

1. Постановка задачи

Вот, к примеру, не слишком давняя цитата одной из веток форума MQL4 Community (орфография и пунктуация подправлена))):

 … на рабочем столе установлены два индикатора (назовем их А и В). Индикатор А строится как обычно, непосредственно с графика [цены - авт.], а индикатор В с данных индикатора А. Теперь возникает вопрос, как сделать так, чтобы индикатор В рисовался не через iCustom("indicator A", ...), а динамически с данных индикатора А (который уже установлен на графике), то есть меняя настройки индикатора А, не нужно было бы менять соответственно параметры индикатора В?

или в другой формулировке:

… Допустим, мы к графику прикрепили индикатор Moving Average, теперь как можно добраться к буферу этого индикатора непосредственно?

и еще:

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

Список вопросов подобного рода можно продолжать довольно долго, они возникают у многих пользователей, и не только у новичков. Если обобщить, то, по сути, проблема заключается в том, что в платформе MetaTrader отсутствует штатный механизм доступа к показаниям пользовательских индикаторов – без использования функции iCustom (MQL4) либо связки iCustom - CopyBuffer (MQL5). А было бы так соблазнительно при разработке очередного шедевра на MQL написать в коде программы – возьми-ка ты, дружок, данные с такого-то графика, да посчитай мне из них то-то…

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

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

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

ну, и ряд других, в той или иной степени повторяющих перечисленные, а также и совсем экзотические вроде сокетов, мэйлслотов и т.д. (есть еще и радикальный метод, часто используемый при написании советников – перенос расчетной индикатора непосредственно в код эксперта, но это уже немного «из другой оперы»).

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

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

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

  • отсутствие копирования данных (а следовательно, и вопроса об их актуальности);
  • минимальное вмешательство в код уже имеющихся инструментов при необходимости их «подключения»;
  • реализация преимущественно средствами MQL (естественно, механизм DLL нам все же потребуется, однако, как мы увидим, его использование будет ограничиваться не более чем десятком строк на С++).

При разработке автор имел в своем распоряжении компилятор семейства С++ Builder для создания DLL, а также терминалы MetaTrader 4 и MetaTrader 5. Исходные коды ниже будут приводиться на языке программирования MQL5, вариант для предыдущей версии языка прикреплен к статье, его существенные отличия будут прокомментированы.

2. Массивы

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

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

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

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

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

Хотя данный вывод не 100% логически строг, его все-таки можно считать вполне достоверным (что косвенно подтверждается и исправной работой нашего продукта, эксплуатирующего эту идею). 

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

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

И еще примем некоторые допущения: под моментами времени мы понимаем моменты вызова функции OnCalculate() (для MQL4 – start()) соответствующего индикатора, кроме того, для простоты будем считать, что буферы данных, с которыми мы работаем, имеют одно измерение и тип double[].

3. Sine qua non

Общеизвестно, что язык MQL не поддерживает работу с такими вещами как указатели (так называемый указатель на объект не в счет, поскольку это собственно и не указатель вовсе) – это неоднократно утверждалось и подтверждалось представителями компании MetaQuotes Software. Проверим, так ли это :-).

Что такое, в сущности, указатель? Это не просто идентификатор со звездочкой, а адрес ячейки в памяти компьютера. А что представляет собой адрес ячейки? Это ее номер по порядку от некоего начала. Наконец, что же такое номер? Это целое число, поставленное в соответствие данной ячейке. А теперь спросим себя: что мешает нам работать с указателем как с целым числом? Да ничего – ведь программа MQL отлично работает с целыми числами! 

Только один вопрос – как преобразовать указатель в целое. В этом нам и поможет Dynamic Link Library, а именно, возможности языка C++ по приведению типов данных. В связи с тем, что указатели в C++ являются четырехбайтовым типом данных, в наших целях удобно использовать четырехбайтовый же тип int.

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

Итак, 

extern "C" __declspec(dllexport) int __stdcall GetPtr(double *a)
{
        return((int)a);
}

Вуаля, имеем в распоряжении длинное целое, содержащее адрес начала массива! Осталось научиться считывать значение его i-го элемента:

extern "C" __declspec(dllexport) double __stdcall GetValue(int pointer,int i)
{
        return(((double*) pointer)[i]);
}

… и записывать (вообще-то наша задача этого не требует, но для комплекта…):

extern "C" __declspec(dllexport) void __stdcall SetValue(int pointer,int i,double value)
{
        ((double*) pointer)[i]=value;
}

Собственно, все. Теперь MQL умеет работать с указателями :-).

4. Обертка

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

В принципе, вариантов, как поступить дальше – миллиард. Мы пойдем по пути, который показался автору наиболее приемлемым. Вспомним, что в терминале MetaTrader есть штатное средство для обмена небольшими порциями информации между отдельными программами – Global Variables. Представляется целесообразным использовать именно их для хранения указателей на индикаторные буфера, к которым мы будем осуществлять доступ. Обзовем совокупность таких переменных таблицей дескрипторов. Каждый дескриптор будет иметь имя – строку следующего вида:

строковый_идентификатор#номер_буфера#символ#период#длина_буфера#направление_индексации#случайное_число,

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

Немного о значении полей дескриптора. 

  • строковый_идентификатор – любая строка (например, имя индикатора, часто используемая переменная short_name и т.п.), по ней будем осуществлять поиск нужного указателя, предлагается условиться, что некий индикатор будет регистрировать дескрипторы с одним и тем же идентификатором, а различать их будет по следующему полю:
  • номер_буфера – то, о чем только что было сказано;
  • длина_буфера – необходима нам, чтобы не выбраться за пределы индексации, а то можно случайно устроить терминалу креш, а Виндовсу синий экран:);
  • символ, период – будут определять окно, на котором находится график;
  • направление_индексации – указывает направление индексации 0 – нормальное, 1 – обратное (флаг AS_SERIES установлен в true);
  • случайное_число – будет использоваться на тот случай, если в терминал загружено несколько копий одного и того же индикатора, но на разных окнах или с разными наборами параметров (они могут выставлять одни и те же значения первого и второго полей, поэтому их также необходимо как-то различать). Автор не претендует на то, что данный способ лучший из возможных, однако он имеет право на существование.

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

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

void RegisterBuffer(double &Buffer[], string name, int mode) export
{
   UnregisterBuffer(Buffer);                    //сначала удалим на всякий случай переменную
   
   int direction=0;
   if(ArrayGetAsSeries(Buffer)) direction=1;    //устанавливаем флаг направления индексации

   name=name+"#"+mode+"#"+Symbol()+"#"+Period()+"#"+ArraySize(Buffer)+"#"+direction;
   int ptr=GetPtr(Buffer);                      //вытаскиваем указатель
   if(ptr==0) return;
   
   MathSrand(ptr);                              //очень удобно использовать значение указателя
                                                //(а не время) для инициализации ГПСЧ
   while(true)
   {
      int rnd=MathRand();
      if(!GlobalVariableCheck(name+"#"+rnd))    //обеспечим уникальность - полагаем,
      {                                         //что вряд ли кому придет в голову
                                                //исследовать больше, чем RAND_MAX буферов:)
         name=name+"#"+rnd;                     
         GlobalVariableSet(name,ptr);           //ну и записываем в глобальные
         break;
      }
   }   
}
void UnregisterBuffer(double &Buffer[]) export
{
   int ptr=GetPtr(Buffer);                      //будем разрегистрироваться по фактическому адресу буфера
   if(ptr==0) return;
   
   int gt=GlobalVariablesTotal();               
   int i;
   for(i=gt-1;i>=0;i--)                         //просто перебрать все глобальные переменные 
   {                                            //и удалить наш буфер отовсюду, где он "засветился"
      string name=GlobalVariableName(i);        
      if(GlobalVariableGet(name)==ptr)
         GlobalVariableDel(name);
   }      
}

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

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

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

int FindBuffers(string name, int mode, string symbol, int period, string &buffers[]) export
{
   int count=0;
   int i;
   bool found;
   string name_i;
   string descriptor[];
   int gt=GlobalVariablesTotal();

   StringTrimLeft(name);                                    //обрезать строку от лишних пробелов
   StringTrimRight(name);
   
   ArrayResize(buffers,count);                              //обнулить размер

   for(i=gt-1;i>=0;i--)
   {
      found=true;
      name_i=GlobalVariableName(i);
      
      StringExplode(name_i,"#",descriptor);                 //разбить строку на поля
      
      if(StringFind(descriptor[0],name)<0&&name!=NULL) found=false; //проверяем каждое поле 
                                                                    //на соответствие критерию поиска
      if(descriptor[1]!=mode&&mode>=0) found=false;
      if(descriptor[2]!=symbol&&symbol!=NULL) found=false;
      if(descriptor[3]!=period&&period>0) found=false;
      
      if(found)
      {
         count++;                                           //все условия сошлись, заносим в список
         ArrayResize(buffers,count);
         buffers[count-1]=name_i;
      }
   }
   
   return(count);
}

Тут можно немного прокомментировать. Как видно из кода функции, некоторые условия поиска можно пропускать. Так, если в качестве параметра name передать NULL, проверка поля строковый_идентификатор дескриптора будет пропускаться. Аналогично и по другим полям: для пропуска соответствующих элементов надо задать mode<0, symbol:=NULL или period<=0. Это при необходимости расширит возможности поиска в таблице дескрипторов.

Например, можно найти все индикаторы Moving Average на всех окнах или только на окнах EURUSD с периодом M15 и т.п. Еще одно замечание: проверка поля строковый_идентификатор производится не по строгому равенству, а с помощью StringFind() – это сделано для того, чтобы можно было осуществлять поиск по части дескриптора (скажем, несколько индикаторов задают строку вида “МА(xxx)”, а поиск осуществляем по строке “МА” – найдем все зарегистрированные МАшки).

Еще нам потребовалась функция StringExplode(string s, string separator, string &result[]). Она делит строку s на части, используя разделитель separator, и записывает результат в массив result.

void StringExplode(string s, string separator, string &result[])
{
   int i,pos;
   ArrayResize(result,1);
   
   pos=StringFind(s,separator); 
   if(pos<0) {result[0]=s;return;}
   
   for(i=0;;i++)
   {
      pos=StringFind(s,separator); 
      if(pos>=0)
      {
         result[i]=StringSubstr(s,0,pos);
         s=StringSubstr(s,pos+StringLen(separator));
      }
      else break;
      ArrayResize(result,ArraySize(result)+1);
   }
}

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

double GetIndicatorValue(string descriptor, int shift) export
{
   int ptr;
   string fields[];
   int size,direction;
   if(GlobalVariableCheck(descriptor)>0)               //убедились в валидности дескриптора
   {                
      ptr = GlobalVariableGet(descriptor);             //получили значение указателя
      if(ptr!=0)
      {
         StringExplode(descriptor,"#",fields);         //разбили имя дескриптора на поля
         size = fields[4];                             //нам нужен текущий размер массива
         direction=fields[5];                                 
         if(direction==1) shift=size-1-shift;          //если индексация с конца
         if(shift>=0&&shift<size)                      //проверка правильности индекса - чтобы не уронить терминал
            return(GetValue(MathAbs(ptr),shift));      //добро, возвращаем значение
      }   
   } 
   return(EMPTY_VALUE);                                //в противном случае извиняйте...
}

Как видим, эта функция представляет собой «обертку» для вызываемой из DLL GetValue(). Перед обращением к ней необходимо проверить, правильно ли указан дескриптор, не выходит ли индекс массива за его текущие пределы, а также изменить направление индексации на обратное в случае, если значение дескриптора отрицательно. При неудачном завершении функция вернет EMPTY_VALUE.

Почти так же осуществляется и запись:

bool SetIndicatorValue(string descriptor, int shift, double value) export
{
   int ptr;
   string fields[];
   int size,direction;
   if(GlobalVariableCheck(descriptor)>0)               //убедились в валидности дескриптора
   {                
      ptr = GlobalVariableGet(descriptor);             //получили значение указателя
      if(ptr!=0)
      {
         StringExplode(descriptor,"#",fields);         //разбили имя дескриптора на поля
         size = fields[4];                             //нам нужен текущий размер массива
         direction=fields[5];                                 
         if(direction==1) shift=size-1-shift;          //если индексация с конца
         if(shift>=0&&shift<size)                      //проверка правильности индекса - чтобы не уронить терминал
         {
            SetValue(MathAbs(ptr),shift,value);
            return(true);
         }   
      }   
   }
   return(false);
}

При удачной проверке всех условий из DLL вызывается функция SetValue(). Возвращаемое значение соответствует успешности всех операций: true – успешно, false – ошибка.

5. Проверка боем

Настало время испытать все на примере. Возьмем в качестве жертвы входящий в стандартную поставку индикатор Average True Range (ATR) и покажем, какие изменения нужно внести в его код, чтобы, скажем, скопировать его значения в окно другого индикатора. Заодно можно и показать, как поменять что-нибудь в исходном индикаторе.

Первое, что мы сделаем, это добавим несколько строчек в начало функции OnCalculate():

//+------------------------------------------------------------------+

//| Average True Range                                               |
//+------------------------------------------------------------------+
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 &TickVolume[],
                const long &Volume[],
                const int &Spread[])
  {
   if(prev_calculated!=rates_total)
      RegisterBuffer(ExtATRBuffer,"ATR",0);
…

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

Кроме этого, понадобится удалить дескрипторы из таблицы при завершении работы индикатора. Для этого необходимо написать обработчик события deinit (не забываем, что он должен возвращать void и иметь один входной параметр виде const int reason): 

void OnDeinit(const int reason)
{
   UnregisterBuffer(ExtATRBuffer);
}
Вот что показывает наш видоизмененный индикатор в окне терминала (Рис. 1) и в списке глобальных переменных (Рис. 2):

 

Рис. 1. Индикатор Average True Range

Рис. 2. Дескриптор создается в списке глобальных переменных терминала

Рис. 2. Дескриптор создается в списке глобальных переменных терминала 

На следующем шаге попытаемся получить доступ к данным индикатора ATR. Создадим новый индикатор (назовем его test) и напишем:

//+------------------------------------------------------------------+
//|                                                         test.mq5 |
//|                                             Copyright 2009, alsu |
//|                                                 alsufx@gmail.com |
//+------------------------------------------------------------------+
#property copyright "2009, alsu"
#property link      "alsufx@gmail.com"
#property version   "1.00"
#property indicator_separate_window
#property indicator_buffers 1
#property indicator_plots   1

#include <exchng.mqh>

//---- plot ATRCopy
#property indicator_label1  "ATRCopy"
#property indicator_type1   DRAW_LINE
#property indicator_color1  Red
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- indicator buffers
double ATRCopyBuffer[];

string atr_buffer;
string buffers[];

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+

int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,ATRCopyBuffer,INDICATOR_DATA);
//---
   return(0);
  }

//+------------------------------------------------------------------+
//| 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[])
  {
//---
   int found=FindBuffers("ATR",0,NULL,0,buffers);   //поиск дескрипторов среди глобальных переменных
   if(found>0) atr_buffer=buffers[0];               //нашли? сохраняем указатель в переменную
   else atr_buffer=NULL;
   int i;
   for(i=prev_calculated;i<rates_total;i++)
   {
      if(atr_buffer==NULL) break;
      ATRCopyBuffer[i]=GetIndicatorValue(atr_buffer,i);  //ну а теперь достать данные
      SetIndicatorValue(atr_buffer,i,i);                 //никакого труда не представляет,
                                                         //как и записать впрочем
   }
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

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

Теперь запускаем индикатор test (наш ATR должен до сих пор быть прикреплен к окну). На Рис. 3 видим, что график ATR волшебным образом перебрался в нижнее окошко, а на его месте, как нам и хотелось, появилась прямая линия, значения которой соответствуют индексам массива.

 

Рис. 3. Результат работы индикатора Test.mq5

Ловкость рук – и никакого мошенничества :)

6. Backward compatibility

Автор попытался сделать библиотеку, написанную в 5 версии языка MQL, максимально близкой к стандартам MQL4. В связи с этим изменения, которые необходимо внести в программу для ее успешного применения в MetaTrader 4, минимальны.

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

Заключение

Несколько слов о перспективах. 

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

Из технических моментов пока вижу необходимость в двух вещах:

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

Также буду рад любым другим предложениям по усовершенствованию библиотеки.

Все необходимые файлы находятся во вложении к статье. Представлены варианты на MQL5, MQL4, а также исходный код библиотеки exchng.dll. Файлы разобраны по папкам так, как они должны лежать в директориях соответствующих терминалов.

Предупреждение

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

Благодарности

В статье использованы цитаты с форума MQL4.Community http://forum.mql4.com и ссылки на идеи пользователей igor.senych, satop, bank, StSpirit, TheXpert, jartmailru, ForexTools, marketeer, IlyaA – простите, если кого забыл.
Прикрепленные файлы |
cpp.zip (0.36 KB)
mql4.zip (39.29 KB)
mql5.zip (46.87 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (21)
Georgiy Merts
Georgiy Merts | 4 сент. 2013 в 19:17

Краём уха, краём... :)

Обновление МТ4 мне очень даже кстати - у меня библиотеки большие, но все - на МТ5 (я, видите ли, без ООП  жисть себе не представляю). А ДЦ, нехорошие люди - далеко не все МТ5 на реале имеют... Так что - мне данная новость очень даже кстати.

Но - пока не будет нормального МТ4++  я не вижу смысла приспосабливать код под обновленную платформу. Выйдет что-то вроде официальной беты (да, чтобы со Стандартой Библиотекой, я без нее тоже не желаю советников писать) - вот тогда и начнем преобразование кода...

А сейчас мне - ужасно хочется получать адреса не только массивов double, но и других... А - ёк... Хотел было через преобразование типов обойти проблему (время и лонги - тоже ведь восьмибайтные), но пока не выходит...   

Georgiy Merts
Georgiy Merts | 4 сент. 2013 в 19:59

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

Georgiy Merts
Georgiy Merts | 5 сент. 2013 в 05:16

Мдя. Рано радовался. GetValue для массивов, отличных от double возвращает что-то совсем не то, что хотелось бы... Увы, без MSVC не обойтись... Эх, давно не брал я в руки шашки...

Aleksei Radchenko
Aleksei Radchenko | 12 июл. 2014 в 08:23

Не понимаю, зачем в MQL нужны указатели? Если вы хотите что-то делать с указателями - делайте это сразу в с++.

Проблем по передаче указателей на другой тип данных и на массивы другого типа - нет! К примеру вот объявление: void  setvar(int& var[]);  для передачи указателя на массив целых чисел (догадаетесь что надо поменять для других типов?)

Накладные расходы с вызовом dll функций не исчезли (билд 646), скажем пустая mql-функция работает быстрее чем пустая dll-функция, но если добавить туда хотя-бы операцию выборки из массива, типа s[i], то уже тут c++ будет в выигрыше, тем не менее количество вызовов надо сводить к минимуму.

Самая большая утечка быстродействия идет при работе с глобальными переменными - они ОЧЕНЬ МЕДЛЕННЫЕ!!! Гораздо проще, если мы уж сделали dll, хранить глобальные переменные там. Для всех копий советников и индикаторов в рамках одного метатрейдера, линкуется одна копия dll, соответственно все ее глобальные переменные являются глобальными для всех окон метатрейдера (эта правда вынуждает персональны данные одного окна хранить либо в экземпляре класса, либо в массиве с доступом по идентификатору окна) 

Yuriy Asaulenko
Yuriy Asaulenko | 5 авг. 2016 в 18:42
Имхо, проще сразу мигрировать в С++ или C# и не возвращаться до применения торгового функционала МТ.  А если нужны индикаторы, то для их рисования. И проблема исчезает.
Исследование статистики повторяемости направления свечей Исследование статистики повторяемости направления свечей
Цель статьи - попытаться предсказать поведение рынка на основе статистики повторяемости направления свечей в определенные промежутки времени.
Как написать индикатор в MQL5 Как написать индикатор в MQL5
Что представляет собою индикатор? Это набор вычисленных значений, которые мы хотим отобразить на экране монитора удобным для нас образом. Наборы значений представляются в программах в виде массивов. Таким образом, создание индикатора - это написание алгоритма, который обрабатывает одни массивы (массивы цен) и записывает результаты обработки в другие массивы (значения индикаторов). На примере создания индикатора True Strength Index в статье рассказывается, как писать индикаторы на MQL5
Инструмент "Ценовая гистограмма" (Рыночный профиль) и его реализация на MQL5 Инструмент "Ценовая гистограмма" (Рыночный профиль) и его реализация на MQL5
Рыночный профиль был разработан Питером Стидлмайером (Peter Steidlmayer), который предложил использовать альтернативное представление информации как о горизонтальном, так и о вертикальном движении рынка, что дает полностью отличный набор моделей. Он предположил, что у рынка существует основной рыночный пульс, или фундаментальная модель, которая называется цикл равновесия и неравновесия (cycle of equilibrium and disequilibrium). В данной статье я сделаю попытку дать общие понятия об упрощенной модели Рыночного профиля (Market Profile) – Ценовой Гистограмме (Price Histogram) и расскажу, как реализовал данный инструмент на MQL5.
Техника оптимизации (тестирования) и некоторые критерии выбора рабочих параметров эксперта Техника оптимизации (тестирования) и некоторые критерии выбора рабочих параметров эксперта
Тестерный "Грааль" получить очень легко и просто, а вот избавиться от этого - гораздо сложнее. В статье рассмотрен вариант выбора рабочих параметров эксперта с автоматизированной групповой обработкой результатов оптимизации и тестирования, с максимальным использованием возможностей терминала и минимальной нагрузкой на конечного пользователя.