快捷手动交易工具箱:持仓和挂单操控·进阶篇
「面板初始化与按钮工厂函数」
在 MT5 自建交易面板时,窗口与控件的创建顺序直接决定后续交互是否生效。先调 m_main_window.CreateWindow 拿到主窗口句柄,若返回 false 必须立即 return,否则后面挂按钮会全部失效。 紧接着 CreateTabs(5,22+27) 建出 5 个标签页,纵向偏移 49 像素(22+27),这一步失败同样要中断。随后连续 5 次 CreateButton 把市价单、挂单、平仓类按钮铺到坐标 (20,10)、(210,10)、(20,60)、(20,110)、(210,60),其中市价相关用 C'87,128,255' 蓝、挂单类用 C'31,209,111' 绿做视觉区分。 CreateModeButton 是统一的按钮工厂:固定尺寸 80×20、字号 9、深紫底白字、悬停与按下不变色,仅接收 x_gap/y_gap 决定位置。注意它先把 button.MainPointer(m_tab) 指向当前 tab,再 m_tab.AddToElementsArray(0,button) 挂进元素数组——漏掉这步按钮不会随标签页切换显示。 CreateModeEdit 的入口注释显示它同样先存窗口指针,逻辑与按钮工厂对称,只是对象换成 CTextEdit。开 MT5 把这套坐标和颜色抄进自己的 EA 面板,能省掉反复调布局的功夫。外汇与贵金属杠杆高,面板只是下单辅助,不代表任何胜率保证。
if(!m_main_window.CreateWindow(m_chart_id,m_subwin,CAPTION_NAME,x,y)) class="kw">return(class="kw">false); class=class="str">"cmt">//--- Tabs if(!CreateTabs(class="num">5,class="num">22+class="num">27)) class="kw">return(class="kw">false); class=class="str">"cmt">//--- if(!CreateButton(m_order_button[class="num">0],MARKET_ORDER_NAME+"(M)",C&class="macro">#x27;class="num">87,class="num">128,class="num">255&class="macro">#x27;,class="num">20,class="num">10)) class="kw">return(class="kw">false); if(!CreateButton(m_order_button[class="num">1],PENDING_ORDER_NAME+"(P)",C&class="macro">#x27;class="num">31,class="num">209,class="num">111&class="macro">#x27;,class="num">210,class="num">10)) class="kw">return(class="kw">false); if(!CreateButton(m_order_button[class="num">2],MARKET_ORDERS_PROFIT_CLOSE+"(C)",C&class="macro">#x27;class="num">87,class="num">128,class="num">255&class="macro">#x27;,class="num">20,class="num">60)) class="kw">return(class="kw">false); if(!CreateButton(m_order_button[class="num">3],MARKET_ORDERS_LOSS_CLOSE+"(D)",C&class="macro">#x27;class="num">87,class="num">128,class="num">255&class="macro">#x27;,class="num">20,class="num">110)) class="kw">return(class="kw">false); if(!CreateButton(m_order_button[class="num">4],PEND_ORDERS_ALL_CLOSE+"(R)",C&class="macro">#x27;class="num">31,class="num">209,class="num">111&class="macro">#x27;,class="num">210,class="num">60)) class="kw">return(class="kw">false); class=class="str">"cmt">//--- class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CFastTrading::CreateModeButton(CButton &button,class="type">class="kw">string text,class="type">int x_gap,class="type">int y_gap) { class=class="str">"cmt">//--- Store the window pointer button.MainPointer(m_tab); class=class="str">"cmt">//--- Attach to tab m_tab.AddToElementsArray(class="num">0,button); class="type">class="kw">color baseclr=clrDarkViolet; class=class="str">"cmt">//--- Set properties before creation button.XSize(class="num">80); button.YSize(class="num">20); button.Font(m_base_font); button.FontSize(class="num">9); button.BackColor(baseclr); button.BackColorHover(baseclr); button.BackColorPressed(baseclr); button.BorderColor(baseclr); button.BorderColorHover(baseclr); button.BorderColorPressed(baseclr); button.LabelColor(clrWhite); button.LabelColorPressed(clrWhite); button.LabelColorHover(clrWhite); button.IsCenterText(true); class=class="str">"cmt">//--- Create a control element if(!button.CreateButton(text,x_gap,y_gap)) class="kw">return(class="kw">false); class=class="str">"cmt">//--- Add a pointer to the element to the database CWndContainer::AddToElementsArray(class="num">0,button); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CFastTrading::CreateModeEdit(CTextEdit &text_edit,class="type">int x_gap,class="type">int y_gap) { class=class="str">"cmt">//--- Store the window pointer
◍ 挂单面板的输入框与窗口初始化
在 MT5 自定义面板里,数值输入框(CTextEdit)要先挂到标签页元素数组才能参与事件循环。下面这段把 text_edit 绑到 m_tab 的第 0 组,并设了 80×20 的像素尺寸、2 位小数、步进 0.01、区间 [0.01, 99999],同时开了 SpinEditMode 让交易者能直接滚轮调手数。 text_edit.SetValue(string(0.01)) 把初值锁成 0.01 手,随后 IsLocked(true) 禁止手动改——这种写法适合做“默认手数不可误触”的快单界面。注意 GetTextBoxPointer().XGap(1) 只调了文本区左边距,框体 XSize 仍是 80,渲染时不会错位。 CreateMainWindow 则搭了 600×375 的主窗,标题栏高 22、可拖动,背景与标题色用成员变量统一接管。它先建窗,再在 (5,20) 偏移上调用 CreateTabs(5,49) 生成标签页,然后以 45 像素为行距铺了 5 个下单按钮:市价单蓝、挂单绿、平仓类分别落在 x=10 与 x=300 两列。外汇与贵金属波动剧烈,这类一键面板若误触可能瞬间成交,实盘前务必在策略测试器里先验证坐标与锁定逻辑。 把下面代码直接塞进你的 CWndContainer 派生类,编译后开 MT5 附加到图表,就能看到可拖动的快单窗体原型。
text_edit.MainPointer(m_tab); class=class="str">"cmt">//--- Attach to tab m_tab.AddToElementsArray(class="num">0,text_edit); class=class="str">"cmt">//--- Properties text_edit.XSize(class="num">80); text_edit.YSize(class="num">20); text_edit.Font(m_base_font); text_edit.FontSize(m_base_font_size); text_edit.SetDigits(class="num">2); text_edit.MaxValue(class="num">99999); text_edit.StepValue(class="num">0.01); text_edit.MinValue(class="num">0.01); text_edit.SpinEditMode(true); text_edit.GetTextBoxPointer().XGap(class="num">1); text_edit.GetTextBoxPointer().XSize(class="num">80); class=class="str">"cmt">//--- Create a control element if(!text_edit.CreateTextEdit("",x_gap,y_gap)) class="kw">return(class="kw">false); text_edit.SetValue(class="type">class="kw">string(class="num">0.01)); text_edit.IsLocked(true); class=class="str">"cmt">//--- Add the object to the common array of object groups CWndContainer::AddToElementsArray(class="num">0,text_edit); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Creates a form for orders | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CFastTrading::CreateMainWindow(class="type">void) { class=class="str">"cmt">//--- Add a window pointer to the window array CWndContainer::AddWindow(m_main_window); class=class="str">"cmt">//--- Properties m_main_window.XSize(class="num">600); m_main_window.YSize(class="num">375); class=class="str">"cmt">//--- Coordinates class="type">int x=class="num">5; class="type">int y=class="num">20; m_main_window.CaptionHeight(class="num">22); m_main_window.IsMovable(true); m_main_window.CaptionColor(m_caption_color); m_main_window.CaptionColorLocked(m_caption_color); m_main_window.CaptionColorHover(m_caption_color); m_main_window.BackColor(m_background_color); m_main_window.FontSize(m_base_font_size); m_main_window.Font(m_base_font); class=class="str">"cmt">//--- Create the form if(!m_main_window.CreateWindow(m_chart_id,m_subwin,CAPTION_NAME,x,y)) class="kw">return(class="kw">false); class=class="str">"cmt">//--- Tabs if(!CreateTabs(class="num">5,class="num">22+class="num">27)) class="kw">return(class="kw">false); class=class="str">"cmt">//--- if(!CreateButton(m_order_button[class="num">0],MARKET_ORDER_NAME+"(M)",C&class="macro">#x27;class="num">87,class="num">128,class="num">255&class="macro">#x27;,class="num">10,class="num">10)) class="kw">return(class="kw">false); if(!CreateButton(m_order_button[class="num">1],PENDING_ORDER_NAME+"(P)",C&class="macro">#x27;class="num">31,class="num">209,class="num">111&class="macro">#x27;,class="num">300,class="num">10)) class="kw">return(class="kw">false); if(!CreateButton(m_order_button[class="num">2],CLOSE_ALL_PROFIT+"(C)",C&class="macro">#x27;class="num">87,class="num">128,class="num">255&class="macro">#x27;,class="num">10,class="num">10+class="num">45*class="num">1)) class="kw">return(class="kw">false); if(!CreateButton(m_order_button[class="num">3],PEND_ORDERS_ALL_CLOSE+"(R)",C&class="macro">#x27;class="num">31,class="num">209,class="num">111&class="macro">#x27;,class="num">300,class="num">10+class="num">45*class="num">1)) class="kw">return(class="kw">false); if(!CreateButton(m_order_button[class="num">4],CLOSE_BUY_PROFIT,C&class="macro">#x27;class="num">87,class="num">128,class="num">255&class="macro">#x27;,class="num">10,class="num">10+class="num">45*class="num">2)) class="kw">return(class="kw">false);
平仓按钮与模式切换的界面布局逻辑
这段面板初始化代码把「按盈亏平仓」的按钮排成了纵向队列:索引 5 到 8 分别对应平盈利多单、平盈利空单(带 D 后缀的平所有亏损单)、平亏损多单、平亏损空单,Y 坐标用 10+45*N 算出来,按钮高度间距固定 45 像素,配色统一走 C'87,128,255'(一种蓝)。
紧接着的 for 循环只跑 6 次,给 m_mode_button 和 m_mode_edit 铺面板:按钮 X 在 195(205-10),编辑框 X 在 194(204-10),Y 随 i+1 步进 45。也就是说模式区最多挂 6 行,超了就不画——想加第 7 种过滤条件得先改循环上限。
底下那组 #define 把六种平仓语义做成英俄双语宏,运行时靠 m_language==RUSSIAN 切标签。你在 MT5 里接这套,若只做英文环境可直接删掉三元判断,省一点字符串开销。
ModeButtonSwitch 的开头用 lparam==button.Id() 做事件源过滤,再取 states 数组长度做轮询,比对 LabelText() 决定高亮态。外汇与贵金属杠杆高,这类一键平仓面板误触可能造成快速回撤,实盘前务必在策略测试器里用历史 tick 验证按钮响应。
if(!CreateButton(m_order_button[class="num">5],CLOSE_SELL_PROFIT,C&class="macro">#x27;class="num">87,class="num">128,class="num">255&class="macro">#x27;,class="num">10,class="num">10+class="num">45*class="num">3)) class="kw">return(class="kw">false); if(!CreateButton(m_order_button[class="num">6],CLOSE_ALL_LOSS+"(D)",C&class="macro">#x27;class="num">87,class="num">128,class="num">255&class="macro">#x27;,class="num">10,class="num">10+class="num">45*class="num">4)) class="kw">return(class="kw">false); if(!CreateButton(m_order_button[class="num">7],CLOSE_BUY_LOSS,C&class="macro">#x27;class="num">87,class="num">128,class="num">255&class="macro">#x27;,class="num">10,class="num">10+class="num">45*class="num">5)) class="kw">return(class="kw">false); if(!CreateButton(m_order_button[class="num">8],CLOSE_SELL_LOSS,C&class="macro">#x27;class="num">87,class="num">128,class="num">255&class="macro">#x27;,class="num">10,class="num">10+class="num">45*class="num">6)) class="kw">return(class="kw">false); class=class="str">"cmt">//--- for(class="type">int i=class="num">0; i<class="num">6; i++) { if(!CreateModeButton(m_mode_button[i],"all",class="num">205-class="num">10,class="num">10+class="num">45*(i+class="num">1))) class="kw">return(class="kw">false); if(!CreateModeEdit(m_mode_edit[i],class="num">204-class="num">10,class="num">30+class="num">45*(i+class="num">1))) class="kw">return(class="kw">false); } class=class="str">"cmt">//--- class="kw">return(true); } class="macro">#define CLOSE_BUY_PROFIT(m_language==RUSSIAN ? "Закрыть BUY прибыльные" : "Close BUY Profit") class="macro">#define CLOSE_SELL_PROFIT(m_language==RUSSIAN ? "Закрыть SELL прибыльные" : "Close SELL Profit") class="macro">#define CLOSE_ALL_PROFIT(m_language==RUSSIAN ? "Закрыть ВСЕ прибыльные" : "Close ALL Profit") class="macro">#define CLOSE_BUY_LOSS(m_language==RUSSIAN ? "Закрыть BUY убыточные" : "Close BUY Losing") class="macro">#define CLOSE_SELL_LOSS(m_language==RUSSIAN ? "Закрыть SELL убыточные" : "Close SELL Losing") class="macro">#define CLOSE_ALL_LOSS(m_language==RUSSIAN ? "Закрыть ВСЕ убыточные" : "Close ALL Losing") class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CFastTrading::ModeButtonSwitch(CButton &button,class="type">long lparam,class="type">class="kw">string &states[]) { if(lparam==button.Id()) { class="type">int size=ArraySize(states); for(class="type">int i=class="num">0; i<size; i++) { if(button.LabelText()==states[i]) {
「按钮切换时编辑框的参数联动」
在自建快速交易面板里,按钮标签文字直接决定编辑框的数值类型。上面这段逻辑遍历 6 个模式按钮,一旦命中 lparam 对应的控件,就按 states 数组里的标签切换编辑框属性。 当标签等于 states[1] 时,编辑框解锁、整数位 0、步进 1、范围 1~9999,并强制写入字符串 "20",同时标记 m_current_mode[i]=1。若标签是 states[2],则改成两位小数、步进 0.01、下限 0.01 上限 99999,默认填 "0.1",模式记为 2——这是给点数或手数微调用的。 states[3] 分支又回到整数配置,和 states[1] 几乎一致只是模式值置 3。开 MT5 把这段塞进 CFastTrading::ModeEditSwitch,改 states 文本就能验证编辑框位数和步进是否跟着跳。外汇与贵金属波动剧烈,面板参数误设可能导致下单量异常,实盘前务必在策略测试器跑一遍。
if(i==size-class="num">1) { SetButtonParam(button,states[class="num">0]); class="kw">break; } else { SetButtonParam(button,states[i+class="num">1]); class="kw">break; } } } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CFastTrading::ModeEditSwitch(class="type">long lparam,class="type">class="kw">string &states[]) { for(class="type">int i=class="num">0; i<class="num">6; i++) { if(lparam==m_mode_button[i].Id()) { if(m_mode_button[i].LabelText()==states[class="num">1]) { m_mode_edit[i].IsLocked(class="kw">false); m_mode_edit[i].SetDigits(class="num">0); m_mode_edit[i].StepValue(class="num">1); m_mode_edit[i].MaxValue(class="num">9999); m_mode_edit[i].MinValue(class="num">1); m_mode_edit[i].SetValue(class="type">class="kw">string(class="num">20)); m_mode_edit[i].GetTextBoxPointer().Update(true); m_current_mode[i]=class="num">1; } else if(m_mode_button[i].LabelText()==states[class="num">2]) { m_mode_edit[i].IsLocked(class="kw">false); m_mode_edit[i].SetDigits(class="num">2); m_mode_edit[i].StepValue(class="num">0.01); m_mode_edit[i].MaxValue(class="num">99999); m_mode_edit[i].MinValue(class="num">0.01); m_mode_edit[i].SetValue(class="type">class="kw">string(class="num">0.1)); m_mode_edit[i].GetTextBoxPointer().Update(true); m_current_mode[i]=class="num">2; } else if(m_mode_button[i].LabelText()==states[class="num">3]) { m_mode_edit[i].IsLocked(class="kw">false); m_mode_edit[i].SetDigits(class="num">0); m_mode_edit[i].StepValue(class="num">1); m_mode_edit[i].MaxValue(class="num">9999); m_mode_edit[i].MinValue(class="num">1); m_mode_edit[i].SetValue(class="type">class="kw">string(class="num">20));
◍ 平仓面板的盈利模式分支与一键清仓
这段逻辑里,模式 4(sum>points)和模式 5(sum>currency)走的是汇总判断分支:当 m_current_mode[0] > 2 时,才进入持仓循环累加 sum_pp 与 sum_cur,否则单笔条件分支直接生效。 按钮切换时若标签等于 states[4](即 "sum>currency"),编辑框会被解锁、精度设为 2 位、步进 0.01、上下限 0.01~99999,并预填 0.1,m_current_mode[i] 记为 4;若匹配不到任何已知标签,编辑框锁死且模式归 0。 CloseAllMarketProfit 绑定了图元点击(m_order_button[2])和键盘 C 键两条触发路径。函数内先取 PositionsTotal() 得到当前持仓数,再按模式决定是否累计盈亏;外汇与贵金属杠杆高,一键全平可能触发滑点或断崖式净值波动,实盘前务必在策略测试器用历史数据跑通。 让小布替你跑这套 把 ModeEditSwitch 里的 states 数组和 m_current_mode 打印到 Experts 日志,切五次按钮看 m_current_mode 是否按 3/4/0 跳变,能省掉一半界面联调时间。
m_mode_edit[i].GetTextBoxPointer().Update(true); m_current_mode[i]=class="num">3; } else if(m_mode_button[i].LabelText()==states[class="num">4]) { m_mode_edit[i].IsLocked(class="kw">false); m_mode_edit[i].SetDigits(class="num">2); m_mode_edit[i].StepValue(class="num">0.01); m_mode_edit[i].MaxValue(class="num">99999); m_mode_edit[i].MinValue(class="num">0.01); m_mode_edit[i].SetValue(class="type">class="kw">string(class="num">0.1)); m_mode_edit[i].GetTextBoxPointer().Update(true); m_current_mode[i]=class="num">4; } else { m_mode_edit[i].IsLocked(true); m_current_mode[i]=class="num">0; } } } } class=class="str">"cmt">//--- class="type">class="kw">string states[class="num">5]= {"all",">points",">currency","sum>points","sum>currency"}; for(class="type">int i=class="num">0; i<class="num">6; i++) ModeButtonSwitch(m_mode_button[i],lparam,states); class=class="str">"cmt">//--- ModeEditSwitch(lparam,states); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CFastTrading::CloseAllMarketProfit(class="type">int id,class="type">long lparam) { if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_order_button[class="num">2].Id()) || (id==CHARTEVENT_KEYDOWN && lparam==KEY_C)) { class=class="str">"cmt">//--- declare the request and the result class="type">MqlTradeRequest request; class="type">MqlTradeResult result; class="type">int total=PositionsTotal(); class=class="str">"cmt">// the number of open positions class="type">int sum_pp=class="num">0; class="type">class="kw">double sum_cur=class="num">0.0; class=class="str">"cmt">//--- Calculate profit for modes class="num">4 and class="num">5 if(m_current_mode[class="num">0]>class="num">2) { for(class="type">int i=total-class="num">1; i>=class="num">0; i--) { class=class="str">"cmt">//--- order parameters class="type">class="kw">ulong position_ticket=PositionGetTicket(i); class=class="str">"cmt">// position ticket
按魔术码与品种归集浮盈点数
遍历持仓时,先用 PositionGetString(POSITION_SYMBOL) 取出品种、PositionGetInteger(POSITION_MAGIC) 取魔术码,再用 PositionGetInteger(POSITION_TYPE) 判定多空方向。利润与库存费分别走 PositionGetDouble(POSITION_PROFIT) 和 POSITION_SWAP,这两项是账户货币计值,和后面的点数要分开算。 点数换算看方向:买单用 (SYMBOL_BID - 开仓价)/_Point,卖单用 (开仓价 - SYMBOL_ASK)/_Point,强转 int 后得到 profit_pp。若当前图表品种与持仓品种一致且魔术码等于 m_magic_number,才进入模式分支。 模式 3 把 profit_pp 累加到 sum_pp,模式 4 把 profit_cur+swap 累加到 sum_cur。这意味着同品种同魔术码的持仓,在模式 3 下只统计点数、不碰金额;模式 4 则忽略点数、只统计净值含隔夜费。 实际验证时把 m_current_mode[0] 在 3 和 4 之间切一下,观察 sum_pp 与 sum_cur 的输出差异,能直接看清 EA 到底在按哪种维度管仓。外汇与贵金属杠杆高,库存费在跨周时会显著改变 sum_cur,回测和实盘都建议单独打印 swap 字段。
class="type">class="kw">string position_symbol=PositionGetString(POSITION_SYMBOL); class=class="str">"cmt">// symbol class="type">class="kw">ulong magic=PositionGetInteger(POSITION_MAGIC); class=class="str">"cmt">// position MagicNumber class="type">ENUM_POSITION_TYPE type=(class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">// position type class="type">class="kw">double profit_cur=PositionGetDouble(POSITION_PROFIT); class="type">class="kw">double swap=PositionGetDouble(POSITION_SWAP); class=class="str">"cmt">//--- class="type">int profit_pp; if(type==POSITION_TYPE_BUY) profit_pp=class="type">int((SymbolInfoDouble(position_symbol,SYMBOL_BID)-PositionGetDouble(POSITION_PRICE_OPEN))/_Point); else profit_pp=class="type">int((PositionGetDouble(POSITION_PRICE_OPEN)-SymbolInfoDouble(position_symbol,SYMBOL_ASK))/_Point); class=class="str">"cmt">//--- if(magic==m_magic_number) if(position_symbol==Symbol()) { class="kw">switch(m_current_mode[class="num">0]) { case class="num">3: sum_pp+=profit_pp; class="kw">break; case class="num">4: sum_cur+=profit_cur+swap; class="kw">break; } } class=class="str">"cmt">//--- iterate over open positions for(class="type">int i=total-class="num">1; i>=class="num">0; i--) { class=class="str">"cmt">//--- order parameters class="type">class="kw">ulong position_ticket=PositionGetTicket(i); class=class="str">"cmt">// position ticket