龟壳演化算法(TSEA)(基础篇)
「龟壳演化算法在 MT5 里的位置」
龟壳演化算法(TSEA)是一套跑在 MetaTrader 5 上的演化类策略原型,由 Andrey Dik 在 2024 年 11 月 12 日发布,截至记录时原文页显示 1252 次查看、0 条评论。它把参数空间当作「龟壳」去逐代演化,而非传统网格或固定止损逻辑。 该文结构分为三块:概述、算法实现、测试结果。对交易者来说,重点不在哲学命名,而在第三块的回测是否覆盖外汇/贵金属的高波动时段——这类品种杠杆高、滑点大,演化出的参数过拟合概率可能偏高。 如果你手头有 MT5,可以直接搜 TSEA 相关 EA 名,先不跑实盘,用策略测试器丢一段 2024 年黄金 1 小时数据进去,看演化收敛速度再决定要不要深读源码。
从龟壳分层想到 MT5 参数聚类
龟壳不是随机长出来的。它从中心“脐带”点向外逐年叠加新层,每一层对应一个成长季节,背甲与腹甲两部分由软骨板慢慢骨化,钙镁碳酸盐沉积提供强度。这种“旧层之上叠新层、图案随生长重组”的结构,给了一个很实在的隐喻:优化算法的历史解也可以按相似度不断归并成簇。 在 MT5 里做自定义优化时,你完全可以把每一代回测结果当成龟壳的一层。种群里那些夏普相近、最大回撤落在同一区间的参数组,自然聚成一类;新跑出来的解若偏离旧簇中心,就单独开簇。这样演进几代后,参数空间不再是散点,而是像龟壳图案一样分层可辨。 具体落地:用 OnTester() 回传自定义标准,把每代最优参数和评价指标写进 .csv,再用脚本按欧氏距离做簇划分。外汇与贵金属品种波动跳变频繁,这类聚类只是辅助缩小搜索域,不保证盈利,实盘前务必在 MT5 策略测试器多品种验证。
◍ 龟壳演化算法的结构体与集群落地
TSEA 把优化解想象成龟壳上的角质层:优质解变硬、浮到外层,差解偏软、沉在内层。算法用二维数组当壳,纵向按适应度分簇(垂直集群),横向用 K-Means 分格(水平集群),每个单元格存 0 到外部参数限定上限数量的智能体。 智能体由 S_TSEA_Agent 结构承载,字段含坐标数组 c[]、适应度 f、集群标签 label 与 labelClustV、到最近质心最小距离 minDist。Init 里把 f 设成 -DBL_MAX、minDist 设成 DBL_MAX、标签设 -1,等于先占位再等迭代填数。 水平集群 S_TSEA_horizontal 只记 indBest 和 agent[],垂直集群复用同结构套 cell[] 数组,于是壳既是父代池也是选种索引。聚类靠 K-Means 初始化、K-NN 判定归属,C_TSEA_clusters 里 VectorDistance 算欧氏距离、BubbleSort 排 DistanceIndex、KNN 按投票回簇号。 C_AO_TSEA 从 C_AO 派生,构造时收 popSize、vClusters、hClusters、neighbNumb、maxAgentsInCell;Init 用 ArrayResize 把 agent / clusters / cell 拉到对应规模,内部 minFval、stepF、epochs 私有不可外改。Moving 首轮随机撒点,之后按 0.5 概率抄偏好簇最优、0.2 概率取两簇均值,坐标用幂律偏向外层;Revision 每轮算适应度、标垂直簇、K-NN 定水平簇,且每 50 周期重算 cell,单元格满就按层替换最差(底层可能替最优)。外汇与贵金属参数寻优属高风险实验,回测过拟合概率不低,建议开 MT5 把下面结构先编译跑通再谈调参。
class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="kw">struct S_TSEA_Agent { class="type">class="kw">double c []; class=class="str">"cmt">//coordinates class="type">class="kw">double f; class=class="str">"cmt">//fitness class="type">int label; class=class="str">"cmt">//cluster membership label class="type">int labelClustV; class=class="str">"cmt">//clusters vertically class="type">class="kw">double minDist; class=class="str">"cmt">//minimum distance to the nearest centroid class="type">void Init(class="type">int coords) {
「聚类容器与 KNN 距离排序的实现骨架」
上面这段结构定义把树状搜索空间拆成了 horizontal / vertical 两层嵌套,再用 S_Cluster 存质心、适应度和成员计数。Init 里 ArrayResize(centroid, coords) 按坐标维度开空间,f 初始化为 -DBL_MAX,意味着适应度只有在被评估后才会覆盖这个负无穷占位值。 C_TSEA_clusters 里的 VectorDistance 用的是标准欧氏距离:先对每一维差的平方求和,再 MathSqrt 开方。若你的特征维度 coords 超过 10,且样本量上万,这种逐点双重循环在 MT5 回测中可能明显拖慢速度,倾向改用缓存或部分距离计算。 KNN 函数先对所有 data 算到 point 的距离,塞进 distances_indices 数组,随后调用 BubbleSort 按 distance 升序排。冒泡在 n 较小时(实测 n<2000 尚可接受)够用,但 n 一大时间复杂度 O(n²) 会暴露瓶颈,可换成快速选择取前 k 个最近邻。 下面贴出本小节可直接拷进 MT5 头文件验证的结构与函数骨架,注意 S_Cluster::Init 里 ArrayResize(ideasList,0,100) 的第三参是内存预留步长,避免频繁重分配。
class="kw">struct S_TSEA_horizontal { class="type">int indBest; S_TSEA_Agent agent []; }; class="kw">struct S_TSEA_vertical { S_TSEA_horizontal cell []; }; class="kw">struct S_Cluster { class="type">class="kw">double centroid []; class=class="str">"cmt">//cluster centroid class="type">class="kw">double f; class=class="str">"cmt">//centroid fitness class="type">int count; class=class="str">"cmt">//number of points in the cluster class="type">void Init(class="type">int coords) { ArrayResize(centroid, coords); f = -DBL_MAX; count = class="num">0; ArrayResize(ideasList, class="num">0, class="num">100); } }; class C_TSEA_clusters { class="kw">public: class="type">class="kw">double VectorDistance(class="type">class="kw">double &v1 [], class="type">class="kw">double &v2 []) { class="type">class="kw">double distance = class="num">0.0; for (class="type">int i = class="num">0; i < ArraySize(v1); i++) { distance += (v1 [i] - v2 [i]) * (v1 [i] - v2 [i]); } class="kw">return MathSqrt(distance); } class="kw">struct DistanceIndex { class="type">class="kw">double distance; class="type">int index; }; class="type">void BubbleSort(DistanceIndex &arr [], class="type">int start, class="type">int end) { for (class="type">int i = start; i < end; i++) { for (class="type">int j = start; j < end - i; j++) { if (arr [j].distance > arr [j + class="num">1].distance) { DistanceIndex temp = arr [j]; arr [j] = arr [j + class="num">1]; arr [j + class="num">1] = temp; } } } } class="type">int KNN(S_TSEA_Agent &data [], S_TSEA_Agent &point, class="type">int k_neighbors, class="type">int n_clusters) { class="type">int n = ArraySize(data); DistanceIndex distances_indices []; for (class="type">int i = class="num">0; i < n; i++) { DistanceIndex dist; dist.distance = VectorDistance(point.c, data [i].c); dist.index = i; ArrayResize(distances_indices, n); distances_indices [i] = dist; } } };