研究烛条分析技术(第二部分):自动搜索新形态·进阶篇
「在面板里画出单根烛台与语言切换」
做价格行为面板时,先把每类烛台(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 像素。想加自定义形态就把宏改大,并确认资源图数量匹配,否则运行期可能越界空行。
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 线清单是否完整渲染;外汇与贵金属波动剧烈,面板只是辅助,信号误判风险始终存在。
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 策略测试器用历史数据验证参数。
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 里照抄验证。
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 组,面板卡顿概率明显上升,建议先用禁用反复档做小样本验证。
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 里改这两个索引对应的阈值,就能重定义‘长烛’的判定边界。外汇与贵金属波动剧烈,这类形态识别仅作概率参考,实盘需严控仓位。
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; }