Русский
preview
Desenvolvendo um EA multimoeda (Parte 28): Adicionando um gerenciador de fechamento de posições

Desenvolvendo um EA multimoeda (Parte 28): Adicionando um gerenciador de fechamento de posições

MetaTrader 5Exemplos |
77 0
Yuriy Bykov
Yuriy Bykov

Introdução

Na Parte 12 adicionamos ao EA multimoeda um módulo de gerenciamento de risco para limitar o rebaixamento diário e total. Embora não aumente o lucro, é muito importante para proteger o capital em condições desfavoráveis. Baseia-se nas regras do prop-trading, com possibilidade de configuração flexível: rebaixamento em moeda, em porcentagem do saldo ou desde o início do dia.

O módulo é implementado como a classe CVirtualRiskManager com métodos para monitoramento do saldo, lucro e verificação de restrições. Também está prevista uma função de fixação de lucro, ao atingir a meta todas as posições são fechadas e o trading é interrompido.

No caso de contas comuns, seria desejável que, após atingir o lucro, o trading fosse reiniciado automaticamente. Atualmente, isso exige intervenção manual. Chegou a hora de automatizar também este processo.

Para reiniciar as estratégias de negociação ao atingir o lucro definido foram considerados dois caminhos:

  • expandir o atual gerenciador de risco,
  • criar um módulo separado.

Escolhemos o segundo caminho, pois o atual gerenciador de risco funciona independentemente das estratégias: ele fecha apenas posições reais, sem afetar as virtuais. Alterar essa lógica complicaria a arquitetura e quebraria a independência modular.

Além disso, como o gerenciador de risco cria carga adicional durante os testes, é melhor levar essa nova funcionalidade para um módulo separado. Assim ele poderá ser usado mesmo sem o gerenciador de risco em execução.

O novo objetivo é um módulo capaz de reiniciar todas as estratégias quando determinadas condições forem atendidas (lucro, perda, tempo etc.), sem depender do histórico e sem intervenção manual. Vamos chamar o novo módulo de gerenciador de fechamento, pois é um módulo separado cuja presença não é obrigatória, mas sua adição pode melhorar os resultados, e ele controla o processo de fechamento completo de todas as posições, tanto reais quanto virtuais.


Requisitos iniciais

Vamos formular de forma mais clara o que fará parte das responsabilidades do gerenciador de fechamento e quais parâmetros serão necessários para isso. 

O gerenciador de fechamento deve:

  1. Fixar lucro, ou seja, fechar todas as posições virtuais ao atingir o lucro definido. As posições reais também serão automaticamente fechadas. Para controlar esse processo vamos introduzir três parâmetros:
    • Saldo base. Quantidade de fundos na conta de negociação a partir da qual será feito o cálculo de lucro ou perda.
    • Método de cálculo do lucro. Pode assumir um entre vários valores possíveis, por exemplo em porcentagem do saldo base ou um valor fixo na moeda do depósito.
    • Valor do lucro. Número usado para calcular o lucro pelo método escolhido.
  2. Fixar perda, ou seja, fechar todas as posições virtuais ao atingir a perda definida. Para esse processo também serão úteis três parâmetros, um ou dois dos quais podem ser comuns aos parâmetros de fixação de lucro:
    • Saldo base. Quantidade de fundos na conta de negociação a partir da qual será feito o cálculo de lucro ou perda.
    • Método de cálculo da perda. Também pode assumir um entre vários valores possíveis, assim como no método de cálculo do lucro.
    • Valor da perda. Número usado para calcular a perda pelo método escolhido.
  3. Ativar trailing de lucro, ao atingir o lucro definido, as posições virtuais não são fechadas, mas é memorizado um nível de lucro um pouco menor no qual ocorrerá o fechamento direto das posições. Se o lucro crescer, esse nível também deverá aumentar. O aumento pode ocorrer tanto de forma contínua quanto em etapas com um determinado passo. Para esse processo podem ser adicionados os seguintes parâmetros:
    • Ativação do trailing (Sim / Não).
    • Método de definição do nível. Nesse parâmetro podemos escolher o método preferido para definir o nível de ativação do trailing. Por exemplo, o nível pode ser definido como uma fração do lucro fixado ou como um valor absoluto na moeda da conta de negociação.
    • Nível de início do trailing. Número usado para calcular o nível inicial do trailing pelo método escolhido.
    • Valor do passoNúmero usado para calcular o valor do passo, ao atingir o qual o nível de trailing é deslocado. Para o cálculo pode ser utilizado o mesmo método que para o nível inicial do trailing.
  4. Ativar nível de breakeven, ao atingir esse valor de lucro, é memorizado um pequeno nível positivo de lucro no qual ocorrerá o fechamento das posições. Com o aumento posterior do lucro esse nível, diferentemente do trailing, não será aumentado. Os parâmetros que controlam esse processo podem ser os seguintes:
    • Ativação do breakeven (Sim / Não).
    • Método de definição do nível. Esse parâmetro é análogo ao parâmetro com o mesmo nome do trailing, ou seja, também pode ser relativo ou absoluto.
    • Nível de ativação do breakevenNúmero usado para calcular o nível de breakeven pelo método escolhido.
Por enquanto vamos nos limitar a essa funcionalidade básica, que poderá ser desenvolvida posteriormente. É possível que durante a implementação precisemos adicionar algo aos parâmetros ou modificar de alguma forma a sua composição. Mas inicialmente vamos nos orientar exatamente por essa descrição da tarefa.


Repositório do projeto

