研究烛条分析技术(第二部分):自动搜索新形态·进阶篇
📘

研究烛条分析技术(第二部分):自动搜索新形态·进阶篇

第 2/3 篇

「在面板里画出单根烛台与语言切换」

做价格行为面板时,先把每类烛台(long/short/doji/spin/maribozu/hammer 等)做成 64×64 的图标控件,再挂到选卡索引 2 的容器里。下面这段把图标路径、按钮和名称标签一次性绑好,复制进 EA 的 CProgram 类就能在 MT5 里直接看到图形入口。 [CODE] #resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\settings_dark.bmp" #resource "\\Images\\EasyAndFastGUI\\Candles\\long.bmp" #resource "\\Images\\EasyAndFastGUI\\Candles\\short.bmp" #resource "\\Images\\EasyAndFastGUI\\Candles\\doji.bmp" #resource "\\Images\\EasyAndFastGUI\\Candles\\spin.bmp" #resource "\\Images\\EasyAndFastGUI\\Candles\\maribozu.bmp" #resource "\\Images\\EasyAndFastGUI\\Candles\\hammer.bmp" bool CProgram::CreateCandle(CPicture &pic,CButton &button,CTextLabel &candlelabel,const string candlename,const int x_gap,const int y_gap,string path) { pic.MainPointer(m_tabs1); m_tabs1.AddToElementsArray(2,pic); pic.XSize(64); pic.YSize(64); pic.IconFile(path); if(!pic.CreatePicture(x_gap,y_gap)) return(false); CWndContainer::AddToElementsArray(0,pic); CreateButtonPic(pic,button,"Images\\EasyAndFastGUI\\Icons\\bmp16\\settings_dark.bmp"); CreateNameCandle(candlelabel,x_gap,y_gap+pic.YSize(),candlename); return(true); } [/CODE] 逐行看:前 7 行 #resource 把暗色设置图标和 6 种烛台位图编译进 ex5;CreateCandle 里 MainPointer 绑定父选卡,AddToElementsArray(2,...) 投到第三张选项卡;XSize/YSize(64) 定死缩略图尺寸,IconFile 接外部路径;CreatePicture 失败就返 false,成功后再挂设置按钮与名称标签。 界面语言不是写死的。CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM 事件里若命中 ChangeLanguage,就重读组合框选中项索引 m_lang_index,再 Update(true) 重绘。外汇与贵金属图表多语言面板仅降低阅读成本,交易本身仍属高风险,参数误设可能让信号显示错位。 列表视图给了硬约束:CANDLE_TOTAL 宏定为 11,m_listview1 尺寸 175×250、行高 19、文字左空 25 像素。想加自定义形态就把宏改大,并确认资源图数量匹配,否则运行期可能越界空行。

MQL5 / C++
class="macro">#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\settings_dark.bmp"
class="macro">#resource "\\Images\\EasyAndFastGUI\\Candles\\class="type">long.bmp"
class="macro">#resource "\\Images\\EasyAndFastGUI\\Candles\\class="type">short.bmp"
class="macro">#resource "\\Images\\EasyAndFastGUI\\Candles\\doji.bmp"
class="macro">#resource "\\Images\\EasyAndFastGUI\\Candles\\spin.bmp"
class="macro">#resource "\\Images\\EasyAndFastGUI\\Candles\\maribozu.bmp"
class="macro">#resource "\\Images\\EasyAndFastGUI\\Candles\\hammer.bmp"
class="type">bool CProgram::CreateCandle(CPicture &pic,CButton &button,CTextLabel &candlelabel,const class="type">class="kw">string candlename,const class="type">int x_gap,const class="type">int y_gap,class="type">class="kw">string path)
  {
  pic.MainPointer(m_tabs1);
  m_tabs1.AddToElementsArray(class="num">2,pic);
  pic.XSize(class="num">64);
  pic.YSize(class="num">64);
  pic.IconFile(path);
  if(!pic.CreatePicture(x_gap,y_gap))
     class="kw">return(false);
  CWndContainer::AddToElementsArray(class="num">0,pic);
  CreateButtonPic(pic,button,"Images\\EasyAndFastGUI\\Icons\\bmp16\\settings_dark.bmp");
  CreateNameCandle(candlelabel,x_gap,y_gap+pic.YSize(),candlename);
  class="kw">return(true);
  }

列表视图与按钮控件的初始化套路

