开发多币种 EA 交易(第 6 部分):自动选择实例组·综合运用
🤖

开发多币种 EA 交易(第 6 部分):自动选择实例组·综合运用

(3/3)·手工筛2到5万组参数凑10到20个好实例,三级分组已是体力极限,本篇用最简代码破局

含代码示例偏理论 第 3/3 篇
手工把数万组优化结果挑成可协作的小组,做到三级分组基本就耗光耐心。先别急着写复杂系统,用最便宜的脚本跑通假设,才知道自动化到底值不值。

用遗传算法跑出更优参数组

把交易策略参数集写成 CSV 丢给 EA,启动优化时用遗传算法、以「利润除以 10% 回撤归一化」为用户标准。测试期锁 2018–2022(含 2022),本地网络开 13 个测试代理,标准遗传优化跑满 1 万次以上耗时约 9 小时。 结果出乎意料地压过基线:自动遴选最优组利润 24,800 美元,人工组仅 15,200 美元,多出一倍半有余,且全程零人工干预。外汇/贵金属策略回测优异不代表实盘概率同向,杠杆品种回撤可能超预期。 细看优化表头部会发现前五组结果几乎一致,只差一两枚次要参数。根因是原 CSV 里本就有多组同结果、异次要参数的集合;优化器把它们拆成两组后仍算出相同结果,变相把「相同组合」重复算了一次,还削弱了本该靠多样性压回撤的效果。 可顺手做一处改动:把原文件中结果完全相同的参数集合并成一组再喂给优化器,省掉重复 pass,也让组别差异更干净。MT5 里直接改 CSV 再跑一轮就能对比代理耗时与顶部利润分布。

◍ 用 k-means 把参数集拆成互斥集群

把原始 CSV 里所有策略参数集按结果相似性分组,能直接砍掉冗余组合。这里取 signalPeriod_、signalDeviation_、signaAddlDeviation_、openDistance_、stopLevel_、takeLevel_ 六列做输入,用现成 k-means 聚成 64 个群集,文件随后多出一列 cluster 编号。 下面这段 Python 是实际跑聚类的核心:读表、拟合、写回。df.iloc[:, [12,13,14,15,17]] 对应上面六列在文件里的位置,n_clusters=64 即群集数,random_state=42 固定随机种子保证可复现。 import pandas as pd from sklearn.cluster import KMeans df = pd.read_csv('Params_SV_EURGBP_H1.csv') kmeans = KMeans(n_clusters=64, n_init='auto', random_state=42).fit(df.iloc[:, [12,13,14,15,17]]) df['cluster'] = kmeans.labels_ df.to_csv('Params_SV_EURGBP_H1-with_cluster.csv', index=False) EA 侧要新建 OptGroupClusterExpert.mq5。初始化时把每个参数集对应的群集号塞进 setClusters 集合,只有 setClusters.Count() == count_(所有群集互不相同)才允许启动这组运行;同时参数字符串末尾带了无关群集号,得在传给策略构造函数前用 CSVStringGet 截掉(取 0~11 列)。 这种排布有明显短板:遗传算法初始种群里若有两个以上相同参数集指数的个体,种群会快速退化、优化提前终止。再跑一次可能就顺利到终点,找到还不错的参数。 实测上,混用输入参数集或压低组内策略数量,能抬升避免退化的概率。不论怎么调,相对不聚类的优化,整体耗时降到原来的 1/1.5 到 1/2 之间。外汇与贵金属品种波动剧烈,此类优化仅降低计算成本,不预示任何盈利概率。