Na Parte 25 adicionamos uma nova estratégia e analisamos como criar um projeto para otimização automática da estratégia escolhida e criação do EA final, que inclui muitas instâncias de estratégias de negociação com diferentes parâmetros. Todo o código foi dividido em duas partes a parte de biblioteca e a parte do projeto. Para a parte de biblioteca já criamos na Parte 26 um repositório de código público chamado Adwizard no repositório MQL5 Algo Forge. Porém, para a parte do projeto isso ainda não havia sido feito.

Vamos corrigir isso e criar um novo repositório SimpleCandles. Nesse repositório ficará localizada a parte do projeto para criação do EA final que utiliza estratégias com o mesmo nome. Além do branch principal main, também criaremos um branch de desenvolvimento chamado develop. Se este projeto for abordado em vários artigos, então as alterações relacionadas a diferentes artigos serão distribuídas em diferentes branches gerados a partir do branch develop. À medida que estiverem prontas, elas serão mescladas novamente nos branches develop e main.

Vamos criar uma pasta local para colocar a pasta do projeto, por exemplo, MQL5/Experts/Articles/17608. Vamos clonar esse repositório para a pasta escolhida e criar dentro dela a pasta Include. Nessa pasta colocaremos o repositório da parte de biblioteca da qual este projeto depende. Vamos clonar na pasta Include o repositório da biblioteca Adwizard.

Após essas operações teremos aproximadamente a seguinte estrutura de pastas na pasta do terminal:

Fig. 1. Estrutura de pastas no repositório do projeto após a clonagem das partes do projeto e da biblioteca

Na pasta clonada do repositório Adwizard vamos alternar para o branch develop. Esse será o branch comum a todos os artigos. Porém, durante o trabalho neste projeto faremos alterações na biblioteca Adwizard, por isso neste repositório criaremos um novo branch gerado a partir do branch develop.

Depois disso, no repositório do projeto SimpleCandles também criaremos um branch separado para trabalhar neste artigo e iniciaremos o desenvolvimento nele.


Preparação do código da biblioteca

Antes de iniciar diretamente a implementação do gerenciador de fechamento, vamos preparar o ambiente para a sua integração. Antes de tudo, vale observar que nas versões mais recentes do MetaTrader foi adicionado um controle mais rigoroso de tipos de variáveis, por causa do qual um código que anteriormente compilava passou a gerar erros deste tipo:

parameter convertion type 'short[260]' to 'ushort[] &' is not allowed   MTTester.mqh    
   int user32::GetClassNameW(long,ushort&[],int)        winuser.mqh     

Felizmente, no código utilizado isso apareceu apenas em um único ponto e foi corrigido alterando o tipo do array: 

static string GetClassName( const HANDLE Handle )
  {
    string Str = NULL;

    ushort Buffer[MAX_PATH] = {0};

    if (user32::GetClassNameW(Handle, Buffer, ::ArraySize(Buffer)))
      Str = ::ShortArrayToString(Buffer);

    return(Str);
  }

No entanto, depois de atualizações posteriores do terminal, esse arquivo foi totalmente substituído pela versão mais recente da biblioteca MultiTester, para corrigir um comportamento incorreto que tinha alguma outra causa.

A próxima alteração está relacionada à necessidade de o gerenciador de fechamento iniciar o fechamento de todas as posições. Vamos adicionar um método separado de fechamento de todas as posições na classe do EA CVirtualAdvisor, para que o gerenciador de fechamento possa chamá-lo quando necessário.

Para implementar esse método já temos tudo o que é necessário: cada estratégia herdada de CVirtualStrategy possui um método para fechar todas as suas posições virtuais. Portanto, na classe do EA basta chamar esse método para cada estratégia:

//+------------------------------------------------------------------+
//| Закрытие позиций всех стратегий                                  |
//+------------------------------------------------------------------+
void CVirtualAdvisor::Close(void) {
// Для всех стратегий вызываем метод закрытия виртуальныхпозиций
   FOREACH(m_strategies) ((CVirtualStrategy *)m_strategies[i]).Close();
}

Na Parte 27 criamos um componente para exibir texto multilinha em uma janela expandida por todo o gráfico ao qual o EA está anexado. Ele foi criado no contexto de outro projeto, porém também será útil aqui. Portanto vamos movê-lo para a biblioteca Adwizard, colocando o arquivo com a classe CConsoleDialog na pasta Adwizard/Utils. Para utilizá-lo vamos adicionar a criação de um objeto dessa classe no EA final no arquivo Adwizard/Experts/Expert.mqh:

CConsoleDialog      *dialog;             // Диалог для вывода текста с результатами

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
// ...

// Создаём и запускаем диалог для вывода результатов
   dialog = new CConsoleDialog();
   dialog.Create(__NAME__ + ":" + (string) magic_);
   dialog.Run();

// Успешная инициализация
   return(INIT_SUCCEEDED);
}

Na função de processamento de novo tick no mesmo arquivo vamos adicionar a definição de um novo texto para esse objeto. O próprio texto será obtido a partir do objeto da classe CVirtualAdvisor, chamando seu método Text(), que escreveremos a seguir:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   expert.Tick();

// ...

// Выводим текст с информацией о работе советника
   if (IsNewBar(Symbol(), PERIOD_M1)) {
      dialog.Text(expert.Text());
   }
}

Para que no fundo do texto não sejam desenhadas as linhas de abertura das posições virtuais, vamos temporariamente desativar a visualização delas, deixando o método CVirtualChartOrder::Show() vazio:

//+------------------------------------------------------------------+
//| Показ виртуальной позиции (ордера)                               |
//+------------------------------------------------------------------+
void CVirtualChartOrder::Show() {
   return;

   // ...
}


Propriedade IsActive para todos os descendentes de CFactorable

