English Русский Español Deutsch 日本語 Português
preview
开发多币种 EA(第 28 部分):添加平仓管理器

开发多币种 EA(第 28 部分):添加平仓管理器

MetaTrader 5示例 |
116 0
Yuriy Bykov
Yuriy Bykov

概述

第 12 部分中,我们在多币种 EA 中增加了一个风险管理模块,以限制每日和总体回撤。它不会增加利润,但对于在不利条件下保护资金至关重要。它基于自营交易规则,设置灵活:资金回撤可以按账户货币金额、按余额百分比,或按日初基准来计算。

该模块以 CVirtualRiskManager 类的形式实现,具有用于跟踪余额、利润和检查限额的方法。同时,系统还提供了一种锁定利润的功能:一旦达到目标,所有持仓将被平仓,交易停止。

对于普通交易账户(相对于 prop 规则账户),最好在达到盈利目标后自动重新开始交易。目前,这需要人工干预。现在也该将这一过程自动化了。

在达到目标利润时,我考虑了两种重启交易策略的方案:

  • 扩大现有风险管理器
  • 创建一个单独的模块。

我选择了第二条路径,因为当前的风险管理工具独立于策略运作:它只平掉真实仓位,而不影响虚拟仓位。改变这一逻辑会使架构变得复杂,并破坏模块独立性。

风险管理器还会带来额外的测试开销,因此最好将新功能移至一个单独的模块中 — 即使风险管理器未运行,该模块也能使用。

新的目标是开发一个模块,该模块能够在满足特定条件(盈利、亏损、时间等)时重新启动所有策略,而无需依赖交易历史,也无需人工干预。我将把这个新模块称为平仓管理器,因为它是一个单独的可选模块,但引入该模块后,策略结果可能得到改善,它可以管理完全平仓所有真实和虚拟仓位的过程。


初始要求

让我们更明确地阐述平仓管理器的职责和参数。 

平仓管理器应该:

  1. 止盈,即当达到指定利润时平掉所有虚拟仓位。在这种情况下,实际仓位也会自动平仓。我们引入三个参数来实现这一目标:
    • 基准余额。交易账户中的资金数额,作为计算盈亏的参考余额。
    • 利润计算方法。它可以取几个可能的值之一,例如,可以是基准余额的百分比,也可以是存款货币的固定金额。
    • 利润值。使用所选方法计算利润时所用的数值。
  2. 限制亏损,即当亏损达到指定金额时平掉所有虚拟仓位。该过程也需要三个参数,其中一到两个可以与获利参数共享:
    • 基准余额。交易账户中的资金数额,作为计算盈亏的参考余额。
    • 亏损计算方法。相对于利润的计算方法,它也可以取几个可能的值之一。
    • 亏损值。使用所选方法计算亏损时所用的数值。
  3. 启用追踪盈利功能 达到指定利润后,不立即平仓,而是记录一个较低的利润回撤平仓线;若利润继续增长,该平仓线也随之上移。如果利润增长,那么这个水平也应该提高。这种增长可以是连续的,也可以是分阶段进行的,每次增加一定的幅度。此过程可以添加以下参数:
    • 启用追踪盈利(是/否)。
    • 水平设定方法。在此参数中,我们可以选择设置追踪启用水平的首选方法。例如,可以将该水平设置为固定利润的百分比,或者设置为交易账户货币的绝对值。
    • 追踪起始水平用于计算所选方法追踪起始水平的数字。
    • 步长。 移动跟踪阈值的水平。计算方法与追踪起始水平的计算方法相同。
  4. 保本功能启用水平  当利润达到此值时,我们将保留一定的小额正利润水平,并在此水平时平仓。随着利润的进一步增加,这一水平(与追踪盈利水平不同)将不会上升。控制此过程的参数如下:
    • 启用保本功能(是/否)。
    • 水平设定方法。此参数与追踪盈利的同名参数类似,意味着它也可以是相对值或绝对值。
    • 保本功能启用水平。 用于计算所选方法保本功能启用水平的数值。
现在,让我们先集中精力讨论这个基本功能,它还有进一步开发的空间。在实现过程中,我们可能需要向参数中添加一些内容,或者以其他方式改变其构成。但最初我们将重点关注任务的描述。


项目仓库

第 25 部分中,我们添加了一个新策略,并研究了如何创建一个项目来自动优化所选策略,并创建一个最终的 EA,其中包含具有不同参数的多个交易策略实例。整个代码分为两部分库和项目。对于库部分,第 26 部分已经介绍了 Adwizard 公共代码库,该代码库位于 MQL5 Algo Forge 存储中。不过,项目部分的仓库目前还没有建立。

