Визуальная оптимизация прибыльности индикаторов и сигналов

Sergey Kravchuk | 11 июня, 2009

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


Введение

Сразу же хочу предупредить всех граалеискателей и критиканов "всего и вся": так же как инструмент из прошлой статьи, этот - тоже не волшебная палочка, которая позволит вам полностью избежать убытков и получать одну только прибыль и ничего кроме прибыли. Это просто средство для быстрых вычислений и отображения их результатов в удобной и наглядной форме. Картинки, которые рисует этот индикатор, - это скорее пища для размышлений трейдера над своими торговыми тактиками и используемыми в них сигналами. Но, тем не менее, в краткосрочной (особенно – внутридневной) торговле он вполне может стать действительно рабочим инструментом.

На идею доработать старый индикатор до нового состояния меня натолкнула одна трогательно-наивная и очевидно совершенно искренняя фраза, сопровождавшая текст выложенного в одной из веток на нашем форуме эксперта: "За 2009 год ведет себя молодцом. Пока довольна. Для облегчения наблюдения за сигналом сделала….". Эксперт действительно вел себя молодцом на указанной автором паре и тайме. Однако любые попытки изменения параметров превращали молодца в откровенного убийцу, который почти мгновенно сливал весь депозит. И хотя автор эксперта не претендовала на то, что им можно торговать на реале, задачка засела в мозгу: как можно не просто проверить какие будут результаты торговли, но и оценить ее характер, если хотите – устойчивость получения результатов. Причем хотелось сделать это быстро и очевидно: чтобы можно было наглядно показать безуспешность некоторых сигналов и торговых тактик. И тут же червячком засверлила другая мысль: но ведь с такими-то параметрами эксперт дает прибыль и не плохую. Ведь если бы я ручками так торговал – я был бы страшно доволен своими результатами. А вдруг их еще можно улучшить? В общем, задача оказалась интересной "сама по себе", и я задумался о том, как ее можно решить.


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

Ясно, что базой для решения должен был стать мой индикатор из статьи "Визуальное тестирование прибыльности индикаторов и сигналов" - там проделана львиная доля работы, нужно только довести его до (нового) ума. Чего не хватало в предыдущей реализации? Конечно же, удобного механизма подбора параметров. Каждый раз заходить в параметры индикатора, что-то там менять, смотреть на полученную итоговую цифру и анализировать линии ордеров на истории – это конечно задача для особо усидчивых. Оно и понятно – тот индикатор создавался для проверки готовых сигналов, а здесь требовалось нечто другое. Как должно выглядеть это другое? Тот, кто интенсивно пользуется тестером стратегий в МТ отлично представляет себе это. Для оценки результатов торговли нужны, в первую очередь, графики изменения баланса и возможность менять исходные параметры для вычисления значений сигналов.


График изменения баланса

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


Треугольная эмуляция ползунков

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

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

Для работы с треугольными ползунками была написана специальная функция:

double ParamValue(int ParamNo, string ParamName, double ParamValue, double vMin, double vMax, color clr)
{
  double Triangle[3],vCur, WMax, WMin; datetime tt1, tt2;

  // если треугольника нет - создадим его
  if(ObjectFind(ParamName) < 0)
  {
    // определим границы графика в текущем масштабе по вертикали
    WMax = WindowPriceMax();  WMin = WindowPriceMin();

    // рассчитаем координаты точек по времени...
    tt1 = Time[0] + Period()*60*ParamNo*20; tt2 = tt1 + Period()*60*20;

    // ... и "цене"
    vCur = WMin + (ParamValue - vMin) * (WMax - WMin) / (vMax - vMin);

    // создадим объект и покрасим его в заданный в параметрах цвет
    ObjectCreate(ParamName,OBJ_TRIANGLE, 0, tt1,WMax, tt2,vCur, tt1,WMin);
    ObjectSet(ParamName,OBJPROP_COLOR,clr);
  }

  // треугольник существует - получим его координаты
  Triangle[0] = ObjectGet(ParamName,OBJPROP_PRICE1);
  Triangle[1] = ObjectGet(ParamName,OBJPROP_PRICE2);
  Triangle[2] = ObjectGet(ParamName,OBJPROP_PRICE3);

  // расположим вершины в порядке их "возрастания
  ArraySort(Triangle);

  // переведем координату средней точки к масштабу реальных значений между vMin и vMax
  vCur = vMin + (Triangle[1] - Triangle[0]) / (Triangle[2] - Triangle[0]) * (vMax - vMin);

  // напишем значение в комментарий объекта
  ObjectSetText(ParamName,DoubleToStr(vCur,2));

  // вернем значение в основной модуль
  return(vCur);

}

