从头开始开发智能交易系统(第 19 部分):新订单系统 (II)·进阶篇
◍ 把持仓参数塞进内联读取器
这段类实现把账户级风控与单笔挂单界限拆成了两个私有结构:m_BaseFinance 存杠杆、日内开关、止盈止损点数,m_Limits 存具体双向价格与买卖方向。四个 inline getter 直接返回 m_BaseFinance 的成员,调用成本趋近于零,适合在 EA 的 OnTick 里高频取杠杆与止损参数。 外汇与贵金属杠杆可调到 1:500 以上,高杠杆下 m_BaseFinance.Leverange 若未做上限校验,可能让单笔风险暴露超出预期,属典型高风险操作。
| MountName 用 StringFormat 拼出形如「前缀 | 类型 | ticket」的对象名,分隔符走 def_SeparatorInfo(通常为 ' | ' 字符)。CreateIndicatorTrade 据此名创建水平线:IT_PRICE 给蓝、IT_STOP 给砖红、其余给墨绿,颜色映射写死在三目里,改类别需同步改这一行。 |
|---|
开 MT5 把这段贴进自定义类,用 ObjectCreate 建一条 OBJ_HLINE 验证颜色分支;若你的指示器枚举不止三种,优先把颜色判断抽成 switch 而不是嵌套三目。
class="kw">inline class="type">int GetBaseFinanceLeveRange(class="type">void) const { class="kw">return m_BaseFinance.Leverange; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">inline class="type">int GetBaseFinanceIsDayTrade(class="type">void) const { class="kw">return m_BaseFinance.IsDayTrade; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">inline class="type">int GetBaseFinanceTakeProfit(class="type">void) const { class="kw">return m_BaseFinance.FinanceTake; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">inline class="type">int GetBaseFinanceStopLoss(class="type">void) const { class="kw">return m_BaseFinance.FinanceStop; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">private : class="type">class="kw">string m_SelectObj; class="kw">struct st00 { class="type">class="kw">double TakeProfit, StopLoss; class="type">bool IsBuy; }m_Limits; class="kw">struct st01 { class="type">int FinanceTake, FinanceStop, Leverange; class="type">bool IsDayTrade; }m_BaseFinance; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string MountName(class="type">ulong ticket, eIndicatorTrade it) { class="kw">return StringFormat("%s%c%c%c%d", def_NameObjectsTrade, def_SeparatorInfo, (class="type">char)it, def_SeparatorInfo, ticket); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">inline class="type">class="kw">string CreateIndicatorTrade(class="type">ulong ticket, eIndicatorTrade it, class="type">bool select) { class="type">class="kw">string sz0 = MountName(ticket, it); ObjectCreate(Terminal.Get_ID(), sz0, OBJ_HLINE, class="num">0, class="num">0, class="num">0); ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_COLOR, (it == IT_PRICE ? clrBlue : (it == IT_STOP ? clrFireBrick : clrForestGreen)));
「持仓线绘制时的对象属性与空价拦截」
在 MT5 里用对象画持仓参考线,属性设置直接决定它会不会干扰你看图。上面那段把线宽锁死为 1、线型用 STYLE_DASHDOT 点划线,OBJPROP_BACK 设 true 把它塞到 K 线后面,避免遮挡实体;同时 OBJPROP_SELECTABLE 由外部 select 参数控制,TP 线传了 true 可被鼠标点选,开仓价线传 false 不可选。 tooltip 拼的是 ticket 号加指标类型枚举截取的子串(从 EnumToString 结果第 3 字符起取 10 位),悬停就能看到是哪单哪类线,调试时不用翻日志。 价格小于等于 0 的分支是硬拦截:直接调 RemoveIndicatorTrade 清掉旧对象并返回 NULL,防止把 0 或负价写进图表对象导致报错。外汇和贵金属杠杆高,这类防御写法能少踩坑,但任何画线都只是辅助,信号失效概率始终存在。 路由类里 UpdatePosition 遍历持仓时,对当前符号的每单都建开仓价线与 TP 线,TP 线显式可选取——你改完这段把 EA 挂上 EURUSD 回测,应能看见点划线标出每单 open 与 take 位置。
ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_WIDTH, class="num">1); ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_STYLE, STYLE_DASHDOT); ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_SELECTABLE, select); ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_SELECTED, false); ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_BACK, true); ObjectSetString(Terminal.Get_ID(), sz0, OBJPROP_TOOLTIP, (class="type">class="kw">string)ticket + " "+StringSubstr(EnumToString(it), class="num">3, class="num">10)); class="kw">return sz0; } class="kw">inline class="type">class="kw">string CreateIndicatorTrade(class="type">ulong ticket, class="type">class="kw">double price, eIndicatorTrade it, class="type">bool select) { if (price <= class="num">0) { RemoveIndicatorTrade(ticket, it); class="kw">return NULL; } class="type">class="kw">string sz0 = CreateIndicatorTrade(ticket, it, select); ObjectMove(Terminal.Get_ID(), sz0, class="num">0, class="num">0, price); class="kw">return sz0; } class C_Router : class="kw">public C_ObjectsTrade { class=class="str">"cmt">// ... Internal class code .... class="type">void UpdatePosition(class="type">int iAdjust = -class="num">1) { class=class="str">"cmt">// ... Internal function code ... for(class="type">int i0 = p; i0 >= class="num">0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol()) { ul = PositionGetInteger(POSITION_TICKET); m_bContainsPosition = true; CreateIndicatorTrade(ul, PositionGetDouble(POSITION_PRICE_OPEN), IT_PRICE, false); CreateIndicatorTrade(ul, take = PositionGetDouble(POSITION_TP), IT_TAKE, true);
持仓与指标线的绑定写法
在 MT5 的 EA 架构里,把当前持仓和图表上的指标线关联起来,常见做法是调用 CreateIndicatorTrade 并传入持仓 ticket 与对应的价格字段。下面这段截取自一个订单路由类的实现,能直接看到三种绑定场景。 开仓时若已有止损,会用 PositionGetDouble(POSITION_SL) 取回止损价并绑定为 IT_STOP 类型:CreateIndicatorTrade(ul, stop = PositionGetDouble(POSITION_SL), IT_STOP, true); 最后一个 true 表示强制刷新显示。 在 C_OrderView 的 InitBaseFinance 中,对默认指标票 def_IndicatorTicket0 分别建立了价格、止盈、止损三条线,全部用 false 标记为非强制:CreateIndicatorTrade(def_IndicatorTicket0, IT_PRICE, false) 等。这样初始化后图表上就有基础财务参考线。 UpdatePosition 里遍历持仓,若符号匹配本终端品种,就取 ticket 并绑定开盘价线:CreateIndicatorTrade(ul, PositionGetDouble(POSITION_PRICE_OPEN), IT_PRICE, false)。外汇与贵金属波动大、杠杆高,这类绑定仅作可视化辅助,不改变订单本身风险。 复制下面代码到 MT5 策略测试器里的自定义类,能验证指标线是否随持仓更新;若 def_IndicatorTicket0 未预先创建,运行会返回无效句柄。
CreateIndicatorTrade(ul, stop = PositionGetDouble(POSITION_SL), IT_STOP, true); class C_OrderView : class="kw">public C_Router { class="kw">private: class="kw">public: class="type">void InitBaseFinance(class="type">int nContracts, class="type">int FinanceTake, class="type">int FinanceStop, class="type">bool b1) { SetBaseFinance(nContracts, FinanceTake, FinanceStop, b1); CreateIndicatorTrade(def_IndicatorTicket0, IT_PRICE, false); CreateIndicatorTrade(def_IndicatorTicket0, IT_TAKE, false); CreateIndicatorTrade(def_IndicatorTicket0, IT_STOP, false); } }; class C_Router : class="kw">public C_ObjectsTrade { class="type">void UpdatePosition(class="type">int iAdjust = -class="num">1) { for(class="type">int i0 = p; i0 >= class="num">0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol()) { ul = PositionGetInteger(POSITION_TICKET); m_bContainsPosition = true; CreateIndicatorTrade(ul, PositionGetDouble(POSITION_PRICE_OPEN), IT_PRICE, false); } } }; class="type">bool GetInfosOrder(const class="type">class="kw">string &sparam, class="type">ulong &ticket, class="type">class="kw">double &price, eIndicatorTrade &it) { class="type">class="kw">string szRet[]; class="type">char szInfo[];
◍ 对象清理时别误删基准挂单
上面这段函数负责把图表上的交易指示对象批量移除,但留了一个口子:当 bFull 为 false 时,会跳过 ticket 等于 def_IndicatorTicket0 的那一个。这个 0 号 ticket 通常是策略用来锚定基准水平的对象,误删会导致后续价格行为判读失去参照。 循环从 ObjectsTotal 拿总数倒序遍历,用 StringSubstr 比对前缀 def_NameObjectsTrade 来识别自绘对象。注意先关 CHART_EVENT_OBJECT_DELETE 再删,否则每删一个都会触发一次事件回调,在外汇和贵金属这种高频波动品种上可能拖慢 MT5 主线程。 实际验证时,可在 RemoveAllsIndicatorTrade(false) 调用前手动画一个 0 号 ticket 对象,跑完函数后用 ObjectFind 查它是否还在。高风险提示:自动化对象管理若逻辑写错,可能在直播盘面瞬间清掉你的预警线。
if (StringSplit(sparam, def_SeparatorInfo, szRet) < class="num">2) class="kw">return false; if (szRet[class="num">0] != def_NameObjectsTrade) class="kw">return false; StringToCharArray(szRet[class="num">1], szInfo); it = (eIndicatorTrade)szInfo[class="num">0]; ticket = (class="type">ulong) StringToInteger(szRet[class="num">2]); price = ObjectGetDouble(Terminal.Get_ID(), sparam, OBJPROP_PRICE); class="kw">return true; } class="kw">inline class="type">void RemoveAllsIndicatorTrade(class="type">bool bFull) { class="type">class="kw">string sz0, szRet[]; class="type">int i0 = StringLen(def_NameObjectsTrade); ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false); for (class="type">int c0 = ObjectsTotal(Terminal.Get_ID(), -class="num">1, -class="num">1); c0 >= class="num">0; c0--) { sz0 = ObjectName(Terminal.Get_ID(), c0, -class="num">1, -class="num">1); if (StringSubstr(sz0, class="num">0, i0) == def_NameObjectsTrade) { if (!bFull) { StringSplit(sz0, def_SeparatorInfo, szRet); if (StringToInteger(szRet[class="num">2]) == def_IndicatorTicket0) class="kw">continue; } }else class="kw">continue; ObjectDelete(Terminal.Get_ID(), sz0); } ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true); } class="kw">inline class="type">void RemoveIndicatorTrade(class="type">ulong ticket, eIndicatorTrade it = IT_NULL) {