Скачать MetaTrader 5

Контроль наклона кривой баланса во время работы торгового эксперта

13 сентября 2010, 11:59
Dmitriy Skub
46
4 873


Введение

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

При этом используются различные регулировочные характеристики в цепи обратной связи: ступенчатая, ступенчатая с гистерезисом, линейная. Это позволяет настроить систему контроля наклона кривой баланса на характеристики конкретной торговой системы.

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

Естественно, данная система не панацея и не сделает из убыточного советника прибыльного. Это своего рода надстройка над ММ советника (управлением деньгами), не позволяющая ему получить существенные убытки на счете.

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


Принцип работы

Рассмотрим подробней принцип работы системы, контролирующей наклон кривой баланса. Представим, что у нас есть некий торгующий советник. Его гипотетическая кривая баланса выглядит следующим образом:

Рисунок 1. Принцип работы системы, контролируюшей наклон кривой баланса

Рисунок 1. Принцип работы системы, контролируюшей наклон кривой баланса

Вверху - исходная кривая баланса для советника с постоянным объемом торговых операций (далее, размером лота). Красными точками отмечены закрытые сделки. Соединим эти точки между собой - получим ломаную линию,  отражающую изменение баланса советника в процессе торговли (толстая черная линия).

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

Таким образом, если торговля попала в неудачную полосу, объем уменьшается от Лмакс. до Лмин. на интервале торговли Т3...Т5. После точки Т5 торговля ведется минимальным заданным объемом - режим режекции торгового объема. После восстановления профитности работы советника и возрастания угла наклона кривой баланса свыше заданного значения, количество лотов начинает увеличиваться. Это происходит на интервале Т8...Т10. После точки Т10 объем в торговых операциях восстанавливается до исходного значения Лмакс.

Результирующая кривая баланса, получающаяся в результате такого регулирования, показана в нижней части рис.1. Видно, что исходная просадка от Б1 до Б2 уменьшилась от Б1 до Б2*. Также видно, что несколько уменьшилась и прибыль на участке восстановления максимального лота Т8...Т10 - обратная сторона медали.

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

  • ступенчатый - объем меняется скачком от максимального до минимального и обратно;
  • линейный - объем линейно меняется в зависимости от угла наклона кривой баланса в интервале регулирования;
  • ступенчатая с гистерезисом - переход от максимального объема к минимальному и обратный переход происходят при разных значениях угла наклона;

Проиллюстрируем это рисунками:

Рисунок 2. Виды регулировочной характеристики

Рисунок 2. Виды регулировочной характеристики

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

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

Также, надо отметить несколько важных моментов:

  • эффективность управления наклоном кривой баланса напрямую зависит от соотношения рабочего лота в режиме нормальной работы к рабочему лоту в режиме режекции лота. Чем соотношение больше, тем эффективней управление. Поэтому исходный рабочий лот должен быть существенно больше минимально возможного.
  • средний период чередования взлетов/падений баланса эксперта должен быть существенно больше, чем время реакции системы контроля. Иначе система просто не будет успевать регулировать наклон кривой баланса. Чем больше отношение среднего периода к времени реакции, тем эффективней работа системы. Данное требование относится практически к любой системе автоматического регулирования.

Реализация на MQL5 с использование объектно-ориентированного программирования

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


Класс TradeSymbol

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

//---------------------------------------------------------------------
//  Операции с рабочим инструментом:
//---------------------------------------------------------------------
class TradeSymbol
{
private:
  string  trade_symbol;                          // рабочий инструмент

private:
  double  min_trade_volume;                      // минимально допустимый объем для торговых операций
  double  max_trade_volume;                      // максимально допустимый объем для торговых операций
  double  min_trade_volume_step;                 // минимальное изменение объема
  double  max_total_volume;                      // максимальный совокупный объем по символу
  double  symbol_point;                          // значение одного пункта
  double  symbol_tick_size;                      // минимальное изменение цены
  int     symbol_digits;                        // число знаков после запятой

protected:

public:
  void    RefreshSymbolInfo( );                  // обновить рыночную информацию по рабочему инструменту
  void    SetTradeSymbol( string _symbol );      // установить/изменить рабочий инструмент
  string  GetTradeSymbol( );                     // получить рабочий инструмент
  double  GetMaxTotalLots( );                    // получить максимальный совокупный объем
  double  GetPoints( double _delta );            // получить изменение цены в пунктах

public:
  double  NormalizeLots( double _requied_lot );  // получить нормализованный торговый объем
  double  NormalizePrice( double _org_price );   // получить нормализованную цену с учетом шага изменения котировки

public:
  void    TradeSymbol( );                       // конструктор
  void    ~TradeSymbol( );                      // деструктор
};

Структура класса очень простая. Назначение - получение, хранение и обработка текущей рыночной информации по заданному инструменту. Основные методы - TradeSymbol::RefreshSymbolInfo, TradeSymbol::NormalizeLots, TradeSymbol::NormalizePrice. Рассмотрим их последовательно.


Метод TradeSymbol::RefreshSymbolInfo предназначен для обновления рыночной информации по рабочему инструменту.

//---------------------------------------------------------------------
//  Обновить рыночную информацию по рабочему инструменту:
//---------------------------------------------------------------------
void TradeSymbol::RefreshSymbolInfo()
{
//  Если не задан рабочий инструмент, то дальше ничего не делаем:
  if(GetTradeSymbol() == NULL)
  {
    return;
  }

//  Считаем параметры, необходимые для нормализации лота:
  min_trade_volume = SymbolInfoDouble(GetTradeSymbol(), SYMBOL_VOLUME_MIN );
  max_trade_volume = SymbolInfoDouble(GetTradeSymbol(), SYMBOL_VOLUME_MAX );
  min_trade_volume_step = SymbolInfoDouble(GetTradeSymbol(), SYMBOL_VOLUME_STEP);

  max_total_volume = SymbolInfoDouble(GetTradeSymbol(), SYMBOL_VOLUME_LIMIT);

  symbol_point = SymbolInfoDouble(GetTradeSymbol(), SYMBOL_POINT);
  symbol_tick_size = SymbolInfoDouble(GetTradeSymbol(), SYMBOL_TRADE_TICK_SIZE);
  symbol_digits = ( int )SymbolInfoInteger(GetTradeSymbol(), SYMBOL_DIGITS);
}

