交易策略的色彩优化(基础篇)
📘

交易策略的色彩优化(基础篇)

第 1/3 篇

用颜色把策略状态钉在图表上

在 MT5 里做策略可视化,最容易被忽略的一步是给不同状态上色。很多人写好了逻辑,却只在日志里打印结果,盯盘时根本分不清当前处在哪一段行情。 Dmitry Fedoseev 在 2019 年 4 月 4 日发布的测试者示例里,用 PlotIndexSetInteger 直接改指标线的颜色,让趋势段和震荡段一眼可辨。这类做法不增加计算量,只在渲染层做区分,对贵金属和外汇这类高波动品种尤其实用。 外汇与贵金属杠杆高、滑点随机,单看数值容易误判。把策略状态用颜色固化到主图,能降低盯盘认知负荷,但颜色只是辅助,不代表任何方向确定性。

◍ 把优化结果染成颜色看

做 EA 参数优化后,真正麻烦的是从一堆解里挑一个。盈利、回撤、恢复系数单独看都不够,组合权重也没有标准答案。 我们可以换一种思路:把三个测试参数直接映射成 RGB 颜色值。红绿蓝各代表一个维度,参数空间就变成一张肉眼能扫的色图,不必先定好评分公式。 这次实验就干这件事——用颜色编码优化结果,到最后再判断这种可视化到底有没有用,而不是预先假定它有效。外汇与贵金属测试请认清:历史拟合不代表实盘概率。

「从优化报告到可计算数据结构」

做策略优化后处理,第一步是把 MT5 优化器导出的 XML 报告变成能直接算数的结构。原报告解析库里,SOptimization 套着 SPass[],但 SPass 所有字段都是 string:Result、Profit、SharpeRatio 这些全是文本,每次用都得现转 double,跑大量 Pass 时既慢又容易写错。 SPass 里我们真正要用的栏位就那几个:Profit 是结果利润值,RecoveryFactor 是绝对利润与最大回撤之比,EquityDD_perc 是回撤百分比,SharpeRatio 是夏普比率,Trades 是成交笔数。一个优化 Pass 对应数组里一个元素,参数名另存在 ParameterName[]。 直接改原结构风险大,所以用继承:新建 SPass2 : SPass,只加两个 double 数组——factor[9] 和 dParameters[]。factor[9] 正好装除 Pass 编号外的 9 个数值字段,dParameters[] 装优化参数本身。这样老代码不破,新数据随手取。 转换函数 ConvertOptimizationStruct 干的事很直白:先 ArraySize 拿 Pass 总数,ArrayResize 给新结构扩好,然后逐条把字符串字段原样拷过去,再把 9 个关键值用 StringToDouble 填进 factor[]。注意 factor 下标从 0 到 8 依次对应 Result、Profit、ExpectedPayoff、ProfitFactor、RecoveryFactor、SharpeRatio、Custom、EquityDD_perc、Trades——这个顺序建议用枚举锁死,后面按索引取数才不会错位。 外汇与贵金属策略优化结果受样本外漂移影响大,这类结构只解决“算得动”的问题,不代表某组参数在未来实盘有概率优势。开 MT5 把这段 struct 和转换函数丢进 ColorOptimization.mqh,引一下 HTMLReport.mqh,就能直接吃优化报告做后续筛选。