让我们解决这个问题并创建新的  SimpleCandles 存储库。该存储库将包含使用同名策略创建最终 EA 的项目部分。在 main 分支之外,我们还将引入一个名为 develop 的分支。如果本项目涉及多篇文章,那么与不同文章相关的编辑将分布在从 develop 分支生成的不同分支中。编辑完成后,它们将被合并回 developmain 分支。

我们创建一个本地文件夹来存放项目文件夹,例如: MQL5/Experts/Articles/17608 。我们将此存储库克隆到所选文件夹中,并在其中创建 Include 文件夹。在这个文件夹中,我们将放置本项目所依赖的库部分的存储库。Include 文件夹会接收 Adwizard 库存储库的克隆。

最终,我们在终端文件夹中得到了大致如下的文件夹结构:

图 1.克隆项目和库部分后,项目存储库中的文件夹结构

Adwizard 存储库的克隆文件夹中, 切换到 develop 分支。该分支将作为所有文章的公共分支。然而,在我们进行此项目时,我们将对 Adwizard 库进行更改,因此在此存储库中,我们将基于 develop 分支创建一个新分支。

之后,在 SimpleCandles 项目存储库中为本文对应的开发工作创建一个单独的分支,并开始开发。


准备库代码

让我们为平仓管理器的实现做好准备。首先,我们应当注意到,最新的 MetaTrader 版本已添加了更严格的变量类型检查,这就是为什么之前编译的代码现在会生成以下类型的错误:

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

幸运的是,这种情况在已使用的代码中仅出现了一次,并通过更改数组类型得到了修复: 

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);
  }

然而,在下次终端更新后,此文件被 MultiTester 库中的最新版本完全替换,以修复由其他原因导致的错误行为。

下一个变化与平仓管理器需要启动所有持仓的平仓有关。让我们在 CVirtualAdvisor EA 类中添加一个单独的方法来平掉所有仓位,以便平仓管理器在必要时可以调用它。

为了实现这种方法,我们已经具备了所需的一切:从 CVirtualStrategy 继承的每个策略都有一个关闭所有虚拟仓位的方法。因此,在 EA 类中,我们只需要对每个策略调用此方法:

//+------------------------------------------------------------------+
//| Close positions of all strategies                                |
//+------------------------------------------------------------------+
void CVirtualAdvisor::Close(void) {
// For all strategies, we call the method for closing virtual positions
   FOREACH(m_strategies) ((CVirtualStrategy *)m_strategies[i]).Close();
}

第 27 部分,我们创建了一个组件,用于在窗口中显示多行文本,该组件可扩展以覆盖 EA 所附加的整个图表。该组件是另一个项目的一部分,但在这里对我们也有用。因此,让我们将其移动到 Adwizard库中,将包含 CConsoleDialog 类的文件放置在 Adwizard/Utils 文件夹中。要使用它,我们需要在 EA 类的 Adwizard/Experts/Expert.mqh 文件中添加此类的对象创建:

CConsoleDialog      *dialog;             // Dialog for displaying text with results

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

// Create and launch a dialog to display the results
   dialog = new CConsoleDialog();
   dialog.Create(__NAME__ + ":" + (string) magic_);
   dialog.Run();

// Successful initialization
   return(INIT_SUCCEEDED);
}

在同一个文件中处理新逐笔报价(tick)的函数中,我们将为该对象添加新文本的设置。我们将通过调用 CVirtualAdvisor 类的 Text() 方法来接收文本,我们将在后面实现该方法:

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

// ...

// Display text with information about the EA operation
   if (IsNewBar(Symbol(), PERIOD_M1)) {
      dialog.Text(expert.Text());
   }
}

为了防止在文本背景上绘制开立虚拟仓位的线条,我们将暂时通过将 CVirtualChartOrder::Show() 方法设为空来禁止其显示:

//+------------------------------------------------------------------+
//| Show virtual position (order)                                    |
//+------------------------------------------------------------------+
void CVirtualChartOrder::Show() {
   return;

   // ...
}


CFactorable 所有子类的 IsActive 属性

当对包含加密货币的交易工具进行优化,而最终 EA 又是在不支持加密货币的经纪商环境中运行时,在发布最终 EA 时可能会出现错误。该错误涉及尝试获取未包含在市场观察中的交易品种的交易历史和属性。在这种情况下,如果最终 EA 包含大量针对可用交易品种的交易策略实例,我们可以简单地禁用未包含在市场观察中的交易工具的策略。

