English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Трассировка, отладка и структурный анализ кода

Трассировка, отладка и структурный анализ кода

MetaTrader 5Примеры | 16 марта 2011, 18:01
4 130 12
---
---


Введение

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

  • Составление структуры используемых классов, функций, файлов.
  • Создание стека вызова, с сохранением всех прошлых стеков. Их последовательности вызовов.
  • Просматривать состояние Watch-параметров на этапе выполнения.
  • Пошаговое выполнение кода.
  • Группировать и сортировать полученные стеки, получать «экстремальные» данные.


Основные принципы разработки

Методом представления структуры стеков был выбран обычный подход – отображения в виде дерева. Для этого необходимо два информационных класса. CNode «узел», в которые записываются все данные стека. CTreeCtrl «дерево», которое обрабатывает все узлы. И сам трейсер CTraceCtrl, который обрабатывает деревья.

Классы реализованы следующей иерархией:


Классы CNodeBase, CTreeBase описывают базовые свойства и методы работы с узлами и деревьями.

Класс-потомок CNode расширяет базовый функционал CNodeBase, а класс CTreeBase работает именно с потомком CNode. Это сделано из-за того, что класс CNodeBase является родителем других типовых узлов и обособлен для удобства иерархии и наследования в самостоятельный класс.

В отличие от CTreeNode из стандартной библиотеки, в классе CNodeBase находится массив указателей на узлы, то есть "веток", выходящих из данного узла, может быть сколь угодно много.

Классы CNodeBase, CNode

class CNode; // forward declaration
//------------------------------------------------------------------    class CNodeBase
class CNodeBase
  {
public:
   CNode   *m_next[]; // список узлов, на которые он указывает
   CNode   *m_prev; // узел родитель
   int     m_id; // уникальный номер
   string  m_text; // текст

public:
          CNodeBase() { m_id=0; m_text=""; } // конструктор
          ~CNodeBase(); // деструктор
  };

//------------------------------------------------------------------    class CNode
class CNode : public CNodeBase
  {
public:
   bool    m_expand; // раcкрытость
   bool    m_check; // отмеченный точкой
   bool    m_select; // подсвеченный

   //--- run-time информация
   int     m_uses; // число обращений к узлу
   long    m_tick; // время проведенное в узле
   long    m_tick0; // время входа в узел
   datetime    m_last; // время входа в узел
   tagWatch   m_watch[]; // список параметров имя/значение
   bool    m_break; // дебаг-пауза

   //--- параметры вызова
   string   m_file; // имя файла
   int      m_line; // номер линии в файле
   string   m_class; // имя класса
   string   m_func; // имя функции
   string   m_prop; // доп.инфо

public:
           CNode(); // конструктор
           ~CNode(); // деструктор
   void    AddWatch(string watch,string val);
  };

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

По принятой классификации CTreeBase представляет собой ориентированный ацикличный граф. Класс-потомок CTreeCtrl использует CNode и обслуживает весь его функционал: добавляет, изменяет, удаляет узлы CNode.

CTreeCtrl и CNode вполне могли бы заменить соответствующие классы из стандартной библиотеки, так как имеют чуть большие возможности.

Классы CTreeBase, CTreeCtrl

//------------------------------------------------------------------    class CTreeBase
class CTreeBase
  {
public:
   CNode   *m_root; // первый узел дерева
   int     m_maxid;    // счетчик ID

   //--- базовые функции
public:
           CTreeBase(); // конструктор
           ~CTreeBase(); // деструктор
   void    Clear(CNode *root=NULL); // удаления всех узлов после указанного
   CNode   *FindNode(int id,CNode *root=NULL); // поиск узла по ID, начиная с заданного узла
   CNode   *FindNode(string txt,CNode *root=NULL); // поиск узла по txt, начиная с заданного узла
   int     GetID(string txt,CNode *root=NULL); // получение ID для указанного Text, поиск с заданного узла
   int     GetMaxID(CNode *root=NULL); // получение максимального ID в дереве
   int     AddNode(int id,string text,CNode *root=NULL); // добавление в список с поиском по ID узла, начиная с заданного узла
   int     AddNode(string txt,string text,CNode *root=NULL); // добавление в список с поиском по тексту узла, начиная с заданного узла
   int     AddNode(CNode *root,string text); // добавление под root
  };