MQL5 / C++
class="kw">struct SOptimization{
  class="type">class="kw">string ParameterName[];
  SPass Pass[];
};
class="kw">struct SPass{
  class="type">class="kw">string Pass;
  class="type">class="kw">string Result;
  class="type">class="kw">string Profit;
  class="type">class="kw">string ExpectedPayoff;
  class="type">class="kw">string ProfitFactor;
  class="type">class="kw">string RecoveryFactor;
  class="type">class="kw">string SharpeRatio;
  class="type">class="kw">string Custom;
  class="type">class="kw">string EquityDD_perc;
  class="type">class="kw">string Trades;
  class="type">class="kw">string Parameters[];
};
class="macro">#include <HTMLReport.mqh>
class="kw">struct SPass2:SPass{
  class="type">class="kw">double factor[class="num">9];
  class="type">class="kw">double dParameters[];  
};
class="kw">struct SOptimization2{
  class="type">class="kw">string ParameterName[];
  SPass2 Pass[];
};
class="type">void ConvertOptimizationStruct(SOptimization & src,SOptimization2 & dst){
  ArrayCopy(dst.ParameterName,src.ParameterName);
  class="type">int cnt=ArraySize(src.Pass);
  ArrayResize(dst.Pass,cnt);  
  for(class="type">int i=class="num">0;i<cnt;i++){
    ArrayCopy(dst.Pass[i].Parameters,src.Pass[i].Parameters);
    
    dst.Pass[i].Pass=src.Pass[i].Pass;
    dst.Pass[i].Result=src.Pass[i].Result;
    dst.Pass[i].Profit=src.Pass[i].Profit;
    dst.Pass[i].ExpectedPayoff=src.Pass[i].ExpectedPayoff;
    dst.Pass[i].ProfitFactor=src.Pass[i].ProfitFactor;
    dst.Pass[i].RecoveryFactor=src.Pass[i].RecoveryFactor;
    dst.Pass[i].SharpeRatio=src.Pass[i].SharpeRatio;
    dst.Pass[i].Custom=src.Pass[i].Custom;
    dst.Pass[i].EquityDD_perc=src.Pass[i].EquityDD_perc;
    dst.Pass[i].Trades=src.Pass[i].Trades;
    dst.Pass[i].factor[class="num">0]=StringToDouble(src.Pass[i].Result);
    dst.Pass[i].factor[class="num">1]=StringToDouble(src.Pass[i].Profit);
    dst.Pass[i].factor[class="num">2]=StringToDouble(src.Pass[i].ExpectedPayoff);
    dst.Pass[i].factor[class="num">3]=StringToDouble(src.Pass[i].ProfitFactor);
    dst.Pass[i].factor[class="num">4]=StringToDouble(src.Pass[i].RecoveryFactor);
    dst.Pass[i].factor[class="num">5]=StringToDouble(src.Pass[i].SharpeRatio);
    dst.Pass[i].factor[class="num">6]=StringToDouble(src.Pass[i].Custom);
    dst.Pass[i].factor[class="num">7]=StringToDouble(src.Pass[i].EquityDD_perc);
    dst.Pass[i].factor[class="num">8]=StringToDouble(src.Pass[i].Trades);
    
    class="type">int pc=ArraySize(src.Pass[i].Parameters);

把优化报告的字符串参数转成数值数组

做 MT5 策略优化结果二次处理时,XML 导出的参数全是字符串,直接算夏普、盈利因子没门。上面这段就是把 src.Pass[i].Parameters 里的 "true"/"false"/数字串,逐一塞进 dst.Pass[i].dParameters 双精度数组:布尔映射成 1 和 0,其余走 StringToDouble。 枚举 EOptizatrionFactor 定义了 9 个排序维度,从 Result=0Trades=8,回测里按哪个字段挑参数全靠它。注意原文拼写 EOptimzatrionFactor 漏了字母,复制代码时最好顺手改对,不然编译报错查半天。 OptimizerXMLReportToStruct2 只是薄封装:先调 OptimizerXMLReportToStruct 读 XML,成功再 ConvertOptimizationStruct 转成你要的结构体。想验证,开 MT5 跑一次 EA 优化、导出 XML,把这段贴进脚本看 dParameters 是否对齐。外汇与贵金属品种波动大,优化结果过拟合概率高,参数上线前务必样本外验证。

MQL5 / C++
  ArrayResize(dst.Pass[i].dParameters,pc);
  
  for(class="type">int j=class="num">0;j<pc;j++){
     if(src.Pass[i].Parameters[j]=="true"){
        dst.Pass[i].dParameters[j]=class="num">1;
     }
     else if(src.Pass[i].Parameters[j]=="class="kw">false"){
        dst.Pass[i].dParameters[j]=class="num">0;     
     }
     else{
        dst.Pass[i].dParameters[j]=StringToDouble(src.Pass[i].Parameters[j]);
     }
  }
   }  
}
enum EOptimizatrionFactor{
   Result=class="num">0,
   Profit=class="num">1,
   ExpectedPayoff=class="num">2,
   ProfitFactor=class="num">3,
   RecoveryFactor=class="num">4,
   SharpeRatio=class="num">5,
   Custom=class="num">6,
   EquityDD_perc=class="num">7,
   Trades=class="num">8
};
class="type">bool OptimizerXMLReportToStruct2(class="type">class="kw">string aFileName,SOptimization2 & aOptimization){
   SOptimization tmp;
   if(!OptimizerXMLReportToStruct(aFileName,tmp)){
      class="kw">return(class="kw">false);
   }
   ConvertOptimizationStruct(tmp,aOptimization);
   class="kw">return(true);
}