目前,所有交易策略都是 CFactorable 类的派生类,这使得可以通过初始化字符串来创建这些策略的对象。此类提供了初始化字符串可能不完全正确的可能性。如果是这样,该对象以及来自公共初始化字符串的所有先前对象都将被视为无效。在这种情况下,EA 将无法初始化并继续运行。

我们希望的是,当初始化字符串中出现某种类型的“错误”时,能够让我们忽略初始化字符串的一部分,最终从完整的初始化字符串中创建一个 EA 对象。为了实现这一点,让我们在 CFactorable 类中添加一个名为 m_isActive 的新属性,以及一个用于读取其值的 IsActive() 方法:

//+------------------------------------------------------------------+
//| Base class of objects created from a string                      |
//+------------------------------------------------------------------+
class CFactorable {
private:
   // ...

protected:
   // ...
   
   bool              m_isActive; // Is the object active?

   // ...

public:
   // ...

   bool              IsActive();                         // Is the object active?

   // ...
};

对于某些类,如 CVirtualRiskManager 风险管理器类,已经存在这样的属性,因此,在这些类中,我们将移除其声明,因为该声明将在基类中定义。这也适用于未来的平仓管理器类,该类也将使用此属性来检查其是否处于活动状态。

同时,我们通过在 CVirtualAdvisor 类构造函数中启动 EA 时检查风险管理器和平仓管理器是否存在,使得在初始化字符串中指定风险管理器和平仓管理器成为可选操作:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CVirtualAdvisor::CVirtualAdvisor(string p_params) {
// Save the initialization string
   m_params = p_params;

// Read the initialization string of the strategy group object
   string groupParams = ReadObject(p_params);

// Read the initialization string of the risk manager object
   string riskManagerParams = NULL;

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

// Read the initialization string of the closing manager object
   string closeManagerParams = NULL;
   if(IsObjectOf(p_params, "CVirtualCloseManager")) {
      closeManagerParams = ReadObject(p_params);
   }

// Read the magic number
   ulong p_magic = ReadLong(p_params);

// Read the EA name
   string p_name = ReadString(p_params);

// Read the work flag only at the bar opening
   m_useOnlyNewBar = (bool) ReadLong(p_params);

// If there are no read errors,
   if(IsValid()) {
// Create a strategy group
      CREATE(CVirtualStrategyGroup, p_group, groupParams);

      // Initialize the symbol monitor with a static symbol monitor
      m_symbols = CSymbolsMonitor::Instance();

      // Initialize the receiver with the static receiver
      m_receiver = CVirtualReceiver::Instance(p_magic);

      // Initialize the interface with the static interface
      m_interface = CVirtualInterface::Instance(p_magic);

      // Form the name of the EA database file for saving the state from the EA name and parameters
      m_fileName = FileName(p_name, p_magic);

      // Save the work (test) start time
      m_fromDate = TimeCurrent();

      // Reset the last save time
      m_lastSaveTime = 0;

      // Add the contents of the group to the EA
      Add(p_group);

      // Remove the group object
      delete p_group;

      // Create the risk manager object
      if(riskManagerParams != NULL) {
         m_riskManager = NEW(riskManagerParams);
      }

      // Create the closing manager object
      if(closeManagerParams != NULL) {
         m_closeManager = NEW(closeManagerParams);
         m_closeManager.Expert(&this);
      }
   }
}

完成这些修改后,让我们继续进行主要部分 创建平仓管理器。


创建平仓管理器

首先,让我们重点介绍一下平仓管理器可能处于的几种状态。正常情况下,既没有达到计划的利润,也没有达到最大的亏损。在此状态下,平仓管理器只需等待向后续几种状态中的一种过渡。当达到指定的利润或亏损时,将向两个相应的状态过渡。在这些状态下,平仓管理器应平掉所有仓位,记住指定的利润和亏损的新水平,并返回正常状态。

如果启用追踪止盈,则当达到指定的盈利水平时,平仓管理器将切换到另一种状态。而要过渡回正常状态,则需要执行更复杂的操作,因此我们暂时不对其进行详细描述。

我们将所有状态实现为 ENUM_CM_STATE 枚举类型。

为了定义计算计划损益的方法,我们还将创建两种不同的枚举类型:ENUM_CM_CALC_LOSSENUM_CM_CALC_PROFIT 。让我们考虑两个选项:一是以货币形式表示的固定值,二是以某一基准余额的百分比表示的相对值。