Se a otimização for executada em instrumentos de negociação que incluem criptomoedas, mas o lançamento ocorrer em uma corretora onde elas não existem, pode surgir um erro ao iniciar o EA final. Ele está relacionado à tentativa de obter o histórico de negociação e as propriedades de um símbolo que não está presente na Observação do Mercado. Nesse caso, quando o EA final possui um grande número de instâncias de estratégias de negociação trabalhando com símbolos disponíveis, é possível simplesmente desativar as estratégias para aqueles instrumentos que não estão presentes na Observação do Mercado.

Atualmente todas as estratégias de negociação são descendentes da classe CFactorable, que permite criar objetos dessas estratégias a partir de uma string de inicialização. Nessa classe está prevista a possibilidade de que a string de inicialização não esteja completamente correta. Nesse caso esse objeto e todos os objetos anteriores da string de inicialização comum serão considerados incorretos. Nessa situação o EA não conseguirá executar a inicialização e continuar o funcionamento.

Mas gostaríamos que um determinado tipo de "erro" na string de inicialização permitisse simplesmente ignorar parte dela, criando no final um objeto do expert a partir da string completa. Para isso vamos adicionar à classe CFactorable uma nova propriedade chamada m_isActive e um método para leitura do seu valor IsActive():

//+------------------------------------------------------------------+
//| Базовый класс объектов, создаваемых из строки                    |
//+------------------------------------------------------------------+
class CFactorable {
private:
   // ...

protected:
   // ...
   
   bool              m_isActive; // Объект активен?

   // ...

public:
   // ...

   bool              IsActive();                         // Объект активный?

   // ...
};

Em algumas classes, por exemplo na classe de gerenciamento de risco CVirtualRiskManager, essa propriedade já existia, portanto nessas classes removeremos sua declaração, já que ela será feita na classe base. Isso também se aplica à futura classe do gerenciador de fechamento, na qual essa propriedade também será usada para verificar se ele está ativo.

Ao mesmo tempo tornamos opcional a indicação do gerenciador de risco e do gerenciador de fechamento na string de inicialização, adicionando uma verificação da sua presença na inicialização do EA no construtor da classe CVirtualAdvisor:

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CVirtualAdvisor::CVirtualAdvisor(string p_params) {
// Запоминаем строку инициализации
   m_params = p_params;

// Читаем строку инициализации объекта группы стратегий
   string groupParams = ReadObject(p_params);

// Читаем строку инициализации объекта риск-менеджера
   string riskManagerParams = NULL;

   if(IsObjectOf(p_params, "CVirtualRiskManager")) {
      riskManagerParams = ReadObject(p_params);
   }

// Читаем строку инициализации объекта менеджера закрытия
   string closeManagerParams = NULL;
   if(IsObjectOf(p_params, "CVirtualCloseManager")) {
      closeManagerParams = ReadObject(p_params);
   }

// Читаем магический номер
   ulong p_magic = ReadLong(p_params);

// Читаем название эксперта
   string p_name = ReadString(p_params);

// Читаем признак работы на только на открытии бара
   m_useOnlyNewBar = (bool) ReadLong(p_params);

// Если нет ошибок чтения, то
   if(IsValid()) {
// Создаём группу стратегий
      CREATE(CVirtualStrategyGroup, p_group, groupParams);

      // Инициализируем монитор символов статическим монитором символов
      m_symbols = CSymbolsMonitor::Instance();

      // Инициализируем получателя статическим получателем
      m_receiver = CVirtualReceiver::Instance(p_magic);

      // Инициализируем интерфейс статическим интерфейсом
      m_interface = CVirtualInterface::Instance(p_magic);

      // Формируем из имени эксперта и параметров имя файла базы данных эксперта для сохранения состояния
      m_fileName = FileName(p_name, p_magic);

      // Запоминаем время начала работы (тестирования)
      m_fromDate = TimeCurrent();

      // Сбрасываем время последнего сохранения
      m_lastSaveTime = 0;

      // Добавляем к эксперту содержимое группы
      Add(p_group);

      // Удаляем объект группы
      delete p_group;

      // Создаём объект риск-менеджера
      if(riskManagerParams != NULL) {
         m_riskManager = NEW(riskManagerParams);
      }

      // Создаём объект менеджера закрытия
      if(closeManagerParams != NULL) {
         m_closeManager = NEW(closeManagerParams);
         m_closeManager.Expert(&this);
      }
   }
}

Após as alterações realizadas vamos passar para a parte principal a criação do gerenciador de fechamento.


Criando o gerenciador de fechamento

Primeiro vamos destacar alguns possíveis estados em que o gerenciador de fechamento pode se encontrar. O estado normal indica que ainda não foi atingido nem o lucro planejado nem a perda máxima. Nesse estado, o gerenciador de fechamento deve apenas aguardar a transição para um dos estados seguintes. Ao atingir o lucro ou a perda definidos ocorrerá a transição para dois estados correspondentes. Nesses estados o gerenciador de fechamento deve fechar todas as posições, memorizar novos níveis de lucro e perda definidos e retornar ao estado normal.

Se o trailing estiver ativado, então ao atingir o nível de lucro definido o gerenciador de fechamento passará para outro estado. O retorno desse estado para o estado normal exigirá ações mais complexas, por isso por enquanto não vamos descrevê-las em detalhes.

Todos os estados serão implementados como um tipo enumerado ENUM_CM_STATE.

Para definir os métodos de cálculo do lucro planejado e da perda também criaremos dois tipos enumerados separados: ENUM_CM_CALC_LOSS e ENUM_CM_CALC_PROFIT. Vamos prever duas opções: um valor fixo em termos monetários e um valor relativo em porcentagem de um determinado saldo base.

// Возможные состояния менеджера закрытия
enum ENUM_CM_STATE {
   CM_STATE_OK,            // Лимиты не превышены
   CM_STATE_LOSS,          // Превышен общий лимит
   CM_STATE_PROFIT,        // Достигнута общая прибыль
   CM_STATE_TRAIL_PROFIT   // Трейлинг прибыли
};

