English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Оптимальный метод подсчета объема совокупной позиции по заданному магическому номеру

Оптимальный метод подсчета объема совокупной позиции по заданному магическому номеру

MetaTrader 5Торговые системы | 22 июля 2010, 11:33
5 207 25
Dmitry Fedoseev
Dmitry Fedoseev

Введение

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

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

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

1. Общий принцип подсчета объема позиции

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

Сам принцип подсчета совокупной позиции достаточно прост: например, если исполнить сделку buy с объемом 0.1, затем, еще buy 0.1, затем sell 0.1, то объем совокупной позиции будет равен 0.1+0.1-0.1=+0.1. Объемы сделок buy прибавляем, объемы сделок sell вычитаем из общей суммы и получаем объем совокупной позиции.

Важно начать этот подсчет с момента времени, когда объем совокупной позиции равен нулю. Первым, и самым очевидным таким моментом, является момент открытия счета. Т.е., можно загрузить всю историю сделок счета функцией HistorySelect() со значением первого параметра 0 (наименьшее возможное время) и значением второго параметра TimeCurrent() (последнее известное время сервера):

HistorySelect(0,TimeCurrent()); // загрузка всей истории

Затем, пройтись по всей истории от начала до конца, прибавляя (для buy) и вычитая (для sell) объемы каждой сделки с заданным магическим номером. Это тоже вариант, однако практически история может быть достаточно длинной. Это может ощутимо сказаться на быстродействии работы эксперта, особенно, при его тестировании и оптимизации, вплоть до нереальности практического применения такого эксперта. Нужно найти в истории самый последний момент, когда совокупная позиция была равно нулю.

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

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

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

2. Применение глобальных переменных

В MQL5 есть функция MQLInfoString(), позволяющая получать различную информацию о работающей mql5-программе.

Функция позволяет программе узнать свое имя файла, для этого нужно вызывать функцию с идентификатором MQL5_PROGRAM_NAME:

MQLInfoString(MQL5_PROGRAM_NAME); // Имя эксперта

Значит, имена всех глобальных переменных начинаем с имени эксперта. Один эксперт может работать на разных символах, значит к имени глобальной переменной добавляем название символа (_Symbol), также, не исключена работа одного и того же советника на одном и том же символе (на разных таймфреймах или с разными настройками, для обеспечения такой возможности используется магический номер), значит к имени глобальной переменной добавляем магический номер.

Например, в окне свойств советника имеется переменная для определения магического номера с именем Magic_N - добавляем ее значение к имени глобальной переменной.

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

gvp=MQLInfoString(MQL_PROGRAM_NAME)+"_"+_Symbol+"_"+IntegerToString(Magic_N)+"_"; // Имя эксперта, символ на котором он 
                                                                            //работает и магический номер эксперта

где переменная gvp (сокращение от Global Variable Prefix) - строковая переменная, объявленная в общем разделе переменных эксперта.

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

Здесь у нас несколько другая ситуация - глобальными переменными так и назовем глобальные переменные терминала (особые переменные, хранящиеся в файле, с которыми работаем при помощи функций GlobalVariable...()). А вот те переменные, которые обычно в программировании называются глобальными (объявленные в общем разделе программного модуля и видимые для всех функций) назовем общими. Локальные переменные так и останутся локальными.

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

При помощи функции AccountInfoInteger(), вызывая ее с идентификатором ACCOUNT_TRADE_MODE, можно узнать, что эксперт работает в тестере, на демосчете или реальном счете.

Добавим к именам глобальных переменных небольшие отличительные знаки: "d" - при работе на демосчете, "r" - при работе на реальном счете, "t" - при работе в тестере:

gvp=MQLInfoString(MQL_PROGRAM_NAME)+"_"+_Symbol+"_"+IntegerToString(Magic_N)+"_"; // Имя эксперта, символ на котором он 
                                                                            // работает и магический номер эксперта
if(AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_DEMO))
  {
   gvp=gvp+"d_"; // Демосчет
  }
if(AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_REAL)
  {
   gvp=gvp+"r_"; // Реальный
  }
if(MQLInfoInteger(MQL_TESTING))
  {
   gvp=gvp+"t_"; // Тестирование
  } 

Функция должна вызываться из функции OnInit() эксперта.

