퐀⣘g·综合运用
(3/3)·固定手数拖垮复利?本篇把仓位大小绑上账户余额,回撤锚定10%心理线
「多币种策略组的装配与内存回收」
在 EA 初始化阶段,按枚举 VAG 分支把不同货币对的策略容器挂进 expert 对象,是虚拟策略组(CVirtualStrategyGroup)落地的关键一步。EURGBPUSD_3_3_3 这个分支最值得盯:它先以 1.25、2.24、2.64 三个缩放系数分别 new 出 EG/EU/GU 子组,再包一层父组交给 expert.Add(),相当于把三套权重不同的子系统并成一个复合信号源。 其余分支走的是「用哪组删哪组」的懒回收逻辑。比如选了 VAG_EURGBP 就只 Add 欧英组,顺手把 EU、GU 的指针 delete 掉;选 GBPUSD 则保留 GU、释放 EU 和 EG。这种写法在 MT5 回测里能省下约 30% 的空闲对象占用,但手滑漏删就会内存泄漏。 default 分支直接 return INIT_FAILED,意味着枚举值写错时 EA 连加载都不让。初始化末尾的 expert.Load() 负责恢复上一次退市的状态,成功才返 INIT_SUCCEEDED——开 MT5 把这段贴进 OnInit 尾部,改几个 scale_ 数值就能对比复合组与单组的净值曲线差异。外汇与贵金属品种波动剧烈,多组叠加不意味风险分散,实盘前务必用策略测试器跑足历史样本。
FOREACH(strategiesEG, class="kw">delete strategiesEG[i]); FOREACH(strategiesGU, class="kw">delete strategiesGU[i]); break; } case VAG_GBPUSD: { expert.Add(CVirtualStrategyGroup(strategiesGU, scale_)); FOREACH(strategiesEU, class="kw">delete strategiesEU[i]); FOREACH(strategiesEG, class="kw">delete strategiesEG[i]); break; } case VAG_EURGBPUSD_9: { expert.Add(CVirtualStrategyGroup(strategies, scale_)); break; } case VAG_EURGBPUSD_3_3_3: { class=class="str">"cmt">// Create a group of three strategy groups CVirtualStrategyGroup *groups[] = { new CVirtualStrategyGroup(strategiesEG, class="num">1.25), new CVirtualStrategyGroup(strategiesEU, class="num">2.24), new CVirtualStrategyGroup(strategiesGU, class="num">2.64) }; expert.Add(CVirtualStrategyGroup(groups, scale_)); break; } class="kw">default: class="kw">return(INIT_FAILED); } class=class="str">"cmt">// Load the previous state if available expert.Load(); class="kw">return(INIT_SUCCEEDED); }
用缩放因子压住组合回撤
拿 EURGBP 上跑的三套同策略副本先试水:单实例最大相对回撤 10%,但三实例组合后降到 8%。回撤空间腾出来了,仓位就能加——把缩放因子从 1 调到 1.25(10% ÷ 8%),组合回撤正好回到 10% 边界。 EURUSD 与 GBPUSD 两组照同样逻辑反推,分别落到 2.24 和 2.64 的缩放因子。把这三组各自标准化后的虚拟策略组再套一层,代码里就是直接 new 出三个带因子的 CVirtualStrategyGroup 塞进数组。 九实例一把梭(第四组)时,Scale=1 回撤仅 3%,因子可拉到 3.3 才触 10% 红线。但换种拼法——先按品种归三组、再叠成总组(第五组)——同样 Scale=1 时余额更高却亏 4.57%。把第五组因子推到 2.18 封住 10% 回撤后,测试期内利润比第四组翻倍有余。 第五组挂 FixedBalance=10000 重跑,净值峰值亏损绝对值 995 美元,占账户约 10%,证实资金管理器没失准。绿色框标出的零增长内期从一个月拖到半年,破法只有一条:往更多品种和周期铺实例,把相关性打散。外汇与贵金属组合策略仍属高风险,回撤控制只降低概率不改尾部风险。
class=class="str">"cmt">// Create a group of three strategy groups CVirtualStrategyGroup *groups[] = { new CVirtualStrategyGroup(strategiesEG, class="num">1.25), new CVirtualStrategyGroup(strategiesEU, class="num">2.24), new CVirtualStrategyGroup(strategiesGU, class="num">2.64) };
◍ 回测里的回撤只能信一半
给不同初始余额跑同一个 EA,就能人为控制各策略实例分到的仓位权重。余额给得多的实例会开更大的仓,给得少的实例持仓自然缩水。靠反复测试,往往能挑出一组勉强符合你预设最大回撤阈值的参数。 但回撤这件事只有在你跑过的样本里才看得见。换到没参与优化的时间段启动 EA,回撤可能比测试时更高,也可能莫名变低,没有先验保证。每个策略实例都得自己掂量:测试结论能信几分、实盘里怎么用。 评论区有个细节值得记:用 (3+3+3) 分组加缩放比例 2.18 跑 SimpleVolumesExpert,日志有虚拟单但测试器不出真实单,作者回说是初始余额没到 10000 美元门槛。余额不够时并不是每个虚拟仓位都会映射成真单。 [CODE] //+------------------------------------------------------------------+ //| 确定虚拟位置的实际大小 //+------------------------------------------------------------------+ double CMoney::Volume(CVirtualOrder *p_order) { // 申请该虚拟仓位的标准化策略平衡 double fittedBalance = p_order.FittedBalance(); // 如果等于 0,则实际卷等于虚拟卷 if(fittedBalance == 0.0) { return p_order.Volume(); } // 否则,我们将得出贸易总余额的值 double totalBalance = s_fixedBalance > 0 ? s_fixedBalance : AccountInfoDouble(ACCOUNT_BALANCE); // 用虚拟体积返回计算出的实际体积 return p_order.Volume() * totalBalance * s_depoPart / fittedBalance ; } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+
| // | 构造函数 |
|---|
//+------------------------------------------------------------------+ CVirtualStrategy::CVirtualStrategy(double p_fittedBalance = 0, double p_fixedLot = 0.01) : m_fittedBalance(p_fittedBalance), m_fixedLot(p_fixedLot) {} // 如果等于 0,则实际卷等于虚拟卷 [/CODE] 上面这段是仓位映射的核心。逐行看:先取虚拟单的 fittedBalance;为 0 时实盘手数直接等于虚拟手数,不做缩放;非 0 时取总余额(固定值或账户实际余额),最后实盘体积 = 虚拟体积 × 总余额 × 存款占比 / fittedBalance。 CVirtualStrategy 构造里 p_fittedBalance 默认 0、p_fixedLot 默认 0.01。想让可变手数生效,必须给实例传 m_fittedBalance > 0,否则会掉回默认值 0,手数永远不缩放。外汇和贵金属杠杆高,这种映射误差会放大穿仓风险,上线前务必在策略测试器用不同余额验证一遍。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 确定虚拟位置的实际大小 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CMoney::Volume(CVirtualOrder *p_order) { class=class="str">"cmt">// 申请该虚拟仓位的标准化策略平衡 class="type">class="kw">double fittedBalance = p_order.FittedBalance(); class=class="str">"cmt">// 如果等于 class="num">0,则实际卷等于虚拟卷 if(fittedBalance == class="num">0.0) { class="kw">return p_order.Volume(); } class=class="str">"cmt">// 否则,我们将得出贸易总余额的值 class="type">class="kw">double totalBalance = s_fixedBalance > class="num">0 ? s_fixedBalance : AccountInfoDouble(ACCOUNT_BALANCE); class=class="str">"cmt">// 用虚拟体积返回计算出的实际体积 class="kw">return p_order.Volume() * totalBalance * s_depoPart / fittedBalance ; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 构造函数| class=class="str">"cmt">//+------------------------------------------------------------------+ CVirtualStrategy::CVirtualStrategy(class="type">class="kw">double p_fittedBalance = class="num">0, class="type">class="kw">double p_fixedLot = class="num">0.01) : m_fittedBalance(p_fittedBalance), m_fixedLot(p_fixedLot) {} class=class="str">"cmt">// 如果等于 class="num">0,则实际卷等于虚拟卷
「成交量策略的构造与初始化落点」
成交量策略类 CSimpleVolumesStrategy 继承自 CVirtualStrategy,构造函数把交易品种、周期、信号周期、偏差与加仓偏差、挂单距离、止损止盈点数、订单有效期、最大订单数以及拟合余额一次性传入。其中 p_fittedBalance 默认 0,代表不启用资金拟合;一旦非 0,就会在初始化列表里交给 CVirtualStrategy(p_fittedBalance, 0.01) 处理,0.01 是该类固定的基础手数系数。 OnInit 里先通过 CMoney::DepoPart(expectedDrawdown_ / 10.0) 把预期回撤缩放成仓位占比,再用 FixedBalance 锁定计算基准。随后 new 一个 CVirtualAdvisor,名字拼上分组枚举,保证多实例在 MT5 终端里不重名。 实际填充策略数组时,EURGBP H1 上跑了两组参数:一组信号周期 13、偏差 0.3、拟合余额 1600;另一组周期 17、偏差 1.7、拟合余额 900。两组止损分别 465 与 220 点,最大订单数都是 3。外汇与贵金属杠杆品种波动剧烈,这类多实例网格在 EURGBP 上可能放大滑点风险,开 MT5 用策略测试器跑这两组能直接看到拟合余额对仓位的压缩效果。
if(fittedBalance == class="num">0.0) { class="kw">return p_order.Volume(); } class CSimpleVolumesStrategy : class="kw">public CVirtualStrategy { ... class="kw">public: class=class="str">"cmt">//--- 公共方法 CSimpleVolumesStrategy( class="type">class="kw">string p_symbol, ENUM_TIMEFRAMES p_timeframe, class="type">int p_signalPeriod, class="type">class="kw">double p_signalDeviation, class="type">class="kw">double p_signaAddlDeviation, class="type">int p_openDistance, class="type">class="kw">double p_stopLevel, class="type">class="kw">double p_takeLevel, class="type">int p_ordersExpiration, class="type">int p_maxCountOfOrders, class="type">class="kw">double p_fittedBalance = class="num">0 ); class=class="str">"cmt">// 构造函数 ... }; ... class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 构造函数| class=class="str">"cmt">//+------------------------------------------------------------------+ CSimpleVolumesStrategy::CSimpleVolumesStrategy( class="type">class="kw">string p_symbol, ENUM_TIMEFRAMES p_timeframe, ... class="type">int p_maxCountOfOrders, class="type">class="kw">double p_fittedBalance = class="num">0) : class=class="str">"cmt">// 初始化列表 CVirtualStrategy(p_fittedBalance, class="num">0.01), m_symbol(p_symbol), m_timeframe(p_timeframe), ... } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 专家初始化函数| class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">// 在资本管理类中设置参数 CMoney::DepoPart(expectedDrawdown_ / class="num">10.0); CMoney::FixedBalance(fixedBalance_); class=class="str">"cmt">// 创建使用虚拟仓位的智能交易系统 expert = new CVirtualAdvisor(magic_, "SimpleVolumes_" + EnumToString(group_)); class=class="str">"cmt">// 创建并填充包含所有策略实例的数组 CVirtualStrategy *strategies[] = { new CSimpleVolumesStrategy("EURGBP", PERIOD_H1, class="num">13, class="num">0.3, class="num">1.0, class="num">0, class="num">10500, class="num">465, class="num">1000, class="num">3, class="num">1600), new CSimpleVolumesStrategy("EURGBP", PERIOD_H1, class="num">17, class="num">1.7, class="num">0.5, class="num">0, class="num">16500, class="num">220, class="num">1000, class="num">3, class="num">900),
别急着下结论
上面这段 CSimpleVolumesStrategy 的实例化只给了一行参数,EURGBP 在 H1 周期下用了 51 期窗口、0.5 阈值系数、1.1 量能倍率,后面还跟了 19500/370/22000 这类挂单距离与止损点数,以及 1600 的尾部缓冲值。 把这些数直接丢进 MT5 跑,你能看到策略在横盘突破时的触发频率,但样本外表现会随点差滑点漂移,贵金属和交叉盘都是高波动品种,回测顺滑不等于实盘能复刻。 真正要验证的是 51 期窗口和 0.5 系数组合在 EURGBP 上的误触发率,建议先关掉自动交易手跑两周再谈参数微调。
new CSimpleVolumesStrategy("EURGBP", PERIOD_H1, class="num">51, class="num">0.5, class="num">1.1, class="num">0, class="num">19500, class="num">370, class="num">22000, class="num">3, class="num">1600),