//------------------------------------------------------------------    class CTreeCtrl
class CTreeCtrl : public CTreeBase
  {
   //--- базовые функции
public:
          CTreeCtrl() { m_root.m_file="__base__"; m_root.m_line=0; 
                        m_root.m_func="__base__"; m_root.m_class="__base__"; } // конструктор
          ~CTreeCtrl() { delete m_root; m_maxid=0; } // деструктор
   void    Reset(CNode *root=NULL); // сброс состояния всех узлов
   void    SetDataBy(int mode,int id,string text,CNode *root=NULL); // изменение текста для указанного ID, поиск с заданного узла
   string  GetDataBy(int mode,int id,CNode *root=NULL); // получение текста для указанного ID, поиск с заданного узла

   //--- обработка состояния
public:
   bool    IsExpand(int id,CNode *root=NULL); // получение свойства m_expand для указанного ID, поиск с заданного узла
   bool    ExpandIt(int id,bool state,CNode *root=NULL); // меняем состояние m_expand, поиск с заданного узла
   void    ExpandBy(int mode,CNode *node,bool state,CNode *root=NULL); // раксрываем узлы только указанного

   bool    IsCheck(int id,CNode *root=NULL); // получение свойства m_check для указанного ID, поиск с заданного узла
   bool    CheckIt(int id,bool state,CNode *root=NULL); // меняем состояние m_check на требуемую, начиная с заданного узла
   void    CheckBy(int mode,CNode *node,bool state,CNode *root=NULL); // отмечаем все дерево

   bool    IsSelect(int id,CNode *root=NULL); // получение свойства m_select для указанного ID, поиск с заданного узла
   bool    SelectIt(int id,bool state,CNode *root=NULL); // меняем состояние m_select на требуемую, начиная с заданного узла
   void    SelectBy(int mode,CNode *node,bool state,CNode *root=NULL); // подсвечиваем все дерево

   bool    IsBreak(int id,CNode *root=NULL); // получение свойства m_break для указанного ID, поиск с заданного узла
   bool    BreakIt(int id,bool state,CNode *root=NULL); // меняем состояние m_break, поиск с заданного узла
   void    BreakBy(int mode,CNode *node,bool state,CNode *root=NULL); // задаем только для указанного 

   //--- операции с нодами
public:
   void    SortBy(int mode,bool ascend,CNode *root=NULL); // сортировка по свойству
   void    GroupBy(int mode,CTreeCtrl *atree,CNode *node=NULL); // группировка по свойству
  };

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

Классы CTraceCtrl, CIn

class CTraceView; // предварительное объявление
//------------------------------------------------------------------    class CTraceCtrl
class CTraceCtrl
  {
public:
   CTreeCtrl   *m_stack; // объект графа
   CTreeCtrl   *m_info; // объект графа
   CTreeCtrl   *m_file; // группировка по файлам
   CTreeCtrl   *m_class; // группировка по классам
   CTraceView  *m_traceview; // указатель на отображение класса

   CNode   *m_cur; // указатель на текущий узел
           CTraceCtrl() { Create(); Reset(); } // создали трейсер
           ~CTraceCtrl() { delete m_stack; delete m_info; delete m_file; delete m_class; } // удалили трейсер
   void    Create(); // создали трейсер
   void    In(string afile,int aline,string aname,int aid); // вход в указанный узел 
   void    Out(int aid); // выход до указанного узла
   bool    StepBack(); // выход из узла на один шаг вверх (переход на родителя)
   void    Reset() { m_cur=m_stack.m_root; m_stack.Reset(); m_file.Reset(); m_class.Reset(); } // сброс всех узлов
   void    Clear() { m_cur=m_stack.m_root; m_stack.Clear(); m_file.Clear(); m_class.Clear(); } // сброс всех узлов

public:
   void    AddWatch(string name,string val); // проверка дебаг режима для узла
   void    Break(); // пауза для узла
  };

//------------------------------------------------------------------    CIn
class CIn
  {
public:
   void In(string afile,int aline,string afunc)
     {
      if(NIL(m_trace)) return; // если нет графа, то выходим
      if(NIL(m_trace.m_tree)) return;
      if(NIL(m_trace.m_tree.m_root)) return;
      if(NIL(m_trace.m_cur)) m_trace.m_cur=m_trace.m_tree.m_root;
      m_trace.In(afile,aline,afunc,-1); // вошли в следующий
     }
   void ~CIn() { if(!NIL(m_trace)) m_trace.Out(-1); } // вышли выше
  };