Комментарии достаточно подробно описывают алгоритм работы функции, поэтому ограничимся объяснением назначения параметров функции.

ParamNo – номер используемого параметра (по нему смещаются треугольники друг относительно друга по оси времени). Впоследствии вы можете изменять их месторасположение и размер по своему усмотрению.

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

ParamValue – начальное значение параметра (используется для правильного расположения средней вершины между двумя значениями минимума vMin и максимума vMax, которые мы будет использовать при оптимизации).

сlr – цвет треугольника.

Работа с треугольниками выполняется следующим кодом:

MAPeriod  = ParamValue(0, SliderPrefix+"MA Period", MAPeriod, 5, 35, Blue);

RSIPeriod = ParamValue(1, SliderPrefix+"RSI Period", RSIPeriod, 2, 25, Red);

RSILevel  = ParamValue(2, SliderPrefix+"RSI Level", RSILevel, 5, 95, Orange);

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

В качестве примера для разработки и отладки индикатора я выбрал индикатор Helen (HI_Line_E_RSI_MA.mq4), который собственно и подвигнул меня на эту разработку. Блок вычисления сигналов пришлось переписать под использование внутри моего индикатора, но в принципе код остался практически неизменным:

// заполним значениями сигнальные массивы и посчитаем их количество
  for(i=DisplayBars;i>=0;i--)
  {

    double t1=iRSI(NULL,0,RSIPeriod,MAPrice,i+1);
    double t11=iRSI(NULL,0,RSIPeriod,MAPrice,i+2);

    double t2=iMA(NULL,0,MAPeriod,0,MAMode,MAPrice,i+1);
    double t3=iMA(NULL,0,MAPeriod*3,0,MAMode,MAPrice,i+1);

    if (t1>RSILevel&&t11<RSILevel&&t1>t11&&t1>RSILevel&&t2>t3) {sBuy[i]=1; sBuyCnt++;}
    if (t1<(100-RSILevel)&&t11>(100-RSILevel)&&t1<t11&&t1<(100-RSILevel)) {sCloseBuy[i]=1; sBuyCloseCnt++;}

    if (t1<(100-RSILevel)&&t11>(100-RSILevel)&&t1<t11&&t1<(100-RSILevel)&&t2<t3) {sSell[i]=1; sSellCnt++; }
    if (t1>RSILevel&&t11<RSILevel&&t1>t11&&t1>RSILevel) {sCloseSell[i]=1; sSellCloseCnt++;}
  }

Я пока не стал делать четвертым параметром кратность увеличения периода для получения длиной МА, а просто использую ее в три раза длиннее основной (MAPeriod*3). При желании это легко можно будет доделать самому.

Еще одна добавка к прежнему индикатору - блок расчета и показа кривой изменения баланса:

// построим график баланса
// соберем последовательно и просуммируем профиты 
i2 = DisplayBars; bBallance[i2] = 0; 
for(i=DisplayBars-1;i>=0;i--) { if(bBallance[i] != 0) { bBallance[i2] = bBallance[i2+1] + bBallance[i]; i2--;  } }
double multiplier; if(ProfitInPoints) multiplier = 1; else multiplier = ToCurrency;
for(i=0;i<DisplayBars-2;i++) bBallance[i] = bBallance[i+i2+1] * multiplier;
SetIndexStyle(0,DRAW_HISTOGRAM,STYLE_SOLID,ProfitWidth,Blue);

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

В результате получился следующий текст индикатора:

/*///——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

    IndicatorOptimizer.mq4 
  
    Визуальное тестирование прибыльности индикаторов и сигналов
    
    Copyright © 2009, Кравчук Сергей,  http://forextools.com.ua

/*///——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
#property copyright "Copyright © 2006-2009, Sergey Kravchuk. http://forextools.com.ua"
#property link      "http://forextools.com.ua"

#property indicator_separate_window
#property indicator_buffers 1
#property indicator_level1 0