Один важный момент, который встречается в нескольких методах. Поскольку в текущей реализации MQL5 нельзя использовать конструктор с параметрами, то для первоначального задания рабочего инструмента обязательно надо вызвать метод:

void    SetTradeSymbol(string _symbol);      // установить/изменить рабочий инструмент


Метод TradeSymbol::NormalizeLots используется для получения правильного и нормализованного торгового объема. Известно, что размер позиции не может быть меньше минимально возможной величины, которую разрешает открывать брокер. Дискретность минимального изменения позиции также определяется брокером и может различаться. Данный метод возвращает ближайшее снизу значение лота.

Также проверяется, не выходит ли объем предполагаемой позиции за пределы максимальной величины, разрешенной брокером.

//---------------------------------------------------------------------
//  Получить нормализованный торговый объем:
//---------------------------------------------------------------------
//  - на входе требуемый лот;
//  - на выходе нормализованный лот;
//---------------------------------------------------------------------
double
TradeSymbol::NormalizeLots( double _requied_lots )
{
  double   lots, koeff;
  int      nmbr;

//  Если не задан рабочий инструмент, то дальше ничего не делаем:
  if( GetTradeSymbol( ) == NULL )
  {
    return( 0.0 );
  }

  if( this.min_trade_volume_step > 0.0 )
  {
    koeff = 1.0 / min_trade_volume_step;
    nmbr = ( int )MathLog10( koeff );
  }
  else
  {
    koeff = 1.0 / min_trade_volume;
    nmbr = 2;
  }
  lots = MathFloor( _requied_lots * koeff ) / koeff;

//  Ограничение лота снизу:
  if( lots < min_trade_volume )
  {
    lots = min_trade_volume;
  }

//  Ограничение лота сверху:
  if( lots > max_trade_volume )
  {
    lots = max_trade_volume;
  }

  lots = NormalizeDouble( lots, nmbr );
  return( lots );
}


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

//---------------------------------------------------------------------
//  Нормализация цены с учетом шага изменения котировок:
//---------------------------------------------------------------------
double
TradeSymbol::NormalizePrice( double _org_price )
{
//  Минимальный размер шага изменения котировок, пунктов:
  double  min_price_step = NormalizeDouble( symbol_tick_size / symbol_point, 0 );

  double  norm_price = NormalizeDouble( NormalizeDouble(( NormalizeDouble( _org_price / symbol_point, 0 )) / min_price_step, 0 ) * min_price_step * symbol_point, symbol_digits );
  return( norm_price );
}

На вход метода подается требуемая ненормализованная цена. Возвращается правильная нормализованная цена, наиболее близкая к требуемой.

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


Класс TBalanceHistory

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

//---------------------------------------------------------------------
//  Операции с историей баланса:
//---------------------------------------------------------------------
class TBalanceHistory
{
private:
  long      current_magic;            // значение "магического номера" при доступе к истории сделок ( 0 - любой номер )
  long      current_type;             // тип сделок ( -1 - все )
  int       current_limit_history;   // предельная глубина истории сделок ( 0 - вся история )
  datetime   monitoring_begin_date;   // дата начала мониторинга истории сделок
  int       real_trades;             // сколько реально трэйдов уже сделано

protected:
  TradeSymbol  trade_symbol;          // операции с рабочим инструментом

protected:
//  "Сырые" массивы:
  double    org_datetime_array[];         // дата/время трэйда
  double    org_result_array[];           // результат трэйда

//  Массивы с сгруппированными по времени данными:
  double    group_datetime_array[];      // дата/время трэйда
  double    group_result_array[];        // результат трэйда

  double    last_result_array[];     // массив для хранения результатов последних трэйдов ( точки по оси OY )
  double    last_datetime_array[];   // массив для хранения времен последних трэйдов ( точки по оси OX )

private:
  void      SortMasterSlaveArray(double& _m[], double& _s[]);  // синхронная сортировка двух массивов по возрастанию

public:
  void      SetTradeSymbol(string _symbol);                       // установить/изменить рабочий инструмент
  string    GetTradeSymbol();                                    // получить рабочий инструмент
  void      RefreshSymbolInfo();                                 // обновить рыночную информацию по рабочему инструменту
  void      SetMonitoringBeginDate(datetime _dt);                // задать дату начала мониторинга
  datetime  GetMonitoringBeginDate();                            // получить дату начала мониторинга
  void      SetFiltrParams(long _magic, long _type = -1, int _limit = 0);// установить параметры фильтрации сделок

public:
// Получить результаты последних трэйдов:
  int       GetTradeResultsArray(int _max_trades);

public:
  void      TBalanceHistory( );       // конструктор
  void      ~TBalanceHistory( );      // деструктор
};

Настройки фильтрации при считывании результатов последних сделок из истории задаются с помощью метода TBalanceHistory::SetFiltrParams. Он имеет следующие входные параметры:

  • _magic           - "магический номер" трейдов, которые должны считываться из истории. Если задан нулевой номер, то считываются трейды с любым "магическим номером".
  • _type             - тип сделок, которые нужно считать. Может принимать значения DEAL_TYPE_BUY (для считывания только длинных трейдов), DEAL_TYPE_SELL (для считывания только коротких трейдов) и значение -1 (для считывания и длинных и коротких трейдов).
  • _limit              - ограничивает глубину просмотра истории трейдов. Если равна нулю, то просматривается вся доступная история.

По умолчанию, после создания объекта класса TBalanceHistory, значения задаются такие: _magic = 0, _type = -1, _limit = 0.


Основной метод данного класса - TBalanceHistory::GetTradeResultsArray. Предназначен для заполнения массивов - членов класса last_result_array и last_datetime_array результатами последних трейдов. Метод имеет следующие входные параметры:

  • _max_trades - максимальное число трейдов, которое нужно считать из истории и записать в выходные массивы. Поскольку, для вычисления угла наклона нужны, по крайней мере, две точки, это значение должно быть не меньше двух. Если это значение равно нулю, то считывается вся доступная история трейдов. На практике здесь задается количество точек, которое требуется для вычисления наклона кривой баланса.
