Советник - данные

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


Чтобы было на чём тренироваться - для начала реализуем простую стратегию «торговля по пересечению двух скользящих средних». Правила элементарнейшие: если первая (быстрая) средняя пересекает вторую снизу вверх то покупаем, а если пересекает сверху вниз то продаём.

CrossMA.class.mqh
#include <EA.mqh>
class CrossMA:public EA
{
public:
   //// параметры советника
   // первая MA
   ENUM_MA_METHOD ma1_method;
   ENUM_APPLIED_PRICE ma1_applied;
   int ma1_period,ma1_shift;
   // вторая MA
   ENUM_MA_METHOD ma2_method;
   ENUM_APPLIED_PRICE ma2_applied;
   int ma2_period,ma2_shift;
   //// используемые данные - просто перечисление
   enum ENUM_DATA {
      MA1,
      MA2
   };
   Data *ma1,*ma2;   // синонимы - указатели (для простого доступа)
public:
   // в конструктор передаётся все параметры
   CrossMA(ENUM_MA_METHOD _ma1_method,ENUM_APPLIED_PRICE ma1_applied,int ma1_period,int ma1_shift,
      ENUM_MA_METHOD ma2_method,ENUM_APPLIED_PRICE ma2_applied,int ma2_period,int ma2_shift,
      int magic
      );
   ~CrossMA();
   // OnInit() - играет ту-же роль что и стандартный, проверка параметров и настройка к окружению
   virtual int OnInit();
   // пока все операции проводим при открытии баров - в методе OnBar()
   virtual void OnBar();
   // функция для вычисления требуемых данных
   virtual double Calc(int data_id,int bar);
};

просто и понятно, но кто такие Data * и функция Calc ?

не вдаваясь в подробности - базовый класс EA автоматически организует вычисления и буферизует результаты. Когда требуются значения данных он вызывает метод Calc для непосредственных расчётов. Тип Data как-раз и есть автоматический буфер.

Доступ к данным получается очень простой: в нашем случае чтобы получить значение 1-й MA, на 3-м баре (0-текущий) достаточно обратится this[MA1][3] или ma1[3].

CrossMA.impl.mqh
// подключаем интерфейс
#include "CrossMA.class.mqh"
// конструктор - просто запоминаем все требуемые параметры
CrossMA::CrossMA(
   ENUM_MA_METHOD _ma1_method,ENUM_APPLIED_PRICE _ma1_applied,int _ma1_period,int _ma1_shift,
   ENUM_MA_METHOD _ma2_method,ENUM_APPLIED_PRICE _ma2_applied,int _ma2_period,int _ma2_shift,
   int _magic):EA(_magic)
{
   ma1_method=_ma1_method;ma1_applied=_ma1_applied;ma1_period=_ma1_period;ma1_shift=_ma1_shift;
   ma2_method=_ma2_method;ma2_applied=_ma2_applied;ma2_period=_ma2_period;ma2_shift=_ma2_shift;
}
CrossMA::~CrossMA()
{
}
// аналог системной OnInit - основную работу по инициализации проводим тут
int
CrossMA::OnInit()
{
   // проверить параметры
   // ... для краткости - опущено
   // настроить синонимы, в принципе это можно делать и в конструкторе
   ma1=this[MA1];
   ma2=this[MA2];
   return INIT_SUCCEEDED;
}
// вычисление значений
// в параметрах получаем ид.набора данных которые надо расчитать
// и номер бара для расчёта (0-текущий бар)
double
CrossMA::Calc(int id,int bar) 
{
   double d=EMPTY_VALUE;
   switch(id) {
      case MA1:   d=iMA(_Symbol,_Period,ma1_period,ma1_shift,ma1_method,ma1_applied,bar);break;
      case MA2:   d=iMA(_Symbol,_Period,ma2_period,ma2_shift,ma2_method,ma2_applied,bar);break;
   }
   return d;
}
 