// параметры отображения элементов графика
extern bool   ShowZZ          = true;           // рисовать ли ЗЗ
extern bool   ShowMARKERS     = false;           // рисовать ли вертикальные линии-метки
extern int    ProfitWidth     = 1;
extern bool   ProfitInPoints  = true;

//даты построения графика
extern datetime DateStart     = D'1.01.1970';
extern datetime DateEnd       = D'31.12.2037';

//параметры расчетов прибыли
extern double LotForCalc      = 0.05;           // размер лота для расчета прибыли

// параметры индикатора RSI
extern int    RSIPeriod       = 8;              // период для расчета RSI
extern int    RSILevel        = 73;             // уровень для расчета RSI

// параметры индикатора MA
extern int    MAPeriod        = 20;             // период для расчета МА
extern int    MAMode          = 3;              // 0-MODE_SMA 1-MODE_EMA 2-MODE_SMMA 3-MODE_LWMA
extern int    MAPrice         = 6;              // 0-Close 1-Open 2-High 3-Low 4-(H+L)/2 5-(H+L+C)/3 6-(H+L+2*C)/4


// параметры индикатора MACD
extern double MACDOpenLevel   = 3;              
extern double MACDCloseLevel  = 2;
extern double MATrendPeriod   = 26;

// цвета линий покупок
extern color  ColorProfitBuy  = Blue;           // цвет линии для профитных покупок
extern color  ColorLossBuy    = Red;            // цвет линии для убыточных покупок
extern color  ColorZeroBuy    = Gray;           // цвет линии для покупок с нулевой прибылью

// цвета линий продаж
extern color  ColorProfitSell = Blue;           // цвет линии для профитных продаж
extern color  ColorLossSell   = Red;            // цвет линии для убыточных продаж
extern color  ColorZeroSell   = Gray;           // цвет линии для продаж с нулевой прибылью

// цвета линий сигналов
extern color  ColorBuy        = CornflowerBlue; // цвет линии сигнала на покупку
extern color  ColorSell       = HotPink;        // цвет линии сигнала на продажу
extern color  ColorClose      = Gainsboro;      // цвет линии сигнала на закрытие

//—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
double sBuy[],sCloseBuy[],sSell[],sCloseSell[]; // массивы для сигналов
int sBuyCnt,sSellCnt,sBuyCloseCnt,sSellCloseCnt;// счетчики сигналов 
int i,DisplayBars;
double bBallance[]; // для баланса по операциям
int IndicatorWindowNo;  // номер окна индикатора
//—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
// служебные коды
#define MrakerPrefix "IO_"
#define SliderPrefix "SL_"
#define OP_CLOSE_BUY  678
#define OP_CLOSE_SELL 876
//—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
int init()   
{ 
  SetIndexBuffer(0,bBallance);
  IndicatorShortName("IndicatorOptimizer");
  
  return(0); 
}
int deinit() 
{ 
  ClearMarkers(); 
  ClearSliders();
  return(0); 
}

double ParamValue(int ParamNo, string ParamName, double ParamValue, double vMin, double vMax, color clr)
{
  double Triangle[3],vCur, WMax, WMin; datetime tt1, tt2; 

  // если треугольника нет - создадим его
  if(ObjectFind(ParamName) < 0)
  {
    // определим границы графика в текущем масштабе по вертикали
    WMax = WindowPriceMax();  WMin = WindowPriceMin();

    // рассчитаем координаты точек по времени...
    tt1 = Time[0] + Period()*60*ParamNo*20; tt2 = tt1 + Period()*60*20;
    // ... и "цене"
    vCur = WMin + (ParamValue - vMin) * (WMax - WMin) / (vMax - vMin);
    // создадим объект и покрасим его в заданный в параметрах цвет
    ObjectCreate(ParamName,OBJ_TRIANGLE, 0, tt1,WMax, tt2,vCur, tt1,WMin);
    ObjectSet(ParamName,OBJPROP_COLOR,clr);
  }
  // треугольник существует - получим его координаты
  Triangle[0] = ObjectGet(ParamName,OBJPROP_PRICE1);
  Triangle[1] = ObjectGet(ParamName,OBJPROP_PRICE2);
  Triangle[2] = ObjectGet(ParamName,OBJPROP_PRICE3);
  // расположим вершины в порядке их "возрастания
  ArraySort(Triangle);
  // переведем координату средней точки к масштабу реальных значений между vMin и vMax
  vCur = vMin + (Triangle[1] - Triangle[0]) / (Triangle[2] - Triangle[0]) * (vMax - vMin);
  // напишем значение в комментарий объекта
  ObjectSetText(ParamName,DoubleToStr(vCur,2)); 
  // вернем значение в основной модуль
  return(vCur); 
}