Как уже упоминалось, при тестировании глобальные переменные должны удаляться, т.е. в функцию OnDeinit() эксперта должна быть добавлена функция удаления глобальных переменных:

void fDeleteGV()
  {
   if(MQLInfoInteger(MQL_TESTER)) // Тестирование
     {
      for(int i=GlobalVariablesTotal()-1;i>=0;i--) // Просмотр всех глобальных переменных (обязательно с конца а не с начала)
        {
         if(StringFind(GlobalVariableName(i),gvp,0)==0) // Начало имени переменной соответсвуют заданному префиксу
           {
            GlobalVariableDel(GlobalVariableName(i)); // Удаление переменной
           }
        }
     }
  }

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

Получаем следующий вид функций OnInit() и OnDeinit():

int OnInit()
  {
   fCreateGVP(); // Формирование префикса имен глобальных переменных
   fDeleteGV(); // Удаление глобальных переменных в случае работы в
   return(0);
  }

void OnDeinit(const int reason)
  {
   fDeleteGV(); // Удаление глобальных переменных в случае работы в тестере
  }

Заодно можно немного облегчить себе жизнь при использовании глобальных переменных, вместо того, чтобы каждый раз выписывать что-то типа GlobalVariableSet(gvp+...), можно создать функции с короткими именами, в которых будет выполняться создание полных имен глобальных переменных.

Функция для установки значения глобальной переменной:

void fGVS(string aName,double aValue)
  {
   GlobalVariableSet(gvp+aName,aValue);
  }

Функция для получения значения глобальной переменной:

double fGVG(string aName)
  {
   return(GlobalVariableGet(gvp+aName));
  }

Функция для удаления глобальной переменной:

void fGVD(string aName)
  {
   GlobalVariableDel(gvp+aName);
  }

В основном с глобальными переменными разобрались, но это еще не все.

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

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

Окончательно функция подготовки префиксов глобальных переменных принимает следующую форму:

void fCreateGVP()
  {
   gvp=MQLInfoString(MQL_PROGRAM_NAME)+"_"+_Symbol+"_"+IntegerToString(Magic_N)+"_";
   Commom_gvp="COMMOM_"; // Префикс общих для всех экспертов переменных
   if(AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_DEMO)
     {
      gvp=gvp+"d_";
     }
   if(AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_REAL)
     {
      gvp=gvp+"r_";
     }
   if(MQL5InfoInteger(MQL_TESTER))
     {
      gvp=gvp+"t_";
      Commom_gvp=gvp; // В тестере, что бы обеспечить удалене общих глобальных переменных после тестирования
     }
  }

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

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

Еще очень важное замечание! Длина имен глобальных переменных ограничена: максимальная длина - 63 знака. В связи с этим не следует давать экспертам очень длинные имена.

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

3. Подсчета объема позиции

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

Если существует - загрузим историю от указанного в переменной времени, если нет - загрузим всю историю:

if(GlobalVariableCheck(Commom_gvp+sSymbol+"_HistStTm")) // Ранее зафиксирован момент существования "нулевой позиции"
  {
   pLoadHistoryFrom=(datetime)GlobalVariableGet(Commom_gvp+pSymbol+"_HistStTm"); // Установка начальной даты 
                                                                             // необходимой части истории
  }
else
 {
   GlobalVariableSet(Commom_gvp+sSymbol+"_HistStTm",0);
 }
if(!HistorySelect(sLoadHistoryFrom,TimeCurrent())) // Загрузка необходимой части истории
  { 
   return(false);
  } 

Далее определим общий объем совокупной позиции по символу:

double CurrentVolume=fSymbolLots(pSymbol);

Определение объема позиции сделано отдельной функцией fSymbolLots().

Возможны несколько способов получения объема позиции, например, функция PositionSelect(). Если функция возвращает значение false, значит позиции не существует (ее объем равен нулю). Если функция вернула true, объем можно узнать функцией PositionGetDouble() с идентификатором POSITION_VOLUME. Направление позиции (buy или sell), определяется при помощи функции PositionGetInteger() с идентификатором POSITION_TYPE. Если позиция buy, функция возвращает положительное значение, если sell - отрицательное.

Полностью готовая функция будет иметь следующий вид:

double fSymbolLots(string aSymbol)
  {
   if(PositionSelect(aSymbol,1000)) // Позицию удалось выделить, значит она существует
     {
      switch(PositionGetInteger(POSITION_TYPE)) // В зависимости от типа позиции она возвращает положительное или
                                           // отрицательное значение объема
        {
         case POSITION_TYPE_BUY:
            return(NormalizeDouble(PositionGetDouble(POSITION_VOLUME),2));
            break;
         case POSITION_TYPE_SELL:
            return(NormalizeDouble(-PositionGetDouble(POSITION_VOLUME),2));
            break;
        }
     }
   else
     {
      return(0);
     }
  }

Возможен другой способ определения общего объема позиции на символе - пройтись в цикле по всем позициям, количество позиций определяется функцией PositionsTotal(). Проверя при помощи функции PositionGetSymbol(), найти нужный символ и определить объем и направление позиции (PositionGetDouble() с идентификатором POSITION_VOLUME и PositionGetInteger() с идентификатором POSITION_TYPE).

В этом случае полностью готовая функция будет иметь вид: 

double fSymbolLots(string aSymbol)
  {
   double TmpLots=0;
   for(int i=0;i<PositionsTotal();i++) // Перебор всех позиций
     {
      if(PositionGetSymbol(i)==aSymbol) // Найдена позиция с заданным символом
        {
         TmpLots=PositionGetDouble(POSITION_VOLUME);
         if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
           {
            TmpLots*=-1; // В зависимости от направления позиции устанавливаем знак числа
           }
         break;
        }
     }
   TmpLots=NormalizeDouble(TmpLots,2);
   return(TmpLots);
  }

После определения текущего объема пройдем в цикле от конца истории сделок к началу до тех пор, пока сумма объемов сделок в истории не будет равна текущему объему.

Длина выделенной истории сделок определяется при помощи функции HistoryDealsTotal(), для каждой сделки определяется ее тикет при помощи функции HistoryDealGetTicket(), данные по каждой сделки извлекаются функциями HistoryDealGetInteger() (тип сделки идентификатором DEAL_TYPE) и HistoryDealGetDouble() (объем сделки идентификатором DEAL_VOLUME):

double Sum=0; 
int FromI=0;
int FromTicket=0;
for(int i=HistoryDealsTotal()-1;i>=0;i--) // По всем сделкам от конца к началу
  {
   ulong ticket=HistoryDealGetTicket(i); // Определение тикета сделки и ее выделение
   if(ticket!=0)
     {
      switch(HistoryDealGetInteger(ticket,DEAL_TYPE)) // В зависимости от направления сделки прибавляем или вычитаем ее объем
        {
         case DEAL_TYPE_BUY:
            Sum+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
            Sum=NormalizeDouble(Sum,2);
            break;
         case DEAL_TYPE_SELL:
            Sum-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
            Sum=NormalizeDouble(Sum,2);
            break;
        }
      if(CurrentVolume==Sum) // Прошли по всем сделкам образующим текущую совокупную позицию
        {
         sLoadHistoryFrom=HistoryDealGetInteger(ticket,DEAL_TIME); // Запоминаем время последней "нулевой" позиции
         GlobalVariableSet(Commom_gvp+aSymbol+"_HistStTm",sLoadHistoryFrom);
         FromI=i; // Запоминаем индекс позиции в списке
         break;
        }
     }
  }

Найдя эту точку, запоминаем в глобальной переменной время, с которого в дальнейшем будет достаточно загружать историю сделок и индекс позиции в списке истории (переменная FromI).

До исполнения этой сделки совокупная позиция по символу была равна нулю.

Теперь идем в цикле от позиции FromI к концу истории и подсчитываем объем позиции по сделкам только с заданным магическим номером:

static double sVolume=0;
static ulong sLastTicket=0;
for(int i=FromI;i<HistoryDealsTotal();i++) // От первой сделки формирующей объем текущей позиции до конца
  {
   ulong ticket=HistoryDealGetTicket(i); // Определяем тике сделки и выделяем ее
   if(ticket!=0)
     {
      if(HistoryDealGetString(ticket,DEAL_SYMBOL)==aSymbol) // Заданный символ
        {
         long PosMagic=HistoryDealGetInteger(ticket,DEAL_MAGIC);
         if(PosMagic==aMagic || aMagic==-1) // Заданный магик
           {
            switch(HistoryDealGetInteger(ticket,DEAL_TYPE)) // В зависимости от направления 
                                                       // сделки прибавляем или вычитаем ее объем
              {
               case DEAL_TYPE_BUY:
                  sVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                  sLastTicket=ticket;
                  break;
               case DEAL_TYPE_SELL:
                  sVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                  sLastTicket=ticket;
                  break;
              }
           }
        }
     }
  } 

