建立一个频谱分析程序·进阶篇
(2/3)· 窗口坐标两套逻辑没理清,指标一挂就把你主图搞乱,这篇专治这种翻车
「用像素坐标批量摆图形对象」
在 MT5 自定义指标或 EA 里,图形对象(趋势线、箭头线、矩形)的定位如果每次手写 ObjectSet 会很烦。上面这段把摆盘逻辑收进 AllGrObject::MoveGrObject,入参是对象名加四个整数:x1/y1 是起点柱序与纵向像素,x2/y2 是终点柱序与纵向像素。 函数内部先读对象类型,只处理 OBJ_TREND、OBJ_ARROWED_LINE、OBJ_RECTANGLE 三类。对这三类,用 CopyTime 把柱序 x1、x2 转成具体时间,价格端则用 _Point*y 直接乘像素值写进去——也就是说纵向坐标是以点值为单位的像素缩放,不是价格。 gr_object_coordinate 给出了实盘可直接抄的摆位数据:Trdline1~4 是 4 根竖线,x 取 48/176/304/432,y 从 150 到 360;Trdline5~8 是 4 根横线,y 固定 350/300/250/200,x 跨 42~560。Arrline1 从右下 (560,150) 指到左上 (28,150),Arrline2 从 (560,150) 拉到 (560,370)。Rect1~3 把顶部 0~624 横向三等分,各高 110;Rect4 是兜底大框 624×400。 外汇与贵金属杠杆高,这类坐标化布盘只解决界面排列,不预示任何方向。复制进 EA 的 OnChartEvent 点击分支前,先开 MT5 把图表尺寸调到示例假设的 624×400 区域,否则像素对不上会偏位。
class="type">void SetButtonProp(class="type">class="kw">string name,class="type">bool state,class="type">color col); class="type">long GetRowNumber(class="type">class="kw">string name); class="type">void LabelTxt(class="type">class="kw">string name,class="type">class="kw">string str); }; class="type">void AllGrObject::MoveGrObject(class="type">class="kw">string name,class="type">int x1,class="type">int y1,class="type">int x2,class="type">int y2) { class="type">class="kw">datetime t1[class="num">1],t2[class="num">1]; class="type">long typ; typ=ObjectGetInteger(m_chart_id,name,OBJPROP_TYPE); if(typ==OBJ_TREND||typ==OBJ_ARROWED_LINE||typ==OBJ_RECTANGLE) { CopyTime(_Symbol,_Period,x1,class="num">1,t1); CopyTime(_Symbol,_Period,x2,class="num">1,t2); ObjectSetInteger(m_chart_id,name,OBJPROP_TIME,class="num">0,t1[class="num">0]); ObjectSetDouble(m_chart_id,name,OBJPROP_PRICE,class="num">0,_Point*y1); ObjectSetInteger(m_chart_id,name,OBJPROP_TIME,class="num">1,t2[class="num">0]); ObjectSetDouble(m_chart_id,name,OBJPROP_PRICE,class="num">1,_Point*y2); } } class="type">void gr_object_coordinate() { GObj.MoveGrObject("Trdline1",class="num">48,class="num">150,class="num">48,class="num">360); GObj.MoveGrObject("Trdline2",class="num">176,class="num">150,class="num">176,class="num">360); GObj.MoveGrObject("Trdline3",class="num">304,class="num">150,class="num">304,class="num">360); GObj.MoveGrObject("Trdline4",class="num">432,class="num">150,class="num">432,class="num">360); GObj.MoveGrObject("Trdline5",class="num">42,class="num">350,class="num">560,class="num">350); GObj.MoveGrObject("Trdline6",class="num">42,class="num">300,class="num">560,class="num">300); GObj.MoveGrObject("Trdline7",class="num">42,class="num">250,class="num">560,class="num">250); GObj.MoveGrObject("Trdline8",class="num">42,class="num">200,class="num">560,class="num">200); GObj.MoveGrObject("Arrline1",class="num">560,class="num">150,class="num">28,class="num">150); GObj.MoveGrObject("Arrline2",class="num">560,class="num">150,class="num">560,class="num">370); GObj.MoveGrObject("Rect1",class="num">0,class="num">1,class="num">208,class="num">110); GObj.MoveGrObject("Rect2",class="num">208,class="num">1,class="num">416,class="num">110); GObj.MoveGrObject("Rect3",class="num">416,class="num">1,class="num">624,class="num">110); GObj.MoveGrObject("Rect4",class="num">0,class="num">1,class="num">624,class="num">400); GObj.MoveGrObject("Rect5",class="num">20,class="num">10,class="num">195,class="num">80); } if(id==CHARTEVENT_OBJECT_CLICK) class=class="str">"cmt">// 点击在图表对象上 {
按钮点击如何切换指标模式
在 MT5 自定义指标里,用四个图形按钮切换分析模式是很实用的做法。下面这段事件处理代码演示了点击 Butt1~Butt4 时,如何把被点按钮染成 Chocolate 色、其余置灰为 SlateGray,并改写两个全局标志位。 YPowerFlag 与 YdBFlag 是两个 0/1 开关。Butt1 对应(0,0),Butt2 对应(0,1),Butt3 对应(1,0),Butt4 对应(1,1),四种组合恰好覆盖两组独立开关的全状态。 外汇与贵金属波动剧烈,这类界面切换只改变显示逻辑,不预示任何方向;实盘使用前请在策略测试器里验证按钮事件是否正常触发。 别把染色当信号 按钮变色只是 UI 反馈,YPowerFlag/YdBFlag 才是后续计算分支的依据,盯盘时别被颜色误导而忽略标志位的真实取值。
if(sparam=="Butt1") class=class="str">"cmt">// 点击了按钮 { GObj.SetButtonProp("Butt1",class="num">1,Chocolate); GObj.SetButtonProp("Butt2",class="num">0,SlateGray); GObj.SetButtonProp("Butt3",class="num">0,SlateGray); GObj.SetButtonProp("Butt4",class="num">0,SlateGray); YPowerFlag=class="num">0;YdBFlag=class="num">0; } if(sparam=="Butt2") class=class="str">"cmt">// 点击了按钮 { GObj.SetButtonProp("Butt1",class="num">0,SlateGray); GObj.SetButtonProp("Butt2",class="num">1,Chocolate); GObj.SetButtonProp("Butt3",class="num">0,SlateGray); GObj.SetButtonProp("Butt4",class="num">0,SlateGray); YPowerFlag=class="num">0;YdBFlag=class="num">1; } if(sparam=="Butt3") class=class="str">"cmt">// 点击了按钮 { GObj.SetButtonProp("Butt1",class="num">0,SlateGray); GObj.SetButtonProp("Butt2",class="num">0,SlateGray); GObj.SetButtonProp("Butt3",class="num">1,Chocolate); GObj.SetButtonProp("Butt4",class="num">0,SlateGray); YPowerFlag=class="num">1;YdBFlag=class="num">0; } if(sparam=="Butt4") class=class="str">"cmt">// 点击了按钮 { GObj.SetButtonProp("Butt1",class="num">0,SlateGray); GObj.SetButtonProp("Butt2",class="num">0,SlateGray); GObj.SetButtonProp("Butt3",class="num">0,SlateGray); GObj.SetButtonProp("Butt4",class="num">1,Chocolate); YPowerFlag=class="num">1;YdBFlag=class="num">1; }
◍ 把频谱面板钉到主图上的代码骨架
想在 MT5 主图直接叠一个频谱分析面板,核心是先声明图形容器对象,再在指标生命周期里分批创建与定位。下面这段骨架把 AllGrObject 类的实例 GObj 作为总控,OnInit 只负责触发创建,不参与绘制逻辑。 OnCalculate 每次重算都先 MainChart.SetOwnProperty() 恢复图表属性,再 gr_object_coordinate() 重设坐标——这意味着面板位置会随图表滚动实时跟随,而不是一次性写死。外汇与贵金属品种在高波动时段重绘频率可能明显上升,需注意对象刷新带来的轻微资源占用。 gr_object_create() 里用 AddLabel 堆出 6 个刻度标签:X 坐标从 28 到 557 像素不等,对应文字 0 / 128 / 256 / 384 / 512 / N,实质是把 512 点 FFT 频域横轴直接标在主图上。你复制这段代码进 MQ5 文件时,若编译报 GObj 未定义,先确认 SpecAnalyzer.mqh 里 AllGrObject 类已暴露 AddLabel 方法。
class="macro">#include "SpecAnalyzer.mqh" GRaphChart MainChart; AllGrObject GObj; class=class="str">"cmt">// <----- 创建 AllGrObject 类的对象 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 自定义指标初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- 指标缓冲区映射 class=class="str">"cmt">//--- gr_object_create(); class=class="str">"cmt">// <----- 创建图形对象 class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 自定义指标迭代函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">class="kw">datetime& time[], const class="type">class="kw">double& open[], const class="type">class="kw">double& high[], const class="type">class="kw">double& low[], const class="type">class="kw">double& close[], const class="type">long& tick_volume[], const class="type">long& volume[], const class="type">int& spread[]) { class=class="str">"cmt">//--- MainChart.SetOwnProperty(); class=class="str">"cmt">// <----- 恢复图表的当前属性 gr_object_coordinate(); class=class="str">"cmt">// <----- 设置图形对象的坐标 class=class="str">"cmt">//--- 返回 prev_calculated 值用于下次调用 class="kw">return(rates_total); } class=class="str">"cmt">//----------------------------------------------- 创建所有的图形对象 -- class="type">void gr_object_create() { GObj.AddLabel("Title",class="num">10,"Arial",DarkGray,class="num">256,class="num">367,"频谱分析仪"); Obj.AddLabel("Label1",class="num">9,"Arial",LightSteelBlue,class="num">557,class="num">128,"class="num">0"); GObj.AddLabel("Label2",class="num">9,"Arial",LightSteelBlue,class="num">422,class="num">128,"class="num">128"); GObj.AddLabel("Label3",class="num">9,"Arial",LightSteelBlue,class="num">294,class="num">128,"class="num">256"); GObj.AddLabel("Label4",class="num">9,"Arial",LightSteelBlue,class="num">166,class="num">128,"class="num">384"); GObj.AddLabel("Label5",class="num">9,"Arial",LightSteelBlue,class="num">40,class="num">128,"class="num">512"); GObj.AddLabel("Label6",class="num">9,"Arial",LightSteelBlue,class="num">28,class="num">156,"N");
「控件坐标与配色落点」
这段初始化把面板右侧的刻度标签和顶部说明一次性铺开:Y 轴方向从 y=141 到 y=341,每隔 50 像素标一个数值,0.00、0.25、0.50、0.75、1.00 正好对应归一化坐标,y=358 处补一个「U」表示上边界。颜色统一用 LightSteelBlue,字号 9、Arial,视觉上和主图区弱区分。 顶部三块灰色说明(DarkGray、字号 9)放在 y=86:x=490 写「Y-轴模式」、x=285 写「输入数据」、x=75 写「水平」、x=185 写「N」,相当于给矩阵列做了静态表头。左侧 x=64 那排 Courier 8 号 DarkOliveGreen 标签(Label17~21)从 y=64 到 y=12 每 13 像素一个,是留给行号或样本索引的空槽。 按钮区才是交互入口:Butt1~Butt4 宽度 185、高度 18,背景 C'32,32,32'、Arial 8 号,分别落在 x=612 的 y=79/61/43/25,文案「振幅(线)」「振幅(对数)」「能量(线)」「能量(对数)」,最后一个参数 0/1 控制线性或对数映射。Butt5、Butt6 挪到 x=403,做「外部数据」和「测试 1. SMA(3)」切换。外汇与贵金属图表加载这类面板时波动剧烈,按钮状态误触可能重算整屏,实盘前建议在 MT5 策略测试器里先点一遍确认重绘耗时。 下面这段是上述标签与按钮的原样声明,可直接粘进 GObj 封装类后对照坐标调间距:
GObj.AddLabel("Label7",class="num">9,"Arial",LightSteelBlue,class="num">569,class="num">141,"class="num">0.00"); GObj.AddLabel("Label8",class="num">9,"Arial",LightSteelBlue,class="num">569,class="num">191,"class="num">0.25"); GObj.AddLabel("Label9",class="num">9,"Arial",LightSteelBlue,class="num">569,class="num">241,"class="num">0.50"); GObj.AddLabel("Label10",class="num">9,"Arial",LightSteelBlue,class="num">569,class="num">291,"class="num">0.75"); GObj.AddLabel("Label11",class="num">9,"Arial",LightSteelBlue,class="num">569,class="num">341,"class="num">1.00"); GObj.AddLabel("Label12",class="num">9,"Arial",LightSteelBlue,class="num">569,class="num">358,"U"); GObj.AddLabel("Label13",class="num">9,"Arial",DarkGray,class="num">490,class="num">86,"Y-轴模式"); GObj.AddLabel("Label14",class="num">9,"Arial",DarkGray,class="num">285,class="num">86,"输入数据"); GObj.AddLabel("Label15",class="num">9,"Arial",DarkGray,class="num">75,class="num">86,"水平"); GObj.AddLabel("Label16",class="num">9,"Arial",DarkGray,class="num">185,class="num">86,"N"); GObj.AddLabel("Label17",class="num">8,"Courier",DarkOliveGreen,class="num">64,class="num">64); GObj.AddLabel("Label18",class="num">8,"Courier",DarkOliveGreen,class="num">64,class="num">51); GObj.AddLabel("Label19",class="num">8,"Courier",DarkOliveGreen,class="num">64,class="num">38); GObj.AddLabel("Label20",class="num">8,"Courier",DarkOliveGreen,class="num">64,class="num">25); GObj.AddLabel("Label21",class="num">8,"Courier",DarkOliveGreen,class="num">64,class="num">12); GObj.AddButton("Butt1",class="num">185,class="num">18,C&class="macro">#x27;class="num">32,class="num">32,class="num">32&class="macro">#x27;,class="num">8,"Arial",SlateGray,class="num">612,class="num">79,"振幅 (线)",class="num">0); GObj.AddButton("Butt2",class="num">185,class="num">18,C&class="macro">#x27;class="num">32,class="num">32,class="num">32&class="macro">#x27;,class="num">8,"Arial",Chocolate,class="num">612,class="num">61,"振幅 (对数)",class="num">1); GObj.AddButton("Butt3",class="num">185,class="num">18,C&class="macro">#x27;class="num">32,class="num">32,class="num">32&class="macro">#x27;,class="num">8,"Arial",SlateGray,class="num">612,class="num">43,"能量 (线)",class="num">0); GObj.AddButton("Butt4",class="num">185,class="num">18,C&class="macro">#x27;class="num">32,class="num">32,class="num">32&class="macro">#x27;,class="num">8,"Arial",SlateGray,class="num">612,class="num">25,"能量 (对数)",class="num">0); GObj.AddButton("Butt5",class="num">185,class="num">18,C&class="macro">#x27;class="num">32,class="num">32,class="num">32&class="macro">#x27;,class="num">8,"Arial",SlateGray,class="num">403,class="num">79,"外部数据",class="num">0); GObj.AddButton("Butt6",class="num">185,class="num">18,C&class="macro">#x27;class="num">32,class="num">32,class="num">32&class="macro">#x27;,class="num">8,"Arial",SlateGray,class="num">403,class="num">61,"测试 class="num">1. SMA(class="num">3)",class="num">0);
画布对象的批量注册与坐标落位
在 MT5 的自定义图形面板里,对象不是凭空显示的,得先向 GObj 容器逐个登记。上面这段把按钮、编辑框、趋势线、箭头线和矩形一次性塞进去:按钮 Butt7~Butt10 用 Arial 或 Wingdings 字体,其中 Wingdings 按钮直接传了 \x0431、\x0432 这类符号码;趋势线一口气加了 8 条,箭头线 2 条,矩形 5 个,颜色多取 C'32,32,32' 或 C'72,72,72' 这类深灰。 登记完只是有了对象,真正决定它们长在哪,是 gr_object_coordinate() 里的 MoveGrObject 调用。趋势线 Trdline1~4 的 x 坐标分别是 48、176、304、432,y 从 150 拉到 360,等于在画布上画出四条等距竖线;Trdline5~8 则固定在 y=350/300/250/200,x 跨 42 到 560,形成横向参考带。 箭头线 Arrline1 从 (560,150) 指向 (28,150),是向左的水平指示;Arrline2 从 (560,150) 落到 (560,370),作右侧垂直标度。矩形 Rect1、Rect2 分别占 (0,1)-(208,110) 和 (208,1)-(416,110),把顶部区域切成了两块。开 MT5 把这段贴进 EA,改一下 MoveGrObject 的像素参数,就能看到面板布局跟着变。
GObj.AddButton("Butt7",class="num">185,class="num">18,C&class="macro">#x27;class="num">32,class="num">32,class="num">32&class="macro">#x27;,class="num">8,"Arial",Chocolate,class="num">403,class="num">43,"测试 class="num">2. SMA(class="num">32)",class="num">1); GObj.AddButton("Butt8",class="num">185,class="num">18,C&class="macro">#x27;class="num">32,class="num">32,class="num">32&class="macro">#x27;,class="num">8,"Arial",SlateGray,class="num">403,class="num">25,"测试 class="num">3. LWMA(class="num">12)",class="num">0); GObj.AddButton("Butt9",class="num">14,class="num">34,C&class="macro">#x27;class="num">32,class="num">32,class="num">32&class="macro">#x27;,class="num">8,"Wingdings",SlateGray,class="num">36,class="num">78,"\x0431",class="num">0); GObj.AddButton("Butt10",class="num">14,class="num">34,C&class="macro">#x27;class="num">32,class="num">32,class="num">32&class="macro">#x27;,class="num">8,"Wingdings",SlateGray,class="num">36,class="num">44,"\x0432",class="num">0); GObj.AddEdit("Edit1",class="num">35,class="num">18,Black,class="num">9,"Arial",SlateGray,class="num">180,class="num">102); GObj.AddTrendLine("Trdline1",C&class="macro">#x27;class="num">32,class="num">32,class="num">32&class="macro">#x27;); GObj.AddTrendLine("Trdline2",C&class="macro">#x27;class="num">32,class="num">32,class="num">32&class="macro">#x27;); GObj.AddTrendLine("Trdline3",C&class="macro">#x27;class="num">32,class="num">32,class="num">32&class="macro">#x27;); GObj.AddTrendLine("Trdline4",C&class="macro">#x27;class="num">32,class="num">32,class="num">32&class="macro">#x27;); GObj.AddTrendLine("Trdline5",C&class="macro">#x27;class="num">32,class="num">32,class="num">32&class="macro">#x27;); GObj.AddTrendLine("Trdline6",C&class="macro">#x27;class="num">32,class="num">32,class="num">32&class="macro">#x27;); GObj.AddTrendLine("Trdline7",C&class="macro">#x27;class="num">32,class="num">32,class="num">32&class="macro">#x27;); GObj.AddTrendLine("Trdline8",C&class="macro">#x27;class="num">32,class="num">32,class="num">32&class="macro">#x27;); GObj.AddArrowline("Arrline1",LightSteelBlue); GObj.AddArrowline("Arrline2",LightSteelBlue); GObj.AddRectangle("Rect1",C&class="macro">#x27;class="num">72,class="num">72,class="num">72&class="macro">#x27;); GObj.AddRectangle("Rect2",C&class="macro">#x27;class="num">72,class="num">72,class="num">72&class="macro">#x27;); GObj.AddRectangle("Rect3",C&class="macro">#x27;class="num">72,class="num">72,class="num">72&class="macro">#x27;); GObj.AddRectangle("Rect4",DarkGray); GObj.AddRectangle("Rect5",C&class="macro">#x27;class="num">72,class="num">72,class="num">72&class="macro">#x27;); } class=class="str">"cmt">//---------- 设置趋势线,箭头线和长方形对象的坐标 -- class="type">void gr_object_coordinate() { GObj.MoveGrObject("Trdline1",class="num">48,class="num">150,class="num">48,class="num">360); GObj.MoveGrObject("Trdline2",class="num">176,class="num">150,class="num">176,class="num">360); GObj.MoveGrObject("Trdline3",class="num">304,class="num">150,class="num">304,class="num">360); GObj.MoveGrObject("Trdline4",class="num">432,class="num">150,class="num">432,class="num">360); GObj.MoveGrObject("Trdline5",class="num">42,class="num">350,class="num">560,class="num">350); GObj.MoveGrObject("Trdline6",class="num">42,class="num">300,class="num">560,class="num">300); GObj.MoveGrObject("Trdline7",class="num">42,class="num">250,class="num">560,class="num">250); GObj.MoveGrObject("Trdline8",class="num">42,class="num">200,class="num">560,class="num">200); GObj.MoveGrObject("Arrline1",class="num">560,class="num">150,class="num">28,class="num">150); GObj.MoveGrObject("Arrline2",class="num">560,class="num">150,class="num">560,class="num">370); GObj.MoveGrObject("Rect1",class="num">0,class="num">1,class="num">208,class="num">110); GObj.MoveGrObject("Rect2",class="num">208,class="num">1,class="num">416,class="num">110);
◍ 用坐标把图形钉到想要的位置
上面三行把五个矩形里的后三个一次性挪了位,参数都是像素坐标,不是价格。Rect3 从 x=416、y=1 拉到 x=624、y=110,相当于贴着图表右侧画一条窄竖条。 Rect4 从 x=0、y=1 到 x=624、y=400,几乎铺满整张图的下半区,适合做背景遮罩或分区底纹。Rect5 只占了 x=20、y=10 到 x=195、y=80 的小块,常用来放标签或说明框。 在 MT5 里跑这段前,先确认图表宽度够 624 像素,否则右边界会被截掉。外汇和贵金属波动剧烈,图形只是辅助定位,不等于方向判断,下单前仍要自担高风险。
GObj.MoveGrObject("Rect3",class="num">416,class="num">1,class="num">624,class="num">110); GObj.MoveGrObject("Rect4",class="num">0,class="num">1,class="num">624,class="num">400); GObj.MoveGrObject("Rect5",class="num">20,class="num">10,class="num">195,class="num">80); } class=class="str">"cmt">//+------------------------------------------------------------------+