// Возможные способы расчёта общего убытка
enum ENUM_CM_CALC_LOSS {
   CM_CALC_LOSS_MONEY_BB,           // [$] Fixed Money
   CM_CALC_LOSS_PERCENT_BB,         // [%] of Base Balance
};

// Возможные способы расчёта общей прибыли
enum ENUM_CM_CALC_PROFIT {
   CM_CALC_PROFIT_MONEY_BB,           // [$] Fixed Money
   CM_CALC_PROFIT_PERCENT_BB,         // [%] of Base Balance
};

A própria classe do gerenciador de fechamento será herdada da classe base CFactorable para garantir a possibilidade de criar um objeto do gerenciador de fechamento a partir da string de inicialização. Além disso, ele já terá a propriedade herdada de atividade, permitindo ativar ou desativar facilmente o gerenciador de fechamento.

Para garantir que a tarefa será executada, o gerenciador de fechamento precisará armazenar o nível do saldo base, a partir do qual o lucro ou a perda obtidos serão calculados. Ao fixar o lucro ou a perda, esse nível também deverá ser atualizado para o valor do saldo atual da conta, alcançado após o fechamento de todas as posições. Essa é a diferença desse parâmetro em relação ao parâmetro com o mesmo nome no gerenciador de risco. Nesse caso, o nível do saldo base permanece sempre inalterado.

O próximo grupo de propriedades será usado para escolher o método de cálculo e realizar o cálculo do lucro e da perda planejados. Elas serão usadas nos métodos de cálculo LossMoney() e ProfitMoney(), que retornam o valor em termos monetários.

Para fechar posições o gerenciador de fechamento deve ter a possibilidade de acessar o objeto do expert solicitando o fechamento. Portanto vamos adicionar à lista de propriedades do gerenciador de fechamento um ponteiro para o objeto do expert e um método para configurá-lo.

Também adicionaremos outra propriedade para armazenar o estado atual do objeto do gerenciador de fechamento.

A herança de CFactorable exige que o construtor seja colocado na área não pública e que sejam adicionados dois macros especiais, conforme descrito na Parte 24.

Como resultado obteremos aproximadamente a seguinte descrição da classe do gerenciador de fechamento:

//+------------------------------------------------------------------+
//| Класс менеджера закрытия (фиксации прибыли и убытков)            |
//+------------------------------------------------------------------+
class CVirtualCloseManager : public CFactorable {
protected:
// Основные параметры конструктора
   double            m_baseBalance;          // Базовый баланс

   ENUM_CM_CALC_LOSS m_calcLossLimit;        // Способ расчёта максимального общего убытка
   double            m_maxLossLimit;         // Параметр расчёта максимального общего убытка

   ENUM_CM_CALC_PROFIT m_calcProfitLimit;    // Способ расчёта максимальной общей прибыли
   double            m_maxProfitLimit;       // Параметр расчёта максимальной общей прибыли

   CVirtualAdvisor*  m_expert;               // Указатель на объект эксперта

// Текущее состояние
   ENUM_CM_STATE     m_state;                // Состояние

// Обновляемые значения
   double            m_balance;              // Текущий баланс
   double            m_equity;               // Текущие средства
   double            m_profit;               // Текущая плавающая прибыль
   double            m_overallProfit;        // Текущая общая прибыль относительно базового баланса

// Защищённые методы
   double            LossMoney();            // Максимальный общий убыток
   double            ProfitMoney();          // Максимальная прибыль

   void              UpdateProfit();         // Обновление текущих значений прибыли
   void              CheckLimits();          // Проверка достижения допустимых уровней прибыли/убытка
  
   CVirtualCloseManager(string p_params);    // Закрытый конструктор

public:
   STATIC_CONSTRUCTOR(CVirtualCloseManager); // Статический метод создания объекта
   virtual void      Tick();                 // Обработка тика в менеджере закрытия

   virtual string    Text();                 // Информация о текущем состоянии

   // Привязка эксперта к менеджеру закрытия
   void              Expert(CVirtualAdvisor* p_expert);

   virtual bool      Save();      // Сохранение состояния
   virtual bool      Load();      // Загрузка состояния

   virtual string    operator~() override;   // Преобразование объекта в строку
};

REGISTER_FACTORABLE_CLASS(CVirtualCloseManager); // Регистрация нового потомка CFactorable

Vamos observar dois métodos principais dessa classe, o construtor e o método de processamento de tick.

No construtor, como de costume, lemos sequencialmente os valores dos parâmetros da string de inicialização e os atribuímos às propriedades correspondentes, definimos o estado atual como normal, atualizamos os valores atuais de lucro e armazenamos o valor do saldo atual como saldo base caso ele não tenha sido definido diretamente:

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CVirtualCloseManager::CVirtualCloseManager(string p_params) {
// Запоминаем строку инициализации
   m_params = p_params;

// Читаем строку инициализации и устанавливаем значения свойств
   m_isActive = (bool) ReadLong(p_params);
   m_baseBalance = ReadDouble(p_params);
   m_calcLossLimit = (ENUM_CM_CALC_LOSS) ReadLong(p_params);
   m_maxLossLimit = ReadDouble(p_params);
   m_calcProfitLimit = (ENUM_CM_CALC_PROFIT) ReadLong(p_params);
   m_maxProfitLimit = ReadDouble(p_params);

// Устанавливаем состояние: Лимиты не превышены
   m_state = CM_STATE_OK;

// Обновляем текущие значения прибыли
   UpdateProfit();

// Корректируем базовый баланс, если он не задан
   if(m_baseBalance == 0) {
      m_baseBalance = m_balance;
   }
}