// Possible states of the closing manager
enum ENUM_CM_STATE {
   CM_STATE_OK,            // Limits are not exceeded
   CM_STATE_LOSS,          // Overall limit exceeded
   CM_STATE_PROFIT,        // Total profit reached
   CM_STATE_TRAIL_PROFIT   // Profit trailing
};

// Possible methods for calculating total loss
enum ENUM_CM_CALC_LOSS {
   CM_CALC_LOSS_MONEY_BB,           // [$] Fixed Money
   CM_CALC_LOSS_PERCENT_BB,         // [%] of Base Balance
};

// Possible methods for calculating total profit
enum ENUM_CM_CALC_PROFIT {
   CM_CALC_PROFIT_MONEY_BB,           // [$] Fixed Money
   CM_CALC_PROFIT_PERCENT_BB,         // [%] of Base Balance
};

平仓管理器类本身将继承自 CFactorable 基类,用于提供从初始化字符串创建平仓管理器对象的功能。同时,它将立即继承一个活动属性,以便轻松启用或禁用平仓管理器。

为了执行这项任务,平仓管理器需要记住基准余额水平,由此计算最终的利润或亏损。在触发止盈或止损并完成全部平仓后,该基准余额也应更新为当前账户余额。这就是该参数与风险管理器中同名参数之间的区别。那里的基准余额水平始终保持不变。

下一组属性将用于选择计划损益的计算方法以及计算过程本身。它们将在 LossMoney()ProfitMoney() 计算方法中使用,以货币形式返回值。

要平仓,平仓管理器应该能够请求 EA 对象平仓。因此,我们将把指向 EA 对象的指针和设置该指针的方法添加到平仓管理器的属性列表中。

我们将添加一个属性来存储平仓管理器对象的当前状态。

基于 CFactorable 进行创建需要将构造函数放在私有区域,并添加第 24 部分中描述的两个特殊宏。

因此,我们得到了类似如下的平仓管理器类的描述:

//+------------------------------------------------------------------+
//| Closing manager class (profit and loss taking)                   |
//+------------------------------------------------------------------+
class CVirtualCloseManager : public CFactorable {
protected:
// Main constructor parameters
   double            m_baseBalance;          // Base balance

   ENUM_CM_CALC_LOSS m_calcLossLimit;        // Method of calculating the maximum overall loss
   double            m_maxLossLimit;         // Parameter of calculating the maximum total loss

   ENUM_CM_CALC_PROFIT m_calcProfitLimit;    // Method for calculating maximum overall profit
   double            m_maxProfitLimit;       // Parameter for calculating the maximum overall profit

   CVirtualAdvisor*  m_expert;               // Pointer to the EA object

// Current state
   ENUM_CM_STATE     m_state;                // State

// Updated values
   double            m_balance;              // Current balance
   double            m_equity;               // Current equity
   double            m_profit;               // Current floating profit
   double            m_overallProfit;        // Current total profit relative to base balance

// Protected methods
   double            LossMoney();            // Maximum total loss
   double            ProfitMoney();          // Maximum profit

   void              UpdateProfit();         // Update current profit values
   void              CheckLimits();          // Check whether acceptable profit/loss levels have been achieved 
  
   CVirtualCloseManager(string p_params);    // Private constructor

public:
   STATIC_CONSTRUCTOR(CVirtualCloseManager); // Static object creation method
   virtual void      Tick();                 // Handle tick in the closing manager

   virtual string    Text();                 // Information about the current state

   // Bind the EA to the closing manager
   void              Expert(CVirtualAdvisor* p_expert);

   virtual bool      Save();      // Save status
   virtual bool      Load();      // Load status

   virtual string    operator~() override;   // Convert object to string
};

REGISTER_FACTORABLE_CLASS(CVirtualCloseManager); // Register a new CFactorable child

我们来看两个主要的类方法:构造函数和 tick 处理方法。

在构造函数中,我们像往常一样,从初始化字符串中按顺序读取参数值,并将它们分配给相应的属性,将当前状态设置为正常,更新当前利润值,并且如果当前余额值未直接设置,则将其保存为基准值:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CVirtualCloseManager::CVirtualCloseManager(string p_params) {
// Save the initialization string
   m_params = p_params;

// Read the initialization string and set the property values
   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);

// Set the state: Limits are not exceeded
   m_state = CM_STATE_OK;

// Update the current profit values
   UpdateProfit();

// Adjust the base balance if it is not set
   if(m_baseBalance == 0) {
      m_baseBalance = m_balance;
   }
}

在基本的 tick 处理方法中,我们分析当前状态:如果管理器处于正常状态,则检查是否达到目标盈亏;否则执行平仓并恢复到正常状态:

//+------------------------------------------------------------------+
//| Tick processing in the risk manager                              |
//+------------------------------------------------------------------+
void CVirtualCloseManager::Tick() {
// If the risk manager is inactive, exit
   if(!m_isActive) {
      return;
   }

// Update the current profit values
   UpdateProfit();

// If the manager is in the trailing state,
   if(m_state == CM_STATE_TRAIL_PROFIT) {
      // immediately take the profit
      // switching the manager to the corresponding state
      if(true) {
         m_state = CM_STATE_PROFIT;
      }
   }

// If the manager is in normal condition,
   if(m_state == CM_STATE_OK) {
      // Check for exceeding loss and profit limits
      CheckLimits();
   }

// If the manager is in a state of achieved loss or profit,
   if(m_state == CM_STATE_LOSS || m_state == CM_STATE_PROFIT) {
      // Close all positions
      m_expert.Close();

      // If all positions are closed,
      if(PositionsTotal() == 0) {
         // Switch to normal state
         m_state = CM_STATE_OK;
         
         // Update the base balance value
         m_baseBalance = m_balance;
      } else {
         // Wait for all positions to close
      }

      // Save the EA state
      m_expert.Save();
   }
}

首先,我们决定仅限于使用平仓管理器的这一功能,因此暂时不启用利润追踪状态。


传递输入参数

创建平仓管理器类之后,我们需要将其连接到 EA。为此,我们需要添加输入参数,以便我们可以控制平仓管理器的初始化字符串的创建。这需要在 Adwizard/Experts/Expert.mqh 文件中完成:

// ...

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input group "::: Use a strategy group"
sinput int        groupId_       = 0;     // - ID of the group from the new library (0 - last)
sinput bool       useAutoUpdate_ = true;  // - Use auto update?

input group "::: Money management"
sinput double expectedDrawdown_  = 10;    // - Maximum risk (%)
sinput double fixedBalance_      = 10000; // - Used deposit (0 - use all) in the account currency
input  double scale_             = 1.00;  // - Group scaling multiplier

input group ":::  Closing manager"
input bool        cmIsActive_                = true;  // - Active?
input double      cmStartBaseBalance_        = 0;     // - Basic balance
input ENUM_CM_CALC_LOSS
cmCalcLossLimit_           = CM_CALC_LOSS_MONEY_BB;   // - Loss calculation method
input double      cmLossLimit_       = 100;           // - Threshold loss value
input ENUM_CM_CALC_PROFIT
cmCalcProfitLimit_                    = CM_CALC_PROFIT_MONEY_BB;  // - Method for calculating total profit
input double      cmProfitLimit_   = 1000000;                     // - Profit target

// ...

编译最终 EA 会包含此文件,因此添加的输入参数将在 EA 中可用。默认情况下,我们将止盈和止损值设置为以货币(存款货币)形式设置。我们仍然需要自己选择值,所以默认值中指定的内容目前并不重要。


初步测试

让我们看看得到了什么。首先,让我们在不考虑已获利润的情况下,检查平仓机制是否正常运行。如果一切正常,那么在下一阶段,我们可以开始优化获得的利润。

让我们使用第 25 部分中获得的最终 EA 数据库,在测试器中启动最终 EA。然后,我们对持续1年的几个时间段进行了加速优化,并获得了存储在 strategy_groups 表中的 12 组策略:

数据库文件名为SimpleCandles-27183.test.db.sqlite。 为了让最终 EA 能够使用此数据库,该文件应位于MetaTrader 5终端的公共数据文件夹中的 Files 子文件夹中。 此外,最终 EA 应命名为 SimpleCandles.ex5,且输入中的幻数值应保持为 27183

我们首先启动 EA,不使用平仓管理器,使用 id_group=20 的第一组策略。为此,让我们为输入设置以下值:

我们将使用与自动优化执行期间相同的时间段作为测试时间段,即 2022 年全年。我们得到以下结果:

图 2.2022 年最终 EA(id_group=20,无平仓管理器)的结果

如您所见,优化为不同的简单交易策略实例找到了相当不错的参数组合,以确保在给定区间内获得显著利润,同时保持在指定的 10% 回撤范围内。

现在,让我们启用平仓管理器,设定一个较小的止盈目标,例如 10 美元:


让我们以可视化测试模式运行 EA。由于我们在最终 EA 中增加了操作数据的显示功能,在此模式下,我们可以从 EA 数据库中看到 ID 为 20 的组中使用了哪些交易品种以及多少策略(三个交易品种:GBPUSD、EURUSD、EURGBP和 48 种策略),以及平仓管理器当前的基准余额、盈亏平仓目标水平。

