Это старая версия документа!


Концепция DataFrame

Продолжаем делать советник. Для того чтобы пореже порождать множественные иерерархии и не обобщать многократно обобщённое, воспользуемся кладезью возможностей ООП и сделаем предстваление данных. Есть такая штука как «табличная модель данных», хорошо знакомая всем например по Excel - вот её-то и реализуем. Первоначально чтобы пользователю было просто описывать исходные данные и их взаимоотношения, а далее задействем это по максимуму.

Даже по начальному use-case прям-таки хочется сделать а-ля большой индикатор с несколькими буферами, в виде двумерного массива у которого колонки - таймсерии, а по строкам бары. А в отдельных ячейках double. Так программистам MQL привычно, и можно использовать обычные математические функции и всякие CopyArray без ограничений.

Так и сделаем :-)

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

Немного забегая - не всё так просто :-) всё-же MQL он ни разу не C++ и массивы у него не first-class и есть куча ограничений по перегрузке и приведению типов. Придётся выкручиваться, чтобы пользователь получил более-менее привычный интерфейс. — в принципе мы выкрутились - изначальный use-case работает и даже торгует :-) Но с кучей ограничений: сейчас внутри советника нет ничего кроме интерфейса с исходными данными, остальное это много-много заглушек и прямые/неправильные вызовы. Это надо развивать далее.

Итог, как говорится «вот он» - сделки открываются/закрываются, всё работает.

Баланс конечно валится вниз :-) Ну а что вы хотели от «пересечения двух МА» :-)

Исходники можно скачать тут , по мере собственных сил заведу git

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

(для компактности - в copy-past удалены лишние комментарии и код MQL5)

enum ENUM_SERIES {
   FAST_MA,       // id. значений FAST_MA
   SLOW_MA,       // id. значений SLOW_MA 
   // для примера - добавим пару вычислимых полей
   SIGNAL_BUY,    // сигнал к покупкам
   SIGNAL_SELL,   // сигнал к продажам
 
   TOTAL_SERIES   // последний элемент перечисления = кол-во элементов
};
 
// в функцию пользователя добавим параметр table для доступа ко всем данным
double GetData(EA *ea,int id,int shift,double &data[],DataFrame *table)
{
   switch ((ENUM_SERIES)id) {
      case FAST_MA:
         return table[FAST_MA][shift]=iMA(ea.Symbol,ea.Period,FAST_MA_PERIOD,0,FAST_MA_METHOD,FAST_MA_PRICE,shift);
      case SLOW_MA:
         return table[SLOW_MA][shift]=iMA(ea.Symbol,ea.Period,SLOW_MA_PERIOD,0,SLOW_MA_METHOD,SLOW_MA_PRICE,shift);
      /* пример вычисления зависимых данных.
         обратите внимание: пользователь не заботится о последовательности вычислений, а просто декларирует "формулы"
      */
      case SIGNAL_BUY:
         return table[SIGNAL_BUY][shift]=table.CrossedUp(FAST_MA,SLOW_MA,shift);
      break;
      case SIGNAL_SELL:
         return table[SIGNAL_SELL][shift]=table.CrossedDn(FAST_MA,SLOW_MA,shift);
      break;
   }
   return EMPTY_VALUE;
}
// в функцию генерации сигнала также добавлен параметр - таблица
// можно использовать любый её данные
int SignalOfCross(EA *ea,int shift,DataFrame *data)
{
   if (FAST_MA_PRICE!=PRICE_OPEN || SLOW_MA_PRICE!=PRICE_OPEN) shift++;
   if (table[SIGNAL_BUY][shift]==1.0)  return OP_BUY;
   if (table[SIGNAL_SELL][shift]==1.0) return OP_SELL;
   return -1;
}

(всё прочее остаётся без изменений)