MQL5 / C++
class="kw">import pandas as pd
from sklearn.cluster class="kw">import KMeans
df = pd.read_csv(&class="macro">#x27;Params_SV_EURGBP_H1.csv&class="macro">#x27;)
kmeans = KMeans(n_clusters=class="num">64, n_init=&class="macro">#x27;auto&class="macro">#x27;, 
                random_state=class="num">42).fit(df.iloc[:, [class="num">12,class="num">13,class="num">14,class="num">15,class="num">17]])
df[&class="macro">#x27;cluster&class="macro">#x27;] = kmeans.labels_
df.to_csv(&class="macro">#x27;Params_SV_EURGBP_H1-with_cluster.csv&class="macro">#x27;, index=False)
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit() {
  
    ... 
  class=class="str">"cmt">// Multiplicities for parameter and cluster set indices
  CHashSet<class="type">class="kw">string> setIndexes;
  CHashSet<class="type">class="kw">string> setClusters;
  class=class="str">"cmt">// Add all indices and clusters to the multiplicities
  FOREACH(indexes, {
    setIndexes.Add(indexes[i]);
    class="type">class="kw">string cluster = CSVStringGet(params[StringToInteger(indexes[i])], class="num">11, class="num">12);
    setClusters.Add(cluster);
  });
  class=class="str">"cmt">// Report an error if
  if(count_ < class="num">1 || count_ > class="num">8       class=class="str">"cmt">// number of instances not in the range class="num">1 .. class="num">8
     || setIndexes.Count()  != count_ class=class="str">"cmt">// not all indexes are unique
     || setClusters.Count() != count_ class=class="str">"cmt">// not all clusters are unique
     ) {
    class="kw">return INIT_PARAMETERS_INCORRECT;
  }
  
    ... 
  FOREACH(indexes, {
    class=class="str">"cmt">// Remove the cluster number from the parameter set class="type">class="kw">string
    class="type">class="kw">string param = CSVStringGet(params[StringToInteger(indexes[i])], class="num">0, class="num">11);
    class=class="str">"cmt">// Add a strategy with a set of parameters with a given index
    APPEND(strategies, new CSimpleVolumesStrategy(param))
  });
  class=class="str">"cmt">// Form and add a group of strategies to the EA
  expert.Add(CVirtualStrategyGroup(strategies, scale_));
  class="kw">return(INIT_SUCCEEDED);
}

「只留每簇一个样本怎么玩」

另一种抑制种群退化的思路是:对每个聚类只保留一个代表集合,再喂给优化器。上面那段 Python 用 KMeans 把参数空间切成 64 簇,每簇按夏普比排个序取最后一个,导出的 CSV 就能直接丢进前面写好的任一个 EA 里跑。 若发现筛完剩的样本太少,可以加大簇数,或从一个簇里多抽几个集合。按 64 组自动筛选的优化结果和前两种办法大体接近,其中一组的成绩甚至超过了之前所有组——但这更像是运气,而非上限设定更优。 最佳小组的单次通过成绩表里能看出,各组高度重复,区别仅在于策略参数集的索引排列顺序不同。要在 EA 里硬控「索引必须递增」能去重,可种群退化太快,遗传优化基本跑不动;哪怕从 64 个集合里挑 8 个做全枚举,通过次数也爆炸,得改掉把 EA 迭代输入转成索引的方式,不过那是后话。 实盘前注意:外汇与贵金属杠杆高、回撤可能超预期。用单簇样本优化时,头几分钟就能捞出和人工筛选相近的结果(利润约 15,000 美元量级),但真要冲最佳,得熬到优化快结束。

MQL5 / C++
class="kw">import pandas as pd
from sklearn.cluster class="kw">import KMeans
df = pd.read_csv(&class="macro">#x27;Params_SV_EURGBP_H1.csv&class="macro">#x27;)
kmeans = KMeans(n_clusters=class="num">64, n_init=&class="macro">#x27;auto&class="macro">#x27;, 
               random_state=class="num">42).fit(df.iloc[:, [class="num">12,class="num">13,class="num">14,class="num">15,class="num">17]])
df[&class="macro">#x27;cluster&class="macro">#x27;] = kmeans.labels_
df = df.sort_values([&class="macro">#x27;cluster&class="macro">#x27;, &class="macro">#x27;Sharpe Ratio&class="macro">#x27;]).groupby(&class="macro">#x27;cluster&class="macro">#x27;).agg(&class="macro">#x27;last&class="macro">#x27;).reset_index()
clusters = df.cluster
df = df.iloc[:, class="num">1:]
df[&class="macro">#x27;cluster&class="macro">#x27;] = clusters
df.to_csv(&class="macro">#x27;Params_SV_EURGBP_H1-one_cluster.csv&class="macro">#x27;, index=False

把工具请下神坛

回测数据摆在那:自动选参组相对人工挑参,盈利结果更优,代价是跑优化更耗时间,但人不用盯场,还能靠加测试代理把算力成本压下来。 下一步自然是把筛出的优组直接交给 EA 自动建组。代码层改动不大,核心只是改读取参数的方式,并支持多策略组并存;更合理的做法是把优化结果统一存进数据库,而非散落成一堆独立文件。 若能在优化测试期之外,单独拉优组做样本外观察,价值会更高——这部分留到后续文章再拆。外汇与贵金属属高风险品种,自动化仅降低人工偏差,不消除回撤可能。

把重复劳动交给小布
这些参数导出与初筛的重复动作,小布盯盘的 AIGC 已内置辅助视图,打开对应品种页即可看到归一并分组后的候选集,你只判质量。

常见问题

核心是先验证自动选组能否不降级甚至优于手工基线,避免一上来投入大量开发却证明方向无效,最小成本试错更符合工程节奏。
小布可自动拉取各品种优化结果、按回撤归一并给出候选小组视图,省去手工导XML与逐列补参数的机械步骤,你专注策略判断。
简单组合按既定规则直接抽实例,聚类则先按特征空间划群再组内挑,倾向降低同质化、提升小组协作时的稳健概率。
当某群特征已被覆盖且冗余高时,单实例代表能压低开销,也可能保留该簇对特定行情的暴露,需看回测分布决定。