◍ 把优化报告染成三通道色图

想直观看 EA 优化结果,光看 XML 报表不够。把每次 pass 的三个因子映射成 RGB 三原色,白格就意味着三项都接近最优,眼睛一扫就能定位好参数区。外汇与贵金属优化含极高风险,色图只是辅助筛选,不代表实盘盈利概率。 具体落地先建脚本 ColorOptimization.mq5,挂上 ColorOptimization.mqh 头文件。外部参数里 ReportName 接源报告、OutputName 定输出、Factor1~3 挑三个染色因子,各自带 Invert 反转开关,Sort 控制是否按色排序。 核心在 SolveColorComponents():先扫全部 pass 找出三因子的最大最小值,把每个值归一到 0~1,再转 255 阶 RGB 写进 SPass 的 ColorComponents[3]。抽象值从 0~1 算起,以后换 HSL 等其他色彩模型也只需改这一层。 排序因子取三分量算术平均,最佳 pass 趋近纯白。脚本里无论 Sort 开关都跑一遍 SolveSortFactor(),气泡法排完序,报告就由带颜色按钮的数据表加多张双因子梯度平面组成。

MQL5 / C++
class="macro">#include <ColorOptimization.mqh>
class="macro">#class="kw">property script_show_inputs
class="kw">input class="type">class="kw">string                ReportName     =  "*.xml";
class="kw">input class="type">class="kw">string                OutputName     =  "ColorOptimization1-class="num">1.htm";
class="kw">input EOptimizatrionFactor Factor1         =  Profit;
class="kw">input EOptimizatrionFactor Factor2         =  EquityDD_perc;
class="kw">input EOptimizatrionFactor Factor3         =  RecoveryFactor;
class="kw">input class="type">bool                  Factor1Invert  =  class="kw">false;
class="kw">input class="type">bool                  Factor2Invert  =  true;
class="kw">input class="type">bool                  Factor3Invert  =  class="kw">false;
class="kw">input class="type">bool                  Sort           =  true;
SOptimization2 opt;
if(!OptimizerXMLReportToStruct2(ReportName,opt)){
   Alert("错误的 OptimizerXMLReportToStruct2");
   class="kw">return;
}
class="type">class="kw">double ColorComponent[class="num">3];
class="type">void SolveColorComponents(   SOptimization2 & aOpt,
                            class="type">int i1,class="type">int i2,class="type">int i3,
                            class="type">bool r1=class="kw">false,class="type">bool r2=class="kw">false,class="type">bool r3=class="kw">false){

   class="type">class="kw">double mx[class="num">3]={class="num">0,class="num">0,class="num">0};
   class="type">class="kw">double mn[class="num">3]={DBL_MAX,DBL_MAX,DBL_MAX};

   class="type">int size=ArraySize(aOpt.Pass);

   for(class="type">int i=class="num">0;i<size;i++){
      mx[class="num">0]=MathMax(mx[class="num">0],aOpt.Pass[i].factor[i1]);
      mx[class="num">1]=MathMax(mx[class="num">1],aOpt.Pass[i].factor[i2]);
      mx[class="num">2]=MathMax(mx[class="num">2],aOpt.Pass[i].factor[i3]);
      mn[class="num">0]=MathMin(mn[class="num">0],aOpt.Pass[i].factor[i1]);

「把多因子压成可排序的彩色权重」

这段逻辑干的事,是把优化结果里三个因子分别做极差归一化,再合成一个 SortFactor 用于排序。归一化前先取每组因子的全局最大 mx[] 与最小 mn[],分母用 mx[k]-mn[k],若跨度为 0 则 c 值保持 0,避免除零崩脚本。 r1/r2/r3 三个反转开关很关键:打开时 c = 1.0 - c,意味着该因子越小反而越优(比如回撤类指标)。算完的 c1/c2/c3 直接写进 ColorComponent[0..2],供可视化着色用。 SortFactor 取三个分量均值,即 (c1+c2+c3)/3,范围落在 0~1。SortFactorSort 是手写冒泡,从尾到头两两比较,SortFactor 小的往后沉,最终数组按综合分降序排列。 在 MT5 里跑这套,重点看 r1~r3 的勾选:同一组优化结果,反转某个因子可能让排名前 10 的 Pass 完全换血。外汇与贵金属市场高杠杆、滑点漂移大,回测排序靠前不等于实盘概率占优,须用样本外数据复核。

MQL5 / C++
    mn[class="num">1]=MathMin(mn[class="num">1],aOpt.Pass[i].factor[i2]);
    mn[class="num">2]=MathMin(mn[class="num">2],aOpt.Pass[i].factor[i3]);      
   }
   class="type">class="kw">double c1,c2,c3,d;
   
   for(class="type">int i=class="num">0;i<size;i++){      
   
      c1=class="num">0;
      c2=class="num">0;
      c3=class="num">0;
   
      d=mx[class="num">0]-mn[class="num">0];
      if(d!=class="num">0){
         c1=(aOpt.Pass[i].factor[i1]-mn[class="num">0])/d;
      }
      
      d=mx[class="num">1]-mn[class="num">1];
      if(d!=class="num">0){
         c2=(aOpt.Pass[i].factor[i2]-mn[class="num">1])/d;
      }
      
      d=mx[class="num">2]-mn[class="num">2];
      if(d!=class="num">0){
         c3=(aOpt.Pass[i].factor[i3]-mn[class="num">2])/d;         
      }
      
      if(r1)c1=class="num">1.0-c1;
      if(r2)c2=class="num">1.0-c2;
      if(r3)c3=class="num">1.0-c3;
      
      aOpt.Pass[i].ColorComponent[class="num">0]=c1;
      aOpt.Pass[i].ColorComponent[class="num">1]=c2;      
      aOpt.Pass[i].ColorComponent[class="num">2]=c3;  
   }
}
SolveColorComponents(opt,Factor1,Factor2,Factor3,Factor1Invert,Factor2Invert,Factor3Invert);
class="type">class="kw">double SortFactor;
class="type">void SolveSortFactor(SOptimization2 & aOpt){
   class="type">int size=ArraySize(aOpt.Pass);
   