//—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
int start() 
{ 
  double Profit=0,P1,P2; int CntProfit=0,CntLoose=0,i1,i2;
  // к-т для перевода пунктов в валюту депозита
  double ToCurrency = MarketInfo(Symbol(),MODE_TICKVALUE)*LotForCalc;    
  
  //———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
  // удалим все метки на случай если индикатор перерисовывается
  ClearMarkers(); 
  //———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
  // подготовим счетчики сигналов
  sBuyCnt=0; sSellCnt=0; sBuyCloseCnt=0; sSellCloseCnt=0; 
  //———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
  // отведем память под сигнальные массивы и обнулим их значения
  DisplayBars=Bars; // к-во отображаемых баров
  ArrayResize(sBuy,DisplayBars);  ArrayInitialize(sBuy,0);
  ArrayResize(sSell,DisplayBars); ArrayInitialize(sSell,0);
  ArrayResize(sCloseBuy,DisplayBars);  ArrayInitialize(sCloseBuy,0);
  ArrayResize(sCloseSell,DisplayBars); ArrayInitialize(sCloseSell,0);

  // подготовим расчет баланса
  ArrayInitialize(bBallance,0); 
  //———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————


  //———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
  // создание (при необходимости) и получение параметров управляющего треугольника
  MAPeriod  = ParamValue(0, SliderPrefix+"MA Period", MAPeriod, 5, 35, Blue);
  RSIPeriod = ParamValue(1, SliderPrefix+"RSI Period", RSIPeriod, 2, 25, Red);
  RSILevel  = ParamValue(2, SliderPrefix+"RSI Level", RSILevel, 5, 95, Orange);
  //———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————


  // заполним значениями сигнальные массивы и посчитаем их количество
  for(i=DisplayBars;i>=0;i--) 
  {
    double t1=iRSI(NULL,0,RSIPeriod,MAPrice,i+1);
    double t11=iRSI(NULL,0,RSIPeriod,MAPrice,i+2);

    double t2=iMA(NULL,0,MAPeriod,0,MAMode,MAPrice,i+1);

    double t3=iMA(NULL,0,MAPeriod*3,0,MAMode,MAPrice,i+1);

    if (t1>RSILevel&&t11<RSILevel&&t1>t11&&t1>RSILevel&&t2>t3) {sBuy[i]=1; sBuyCnt++;}
    if (t1<(100-RSILevel)&&t11>(100-RSILevel)&&t1<t11&&t1<(100-RSILevel)) {sCloseBuy[i]=1; sBuyCloseCnt++;}

    if (t1<(100-RSILevel)&&t11>(100-RSILevel)&&t1<t11&&t1<(100-RSILevel)&&t2<t3) {sSell[i]=1; sSellCnt++; } 
    if (t1>RSILevel&&t11<RSILevel&&t1>t11&&t1>RSILevel) {sCloseSell[i]=1; sSellCloseCnt++;}
  }

 
        //———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
  // удалим повторяющиеся сигналы оставив только самые первые - левые по графику
  for(i=0;i<DisplayBars;i++) 
  {
    if(sBuy[i]==sBuy[i+1]) sBuy[i]=0;
    if(sSell[i]==sSell[i+1]) sSell[i]=0;
    if(sCloseBuy[i]==sCloseBuy[i+1]) sCloseBuy[i]=0;
    if(sCloseSell[i]==sCloseSell[i+1]) sCloseSell[i]=0;
  }
  // удалим сигналы вне заданного диапазона дат
  for(i=0;i<DisplayBars;i++) 
  {
    if(Time[i]<DateStart || DateEnd<Time[i]) { sBuy[i]=0; sSell[i]=0; sCloseBuy[i]=0; sCloseSell[i]=0; }
  }
  // добавим принудительное закрытие на границе диапазона
  if(DateEnd<=Time[0]) { i=iBarShift(Symbol(),Period(),DateEnd); sBuy[i]=0; sSell[i]=0; sCloseBuy[i]=1; sCloseSell[i]=1; }
  if(DateEnd >Time[0]) { sCloseBuy[0]=1; sCloseSell[0]=1; }
  //———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
  // посчитаем количество сигналов
  for(i=0;i<DisplayBars;i++) 
  {
    if(sBuy [i]!=0) sBuyCnt++;  if(sCloseBuy [i]!=0) sBuyCloseCnt++;
    if(sSell[i]!=0) sSellCnt++; if(sCloseSell[i]!=0) sSellCloseCnt++;
  }
  //———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
  // расставим метки, нарисуем ЗЗ и посчитаем прибыль
  //———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
  // обработаем покупки
  for(i=DisplayBars-1;i>=0;i--) // пойдем собирать точки
  {
    // найдем очередную точку открытия и запомним ее место и цену
    for(i1=i;i1>=0;i1--) if(sBuy[i1]!=0) break; 

    // найдем очередную точку закрытия покупки и запомним ее место и цену
    for(i2=i1-1;i2>=0;i2--) if(sCloseBuy[i2]!=0) break;
    if(i2<0) i2=0; // для последней незакрытой позы считаем закрытие на текущей цене
    i=i2; // новый бар для продолжения поиска точек открытия

    // определим цены для рисования 
    P1=Open[i1]; P2=Open[i2];  
    
    P1/=Point; P2/=Point; // приведем цены к пунктам
    
    // определяем профит и заполняем соответствующий буфер
    if(i1>=0) 
    { 
      Profit=Profit+P2-P1; // соберем суммарный профит
      if(P2-P1>=0) CntProfit++; else CntLoose++; // посчитаем число ордеров
      DrawLine(i1,i2,OP_BUY); // нарисуем линию ордера
      PlaceMarker(i1,OP_BUY); 
      PlaceMarker(i2,OP_CLOSE_BUY); 
      
      bBallance[i2] += P2-P1;
    }
  }
  //———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
  // обработаем продажи
  for(i=DisplayBars-1;i>=0;i--) // пойдем собирать точки
  {
    // найдем очередную точку открытия и запомним ее место и цену
    for(i1=i;i1>=0;i1--) if(sSell[i1]!=0) break; 

    // найдем очередную точку закрытия покупки и запомним ее место и цену
    for(i2=i1-1;i2>=0;i2--) if(sCloseSell[i2]!=0) break;
    if(i2<0) i2=0; // для последней незакрытой позы считаем закрытие на текущей цене
    i=i2; // новый бар для продолжения поиска точек открытия

    // определим цены для рисования в зависимости от параметра оптимистичности Optimizm
    P1=Open[i1]; P2=Open[i2]; 
    
    P1/=Point; P2/=Point; // приведем цены к пунктам
    
    // если обе точки есть - определяем профит и заполняем соответствующий буфер
    if(i1>=0) 
    { 
      Profit=Profit+P1-P2; // соберем суммарный профит
      if(P1-P2>=0) CntProfit++; else CntLoose++; // посчитаем число ордеров
      DrawLine(i1,i2,OP_SELL); // нарисуем линию ордера
      PlaceMarker(i1,OP_SELL); 
      PlaceMarker(i2,OP_CLOSE_SELL);
      
      bBallance[i2] += P1-P2;
    }
  }

  //———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
  // построим график баланса
  // соберем последовательно и просуммируем профиты 
  i2 = DisplayBars; bBallance[i2] = 0; 
  for(i=DisplayBars-1;i>=0;i--) { if(bBallance[i] != 0) { bBallance[i2] = bBallance[i2+1] + bBallance[i]; i2--;  } }
  double multiplier; if(ProfitInPoints) multiplier = 1; else multiplier = ToCurrency;
  for(i=0;i<DisplayBars-2;i++) bBallance[i] = bBallance[i+i2+1] * multiplier;
  SetIndexStyle(0,DRAW_HISTOGRAM,STYLE_SOLID,ProfitWidth,Blue);

  //———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
  // расчет итоговых цифр для комментария  
  int Cnt=CntProfit+CntLoose; // общее число операций
  string msg="Период: "+TimeToStr(MathMax(DateStart,Time[Bars-1]))+" - "+TimeToStr(MathMin(DateEnd,Time[0]))+"\n\n";
  
  msg=msg + 
  "Период RSI: " + RSIPeriod + "\n" +
  "Уровень RSI: " + RSILevel + "\n" +
  "Период МА: " + MAPeriod + "\n\n" +
  sBuyCnt+" покупок и "+sBuyCloseCnt+" их закрытий\n"+
  sSellCnt+" продаж и "+sSellCloseCnt+" их закрытий\n\n";
  
  // общее время в днях
  int TotalDays = (MathMin(DateEnd,Time[0])-MathMax(DateStart,Time[Bars-1]))/60/60/24; //переведем секунды в дни
  if(TotalDays<=0) TotalDays=1; // чтоб не было деления на ноль для неполного дня
  
  if(Cnt==0) msg=msg+("Нет ни одной операции");
  else msg=msg+
  (
    DoubleToStr(Profit,0)+" пункт на "+Cnt+" операциях за "+TotalDays+" дней\n"+
    DoubleToStr(Profit/Cnt,1)+" пункт на операцию ("+
    DoubleToStr(Profit/TotalDays,1)+" за день)\n\n"+
    "При торговле лотом "+DoubleToStr(LotForCalc,2)+" получаем в "+AccountCurrency()+":\n"+
    DoubleToStr(Profit*ToCurrency,0)+" всего, по "+
    DoubleToStr(Profit/Cnt*ToCurrency,1)+" на операцию ("+
    DoubleToStr(Profit/TotalDays*ToCurrency,1)+" за день)\n\n"+
    CntProfit+" прибыльных ("+DoubleToStr(((CntProfit)*1.0/Cnt*1.0)*100.0,1)+"%)\n"+
    CntLoose+" убыточных ("+DoubleToStr(((CntLoose)*1.0/Cnt*1.0)*100.0,1)+"%)"
  );
  Comment(msg);
  
}
//—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————



