MQL5交易管理面板开发(第九部分):代码组织(4):交易管理面板类·进阶篇
(2/3)· 从散装函数到CTradeManagementPanel类,解决大型面板集成与维护的两大难题
◍ 挂单面板的控件坐标怎么排
这段逻辑在搭一个自定义交易面板的 UI,先把市价单的成交量编辑框和卖出按钮画出来,再往下排 TP/SL 输入行,最后切到挂单区画表头。坐标全靠 curX、curY 和 GAP 这几个变量递推,改一个间距常数,整块布局会跟着挪。 成交量编辑框创建完之后,代码会去读 SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN),把当前品种的最小交易量塞进框里并保留 2 位小数。不同品种这个值差很多,比如 XAUUSD 常见是 0.01,而某些外汇对可能是 0.0001 量级,面板初始化时显示的数字直接受它控制。 TP 和 SL 的编辑框默认写死 "0.00000",旁边各挂一个报错标签(初始空串),放在输入框下方一行的 DEFAULT_LABEL_HEIGHT 处。注意 slX 被算成 2*EDIT_WIDTH,但实际创建时又加了 4*curX 偏移,这种混合坐标写法在后续微调列宽时很容易算错,建议开 MT5 把这几个宏打印出来核对。 挂单区表头用 headerX = curX + BUTTON_WIDTH + GAP 起头,后面 TP、SL、过期时间标签各自再叠 EDIT_WIDTH+GAP 的步长。最后用 TimeCurrent() 取当前时间,给挂单过期时间准备默认值——外汇和贵金属杠杆高,挂单过期设错可能让你在波动中吃到意外成交,验证前先把这段跑一遍看日期格式。
class="kw">return class="kw">false; if(!CreateEdit(m_volumeEdit, "VolumeEdit", volX, curY, EDIT_WIDTH, EDIT_HEIGHT)) class="kw">return class="kw">false; class="type">class="kw">double minVolume = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN); m_volumeEdit.Text(DoubleToString(minVolume, class="num">2)); class="type">int sellBtnX = volX + EDIT_WIDTH + GAP; if(!CreateButton(m_sellButton, "Sell", sellBtnX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrRed)) class="kw">return class="kw">false; curY += BUTTON_HEIGHT + GAP; class=class="str">"cmt">// Next row: TP and SL for market orders if(!CreateEdit(m_tpEdit, "TPEdit", class="num">4*curX+ GAP , curY, EDIT_WIDTH, EDIT_HEIGHT)) class="kw">return class="kw">false; m_tpEdit.Text("class="num">0.00000"); if(!CreateLabelEx(m_tpRightLabel, curX + INPUT_LABEL_GAP, curY, DEFAULT_LABEL_HEIGHT, "TP", "TP:", clrBlack)) class="kw">return class="kw">false; if(!CreateLabelEx(m_tpErrorLabel, curX, curY + DEFAULT_LABEL_HEIGHT, DEFAULT_LABEL_HEIGHT, "TPError", "", clrBlack)) class="kw">return class="kw">false; class="type">int slX = class="num">2*EDIT_WIDTH ; if(!CreateEdit(m_slEdit, "SLEdit", slX + class="num">4*curX, curY, EDIT_WIDTH, EDIT_HEIGHT)) class="kw">return class="kw">false; m_slEdit.Text("class="num">0.00000"); if(!CreateLabelEx(m_slRightLabel, slX , curY, DEFAULT_LABEL_HEIGHT, "SL", "SL:", clrBlack)) class="kw">return class="kw">false; if(!CreateLabelEx(m_slErrorLabel, slX, curY + DEFAULT_LABEL_HEIGHT, DEFAULT_LABEL_HEIGHT, "SLError", "", clrBlack)) class="kw">return class="kw">false; curY += EDIT_HEIGHT + GAP*class="num">2; class=class="str">"cmt">// Section class="num">2: Pending Orders if(!CreateLabelEx(m_secPendingLabel, curX, curY, DEFAULT_LABEL_HEIGHT, "SecPend", "Pending Orders:", clrBlack)) class="kw">return class="kw">false; curY += DEFAULT_LABEL_HEIGHT + GAP; class=class="str">"cmt">// Column headers for pending orders(each label includes a colon) class="type">int headerX = curX + BUTTON_WIDTH + GAP; if(!CreateLabelEx(m_pendingPriceHeader, headerX, curY, DEFAULT_LABEL_HEIGHT, "PendPrice", "Price:", clrBlack)) class="kw">return class="kw">false; if(!CreateLabelEx(m_pendingTPHeader, headerX + EDIT_WIDTH + GAP, curY, DEFAULT_LABEL_HEIGHT, "PendTP", "TP:", clrBlack)) class="kw">return class="kw">false; if(!CreateLabelEx(m_pendingSLHeader, headerX + class="num">2*(EDIT_WIDTH + GAP), curY, DEFAULT_LABEL_HEIGHT, "PendSL", "SL:", clrBlack)) class="kw">return class="kw">false; if(!CreateLabelEx(m_pendingExpHeader, headerX + class="num">3*(EDIT_WIDTH + GAP), curY, DEFAULT_LABEL_HEIGHT, "PendExp", "Expiration Date:", clrBlack)) class="kw">return class="kw">false; curY += DEFAULT_LABEL_HEIGHT + GAP; class=class="str">"cmt">// Prepare class="kw">default expiration value as current day end time. class="type">class="kw">datetime now = TimeCurrent();
「挂单面板的逐行坐标拼装」
在 MT5 自定义面板里铺挂单控件,核心是用横纵偏移量把按钮和编辑框排成一行。下面这段把 Buy Limit / Buy Stop / Sell Limit 的控件生成逻辑直接落到了坐标算术上,每行间距由 GAP 常量控制,垂直方向每铺完一行就令 curY 累加 BUTTON_HEIGHT+GAP。 到期时间编辑框宽度是普通编辑框的两倍(2*EDIT_WIDTH),因为它要塞进「2024.05.20 23:59:59」这种日期字符串;价格、TP、SL 编辑框统一用 EDIT_WIDTH。初始值里价格框直接读 SYMBOL_ASK 或 SYMBOL_BID 并格式化为 5 位小数,TP/SL 先填 0.00000,到期填当日 23:59:59。 [CODE] 里的 CreateButton / CreateEdit 若返回 false 会直接令函数 return false,意味着任一个控件资源申请失败,整面板就不建了——实盘前应在 OnInit 里检查返回值,避免半截 UI 挂上去。外汇与贵金属杠杆高,挂单参数填错可能瞬时成交并放大回撤,先在策略测试器里用可视化面板验证坐标无重叠再上真仓。
class="type">class="kw">string exp_default = TimeToString(now, TIME_DATE) + " class="num">23:class="num">59:class="num">59"; class=class="str">"cmt">// --- Buy Pending Orders --- class=class="str">"cmt">// Buy Limit Order row: if(!CreateButton(m_buyLimitButton, "Buy Limit", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrBlue)) class="kw">return class="kw">false; class="type">int pendingX = curX + BUTTON_WIDTH + GAP; if(!CreateEdit(m_buyLimitPriceEdit, "BuyLimitPrice", pendingX, curY, EDIT_WIDTH, EDIT_HEIGHT)) class="kw">return class="kw">false; class="type">class="kw">string askStr = DoubleToString(SymbolInfoDouble(Symbol(), SYMBOL_ASK), class="num">5); m_buyLimitPriceEdit.Text(askStr); class="type">int pending2X = pendingX + EDIT_WIDTH + GAP; if(!CreateEdit(m_buyLimitTPEdit, "BuyLimitTP", pending2X, curY, EDIT_WIDTH, EDIT_HEIGHT)) class="kw">return class="kw">false; m_buyLimitTPEdit.Text("class="num">0.00000"); class="type">int pending3X = pending2X + EDIT_WIDTH + GAP; if(!CreateEdit(m_buyLimitSLEdit, "BuyLimitSL", pending3X, curY, EDIT_WIDTH, EDIT_HEIGHT)) class="kw">return class="kw">false; m_buyLimitSLEdit.Text("class="num">0.00000"); class="type">int pending4X = pending3X + EDIT_WIDTH + GAP; class=class="str">"cmt">// Double width for expiration input if(!CreateEdit(m_buyLimitExpEdit, "BuyLimitExp", pending4X, curY, class="num">2*EDIT_WIDTH, EDIT_HEIGHT)) class="kw">return class="kw">false; m_buyLimitExpEdit.Text(exp_default); curY += BUTTON_HEIGHT + GAP; class=class="str">"cmt">// Buy Stop Order row: if(!CreateButton(m_buyStopButton, "Buy Stop", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrBlue)) class="kw">return class="kw">false; pendingX = curX + BUTTON_WIDTH + GAP; if(!CreateEdit(m_buyStopPriceEdit, "BuyStopPrice", pendingX, curY, EDIT_WIDTH, EDIT_HEIGHT)) class="kw">return class="kw">false; m_buyStopPriceEdit.Text(askStr); pending2X = pendingX + EDIT_WIDTH + GAP; if(!CreateEdit(m_buyStopTPEdit, "BuyStopTP", pending2X, curY, EDIT_WIDTH, EDIT_HEIGHT)) class="kw">return class="kw">false; m_buyStopTPEdit.Text("class="num">0.00000"); pending3X = pending2X + EDIT_WIDTH + GAP; if(!CreateEdit(m_buyStopSLEdit, "BuyStopSL", pending3X, curY, EDIT_WIDTH, EDIT_HEIGHT)) class="kw">return class="kw">false; m_buyStopSLEdit.Text("class="num">0.00000"); pending4X = pending3X + EDIT_WIDTH + GAP; if(!CreateEdit(m_buyStopExpEdit, "BuyStopExp", pending4X, curY, class="num">2*EDIT_WIDTH, EDIT_HEIGHT)) class="kw">return class="kw">false; m_buyStopExpEdit.Text(exp_default); curY += BUTTON_HEIGHT + GAP; class=class="str">"cmt">// --- Sell Pending Orders --- class=class="str">"cmt">// Sell Limit Order row: if(!CreateButton(m_sellLimitButton, "Sell Limit", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrRed)) class="kw">return class="kw">false; pendingX = curX + BUTTON_WIDTH + GAP; if(!CreateEdit(m_sellLimitPriceEdit, "SellLimitPrice", pendingX, curY, EDIT_WIDTH, EDIT_HEIGHT)) class="kw">return class="kw">false; class="type">class="kw">string bidStr = DoubleToString(SymbolInfoDouble(Symbol(), SYMBOL_BID), class="num">5); m_sellLimitPriceEdit.Text(bidStr); pending2X = pendingX + EDIT_WIDTH + GAP; if(!CreateEdit(m_sellLimitTPEdit, "SellLimitTP", pending2X, curY, EDIT_WIDTH, EDIT_HEIGHT))
挂单面板与批量平仓按钮的布局落点
这段逻辑紧接着 Sell Limit 行之后,把 Sell Stop 挂单的输入控件也按同样的横向间距排好。每个编辑框创建失败就直接 return false,保证面板不完整时不继续往下画。 Sell Stop 行的 price 编辑框默认填入 bidStr(即当前卖价字符串),而 TP、SL 编辑框初始写死为 "0.00000",有效期编辑框宽度是普通编辑框的两倍(2*EDIT_WIDTH),默认文本取 exp_default。 随后进入“All Order Operations”区段,先放一个标签,再在两行内铺完 9 个操作按钮:Close All / Close Profit 用红绿配色,Delete Limits 和 Delete Stops 放在 deleteX 位置(curX + 2*BUTTON_WIDTH + 2*GAP),Delete Stops 按钮宽度比 Delete Limits 多 10 像素。 Reset 按钮高度设为 2*BUTTON_HEIGHT 且纵向占两行空间,其 X 坐标用 deleteX + 2*BUTTON_WIDTH - 3*curX 算出来,这种写法在 curX 非 0 时容易偏出预期,建议开 MT5 把 curX 打印出来核对实际落点。外汇与贵金属杠杆交易风险高,面板逻辑错误可能误触平仓。
class="kw">return class="kw">false; m_sellLimitTPEdit.Text("class="num">0.00000"); pending3X = pending2X + EDIT_WIDTH + GAP; if(!CreateEdit(m_sellLimitSLEdit, "SellLimitSL", pending3X, curY, EDIT_WIDTH, EDIT_HEIGHT)) class="kw">return class="kw">false; m_sellLimitSLEdit.Text("class="num">0.00000"); pending4X = pending3X + EDIT_WIDTH + GAP; if(!CreateEdit(m_sellLimitExpEdit, "SellLimitExp", pending4X, curY, class="num">2*EDIT_WIDTH, EDIT_HEIGHT)) class="kw">return class="kw">false; m_sellLimitExpEdit.Text(exp_default); curY += BUTTON_HEIGHT + GAP; class=class="str">"cmt">// Sell Stop Order row: if(!CreateButton(m_sellStopButton, "Sell Stop", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrRed)) class="kw">return class="kw">false; pendingX = curX + BUTTON_WIDTH + GAP; if(!CreateEdit(m_sellStopPriceEdit, "SellStopPrice", pendingX, curY, EDIT_WIDTH, EDIT_HEIGHT)) class="kw">return class="kw">false; m_sellStopPriceEdit.Text(bidStr); pending2X = pendingX + EDIT_WIDTH + GAP; if(!CreateEdit(m_sellStopTPEdit, "SellStopTP", pending2X, curY, EDIT_WIDTH, EDIT_HEIGHT)) class="kw">return class="kw">false; m_sellStopTPEdit.Text("class="num">0.00000"); pending3X = pending2X + EDIT_WIDTH + GAP; if(!CreateEdit(m_sellStopSLEdit, "SellStopSL", pending3X, curY, EDIT_WIDTH, EDIT_HEIGHT)) class="kw">return class="kw">false; m_sellStopSLEdit.Text("class="num">0.00000"); pending4X = pending3X + EDIT_WIDTH + GAP; if(!CreateEdit(m_sellStopExpEdit, "SellStopExp", pending4X, curY, class="num">2*EDIT_WIDTH, EDIT_HEIGHT)) class="kw">return class="kw">false; m_sellStopExpEdit.Text(exp_default); curY += BUTTON_HEIGHT + GAP; class=class="str">"cmt">// Section class="num">3: All Order Operations if(!CreateLabelEx(m_secAllOpsLabel, curX, curY, DEFAULT_LABEL_HEIGHT, "SecAllOps", "All Order Operations:", clrBlack)) class="kw">return class="kw">false; curY += DEFAULT_LABEL_HEIGHT + GAP; class="type">int deleteX = curX + class="num">2*BUTTON_WIDTH + class="num">2*GAP; class="type">int deleteY = curY ; CreateButton(m_closeAllButton, "Close All", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrRed); CreateButton(m_closeProfitButton, "Close Profit", curX + BUTTON_WIDTH + GAP, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrGreen); CreateButton(m_deleteLimitOrdersButton, "Delete Limits", deleteX, curY, DELETE_BUTTON_WIDTH, BUTTON_HEIGHT, clrRed); curY += BUTTON_HEIGHT + GAP; CreateButton(m_closeLossButton, "Close Loss", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrRed); CreateButton(m_closeBuyButton, "Close Buy", curX + BUTTON_WIDTH + GAP, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrGreen); CreateButton(m_deleteStopOrdersButton, "Delete Stops", deleteX, curY , DELETE_BUTTON_WIDTH+class="num">10, BUTTON_HEIGHT, clrRed); CreateButton(m_resetButton, "Reset", deleteX+ class="num">2*BUTTON_WIDTH-class="num">3*curX, curY, DELETE_BUTTON_WIDTH, class="num">2*BUTTON_HEIGHT, clrRed); curY += BUTTON_HEIGHT + GAP; CreateButton(m_closeSellButton, "Close Sell", curX, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrBlue); CreateButton(m_deleteAllOrdersButton, "Delete All", curX + BUTTON_WIDTH + GAP, curY, BUTTON_WIDTH, BUTTON_HEIGHT, clrRed); CreateButton(m_deleteStopLimitOrdersButton, "Delete StopLimits", deleteX, curY , DELETE_BUTTON_WIDTH+class="num">10, BUTTON_HEIGHT, clrDarkRed); curY += BUTTON_HEIGHT + GAP; class="kw">return true;
◍ 面板控件封装与点击事件分发
在 MT5 自定义面板里,把文字标签、按钮、输入框的创建逻辑收进三个 Helper,能省掉主流程里大量重复的坐标与资源判断。下面这组函数都返回 bool,任意一步 Create 或 Add 失败就直接 false,调用方不用再逐个查句柄。 CreateLabelEx 先拼出全局唯一名(m_name + label_name),再按 (x, y) 到 (x, y+height) 的矩形建标签;文本和颜色在最后赋值,意味着标签底色默认走控件基类,你想改必须显式调 Color()。 按钮和编辑框的 Helper 给了默认宽高参(BUTTON_WIDTH / EDIT_WIDTH 等),实际写死在宏里,改尺寸要去头部调宏而不是传参覆盖。CreateButton 里 Text(name) 直接把按钮名当显示文字,若想显示中文交易指令,得在调用前改 name 或单独setText。 OnEvent 只认 CHARTEVENT_OBJECT_CLICK:用 sparam 比对六个下单键加两个平仓键的 Name(),命中即转发到对应的 OnClickXxx。外汇与贵金属波动剧烈,这类一键发单面板若没做二次确认,滑点可能吞掉你预设的止损空间,上线前务必在策略测试器里用历史 tick 跑一遍点击链路。
class="type">bool CreateLabelEx(CLabel &label, class="type">int x, class="type">int y, class="type">int height, class="type">class="kw">string label_name, class="type">class="kw">string text, class="type">color clr) { class="type">class="kw">string unique_name = m_name + label_name; if(!label.Create(m_chart, unique_name, m_subwin, x, y, x, y+height)) class="kw">return class="kw">false; if(!Add(label)) class="kw">return class="kw">false; if(!label.Text(text)) class="kw">return class="kw">false; label.Color(clr); class="kw">return true; } class="type">bool CreateButton(CButton &button, class="type">class="kw">string name, class="type">int x, class="type">int y, class="type">int w = BUTTON_WIDTH, class="type">int h = BUTTON_HEIGHT, class="type">color clr = clrWhite) { if(!button.Create(m_chart, name, m_subwin, x, y, x+w, y+h)) class="kw">return class="kw">false; button.Text(name); button.Color(clr); if(!Add(button)) class="kw">return class="kw">false; class="kw">return true; } class="type">bool CreateEdit(CEdit &edit, class="type">class="kw">string name, class="type">int x, class="type">int y, class="type">int w = EDIT_WIDTH, class="type">int h = EDIT_HEIGHT) { if(!edit.Create(m_chart, name, m_subwin, x, y, x+w, y+h)) class="kw">return class="kw">false; if(!Add(edit)) class="kw">return class="kw">false; class="kw">return true; } class="type">bool CTradeManagementPanel::OnEvent(const class="type">int id, const class="type">long &lparam, const class="type">class="kw">double &dparam, const class="type">class="kw">string &sparam) { if(id == CHARTEVENT_OBJECT_CLICK) { if(sparam == m_buyButton.Name()) OnClickBuy(); else if(sparam == m_sellButton.Name()) OnClickSell(); else if(sparam == m_buyLimitButton.Name()) OnClickBuyLimit(); else if(sparam == m_buyStopButton.Name()) OnClickBuyStop(); else if(sparam == m_sellLimitButton.Name()) OnClickSellLimit(); else if(sparam == m_sellStopButton.Name()) OnClickSellStop(); else if(sparam == m_closeAllButton.Name()) OnClickCloseAll(); else if(sparam == m_closeProfitButton.Name()) OnClickCloseProfit();
「按钮路由与买卖单参数校验的逻辑落点」
面板上的每一个功能按钮,在 OnClick 事件里都靠 sparam 与按钮 Name() 比对来分流。下面这段链式 else if 把平仓、删单、重置等动作一一映射到各自处理函数,最后 return true 表示事件已消化。 else if(sparam == m_closeLossButton.Name()) OnClickCloseLoss(); else if(sparam == m_closeBuyButton.Name()) OnClickCloseBuy(); else if(sparam == m_closeSellButton.Name()) OnClickCloseSell(); else if(sparam == m_deleteAllOrdersButton.Name()) OnClickDeleteAllOrders(); else if(sparam == m_deleteLimitOrdersButton.Name()) OnClickDeleteLimitOrders(); else if(sparam == m_deleteStopOrdersButton.Name()) OnClickDeleteStopOrders(); else if(sparam == m_deleteStopLimitOrdersButton.Name()) OnClickDeleteStopLimitOrders(); else if(sparam == m_resetButton.Name()) OnClickReset(); // Handle reset button click 买单校验 ValidateBuyParameters 接收 volume、tp、sl、ask 四个量:volume<=0 直接标红 Invalid volume;tp!=0 且 tp<=ask 判为 Invalid TP;sl!=0 且 sl>=ask 判为 Invalid SL。任一不满足 valid 置 false,标签变红。 卖单校验逻辑镜像对称:tp!=0 且 tp>=bid 才非法,sl!=0 且 sl<=bid 才非法。外汇与贵金属杠杆高,滑点可能让 ask/bid 瞬间越界,这类前置拦截能减少错单概率,但无法消除执行风险。 开 MT5 把这段塞进你的面板类,故意填 sl=ask 的买单,应该看到 SL 标签立刻标红且 valid 返回 false。
else if(sparam == m_closeLossButton.Name()) OnClickCloseLoss(); else if(sparam == m_closeBuyButton.Name()) OnClickCloseBuy(); else if(sparam == m_closeSellButton.Name()) OnClickCloseSell(); else if(sparam == m_deleteAllOrdersButton.Name()) OnClickDeleteAllOrders(); else if(sparam == m_deleteLimitOrdersButton.Name()) OnClickDeleteLimitOrders(); else if(sparam == m_deleteStopOrdersButton.Name()) OnClickDeleteStopOrders(); else if(sparam == m_deleteStopLimitOrdersButton.Name()) OnClickDeleteStopLimitOrders(); else if(sparam == m_resetButton.Name()) OnClickReset(); class=class="str">"cmt">// Handle reset button click } class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Validate Buy order parameters(Market orders) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CTradeManagementPanel::ValidateBuyParameters(class="type">class="kw">double volume, class="type">class="kw">double tp, class="type">class="kw">double sl, class="type">class="kw">double ask) { class="type">bool valid = true; m_tpErrorLabel.Text(""); m_slErrorLabel.Text(""); if(volume <= class="num">0) { m_tpErrorLabel.Text("Invalid volume"); m_tpErrorLabel.Color(clrRed); valid = class="kw">false; } if(tp != class="num">0 && tp <= ask) { m_tpErrorLabel.Text("Invalid TP"); m_tpErrorLabel.Color(clrRed); valid = class="kw">false; } if(sl != class="num">0 && sl >= ask) { m_slErrorLabel.Text("Invalid SL"); m_slErrorLabel.Color(clrRed); valid = class="kw">false; } class="kw">return valid; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Validate Sell order parameters(Market orders) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CTradeManagementPanel::ValidateSellParameters(class="type">class="kw">double volume, class="type">class="kw">double tp, class="type">class="kw">double sl, class="type">class="kw">double bid) { class="type">bool valid = true; m_tpErrorLabel.Text(""); m_slErrorLabel.Text(""); if(volume <= class="num">0) { m_tpErrorLabel.Text("Invalid volume"); m_tpErrorLabel.Color(clrRed); valid = class="kw">false; } if(tp != class="num">0 && tp >= bid) { m_tpErrorLabel.Text("Invalid TP"); m_tpErrorLabel.Color(clrRed); valid = class="kw">false; } if(sl != class="num">0 && sl <= bid) { m_slErrorLabel.Text("Invalid SL"); m_slErrorLabel.Color(clrRed); valid = class="kw">false; } class="kw">return valid; } class=class="str">"cmt">//+------------------------------------------------------------------+
挂单面板的到期解析与四类触发函数
面板把限价/止损挂单的录入逻辑收敛成四个点击处理函数,BuyLimit、BuyStop、SellLimit、SellStop 各占一个,结构完全对称。每个函数先从对应编辑框取价格、TP、SL、手数和到期字符串,再统一交给 ParseExpiration 判定时间类型。 ParseExpiration 只做一件事:如果输入字符串转大写后等于 GTC,就把 type_time 设为 ORDER_TIME_GTC 且 expiration 填 0;否则视为指定时间,调用 StringToTime 把字符串转成 datetime 并置为 ORDER_TIME_SPECIFIED。这样用户在框里填 GTC 或 2024.08.01 12:00 都能被正确处理。 外汇与贵金属挂单受点差和跳空影响,指定过期时间的挂单可能在非活跃时段未被触发而直接作废,属于正常概率风险。建议先在策略测试器用历史数据跑一遍四个函数,确认 GTC 与具体时间两种分支都不会抛 INVALID_PARAMETER。
class="type">void ParseExpiration(class="type">class="kw">string sExp, ENUM_ORDER_TYPE_TIME &type_time, class="type">class="kw">datetime &expiration) { if(StringCompare(StringToUpper(sExp), "GTC") == class="num">0) { type_time = ORDER_TIME_GTC; expiration = class="num">0; } else { type_time = ORDER_TIME_SPECIFIED; expiration = StringToTime(sExp); } } class="type">void CTradeManagementPanel::OnClickBuyLimit() { class="type">class="kw">double price = StringToDouble(m_buyLimitPriceEdit.Text()); class="type">class="kw">double tp = StringToDouble(m_buyLimitTPEdit.Text()); class="type">class="kw">double sl = StringToDouble(m_buyLimitSLEdit.Text()); class="type">class="kw">double volume = StringToDouble(m_volumeEdit.Text()); class="type">class="kw">string sExp = m_buyLimitExpEdit.Text(); ENUM_ORDER_TYPE_TIME type_time; class="type">class="kw">datetime expiration; ParseExpiration(sExp, type_time, expiration); m_trade.BuyLimit(volume, price, Symbol(), sl, tp, type_time, expiration, ""); } class="type">void CTradeManagementPanel::OnClickBuyStop() { class="type">class="kw">double price = StringToDouble(m_buyStopPriceEdit.Text()); class="type">class="kw">double tp = StringToDouble(m_buyStopTPEdit.Text()); class="type">class="kw">double sl = StringToDouble(m_buyStopSLEdit.Text()); class="type">class="kw">double volume = StringToDouble(m_volumeEdit.Text()); class="type">class="kw">string sExp = m_buyStopExpEdit.Text(); ENUM_ORDER_TYPE_TIME type_time; class="type">class="kw">datetime expiration; ParseExpiration(sExp, type_time, expiration); m_trade.BuyStop(volume, price, Symbol(), sl, tp, type_time, expiration, ""); } class="type">void CTradeManagementPanel::OnClickSellLimit() { class="type">class="kw">double price = StringToDouble(m_sellLimitPriceEdit.Text()); class="type">class="kw">double tp = StringToDouble(m_sellLimitTPEdit.Text()); class="type">class="kw">double sl = StringToDouble(m_sellLimitSLEdit.Text()); class="type">class="kw">double volume = StringToDouble(m_volumeEdit.Text()); class="type">class="kw">string sExp = m_sellLimitExpEdit.Text(); ENUM_ORDER_TYPE_TIME type_time; class="type">class="kw">datetime expiration; ParseExpiration(sExp, type_time, expiration); m_trade.SellLimit(volume, price, Symbol(), sl, tp, type_time, expiration, ""); } class="type">void CTradeManagementPanel::OnClickSellStop() { class="type">class="kw">double price = StringToDouble(m_sellStopPriceEdit.Text()); class="type">class="kw">double tp = StringToDouble(m_sellStopTPEdit.Text()); class="type">class="kw">double sl = StringToDouble(m_sellStopSLEdit.Text()); class="type">class="kw">double volume = StringToDouble(m_volumeEdit.Text()); class="type">class="kw">string sExp = m_sellStopExpEdit.Text(); ENUM_ORDER_TYPE_TIME type_time; class="type">class="kw">datetime expiration; ParseExpiration(sExp, type_time, expiration); m_trade.SellStop(volume, price, Symbol(), sl, tp, type_time, expiration, ""); }