// работаем по открытию баров
void 
CrossMA::OnBar()
{
   if (CrossUp(ma1,ma2,1)) {
      // первая MA пересекла вторую снизу ВВЕРХ
      // покупаем
      Buy();
   } else if (CrossDown(ma1,ma2,1)) {
      // первая MA пересекла вторую сверху ВНИЗ
      // продаём
      Sell();
   }
}

если потребуется добавить например среднее значение двух MA, достаточно в перечисление добавить ид. AVG, синоним Data *avg, и функцию Calc формулу вычисления «case AVG: d=(ma1[bar]+ma2[bar])/2;break;». При этом заботится о предварительном вычислении ma1,ma2 ненадо - об этом позаботится система.

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

Для того чтобы полученный советник совместить со стандартным интерфейсом, надо воспользоваться например таким шаблоном:

CrossMA.mq4
/** простой эксперт для демонстрации библиотеки EA
   торговля по пересечению двух MA
**/
#property copyright "Maxim A.Kuznetsov"
#property link      "http://luxtrade.tk"
#property version   "1.00"
#property description "GenEA demonstration"
#property description "simple advisor: CrossMA"
#property strict
 
//--- input parameters
input ENUM_MA_METHOD MA1_METHOD=MODE_SMA;
input ENUM_APPLIED_PRICE MA1_APPLIED=PRICE_CLOSE;
input int      MA1_PERIOD=7;
input int      MA1_SHIFT=0;
 
input ENUM_MA_METHOD MA2_METHOD=MODE_SMA;
input ENUM_APPLIED_PRICE MA2_APPLIED=PRICE_CLOSE;
input int      MA2_PERIOD=5;
input int      MA2_SHIFT=0;
 
input int      MAGIC=0x11bc;
//----
// файлы класса (интерфейс и имплементацию)
// #include "CrossMA.class.mqh"
#include "CrossMA.impl.mqh"
 
CrossMA *ea; // экземпляр эксперта
 
int
OnInit()
{
   // создать экземпляр эксперта
   ea=new CrossMA(
      MA1_METHOD,MA1_APPLIED,MA1_PERIOD,MA1_SHIFT,
      MA2_METHOD,MA2_APPLIED,MA2_PERIOD,MA2_SHIFT,
      MAGIC);
   if (ea==NULL) {
      return INIT_FAILED;
   }
   // инициализовать его
   return ea.Init();	// важно чтобы именно .Init, а не OnInit
}
 
void
OnDeinit(const int reason)
{
   ea.Deinit(reason);	// сообщить эксперту о выгрузке
   delete ea;	// удалить экземпляр
}
/// прочие методы как правило не требуют изменений
void
OnTick()
{
   ea.Tick();
}
 
void
OnTimer()
{
   ea.Timer();
}
 
void
OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
   ea.ChartEvent(id,lparam,dparam,sparam);
}

Шаблон и реализация класса очень простые и вполне могут генерироваться внешними средствами.

И кстати советник вполне работоспособен !

По умолчаниям:

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

Каких целей добились?

  • расчётная часть отделена от всего остального, и это позволяет :
    • легко и унифицированно обращаться к данным. Действительно обращение this[MA1][i] или ma1[i] проще и естественней чем постоянное упоминание формул, ввод лишних переменных или копирование массивов (как в MT5)
    • отчасти оптимизировать вычисления и кешировать их результаты - система сама определяет когда надо производить вычисления, а когда использовать прежние результаты
    • все вычисление сведены в одну функцию и это вполне естественное место для точек останова или записей в журнал отладки. Отлаживать стало проще
    • открыт путь к интеграции с внешними средствами, например можно использовать механизм RTD для передачи данных и on-line транслировать данные в Excel
  • сделали элементарный советник и его код довольно компактен и ясен чтобы :
    • использовать его как шаблон для советников
    • возможна авто-генерация

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