图 3.启动 EA 可视化测试,并启用平仓管理器。

图 3 显示,平仓管理器的基准余额已达到 10009.89 美元,这意味着达到 10 美元的目标利润后,所有仓位都已平仓。

我们在日志中看到以下这行:

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

当总利润(OverallProfit = 10.54)相对于初始基准余额 10,000 美元超过 10 美元时,平仓管理器被触发。由于测试模式仅在每分钟 K 线的开始(1 分钟 OHLC)进行,因此所有仓位的平仓过程延续到了相邻的两个分钟柱上。因此记录的新基准水平略低于10,010美元。启用每个 tick 模式后,我们不再观察到此类差异。

现在我们来测试一下止损平仓管理器。让我们设定一个较小的亏损承受值,比如 20 美元,同时设定一个较大的获利值,这样我们很可能会承受亏损,而不是获利。

在其他参数设置中,我们将禁用仅在开盘时执行操作的功能,这样,当启用每个 tick 的模拟模式时,EA 会在每个时间段执行所有所需操作,而不仅仅是在分钟 K 线开始时:


我们将以一天为间隔进行测试(2022.01.03)。通过筛选日志消息,我们只选择在损失达到指定金额 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)

我们可以看到,在测试当天,这种情况发生了三次。在每个 tick 模式下,总利润(Overall Profit)在达到指定亏损时触发平仓,并且与参数中指定的值非常接近。 

请注意,上面的第一条日志条目包含以下部分:

Profit = -33.13

这是当前持仓的盈利值(负盈利即亏损)。在这种情况下,它与 -20 美元的值不同,因为最初平仓了几个仓位,盈利约 13 美元。因此,相对于初始基准余额达到 -20 美元总收益时,未平仓持仓的浮动盈亏正好可能是 -33.13 美元,因为此前已有约 13 美元的已实现利润。

因此,初步测试表明,所开发的平仓管理器已经能够执行其基本工作部分。


结论

我们在此稍作休息,并将在下一部分中继续进一步开发平仓管理器。进一步开发其功能的计划主要包括为持仓添加利润追踪功能和设置保本功能启用水平的功能。

改进不止于此。例如,目前的平仓管理器通过简单地等待持仓数量降至零来检查所有持仓是否已平仓。但如果存在未平仓的虚拟仓位,也会出现这种情况,因此让我们看看是否需要在此处使用更可靠的验证方法。我们可能还必须安排风险管理器和平仓管理器之间的交互:在平仓时,应更新风险管理器的状态,反之亦然。

尽管如此,第一个版本已经完成,下一步将不会从头开始。

感谢您的关注!期待很快与您见面!


重要警告

本文和本系列之前的所有文章中的所有结果仅基于历史测试数据,并不保证未来会有任何利润。该项目中的工作具有研究性质。所有已发表的结果都可以由任何人使用,风险自负。

存档内容

#
 名称
版本  描述  最近修改
  SimpleCandles     项目工作文件夹(应位于 MQL5/Experts 目录下)  
SimpleCandles.mq5
1.01
最终 EA,用于多组模型策略的并行操作。参数将取自内置的组库。
第 25 部分
  Optimization
  项目优化 EA 文件夹  
2    CreateProject.mq5 1.02 用于创建具有阶段、作业和优化任务的项目的 EA 脚本。
第 25 部分
3    Optimization.mq5 1.00
用于项目自动优化的 EA
 
4    Stage1.mq5 1.02
交易策略单实例优化 EA(第一阶段)
第 25 部分
5    Stage2.mq5 1.01
交易策略实例组优化 EA(第二阶段)
第 25 部分
6 Stage3.mq5 1.01
EA 将生成的标准化策略组保存到具有给定名称的 EA 数据库中。  第 25 部分
  └ Strategies   项目策略文件夹
第 25 部分
7    SimpleCandlesStrategy.mqh
1.01
SimpleCandles 交易策略类
第 25 部分
  └ Include/Adwizard   Adwizard 库文件夹  
    └ Base
  其他项目类所继承的基类    
8       Advisor.mqh 1.04 EA 基类 第 10 部分
9       Factorable.mqh
1.06
从字符串创建的对象的基类
第 28 部分
10       FactorableCreator.mqh
1.00 绑定 CFactorable 派生类的名称和静态构造函数的创建器类 第 24 部分
11       Interface.mqh 1.01
可视化各种对象的基类
第 4 部分
12       Receiver.mqh
1.04  将未平仓交易量转换为市场仓位的基类
第 12 部分
13       Strategy.mqh
1.04
交易策略基类
第 10 部分
     └ Database
  用于处理项目 EA 使用的所有类型数据库的文件
 