No método principal de processamento de tick analisamos o estado atual e, dependendo dele, verificamos se os níveis definidos de lucro ou perda foram atingidos caso o gerenciador esteja no estado normal, ou iniciamos o fechamento de todas as posições com posterior retorno ao estado normal:

//+------------------------------------------------------------------+
//| Обработка тика в риск-менеджере                                  |
//+------------------------------------------------------------------+
void CVirtualCloseManager::Tick() {
// Если риск-менеджер неактивен, то выходим
   if(!m_isActive) {
      return;
   }

// Обновляем текущие значения прибыли
   UpdateProfit();

// Если менеджер в состоянии трейлинга, то
   if(m_state == CM_STATE_TRAIL_PROFIT) {
      // Пока просто будем сразу фиксировать прибыль,
      // переводя менеджер в соответсвующее состояние
      if(true) {
         m_state = CM_STATE_PROFIT;
      }
   }

// Если менеджер в нормальном состоянии, то
   if(m_state == CM_STATE_OK) {
      // Проверяем превышение пределов убытка и прибыли
      CheckLimits();
   }

// Если менеджер в состоянии достигнутого убытка или прибыли, то
   if(m_state == CM_STATE_LOSS || m_state == CM_STATE_PROFIT) {
      // Закрываем все позиции
      m_expert.Close();

      // Если все позиции закрыты, то
      if(PositionsTotal() == 0) {
         // Переходим в нормальное состояние
         m_state = CM_STATE_OK;
         
         // Обновляем значение базового баланса
         m_baseBalance = m_balance;
      } else {
         // Ждём закрытия всех позиций
      }

      // Сохраняем состояние эксперта
      m_expert.Save();
   }
}

Inicialmente decidimos nos limitar apenas a essa funcionalidade do gerenciador de fechamento, portanto o estado de trailing de lucro ainda não é utilizado.


Passagem dos parâmetros de entrada

Após criar a classe do gerenciador de fechamento precisamos conectá-la ao EA. Para isso é necessário adicionar parâmetros de entrada por meio dos quais possamos controlar a criação da string de inicialização para o gerenciador de fechamento. Isso deve ser feito no arquivo Adwizard/Experts/Expert.mqh:

// ...

//+------------------------------------------------------------------+
//| Входные параметры                                                |
//+------------------------------------------------------------------+
input group "::: Использовать группу стратегий"
sinput int        groupId_       = 0;     // - ID группы из новой библиотеки (0 - последняя)
sinput bool       useAutoUpdate_ = true;  // - Использовать автообновление?

input group "::: Управление капиталом"
sinput double expectedDrawdown_  = 10;    // - Максимальный риск (%)
sinput double fixedBalance_      = 10000; // - Используемый депозит (0 - использовать весь) в валюте счета
input  double scale_             = 1.00;  // - Масштабирующий множитель для группы

input group ":::  Менеджер закрытия"
input bool        cmIsActive_                = true;  // - Активен?
input double      cmStartBaseBalance_        = 0;     // - Базовый баланс
input ENUM_CM_CALC_LOSS
cmCalcLossLimit_           = CM_CALC_LOSS_MONEY_BB;   // - Способ расчёта убытка
input double      cmLossLimit_       = 100;           // - Значение убытка для фиксации
input ENUM_CM_CALC_PROFIT
cmCalcProfitLimit_                    = CM_CALC_PROFIT_MONEY_BB;  // - Способ расчёта общей прибыли
input double      cmProfitLimit_   = 1000000;                     // - Значение общей прибыли для фиксации

// ...

Esse arquivo é incluído na compilação do EA final, portanto os parâmetros de entrada adicionados estarão disponíveis nele. Por padrão definimos que os valores de lucro e perda fixados serão especificados em dinheiro, na moeda do depósito. Os próprios valores ainda precisarão ser ajustados, portanto o que está indicado nos valores padrão por enquanto não é importante.


Testes iniciais

Vejamos o que obtivemos. Para começar vamos verificar se o próprio mecanismo de fechamento de posições sem considerar o lucro obtido funciona corretamente. Se tudo funcionar corretamente, então na próxima etapa já poderemos trabalhar também na otimização do lucro obtido.

Vamos usar o banco de dados do EA final obtido na Parte 25 para executar o EA final no testador. Naquela ocasião realizamos uma otimização acelerada em vários intervalos com duração de 1 ano e obtivemos doze grupos de estratégias salvos na tabela strategy_groups:

O arquivo do banco de dados tinha o nome SimpleCandles-27183.test.db.sqlitePara que o EA final possa usar esse banco de dados, esse arquivo deve estar localizado na pasta comum de dados dos terminais MetaTrader 5 na subpasta Files. Além disso, o nome do EA final deve ser SimpleCandles.ex5 e o valor do número mágico nos parâmetros de entrada deve permanecer igual a 27183.

Vamos iniciar primeiro o EA sem utilizar o gerenciador de fechamento com o primeiro grupo de estratégias com id_group=20. Para isso definiremos os seguintes valores nos parâmetros de entrada:

Como intervalo de teste vamos utilizar o mesmo intervalo no qual foi realizada a otimização automática, ou seja, todo o ano de 2022. Obtemos os seguintes resultados:

Fig. 2. Resultados do EA final com id_group=20 sem o gerenciador de fechamento para o ano de 2022

Como podemos ver, a otimização encontrou combinações bastante boas de parâmetros para diferentes instâncias de estratégias de negociação simples, de modo a garantir lucro significativo no intervalo definido, permanecendo dentro do limite de rebaixamento de 10%.

Agora vamos ativar o gerenciador de fechamento e definir como lucro esperado para fixação um pequeno valor, por exemplo 10 USD:


Vamos executar o EA no modo de teste visual. Como adicionamos ao EA final a exibição de informações sobre o funcionamento, nesse modo podemos observar quais símbolos e quantas estratégias no total são utilizadas no grupo com identificador 20 do banco de dados do EA (três símbolos GBPUSD, EURUSD, EURGBP e 48 estratégias), qual é o valor do saldo base do gerenciador de fechamento e quais são os níveis alvo de lucro e perda para o fechamento.

Fig. 3. Início do teste visual do EA com o gerenciador de fechamento ativado

Na Fig. 3 é possível ver que o saldo base do gerenciador de fechamento já atingiu $10009.89, ou seja, uma vez ocorreu o fechamento de todas as posições ao atingir o lucro definido de $10.

Vemos no log a seguinte linha:

2022.01.03 02:31:00 CVirtualCloseManager::CheckLimits CLOSE PROFIT Profit = 12.94 OverallProfit = 10.54 (10.00)

O gerenciador de fechamento foi acionado quando o lucro total (OverallProfit = 10.54) em relação ao saldo base inicial de $10000 excedeu $10. Devido ao modo de teste apenas no início de cada barra de um minuto (1 minute OHLC), o processo de fechamento de todas as posições abertas se estendeu por dois minutos consecutivos, por isso o novo nível base fixado acabou sendo um pouco menor que $10010. Quando o modo de todos os ticks é ativado essas diferenças já não são observadas.

Agora vamos testar o funcionamento do gerenciador de fechamento para limitação de perdas. Vamos definir um pequeno valor para fixação da perda, por exemplo $20, e o valor para fixação do lucro, ao contrário, vamos torná-lo grande, para que com alta probabilidade a perda seja atingida e o lucro não.

Nos demais parâmetros vamos desativar o funcionamento apenas na abertura da barra, para que ao ativar o modo de modelagem de todos os ticks (Every tick) o EA execute todas as ações necessárias em cada tick, e não apenas no início da barra de um minuto:


O teste será executado em um intervalo curto de um dia (2022.01.03). Filtrando as mensagens do log, vamos destacar apenas as linhas que são exibidas ao atingir a perda indicada de $20:

2022.01.03 17:11:33 CVirtualCloseManager::CheckLimits CLOSE LOSS Profit = -33.13 OverallProfit = -20.06 (-20.00)
2022.01.03 17:30:39 CVirtualCloseManager::CheckLimits CLOSE LOSS Profit = -20.51 OverallProfit = -20.51 (-20.00)
2022.01.03 19:13:31 CVirtualCloseManager::CheckLimits CLOSE LOSS Profit = -21.20 OverallProfit = -20.11 (-20.00)

Vemos que isso aconteceu três vezes durante o dia de teste e, no modo de todos os ticks, o lucro total (OverallProfit) no qual ocorre o acionamento do fechamento das posições ao atingir a perda definida fica muito mais próximo do valor especificado nos parâmetros. 

Observe que no primeiro registro do log apresentado acima existe a seguinte parte:

Profit = -33.13

Esse é o valor do lucro atual das posições abertas (lucro negativo é justamente a perda). Nesse caso ele difere do valor de -$20 porque inicialmente foram fechadas algumas posições com lucro de aproximadamente $13. Portanto a perda de $20 em relação ao saldo base inicial da conta foi atingida exatamente com esse valor de lucro das posições abertas.

Assim, os testes iniciais mostraram que o gerenciador de fechamento desenvolvido já é capaz de executar a parte básica do seu trabalho.


Considerações finais

Aqui faremos uma pequena pausa e continuaremos o desenvolvimento do gerenciador de fechamento em uma das próximas partes. Nos planos de desenvolvimento adicional da sua funcionalidade permanece прежде всего a adição do trailing do lucro das posições abertas e a possibilidade de definir um nível de breakeven.

As melhorias não se limitam a isso. Por exemplo, atualmente o gerenciador de fechamento verifica a conclusão do fechamento de todas as posições simplesmente aguardando até que o número de posições abertas se torne igual a zero. Porém isso também pode ocorrer mesmo com a existência de posições virtuais abertas, portanto vamos analisar se aqui não será necessário aplicar algum método de verificação mais confiável. Também talvez precisemos implementar a interação entre o gerenciador de risco e o gerenciador de fechamento: ao fechar posições será necessário atualizar o estado do gerenciador de risco e vice-versa.

Mesmo assim a primeira versão já foi criada e o próximo passo não precisará começar do zero.

Obrigado pela atenção e até a próxima!


Aviso importante

Todos os resultados apresentados neste artigo e nos anteriores deste ciclo baseiam-se exclusivamente em testes realizados com dados históricos e não constituem garantia de qualquer tipo de rentabilidade futura. O trabalho realizado no âmbito deste projeto tem caráter de pesquisa. Todos os resultados publicados podem ser utilizados livremente, por conta e risco dos próprios usuários.

Conteúdo do arquivo compactado

#
 Nome
Versão  Descrição  Últimas alterações
  SimpleCandles   Pasta de trabalho do projeto (deve estar dentro de MQL5/Experts)  
SimpleCandles.mq5
1.01
EA final para funcionamento paralelo de vários grupos de estratégias de modelo. Os parâmetros serão obtidos da biblioteca integrada de grupos.
Parte 25
  Optimization
  Pasta dos EAs de otimização do projeto  
2 CreateProject.mq5 1.02 EA-script para criação de projeto com etapas, trabalhos e tarefas de otimização.
Parte 25
3 Optimization.mq5 1.00
EA para otimização automática de projetos
 