Модель работы класса CIn

На этот класс возлагается основная забота о создании дерева стека.

Построение графа происходит по шагам в два этапа с использованием двух функций CTraceCtrl:

void In(string afile, int aline, string aname, int aid); // вход в указанный узел
void Out(int aid);  // выход до указанного узла

Другими словами для построения дерева происходит постоянный вызов In-Out-In-Out-In-In-Out-Out и т.д.

Работа пары In-Out выглядит так:

1. Вход в блок (функцию, цикл, условие и т.д.), то есть сразу после скобки «{».
При входе в блок создается новый экземпляр CIn, который получает текущий CTraceCtrl, уже начатый с какими-то предыдущими узлами. В CIn вызывается функция CTraceCtrl::In и она создает новый узел в стеке. Узел создается сразу под текущим узлом CTraceCtrl::m_cur.  В новый узел заносится вся актуальная информация о входе: имя файла, номер строки, имя класса, функции, текущее время и т.д.

2. Выход из блока при скобке «}».
При выходе из блока – MQL автоматически вызывает деструктор CIn::~CIn. В деструкторе вызывается функция CTraceCtrl::Out. Указатель текущего узла CTraceCtrl::m_cur поднимается в дереве на уровень выше. При этом деструктор для нового узла не вызывается, узел остается в дереве.

Схема построения стека


Построение стека вызовов в виде дерева с заполнением всех данных вызова происходит при помощи контейнера CIn.



Макросы для упрощения вызова

Чтоб не прописывать в вашем коде длинные строки создания объекта CIn и входа в узел, удобно заменить её вызов макросом:
#define _IN    CIn _in; _in.In(__FILE__, __LINE__, __FUNCTION__)
Как видите, происходит создание объекта CIn и последующий вход в узел.


В связи с тем, что MQL выдаёт предупреждение в случае совпадения имен локальных и внешних переменных – лучше (аккуратнее и чище) создать аналогично 3-4 определения с другими именами переменных в таком виде:

#define _IN1    CIn _in1; _in1.In(__FILE__, __LINE__, __FUNCTION__)
#define _IN2    CIn _in2; _in2.In(__FILE__, __LINE__, __FUNCTION__)
#define _IN3    CIn _in3; _in3.In(__FILE__, __LINE__, __FUNCTION__)
По мере захода в подблоки нужно использовать последующие макросы _INx
bool CSampleExpert::InitCheckParameters(int digits_adjust)
  { _IN;
//--- initial data checks
   if(InpTakeProfit*digits_adjust<m_symbol.StopsLevel())
     { _IN1;
      printf("Take Profit must be greater than %d",m_symbol.StopsLevel());

С появлением в 411 билде макросов, можно полноценно использовать передачу параметров в #define.

Поэтому в классе CTraceCtrl вы встретите такое специальное макро-определение:

#define NIL(p)    (CheckPointer(p)==POINTER_INVALID)

Оно позволяет укоротить проверку валидности указателя.

Например, строка:

if (CheckPointer(m_tree))==POINTER_INVALID || CheckPointer(m_cur))==POINTER_INVALID) return;  

заменяется на более короткий вариант

if (NIL(m_tree) || NIL(m_cur)) return;



Подготовка ваших файлов к трассировке

Для контроля и получения стека необходимо сделать три шага.

1. Добавить в требуемые файлы
#include <Trace.mqh>

На данный момент вся стандартная библиотека основана на классе CObject. Поэтому если и в ваших классах он тоже является базовым, то будет достаточно подключить Trace.mqh  только в Object.mqh.

2. Расставить в требуемых блоках (можно через поиск-замена) макросы _IN