14       Database.mqh 1.12 处理数据库的类 第 25 部分
15       db.adv.schema.sql 1.00
最终 EA 的数据库结构 第 22 部分
16       db.cut.schema.sql
1.00 截断优化数据库的结构
第 22 部分
17       db.opt.schema.sql
1.05  优化数据库结构
第 22 部分
18       Storage.mqh   1.01
用于处理 EA 数据库中最终 EA 的键值存储的类
第 23 部分
     └ Experts
  包含不同类型已使用 EA 的公共部分的文件
 
19       Expert.mqh  1.24 最终 EA 的库文件。组参数可以从 EA 数据库中获取。
第 28 部分
20       Optimization.mqh  1.04 用于管理优化任务启动 EA 的库文件
第 23 部分
21       Stage1.mqh
1.19 单实例交易策略优化 EA(第一阶段)的库文件
第 23 部分
22       Stage2.mqh 1.04 用于优化一组交易策略实例的 EA 的库文件(第二阶段)   第 23 部分
23       Stage3.mqh
1.04 EA 库文件,用于将生成的标准化策略组保存到具有给定名称的 EA 数据库中。 第 23 部分
     └ Optimization
  负责自动优化的类
 
24       OptimizationJob.mqh 1.00 优化项目阶段作业类
第 25 部分
25       OptimizationProject.mqh 1.00 优化项目类 第 25 部分
26       OptimizationStage.mqh 1.00 优化项目阶段类 第 25 部分
27       OptimizationTask.mqh 1.00 优化任务类(创建) 第 25 部分
28       Optimizer.mqh
1.03  项目自动优化管理器类
第 22 部分
29       OptimizerTask.mqh
1.03
优化任务类(流水线)
第 22 部分
     └ Strategies    用于演示项目工作方式的交易策略示例
 
24       HistoryStrategy.mqh 
1.00 用于回放交易历史的交易策略类
第 16 部分
25       SimpleVolumesStrategy.mqh
1.11
使用分时交易量的交易策略类
第 22 部分
     └ Utils
  辅助工具、用于代码简化的宏

26        ConsoleDialog.mqh 1.01 用于在图表上显示文本数据的类 第 28 部分
26       ExpertHistory.mqh 1.00 用于将交易历史导出到文件的类 第 16 部分
27       Macros.mqh 1.07 用于数组操作的有用的宏 第 26 部分
28        MTTester.mqh 
用于从 MultiTester 库中与策略测试器配合工作的文件
第 28 部分
29       NewBarEvent.mqh 1.00  用于定义特定交易品种的新 K 线的类  第 8 部分
30       SymbolsMonitor.mqh  1.01 用于获取交易工具(交易品种)信息的类 第 28 部分
     └ Virtual
  通过使用虚拟交易订单和仓位系统创建各种对象的类

31       Money.mqh 1.01  资金管理基类
第 12 部分
32       TesterHandler.mqh  1.07 优化事件处理类  第 23 部分
33       VirtualAdvisor.mqh  1.12  处理虚拟仓位(订单)的 EA 类 第 28 部分
34       VirtualChartOrder.mqh  1.02  图形虚拟仓位类 第 28 部分
35       VirtualCloseManager.mqh 1.00 平仓管理器类 第 28 部分
36       VirtualHistoryAdvisor.mqh 1.00  交易历史回放 EA 类  第 16 部分
37       VirtualInterface.mqh  1.00  EA GUI 类  第 4 部分
38       VirtualOrder.mqh 1.09  虚拟订单和仓位类  第 22 部分
39       VirtualReceiver.mqh 1.04 将未平仓交易量转换为市场仓位的类(接收方)  第 23 部分
40       VirtualRiskManager.mqh  1.06 风险管理类(风险管理器)  第 28 部分
41       VirtualStrategy.mqh 1.09  具有虚拟仓位的交易策略类  第 23 部分
42       VirtualStrategyGroup.mqh  1.04  交易策略组类 第 28 部分
43       VirtualSymbolReceiver.mqh  1.00 交易品种接收器类  第 3 部分
  Common/Files   MetaTrader 5 终端数据共享文件夹  
44 SimpleCandles-27183.test.db.sqlite 最终 EA 数据库 第 25 部分

源代码同时可在以下两个链接获取:SimpleCandlesAdwizard