4 Stage1.mq5 1.02
EA de otimização de uma única instância de estratégia de negociação (Etapa 1)
Parte 25
5 Stage2.mq5 1.01
EA de otimização de grupo de instâncias de estratégias de negociação (Etapa 2)
Parte 25
6 Stage3.mq5 1.01
EA que grava o grupo de estratégias normalizadas formado no banco de dados do Expert Advisor com o nome especificado.  Parte 25
  Strategies   Pasta das estratégias do projeto
Parte 25
7 SimpleCandlesStrategy.mqh
1.01
Classe da estratégia de negociação SimpleCandles
Parte 25
  Include/Adwizard   Pasta da biblioteca Adwizard  
  Base
  Classes base das quais herdam outras classes do projeto    
8       Advisor.mqh 1.04 Classe base do EA Parte 10
9       Factorable.mqh
1.06
Classe base de objetos criados a partir de uma string
Parte 28
10       FactorableCreator.mqh
1.00 Classe de criadores que associam nomes a construtores estáticos de classes derivadas de CFactorable Parte 24
11       Interface.mqh 1.01
Classe base para visualização de diversos objetos
Parte 4
12       Receiver.mqh
1.04  Classe base para conversão de volumes abertos em posições de mercado
Parte 12
13       Strategy.mqh
1.04
Classe base para estratégia de negociação
Parte 10
  Database
  Arquivos para trabalho com todos os tipos de bancos de dados utilizados pelos EAs do projeto
 
14       Database.mqh 1.12 Classe para trabalhar com banco de dados Parte 25
15       db.adv.schema.sql 1.00
Esquema do banco de dados do EA final Parte 22
16       db.cut.schema.sql
1.00 Esquema do banco de dados reduzido de otimização
Parte 22
17       db.opt.schema.sql
1.05  Esquema do banco de dados de otimização
Parte 22
18       Storage.mqh   1.01
Classe para trabalhar com armazenamento do tipo Key-Value para o EA final no banco de dados do expert
Parte 23
  Experts
  Arquivos com as partes comuns dos EAs de diferentes tipos utilizados
 
19       Expert.mqh  1.24 Arquivo de biblioteca para o EA final. Os parâmetros de grupos podem ser obtidos do banco de dados do expert
Parte 28
20       Optimization.mqh  1.04 Arquivo de biblioteca para o EA que gerencia a execução de tarefas de otimização
Parte 23
21       Stage1.mqh
1.19 Arquivo de biblioteca para o EA de otimização de uma instância individual de estratégia de negociação (Etapa 1)
Parte 23
22       Stage2.mqh 1.04 Arquivo de biblioteca para o EA de otimização de um grupo de instâncias de estratégias de negociação (Etapa 2)   Parte 23/td>
23       Stage3.mqh
1.04 Arquivo de biblioteca para o EA que grava o grupo de estratégias normalizadas formado no banco de dados do Expert com o nome especificado. Parte 23
  Optimization
  Classes responsáveis pelo funcionamento da otimização automática
 
24       OptimizationJob.mqh 1.00 Classe para a etapa de execução do projeto de otimização
Parte 25
25       OptimizationProject.mqh 1.00 Classe para o projeto de otimização Parte 25
26       OptimizationStage.mqh 1.00 Classe para a etapa do projeto de otimização Parte 25
27       OptimizationTask.mqh 1.00 Classe para a tarefa de otimização (para criação) Parte 25
28       Optimizer.mqh
1.03  Classe para o gerenciador da otimização automática de projetos
Parte 22
29       OptimizerTask.mqh
1.03
Classe para a tarefa de otimização (para o pipeline)
Parte 22
  Strategies    Exemplos de estratégias de negociação usadas para demonstrar o funcionamento do projeto
 
24       HistoryStrategy.mqh 
1.00 Classe da estratégia de negociação para reprodução do histórico de operações
Parte 16
25       SimpleVolumesStrategy.mqh
1.11
Classe da estratégia de negociação com uso de volumes de ticks
Parte 22
  Utils
  Utilitários auxiliares e macros para reduzir o código

26        ConsoleDialog.mqh 1.01 Classe para exibição de informações textuais no gráfico Parte 28
26       ExpertHistory.mqh 1.00 Classe para exportar o histórico de ordens para arquivo Parte 16
27       Macros.mqh 1.07 Macros úteis para operações com arrays Parte 26
28        MTTester.mqh 
Arquivo para trabalhar com o testador de estratégias da biblioteca MultiTester
Parte 28
29       NewBarEvent.mqh 1.00  Classe para detecção de novo candle para um símbolo específico  Parte 8
30       SymbolsMonitor.mqh  1.01 Classe para obtenção de informações sobre instrumentos de negociação (símbolos) Parte 28
  Virtual
  Classes para criação de diversos objetos, unificados pelo uso do sistema de ordens e posições virtuais de trading

31       Money.mqh 1.01  Classe base para gerenciamento de capital
Parte 12
32       TesterHandler.mqh  1.07 Classe para tratamento de eventos de otimização  Parte 23
33       VirtualAdvisor.mqh  1.12  Classe do expert que trabalha com posições (ordens) virtuais Parte 28
34       VirtualChartOrder.mqh  1.02  Classe da posição virtual gráfica Parte 28
35       VirtualCloseManager.mqh 1.00 Classe do gerenciador de fechamento Parte 28
36       VirtualHistoryAdvisor.mqh 1.00  Classe do expert para reprodução do histórico de ordens  Parte 16
37       VirtualInterface.mqh  1.00  Classe da interface gráfica do EA  Parte 4
38       VirtualOrder.mqh 1.09  Classe de ordens e posições virtuais  Parte 22
39       VirtualReceiver.mqh 1.04 Classe que converte volumes abertos em posições de mercado (receptor)  Parte 23
40       VirtualRiskManager.mqh  1.06 Classe de gerenciamento de risco (risk manager)  Parte 28
41       VirtualStrategy.mqh 1.09  Classe da estratégia de negociação com posições virtuais  Parte 23
42       VirtualStrategyGroup.mqh  1.04  Classe de grupo de estratégias de negociação ou grupos de estratégias Parte 28
43       VirtualSymbolReceiver.mqh  1.00 Classe do receptor simbólico  Parte 3
  Common/Files   Pasta comum de dados dos terminais MetaTrader 5  