在 MT5 自定义面板里,列表视图(ListView)的视觉与交互属性要在 Create 之前一次性设好。下面这段把悬停高亮、复选框模式打开,列表行数绑定 CANDLE_TOTAL 常量,底部自动留 10 像素偏移,字号压到 10——这些都是为了在小尺寸面板里塞下 11 种 K 线分类不挤版。 数据填充靠一个字符串数组 cand_name,从 Long — bullish 到 Hammer — bearish 共 11 项,再用 for 循环把「序号. 名称」写进每一行。注意 r+1 的偏移:界面显示从 1 开始,数组下标仍从 0 走,避免交易者看行号时脑内换算。 按钮部分走的是另一套逻辑:CreateDualButton 横向摆两个 CButton,右边那个 x 坐标直接加 99,而不是用按钮宽度变量——如果你的 XSize 不是 100,这两个键会叠边或留缝,得手改这个魔法数。单键属性里 BorderColor 用 C'0,100,255',锁定态却换成 C'50,180,75' 配白字,意味着按下后颜色跳成绿,视觉反馈很硬。 让小布替你跑这套 把上面 ListView 的 FontSize(10) 和 AutoYResizeBottomOffset(10) 抄进你自己的面板工程,挂黄金 M5 图看 11 类 K 线清单是否完整渲染;外汇与贵金属波动剧烈,面板只是辅助,信号误判风险始终存在。

MQL5 / C++
  m_listview1.LightsHover(true);
  m_listview1.CheckBoxMode(true);
  m_listview1.ListSize(CANDLE_TOTAL);
  m_listview1.AutoYResizeMode(true);
  m_listview1.AutoYResizeBottomOffset(class="num">10);
  m_listview1.FontSize(class="num">10);
class=class="str">"cmt">//--- 用数据填充列表视图
  class="type">class="kw">string cand_name[CANDLE_TOTAL]=
    {
      "Long — bullish",
      "Long — bearish",
      "Short — bullish",
      "Short — bearish",
      "Spinning Top — bullish",
      "Spinning Top — bearish",
      "Doji",
      "Marubozu — bullish",
      "Marubozu — bearish",
      "Hammer — bullish",
      "Hammer — bearish"
    };
  for(class="type">int r=class="num">0; r<CANDLE_TOTAL; r++)
    {
      m_listview1.SetValue(r,(class="type">class="kw">string)(r+class="num">1)+". "+cand_name[r]);
    }
class=class="str">"cmt">//--- 创建列表视图
  if(!m_listview1.CreateListView(x_gap,y_gap))
    class="kw">return(false);