//---------------------------------------------------------------------
//  Считывает результаты последних по времени трэйдов в массивы:
//---------------------------------------------------------------------
//  - возвращает число реально считанных трэйдов, но не более заданного;
//---------------------------------------------------------------------
int
TBalanceHistory::GetTradeResultsArray(int _max_trades)
{
  int       index, limit, count;
  long      deal_type, deal_magic, deal_entry;
  datetime   deal_close_time, current_time;
  ulong     deal_ticket;                        // тикет сделки
  double    trade_result;
  string    symbol, deal_symbol;

  real_trades = 0;

//  Число трэйдов должно быть не меньше двух:
  if(_max_trades < 2)
  {
    return(0);
  }

//  Если не задан рабочий инструмент, то дальше ничего не делаем:
  symbol = trade_symbol.GetTradeSymbol();
  if(symbol == NULL)
  {
    return(0);
  }

//  Запросим историю сделок и ордеров c заданного времени по текущий момент:
  if(HistorySelect(monitoring_begin_date, TimeCurrent()) != true)
  {
    return(0);
  }

//  Считаем число трэйдов:
  count = HistoryDealsTotal();

//  Если в истории трэйдов меньше, чем нужно, то на этом все:
  if(count < _max_trades)
  {
    return(0);
  }

//  Если в истории трэйдов больше, чем нужно, то ограничим:
  if(current_limit_history > 0 && count > current_limit_history)
  {
    limit = count - current_limit_history;
  }
  else
  {
    limit = 0;
  }

//  Если нужно, подстроим размерности "сырых" массивов на заданное количество трэйдов:
  if((ArraySize(org_datetime_array)) != (count - limit))
  {
    ArrayResize(org_datetime_array, count - limit);
    ArrayResize(org_result_array, count - limit);
  }

//  Заполним "сырой" массив из базы истории трэйдов:
  real_trades = 0;
  for(index = count - 1; index >= limit; index--)
  {
    deal_ticket = HistoryDealGetTicket(index);

//  Если это не закрытие сделки, то дальше не идем:
    deal_entry = HistoryDealGetInteger(deal_ticket, DEAL_ENTRY);
    if(deal_entry != DEAL_ENTRY_OUT)
    {
      continue;
    }

//  Проверим "магический номер" сделки, если требуется:
    deal_magic = HistoryDealGetInteger(deal_ticket, DEAL_MAGIC);
    if(current_magic != 0 && deal_magic != current_magic)
    {
      continue;
    }

//  Проверим символ сделки:
    deal_symbol = HistoryDealGetString( deal_ticket, DEAL_SYMBOL );
    if( symbol != deal_symbol )
    {
      continue;
    }
                
//  Проверим тип сделки, если требуется:
    deal_type = HistoryDealGetInteger(deal_ticket, DEAL_TYPE);
    if(current_type != -1 && deal_type != current_type)
    {
      continue;
    }
    else if(current_type == -1 && (deal_type != DEAL_TYPE_BUY && deal_type != DEAL_TYPE_SELL))
    {
      continue;
    }
                
//  Проверим время закрытия сделки:
    deal_close_time = (datetime)HistoryDealGetInteger(deal_ticket, DEAL_TIME);
    if(deal_close_time < monitoring_begin_date)
    {
      continue;
    }

//  Итак, можно считывать очередной трэйд:
    org_datetime_array[real_trades] = deal_close_time / 60;
    org_result_array[real_trades] = HistoryDealGetDouble(deal_ticket, DEAL_PROFIT)/HistoryDealGetDouble(deal_ticket, DEAL_VOLUME);
    real_trades++;
  }

//  Если трэйдов меньше, чем нужно, то на этом все:
  if(real_trades < _max_trades)
  {
    return(0);
  }

  count = real_trades;

//  Отсортируем "сырой" массив по дате/времени закрытия ордера:
  SortMasterSlaveArray(org_datetime_array, org_result_array);

//  Если нужно, настроим размерности групповых массивов на заданное количество точек:
  if((ArraySize(group_datetime_array )) != count)
  {
    ArrayResize(group_datetime_array, count);
    ArrayResize(group_result_array, count);
  }
  ArrayInitialize(group_datetime_array, 0.0);
  ArrayInitialize(group_result_array, 0.0);

//  Заполним выходной массив сгруппированными данными ( группируем по одинаковости даты/времени закрытия позиции ):
  for(index = 0; index < count; index++)
  {
//  Получим очередной трэйд:
    deal_close_time = (datetime)org_datetime_array[index];
    trade_result = org_result_array[index];

//  Теперь проверим, нет ли уже такого времени в выходном массиве:
    current_time = (datetime)group_datetime_array[real_trades];
    if(current_time > 0 && MathAbs(current_time - deal_close_time) > 0.0)
    {
      real_trades++;                      // переместим указатель на следующий элемент
      group_result_array[real_trades] = trade_result;
      group_datetime_array[real_trades] = deal_close_time;
    }
    else
    {
      group_result_array[real_trades] += trade_result;
      group_datetime_array[real_trades] = deal_close_time;
    }
  }
  real_trades++;                          // теперь это число неповторяющихся элементов

//  Если трэйдов меньше, чем нужно, то на этом все:
  if(real_trades < _max_trades)
  {
    return(0);
  }

  if(ArraySize(last_result_array ) != _max_trades)
  {
    ArrayResize(last_result_array, _max_trades);
    ArrayResize(last_datetime_array, _max_trades);
  }

//  Запишем набранные данные в выходные массивы с переворотом индексации:
  for(index = 0; index < _max_trades; index++)
  {
    last_result_array[_max_trades - 1 - index] = group_result_array[index];
    last_datetime_array[_max_trades - 1 - index] = group_datetime_array[index];
  }

//  Заменим в выходном массиве результаты отдельных трэйдов на нарастающий итог:
  for(index = 1; index < _max_trades; index++)
  {
    last_result_array[index] += last_result_array[index - 1];
  }

  return( _max_trades );
}

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