Пример использования макроса _IN
bool CSampleExpert::InitCheckParameters(int digits_adjust)
  { _IN;
//--- initial data checks
   if(InpTakeProfit*digits_adjust<m_symbol.StopsLevel())
     { _IN1;
      printf("Take Profit must be greater than %d",m_symbol.StopsLevel());


3. В главном модуле программы в функциях OnInit, OnTime, OnDeinit прописать создание, обновление и удаление глобального объекта CTraceCtrl соответственно. Ниже показан уже готовый код для вставки:

Внедрение трассировщика в основной код

//------------------------------------------------------------------    OnInit
int OnInit()
  {
   //****************
   m_traceview= new CTraceView; // создали отображение граф
   m_trace= new CTraceCtrl; // создали граф
   m_traceview.m_trace=m_trace; // присоединили граф
   m_trace.m_traceview=m_traceview; // присоединили отображение графа
   m_traceview.Create(ChartID()); // создали чарт
   //****************
   // остальной ваш код…
   return(0);
  }
//------------------------------------------------------------------    OnDeinit
void OnDeinit(const int reason)
  {
   //****************
   delete m_traceview;
   delete m_trace;
   //****************
   // остальной ваш код…
  }
//------------------------------------------------------------------    OnTimer
void OnTimer()
  {
   //****************
   if (m_traceview.IsOpenView(m_traceview.m_chart)) m_traceview.OnTimer();
   else { m_traceview.Deinit(); m_traceview.Create(ChartID()); } // если окно было случайно закрыто
   //****************
   // остальной ваш код…
  }
//------------------------------------------------------------------    OnChartEvent
void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
  {
   //****************
   m_traceview.OnChartEvent(id, lparam, dparam, sparam);
   //****************
   // остальной ваш код…
  }


Классы отображения трассировки

Итак, организация стека выполнена. Давайте теперь рассмотрим отображение получаемой информации.

Для этого мы создали два класса. CTreeView – для отображения дерева, и CTraceView – для контроля отображения деревьев и дополнительной информации по стеку. Оба класса являются потомками базового CView.


Классы CTreeView, CTraceView

//------------------------------------------------------------------    class CTreeView
class CTreeView: public CView
  {
   //--- базовые функции
public:
           CTreeView(); // конструктор
           ~CTreeView(); // деструктор
   void    Attach(CTreeCtrl *atree); // присоединили объект дерева для его отображения
   void    Create(long chart,string name,int wnd,color clr,color bgclr,color selclr,
                    int x,int y,int dx,int dy,int corn=0,int fontsize=8,string font="Arial");

   //--- функции обработки состояния
public:
   CTreeCtrl        *m_tree; // указатель на объект дерева для отображения
   int     m_sid; // последний выделенный объект (для подсветки)
   int     OnClick(string name); // обработка события клика на объекте

   //--- функции отображения
public:
   int     m_ndx, m_ndy; // размер отступов от кнопки для отрисовки
   int     m_bdx, m_bdy; // размер кнопки узлов
   CScrollView       m_scroll;
   bool    m_bProperty; // показывать свойства рядом с узлом

   void    Draw(); // обновить вид
   void    DrawTree(CNode *first,int xpos,int &ypos,int &up,int &dn); // перерисовать
   void    DeleteView(CNode *root=NULL,bool delparent=true); // удалить все элементы отображения, начиная с заданного узла
  };

//------------------------------------------------------------------    class CTreeView
class CTraceView: public CView
  {
   //--- базовые функции
public:
           CTraceView() { }; // конструктор
           ~CTraceView() { Deinit(); } // деструтор
   void    Deinit(); // полная деинициализация представления
   void    Create(long chart); // создаем и активируем представление

   //--- функции обработки состояния
public:
   int     m_hagent; // хендлер индикатора-агента для отправки сообщений
   CTraceCtrl   *m_trace; // указатель на созданные трейсер
   CTreeView    *m_viewstack; // дерево для отображения стека
   CTreeView    *m_viewinfo; // дерево для отображения свойств узла
   CTreeView    *m_viewfile; // дерево для отображения стека с группировкой по файлам
   CTreeView    *m_viewclass; // дерево для отображения стека с группировкой по классам
   void    OnTimer(); // обработчик таймера
   void    OnChartEvent(const int,const long&,const double&,const string&); // обработчик события

   //--- функции отображения
public:
   void    Draw(); // обновили объекты
   void    DeleteView(); // удалили отображение

   void    UpdateInfoTree(CNode *node,bool bclear); // вывод окна подробной информации узла
   string  TimeSeparate(long time); // спецфункция для преобразования времени в строку
  };

По оптимальному варианту было решено выполнить представление стека в отдельном окне.

То есть, класс CTraceView при создании в функции CTraceView::Create открывает окно чарта, и все объекты рисуются именно на нем, хотя сам CTraceView создан и работает в эксперте в другом окне. Это сделано для того, чтобы большой объем информации не мешал работе исходного кода трассируемой программы и её выводу собственной информации на чарт.

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

Индикатор создается в той же функции CTraceView::Create. У него всего один внешний параметр – ID чарта, на который надо отправлять все события.

Индикатор TraceAgent

#property indicator_chart_window
input long cid=0; // чарт получателя
//------------------------------------------------------------------    OnCalculate
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double& price[])
{ return(rates_total); }
//------------------------------------------------------------------    OnChartEvent
void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
    EventChartCustom(cid, (ushort)id, lparam, dparam, sparam);
  }

