English 中文 Español Deutsch 日本語 Português
Визуальная оптимизация прибыльности индикаторов и сигналов

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

MetaTrader 4Примеры | 11 июня 2009, 08:18
5 243 30
Sergey Kravchuk
Sergey Kravchuk

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


Введение

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

На идею доработать старый индикатор до нового состояния меня натолкнула одна трогательно-наивная и очевидно совершенно искренняя фраза, сопровождавшая текст выложенного в одной из веток на нашем форуме эксперта: "За 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% надежные и подтвержденные сигналы могут быть в одну секунду обращены в противоположные выходом какой-то особо важной новости. В процессе обсуждения этой статьи наверняка "зубры терйдинга" очень доходчиво будут объяснять, что, например, не учитывая фундаментальных сигналов или не имея данных об объемах торгов (хотя бы того же стакана Оанды), вы никогда не поймете, куда двинется цена, и не получите устойчивую прибыль. Ну что же, это их правда, они так торгуют, и у них так получается. Говорить о таком стиле торговли можно, имея в своем управлении не одну тысячу долларов.

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

Прикрепленные файлы |
IndicatorOptimizer.zip (1820.28 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (30)
Stanislav Korotky
Stanislav Korotky | 23 июл. 2009 в 11:56
ForexTools:

чтож так пессимистично? ;)

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

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

Но, так уж получилось, что я успел уже подправить версию 3, так что выкладываю версию 4.

В ней исправлен глюк со случайной неперерисовкой контролов.

А самое главное - сделан вывод графика эквити - разумеется на объектах, т.к. это не индикатор. Параметры:

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

- SqueezeEquityGraph - выбрасывать ли промежуточные бары, на которых не было изменений; если сжатие включено, то график НЕ соответствует времени баров (время и значение эквити указывается в описании объектов); если сжатие не включено, график совпадает с барами, но имеет промежутки там, где нет сделок.

Stanislav Korotky
Stanislav Korotky | 23 авг. 2009 в 20:21

Выкладываю последнюю версию OptiRunner5.mq4. Исправлены мелкие баги, добавлена возможность управлять периодом оптимизации с помощью двух вертикальных линий. Если что-то еще и появится, то это возможность анализировать стратегии с заданными уровнями TakeProfit и StopLoss (что уже вошло в библиотеку Optimatic, которую я готовлю для публикации ;-) ).

Sergey Kravchuk
Sergey Kravchuk | 24 авг. 2009 в 09:37
marketeer:

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

как говаривал один классик: "правильной дорогой идете товарищ!", но более полный вариант - это задание нескольких отрезков с одинаково начинающимися именами (например: участок01, участок02,...). Они расставляются на графике ручками и оптимизация выполняется только на барах "внутри" отрезков с нужным префиксом ("участок") который задается в параметрах индикатора. Что это дает? Можно выделить несколько однотипных участков - тогда картина оптимизации будет более "точная". Вторая фишка: находим однотипные участки, метим их отрезками, потом у одного из них имя отрезка меняется на "-участок" и он выпадает из оптимизации. После того как оптимальные параметры по остальным участкам найдены - задаем другое имя префиксов отрезков (в нашем случае - "-участок") и делаем "контрольный выстрел" на контрольном участке ;)

Я в своем рабочем варианте сделал такое - замечательно получилось. К сожалению, выложить этот вариант я не могу, поскольку очень тесно "скрестил" его с другим своим индикатором поиска аналогичных участков истории ft.Dejavu и он перестал быть универсальным. Может вы встроите этот механизм в свой OptiRunner6 ?

Stanislav Korotky
Stanislav Korotky | 24 авг. 2009 в 10:37
ForexTools:

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

Я в своем рабочем варианте сделал такое - замечательно получилось. К сожалению, выложить этот вариант я не могу, поскольку очень тесно "скрестил" его с другим своим индикатором поиска аналогичных участков истории ft.Dejavu и он перестал быть универсальным. Может вы встроите этот механизм в свой OptiRunner6 ?

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

Про вторую "фишку" я б предположил, что для двух этапов - бэк-теста (настройки) и форвард-теста - достаточно иметь два разных сплошных (с учетом предыдущего замечания) отрезка, т.е. множественность участков для меня опять не ясна. А для двух отрезков вполне хватает текущего функционала - достаточно после настройки на первом отрезке прогнать скрипт на втором отрезке. В этом скрипте так и так используется ручной (и глазной) труд ;-). Другой вопрос, что при полностью автоматической оптимизации, которую делает библиотека Optimatic, имеет смысл производить форвард тест на отрезке, отличном от того, на котором проводилась оптимизации. Библиотека на проверке у модераторов. Я там пока этой возможности не реализовал, а только написал такое TODO в комментариях.

Sergey Kravchuk
Sergey Kravchuk | 24 авг. 2009 в 12:26
marketeer:

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

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

Тестирование и оптимизация советников Тестирование и оптимизация советников
В статье описан процесс тестирования и оптимизации советников в тестере торговой платформы МТ4. Необходимость и востребованность такого рода материала назрела давно. Многие начинающие посетители форума плохо представляют себе суть и последовательность действий при работе с экспертами. Предлагаемая статья дает им возможность чуть более профессионально подойти к делу.
Файл Lite_EXPERT2.mqh - функциональный конструктор экспертописателя Файл Lite_EXPERT2.mqh - функциональный конструктор экспертописателя
Эта статья является продолжением цикла статей "Эксперты на основе популярных торговых систем и алхимия оптимизации торгового робота". В ней автор знакомит читателей с более универсальной библиотекой функций файла Lite_EXPERT2.mqh
Alert и Comment для внешних индикаторов (часть вторая) Alert и Comment для внешних индикаторов (часть вторая)
С момента публикации статьи "Алерт и коммент для внешних индикаторов" продолжают поступать письма с просьбами или вопросами как сделать внешний информер от индикаторных линий. Обобщив эту группу вопросов я решил продолжить тему. Вторым направлением в котором заинтересованы пользователи стало получение информации которая хранится в индикаторных буферах.
Графическое управление внешними параметрами индикаторов Графическое управление внешними параметрами индикаторов
Управление внешними переменными индикаторов вызывается специальным окном, в котором мы меняем параметры и вновь запускаем индикатор. Неудобство таких манипуляций очевидно, поэтому возникла потребность вынести на экран нужные параметры и сделать графическое управление индикатором.