如何使用开源仓库

在我们逐步过渡到新的 Algo Forge 存储的过程中,我们仍在探索使用它的最佳和最便捷方式。MetaEditor 限制只能使用与 MQL5 根文件夹相对应的单个存储库,这并不是特别方便。其他存储库只能作为“共享项目”文件夹的子文件夹使用,这虽然使管理变得更容易一些,但还不足以立即采用这种方法。

此外,在过渡期间,仅有的主存储库曾两次以超级管理员的名义被创建,而第二次创建时,由于某种原因,其创建操作破坏了用户之前创建的其他附加存储库。幸运的是,拥有存储库的本地副本使我们能够将其重新上传到服务器,但这种被迫操作并不是特别理想。因此,目前我们暂时先不直接在 MetaEditor 中使用仓库功能,而是等待其进一步完善。

这并不复杂:在继续使用 MetaEditor 的同时,我们暂时只需将所有与存储相关的操作转移到外部应用程序中。

例如,我们可以在控制台中运行以下脚本,在本地计算机上获取包含本文代码的所有文件的副本,但首先需要在所需 MetaTrader 终端的 MQL5 文件夹中设置一个文件夹作为当前文件夹:

# 创建项目文件夹
mkdir SimpleCandles

# 转到项目文件夹
cd SimpleCandles

# 将项目仓库克隆到当前文件夹
git clone https://forge.mql5.io/antekov/SimpleCandles.git .

# 将仓库切换到所需的分支(对于本文而言是“ article-17608-close-manager”
git checkout article-17608-close-manager

# 确保我们已切换到该分支
git status

# 为库部分创建一个文件夹
mkdir Include

# 转向它
cd Include

# 将 Adwizard 存储库克隆到库文件夹中
git clone https://forge.mql5.io/antekov/Adwizard.git 

# 转到创建的文件夹
cd Adwizard

# 将仓库切换到所需的分支(对于本文而言是“ article-17608-close-manager”
git checkout article-17608-close-manager

# 确保我们已切换到该分支
git status

# 返回上两级目录,找到原始项目文件夹
cd ./../..

这些存储库中唯一缺少的是包含最终 EA 数据库的文件,因为存储库中的代码允许我们通过执行自动优化来获取该文件。如有必要,可将此文件从存档中提取到文章中,并放置在 Files 文件夹中的公共终端文件夹中。

本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/17608

附加的文件 |
SimpleCandles.zip (483.1 KB)
从基础到中级:对象事件(三) 从基础到中级:对象事件(三)
在本文中,我们将为下一篇文章所涵盖的内容奠定基础。我们还将探讨如何使 OBJ_LABEL 对象完全可交互,以便进行编辑和移动。换句话说,我们无需打开“对象属性”对话框,即可更改 OBJ_LABEL 对象的文本和位置。
利用深度强化学习优化Ilan智能交易系统 利用深度强化学习优化Ilan智能交易系统
本研究对经典Ilan网格交易策略进行改进,通过在MQL5中集成Q-learning算法,构建出适用于MetaTrader 5平台的自适应版本。本文展示如何定义状态特征、将连续状态空间映射为Q-table可处理的离散值、使用ε-贪婪算法选择操作,以及针对平均持仓收益与平仓事件设计奖励机制。您将实现Q-table的存储与加载功能、通过参数调优优化学习效率,并在策略测试器中对EURUSD(欧元兑美元)/AUDUSD(澳元兑美元)进行测试,以评估稳定性和回撤风险。
交易中的神经网络:不依赖特定数据的时间序列泛化(核心模型模块) 交易中的神经网络:不依赖特定数据的时间序列泛化(核心模型模块)
我们继续介绍Mamba4Cast框架。今天,我们将深入探讨该方案的实现。Mamba4Cast的设计目标并非对每一条全新时间序列都进行冗长的预热,而是支持直接部署上线。依托零样本预测理念,该模型无需额外训练、也无需调优超参数,就可以在真实业务数据上输出高质量预测结果。
MQL5 交易策略自动化(第二十七部分):创建带可视化反馈的价格行为螃蟹谐波形态 MQL5 交易策略自动化(第二十七部分):创建带可视化反馈的价格行为螃蟹谐波形态
本文将基于 MQL5 开发一套螃蟹谐波形态交易系统。程序借助枢轴点与斐波那契比例识别看涨、看跌螃蟹谐波形态,并按照精准的入场、止损、止盈点位触发交易。同时使用三角形、趋势线等图表对象做可视化展示,完整展示 XABCD 形态结构与各个交易价位。