В результате получилось довольно структурированное представление стека.


В дереве слева TRACE отображается исходный полученный стек.

Под ним находится окно расширенной информации INFO по выделенному узлу (в текущем примере CTraceView::OnChartEvent). Два соседних окна с деревьями отображают этот же стек, но сгруппированный по классам (среднее дерево CLASS) и файлам (правое дерево FILE).

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



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



Возможности работы со стеком

  • Добавление Watch-параметров

Как вы заметили, в параметрах узла CNode имеется массив структур tagWatch. Она создана только для удобства представления информации. В ней хранится именованное значение переменной или выражения.

Структура Watch-значения

//------------------------------------------------------------------    struct tagWatch
struct tagWatch
{
    string m_name;     // имя
    string m_val;    // значение
};

Для добавления нового Watch-значения в текущий узел нужно вызвать функцию CTrace::AddWatch, или воспользоваться макросом _WATCH

#define _WATCH(w, v)         if (!NIL(m_trace) && !NIL(m_trace.m_cur)) m_trace.m_cur.AddWatch(w, string(v));


Специальным ограничением на добавляемые значения (по аналогии с узлами) есть отслеживание уникальности их имён. Это значит, что название Watch-значения проверяется на уникальность перед его добавлением в массив CNode::m_watch[]. Если в массиве значение с таким именем уже есть, то добавления не происходит, а только обновляется это значение.

Все отслеживаемые Watch-значения отображаются в информационном окне.



  • Пошаговое выполнение кода

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

Пауза организуется простым бесконечным циклом while (true). Удобство MQL5 здесь именно в отработке события выхода из этого цикла – ожидание клика на управляющей красной кнопке. Для создания точки остановки в процессе выполнения воспользуйтесь функцией CTrace::Break.


Функция для организации точки остановки

//------------------------------------------------------------------    Break
void CTraceCtrl::Break() // проверка дебаг режима для узла
  {
   if(NIL(m_traceview)) return; // проверка валидности
   m_stack.BreakBy(TG_ALL,NULL,false); // убрали флаги m_break со всех ущлов
   m_cur.m_break=true; // активировали только на текущем
   m_traceview.m_viewstack.m_sid=m_cur.m_id; // перенесли выделение на него
   m_stack.ExpandBy(TG_UP,m_cur,true,m_cur); // раскрыли родительские узлы, если закрыты
   m_traceview.Draw(); // отрисовали все
   string name=m_traceview.m_viewstack.m_name+string(m_cur.m_id)+".dbg"; // получили имя кнопки BREAK
   bool state=ObjectGetInteger(m_traceview.m_chart,name,OBJPROP_STATE);
   while(!state) // пока кнопка не нажата, выполняем цикл
     {
      Sleep(1000); // сделали паузу
      state=ObjectGetInteger(m_traceview.m_chart,name,OBJPROP_STATE);  // проверяем её состояние
      if(!m_traceview.IsOpenView()) break; // если окно закрыто, то выходим
      m_traceview.Draw(); // отрисовали возможные изменения
     }
   m_cur.m_break=false; // убрали флаг
   m_traceview.Draw(); // отрисовали обновление
  }


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

Для выхода из CTraceCtrl::Break надо кликнуть на красную кнопку возле имени узла.


Послесловие

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

В приложенных кодах вы найдете все классы, которые описаны в статье, плюс сервисные библиотеки (даются в минимальном требуемом объеме, так как они не самоцель). А также выкладываем в качестве готового примера - обновленные файлы из стандартной библиотеки, в которых просто расставлены макросы _IN. Все опыты проводились над экспертом из поставки MetaTrader 5 - MACD Sample.mq5.