Затем считывается история сделок и ордеров с заданного времени по текущее. Это делает следующий фрагмент кода:

//  Запросим историю сделок и ордеров c заданного времени по текущий момент:
  if(HistorySelect(monitoring_begin_date, TimeCurrent()) != true)
  {
    return(0);
  }

//  Считаем число трэйдов:
  count = HistoryDealsTotal();

//  Если в истории трэйдов меньше, чем нужно, то на этом все:
  if(count < _max_trades)
  {
    return(0);
  }

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

//  Заполним "сырой" массив из базы истории трэйдов:
  real_trades = 0;
  for(index = count - 1; index >= limit; index--)
  {
    deal_ticket = HistoryDealGetTicket(index);

//  Если это не закрытие сделки, то дальше не идем:
    deal_entry = HistoryDealGetInteger(deal_ticket, DEAL_ENTRY );
    if(deal_entry != DEAL_ENTRY_OUT)
    {
      continue;
    }

//  Проверим "магический номер" сделки, если требуется:
    deal_magic = HistoryDealGetInteger(deal_ticket, DEAL_MAGIC);
    if(_magic != 0 && deal_magic != _magic)
    {
      continue;
    }

//  Проверим символ сделки:
    deal_symbol = HistoryDealGetString(deal_ticket, DEAL_SYMBOL);
    if(symbol != deal_symbol)
    {
      continue;
    }
                
//  Проверим тип сделки, если требуется:
    deal_type = HistoryDealGetInteger(deal_ticket, DEAL_TYPE);
    if(_type != -1 && deal_type != _type)
    {
      continue;
    }
    else if(_type == -1 && (deal_type != DEAL_TYPE_BUY && deal_type != DEAL_TYPE_SELL))
    {
      continue;
    }
                
//  Проверим время закрытия сделки:
    deal_close_time = (datetime)HistoryDealGetInteger(deal_ticket, DEAL_TIME);
    if(deal_close_time < monitoring_begin_date)
    {
      continue;
    }

//  Итак, можно считывать очередной трэйд:
    org_datetime_array[ real_trades ] = deal_close_time / 60;
    org_result_array[ real_trades ] = HistoryDealGetDouble( deal_ticket, DEAL_PROFIT ) / HistoryDealGetDouble( deal_ticket, DEAL_VOLUME );
    real_trades++;
  }

//  Если трэйдов меньше, чем нужно, то на этом все:
  if( real_trades < _max_trades )
  {
    return( 0 );
  }

Вначале считывается тикет сделки в истории с помощью функции HistoryDealGetTicket и дальнейшие считывания информации по сделки используют полученный тикет. Поскольку нас интересуют только закрытые сделки (мы будем анализировать баланс), то сначала проверяется тип сделки. Это делается вызовом функции HistoryDealGetInteger с параметром DEAL_ENTRY. Если функция возвращает значение DEAL_ENTRY_OUT, то это закрытие сделки.

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

//  Проверим время закрытия сделки:
    deal_close_time = ( datetime )HistoryDealGetInteger( deal_ticket, DEAL_TIME );
    if( deal_close_time < monitoring_begin_date )
    {
      continue;
    }

Считанные дата/время сделки сравнивается с заданными датой/временем начала мониторинга истории. Если дата/время сделки больше заданной, то переходим к считыванию нашего трейда в массив - считываем результат сделки в пунктах и время сделки в минутах (в данном случае, время закрытия). После чего наращивается счетчик считанных сделок real_trades и цикл продолжается дальше.

После того как "сырые" массивы заполнены необходимым числом данных, надо отсортировать массив, в котором хранятся времена закрытия сделок. При этом должно сохраниться соответствие времени закрытия в массиве org_datetime_array и результата сделки в массиве org_result_array. Это делается специально написанным методом:

TBalanceHistory::SortMasterSlaveArray( double& _master[ ], double& _slave[ ] ). Первый параметр _master - массив, который собственно сортируется по возрастанию. Второй параметр _slave - массив, элементы которого должны перемещаться синхронно с элементами первого. Сортировка проводится методом "пузырька".

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

//  Заполним выходной массив сгруппированными данными ( группируем по одинаковости даты/времени закрытия позиции ):
  real_trades = 0;
  for(index = 0; index < count; index++)
  {
//  Получим очередной трэйд:
    deal_close_time = (datetime)org_datetime_array[index];
    trade_result = org_result_array[index];

//  Теперь проверим, нет ли уже такого времени в выходном массиве:
    current_time = (datetime)group_datetime_array[real_trades];
    if(current_time > 0 && MathAbs(current_time - deal_close_time) > 0.0)
    {
      real_trades++;                      // переместим указатель на следующий элемент
      group_result_array[real_trades] = trade_result;
      group_datetime_array[real_trades] = deal_close_time;
    }
    else
    {
      group_result_array[real_trades] += trade_result;
      group_datetime_array[real_trades] = deal_close_time;
    }
  }
  real_trades++;                          // теперь это число неповторяющихся элементов

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

Рисунок 3. Группирование сделок с одинаковым временем

Рисунок 3. Группирование сделок с одинаковым временем

Все сделки в пределах минуты (левая часть рисунка) группируются в одну с округлением времени и суммированием результатов (правая часть рисунка). Это позволяет сгладить "дребезг" по времени закрытия сделок и улучшить стабильность регулирования.

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

//  Запишем набранные данные в выходные массивы с переворотом индексации:
  for(index = 0; index < _max_trades; index++)
  {
    last_result_array[_max_trades - 1 - index] = group_result_array[index];
    last_datetime_array[_max_trades - 1 - index] = group_datetime_array[index];
  }

//  Заменим в выходном массиве результаты отдельных трэйдов на нарастающий итог:
  for(index = 1; index < _max_trades; index++ )
  {
    last_result_array[index] += last_result_array[index - 1];
  }


Класс TBalanceSlope

Данный класс предназначен для проведения операций с кривой баланса счета. Он порожден от класса TBalanceHistory и наследует все его защищенные и открытые данные и методы. Рассмотрим его структуру подробней:

