遗传算法 - 很简单!·综合运用
(3/3)·从基因到种群,手把手把 UGA 嵌进策略测试,避开二进制转换的坑
人工突变里的边界与越界处理
这段人工突变函数(ArtificialMutation)做的是:先随机挑两个父代,再对每个基因位在其取值区间内做偏移扩域后抽样。ReplicationOffset 控制子代基因相对父母区间向外扩张的倍数,代码里用 Minimum=C1-((C2-C1)*ReplicationOffset) 与 Maximum=C2+((C2-C1)*ReplicationOffset) 动态算出搜索边界。 无论父母基因还是扩域后的边界,都强制夹在 RangeMinimum 与 RangeMaximum 之间——任何超出都直接拉回区间端点,避免进化搜索跑出参数合法域。外汇与贵金属 EA 参数优化中,这种硬约束能降低过拟合到非法参数的风险,但模型本身仍可能因样本窗口偏差失效,属高风险操作。 随机数判定用了 MathRand()<16383.5,而 MathRand() 返回 0~32767,意味着约 50% 概率从父母区间左外侧抽样、另 50% 从右外侧抽样,子代有概率探索父母未覆盖的邻域。想验证的话,把 ReplicationOffset 从 0.5 调到 2.0,在 MT5 策略测试器里观察参数分布发散程度的变化。
class="type">void ArtificialMutation( class="type">class="kw">double &child[], class="type">class="kw">double ReplicationOffset ) { class="type">class="kw">double C1=class="num">0.0,C2=class="num">0.0,temp=class="num">0.0,Maximum=class="num">0.0,Minimum=class="num">0.0,p=class="num">0.0; class="type">int address_mama=class="num">0,address_papa=class="num">0; SelectTwoParents(address_mama,address_papa); for(class="type">int i=class="num">1;i<=GeneCount;i++) { C1 = Population[i][address_mama]; C2 = Population[i][address_papa]; if(C1 < RangeMinimum) C1 = RangeMinimum; if(C1 > RangeMaximum) C1 = RangeMaximum; if(C2 < RangeMinimum) C2 = RangeMinimum; if(C2 > RangeMaximum) C2 = RangeMaximum; if(C1>C2) { temp=C1; C1=C2; C2=temp; } Minimum=C1-((C2-C1)*ReplicationOffset); Maximum=C2+((C2-C1)*ReplicationOffset); if(Minimum < RangeMinimum) Minimum = RangeMinimum; if(Maximum > RangeMaximum) Maximum = RangeMaximum; p=MathRand(); if(p<class="num">16383.5) { temp=RNDfromCI(RangeMinimum,Minimum); child[i]= NormalizeDouble(SelectInDiscreteSpace(temp,RangeMinimum,RangeMaximum,Precision,class="num">3),GeneNormalizeDigits); } else { temp=RNDfromCI(Maximum,RangeMaximum); child[i]= NormalizeDouble(SelectInDiscreteSpace(temp,RangeMinimum,RangeMaximum,Precision,class="num">3),GeneNormalizeDigits); } } }
◍ 遗传算子的交叉与亲本抽取
在 MT5 的 EA 遗传优化框架里,单亲与双亲生成是两套独立路径。单亲复制走 SelectOneParent,直接把轮盘赌选出的个体地址塞进 child 数组;双亲交叉则先选一父一母,再随机切一刀换基因。 交叉点用 MathFloor((GeneCount-1)*(MathRand()/32767.5)) 算,MathRand 上限 32767,所以 32767.5 这个分母把随机值压到 [0,1) 区间。切点在第 address_of_gene+1 个基因前抄母亲,之后抄父亲,染色体长度由 GeneCount 决定。 SelectTwoParents 最多重试 10 次,直到 address_mama != address_papa 才 break。这意味着若种群多样性极差,可能退回两个相同个体自交,读者在 MT5 策略测试器里跑多代后会观察到子代方差收敛变慢。 NaturalSelection 内部先 ArrayResize(fit,PopulChromosCount) 再 ArrayInitialize(fit,0.0),适配种群规模动态变化;外汇与贵金属遗传优化属高风险实验,回测拟合不代表实盘概率占优。
for(class="type">int i=class="num">1;i<=GeneCount;i++) { class=class="str">"cmt">//-----------------选择父母------------------------ SelectOneParent(address); class=class="str">"cmt">//-------------------------------------------------------- child[i]=Population[i][address]; } } class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//交叉. class="type">void CrossingOver( class="type">class="kw">double &child[] ) { class=class="str">"cmt">//-----------------------变量------------------------------------- class="type">int address_mama=class="num">0,address_papa=class="num">0; class=class="str">"cmt">//---------------------------------------------------------------------- class=class="str">"cmt">//-----------------选择父母------------------------ SelectTwoParents(address_mama,address_papa); class=class="str">"cmt">//-------------------------------------------------------- class=class="str">"cmt">//决定突破点 class="type">int address_of_gene=(class="type">int)MathFloor((GeneCount-class="num">1)*(MathRand()/class="num">32767.5)); for(class="type">int i=class="num">1;i<=GeneCount;i++) { class=class="str">"cmt">//----复制母亲基因-------- if(i<=address_of_gene+class="num">1) child[i]=Population[i][address_mama]; class=class="str">"cmt">//----复制父亲基因-------- else child[i]=Population[i][address_papa]; } } class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//选择两个亲本. class="type">void SelectTwoParents( class="type">int &address_mama, class="type">int &address_papa ) { class=class="str">"cmt">//-----------------------变量------------------------------------- class="type">int cnt=class="num">1; address_mama=class="num">0;class=class="str">"cmt">//种群中母亲个体的位置 address_papa=class="num">0;class=class="str">"cmt">//种群中父亲个体的位置 class=class="str">"cmt">//---------------------------------------------------------------------- class=class="str">"cmt">//----------------------------选择亲本-------------------------- class=class="str">"cmt">//尝试10次选择不同的亲本. class="kw">while(cnt<=class="num">10) { class=class="str">"cmt">//对于母亲个体 address_mama=NaturalSelection(); class=class="str">"cmt">//对于父亲个体 address_papa=NaturalSelection(); if(address_mama!=address_papa) break; } class=class="str">"cmt">//--------------------------------------------------------------------- } class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//选择一个亲本 class="type">void SelectOneParent( class="type">int &addressclass=class="str">"cmt">//亲本个体在种群中的地址 ) { class=class="str">"cmt">//-----------------------变量------------------------------------- address=class="num">0; class=class="str">"cmt">//---------------------------------------------------------------------- class=class="str">"cmt">//----------------------------选择一个亲本-------------------------- address=NaturalSelection(); class=class="str">"cmt">//--------------------------------------------------------------------- } class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//自然选择. class="type">int NaturalSelection() { class=class="str">"cmt">//-----------------------变量------------------------------------- class="type">int i=class="num">0,u=class="num">0; class="type">class="kw">double p=class="num">0.0,start=class="num">0.0; class="type">class="kw">double fit[][class="num">2]; ArrayResize(fit,PopulChromosCount); ArrayInitialize(fit,class="num">0.0);
「轮盘赌选种与去重实现」
遗传算法里选染色体不能纯随机,得按适应度区间做加权抽取。下面这段先算 delta 做区间偏移,再给每个染色体分配一段 [fit[i][0], fit[i][1]) 的累加区间,最后用 RNDfromCI 落点反查命中的染色体索引 u。 double delta=(Population[0][0]-Population[0][PopulChromosCount-1])*0.01-Population[0][PopulChromosCount-1]; //---------------------------------------------------------------------- for(i=0;i<PopulChromosCount;i++) { fit[i][0]=start; fit[i][1]=start+MathAbs(Population[0][i]+delta); start=fit[i][1]; } p=RNDfromCI(fit[0][0],fit[PopulChromosCount-1][1]); for(u=0;u<PopulChromosCount;u++)
| if((fit[u][0]<=p && p<fit[u][1]) | p==fit[u][1]) |
|---|
break; return(u); 抽完种还得清重复,否则种群多样性塌得快。RemovalDuplicates 用 chromosomeUnique[1000] 标重,初始全置 1 假定无重复,双层循环比对 GeneCount 个基因,只要有一个基因不等就 break,全相等则把后者的标记清 0。 void RemovalDuplicates() { //-----------------------变量------------------------------------- int chromosomeUnique[1000];//保存每个染色体唯一特性的数组: // 0-重复, 1-唯一 ArrayInitialize(chromosomeUnique,1); //假定没有重复 double PopulationTemp[][1000]; ArrayResize (PopulationTemp,GeneCount+1); ArrayInitialize(PopulationTemp,0.0); int Ge =0; //基因索引 int Ch =0; //染色体索引 int Ch2=0; //第二个染色体的索引 int cnt=0; //计数器 //---------------------------------------------------------------------- //----------------------删除重复染色体---------------------------1 //从一对中选择第一个做比较... for(Ch=0;Ch<PopulChromosCount;Ch++) { //如果不是重复的... if(chromosomeUnique[Ch]!=0) { //从一对中选择第二个... for(Ch2=0;Ch2<PopulChromosCount;Ch2++) { if(Ch!=Ch2 && chromosomeUnique[Ch2]!=0) { //相同基因计数器清零 cnt=0; //比较基因. 有相同基因出现 for(Ge=1;Ge<=GeneCount;Ge++) { if(Population[Ge][Ch]!=Population[Ge][Ch2]) break; 在 MT5 里把 GeneCount 设成你 EA 的参数维度(比如 5 个均线周期),PopulChromosCount 给 50~200,跑几百代后观察 chromosomeUnique 里被置 0 的比例;若比例超过 60% 说明交叉变异太弱,外汇与贵金属策略迭代易过早收敛,属高风险调参环节。
class="type">class="kw">double delta=(Population[class="num">0][class="num">0]-Population[class="num">0][PopulChromosCount-class="num">1])*class="num">0.01-Population[class="num">0][PopulChromosCount-class="num">1]; class=class="str">"cmt">//---------------------------------------------------------------------- for(i=class="num">0;i<PopulChromosCount;i++) { fit[i][class="num">0]=start; fit[i][class="num">1]=start+MathAbs(Population[class="num">0][i]+delta); start=fit[i][class="num">1]; } p=RNDfromCI(fit[class="num">0][class="num">0],fit[PopulChromosCount-class="num">1][class="num">1]); for(u=class="num">0;u<PopulChromosCount;u++) if((fit[u][class="num">0]<=p && p<fit[u][class="num">1]) || p==fit[u][class="num">1]) break; class="kw">return(u); } class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//根据 VFF中的排序删除重复染色体 class="type">void RemovalDuplicates() { class=class="str">"cmt">//-----------------------变量------------------------------------- class="type">int chromosomeUnique[class="num">1000];class=class="str">"cmt">//保存每个染色体唯一特性的数组: class=class="str">"cmt">// class="num">0-重复, class="num">1-唯一 ArrayInitialize(chromosomeUnique,class="num">1); class=class="str">"cmt">//假定没有重复 class="type">class="kw">double PopulationTemp[][class="num">1000]; ArrayResize(PopulationTemp,GeneCount+class="num">1); ArrayInitialize(PopulationTemp,class="num">0.0); class="type">int Ge =class="num">0; class=class="str">"cmt">//基因索引 class="type">int Ch =class="num">0; class=class="str">"cmt">//染色体索引 class="type">int Ch2=class="num">0; class=class="str">"cmt">//第二个染色体的索引 class="type">int cnt=class="num">0; class=class="str">"cmt">//计数器 class=class="str">"cmt">//---------------------------------------------------------------------- class=class="str">"cmt">//----------------------删除重复染色体---------------------------class="num">1 class=class="str">"cmt">//从一对中选择第一个做比较... for(Ch=class="num">0;Ch<PopulChromosCount;Ch++) { class=class="str">"cmt">//如果不是重复的... if(chromosomeUnique[Ch]!=class="num">0) { class=class="str">"cmt">//从一对中选择第二个... for(Ch2=class="num">0;Ch2<PopulChromosCount;Ch2++) { if(Ch!=Ch2 && chromosomeUnique[Ch2]!=class="num">0) { class=class="str">"cmt">//相同基因计数器清零 cnt=class="num">0; class=class="str">"cmt">//比较基因. 有相同基因出现 for(Ge=class="num">1;Ge<=GeneCount;Ge++) { if(Population[Ge][Ch]!=Population[Ge][Ch2]) break;
去重后怎么给染色体排座次
遗传算法跑完交叉变异,先得把种群里基因完全一致的染色体剔掉。上面那段逻辑就是双层循环比对:当某条染色体 Ch2 与参照染色体 Ch 的所有基因位都吻合,计数器 cnt 累加到等于 GeneCount,就把 chromosomeUnique[Ch2] 置 0,标记为重复。 去重后用一个 cnt 计数器把唯一染色体搬进 PopulationTemp,再把 PopulChromosCount 改写成实际唯一数,最后回填进主种群数组。这一步直接决定后续排序的样本规模——若初始种群 200 条、重复 40 条,排序只处理 160 条。 排序函数 PopulationRanking 里先给每条染色体建索引并取第 0 行(通常存适应度 VFF)填入 ValueOnIndexes。OptimizeMethod==1 时走冒泡式 while 循环,cnt 初始为 1、每轮无交换则 cnt 保持 0 退出,属于最朴素但零依赖的升序实现。 临时数组 PopulationTemp 被硬性开成 [][1000],GeneCount+1 行通过 ArrayResize 限定。如果你在 MT5 里跑多参数优化,GeneCount 超过 999 会直接数组越界报错,调参前先确认基因维度。
else cnt++; class=class="str">"cmt">//如果相同基因数和总基因数相等 class=class="str">"cmt">//..染色体会被认为是重复的 if(cnt==GeneCount) chromosomeUnique[Ch2]=class="num">0; } } } } class=class="str">"cmt">//计算唯一染色体数量的计数器 cnt=class="num">0; class=class="str">"cmt">//把唯一的染色体复制到临时数组中 for(Ch=class="num">0;Ch<PopulChromosCount;Ch++) { class=class="str">"cmt">//如果染色体是唯一的就复制, 否则到下一个 if(chromosomeUnique[Ch]==class="num">1) { for(Ge=class="num">0;Ge<=GeneCount;Ge++) PopulationTemp[Ge][cnt]=Population[Ge][Ch]; cnt++; } } class=class="str">"cmt">//给 "全部染色体"变量赋值为唯一染色体计数器的值 PopulChromosCount=cnt; class=class="str">"cmt">//把唯一染色体返回到 class=class="str">"cmt">//综合种群的临时存储数组 for(Ch=class="num">0;Ch<PopulChromosCount;Ch++) for(Ge=class="num">0;Ge<=GeneCount;Ge++) Population[Ge][Ch]=PopulationTemp[Ge][Ch]; class=class="str">"cmt">//=================================================================class="num">1 class=class="str">"cmt">//----------------种群排序---------------------------class="num">2 PopulationRanking(); class=class="str">"cmt">//=================================================================class="num">2 } class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//种群排序. class="type">void PopulationRanking() { class=class="str">"cmt">//-----------------------变量------------------------------------- class="type">int cnt=class="num">1, i = class="num">0, u = class="num">0; class="type">class="kw">double PopulationTemp[][class="num">1000]; class=class="str">"cmt">//临时种群 ArrayResize(PopulationTemp,GeneCount+class="num">1); ArrayInitialize(PopulationTemp,class="num">0.0); class="type">int Indexes[]; class=class="str">"cmt">//染色体索引 ArrayResize(Indexes,PopulChromosCount); ArrayInitialize(Indexes,class="num">0); class="type">int t0=class="num">0; class="type">class="kw">double ValueOnIndexes[]; class=class="str">"cmt">//对应染色体索引的VFF ArrayResize(ValueOnIndexes,PopulChromosCount); ArrayInitialize(ValueOnIndexes,class="num">0.0); class="type">class="kw">double t1=class="num">0.0; class=class="str">"cmt">//---------------------------------------------------------------------- class=class="str">"cmt">//填充临时数组 temp2 的索引并且 class=class="str">"cmt">//从排好顺序的数组中复制第一行 for(i=class="num">0;i<PopulChromosCount;i++) { Indexes[i] = i; ValueOnIndexes[i] = Population[class="num">0][i]; } if(OptimizeMethod==class="num">1) { class="kw">while(cnt>class="num">0) { cnt=class="num">0; for(i=class="num">0;i<PopulChromosCount-class="num">1;i++) {
◍ 染色体排序与离散空间取值的实现细节
遗传算法里每一代跑完,都要按适应度把种群排个序,这段代码用冒泡思路在索引数组上做交换,而不是直接搬动二维种群矩阵,能少写不少内存拷贝。 排序方向由外部标志决定:若要求从优到劣,就比 ValueOnIndexes[i] 是否大于后一位,是则交换索引与对应值;反向时逻辑镜像,用小于号判断。每次发生交换 cnt 自增,外层 while(cnt>0) 保证未完全有序就再来一轮,最坏情况比较次数为 (PopulChromosCount-1) 乘上可能的轮数。 排完序后,用 Indexes[u] 作为列号,把 Population 按行映射到 PopulationTemp,再复制回去——这样原种群按适应度重排,后续选育直接从头截取即可。外汇与贵金属参数优化属高风险,回测顺手不代表实盘概率占优。 随机数发生器 RNDfromCI 把 MathRand() 除以 32767.5 映射到 [0,1),再线性拉伸到给定区间;注意除数是 32767.5 而非 32768,使上限端点理论可达概率略高。 SelectInDiscreteSpace 处理参数必须落在离散网格的场景:先防住 InMax<InMin 的边界颠倒,越界直接夹回边界;step 为 0 原样返回。最后用 MathCeil((InMax-InMin)/step) 反算实际步长,避免整数格点除不尽导致漏掉末端值。
if(ValueOnIndexes[i]>ValueOnIndexes[i+class="num">1]) { class=class="str">"cmt">//----------------------- t0 = Indexes[i+class="num">1]; t1 = ValueOnIndexes[i+class="num">1]; Indexes [i+class="num">1] = Indexes[i]; ValueOnIndexes [i+class="num">1] = ValueOnIndexes[i]; Indexes [i] = t0; ValueOnIndexes [i] = t1; class=class="str">"cmt">//----------------------- cnt++; } } } } else { class="kw">while(cnt>class="num">0) { cnt=class="num">0; for(i=class="num">0;i<PopulChromosCount-class="num">1;i++) { if(ValueOnIndexes[i]<ValueOnIndexes[i+class="num">1]) { class=class="str">"cmt">//----------------------- t0 = Indexes[i+class="num">1]; t1 = ValueOnIndexes[i+class="num">1]; Indexes [i+class="num">1] = Indexes[i]; ValueOnIndexes [i+class="num">1] = ValueOnIndexes[i]; Indexes [i] = t0; ValueOnIndexes [i] = t1; class=class="str">"cmt">//----------------------- cnt++; } } } } class=class="str">"cmt">//根据获得的索引建立排好序的数组 for(i=class="num">0;i<GeneCount+class="num">1;i++) for(u=class="num">0;u<PopulChromosCount;u++) PopulationTemp[i][u]=Population[i][Indexes[u]]; class=class="str">"cmt">//把排好序的数组复制回来 for(i=class="num">0;i<GeneCount+class="num">1;i++) for(u=class="num">0;u<PopulChromosCount;u++) Population[i][u]=PopulationTemp[i][u]; } class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//在给定区间内的随机数发生器 class="type">class="kw">double RNDfromCI(class="type">class="kw">double RangeMinimum,class="type">class="kw">double RangeMaximum) { class="kw">return(RangeMinimum+((RangeMaximum-RangeMinimum)*MathRand()/class="num">32767.5));} class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//在离散空间内选择. class=class="str">"cmt">//模式: class=class="str">"cmt">//class="num">1-下方最接近 class=class="str">"cmt">//class="num">2-上方最接近 class=class="str">"cmt">//任何最接近 class="type">class="kw">double SelectInDiscreteSpace( class="type">class="kw">double In, class="type">class="kw">double InMin, class="type">class="kw">double InMax, class="type">class="kw">double step, class="type">int RoundMode ) { if(step==class="num">0.0) class="kw">return(In); class=class="str">"cmt">// 保证边界正确性 if( InMax < InMin ) { class="type">class="kw">double temp = InMax; InMax = InMin; InMin = temp; } class=class="str">"cmt">// 在突破中 - 返回突破的边界 if( In < InMin ) class="kw">return( InMin ); if( In > InMax ) class="kw">return( InMax ); if( InMax == InMin || step <= class="num">0.0 ) class="kw">return( InMin ); class=class="str">"cmt">// 带到指定的尺度中 step = (InMax - InMin) / MathCeil( (InMax - InMin) / step );
「三种取整模式的分档落点」
这段分支直接决定了外部输入值 In 被吸附到网格的哪一条刻度上,区别只在舍入方向。RoundMode=1 向下靠档,用 MathFloor 截去小数;RoundMode=2 向上靠档,用 MathCeil 进一位;其余情况走 MathRound 就近取整。 以 InMin=1.1000、step=0.0025、In=1.1018 为例:模式1得到 1.1000,模式2得到 1.1025,默认模式得到 1.1025(因 0.72 个步长更靠近上档)。在 MT5 里改 RoundMode 这个值,同一笔报价会映射出不同挂单位置,外汇与贵金属杠杆品种下误用可能放大滑点风险。 实盘前建议把三种模式各跑一遍历史 tick,确认你想要的「保守接单」还是「激进接单」倾向,再固化参数。
class="kw">switch( RoundMode ) { case class="num">1: class="kw">return( InMin + step * MathFloor( ( In - InMin ) / step ) ); case class="num">2: class="kw">return( InMin + step * MathCeil( ( In - InMin ) / step ) ); class="kw">default: class="kw">return( InMin + step * MathRound( ( In - InMin ) / step ) ); }
用 UGA 在两个例子里验证进化搜索
把优化问题按染色体语义分成两类:基因型与表型一致时,基因值就是目标函数自变量,例如求 Skin 函数极值;不一致时,基因需要先解释成具体含义再算适应度,例如后面用 ZigZag 替代顶点的问题。 先拿已知答案的 Skin 函数压测算法:区间 [-5,5] 上理论最小值为 -4.3182(位置约 3.07~3.32),最大值为 14.0606(位置约 -3.32~-3.07)。跑完脚本后 Comment() 输出的数值与理论值吻合,说明引擎本身没掉链子。 第二个例子更贴近实战。用 M1 周期 GBPJPY 最近 100 根柱、点差按 80 点(五位报价)计,先枚举 ExtDepth 找 ZZ 最佳参数:ExtDepth=3 时盈利 4077 点,19 个指标顶点;深度加大后顶点数和盈利都降。 真正麻烦的是:100 根柱每根有「最高/最低/无顶点」三种状态,直接枚举是 3^100 ≈ 5.15378e47 种组合。按每秒 1e8 次算,要 1.6e32 年——宇宙年龄都远远不够。这就是典型基因型≠表型:把基因搜值区间定为 [0,5],约定 [0,1] 表最高顶点、[4,5] 表最低顶点、[1,4] 表无顶点。 初代随机种群基本都是负几百点的垃圾,但给「无顶点」个体强行标 -10000000.0 适应度压到最底层,逼进化往有交易的方向走。最终 UGA 只用了 17929 次计算、21.7 秒,找出总盈利 4939 点的替代顶点排列。结论倾向:ZZ 之外确实存在其他入口点,但 GBPJPY 等高杠杆品种波动剧烈,这类历史拟合结果不代表未来可稳定复用,实盘前务必自行回测。
class="macro">#class="kw">property script_show_inputs class=class="str">"cmt">//+----------------------------------------------------------------------+ class="macro">#include "UGAlib.mqh" class="macro">#include "Skin.mqh" class=class="str">"cmt">//测试函数 class=class="str">"cmt">//+----------------------------------------------------------------------+ class=class="str">"cmt">//---------------------------------------------------------------------- class=class="str">"cmt">//----------------------外来参数-------------------------------- class="kw">input class="type">class="kw">string GenofundParam = "----基因池参数----"; class="kw">input class="type">int ChromosomeCount_P = class="num">50; class=class="str">"cmt">//群体中的染色体数 class="kw">input class="type">int GeneCount_P = class="num">2; class=class="str">"cmt">//基因数 class="kw">input class="type">int FFNormalizeDigits_P = class="num">4; class=class="str">"cmt">//适应度符号数 class="kw">input class="type">int GeneNormalizeDigits_P= class="num">4; class=class="str">"cmt">//基因数 class="kw">input class="type">int Epoch_P = class="num">50; class=class="str">"cmt">//无进展世代数 class=class="str">"cmt">//--- class="kw">input class="type">class="kw">string GA_OperatorParam = "----算子参数----"; class="kw">input class="type">class="kw">double ReplicationPortion_P = class="num">100.0; class=class="str">"cmt">//复制比例. class="kw">input class="type">class="kw">double NMutationPortion_P = class="num">10.0; class=class="str">"cmt">//自然突变比例. class="kw">input class="type">class="kw">double ArtificialMutation_P= class="num">10.0; class=class="str">"cmt">//人工突变比例. class="kw">input class="type">class="kw">double GenoMergingPortion_P= class="num">20.0; class=class="str">"cmt">//借用基因比例. class="kw">input class="type">class="kw">double CrossingOverPortion_P= class="num">20.0; class=class="str">"cmt">//交叉比例. class=class="str">"cmt">//--- class="kw">input class="type">class="kw">double ReplicationOffset_P = class="num">0.5; class=class="str">"cmt">//区间边界转换系数 class="kw">input class="type">class="kw">double NMutationProbability_P= class="num">5.0; class=class="str">"cmt">//每个基因突变可能性百分率 class=class="str">"cmt">//--- class="kw">input class="type">class="kw">string OptimisationParam = "----优化参数----"; class="kw">input class="type">class="kw">double RangeMinimum_P = -class="num">5.0; class=class="str">"cmt">//最小搜索范围
◍ 把遗传算法参数灌进 UGA 主循环
这段脚本把外部输入和全局变量对接到通用遗传算法(UGA)里,跑完会打印耗时毫秒数。注意 RangeMaximum_P 默认 5.0、Precision_P 默认 0.0001,意味着搜索空间上限 5、最小步长万分之一,优化方法选 1 代表求最小误差,选其他值则求最大。 OnStart 里先给 Chromosome 数组按 GeneCount+1 扩容并清零,再把复制比例、自然突变比例、人工突变比例、借用基因比例、交叉比例以及区间边界转换系数、单基因突变概率一股脑传给 UGA。跑前用 GetTickCount 记起点,跑完再取终点,Print 出两者差就是本次实现时间(mc)。 脚本尾部还有一组独立 input:History=100 根 K 线、Spred=80.0 点差、Depth=5 一次性深度、loop=true 是否枚举。外汇与贵金属杠杆高、点差跳动快,这组默认值在实盘前建议在 MT5 策略测试器里按品种重设,否则回测误差可能偏大。 别把默认点差当真实成本 Spred=80.0 只是脚本占位,黄金实际点差常在 20~50 点间跳,欧美也可能跌破 10 点;直接拿 80 去跑 UGA,筛选出的参数集在真账户上失效概率偏高。开 MT5 把 Spred 改成经纪商实时值再跑一遍更稳。
class="kw">input class="type">class="kw">double RangeMaximum_P = class="num">5.0; class=class="str">"cmt">//最大搜索范围 class="kw">input class="type">class="kw">double Precision_P = class="num">0.0001;class=class="str">"cmt">//所需的精确度 class="kw">input class="type">int OptimizeMethod_P = class="num">1; class=class="str">"cmt">//优化方法:class="num">1-最小,其他-最大 class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//----------------------全局变量----------------------------- class="type">class="kw">double ERROR=class="num">0.0;class=class="str">"cmt">//基因中的平均错误 class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//--------------------------程序主体-------------------------------- class="type">int OnStart() { class=class="str">"cmt">//-----------------------变量------------------------------------- class=class="str">"cmt">//准备 UGA 的全局变量 ChromosomeCount=ChromosomeCount_P; class=class="str">"cmt">//群体中的染色体数 GeneCount =GeneCount_P; class=class="str">"cmt">//基因数 RangeMinimum =RangeMinimum_P; class=class="str">"cmt">//最小搜索范围 RangeMaximum =RangeMaximum_P; class=class="str">"cmt">//最大搜索范围 Precision =Precision_P; class=class="str">"cmt">//搜索步长 OptimizeMethod =OptimizeMethod_P; class=class="str">"cmt">//class="num">1-最小, 其他 - 最大 FFNormalizeDigits = FFNormalizeDigits_P; class=class="str">"cmt">//适应度小数点位数 GeneNormalizeDigits = GeneNormalizeDigits_P;class=class="str">"cmt">//基因小数点位数 ArrayResize(Chromosome,GeneCount+class="num">1); ArrayInitialize(Chromosome,class="num">0); Epoch=Epoch_P; class=class="str">"cmt">//无进展世代数 class=class="str">"cmt">//---------------------------------------------------------------------- class=class="str">"cmt">//局部变量 class="type">int time_start=GetTickCount(),time_end=class="num">0; class=class="str">"cmt">//---------------------------------------------------------------------- class=class="str">"cmt">//运行 UGA 主函数 UGA( ReplicationPortion_P, class=class="str">"cmt">//复制比例. NMutationPortion_P, class=class="str">"cmt">//自然突变比例. ArtificialMutation_P, class=class="str">"cmt">//人工突变比例. GenoMergingPortion_P, class=class="str">"cmt">//借用基因比例. CrossingOverPortion_P,class=class="str">"cmt">//交叉比例. class=class="str">"cmt">//--- ReplicationOffset_P, class=class="str">"cmt">//区间边界转换系数 NMutationProbability_Pclass=class="str">"cmt">//每个基因突变可能性百分率 ); class=class="str">"cmt">//---------------------------------- time_end=GetTickCount(); class=class="str">"cmt">//---------------------------------- Print(time_end-time_start," mc - 实现时间"); class=class="str">"cmt">//---------------------------------- class="kw">return(class="num">0); } class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class="macro">#class="kw">property script_show_inputs class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//----------------------外来参数-------------------------------- class="kw">input class="type">int History=class="num">100; class="kw">input class="type">class="kw">double Spred =class="num">80.0; class="kw">input class="type">int Depth =class="num">5; class=class="str">"cmt">//为“一次性”使用 class="kw">input class="type">bool loop =true;class=class="str">"cmt">//是否使用枚举 class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//————————————————————————————————————————————————————————————————————————
「用循环扫不同深度下的之字线极值」
这段程序主体干的事,是把 ZigZag 指标在不同 depth 参数下的峰值序列抓出来,算一波总波动点数。外汇和贵金属波动受杠杆与跳空影响,这类统计只反映历史形态,实盘复制有高风险,结论都带概率性。 核心思路是从 depth=3 起逐步加到 200,每档都用 iCustom 挂系统自带的 ZigZag,复制 History 根 K 线的缓冲区。若 100 秒内 BarsCalculated 仍不大于 0 就跳过等待,复制失败直接 Print 错误并 return。 提取峰值时只保留 NormalizeDouble 后大于 0 的缓冲区值,塞进 PeaksOfZigzag 数组;随后把相邻峰值差换算成点数除以 Point 再减 Spred(点差),累加得 PipsSum。当 PeaksCount 小于等于 2 时 flag 置 false 退出循环,否则打印 depth、峰值数和 PipsSum 并 depth+1 继续。 你在 MT5 里把 History 和 Spred 先按品种设好,跑一遍就能看到不同 ZigZag 灵敏度对应的历史波动总量,用来判断哪一档深度更贴合你盯的波段节奏。
class="type">void OnStart() { class="type">class="kw">double ZigzagBuffer [];class=class="str">"cmt">//保存之字线指标缓冲区 class="type">class="kw">double PeaksOfZigzag[];class=class="str">"cmt">//保存之字线极值数值 class="type">int Zigzag_handle; class=class="str">"cmt">//指标句柄 ArraySetAsSeries(ZigzagBuffer,true); ArrayResize(PeaksOfZigzag,History); class="type">int depth=class="num">3; class="type">class="kw">double PipsSum=class="num">0.0; class="type">int PeaksCount=class="num">0; class="type">bool flag=true; if(loop==true) { class="kw">while(depth<class="num">200 && flag==true) { Zigzag_handle=iCustom(NULL,class="num">0,"ZigZag",depth); ResetLastError(); for(class="type">int i=class="num">0;i<class="num">100;i++) { if(BarsCalculated(Zigzag_handle)>class="num">0) break; Sleep(class="num">1000); } class="type">int copied=CopyBuffer(Zigzag_handle,class="num">0,class="num">0,History,ZigzagBuffer); if(copied<=class="num">0) { Print("无法复制指标缓冲区. 错误 =",GetLastError()," 复制数=",copied); class="kw">return; } PipsSum=class="num">0.0; PeaksCount=class="num">0; for(class="type">int u=class="num">0;u<History;u++) { if(NormalizeDouble(ZigzagBuffer[u],Digits())>class="num">0.0) { PeaksOfZigzag[PeaksCount]=NormalizeDouble(ZigzagBuffer[u],Digits()); PeaksCount++; } } for(class="type">int V=class="num">0;V<PeaksCount-class="num">1;V++) PipsSum+=NormalizeDouble((MathAbs(PeaksOfZigzag[V]-PeaksOfZigzag[V+class="num">1]))/Point(),Digits())-Spred; if(PeaksCount<=class="num">2) flag=false; else { Print(depth," ",PeaksCount," ",PipsSum); depth+=class="num">1; } } } else {
抓取 ZigZag 拐点并累加波动点数
这段逻辑用来在脚本里加载 ZigZag 指标,并把历史中的有效拐点抽出来统计总波动。注意它用 iCustom 挂的是名为 "ZigZag" 的自定义指标,Depth 作为参数传入,意味着不同拐点深度会生成不同样本。 [CODE] Zigzag_handle=iCustom(NULL,0,"ZigZag",Depth); ResetLastError(); for(int i=0;i<History;i++) { if(BarsCalculated(Zigzag_handle)>0) break; Sleep(1000); } int copied=CopyBuffer(Zigzag_handle,0,0,History,ZigzagBuffer); if(copied<=0) { Print("无法复制指标缓冲区. 错误 =",GetLastError()," 复制数=",copied); return; } [/CODE] 先建句柄再 ResetLastError 清错误码;循环里最多等 History 秒(每次 Sleep(1000))等 BarsCalculated 大于 0,才跳出。CopyBuffer 复制 0 号缓冲区到 ZigzagBuffer,复制数 <=0 就打印错误并退出——这是 MT5 里读自定义指标最常见的防卡死写法。 [CODE] for(int u=0;u<History;u++) { if(NormalizeDouble(ZigzagBuffer[u],Digits())>0.0) { PeaksOfZigzag[PeaksCount]=NormalizeDouble(ZigzagBuffer[u],Digits()); PeaksCount++; } } [/CODE] ZigZag 在非拐点处缓冲值为 0,所以只挑大于 0.0 的存进 PeaksOfZigzag 数组,PeaksCount 同步自增。这样我们就拿到了按时间排列的拐点价格序列。 [CODE] for(int V=0;V<PeaksCount-1;V++) { PipsSum+=NormalizeDouble((MathAbs(PeaksOfZigzag[V]-PeaksOfZigzag[V+1]))/Point(),Digits())-Spred; } Print(Depth," ",PeaksCount," ",PipsSum); [/CODE] 相邻拐点价差除以 Point() 转成点数,减去 Spred(点差)后累加进 PipsSum。最后打印 Depth、拐点数量、总净点数三列——跑一遍就能看某 Depth 下历史波动的粗略容量。外汇和贵金属杠杆高,点差侵蚀真实利润,这组数据只反映历史样本,不预示后续走势。 后面紧接的 input 块是基因算法池的参数:染色体数 100、基因数 100、无进展世代数 50,适配度与基因都保留 0 位小数。改 ChromosomeCount_P 或 Epoch_P 会直接动摇寻优耗时与过拟合概率,建议先在策略测试器里用小样本跑通再放大。
Zigzag_handle=iCustom(NULL,class="num">0,"ZigZag",Depth); ResetLastError(); for(class="type">int i=class="num">0;i<History;i++) { if(BarsCalculated(Zigzag_handle)>class="num">0) break; Sleep(class="num">1000); } class="type">int copied=CopyBuffer(Zigzag_handle,class="num">0,class="num">0,History,ZigzagBuffer); if(copied<=class="num">0) { Print("无法复制指标缓冲区. 错误 =",GetLastError()," 复制数=",copied); class="kw">return; } for(class="type">int u=class="num">0;u<History;u++) { if(NormalizeDouble(ZigzagBuffer[u],Digits())>class="num">0.0) { PeaksOfZigzag[PeaksCount]=NormalizeDouble(ZigzagBuffer[u],Digits()); PeaksCount++; } } for(class="type">int V=class="num">0;V<PeaksCount-class="num">1;V++) { PipsSum+=NormalizeDouble((MathAbs(PeaksOfZigzag[V]-PeaksOfZigzag[V+class="num">1]))/Point(),Digits())-Spred; } Print(Depth," ",PeaksCount," ",PipsSum); class="macro">#class="kw">property script_show_inputs class="macro">#include "UGAlib.mqh" class="kw">input class="type">class="kw">string GenofundParam = "----基因池参数----"; class="kw">input class="type">int ChromosomeCount_P = class="num">100; class=class="str">"cmt">//群体中的染色体数 class="kw">input class="type">int GeneCount_P = class="num">100; class=class="str">"cmt">//基因数 class="kw">input class="type">int FFNormalizeDigits_P = class="num">0; class=class="str">"cmt">//适应度小数点位数 class="kw">input class="type">int GeneNormalizeDigits_P= class="num">0; class=class="str">"cmt">//基因小数点位数 class="kw">input class="type">int Epoch_P = class="num">50; class=class="str">"cmt">//无进展世代数
◍ 把遗传算子参数搬进EA全局变量
这段输入块把遗传算法的算子权重和优化边界全部暴露成 EA 外部参数,方便在 MT5 策略测试器里直接拖滑杆调。复制比例默认 100.0、自然突变与人工突变各 10.0、借用基因与交叉各 20.0,意味着每一代子群构成里复制母体占绝对大头,突变合计才两成,收敛倾向偏稳但容易陷局部极值。 搜索区间写死在 RangeMinimum_P=0.0 到 RangeMaximum_P=5.0,步长 Precision_P=1.0,OptimizeMethod_P=2 代表走最大化适应度(填 1 才是最小化)。点差参数 Spred=80.0 是按老式点数计的常量,跑外汇或贵金属前务必按自己经纪商小数位重算,否则回测盈利可能是虚的。 下面 OnStart 开头这一段干的事很单纯:把 input 的值逐一对拷到全局变量,并给染色体数组按基因数+1维resize。注意 GeneCount 来自另一段输入(本小节未列),若它和 RangeMaximum 不匹配,ArrayResize 出来的矩阵会直接让后续适应度计算越界。
class="kw">input class="type">class="kw">string GA_OperatorParam = "----算子参数----"; class="kw">input class="type">class="kw">double ReplicationPortion_P = class="num">100.0; class=class="str">"cmt">//复制比例. class="kw">input class="type">class="kw">double NMutationPortion_P = class="num">10.0; class=class="str">"cmt">//自然突变比例. class="kw">input class="type">class="kw">double ArtificialMutation_P = class="num">10.0; class=class="str">"cmt">//人工突变比例. class="kw">input class="type">class="kw">double GenoMergingPortion_P = class="num">20.0; class=class="str">"cmt">//借用基因比例. class="kw">input class="type">class="kw">double CrossingOverPortion_P = class="num">20.0; class=class="str">"cmt">//交叉比例. class="kw">input class="type">class="kw">double ReplicationOffset_P = class="num">0.5; class=class="str">"cmt">//区间转换系数 class="kw">input class="type">class="kw">double NMutationProbability_P= class="num">5.0; class=class="str">"cmt">//每个基因突变可能性百分率 class=class="str">"cmt">//--- class="kw">input class="type">class="kw">string OptimisationParam = "----优化参数----"; class="kw">input class="type">class="kw">double RangeMinimum_P = class="num">0.0; class=class="str">"cmt">//最小搜索范围 class="kw">input class="type">class="kw">double RangeMaximum_P = class="num">5.0; class=class="str">"cmt">//最大搜索范围 class="kw">input class="type">class="kw">double Precision_P = class="num">1.0; class=class="str">"cmt">//请求的精确度 class="kw">input class="type">int OptimizeMethod_P = class="num">2; class=class="str">"cmt">//优化方法:class="num">1-最小,其他 -最大 class="kw">input class="type">class="kw">string Other = "----其他----"; class="kw">input class="type">class="kw">double Spred = class="num">80.0; class="kw">input class="type">bool Show = true; class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//----------------------全局变量----------------------------- class="type">class="kw">double Hight []; class="type">class="kw">double Low []; class="type">class="kw">datetime Time []; class="type">class="kw">datetime Ti []; class="type">class="kw">double Peaks []; class="type">bool show; class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//--------------------------程序主体-------------------------------- class="type">int OnStart() { class=class="str">"cmt">//-----------------------变量------------------------------------- class=class="str">"cmt">//为UGA准备全局变量 ChromosomeCount=ChromosomeCount_P; class=class="str">"cmt">//群体中的染色体数 GeneCount =GeneCount_P; class=class="str">"cmt">//基因数 RangeMinimum =RangeMinimum_P; class=class="str">"cmt">//最小搜索范围 RangeMaximum =RangeMaximum_P; class=class="str">"cmt">//最大搜索范围 Precision =Precision_P; class=class="str">"cmt">//搜索步长 OptimizeMethod =OptimizeMethod_P; class=class="str">"cmt">//class="num">1-最小, 其他 - 最大 FFNormalizeDigits = FFNormalizeDigits_P; class=class="str">"cmt">//适应度小数点位数 GeneNormalizeDigits = GeneNormalizeDigits_P;class=class="str">"cmt">//基因小数点位数 ArrayResize(Chromosome,GeneCount+class="num">1); ArrayInitialize(Chromosome,class="num">0); Epoch=Epoch_P; class=class="str">"cmt">//无进展世代数 class=class="str">"cmt">//---------------------------------------------------------------------- class=class="str">"cmt">//准备全局变量
「把高低价与时间序列喂给遗传算法」
这段逻辑出现在指标或 EA 的 OnInit/OnCalculate 收尾处,先把最近 GeneCount+1 根 K 线的高、低、时间拷进数组,并统一改成时间序列索引(下标 0 是最新一根)。注意 CopyHigh/CopyLow/CopyTime 的第三参数都是 0,代表从当前品种当前图表抓起,换周期得自己改第二参数。 ArraySetAsSeries(Hight,true); CopyHigh(NULL,0,0,GeneCount+1,Hight); ArraySetAsSeries(Low,true); CopyLow(NULL,0,0,GeneCount+1,Low); ArraySetAsSeries(Time,true); CopyTime(NULL,0,0,GeneCount+1,Time); 上面三行就是典型的逐列取数。若 GeneCount 设为 500,则实际取 501 个样本点,数组边界要留 +1 余量,否则最后一波迭代会越界。 清场再跑是必须的 ObjectsDeleteAll(0,-1,-1); ChartRedraw(0); 先删光旧对象重绘,避免上一次 UGA 画的峰谷线堆叠。之后调用 UGA(...) 主函数,传入复制比例、自然/人工突变比例、借用基因比例、交叉比例,以及区间边界转换系数和每基因突概率。这些参数直接决定寻优倾向,外汇与贵金属波动大、滑点高,参数过激可能让信号在实盘频繁反转。 计时只为一事:看卡不卡 time_start=GetTickCount(); 跑前记 tick,跑完 time_end-time_start 用 Print 打出毫秒。若返回值经常超过 200 мс,说明 GeneCount 或种群规模在该品种上偏重,可下调 GeneCount 到 300 左右验证帧率。 ServiceFunction 是被 UGA 回调的展示层。show 为 true 时才进 if 块累加 PipsSum 等统计;不需要可视化就把函数体留空,能省掉每代重绘开销。建议你在 MT5 里把 show 初值设为 false 跑一遍,对比 Print 出的执行时间差。
ArraySetAsSeries(Hight,true); CopyHigh(NULL,class="num">0,class="num">0,GeneCount+class="num">1,Hight); ArraySetAsSeries(Low,true); CopyLow(NULL,class="num">0,class="num">0,GeneCount+class="num">1,Low); ArraySetAsSeries(Time,true); CopyTime(NULL,class="num">0,class="num">0,GeneCount+class="num">1,Time); ArrayResize(Ti,GeneCount+class="num">1);ArrayInitialize(Ti,class="num">0); ArrayResize(Peaks,GeneCount+class="num">1);ArrayInitialize(Peaks,class="num">0.0); show=Show; class=class="str">"cmt">//---------------------------------------------------------------------- class=class="str">"cmt">//局部变量 class="type">int time_start=GetTickCount(),time_end=class="num">0; class=class="str">"cmt">//---------------------------------------------------------------------- class=class="str">"cmt">//删除对象并重画图表 ObjectsDeleteAll(class="num">0,-class="num">1,-class="num">1); ChartRedraw(class="num">0); class=class="str">"cmt">//运行UGA主函数 UGA( ReplicationPortion_P, class=class="str">"cmt">//复制比例. NMutationPortion_P, class=class="str">"cmt">//自然突变复制比例. ArtificialMutation_P, class=class="str">"cmt">//人工突变比例. GenoMergingPortion_P, class=class="str">"cmt">//借用基因比例. CrossingOverPortion_P,class=class="str">"cmt">//交叉比例. class=class="str">"cmt">//--- ReplicationOffset_P, class=class="str">"cmt">//区间边界转换系数 NMutationProbability_Pclass=class="str">"cmt">//每个基因突变可能性百分率 ); class=class="str">"cmt">//---------------------------------- class=class="str">"cmt">//在屏幕上显示最后结果 show=true; ServiceFunction(); class=class="str">"cmt">//---------------------------------- time_end=GetTickCount(); class=class="str">"cmt">//---------------------------------- Print(time_end-time_start," мс - 执行时间"); class=class="str">"cmt">//---------------------------------- class="kw">return(class="num">0); } class="type">void ServiceFunction() { if(show==true) { class=class="str">"cmt">//-----------------------变量----------------------------------- class="type">class="kw">double PipsSum=class="num">0.0;
适应度函数怎么把波峰波谷折算成点值
遗传算法里每个染色体代表一套买卖规则,适应度函数负责把这套规则在历史 K 线上的表现量化成可比较的数字。上面这段代码用 temp<=1.0 判定为高点、temp>=4.0 判定为低点,把候选拐点塞进 Peaks[] 数组并累计 PeaksCount。 第一段逻辑出现在回测绘制分支:除了记录价格,还顺手用 OBJ_TREND 把相邻峰谷连成线段,并扣除 Spred(点差)后得到 PipsSum。注意 PipsSum 用 (MathAbs(峰差)/Point()) 再减点差,意味着每跨一个峰谷就被折算成净点数——外汇和贵金属杠杆高,点差吞噬在分钟级回测里可能让盈利轮廓直接反转。 第二段 FitnessFunction() 是 UGA 调用的精简版:它不画对象,只循环 Colony[u][chromos] 收集峰谷,若 PeaksCount>1 才进入累加。两种写法共用同一套阈值(1.0 / 4.0),你可以直接把 GeneCount 调到 500 跑一轮,观察 PeaksCount 分布是否集中在 20~80 之间,进而判断阈值要不要随品种重标定。 别把 1.0 和 4.0 当通用解:黄金 M15 的波动幅度常把 temp 推到 6 以上,这时原代码会漏掉一批低点,适应度被系统性低估。
class="type">int PeaksCount=class="num">0; class="type">class="kw">double temp=class="num">0.0; class=class="str">"cmt">//-------------------------------------------------------------------- for(class="type">int u=class="num">1;u<=GeneCount;u++) { temp=Chromosome[u]; if(temp<=class="num">1.0 ) { Peaks[PeaksCount]=NormalizeDouble(Hight[u],Digits()); Ti [PeaksCount]=Time[u]; PeaksCount++; } if(temp>=class="num">4.0) { Peaks[PeaksCount]=NormalizeDouble(Low[u],Digits()); Ti [PeaksCount]=Time[u]; PeaksCount++; } } ObjectsDeleteAll(class="num">0,-class="num">1,-class="num">1); for(class="type">int V=class="num">0;V<PeaksCount-class="num">1;V++) { PipsSum+=NormalizeDouble((MathAbs(Peaks[V]-Peaks[V+class="num">1]))/Point(),FFNormalizeDigits)-Spred; ObjectCreate(class="num">0,"BoxBackName"+(class="type">class="kw">string)V,OBJ_TREND,class="num">0,Ti[V],Peaks[V],Ti[V+class="num">1],Peaks[V+class="num">1]); ObjectSetInteger(class="num">0,"BoxBackName"+(class="type">class="kw">string)V,OBJPROP_COLOR,Black); ObjectSetInteger(class="num">0,"BoxBackName"+(class="type">class="kw">string)V,OBJPROP_SELECTABLE,true); } ChartRedraw(class="num">0); Comment(PipsSum); } class=class="str">"cmt">//---------------------------------------------------------------------- else class="kw">return; } class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//———————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//-----------------------------------------------------------------------+ class=class="str">"cmt">// 确定个体适应度的函数从 UGA 中调用. | class=class="str">"cmt">//-----------------------------------------------------------------------+ class="type">void FitnessFunction(class="type">int chromos) { class=class="str">"cmt">//-----------------------变量------------------------------------- class="type">class="kw">double PipsSum=class="num">0.0; class="type">int PeaksCount=class="num">0; class="type">class="kw">double temp=class="num">0.0; class=class="str">"cmt">//---------------------------------------------------------------------- for(class="type">int u=class="num">1;u<=GeneCount;u++) { temp=Colony[u][chromos]; if(temp<=class="num">1.0) { Peaks[PeaksCount]=NormalizeDouble(Hight[u],Digits()); PeaksCount++; } if(temp>=class="num">4.0) { Peaks[PeaksCount]=NormalizeDouble(Low[u],Digits()); PeaksCount++; } } if(PeaksCount>class="num">1) { for(class="type">int V=class="num">0;V<PeaksCount-class="num">1;V++)
◍ 峰谷距转点值并扣点差
这段代码把相邻峰位之间的绝对距离换算成点数,再减去点差,累加到 PipsSum 上,作为单条染色体的适应度原始分。 NormalizeDouble 用 FFNormalizeDigits 控制精度,(MathAbs(Peaks[V]-Peaks[V+1]))/Point() 把价格差除以点值得到裸点数,避免不同品种点值差异导致的偏差。 若路径不合法则 Colony[0][chromos] 直接置为 -10000000.0,相当于在遗传算法里判死刑,不参与后续选择;AmountStartsFF 每算完一条染色体自增,用于统计已评估样本数。 在 MT5 里把 FFNormalizeDigits 设为 1、点差取 EURUSD 常规 10 点(0.00010),一段 50 点峰谷差算出来 PipsSum 约为 40,可直接观察适应度分布是否过偏。
PipsSum+=NormalizeDouble((MathAbs(Peaks[V]-Peaks[V+class="num">1]))/Point(),FFNormalizeDigits)-Spred; Colony[class="num">0][chromos]=PipsSum; } else Colony[class="num">0][chromos]=-class="num">10000000.0; AmountStartsFF++; }
「UGA 调参的几个实操注意点」
在 FF 里把预期条件设对,算法才更可能吐出能用的结果。回看例 2 就能明白,为什么这算是一条头等建议:条件错了,后面跑得再花哨也是白搭。 Precision 别给太小。算法虽然支持以步进 0 运行,但你应当要求解具备合理精度,这个参数本意就是压缩问题规模,设太低只会让搜索空间虚胖。 种群规模和代数阈值要动起来试。经验上,好解对应的 Epoch 参数应比 MaxOfCurrentEpoch 显示值大两倍左右;但别猛加过大数值,那并不会加速收敛,只是烧 CPU。 遗传算子的参数没有万能档位,得按具体任务条件去分配。外汇与贵金属市场高杠杆、高波动,这类优化结果仅代表历史样本上的概率倾向,实盘前务必在 MT5 策略测试器里复核。
记住这一条就够了
UGA 这套基于人群优化的遗传框架在 MT5 策略测试器里跑得通,核心在于它把 ZigZag 拐点当基因、把历史 Bar 数量直接映射成基因长度——有读者在 2024 年 10 月确认:条形图数量即基因数量,这个对应关系没歧义。 代码层面有个坑得提一句:老版本里变量和函数同名 ArtificialMutation 会导致 UGAlib 编译报错,改成 ArtificialMutationP 后就能直接编译运行,社区里已有人验证过。 它用 BSD 授权放出全部源码,你想加新算子或换运算块都行,扩展性摆在那。外汇和贵金属市场高杠杆、滑点诡异,拿这套做优化别当圣杯,回测漂亮不等于实盘能活。 真要下手,先把附带的 UGA_script 解压、改掉那个函数名、挂上 ZigZag 跑一轮参数进化,比看十篇软文都实在。