44 SimpleCandles-27183.test.db.sqlite Banco de dados do EA final Parte 25

Também o código-fonte está disponível nos repositórios públicos SimpleCandles e Adwizard


Como utilizar os repositórios abertos

Devido à transição gradual para o novo repositório Algo Forge, ainda não decidimos completamente qual será a melhor e mais conveniente forma de utilizá-lo. A limitação do editor MetaEditor ao uso de apenas um repositório correspondente à pasta raiz MQL5 não é particularmente conveniente. Os outros repositórios estarão disponíveis apenas como subpastas da pasta Shared Projects, o que melhora um pouco a situação, mas não o bastante para adotarmos essa abordagem de imediato.

Além disso, durante o processo de transição o único repositório principal foi criado em nome do superadministrador duas vezes, e na segunda vez sua criação por algum motivo destruiu outros repositórios adicionais do usuário que haviam sido criados anteriormente. Felizmente, tendo uma cópia local do repositório é possível enviá-la novamente para o servidor, porém esse tipo de ação forçada não é particularmente desejável. Portanto por enquanto vamos aguardar o desenvolvimento adicional das funcionalidades do editor MetaEditor para trabalhar com repositórios sem utilizá-los diretamente.

Não há nada complicado nisso: continuando a trabalhar no MetaEditor, simplesmente transferiremos por enquanto todas as operações relacionadas ao repositório para aplicações externas.

Por exemplo, para obter no computador local uma cópia de todos os arquivos de código deste artigo é possível executar o seguinte script no console, tendo previamente definido como pasta atual alguma pasta dentro da pasta MQL5 do terminal MetaTrader necessário:

# Criamos uma pasta para o projeto
mkdir SimpleCandles

# Entramos na pasta do projeto
cd SimpleCandles

# Clonamos o repositório do projeto na pasta atual
git clone https://forge.mql5.io/antekov/SimpleCandles.git .

# Mudamos o repositório para o branch necessário (para este artigo "article-17608-close-manager")
git checkout article-17608-close-manager

# Verificamos se realmente mudamos para o branch
git status

# Criamos a pasta para a parte de biblioteca
mkdir Include

# Entramos nela
cd Include

# Clonamos o repositório Adwizard na pasta da biblioteca
git clone https://forge.mql5.io/antekov/Adwizard.git 

# Entramos na pasta criada
cd Adwizard

# Mudamos o repositório para o branch necessário (para este artigo "article-17608-close-manager")
git checkout article-17608-close-manager

# Verificamos se realmente mudamos para o branch
git status

# Voltamos para a pasta inicial do projeto dois níveis acima
cd ./../..

A única coisa que não está presente nesses repositórios é o arquivo com o banco de dados do EA final, pois o código do repositório permite obtê-lo executando a otimização automática. Porém, se necessário, esse arquivo pode ser obtido a partir do arquivo compactado do artigo e colocado na pasta comum dos terminais dentro da pasta Files.

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/17608

Arquivos anexados |
SimpleCandles.zip (494.95 KB)
Redes neurais em trading: Previsão probabilística de série temporal (K2VAE) Redes neurais em trading: Previsão probabilística de série temporal (K2VAE)
Apresentamos a implementação original do framework K²VAE, um modelo flexível capaz de aproximar linearmente dinâmicas complexas no espaço latente. Este artigo mostra como implementar os componentes principais na linguagem MQL5, incluindo matrizes parametrizadas e seu gerenciamento fora das camadas padrão de redes neurais. Este material será útil para todos os que procuram uma abordagem prática para criar modelos interpretáveis de séries temporais.
Componentes View e Controller para tabelas no paradigma MVC em MQL5: Contêineres Componentes View e Controller para tabelas no paradigma MVC em MQL5: Contêineres
No artigo será analisada a criação do elemento de controle "Contêiner" com a possibilidade de rolar o seu conteúdo. Durante o processo, serão aperfeiçoadas classes já prontas de elementos de controle da biblioteca gráfica.
Automatizando Estratégias de Trading em MQL5 (Parte 10): Desenvolvendo a Estratégia Trend Flat Momentum Automatizando Estratégias de Trading em MQL5 (Parte 10): Desenvolvendo a Estratégia Trend Flat Momentum
Neste artigo, desenvolvemos um Expert Advisor em MQL5 para a estratégia Trend Flat Momentum. Combinamos um cruzamento de duas médias móveis com filtros de momentum RSI e CCI para gerar sinais de negociação. Também abordamos backtesting e possíveis melhorias para desempenho em condições reais de mercado.
Desenvolvimento do Toolkit de Análise de Price Action (Parte 15): Introduzindo a Teoria dos Quartos (I) — Script Quarters Drawer Desenvolvimento do Toolkit de Análise de Price Action (Parte 15): Introduzindo a Teoria dos Quartos (I) — Script Quarters Drawer
Pontos de suporte e resistência são níveis críticos que sinalizam possíveis reversões e continuações de tendência. Embora identificar esses níveis possa ser desafiador, uma vez que você os localiza, estará bem preparado para navegar no mercado. Para obter ajuda adicional, confira a ferramenta Quarters Drawer apresentada neste artigo; ela ajudará você a identificar tanto níveis primários quanto secundários de suporte e resistência.