//—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
// удаление всех объектов нашего графика
void ClearMarkers() 
{ 
  for(int i=ObjectsTotal()-1;i>=0;i--) if(StringFind(ObjectName(i),MrakerPrefix)==0) ObjectDelete(ObjectName(i)); 
}
void ClearSliders() 
{ 
  for(int i=ObjectsTotal()-1;i>=0;i--) if(StringFind(ObjectName(i),SliderPrefix)==0) ObjectDelete(ObjectName(i)); 
}
//—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
// установка вертикальной линии - метки операции типа op_type
void PlaceMarker(int i, int op_type)
{
  if(!ShowMARKERS) return; // показ маркеров отключен

  color MarkerColor; string MarkName; 

  // __ чтобы по сортировке линиия закрытия нарисовалась ниже линии открытия 
  if(op_type==OP_CLOSE_SELL) { MarkerColor=ColorClose; MarkName=MrakerPrefix+"__SELL_"+i; }
  if(op_type==OP_CLOSE_BUY)  { MarkerColor=ColorClose; MarkName=MrakerPrefix+"__BUY_"+i;  }  
  if(op_type==OP_SELL)       { MarkerColor=ColorSell;  MarkName=MrakerPrefix+"_SELL_"+i;  }
  if(op_type==OP_BUY)        { MarkerColor=ColorBuy;   MarkName=MrakerPrefix+"_BUY_"+i;   }

  ObjectCreate(MarkName,OBJ_VLINE,0,Time[i],0); 
  ObjectSet(MarkName,OBJPROP_WIDTH,1); 
  if(op_type==OP_CLOSE_BUY || op_type==OP_CLOSE_SELL) ObjectSet(MarkName,OBJPROP_STYLE,STYLE_SOLID); 
  else ObjectSet(MarkName,OBJPROP_STYLE,STYLE_DOT);
  ObjectSet(MarkName,OBJPROP_BACK,True);  

  ObjectSet(MarkName,OBJPROP_COLOR,MarkerColor);
}
//—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
// установка линии на графике для операции типа op_type по ценам параметра Optimizm
void DrawLine(int i1,int i2, int op_type)
{
  if(!ShowZZ) return; // показ ЗЗ отключен
  
  color СurColor;
  string MarkName=MrakerPrefix+"_"+i1+"_"+i2;
  double P1,P2;
  
  // определим цены для рисования в зависимости от параметра оптимистичности Optimizm
  P1=Open[i1]; P2=Open[i2];  

  ObjectCreate(MarkName,OBJ_TREND,0,Time[i1],P1,Time[i2],P2);
  
  ObjectSet(MarkName,OBJPROP_RAY,False); // рисуем отрезки а не линии
  ObjectSet(MarkName,OBJPROP_BACK,False);
  ObjectSet(MarkName,OBJPROP_STYLE,STYLE_SOLID);
  ObjectSet(MarkName,OBJPROP_WIDTH,2);

  // зададим цвет линии в зависимости от прибыльности операции
  if(op_type==OP_BUY) 
  { 
    if(P1 <P2) СurColor = ColorProfitBuy;
    if(P1==P2) СurColor = ColorZeroBuy;
    if(P1 >P2) СurColor = ColorLossBuy;
  }
  if(op_type==OP_SELL) 
  { 
    if(P1 >P2) СurColor = ColorProfitSell;
    if(P1==P2) СurColor = ColorZeroSell;
    if(P1 <P2) СurColor = ColorLossSell;
  }
  ObjectSet(MarkName,OBJPROP_COLOR,СurColor);
}
//—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

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