//---------------------------------------------------------------------
//  Операции с кривой баланса:
//---------------------------------------------------------------------
class TBalanceSlope : public TBalanceHistory
{
private:
  double    current_slope;               // текущий угол наклона кривой баланса
  int       slope_count_points;          // число точек ( трэйдов ) для расчета угла наклона
        
private:
  double    LR_koeff_A, LR_koeff_B;      // коэффициенты для уравнения прямой линейной регрессии
  double    LR_points_array[];           // массив точек прямой линейной регрессии

private:
  void      CalcLR( double& X[ ], double& Y[ ] );  // вычислить уравнение прямой линейной регрессии

public:
  void      SetSlopePoints(int _number);          // задать число точек для вычисления угла наклона
  double    CalcSlope();                          // вычислить угол наклона

public:
  void      TBalanceSlope();                     // конструктор
  void      ~TBalanceSlope();                    // деструктор
};


Мы будем определять угол наклона кривой баланса по углу наклона линии линейной регрессии, построенной для заданного числа последних точек (трейдов) на кривой баланса счета. Таким образом, сначала нужно вычислить уравнение линии регрессии вида A*x + B. Это делает следующий метод:

//---------------------------------------------------------------------
//  Вычислить уравнение прямой линейной регрессии:
//---------------------------------------------------------------------
//  входные параметры:
//    X[ ] - массив значений числового ряда по оси ОX;
//    Y[ ] - массив значений числового ряда по оси ОY;
//---------------------------------------------------------------------
void TBalanceSlope::CalcLR(double& X[], double& Y[])
{
  double    mo_X = 0, mo_Y = 0, var_0 = 0, var_1 = 0;
  int       i;
  int       size = ArraySize(X);
  double    nmb = (double)size;

//  Если число точек меньше двух, то прямую вычислить невозможно:
  if(size < 2)
  {
    return;
  }

  for(i = 0; i < size; i++)
  {
    mo_X += X[i];
    mo_Y += Y[i];
  }
  mo_X /= nmb;
  mo_Y /= nmb;

  for( i = 0; i < size; i++)
  {
    var_0 += (X[i] - mo_X) * (Y[i] - mo_Y);
    var_1 += (X[i] - mo_X) * (X[i] - mo_X);
  }

//  Значение коэффициента A:
  if( var_1 != 0.0 )
  {
    LR_koeff_A = var_0/var_1;
  }
  else
  {
    LR_koeff_A = 0.0;
  }

//  Значение коэффициента B:
  LR_koeff_B = mo_Y - LR_koeff_A * mo_X;

//  Заполним массив точек, лежащих на прямой регрессии:
  ArrayResize(LR_points_array, size);
  for(i = 0; i < size; i++)
  {
    LR_points_array[i] = LR_koeff_A * X[i] + LR_koeff_B;
  }
}

Здесь используется метод наименьших квадратов для вычисления минимальной погрешности расположения линии регрессии относительно исходных данных. Также, заполняется массив, хранящий значения координат Y, лежащих на вычисленной прямой. Данный массив пока не используется и рассчитан на дальнейшее развитие


Основной метод, который используется в данном классе - TBalanceSlope::CalcSlope. Он возвращает угол наклона кривой баланса, вычисленный по заданному числу последних трейдов. Вот его реализация:

//---------------------------------------------------------------------
//  Вычислить угол наклона:
//---------------------------------------------------------------------
double TBalanceSlope::CalcSlope()
{
//  Получим результаты торговли из базы истории трейдов:
  int nmb = GetTradeResultsArray(slope_count_points);
  if(nmb < slope_count_points)
  {
    return( 0.0 );
  }

//  Вычислим линию регрессии по результатам последних трейдов:
  CalcLR(last_datetime_array, last_result_array);
  current_slope = LR_koeff_A;

  return(current_slope);
}

Сначала считываются заданное число последних точек кривой баланса. Это делается вызовом метода базового класса TBalanceSlope::GetTradeResultsArray. Если считано точек не меньше заданного числа, то вычисляется прямая регрессии. Это делается вызовом метода TBalanceSlope::CalcLR. В качестве аргументов используются заполненные на предыдущем шаге массивы last_result_array и last_datetime_array, принадлежащие базовому классу.

Остальные методы достаточно просты и не требуют пояснений.


Класс TBalanceSlopeControl

Это основной класс, который, собственно, и управляет наклоном кривой баланса посредством изменения рабочего лота. Он порожден от класса TBalanceSlope и наследует все его открытые и защищенные методы и данные. Единственная задача данного класса: вычисление текущего рабочего лота в зависимости от текущего угла наклона кривой баланса. Рассмотрим его подробней:

//---------------------------------------------------------------------
//  Управление наклоном кривой баланса:
//---------------------------------------------------------------------
enum LotsState
{
  LOTS_NORMAL = 1,            // режим торговли нормальным лотом
  LOTS_REJECTED = -1,         // режим торговли пониженным лотом
  LOTS_INTERMEDIATE = 0,      // режим торговли промежуточным лотом
};
//---------------------------------------------------------------------
class TBalanceSlopeControl : public TBalanceSlope
{
private:
  double    min_slope;          // угол наклона, соответствующий режиму режекции лота
  double    max_slope;          // угол наклона, соответствующий режиму нормального лота
  double    centr_slope;        // угол наклона, соответствующий переключению лота без гистерезиса

private:
  ControlType  control_type;    // тип регулировочной функции

private:
  double    rejected_lots;      // размер лота в режиме режекции
  double    normal_lots;        // размер лота в нормальном режиме
  double    intermed_lots;      // размер лота в промежуточном режиме

private:
  LotsState current_lots_state; // текущий режим лота

public:
  void      SetControlType( ControlType _control );  // задать тип регулировочной характеристики
  void      SetControlParams( double _min_slope, double _max_slope, double _centr_slope );

public:
  double    CalcTradeLots( double _min_lots, double _max_lots );  // получить торговый объем

protected:
  double    CalcIntermediateLots( double _min_lots, double _max_lots, double _slope );

public:
  void      TBalanceSlopeControl( );   // конструктор
  void      ~TBalanceSlopeControl( );  // деструктор
};