По завершению этого цикла в переменой sVolume имеем значение текущей позиции по заданному магическому номеру, а в переменной sLastTicket - тикет последней сделки с заданным магическим номером, после исполнения этой сделки объем позиции по заданному магическому номеру стал равен sVolume. На этом предварительная работа функции закончена.

Переменные sLoadHistoryFrom, sLastTicket и sVolume объявлены в функции как статические (сохраняют свои значения по завершению работы функции), значения хранящиеся в них понадобятся в дальнейшем при каждом вызове функции.

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

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

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

if(!HistorySelect(sLoadHistoryFrom,TimeCurrent())) // Подгрузка истории до текущего момента времени
  {
   return(false);
  }
for(int i=HistoryDealsTotal()-1;i>=0;i--) // Цикл от конца истории сделок до сделки для которой уже подсчитан объем
  {
   ulong ticket=HistoryDealGetTicket(i); // Определение тикета сделки и ее выделение
   if(ticket!=0)
     {
      if(ticket==sLastTicket) // Дошли до уже посчитанной сделки, запоминаем значение тикета и завершаем цикл
        {
         sLastTicket=HistoryDealGetTicket(HistoryDealsTotal()-1);
         break;
        }
      switch(HistoryDealGetInteger(ticket,DEAL_TYPE)) // В зависимости от направления сделки прибавляем ии вычитаем ее объем
        {
         case DEAL_TYPE_BUY:
            sVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
            break;
         case DEAL_TYPE_SELL:
            sVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
            break;
        }
     }
  }

Алгоритм работы функции можно изобразить следующей схемой:

Полностью готовая функция:

bool fGetPositionVolume(string aSymbol,int aMagic,double aVolume)
  {
   static bool FirstStart=false;
   static double sVolume=0;
   static ulong sLastTicket=0;
   static datetime sLoadHistoryFrom=0;
   // Первое выполнение функции на запуске эксперта
   if(!FirstStart)
     {
      if(GlobalVariableCheck(Commom_gvp+aSymbol+"_HistStTm"))
        {
         sLoadHistoryFrom=(datetime)GlobalVariableGet(Commom_gvp+aSymbol+"_HistStTm");
        }
      else
        {
         GlobalVariableSet(Commom_gvp+aSymbol+"_HistStTm",0);
        }
      if(!HistorySelect(sLoadHistoryFrom,TimeCurrent())) // Не удалось выполнить функцию правильно, 
                                                      // завершаем, повторим на следующе тике
        {
         return(false);
        }
      double CurrentVolume=fSymbolLots(aSymbol); // Общий объем позиции по символу
      double Sum=0;
      int FromI=0;
      int FromTicket=0;
      // Ищем последний момент времени существования "нулевой позиции"
      for(int i=HistoryDealsTotal()-1;i>=0;i--)
        {
         ulong ticket=HistoryDealGetTicket(i);
         if(ticket!=0)
           {
            switch(HistoryDealGetInteger(ticket,DEAL_TYPE))
              {
               case DEAL_TYPE_BUY:
                  Sum+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                  Sum=NormalizeDouble(Sum,2);
                  break;
               case DEAL_TYPE_SELL:
                  Sum-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                  Sum=NormalizeDouble(Sum,2);
                  break;
              }
            if(CurrentVolume==Sum)
              {
               sLoadHistoryFrom=HistoryDealGetInteger(ticket,DEAL_TIME);
               GlobalVariableSet(Commom_gvp+aSymbol+"_HistStTm",sLoadHistoryFrom);
               FromI=i;
               break;
              }
           }
        }
      // Подсчет значения объема позиции по заданному магику и символу
      for(int i=FromI;i<HistoryDealsTotal();i++)
        {
         ulong ticket=HistoryDealGetTicket(i);
         if(ticket!=0)
           {
            if(HistoryDealGetString(ticket,DEAL_SYMBOL)==aSymbol)
              {
               long PosMagic=HistoryDealGetInteger(ticket,DEAL_MAGIC);
               if(PosMagic==aMagic || aMagic==-1)
                 {
                  switch(HistoryDealGetInteger(ticket,DEAL_TYPE))
                    {
                     case DEAL_TYPE_BUY:
                        sVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                        sLastTicket=ticket;
                        break;
                     case DEAL_TYPE_SELL:
                        sVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                        sLastTicket=ticket;
                        break;
                    }
                 }
              }
           }
        }
      FirstStart=true;
     }
   // Досчитывание объема позиции по заданному магику и символу в случае появления новых сделок
   if(!HistorySelect(sLoadHistoryFrom,TimeCurrent()))
     {
      return(false);
     }
   for(int i=HistoryDealsTotal()-1;i>=0;i--)
     {
      ulong ticket=HistoryDealGetTicket(i);
      if(ticket!=0)
        {
         if(ticket==sLastTicket)
           {
            sLastTicket=HistoryDealGetTicket(HistoryDealsTotal()-1);
            break;
           }
         switch(HistoryDealGetInteger(ticket,DEAL_TYPE))
           {
            case DEAL_TYPE_BUY:
               sVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
               break;
            case DEAL_TYPE_SELL:
               sVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
               break;
           }
        }
     }
   aVolume=NormalizeDouble(sVolume,2);;
   return(true);
  }