Оптимизатор в действии

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

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

Работа с оптимизатором теперь превращается в своеобразную игру с ним, известную под названием "а что если?". Вы просто выделяете треугольники-ползунки и начинаете перемещать вверх\вниз их серединки. Неважно, где будет размещена вершина треугольника по горизонтали – значение имеет только вертикальное расположение вершин. Итого – вы двигаете серединки треугольников, и пытаетесь уловить, к каким последствиям это перемещение приводит. Интуиция и ваш личный практический опыт наверняка подскажут вам, что и куда нужно двигать. Ну а если этого еще нет, "метод тыка" еще никого не подводил: пробуйте, ищите варианты, которые вас устроят, и впоследствии торгуйте по найденным параметрам.


Как это использовать: часть первая - что делать НЕ нужно

Так же как и мой прошлый индикатор, этот нужно использовать с пониманием того, что вы делаете и какие результаты получаете. Вы можете самостоятельно заменить расчетный блок сигналов на свой собственный, а также увеличить или уменьшить количество используемых сигналов, но глядя на получаемые результаты никогда не забывайте о том, что если вы смогли оптимизировать параметры индикатора для EURUSD М30 и он показывает хорошую кривую роста прибыли, это совершенно не означает, что с такими же параметрами вы получите такие же результаты на другой валютной паре или другом периоде, или на том и другом вместе. Чтобы понять, почему это так, просто взгляните на график М1 и D1. Разница в характере смены баров и их размерах настолько очевидна, что я даже не вижу смысла что-либо еще добавлять.

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