   for(class="type">int i=class="num">0;i<size;i++){
      aOpt.Pass[i].SortFactor=class="num">0;
      for(class="type">int j=class="num">0;j<class="num">3;j++){
         aOpt.Pass[i].SortFactor+=aOpt.Pass[i].ColorComponent[j];
      }
      aOpt.Pass[i].SortFactor/=class="num">3;
   }
}
class="type">void SortFactorSort(SOptimization2 & aOpt){
   class="type">int size=ArraySize(aOpt.Pass);
   for(class="type">int i=size-class="num">1;i>class="num">0;i--){
      for(class="type">int j=class="num">0;j<i;j++){
         if(aOpt.Pass[j].SortFactor<aOpt.Pass[j+class="num">1].SortFactor){
            SPass2 tmp=aOpt.Pass[j];
            aOpt.Pass[j]=aOpt.Pass[j+class="num">1];
            aOpt.Pass[j+class="num">1]=tmp;
         }
      }
   }
}
SolveSortFactor(opt);
if(Sort){  
   SortFactorSort(opt);
}

常见问题

在指标或EA里用不同Color常量绑定状态变量,比如多头红、空头绿、观望灰,直接PlotIndexSetInteger设线色即可。
把优化结果按盈亏、回撤等因子映射成RGB三通道色值,生成色图,颜色越亮代表综合表现越好。
可以,小布盯盘的AIGC分析能读取你的优化报告并直接渲染成彩色权重图,打开品种页就能看。
用StringSplit按分隔符拆出子串,再StringToInteger或StringToDouble逐个转,存进double数组就能参与计算。
给每个因子归一化后乘权重求和,映射到0-255色阶,按色值降序排即可挑出靠前组合。