Прикрепленные файлы |
mql5.zip (26.07 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (12)
---
--- | 27 мар. 2011 в 18:10
denkir:

А можно как-нибудь этот механизм использовать в скриптах?

думаю да. Но обычно в скриптах код не разветвлен сильно (если конечно скрипт не в цикле).

К тому же есть неудобство - в скриптах не обрабатывается событие OnChartEvent.

Denis Kirichenko
Denis Kirichenko | 27 мар. 2011 в 18:33
sergeev:

думаю да. Но обычно в скриптах код не разветвлен сильно (если конечно скрипт не в цикле).

К тому же есть неудобство - в скриптах не обрабатывается событие OnChartEvent.

А если мой скрипт использует много разных классов, иерархий классов?

Думаю, что нужно заточить инструмент и под скрипты...

---
--- | 27 мар. 2011 в 19:05

классу СTraceView все равно кто его вызывает.  Дерево он сделает и отобразит.

Но у скриптов нерешаемая проблема в обратной связи. Активно работать с деревом не получится.

Artapov Alexandr
Artapov Alexandr | 6 апр. 2011 в 22:02

Уважаемый, sergeev, помогите разобраться!

Не могу воспроизвести даже дерево эксперта из примера, что делаю не так? Он то должен казать:(

Взял mql5-3.zip (последний), распаковал -папку MQH в include\expert\ - индикатор в Индикаторы, EXPERT(пример) в  папку Expert.

Да, и в Объект вписал <Trace>.

Все пути исправил, скомпилировал - всё прошло.

А дальше - кидаю Индикатор на график-окно НЕ открывается; кидаю Эксперта - и тут в свойствах его уже НЕТ кнопки "ДА, ОК" лишь "Отмена и Сброс".

Спасибо.

---
--- | 6 апр. 2011 в 22:09
artall:

А дальше - кидаю Индикатор на график-окно НЕ открывается; кидаю Эксперта - и тут в свойствах его уже НЕТ кнопки "ДА, ОК" лишь "Отмена и Сброс".


1. индикатор кидать никуда не надо. его эксперт сам кинет.

2. прочитайте мануал. последняя строка поста.

Уменьшаем расход памяти на вспомогательные индикаторы Уменьшаем расход памяти на вспомогательные индикаторы
Если индикатор для своих расчетов задействует значения множества других индикаторов, то такая система расходует много памяти. В статье рассмотрены несколько способов снижения расхода памяти при использовании вспомогательных индикаторов. Сэкономленная память позволит вам увеличить число одновременно используемых в терминале валютных пар, индикаторов и стратегий, что повысит надежность вашего торгового портфеля. Вот так простая забота о технических ресурсах вашего компьютера способна превратиться в материальные ресурсы на вашем депозите.
Статистические оценки Статистические оценки
Оценка статистических параметров последовательности очень важна, так как большинство математических моделей и методов строятся исходя из различного рода предположений, например, о нормальности закона распределения, или требуют знания значения дисперсии или других параметров. В статье кратко рассматриваются простейшие статистические параметры случайной последовательности и некоторые методы ее визуального анализа. Предлагается реализация этих методов на MQL5 и способ визуализации результатов расчета при помощи программы Gnuplot.
Индикаторы малой, промежуточной и основной тенденции Индикаторы малой, промежуточной и основной тенденции
Предметом статьи является исследование возможности автоматизации торговли и анализа на основании некоторых идей из книги Джеймса Хьержика "Модель, Цена и Время. Применение теории Ганна в системах торговли" в виде индикаторов и эксперта. Не претендуя на исчерпывающую полноту, здесь исследуется только первая часть теории Ганна - Модель.
Использование ресурсов в MQL5 Использование ресурсов в MQL5
Программы на MQL5 позволяют не только автоматизировать рутинные вычисления, но и создавать полноценную графическую оболочку. Возможности по созданию по-настоящему интерактивных элементов управления стали практически такими же широкими, как и в классических языках программирования. Если вы хотите писать полноценные самостоятельные программы на MQL5, используйте в них ресурсы. Такие программы легче поддерживать и распространять.