Прежде чем вычислять размер текущего лота, необходимо задать начальные параметры. Это делается вызовом следующих методов:

  void      SetControlType( ControlType _control );  // задать тип регулировочной характеристики

Входной параметр _control - это тип регулировочной характеристики. Может принимать значения:

  • STEP_WITH_HYSTERESISH      - регулировочная характеристика ступенчатая с гистерезисом;
  • STEP_WITHOUT_HYSTERESIS  - регулировочная характеристика ступенчатая без гистерезиса;
  • LINEAR                                   - регулировочная характеристика линейная;
  • NON_LINEAR                           - регулировочная характеристика не линейная (в данной версии не реализовано);


  void      SetControlParams( double _min_slope, double _max_slope, double _centr_slope );

Входные параметры следующие:

  • _min_slope - угол наклона кривой баланса, соответствующий режиму торговли минимальным лотом;
  • _max_slope - угол наклона кривой баланса, соответствующий режиму торговли максимальным лотом;
  • _centr_slope - угол наклона кривой баланса, использующийся при задании ступенчатой регулировочной характеристики без гистерезиса;


Размер лота вычисляется вызовом следующего метода:

//---------------------------------------------------------------------
//  Получить торговый объем:
//---------------------------------------------------------------------
double TBalanceSlopeControl::CalcTradeLots(double _min_lots, double _max_lots)
{
//  Попробуем вычислить наклон кривой баланса:
  double    current_slope = CalcSlope( );

//  Если еще не набрано заданное число трэйдов, то торгуем минимальным лотом:
  if(GetRealTrades() < GetSlopePoints())
  {
    current_lots_state = LOTS_REJECTED;
    rejected_lots = trade_symbol.NormalizeLots(_min_lots);
    return(rejected_lots);
  }

//  Если функция регулирования ступенчатая без гистерезиса:
  if(control_type == STEP_WITHOUT_HYSTERESIS)
  {
    if(current_slope < centr_slope)
    {
      current_lots_state = LOTS_REJECTED;
      rejected_lots = trade_symbol.NormalizeLots(_min_lots);
      return(rejected_lots);
    }
    else
    {
      current_lots_state = LOTS_NORMAL;
      normal_lots = trade_symbol.NormalizeLots(_max_lots);
      return(normal_lots);
    }
  }

//  Если наклон ЛР для кривой баланса меньше заданного допустимого:
  if(current_slope < min_slope)
  {
    current_lots_state = LOTS_REJECTED;
    rejected_lots = trade_symbol.NormalizeLots(_min_lots);
    return(rejected_lots);
  }

//  Если наклон ЛР для кривой баланса больше заданного:
  if(current_slope > max_slope)
  {
    current_lots_state = LOTS_NORMAL;
    normal_lots = trade_symbol.NormalizeLots(_max_lots);
    return(normal_lots);
  }

//  Наклон ЛР для кривой баланса находится внутри заданных границ ( промежуточное состояние ):
  current_lots_state = LOTS_INTERMEDIATE;

//  Вычислим значение промежуточного лота:
  intermed_lots = CalcIntermediateLots(_min_lots, _max_lots, current_slope);
  intermed_lots = trade_symbol.NormalizeLots(intermed_lots);

  return(intermed_lots);
}

Основные существенные моменты реализации метода TBalanceSlopeControl::CalcTradeLots следующие:

  • Пока не набрано заданное минимальное число трейдов, торговый лот будет минимальным. Это логично, поскольку после постановки эксперта на торговлю, заранее не известно в каком периоде находится торговая система (профитном или убыточном).
  • Если задана регулировочная функция - ступенчатая без гистерезиса, то для задания угла переключения режима торговли методом TBalanceSlopeControl::SetControlParams используется только параметр _centr_slope. Параметры _min_slope и _max_slope игнорируются. Это сделано, чтобы можно было корректно проводить оптимизацию в тестере MT5 по этому параметру.

В зависимости от вычисленного угла наклона, торговля идет либо минимальным лотом, либо максимальным, либо - промежуточным. Промежуточный лот вычисляется простым методом TBalanceSlopeControl::CalcIntermediateLots. Данный метод является защищенным и используется внутри класса. Его код приведен ниже:

//---------------------------------------------------------------------
//  Вычисление промежуточного лота:
//---------------------------------------------------------------------
double TBalanceSlopeControl::CalcIntermediateLots(double _min_lots, double _max_lots, double _slope)
{
  double    lots;

//  Если функция регулирования ступенчатая с гистерезисом:
  if(control_type == STEP_WITH_HYSTERESISH)
  {
    if(current_lots_state == LOTS_REJECTED && _slope > min_slope && _slope < max_slope)
    {
      lots = _min_lots;
    }
    else if(current_lots_state == LOTS_NORMAL && _slope > min_slope && _slope < max_slope)
    {
      lots = _max_lots;
    }
  }
//  Если функция регулирования линейная:
  else if(control_type == LINEAR)
  {
    double  a = (_max_lots - _min_lots)/(max_slope - min_slope);
    double  b = normal_lots - a * .max_slope;
    lots = a * _slope + b;
  }
//  Если функция регулирования не линейная ( пока не реализованная ):
  else if(control_type == NON_LINEAR)
  {
    lots = _min_lots;
  }
//  Если функция регулирования не известная:
  else
  {
    lots = _min_lots;
  }

  return(lots);
}

Остальные методы данного класса пояснений не требуют.


Пример встраивания системы в эксперт

Рассмотрим пошагово процесс внедрения системы контроля наклона кривой баланса в эксперт.


Шаг 1 - добавление в советник директивы для подключения разработанной библиотеки:

#include  <BalanceSlopeControl.mqh>


Шаг 2 - добавление в советник внешних переменных для задания параметров системы контроля наклона кривой баланса:

//---------------------------------------------------------------------
//  Параметры системы контроля наклона кривой баланса;
//---------------------------------------------------------------------
enum SetLogic 
{
  No = 0,
  Yes = 1,
};
//---------------------------------------------------------------------
input SetLogic     UseAutoBalanceControl = No;
//---------------------------------------------------------------------
input ControlType  BalanceControlType = STEP_WITHOUT_HYSTERESIS;
//---------------------------------------------------------------------
//  Число последних трэйдов для вычисления ЛР кривой баланса:
input int          TradesNumberToCalcLR = 3;
//---------------------------------------------------------------------
//  Наклон ЛР для снижения торгового лота до минимума:
input double       LRKoeffForRejectLots = -0.030;
//---------------------------------------------------------------------
//  Наклон ЛР для восстановления нормального режима торговли:
input double       LRKoeffForRestoreLots = 0.050;
//---------------------------------------------------------------------
//  Наклон ЛР для работы в промежуточном режиме:
input double       LRKoeffForIntermedLots = -0.020;
//---------------------------------------------------------------------
//  Снижать входной лот до заданной величины при наклоне ЛР вниз
input double       RejectedLots = 0.10;
//---------------------------------------------------------------------
//  Нормальный рабочий лот в режиме ММ с фиксированным лотом:
input double       NormalLots = 1.0;


Шаг 3 - добавление в советник объекта типа TBalanceSlopeControl:

TBalanceSlopeControl  BalanceControl;

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


Шаг 4 - добавление в функцию советника OnInit кода для инициализации системы контроля наклона кривой баланса:

//  Настроим систему контроля наклона кривой баланса:
  BalanceControl.SetTradeSymbol(Symbol());
  BalanceControl.SetControlType(BalanceControlType);
  BalanceControl.SetControlParams(LRKoeffForRejectLots, LRKoeffForRestoreLots, LRKoeffForIntermedLots);
  BalanceControl.SetSlopePoints(TradesNumberToCalcLR);
  BalanceControl.SetFiltrParams(0, -1, 0);
  BalanceControl.SetMonitoringBeginDate(0);


Шаг 5 - добавление в функцию OnTick советника вызова метода для обновления текущей информации по рынку:

//  Обновим рыночную информацию:
  BalanceControl.RefreshSymbolInfo();

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


Шаг 6 - добавление в советник, в место перед открытием позиции, кода для вычисления текущего лота:

  if(UseAutoBalanceControl == Yes)
  {
    current_lots = BalanceControl.CalcTradeLots(RejectedLots, NormalLots);
  }
  else
  {
    current_lots = NormalLots;
  }

Если в советнике используется автоматический мани менеджмент, то вместо NormalLots надо передать в метод TBalanceSlopeControl::CalcTradeLots текущий размер лота, вычисленный системой ММ советника.

Тестовый советник BSCS-TestExpert.mq5, в который встроена описанная система, приложен к данной статье. Принцип его работы основан на пересечении уровней индикатора CCI. Данный эксперт разработан в чисто тестовых целях и не пригоден к работе на реальных счетах. Тестировать будем на периоде H4 (2008.07.01 - 2010.09.01) на инcтрументе EURUSD.

Рассмотрим результаты работы данного эксперта. Ниже приведен график изменения баланса при отключенной системе контроля наклона. Для этого установим внешний параметр UseAutoBalanceControl в значение No.

Рисунок 4. Исходный график изменения баланса

Рисунок 4. Исходный график изменения баланса


Теперь установим внешний параметр UseAutoBalanceControl в значение Yes и протестируем эксперт. Получим график при включенной системе контроля наклона баланса.

Рисунок 5. График изменения баланса при включенной системе контроля

Рисунок 5. График изменения баланса при включенной системе контроля

Можно заметить визуально, что большинство периодов просадки на верхнем графике (рис.4) как бы срезаны и имеют почти плоский вид на нижнем графике (рис.5). Это результат действия нашей системы. Можно  сравнить основные показатели работы эксперта:

 Параметр
 UseAutoBalanceControl = No  UseAutoBalanceControl = Yes
 Чистая прибыль: 18 378.0017 261.73
 Прибыльность: 1.471.81
 Фактор восстановления: 2.66 3.74
 Матожидание выигрыша:  117.81110.65
 Абсолютная просадка по балансу:1 310.50131.05
 Абсолютная просадка по средствам: 1 390.50514.85
 Максимальная просадка по балансу: 5 569.50 (5.04%) 3 762.15 (3.35%)
 Максимальная просадка по средствам:6 899.50 (6.19%)
4 609.60 (4.08%)


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


Заключение

Просматриваются некоторые пути улучшения данной системы:
  • Использование виртуального ведения сделок при входе советника в неблагоприятный период работы. Тогда размер нормального рабочего лота уже не будет иметь значения. Это позволит еще уменьшить просадку.
  • Использование более сложных алгоритмов для определения текущего состояния работы эксперта (профитное или убыточное). Например, можно попробовать применить нейросеть для такого анализа. Здесь, конечно, нужны дополнительные исследования.

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

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


Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (46)
Иван
Иван | 28 сен 2012 в 23:37

УРА!!! Я победил проблему! Проблема заключалась в некорректной передаче массива как элемента объекта по ссылке в функцию сортировки.
Если сначала скопировать массив объекта в обычный массив и потом передавать этот временный массив в функцию сортировки, то никаких неожиданных глюков не происходит. Данные в массивах в небеса не улетают и все переключения лота совпадают. Также практически полностью совпадает итоговый баланс на совместном прогоне валютных пар (отличия микроскопичны).
Ниже приведены результаты тестов, а также код, который я использую для ликвидации глюка. (Обратите внимание, что текущие данные отличаются даже при одиночных прогонах на первой паре от данных, которые были получены до исправления кода -

solandr 2012.09.26 23:16 2012.09.26 23:16:04 )

 

С включенным контролем лота
34 0 0 6487.33 первая пара
0 36 0 5556,60 вторая пара
0 0 168 4374.44 третья пара
34 36 168 16418.47 все три пары
сумма баланса по трём парам 16418,37 (Разница с совместным прогоном 0.1)

С выключенным контролем лота
0 0 0 6702.44 первая пара
0 0 0 5742.89 вторая пара
0 0 0 4358.22 третья пара
0 0 0 16804.53 все три пары
сумма баланса по трём парам 16803.55 (Разница с совместным прогоном 0.98)

