
开发多币种 EA 交易 (第 5 部分):可变仓位大小
概述
在上一部分中,我们添加了重启后恢复 EA 状态的功能。无论原因是什么 - 重新启动终端、使用 EA 更改图表上的时间框架、启动较新版本的 EA - 在所有情况下,恢复状态都允许 EA 不从头开始工作并且不会丢失已经打开的仓位,而是继续处理它们。
然而,在整个测试期间,该策略的每个实例的开仓大小保持不变。其大小是在 EA 启动时设定的。如果由于 EA 的运行,交易账户余额增加了,那么这将允许使用增加的仓位大小而不会增加风险。利用这一点是合理的,所以让我们开始实现使用可变仓位大小。
概念
首先,我们需要在一个共同的目标上达成概念共识 - 实现多个交易策略实例之间的最佳协作。
固定策略大小 (固定手数)- 用于计算交易策略中所有未平仓位大小的特定大小。在最简单的情况下,所有开仓的大小都可以等于该值。当使用任何技巧来增加或减少一系列中第二个及后续开仓的大小时,固定大小可以设置一系列中第一个仓位的大小,后续仓位则根据它和已开仓位的数量来计算。这不被认为是一种很好的技术。
标准化策略余额(拟合余额)- 初始余额,在整个测试期间的回撤达到但不超过所选 固定策略大小的初始余额的 10%。为什么恰好是10%?这个数字还不算很大的回撤,从心理上来说似乎是可以接受的,而且更方便进行快速粗略的心算。一般来说,我们可以取任意值 - 1%、5%甚至 50%。这只不过是一个规范化的参数。
标准化交易策略 - 一种交易策略,其 策略大小 和 选择了规范化的策略余额 。因此,在选定的测试期间启动此类策略时,我们应该获得最大回撤值,约为规范化的策略余额 的 10%。
有了交易策略,我们可以通过执行以下操作将其转变为规范化的交易策略 :
- 选择 固定的策略大小,例如 0.01。
- 选择测试期间(开始和结束日期)
- 在选定的测试期间内以较大的初始余额启动该策略,并查看净值的最大绝对回撤值。
- 将最大净值绝对回撤乘以10,就得到了标准化策略余额。
我们来研究下面的例子。假设固定策略规模为 0.01,则最大绝对回撤为 440 美元。如果我们希望该值正好是初始余额的 10%,我们可以将 440 美元除以 0.10 或乘以 10(这是相同的):
440 美元 / 0.10 = 440 美元 * 10 = 4400 美元
我们在创建交易策略实例的参数中设置这两个值(0.01和4400),并得到一个规范化的交易策略。
现在,对于标准化的交易策略,我们就可以计算任何余额值的未平仓仓位的大小,同时保持净值的最大相对回撤等于 10%。为此,只需按照当前总余额 (Total Balance) 和标准余额 (Fitted Balance) 的比例更改开仓仓位的大小即可。
CurrentLot = FixedLot * (TotalBalance / FittedBalance)
例如,对于上例中使用的值 0.01 和 4400,余额为 10,000 美元,则应根据基准值计算未平仓仓位的大小:
CurrentLot = 0.01 * (10,000 / 4400) = 0.0227
无法打开正好这样大小的仓位。我们必须将其四舍五入为 0.02,因此在这种情况下,测试中的回撤可能略低于 10%。如果我们四舍五入(到 0.03),那么回撤可能会略高于 10%。随着余额的增加,舍入误差将会减少。
如果我们为某个策略引入了固定仓位大小的概念,那么我们可以将管理策略仓位大小的任何选择权委托给该策略本身。因此,我们只需要在结合各种交易策略实例的 EA 水平上实现三种可能的资金管理策略选项:
- 固定大小或缺乏资金管理策略。无论交易账户余额有多少,均适用策略中指定的固定大小。在测试策略的单独实例时将使用该策略来确定标准化的策略余额。
- 指定固定余额的恒定大小。开始时,根据策略标准化余额和固定大小计算策略的固定余额比例大小。整个测试期间都会使用此大小。此策略将用来检查整个测试期间资金增长曲线的均匀性(线性),但须遵守规定的最大绝对回撤。
- 当前余额的可变大小。每笔开仓均根据策略标准化余额和固定大小,按照当前账户余额的比例确定大小。该策略将用于实际工作,提供最大相对回撤的预期值。
让我们提供使用这三个选项的示例。让我们使用一份策略副本来制作 EA,设置 100,000 美元的大额起始余额,并以 0.01 手的固定开仓仓位大小开始测试 2018-2022 年期间。我们得到以下结果:
图 1.固定手数大小且余额为 100,000 美元的结果
我们可以看到,在此测试期间,权益的最大绝对回撤约为 153 美元,约占账户余额的 0.15%。更准确地说,我们根据初始账户余额来评估相对回撤是更正确的。但由于初始余额和最终余额之间的差异很小(约为初始余额的 1%),因此在测试期间的任何时间点,0.15% 的回撤都将非常准确地达到 150 美元的绝对值。
让我们计算一下可以将初始余额设置为多大,以便最大绝对回撤为初始余额的 10%:
FittedBalance = MaxDrawdown / 10% = 153 / 0.10 = 153 * 10 = USD 1530
让我们检查一下计算结果:
图 2.固定手数大小的结果和 1530 美元的余额
我们可以看到,净值的绝对回撤同样为 153 美元,但相对回撤并非 10%,而仅为 7.2%。这是正常的,因为这仅意味着最大的亏损发生在账户余额已从其初始值有所增长时,而 153 美元的价值已经低于当前余额的 10%。
现在让我们检查第二个选项 - 给定固定余额的恒定大小。设定一个 USD 100,000 的大额初始余额,但只允许使用其中的十分之一,也就是 USD 10,000。这是 当前余额 (Current Balance)值,在整个测试期间保持不变。在此条件下,开仓大小应为:
CurrentBalance = TotalBalance * 0.1 = 10,000
CurrentLot = FixedLot * (CurrentBalance / FittedBalance) = 0.01 * (10,000 / 1530) = 0.0653
在操作过程中,该值将被四舍五入为手数变化步长的倍数。我们得到以下结果:
图 3。可用金额为 100,000 美元,其中固定余额为 10,000 美元的固定手数大小结果
如您所见,绝对回撤为 1016 美元,准确来说,这是分配给该策略的 10,000 美元的 10%。但这仅占整个余额的1%。
最后,让我们看看第三个选项 - 当前余额的可变大小。将初始余额设置为 10,000 美元,并允许全额使用。测试情况如下:
图 4.当前余额可变大小的结果
这里我们看到最大绝对回撤已经超过了初始余额的10%,但相对回撤仍然保持在可接受的10%以内。我们已经获得了一个标准化的交易策略,其 Fitted Balance = 1530,现在我们可以轻松计算未平仓仓位的大小,以确保给定的 10% 的亏损。
仓位大小计算
在考虑以下资金管理选项时,可以得出以下观察结果:
- 如果我们谈论的是该策略的一份副本,那么具有可变仓位的选项是否有用?看起来它们没有什么用。我们只需要使用第一个选项。我们可以使用第二个和第三个选项来展示性能,但之后我们就不需要它们了。
- 如果我们正在使用已经结合了多种交易策略实例的 EA,那么使用固定手数进行交易是否有用?看起来不能。在这种情况下,第二个选项在测试阶段可能对我们有用,而第三个选项将是主要使用的。
这使我们得出以下结论:虚拟订单始总是具有根据 固定策略大小 参数计算出的固定大小。在这里讨论的策略中,使用最小手数作为固定大小就足够了,对于大多数工具来说,该手数等于 0.01。
根据工作流程,接收者对象或交易品种接收者必须重新计算为实际持仓大小。为此,它们必须从策略中,或者更准确地说,从虚拟订单中获得标准化余额的值,以确保该余额的 10% 回撤。
但是如果我们想提供更小或更大的回撤怎么办?为此,只需以某种方式按照我们希望将预期最大亏损相对于 10% 的值改变多少次的比例来改变开仓仓位的大小即可。
其中一种方法是明确引入加权乘数,显示 EA 可以使用当前账户余额的哪一部分。
分配的余额 (Current Balance)是分配给此 EA 用于交易的总账户余额的一部分。
余额乘数 (Depo Part)- 分配的策略余额与账户总余额的比率。
DepoPart = CurrentBalance / TotalBalance
然后初始仓位大小可以计算如下:
CurrentLot = FixedLot * (CurrentBalance / FittedBalance)
CurrentLot = FixedLot * (DepoPart * TotalBalance / FittedBalance)
这里我们可以提出一个重要的意见,这对于我们的实现非常有用。如果我们重新计算标准化余额 创建策略实例后,总余额将用于仓位大小计算公式,而不是当前策略的余额:
FittedBalance = FittedBalance / DepoPart
CurrentLot = FixedLot * (TotalBalance / FittedBalance)
在初始化 EA 时,我们将重新计算一次标准化余额。此后我们将不再需要 Depo Part 乘数。
组合多种策略
先前的讨论是针对在 EA 中使用一个策略实例的情况。现在让我们想想,如果我们想要采用几种标准化策略并将它们组合到一个 EA 中,允许整个测试期间的亏损不超过 10%(或提前指定的其他值),我们需要做什么。现在,我们将考虑指定的回撤值正好是 10%。
如果我们关注组合策略时可能发生的最坏情况,那么这就是所有策略实例同时实现 10% 的最大回撤的事件。在这种情况下,我们将必须根据实例数量比例减少每个策略的仓位大小。例如,如果我们合并一个策略的三个副本,那么我们需要将仓位大小减少三倍。
这可以通过将分配给策略的余额减少给定的倍数,或将策略的标准化余额增加给定的倍数来实现。我们将使用第二个选项。
如果我们通过 StrategiesCount 表示一组中的策略数量,那么重新计算标准化余额的公式如下:
FittedBalance = StrategiesCount * FittedBalance
然而,如果选择的策略实例尽可能彼此不同,那么随着策略实例数量的增加,出现这种最坏情况的可能性就会大大降低。在这种情况下,亏损发生的时间不同,而不是同时发生。这可以在测试过程中看到。然后,我们可以引入另一个缩放因子(Scale),它默认等于 1,但如果需要,可以将其调大,以通过减少策略的标准化余额来增加仓位大小:
FittedBalance = StrategiesCount * FittedBalance
FittedBalance = FittedBalance / Scale
通过选择 Scale 乘数,我们可以再次确保一组策略在整个测试期间提供指定的回撤。在这种情况下,我们将获得一组规范化的策略。
标准化策略组 - 一组标准化的交易策略,为其选择一个缩放因子,以确保当该策略组协同工作时,最大亏损不超过 10%。
然后,如果我们已经制作了几组规范化的策略,那么它们都可以按照组合规范化策略的相同原理重新组合成一个新的规范化组。换句话说,我们应该为组群选择一个乘数,使得当所有组群的所有策略同时起作用时,最大回撤不超过 10%。这个统一过程可以延续到任意数量的级别。在这种情况下,每个策略的初始标准化余额将简单地乘以每个关联级别的组中的策略或组的数量,然后除以每个级别的缩放因子:
FittedBalance = StrategiesCount1 * FittedBalance
FittedBalance = StrategiesCount2 * FittedBalance
...
FittedBalance = FittedBalance / Scale1
FittedBalance = FittedBalance / Scale2
...
然后,重新计算每个策略的标准化余额的最终公式将如下所示:
FittedBalance = (StrategiesCount1 * StrategiesCount1 * ... ) * FittedBalance / (Scale1 * Scale2 * ... )
最后,在仓位大小计算公式中应用最后一个缩放的 Depo Part 乘数,以便将处于组合最高级别的标准化策略组转换为具有不同指定回撤(而不是 10%)的组:
CurrentLot = FixedLot * (DepoPart * TotalBalance / FittedBalance)
有两个新的类需要实现。第一个类 CVirtualStrategyGroup 将负责在将策略组合成组时重新计算其标准化余额。第二类 CMoney 将负责根据策略固定大小、标准化余额和分配余额计算实际开仓量。
交易策略组类
此类将用于创建代表一组策略或一组策略组的对象。在这两种情况下,创建组时,将通过调用单个 Scale() 方法来应用缩放因子。
//+------------------------------------------------------------------+ //| Class of trading strategies group(s) | //+------------------------------------------------------------------+ class CVirtualStrategyGroup { protected: void Scale(double p_scale); // Scale normalized balance public: CVirtualStrategyGroup(CVirtualStrategy *&p_strategies[], double p_scale = 1); // Constructor for a group of strategies CVirtualStrategyGroup(CVirtualStrategyGroup *&p_groups[], double p_scale = 1); // Constructor for a group of strategy groups CVirtualStrategy *m_strategies[]; // Array of strategies CVirtualStrategyGroup *m_groups[]; // Array of strategy groups };
构造函数将采用缩放因子作为参数,以及指向策略的指针数组或指向策略组的指针数组。将结果数组复制到创建对象的相应属性中,并将 Scale() 方法应用于每个数组元素。我们需要将此方法添加到策略对象的策略类中。
//+------------------------------------------------------------------+ //| Constructor for strategy groups | //+------------------------------------------------------------------+ CVirtualStrategyGroup::CVirtualStrategyGroup( CVirtualStrategy *&p_strategies[], double p_scale ) { ArrayCopy(m_strategies, p_strategies); Scale(p_scale / ArraySize(m_strategies)); } //+------------------------------------------------------------------+ //| Constructor for a group of strategy groups | //+------------------------------------------------------------------+ CVirtualStrategyGroup::CVirtualStrategyGroup( CVirtualStrategyGroup *&p_groups[], double p_scale ) { ArrayCopy(m_groups, p_groups); Scale(p_scale / ArraySize(m_groups)); } //+------------------------------------------------------------------+ //| Scale normalized balance | //+------------------------------------------------------------------+ void CVirtualStrategyGroup::Scale(double p_scale) { FOREACH(m_groups, m_groups[i].Scale(p_scale)); FOREACH(m_strategies, m_strategies[i].Scale(p_scale)); }
将代码保存在当前文件夹的 VirtualStrategyGroup.mqh 文件中。
让我们对虚拟策略类添加必要的代码。我们需要添加两个新的类属性来存储策略标准化余额和固定大小。由于应该添加它们,所以现在需要一个以前不必要的构造函数。FittedBalance() 公有方法将简单地返回策略标准化余额的值,而 Scale() 方法将按指定的乘数对其进行缩放。
//+------------------------------------------------------------------+ //| Class of a trading strategy with virtual positions | //+------------------------------------------------------------------+ class CVirtualStrategy : public CStrategy { protected: ... double m_fittedBalance; // Strategy normalized balance double m_fixedLot; // Strategy fixed size ... public: CVirtualStrategy(double p_fittedBalance = 0, double p_fixedLot = 0.01); // Constructor ... double FittedBalance() { // Strategy normalized balance return m_fittedBalance; } void Scale(double p_scale) { // Scale normalized balance m_fittedBalance /= p_scale; } };
将代码保存到当前文件夹的 VirtualStrategy.mqh 文件中。
另外,我们需要对 CSimpleVolumesStrategy 类做一些小的修改。我们应该在策略标准化余额的构造函数中实现一个附加参数,并删除用于设置虚拟仓位大小的参数。现在它将始终相同且等于最小手数 0.01。
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CSimpleVolumesStrategy::CSimpleVolumesStrategy( string p_symbol, ENUM_TIMEFRAMES p_timeframe, int p_signalPeriod, double p_signalDeviation, double p_signaAddlDeviation, int p_openDistance, double p_stopLevel, double p_takeLevel, int p_ordersExpiration, int p_maxCountOfOrders, double p_fittedBalance = 0) : // Initialization list CVirtualStrategy(p_fittedBalance, 0.01), m_symbol(p_symbol), m_timeframe(p_timeframe), m_signalPeriod(p_signalPeriod), m_signalDeviation(p_signalDeviation), m_signaAddlDeviation(p_signaAddlDeviation), m_openDistance(p_openDistance), m_stopLevel(p_stopLevel), m_takeLevel(p_takeLevel), m_ordersExpiration(p_ordersExpiration), m_maxCountOfOrders(p_maxCountOfOrders) { ... }
将更改保存在当前文件夹的 SimpleVolumesStrategy.mqh文件中。
我们需要能够将一组策略(即,我们的新 CVirtualStrategyGroup 类的实例)添加到 EA 对象。因此,让我们为 EA 类实现重载的 Add() 方法,它将执行此操作:
//+------------------------------------------------------------------+ //| Class of the EA handling virtual positions (orders) | //+------------------------------------------------------------------+ class CVirtualAdvisor : public CAdvisor { ... public: ... virtual void Add(CVirtualStrategyGroup &p_group); // Method for adding a group of strategies ... }; //+------------------------------------------------------------------+ //| Method for adding a group of strategies | //+------------------------------------------------------------------+ void CVirtualAdvisor::Add(CVirtualStrategyGroup &p_group) { FOREACH(p_group.m_groups, { CVirtualAdvisor::Add(p_group.m_groups[i]); delete p_group.m_groups[i]; }); FOREACH(p_group.m_strategies, CAdvisor::Add(p_group.m_strategies[i])); }
由于策略组在添加到 EA 后就不再需要了,我们在此方法中立即将其从动态内存区域中删除。将对VirtualAdvisor.mqh 文件所做的更改保存在当前文件夹中。
资金管理类
该类将负责根据三种可能的资金管理策略选项确定虚拟仓位的实际大小。
类对象应该是唯一的。因此,我们既可以使用 Singleton 设计模式,也可以像最终实现的那样,该类只能包含任何对象可访问的静态字段和方法。
此类中的主要方法是确定 Volume() 虚拟仓位(订单)的实际大小的方法。另外两个方法允许我们设置两个参数的值,这两个参数决定交易账户余额的哪一部分参与交易。
//+------------------------------------------------------------------+ //| Basic money management class | //+------------------------------------------------------------------+ class CMoney { static double s_depoPart; // Used part of the total balance static double s_fixedBalance; // Total balance used public: CMoney() = delete; // Disable the constructor static double Volume(CVirtualOrder *p_order); // Determine the real size of the virtual position static void DepoPart(double p_depoPart) { s_depoPart = p_depoPart; } static void FixedBalance(double p_fixedBalance) { s_fixedBalance = p_fixedBalance; } }; double CMoney::s_depoPart = 1.0; double CMoney::s_fixedBalance = 0; //+------------------------------------------------------------------+ //| Determine the real size of the virtual position | //+------------------------------------------------------------------+ double CMoney::Volume(CVirtualOrder *p_order) { // Request the normalized strategy balance for the virtual position double fittedBalance = p_order.FittedBalance(); // If it is 0, then the real volume is equal to the virtual one if(fittedBalance == 0.0) { return p_order.Volume(); } // Otherwise, find the value of the total balance for trading double totalBalance = s_fixedBalance > 0 ? s_fixedBalance : AccountInfoDouble(ACCOUNT_BALANCE); // Return the calculated real volume based on the virtual one return p_order.Volume() * totalBalance * s_depoPart / fittedBalance ; } //+------------------------------------------------------------------+
将此代码保存在当前文件夹的 Money.mqh 文件中。
测试 EA
让我们对 EA 文件进行一些修改以进行测试。在 SimpleVolumesExpertSingle.mq5 文件中,我们只需从 EA 初始化函数中的策略构造函数参数列表中删除仓位大小参数:
int OnInit() { // Create an EA handling virtual positions expert = new CVirtualAdvisor(magic_, "SimpleVolumesSingle"); expert.Add(new CSimpleVolumesStrategy( symbol_, timeframe_, fixedLot_, signalPeriod_, signalDeviation_, signaAddlDeviation_, openDistance_, stopLevel_, takeLevel_, ordersExpiration_, maxCountOfOrders_) ); // Add one strategy instance return(INIT_SUCCEEDED); }
我们现在不会使用 EA 来为各个策略实例搜索新的良好参数组合,因为我们将使用之前找到的组合。但如果有必要,EA 将会准备好进行优化。
让我们对 SimpleVolumesExpert.mq5 文件进行更重要的补充。我们主要需要它们来展示所添加类的功能,因此我们不应将其视为最终代码。
首先,我们将创建一个枚举来表示对交易策略实例进行分组的不同方式:
enum ENUM_VA_GROUP { VAG_EURGBP, // Only EURGBP (3 items) VAG_EURUSD, // Only EURUSD (3 items) VAG_GBPUSD, // Only GBPUSD (3 items) VAG_EURGBPUSD_9, // EUR-GBP-USD (9 items) VAG_EURGBPUSD_3_3_3 // EUR-GBP-USD (3+3+3 items) };
前三个值将对应使用针对其中一个交易品种 (EURGBP、EURUSD 或 GBPUSD) 的三个交易策略副本。第四个值将对应使用一组所有九个策略实例。第五个值将对应使用一组三个标准化组,其中将包括特定交易品种的三份交易策略副本。
让我们稍微扩展一下输入参数列表:
//+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input group "::: Strategy groups" input ENUM_VA_GROUP group_ = VAG_EURGBP; // - Strategy group input group "::: Money management" input double expectedDrawdown_ = 10; // - Maximum risk (%) input double fixedBalance_ = 0; // - Used deposit (0 - use all) in the account currency input double scale_ = 1.0; // - Group scaling multiplier input group "::: Other parameters" input ulong magic_ = 27183; // - Magic
在 EA 初始化函数中,设置资金管理参数,考虑到 10% 的最大允许回撤的规范化,创建九份策略副本,按照所选分组排列它们并将它们添加到 EA 中:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Set parameters in the money management class CMoney::DepoPart(expectedDrawdown_ / 10.0); CMoney::FixedBalance(fixedBalance_); // Create an EA handling virtual positions expert = new CVirtualAdvisor(magic_, "SimpleVolumes_" + EnumToString(group_)); // Create and fill the array of all strategy instances CVirtualStrategy *strategies[] = { new CSimpleVolumesStrategy("EURGBP", PERIOD_H1, 13, 0.3, 1.0, 0, 10500, 465, 1000, 3, 1600), new CSimpleVolumesStrategy("EURGBP", PERIOD_H1, 17, 1.7, 0.5, 0, 16500, 220, 1000, 3, 900), new CSimpleVolumesStrategy("EURGBP", PERIOD_H1, 51, 0.5, 1.1, 0, 19500, 370, 22000, 3, 1600), new CSimpleVolumesStrategy("EURUSD", PERIOD_H1, 24, 0.1, 0.3, 0, 7500, 2400, 24000, 3, 2300), new CSimpleVolumesStrategy("EURUSD", PERIOD_H1, 18, 0.2, 0.4, 0, 19500, 1480, 6000, 3, 2000), new CSimpleVolumesStrategy("EURUSD", PERIOD_H1, 128, 0.7, 0.3, 0, 3000, 170, 42000, 3, 2200), new CSimpleVolumesStrategy("GBPUSD", PERIOD_H1, 80, 1.1, 0.2, 0, 6000, 1190, 1000, 3, 2500), new CSimpleVolumesStrategy("GBPUSD", PERIOD_H1, 128, 2.0, 0.9, 0, 2000, 1170, 1000, 3, 900), new CSimpleVolumesStrategy("GBPUSD", PERIOD_H1, 13, 1.5, 0.8, 0, 2500, 1375, 1000, 3, 1400), }; // Create arrays of pointers to strategies, one symbol at a time, from the available strategies CVirtualStrategy *strategiesEG[] = {strategies[0], strategies[1], strategies[2]}; CVirtualStrategy *strategiesEU[] = {strategies[3], strategies[4], strategies[5]}; CVirtualStrategy *strategiesGU[] = {strategies[6], strategies[7], strategies[8]}; // Create and add selected groups of strategies to the EA switch(group_) { case VAG_EURGBP: { expert.Add(CVirtualStrategyGroup(strategiesEG, scale_)); FOREACH(strategiesEU, delete strategiesEU[i]); FOREACH(strategiesGU, delete strategiesGU[i]); break; } case VAG_EURUSD: { expert.Add(CVirtualStrategyGroup(strategiesEU, scale_)); FOREACH(strategiesEG, delete strategiesEG[i]); FOREACH(strategiesGU, delete strategiesGU[i]); break; } case VAG_GBPUSD: { expert.Add(CVirtualStrategyGroup(strategiesGU, scale_)); FOREACH(strategiesEU, delete strategiesEU[i]); FOREACH(strategiesEG, delete strategiesEG[i]); break; } case VAG_EURGBPUSD_9: { expert.Add(CVirtualStrategyGroup(strategies, scale_)); break; } case VAG_EURGBPUSD_3_3_3: { // Create a group of three strategy groups CVirtualStrategyGroup *groups[] = { new CVirtualStrategyGroup(strategiesEG, 1.25), new CVirtualStrategyGroup(strategiesEU, 2.24), new CVirtualStrategyGroup(strategiesGU, 2.64) }; expert.Add(CVirtualStrategyGroup(groups, scale_)); break; } default: return(INIT_FAILED); } // Load the previous state if available expert.Load(); return(INIT_SUCCEEDED); }
将所做的更改保存到当前文件夹中的 SimpleVolumesExpert.mq5 文件中。
测试
让我们测试第一组 - 针对 EURGBP 交易品种运行的策略的三个副本。我们得到以下结果:
图 5.采用三种策略的 EURGBP 结果,缩放因子=1
我们可以看到,组合起来后,标准化策略每个单独实例的最大相对回撤为 8%,而不是 10%。这意味着我们可以稍微增加我们的仓位大小。为了实现 10% 的回撤,我们将设置缩放因子 = 10% / 8% = 1.25。
图 6.采用三种策略的 EURGBP 结果,缩放因子=1.25
现在回撤幅度约为10%。我们执行类似的操作来为第二组和第三组选择缩放因子。我们得到以下结果:
图 7.采用三种策略的 EURUSD 的结果,缩放因子=2.24
图 8.GBPUSD 采用三种策略的结果,缩放因子=2.64
我们使用代码中选定的缩放因子值来创建三个标准化策略组的标准化组:
// Create a group of three strategy groups CVirtualStrategyGroup *groups[] = { new CVirtualStrategyGroup(strategiesEG, 1.25), new CVirtualStrategyGroup(strategiesEU, 2.24), new CVirtualStrategyGroup(strategiesGU, 2.64) };
现在让我们为第四组选择一个缩放因子。如果我们将所有 9 个实例合并为一组,我们将得到以下结果:
图 9.EURGBP、EURUSD、GBPUSD 的结果(共 9 种策略),缩放因子=1
这样我们就可以将缩放因子提高到 3.3,并且保持在相对回撤的 10% 以内:
图 10。EURGBP、EURUSD 和 GBPUSD 的结果(共 9 种策略),缩放因子=3.3
最后,最有趣的事情。让我们将相同的 9 种标准化策略组合起来,但采用不同的方式:分别为各个交易品种标准化三种策略组,然后将得到的三个标准化组组合成一个组。我们得到的结果如下:
图 11.EURGBP、EURUSD 和 GBPUSD 的结果(3 + 3 + 3 策略),缩放因子=1
最终余额比第四组(同样 Scale=1)的余额要大,但亏损也更大:4.57%,而不是 3%。我们将第五组带到 10% 的回撤,然后比较最终结果:
图 12。EURGBP、EURUSD、GBPUSD 的结果(3 + 3 + 3 策略),缩放因子=2.18
现在很明显,分组策略的第五选项在将最大相对回撤保持在 10% 以内的同时给出了更好的结果。在选定的测试期间,与第四个分组选项相比,利润增加了一倍多。
最后,我们来看看第五个分组选项的余额增长的线性。这将使我们能够评估是否存在任何内部时期,其中 EA 的表现明显比整个测试期间的其他内部时期更差。为此,请设置参数 FixedBalance= 10,000 的值,以便 EA 始终仅使用此金额的账户余额来计算头寸规模。
图 13。EURGBP、EURUSD、GBPUSD(3 + 3 + 3 策略)的结果,固定余额 = 10000,缩放因子 = 2.18
在测试图上,我用绿色矩形标记了余额增长接近于零的内部时期。其持续时间从一个月到六个月不等。嗯,那就意味着有值得奋斗的事情。应对这种时期的最简单方法是更加多样化:使用更多适用于不同交易品种和时间框架的交易策略实例。
就净值而言,最大亏损绝对值为 995 美元,即仅占用于交易的 10,000 美元余额的 10% 左右。这证实了所实施的资金管理系统运行正确。
结论
现在我们可以在具有不同初始余额值的交易账户上运行我们的 EA,并控制如何为不同交易策略实例分配余额。有些策略会得到更多,并且会开设更大的仓位。其他策略得到的将会更少,它们的持仓规模也会更小。通常,通过测试,我们可以选择符合预先选择的最大允许回撤的参数。
值得注意的是,我们只有通过测试才能知道回撤是否得到遵守。我们无法保证在未用于优化的时间段内启动 EA 时是否能够保持遵守。它既可以变得更高,也可能降低(有些奇怪)。因此,每个策略都应该在这里独立决定它们可以信任多少以及如何使用测试中获得的结果。
我将继续开发这个项目。感谢您的阅读!
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/14336