В функцию передается символ и магический номер, по которым надо получить объем позиции. В случае успешного выполнения функции она возвращает true, в случае неудачного - false.

После успешного выполнения функции в переменной aVolume, передаваемой в функцию по ссылке находится искомое значение. В функции объявлены статические переменные, что делает функцию непригодной для вызова с другими параметрами (символом и магическим номером).

Если бы это был MQL4, можно было бы сделать копию функции с другим именем и вызывать ее для другой пары символ - магический номер, или объявить переменные FirstStart, sVolume, sLastTicket, sLoadHistoryFrom как общие переменные - свои для каждой пары символ - магический номер и передавать их в функцию.

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

Объявим класс class PositionVolume. Все переменные, которые были объявлены в функции как статические, объявим как private, так как значение ни одной из них нам не будет нужно в эксперте, за исключением переменной Volume. Но и оно будет нужно только после выполнения функции подсчета объема позиции. Также объявим переменные Symbol и Magic - нецелесообразно каждый раз передавать их в функцию, достаточно это сделать один раз при инициализации экземпляра класса.

Кроме упомянутых переменных класс будет включать две функции public: функцию инициализации и собственно функцию получения объема позиции, а так же функцию private для определения общего объема позиции на символе:

class PositionVolume
  {
private:
   string            pSymbol;
   int               pMagic;
   bool              pFirstStart;
   ulong             pLastTicket;
   double            pVolume;
   datetime         pLoadHistoryFrom;
   double            SymbolLots();
public:
   void Init(string aSymbol,int aMagic)
     {
      pSymbol=aSymbol;
      pMagic=aMagic;
      pFirstStart=false;
      pLastTicket=0;
      pVolume=0;
     }
   bool              GetVolume(double  &aVolume);
  }; 