double temp_array1[],temp_array2[];
ArrayResize(temp_array1,count);
ArrayResize(temp_array2,count);

        for( index = 0; index < count; index++ )
        {
           temp_array1[index]=this.org_datetime_array[index];
           temp_array2[index]=this.org_result_array[index];
        }



//      Отсортируем "сырой" массив по дате/времени закрытия ордера:
        //SortMasterSlaveArray( this.org_datetime_array, this.org_result_array ); - не всегда корректно работает этот код
        SortMasterSlaveArray(temp_array1,temp_array2);

        for( index = 0; index < count; index++ )
        {
           this.org_datetime_array[index]=temp_array1[index];
           this.org_result_array[index]=temp_array2[index];
        }       

 Поменяйте, пожалуйста, инклюдник в статье, чтобы другие люди по нескольку дней не ломали себе голову расходящимися тестами. Спасибо!

Dmitriy Skub
Dmitriy Skub | 29 сен 2012 в 21:30

ИМХО, здесь надо не инклудник менять, а в сервис-деск писать.

Так не должно быть. А лишнее копирование ни к чему, со всех точек зрения. А вообще, Вы молодец!

Плюсаните себе рейтинга, через сервис-деск))



Иван
Иван | 2 окт 2012 в 08:06

Сообщение для команды MQ:

Уважаемые разработчики MT5, хотелось бы обратить Ваше внимание на некоторую неожиданную проблему, обнаруженную при тестировании на MT5 Build 695 (6 Sep 2012, Терминал Чемпионата-2012, Счёт: 1101505, Сервер: MetaQuotes-Demo), работающем под операционной системой Windows 7 Enterprise (лицензионной, английской). Проблема заключается в необъяснимом искажении данных (массива как элемента объекта), передаваемых по ссылке в функцию сортировки.

В приложении находятся исходники иклюдников ORIGINAL (с ошибкой) и CORRECTED (без ошибки), а также логфайлы работы эксперта, демонстрирующие работу обоих вариантов кода. Ошибка с искажением данных стабильно воспроизводится при одних и тех же заданных условиях тестирования. Обратите, пожалуйста, внимание на логи за 2012.02.24 08:03:40 (данные массивов перепутаны местами) и за 2012.05.31 14:41:59 (даные "улетели в небеса").

Спасибо!

 

Ilyas
Ilyas | 2 окт 2012 в 11:46
solandr:

Сообщение для команды MQ:

Уважаемые разработчики MT5, хотелось бы обратить Ваше внимание на некоторую неожиданную проблему, обнаруженную при тестировании на MT5 Build 695 (6 Sep 2012, Терминал Чемпионата-2012, Счёт: 1101505, Сервер: MetaQuotes-Demo), работающем под операционной системой Windows 7 Enterprise (лицензионной, английской). Проблема заключается в необъяснимом искажении данных (массива как элемента объекта), передаваемых по ссылке в функцию сортировки.

В приложении находятся исходники иклюдников ORIGINAL (с ошибкой) и CORRECTED (без ошибки), а также логфайлы работы эксперта, демонстрирующие работу обоих вариантов кода. Ошибка с искажением данных стабильно воспроизводится при одних и тех же заданных условиях тестирования. Обратите, пожалуйста, внимание на логи за 2012.02.24 08:03:40 (данные массивов перепутаны местами) и за 2012.05.31 14:41:59 (даные "улетели в небеса").

Спасибо!

 

Заключение.

Ошибка на стороне пользователя в функции GetTradeResultsArray.

Подготавливается динамический массив на X данных, а заполняется на N (при этом N<X), например при наличии сделки с "чужим" magic.

Перед сортировкой выводятся именно N данных, а в сортировке участвует X, естественно что данные X-N это случайные числа в памяти.
В зависимости от значения, при сортировке они "поднимаются" наверx и выводятся после сортировки в лог.

Решение:
  1) "Подрезать" массив после заполнения до N
  2) Передавать в функцию сортировки N
  3) Инициализировать массив X заведомо большими/малыми данными, которые после сортировки останутся "за бортом".
Иван
Иван | 2 окт 2012 в 12:54
mql5:
Заключение.

Спасибо за очень оперативный ответ! Я думаю автор сделает необходимую коррекцию и разместит в статье обновлённый файл mqh.
Интервью с Александром Топчило (ATC 2010) Интервью с Александром Топчило (ATC 2010)

Александр Топчило (Better) - победитель Чемпионата Automated Trading Championship 2007. Коньком Александра являются нейронные сети - именно нейроэксперт со значительным отрывом опередил конкурентов в Чемпионате 2007 года. Интересный собеседник и успешный разработчик экспертов рассказывает в этом интервью о своей жизни после Чемпионатов, собственном бизнесе и новых алгоритмах для создания торговых систем.

Адаптивные торговые системы и их использование в терминале MetaTrader 5 Адаптивные торговые системы и их использование в терминале MetaTrader 5

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

Simulink: в помощь разработчику эксперта Simulink: в помощь разработчику эксперта

Я не являюсь профессиональным программистом. И поэтому принцип «от простого к сложному» имеет для меня первостепенное значение, когда я встречаюсь с таким понятием как МТС, а точнее создание МТС. Что есть для меня простое? Прежде всего это визуализация самого процесса создания системы и логики её функционирования. А также минимум рукописного кода. В данной статье я попробую создать и протестировать МТС на основе матлабовского пакета, а затем напишу эксперт для MetaTrader 5. Причём для тестирования будут использованы исторические данные из МetaTrader 5.

Оценка торговых систем - эффективности входа, выхода и сделок Оценка торговых систем - эффективности входа, выхода и сделок

Существует масса критериев, которые позволяют оценить эффективность или прибыльность торговой стратегии. Но трейдеры всегда готовы подвергнуть любую систему новому краштесту. В статье рассказывается, как можно применить статистику для платформы MetaTrader 5 на основе измерения эффективности. Представлен класс перевода учёта статистики сделок в вид, не противоречащий описанному в книге "Статистика для трейдера" Булашева С.В. Приведён пример пользовательской функции оптимизации.