快捷手动交易工具箱:持仓和挂单操控(基础篇)
手动交易也能像脚本一样快
在 MT5 里做手动交易,最耗时的不是判断方向,而是反复点右键、填手数、拉止损。2020 年 11 月发布的这套快捷工具箱,把持仓和挂单的常用操控收敛进一组热键与面板,社区帖 5 669 次浏览、10 条讨论,说明手动党确实有这需求。 它不替你决策,只把「平仓一半」「挂单改到点位 A」「一键锁仓」这类动作压缩到一次点击。外汇和贵金属杠杆高,误触可能瞬间放大风险,所以工具默认不自动带止损,留给你自己定。 原文结构分了问题表现、程序重构、收尾三段,核心就一句:把重复操作从 GUI 迷宫里捞出来,交给快捷键。开 MT5 后你也能照这个思路,用 EA 的 OnChartEvent 接鼠标和按键,自己攒一个顺手箱子。
◍ 手动交易工具箱目前只管入场
早先做的那套辅助函数,重心全压在手动交易员的市价下单体验上,绝大多数接口都围绕一键市价入场。回看任何一套交易逻辑,不管人手还是 EA,市价运作都该拆成三段:准入规则、持仓管理、离场条件。 目前这个工具箱实际只覆盖了第一段——准入。也就是说,持仓怎么挪、挂单怎么挂、什么时候砍,都还没接进来。后续迭代的方向很明确:把持仓操控和挂单能力补上,再把平仓判定做宽。 需要钉死的一点是分工边界:所有数值计算交给工具箱跑,决断权留在交易者手里。外汇和贵金属波动剧烈、杠杆风险高,机器算得再快也不能替你拍板。
「现有面板到底卡在哪」
原面板是把主窗口和一组任务按钮写死的:两个按钮弹模式窗口,分别管市价单和挂单的开单工具;另外三个按钮只做市价平仓、删全部挂单这类简单动作。要在 MT5 里加持仓管理和挂单编辑,第一步就得动应用程序结构——主窗口那套写死的逻辑不够用了。 订单操作也偏糙。之前只能把全部盈利或亏损订单一把平掉,现在要按持仓类型切分,还得能挂条件去选全部或指定订单平仓。挂单侧更寒碜:早先工具箱只支持批量删除自己建的挂单,单独改挂单价、删某一张都做不到。 一个可验证的现象:在 MT5 终端里打开早期版本面板,右键持仓区没有「修改」入口,挂单列表也不支持单选删除,这就是结构限制的直接表现。外汇和贵金属杠杆高,这类功能缺口会放大误操作风险。 下一步就是把上面每一个阶段拆开,看代码层面怎么改。
把工具箱拆成三块选项卡
原主窗口里的功能被重新切分,按图例1的职责划分归到三个类:开仓/创建、平仓/删除、以及手工管理与修改。实际落地时对应三个选项卡,第一个承载图例1里的交易函数,另外两个分别管在场持仓和挂单。 在场持仓选项卡(图例3)直接复刻了 MT5 终端“交易”标签的表格样式,列出本程序开出的所有在场仓位。下方三个输入框绑定成交量、止损、止盈三列;点选某行后,字段回显,可改 SL/TP 或删除。平仓按钮会先读交易量框——填小于持仓手数的值就能做部分平仓,这是手动减仓的入口。 挂单选项卡(图例4)结构类似:表格只显示本工具箱下的挂单,选单后可改执行价、SL、TP,Edit 放开三个输入域,Close 直接撤单。 Trading 选项卡扩展了双向平仓(图例5):Close BUY profit / Close SELL profit / Close BUY loss / Close SELL loss 四个按钮配合右侧开关。开关含 All(默认无限制)、>Point(单仓盈亏超点数)、>Currency(单仓盈亏超存款币金额)、Sum>Points(同类型总盈亏超点数)、Sum>Currency(同类型总盈亏超存款币金额)。注意后两种只累计带本程序魔幻数字的开仓,且需同时满足类型与魔幻号。例:选 Close All Loss + sum>currency,程序只扫自己开的仓,累计亏损折存款币超 10 单位才全平。外汇与贵金属杠杆高,这类批量平仓逻辑误设阈值可能瞬间清空账户,上 MT5 前先拿模拟盘验一遍开关组合。
◍ 给手动交易工具箱加选项卡与持仓管理界面
在 MT5 里给早前的快捷手动交易工具箱扩功能,第一步是把单窗口重构成多选项卡。基类 CProgram 里加 CreateTabs() 方法,MainWindow.mqh 中实现,选卡标题靠 Defines.mqh 里的宏替换做双语支持,编译后所有按钮先归到第一个选项卡之下。 可视化只是壳。逻辑上要建 ModeButtonSwitch() 和 ModeEditSwitch() 两个新方法:前者切换平仓模式,后者按模式改输入字段属性——比如点数是整数,存款货币下就显示两位小数。静态数组 m_current_mode 大小固定为 6,对应 6 个模式开关,用户选择写进数组,OnEvent() 里挂上点击事件即可生效。 平仓动作从「全部盈利/亏损」扩到按模式计算:模式 4、5 分别以点数或存款货币统计总利润/亏损,满足条件才平掉工具箱开的仓。买单/卖单盈利亏损平仓是上面方法的特例,加持仓类型过滤就行,热键也能在代码里重绑,按钮旁直接显示键值。 第二个选项卡做「Market Positions Control」:CreateLotControl()、CreateStopLossControl()、CreateTakeProfitControl() 建三个编辑字段,CreateModifyButton() 和 CreateCloseButton() 建按钮,CreatePositionsTable() 建 9 列表格。窗口宽度用 WindowRezise() 按内容自适应,不然表格会超出右边缘。 表格数据分三步:InitializePositionsTable() 按品种和魔幻数字筛持仓并建对应行;SetValuePositionTable() 填每笔参数;UpdatePositionsTable() 刷新。启动时任 OnInit() 调初始化,之后 OnTrade() 跟持仓数量与参数变化,OnTick() 里调 UpdatePositionProfit() 刷利润,点行就把手数/止损/止盈灌进输入字段。 挂单选项卡复用上面逻辑:建订单表、InitializeOrdersTable() 找挂单、SetValuesToOrderTable() 填表、ModifyOrder()/RemoveOrder() 执行修改删除。额外做了图表拾价——点输入字段后去图表上用十字准线单击取价写回字段,比手敲快,直观分析时很好用。外汇和贵金属波动大,这类半自动工具只降操作成本,不消除高风险。
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| Create a group with tabs |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">bool</span> CFastTrading::CreateTabs(<span class="keyword">class="kw">const</span> <span class="keyword">class="type">int</span> x_gap,<span class="keyword">class="kw">const</span> <span class="keyword">class="type">int</span> y_gap) { <span class="comment">class=class="str">"cmt">//--- Store the pointer to the main control</span> m_tab.MainPointer(m_main_window); <span class="comment">class=class="str">"cmt">//--- Properties</span> m_tab.Font(m_base_font); m_tab.FontSize(m_base_font_size); m_tab.LabelColor(<span class="macro">clrWhite</span>); m_tab.LabelColorHover(<span class="macro">clrWhite</span>); m_tab.IsCenterText(<span class="macro">true</span>); m_tab.AutoXResizeMode(<span class="macro">true</span>); m_tab.AutoYResizeMode(<span class="macro">true</span>); m_tab.AutoXResizeRightOffset(<span class="number">class="num">5</span>); m_tab.AutoYResizeBottomOffset(<span class="number">class="num">5</span>); m_tab.TabsYSize(<span class="number">class="num">27</span>); m_tab.GetButtonsGroupPointer().Font(m_base_font); m_tab.GetButtonsGroupPointer().FontSize(m_base_font_size); <span class="comment">class=class="str">"cmt">//--- Add tabs with the specified properties</span>
「把面板窗口和按钮真正画出来」
上面那套标签和宏定义只是铺垫,真正让交易面板在 MT5 图表上显形的是 CreateMainWindow 与 CreateButton 这两个成员函数。先说主窗体:代码里把 m_main_window 的 XSize 设成 600、YSize 设成 375,标题栏高度 22,左上角坐标锁在 x=5、y=20,且 IsMovable(true) 允许拖拽。这套尺寸在 1920×1080 屏上约占右侧 1/3,不会挡掉主图 K 线,读者可直接改这两个数验证布局。 按钮的创建逻辑更死板也更好抄:CreateButton 里统一把 XSize 写死 180、YSize 写死 40,背景色与边框色都用同一个 baseclr 参数,文字颜色强制 clrWhite 且 IsCenterText(true) 居中。也就是说你每加一个功能键,只要传不同的 text 和 baseclr,位置靠 x_gap / y_gap 错开即可,不用重设样式。 标签页的挂载在开头那段循环里:tabs_names 数组长度 3,分别指向 Trading、Market Control、Pending Control 三个宏(宏里按 m_language 切换俄文 / 英文),for 循环调 m_tab.AddTab(...,180) 把每个标签宽度固定 180,然后 CreateTabs 一次性生成。外汇与贵金属界面定制高风险,参数乱改可能导致控件重叠无法点击,建议先在模拟盘账户跑。 顺带一提,所有控件最后都通过 CWndContainer::AddToElementsArray(0,...) 塞进 0 号元素组,主窗体本身用 AddWindow 登记。漏掉这一步,对象不会随图表刷新重绘,只会静默消失。
class="type">class="kw">string tabs_names[class="num">3]; tabs_names[class="num">0]=TRADING; tabs_names[class="num">1]=CAPTION_M_CONTROL_NAME; tabs_names[class="num">2]=CAPTION_P_CONTROL_NAME; for(class="type">int i=class="num">0; i<class="num">3; i++) m_tab.AddTab(tabs_names[i],class="num">180); class=class="str">"cmt">//--- Create a control element if(!m_tab.CreateTabs(x_gap,y_gap)) class="kw">return(class="kw">false); class=class="str">"cmt">//--- Add the object to the common array of object groups CWndContainer::AddToElementsArray(class="num">0,m_tab); class="kw">return(true); } class="macro">#define TRADING(m_language==RUSSIAN ? "Трейдинг" : "Trading") class="macro">#define CAPTION_M_CONTROL_NAME(m_language==RUSSIAN ? "Контроль рыночных позиций" : "Market Control") class="macro">#define CAPTION_P_CONTROL_NAME(m_language==RUSSIAN ? "Контроль отложенных ордеров" : "Pending Control") class="type">bool CFastTrading::CreateButton(CButton &button,class="type">class="kw">string text,class="type">class="kw">color baseclr,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=class="str">"cmt">//--- Set properties before creation button.XSize(class="num">180); button.YSize(class="num">40); button.Font(m_base_font); button.FontSize(m_base_font_size); 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="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