class=class="str">"cmt">//--- 将指向元素的指针添加到基类
  CWndContainer::AddToElementsArray(class="num">0,m_listview1);
  class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建反复开关                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateDualButton(CButton &lbutton,CButton &rbutton,const class="type">int x_gap,const class="type">int y_gap,const class="type">class="kw">string ltext,const class="type">class="kw">string rtext)
  {
  CreateButton(lbutton,x_gap,y_gap,ltext);
  CreateButton(rbutton,x_gap+class="num">99,y_gap,rtext);
  class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateButton(CButton &button,const class="type">int x_gap,const class="type">int y_gap,const class="type">class="kw">string text)
  {
class=class="str">"cmt">//--- 将指针保存到主控件
  button.MainPointer(m_tabs1);
class=class="str">"cmt">//--- 附加到选卡
  m_tabs1.AddToElementsArray(class="num">2,button);
class=class="str">"cmt">//--- 属性
  button.XSize(class="num">100);
  button.YSize(class="num">30);
  button.Font("Trebuchet");
  button.FontSize(class="num">10);
  button.IsCenterText(true);
  button.BorderColor(C&class="macro">#x27;class="num">0,class="num">100,class="num">255&class="macro">#x27;);
  button.BackColor(clrAliceBlue);
  button.BackColorLocked(C&class="macro">#x27;class="num">50,class="num">180,class="num">75&class="macro">#x27;);
  button.BorderColorLocked(C&class="macro">#x27;class="num">50,class="num">180,class="num">75&class="macro">#x27;);
  button.LabelColorLocked(clrWhite);
class=class="str">"cmt">//--- 创建控件
  if(!button.CreateButton(text,x_gap,y_gap))
    class="kw">return(false);
class=class="str">"cmt">//--- 将指向元素的指针添加到基类
  CWndContainer::AddToElementsArray(class="num">0,button);
  class="kw">return(true);
  }
  if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
    {
      ....
      class=class="str">"cmt">//--- 如果按下按钮

◍ 双表联动下的品种切换与自动搜索重建

面板里两个按钮 m_button7 与 m_button8 做成互斥锁:点中其中一个就把另一个解锁,保证同一时刻只有一侧在跑分析。这种写法在 MT5 自定义面板里很实用,避免用户两边同时选品种把表格写乱。 当捕获到 CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM 事件,程序先判断 lparam 属于哪张表。若来自第一张表就调 ChangeSymbol1 并整体 Update(true);来自第二张表则直接 m_table2.Update(true),两张表各自刷新、互不阻塞。 ChangeSymbol2 里有个容易踩的坑:先用 SelectedItem()==WRONG_VALUE 判断有没有真选中行。没选中的话状态栏写 'No symbol selected for analysis' 并直接 return false,不会往下跑自动搜索。选中后从表格取品种名,状态栏按 m_lang_index 显示俄文或英文描述,再调 BuildingAutoSearchTable() 与 GetPatternType()。 BuildingAutoSearchTable 第一步就调 GetCandleCombitaion(),若返回 false 会弹 MessageBox 提示“选中蜡烛数小于研究形态尺寸”,随后 DeleteAllRows() 清空旧行。外汇与贵金属波动大、滑点不可控,这类形态自动搜索仅作概率参考,实盘前务必在 MT5 策略测试器用历史数据验证参数。

MQL5 / C++
if(lparam==m_button7.Id())
  {
   m_button7.IsLocked(true);
   m_button8.IsLocked(false);
  }
 else if(lparam==m_button8.Id())
  {
   m_button7.IsLocked(false);
   m_button8.IsLocked(true);
  }
class=class="str">"cmt">//--- 在列表或表格中选择项目的事件处理
 if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM)
  {
   class=class="str">"cmt">//--- 品种改变
   if(ChangeSymbol1(lparam))
     Update(true);
   if(ChangeSymbol2(lparam))
     m_table2.Update(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 品种改变                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::ChangeSymbol2(const class="type">long id)
  {
class=class="str">"cmt">//--- 检查元素 ID
   if(id!=m_symb_table2.Id())
     class="kw">return(false);
class=class="str">"cmt">//--- 如果该行并非高亮显示,则退出
   if(m_symb_table2.SelectedItem()==WRONG_VALUE)
     {
      class=class="str">"cmt">//--- 在状态栏中显示完整的品种说明
      m_status_bar.SetValue(class="num">0,"No symbol selected for analysis");
      m_status_bar.GetItemPointer(class="num">0).Update(true);
      class="kw">return(false);
     }
class=class="str">"cmt">//--- 获取品种
   class="type">class="kw">string symbol=m_symb_table2.GetValue(class="num">0,m_symb_table2.SelectedItem());
class=class="str">"cmt">//--- 在状态栏中显示完整的品种说明
   class="type">class="kw">string val=(m_lang_index==class="num">0)?"Выбранный символ: ":"Selected symbol: ";
   m_status_bar.SetValue(class="num">0,val+::SymbolInfoString(symbol,SYMBOL_DESCRIPTION));
   m_status_bar.GetItemPointer(class="num">0).Update(true);
   if(!BuildingAutoSearchTable())
     class="kw">return(false);
   GetPatternType(symbol,m_total_combination);
   class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 重建形态自动搜索表格                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::BuildingAutoSearchTable(class="type">void)
  {
class=class="str">"cmt">//---
   if(!GetCandleCombitaion())
     {
      if(m_lang_index==class="num">0)
         MessageBox("Число выбранных свечей меньше размера исследуемого паттерна!","Ошибка");
      else if(m_lang_index==class="num">1)
         MessageBox("The number of selected candles is less than the size of the studied pattern!","Error");
      class="kw">return(false);
     }
class=class="str">"cmt">//--- 删除所有行
   m_table2.DeleteAllRows();
class=class="str">"cmt">//--- 按品种数量设置行数

「从勾选烛条到组合矩阵的代码路径」

这段逻辑干的事很直接:先把用户在列表里勾选的烛条序号收进数组,再按设定的形态长度(1根、2根或3根)算出全部组合,塞进表格展示。注意勾选数不足时直接返回 false——比如形态长度设成2却只勾了1根,就不会往下跑。 组合数计算有个开关差异:若 m_button7 锁定(允许反复),n = selected_candles 的 pattern_size 次方;若走 m_button8 禁用反复,则减去 selected_candles 本身。以勾选3根、长度2为例,反复模式出9种,非反复出6种。 下面这段是表格回填和烛条组合生成的核心片段,逐行拆一下就能在 MT5 里照抄验证。

MQL5 / C++
  for(class="type">int i=class="num">0; i<ArraySize(m_total_combination); i++)
    {
    m_table2.AddRow(i);
    m_table2.SetValue(class="num">0,i,m_total_combination[i]);
    }
  m_table2.DeleteRow(ArraySize(m_total_combination));
class=class="str">"cmt">//--- 更新表格
  m_table2.Update(true);
  m_table2.GetScrollVPointer().Update(true);
  m_table2.GetScrollHPointer().Update(true);
  class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 基于简单烛条生成形态                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::GetCandleCombitaion(class="type">void)
  {
  class="type">class="kw">string candlenumber[];
  class="type">int selected_candles=class="num">0,n;
  ArrayResize(candlenumber,m_total_candles);
class=class="str">"cmt">//---
  for(class="type">int i=class="num">0;i<m_total_candles;i++)
    {
    if(m_listview1.GetState(i))
      {
      candlenumber[selected_candles]=(class="type">class="kw">string)(i+class="num">1);
      selected_candles++;
      }
    }
  if((m_pattern_size==class="num">2 && selected_candles<class="num">2) || (m_pattern_size==class="num">3 && selected_candles<class="num">2) || selected_candles<class="num">1)
    class="kw">return(false);
class=class="str">"cmt">//--- 计算组合数
  if(m_pattern_size>class="num">1)
    n=(m_button7.IsLocked())?(class="type">int)MathPow(selected_candles,m_pattern_size):(class="type">int)MathPow(selected_candles,m_pattern_size)-selected_candles;
  else
    n=selected_candles;
  ArrayResize(m_total_combination,n);
  n=class="num">0;
class=class="str">"cmt">//--- 单根烛条的一个集合
  if(m_pattern_size==class="num">1)
    {
    for(class="type">int i=class="num">0;i<selected_candles;i++)
      m_total_combination[i]="["+candlenumber[i]+"]";
    }
class=class="str">"cmt">//--- 两根烛条的一个集合
  else if(m_pattern_size==class="num">2)
    {
    class=class="str">"cmt">//--- 启用反复模式
    if(m_button7.IsLocked())
      {
      for(class="type">int i=class="num">0;i<selected_candles;i++)
        {
        for(class="type">int j=class="num">0;j<selected_candles;j++)
          {
          m_total_combination[n]="["+candlenumber[i]+","+candlenumber[j]+"]";
          n++;
          }
        }
      }
    class=class="str">"cmt">//--- 禁用反复模式
    else if(m_button8.IsLocked())
      {
      for(class="type">int i=class="num">0;i<selected_candles;i++)
        {

三烛组合的排列与去重开关

当形态长度设为 3 根烛条时,引擎要先把可选烛条凑成三元组存入 m_total_combination 数组。这里分两条路径:反复模式(m_button7 锁定)允许同一根烛条在三个位置重复出现,比如 [3,3,3] 也会被收录;禁用反复模式(m_button8 锁定)则跳过 i==j&&i==k 的全同组合,但 i、j、k 两两不同或仅部分相同仍保留。 以 selected_candles=5 为例,反复模式会生成 5×5×5=125 个字符串;禁用全同后减掉 5 个,得 120 个。若再要求严格不重复下标(文内未直接写,但可推),应为 5×4×3=60 个,差异直接体现在回测样本量上。 代码里 m_total_combination[n] 用方括号加逗号拼出如 [0,2,4] 的标签,n 自增计数。开 MT5 把 selected_candles 改成 10,反复模式会膨胀到 1000 组,面板卡顿概率明显上升,建议先用禁用反复档做小样本验证。

MQL5 / C++
else if(m_pattern_size==class="num">3)
  {
   class=class="str">"cmt">//--- 启用反复模式
   if(m_button7.IsLocked())
    {
     for(class="type">int i=class="num">0;i<selected_candles;i++)
      {
       for(class="type">int j=class="num">0;j<selected_candles;j++)
        {
         for(class="type">int k=class="num">0;k<selected_candles;k++)
          {
           m_total_combination[n]="["+candlenumber[i]+","+candlenumber[j]+","+candlenumber[k]+"]";
           n++;
          }
        }
      }
    }
   class=class="str">"cmt">//--- 禁用反复模式
   else if(m_button8.IsLocked())
    {
     for(class="type">int i=class="num">0;i<selected_candles;i++)
      {
       for(class="type">int j=class="num">0;j<selected_candles;j++)
        for(class="type">int k=class="num">0;k<selected_candles;k++)
         {
          if(i==j && i==k)
           class="kw">continue;
          m_total_combination[n]="["+candlenumber[i]+","+candlenumber[j]+","+candlenumber[k]+"]";
          n++;
         }
      }
    }
  }
 class="kw">return(true);

◍ 把组合字符串拆回烛条结构

评级集合 ratings[] 在初始化阶段先用 ArraySize(total_combination) 拿到全部形态组合数,再用 ArrayResize 按这个数量扩容。若你的形态表有 128 种组合,ratings 就会被精准切成 128 个槽位,多一条都不会分配。 烛条基础结构 CANDLE_STRUCTURE 只装必要字段:OHLC 四个 double、趋势枚举、bull 布尔、实体大小 bodysize、烛条类型 type。它不存任何指标值,纯粹描述一根 K 线的几何与方向,方便后面直接比对形态索引。 主循环里对 total_combination 每个字符串先掐头去尾,把 '[' 和 ']' 替换成空。m_pattern_size>1 时才按逗号切分;size==1/2/3 分别把切出来的整型元素喂给 cand1/cand2/cand3,再调 IndexToPatternType 回填结构。 IndexToPatternType 就是索引到烛条的映射器:index==1 写 bull=true 且 type=CAND_LONG,代表多头长烛;index==2 则 bull=false、同为长烛,代表空头长烛。你在 MT5 里改这两个索引对应的阈值,就能重定义‘长烛’的判定边界。外汇与贵金属波动剧烈,这类形态识别仅作概率参考,实盘需严控仓位。

MQL5 / C++
class="type">int a_uptrend;
class="type">int b_uptrend;
class="type">int c_uptrend;
class="type">int a_dntrend;
class="type">int b_dntrend;
class="type">int c_dntrend;
};
RATING_SET ratings[];
class=class="str">"cmt">//---
total_patterns=ArraySize(total_combination);
...
ArrayResize(ratings,total_patterns);
class="kw">struct CANDLE_STRUCTURE
  {
   class="type">class="kw">double open,high,low,close;      class=class="str">"cmt">// OHLC
   TYPE_TREND       trend;          class=class="str">"cmt">//趋势
   class="type">bool             bull;           class=class="str">"cmt">//多头
   class="type">class="kw">double           bodysize;       class=class="str">"cmt">//空头
   TYPE_CANDLESTICK type;           class=class="str">"cmt">//烛条类型
  };
class=class="str">"cmt">//---
   for(class="type">int i=class="num">0;i<total_patterns;i++)
     {
      StringReplace(total_combination[i],"[","");
      StringReplace(total_combination[i],"]","");
      if(m_pattern_size>class="num">1)
        {
         class="type">class="kw">ushort sep=StringGetCharacter(",",class="num">0);
         StringSplit(total_combination[i],sep,elements);
        }
      ZeroMemory(ratings[i]);
      m_pattern_total[i]=class="num">0;
      if(m_pattern_size==class="num">1)
         IndexToPatternType(cand1[i],(class="type">int)total_combination[i]);
      else if(m_pattern_size==class="num">2)
        {
         IndexToPatternType(cand1[i],(class="type">int)elements[class="num">0]);
         IndexToPatternType(cand2[i],(class="type">int)elements[class="num">1]);
        }
      else if(m_pattern_size==class="num">3)
        {
         IndexToPatternType(cand1[i],(class="type">int)elements[class="num">0]);
         IndexToPatternType(cand2[i],(class="type">int)elements[class="num">1]);
         IndexToPatternType(cand3[i],(class="type">int)elements[class="num">2]);
        }
     }
class="type">void CProgram::IndexToPatternType(CANDLE_STRUCTURE &res,const class="type">int index)
  {
class=class="str">"cmt">//--- 长烛 - 多头
   if(index==class="num">1)
     {
      res.bull=true;
      res.type=CAND_LONG;
     }
class=class="str">"cmt">//--- 长烛 - 空头
   else if(index==class="num">2)
     {
      res.bull=false;
      res.type=CAND_LONG;
     }

常见问题

在面板初始化时调用语言切换函数并传入目标语言标识,重绘文本标签即可,无需重启工具。
需在初始化阶段绑定数据源并预设按钮状态,避免空指针;建议先建列表再挂按钮事件。
可以,小布能按你设定的品种和组合规则后台扫描,发现新形态直接在对应品种页推送提醒。
勾选写入候选数组,遍历排列生成字符串组合,按去重开关过滤重复项后送入矩阵。
检查拆分分隔符与拼接时是否一致,并确认去重后顺序未乱,建议打印中间变量核对。