bool PositionVolume::GetVolume(double  &aVolume)
  {
   if(!pFirstStart)
     {
      if(GlobalVariableCheck(Commom_gvp+pSymbol+"_HistStTm"))
        {
         pLoadHistoryFrom=(datetime)GlobalVariableGet(Commom_gvp+pSymbol+"_HistStTm");
        }
      else
        {
         GlobalVariableSet(Commom_gvp+pSymbol+"_HistStTm",0);
        }
      if(!HistorySelect(pLoadHistoryFrom,TimeCurrent()))
        {
         return(false);
        }
      double CurrentVolume=fSymbolLots(pSymbol);
      double Sum=0;
      int FromI=0;
      int FromTicket=0;
      for(int i=HistoryDealsTotal()-1;i>=0;i--)
        {
         ulong ticket=HistoryDealGetTicket(i);
         if(ticket!=0)
           {
            switch(HistoryDealGetInteger(ticket,DEAL_TYPE))
              {
               case DEAL_TYPE_BUY:
                  Sum+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                  Sum=NormalizeDouble(Sum,2);
                  break;
               case DEAL_TYPE_SELL:
                  Sum-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                  Sum=NormalizeDouble(Sum,2);
                  break;
              }
            if(CurrentVolume==Sum)
              {
               pLoadHistoryFrom=HistoryDealGetInteger(ticket,DEAL_TIME);
               GlobalVariableSet(Commom_gvp+pSymbol+"_HistStTm",pLoadHistoryFrom);
               FromI=i;
               break;
              }
           }
        }
      for(int i=FromI;i<HistoryDealsTotal();i++)
        {
         ulong ticket=HistoryDealGetTicket(i);
         if(ticket!=0)
           {
            if(HistoryDealGetString(ticket,DEAL_SYMBOL)==pSymbol)
              {
               long PosMagic=HistoryDealGetInteger(ticket,DEAL_MAGIC);
               if(PosMagic==pMagic || pMagic==-1)
                 {
                  switch(HistoryDealGetInteger(ticket,DEAL_TYPE))
                    {
                     case DEAL_TYPE_BUY:
                        pVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                        pLastTicket=ticket;
                        break;
                     case DEAL_TYPE_SELL:
                        pVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                        pLastTicket=ticket;
                        break;
                    }
                 }
              }
           }
        }
      pFirstStart=true;
     }
   if(!HistorySelect(pLoadHistoryFrom,TimeCurrent()))
     {
      return(false);
     }
   for(int i=HistoryDealsTotal()-1;i>=0;i--)
     {
      ulong ticket=HistoryDealGetTicket(i);
      if(ticket!=0)
        {
         if(ticket==pLastTicket)
           {
            break;
           }
         if(HistoryDealGetString(ticket,DEAL_SYMBOL)==pSymbol)
           {
            long PosMagic=HistoryDealGetInteger(ticket,DEAL_MAGIC);
            if(PosMagic==pMagic || pMagic==-1)
              {
               switch(HistoryDealGetInteger(ticket,DEAL_TYPE))
                 {
                  case DEAL_TYPE_BUY:
                     pVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                     break;
                  case DEAL_TYPE_SELL:
                     pVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                     break;
                 }
              }
           }
        }
     }
   if(HistoryDealsTotal()>0)
     {
      pLastTicket=HistoryDealGetTicket(HistoryDealsTotal()-1);
     }
   pVolume=NormalizeDouble(pVolume,2);
   aVolume=pVolume;
   return(true);
  }

double PositionVolume::SymbolLots()
  {
   double TmpLots=0;
   for(int i=0;i<PositionsTotal();i++)
     {
      if(PositionGetSymbol(i)==pSymbol)
        {
         TmpLots=PositionGetDouble(POSITION_VOLUME);
         if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
           {
            TmpLots*=-1;
           }
         break;
        }
     }
   TmpLots=NormalizeDouble(TmpLots,2);
   return(TmpLots);
  }

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

PositionVolume PosVol11;
PositionVolume PosVol12;
PositionVolume PosVol21;
PositionVolume PosVol22;

В функции OnInit() эксперта провести их инициализацию, например:

PosVol11.Init(Symbol_1,Magic_1); 
PosVol12.Init(Symbol_1,Magic_2);
PosVol21.Init(Symbol_2,Magic_1); 
PosVol22.Init(Symbol_2,Magic_2);   

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

При успешном выполнении функции она возвращает значение true, искомое значение находится в переменной передаваемой в функцию по ссылке:

double Vol11;
double Vol12;
double Vol21;
double Vol22;
PosVol11.GetVolume(Vol11);
PosVol12.GetVolume(Vol12);
PosVol21.GetVolume(Vol21);
PosVol22.GetVolume(Vol22);

На этом, можно сказать, все готово, осталось провести контрольное тестирование.

4. Контрольный тест

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

  1. по индикатору RSI с периодом 14 на символе EURUSD с магическим номером 1;
  2. по индикатору RSI с периодом 21 на символе EURUSD с магическим номером 2;
  3. по индикатору RSI с периодом 14 на символе GBPUSD с магическим номером 1;
  4. по индикатору RSI с периодом 21 на символе GBPUSD с магическим номером 2.