Как это использовать: часть вторая - что делать НУЖНО

Однако не все так плохо, как кажется. Как отмечают некоторые философы, "недостатки – это продолжения наших достоинств". Если мы видим недостатки и до сих пор не видим достоинства, это вовсе не означает, что их нет. Посмотрите на дневной график с достаточно длинным равномерно восходящим трендом. Неужели вы думаете, что индикатор, оптимизированный на начале этого периода, не принесет вам прибыль в середине этого тренда?!

Если вы решите использовать этот инструмент в своей торговле, так же как собираюсь его использовать и я, вам придется просто периодически его настраивать под текущие пары и таймы. Вы можете взять тот же эксперт Helen и пустить его в автоторговлю, периодически поправляя его параметры под результаты очередной оптимизации, благо, индикатор позволяет без особого напряга выполнять этот процесс хоть по десять раз на день и таким образом использовать его для торговли даже на минутных графиках (М5, М15 и М30).


Заключение

Рынок, по большому счету, все-равно непредсказуем. Любые 100% надежные и подтвержденные сигналы могут быть в одну секунду обращены в противоположные выходом какой-то особо важной новости. В процессе обсуждения этой статьи наверняка "зубры терйдинга" очень доходчиво будут объяснять, что, например, не учитывая фундаментальных сигналов или не имея данных об объемах торгов (хотя бы того же стакана Оанды), вы никогда не поймете, куда двинется цена, и не получите устойчивую прибыль. Ну что же, это их правда, они так торгуют, и у них так получается. Говорить о таком стиле торговли можно, имея в своем управлении не одну тысячу долларов.

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