С магическим номером 1 сделки выполнялись с лотом 0.1, с магическим номером 2 - с лотом 0.2.

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

Эксперт в приложении (имя файла ePosVolTest).

Заключение

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

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

Прикрепленные файлы |
eposvoltest.mq5 (18.63 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (25)
Dmitry Fedoseev
Dmitry Fedoseev | 20 февр. 2013 в 00:08
BlinGoblin:

я не понял последней фразы, Integer, если не трудно, поясните, что вы хотели сказать

Наверное вы предлагаете заменить PositionGetDouble(), POSITION_COMMISSION, POSITION_SWAP, POSITION_PROFIT. на  HistoryDealGetDouble(),  DEAL_COMMISSION, DEAL_SWAP, DEAL_PROFIT.

 

Нет, не заменять. Пользоваться этими функциями для сбора прибыли за всю история. Выделить историю, пройтись по всем сделкам и суммировать их прибыль, своп, комиссию.
Valerii Mazurenko
Valerii Mazurenko | 20 февр. 2013 в 01:03
NIKOLIA:

ПОДСКАЖИТЕ КАК СНЯТЬ ДЕНЬГИ С ФОРЕКСА, ИЛИ КАК ИХ ПЕРЕВЕСТИ В ВЕБМАНИ

а много?
Viktor Vlasenko
Viktor Vlasenko | 30 мар. 2017 в 15:55

замечено следующее, при работе многих экспертов на одном счету (ФОРТС, Открытие) совокупная позиция может может равняться 0, при этом эксперты находятся в позициях

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


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

правда встаёт вопрос, например, а если какой-то эксперт был удалён а его поза закрыта руками?

Dmitry Fedoseev
Dmitry Fedoseev | 30 мар. 2017 в 17:43
Так смысл то в поиске своей совокупной позиции каждого эксперта, а общая совокупная позиция она и так известна в терминале.
SergeyNU
SergeyNU | 31 июл. 2019 в 14:58

Добрый день.

Помогите разобраться как работают эти классы и ООП. Допустим мы подключили этот класс к эксперту, он выполняется только при обращение к нему из эксперта? Или он работает параллельно и по запросу выдает только результаты?

Конкурс советников внутри советника Конкурс советников внутри советника
С помощью виртуальной торговли можно создать адаптивный советник, который будет выполнять включение/отключение сделок на реальном рынке. Соберите несколько стратегий в одном эксперте! Ваш мультисистемный советник будет автоматически выбирать торговую стратегию, которой стоит торговать на реальном рынке по результатам успешности виртуальных сделок. Такой метод позволяет снизить просадку и увеличить прибыльность Вашей работы на рынке. Экспериментируйте и делитесь результатами с другими! Думаю, будет интересно многим узнать о вашем портфеле стратегий.
Перенос индикаторов из MQL4 в MQL5 Перенос индикаторов из MQL4 в MQL5
Статья посвящена особенностям переноса в MQL5 ценовых конструкций, используемых в индикаторах, написанных на MQL4. Для упрощения переноса индикаторных расчетов из MQL4 в MQL5 предложена библиотека функций mql4_2_mql5.mqh, применение которой рассмотрено на примере переноса индикаторов MACD, Stochastic и RSI.
Взаимодействие MetaTrader 5 и MATLAB Взаимодействие MetaTrader 5 и MATLAB
Статья раскрывает детали реализации связки MetaTrader 5 и математического пакета MatLab. Детально раскрывается механизм преобразования данных, процесс разработки универсальной библиотеки для взаимодействия с рабочим столом MatLab, также рассматривается вопрос использования DLL библиотек, сгенерированных средой MatLab. Данная статья рассчитана на подготовленных читателей, знающих C++ и MQL5.
Как написать индикатор на основе другого индикатора Как написать индикатор на основе другого индикатора
На MQL5 можно не только создать новый пользовательский индикатор с чистого листа, но и написать индикатор на базе другого, уже существующего индикатора, встроенного в терминал или пользовательского. И тут существует два способа: первый - доработать индикатор, добавить к нему новые вычисления и графические стили, второй - использовать встроенный в терминал индикатор или существующий пользовательский индикатор при помощи функций iCustom() или IndicatorCreate().