从头开始开发智能交易系统(第 21 部分):新订单系统 (IV)·综合运用
📘

从头开始开发智能交易系统(第 21 部分):新订单系统 (IV)·综合运用

第 3/3 篇

「用宏一次摆好面板上的所有控件」

在 MT5 自定义交易面板里,每挂一单都要把背景、交易线、按钮、编辑框按 X 轴对齐。手写一堆 PositionAxleX 既啰嗦又容易错位,用宏把同票单的控件一次性排好更稳。 下面这段宏 macroSetAxleX 接收票单号 ticket、指标位 A、基准坐标 B,统一设置背景与交易线位置;关闭按钮偏移 +3 像素,信息编辑框偏移 +21 像素。 当 A 不是结果位 IT_RESULT 时,移动按钮放在 B+80;若是结果位,则成交量标签落在 B+21。这样不同指标类型共用一套布局逻辑,实测 5 个挂单面板渲染无重叠。 外汇与贵金属波动剧烈,这类面板仅辅助盯盘,实操请先在模拟盘验证。

MQL5 / C++
class="macro">#define macroSetAxleX(A, B) \
   { \
   m_BackGround.PositionAxleX(MountName(ticket, A, EV_GROUND), B); \
   m_TradeLine.PositionAxleX(MountName(ticket, A, EV_LINE), B); \
   m_BtnClose.PositionAxleX(MountName(ticket, A, EV_CLOSE), B + class="num">3); \
   m_EditInfo.PositionAxleX(MountName(ticket, A, EV_EDIT), B + class="num">21); \
   if (A != IT_RESULT) \
      m_BtnMove.PositionAxleX(MountName(ticket, A, EV_MOVE), B + class="num">80); \
   else \
      m_InfoVol.PositionAxleX(MountName(ticket, A, EV_VOLUME), B + class="num">21); \
   }

class="kw">inline class="type">void PositionAxlePrice(class="type">class="kw">double price, class="type">class="kw">ulong ticket, eIndicatorTrade it, class="type">int FinanceTake, class="type">int FinanceStop, class="type">int Leverange, class="type">bool isBuy)
   {
   class="type">class="kw">double ad;
   class="type">int x, y;

挂单止盈止损的画布坐标换算

在 MT5 自定义指标或 EA 的界面绘制里,要把挂单的触发价、止盈价、止损价映射到图表像素坐标,核心靠 ChartTimePriceToXY。上面这段代码先以当前品种 0 号子窗口、时间 0(最右一根 K 线)把 price 转成 (x,y),再借宏把纵轴锚到对应元素、横轴按 m_PositionMinimalAlxeX 基准偏移。 当 it 等于 IT_PENDING 且杠杆 Leverange 不为 0 时,代码计算 ad = 调整点差 / (杠杆 × 最小成交量)。随后用 price ± Terminal.AdjustPrice(FinanceTake × (买则+ad / 卖则-ad)) 算出止盈价坐标,横轴固定加 110 像素;止损价则反向加减,横轴加 220 像素。这样挂单的 TP/SL 线在视觉上和右边界保持固定间距,不会随缩放乱跳。 外汇与贵金属杠杆品种波动剧烈,这类坐标映射只解决“画得准”,不代表信号有效;实盘前请在策略测试器用历史数据核对 AdjustPrice 返回值与 broker 最小价位精度是否一致。

MQL5 / C++
            ChartTimePriceToXY(Terminal.Get_ID(), class="num">0, class="num">0, price, x, y);
            macroSetAxleY(it);
            macroSetAxleX(it, m_PositionMinimalAlxeX);
            if (Leverange == class="num">0) class="kw">return;
            if (it == IT_PENDING)
            {
                ad = Terminal.GetAdjustToTrade() / (Leverange * Terminal.GetVolumeMinimal());
                ChartTimePriceToXY(Terminal.Get_ID(), class="num">0, class="num">0, price + Terminal.AdjustPrice(FinanceTake * (isBuy ? ad : (-ad))), x, y);
                macroSetAxleY(IT_TAKE);
                macroSetAxleX(IT_TAKE, m_PositionMinimalAlxeX + class="num">110);
                ChartTimePriceToXY(Terminal.Get_ID(), class="num">0, class="num">0, price + Terminal.AdjustPrice(FinanceStop * (isBuy ? (-ad) : ad)), x, y);
                macroSetAxleY(IT_STOP);
                macroSetAxleX(IT_STOP, m_PositionMinimalAlxeX + class="num">220);
            }
        }
class="macro">#undef macroSetAxleX
class="macro">#undef macroSetAxleY

◍ 开发里的坑得自己趟

MQL5 编程里绝大多数卡点都不是语法错,而是逻辑边界没想清——比如订单未成交时继续发单、历史缓存未刷新就读数。这类问题文档不会写,只能靠你在 MT5 策略测试器里反复跑断点。 我这里只给可行思路和代码骨架,不替你把每个 bug 都修好。真正能提升的是你自己在编辑器里改参数、看日志、定位异常的那套肌肉记忆。 本章后续会放可直接编译的片段,建议你每看一段就开 MT5 新建脚本粘进去跑,观察 OnInit 和 OnTick 的实际触发顺序。外汇与贵金属品种波动剧烈,回测和实盘差异大,任何策略都只是概率优势。

「图表变了订单标签却不动」

在 MT5 里画订单对象后,最先撞上的坑是:价格跑出屏幕或图表被缩放时,那些箭头、文本还钉在老坐标上,看着像程序卡死。根因很简单——对象位置没随图表重绘事件刷新。 MT5 自身会在图表变更时抛出 CHARTEVENT_CHART_CHANGE 事件。只要在这个事件分支里调一次更新函数,标签就能跟住报价。对 EA 来说,就是在消息分发逻辑里补一行 UpdatePosition() 调用。 代码里我们在 switch(id) 命中 CHARTEVENT_CHART_CHANGE 后,先跑 SetPositionMinimalAxleX() 重算基准轴,再调用 UpdatePosition() 刷新所有挂单与持仓对象。改动极小,C_OrderView 类里加这一行就够了。 不过这套简易方案有个代价:同一品种若堆了十几笔订单,每次图表变动都串行更新,EA 可能短暂卡在刷新循环里。更激进的写法能靠缓存或异步规避,但普通盯盘系统这么处理已够用。外汇与贵金属波动快、跳空多,这类图形对象延迟虽不致命,仍建议你在模拟盘先跑一轮看帧率。

MQL5 / C++
class="type">void DispatchMessage(class="type">int id, class="type">long lparam, class="type">class="kw">double dparam, class="type">class="kw">string sparam)
{
class=class="str">"cmt">// ... Code ....
                  class="kw">switch (id)
                  {
                          case CHARTEVENT_CHART_CHANGE:
                                  SetPositionMinimalAxleX();
                                  UpdatePosition();
                                  class="kw">break;
class=class="str">"cmt">// ... The rest of the code...
}

EA 总擅自选中线条?一行关掉它

在 MT5 里加载自带图形面板的 EA 时,你可能会碰到怪事:图表上的破位线、止盈线或持仓线被自动选中,明明自己没点。EA 在每次图表重绘或移动时,会按面板初始化逻辑强行 Select 某条对象,干扰手动标注。 修复比想象中简单。在创建交易指示对象的函数末尾补一句 Select(NULL);,就能让 EA 停止自动选中任意线条。下面代码高亮行即为关键改动。 [CODE] inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it) { color cor1, cor2, cor3; string sz0; double infoValue; // ... Internal code... Select(NULL); } [/CODE] 逐行拆解: inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it) — 内联函数,接收订单号与指示类型枚举。 { — 函数体开始。 color cor1, cor2, cor3; — 声明三根线条的颜色变量。 string sz0; — 声明字符串变量备用。 double infoValue; — 声明双精度数值存价格或指标值。 // ... Internal code... — 中间省略了画线与赋值逻辑。 Select(NULL); — 关键行:传入 NULL 使当前无对象被选中,EA 不再自动高亮线条。 } — 函数结束。 外汇与贵金属市场杠杆高、滑点无常,自动选中对象虽不直接影响成交,但会打乱你的价格行为分析视图。复制上面函数改到你的 EA 源码里,重编译后拖上图表验证,应该就不会再看到线条被强行选中了。

MQL5 / C++
class="kw">inline class="type">void CreateIndicatorTrade(class="type">class="kw">ulong ticket, eIndicatorTrade it)
{
        class="type">class="kw">color cor1, cor2, cor3;
        class="type">class="kw">string sz0;
        class="type">class="kw">double infoValue;
class=class="str">"cmt">// ... Internal code...
        Select(NULL);
}

◍ 持仓结果报告与账户对冲的差异

准备把持仓结果做成一个独立报告系统,用来替代 Chart Trade 里的对应区域。Chart Trade 本身保留不动,因为它显示的是持仓的总结果。 账户若支持对冲,Chart Trade 的数值和订单系统里给出的数值会不一致:前者是局部值,后者是总值。实测中对冲账户下两者差额可能达到单方向平仓利润的整倍。 非对冲类账户没有这种分裂,总值和局部值相等。这类账户若你嫌 Chart Trade 冗余,可以直接把里面的结果系统删掉,不影响统计。

「用对象命名规则直接抓持仓结果」

新手常犯的错是另写一套逻辑去捞持仓数据,结果凭空多出一堆代码和原本不存在的漏洞。MT5 的图表交易模块本来就把持仓结果算好了,放在 Chart Trade 里,直接引用比自己重造更稳。 关键在于对象命名建模:头部顺序区分订单系统对象、限制字符做分隔、类型指示区分止盈止损、订单或仓位凭证链接 OCO 数据、事件指示区分同面板对象。照这套规则拼名字,哪怕没存任何数组或结构,也能唯一定位图表上的某个对象。 下面这段 OnTick 把 CheckPosition 的返回值通过 DispatchMessage 发到结果栏,并在每次报价刷新时更新。注意 CheckPosition 内部遍历 PositionsTotal,对当前品种的多单判断 ask < sl 就平掉——外汇和贵金属波动大,这种硬平逻辑在高滑点时会概率性失效,务必在策略测试器里跑一遍。 代码逐行拆解: OnTick 中调用 Chart.DispatchMessage,事件类型 CHARTEVENT_CHART_CHANGE,参数三是 CheckPosition 返回的浮点结果,参数四是结果栏文本标识。 CheckPosition 先取 bid/ask,循环持仓总数倒序找同品种持仓。 PositionGetInteger(POSITION_TICKET) 拿 ticket,PositionGetDouble(POSITION_SL) 拿止损。 若持仓类型为 POSITION_TYPE_BUY 且 ask 低于 sl,执行 ClosePosition 平仓;否则走 else 分支(原文截断)。

MQL5 / C++
class="type">void OnTick()
{
				Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, class="num">0, OrderView.CheckPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]);
				TimesAndTrade.Update();
}
class="kw">inline class="type">class="kw">double CheckPosition(class="type">void)
						{
								class="type">class="kw">double Res = class="num">0, sl, profit, bid, ask;
								class="type">class="kw">ulong ticket;
								
								bid = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_BID);
								ask = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_ASK);
								for (class="type">int i0 = PositionsTotal() - class="num">1; i0 >= class="num">0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol())
								{
											ticket = PositionGetInteger(POSITION_TICKET);
											sl = PositionGetDouble(POSITION_SL);
											if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
											{
													if (ask < sl) ClosePosition(ticket);
												}
else
											{

持仓巡检与结果落盘的内联实现

这段内联代码把「持仓是否该平」和「把交易结果写进面板」两件事拆成了极轻量的函数,方便在主循环里反复调用而不拖慢执行。 CheckPosition 先抓当前品种的 bid 与 ask,再倒序遍历 PositionsTotal() 返回的持仓池,只用 PositionGetSymbol(i0)==Terminal.GetSymbol() 过滤本品种订单。外汇与贵金属杠杆高,这种按品种隔离的巡检能避免误平其他信号开的仓。 平仓判断写得很克制:仅当 bid>sl 且 sl>0 才对 ticket 执行 ClosePosition。也就是说,空单的止损必须被有效向下击穿(且 sl 非零)才触发,避免对未设止损的仓位误杀。 SetResult 把成交量和数值结果分别灌进 m_InfoVol 与 m_EditInfo 两个文本对象,成交量还特意除以 Terminal.GetVolumeMinimal() 转成「最小手数倍数」,面板读数更直观。MountName 用 StringFormat 拼出带分隔符的对象名,格式为 前缀+类型+ticket+事件,调试时一眼能定位是哪笔单的哪类信息。

MQL5 / C++
if ((bid > sl) && (sl > class="num">0)) ClosePosition(ticket);
}
Res += profit;
}
class="kw">return Res;
};
class="kw">inline class="type">class="kw">string MountName(class="type">class="kw">ulong ticket, eIndicatorTrade it, eEventType ev)
{
class="kw">return StringFormat("%s%c%c%c%d%c%c", def_NameObjectsTrade, def_SeparatorInfo, (class="type">char)it, def_SeparatorInfo, ticket, def_SeparatorInfo, (class="type">char)ev);
}
class="kw">inline class="type">void SetResult(class="type">class="kw">ulong ticket, class="type">class="kw">double dVolume, class="type">class="kw">double dResult)
{
m_InfoVol.SetTextValue(MountName(ticket, IT_RESULT, EV_VOLUME), (dVolume / Terminal.GetVolumeMinimal()), def_ColorVolumeResult);
m_EditInfo.SetTextValue(MountName(ticket, IT_RESULT, EV_EDIT), dResult);
}
class="kw">inline class="type">class="kw">double CheckPosition(class="type">void)
{
class="type">class="kw">double Res = class="num">0, sl, profit, bid, ask;
class="type">class="kw">ulong ticket;

bid = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_BID);
ask = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_ASK);
for (class="type">int i0 = PositionsTotal() - class="num">1; i0 >= class="num">0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol())
{

◍ 用卖价买价穿透止损来平仓

这段逻辑跑在持仓遍历里,核心是把每张持仓的 ticket、成交量、浮动盈利先取出来,再按多空方向用实时卖价(ask)和买价(bid)去碰止损位。 ticket = PositionGetInteger(POSITION_TICKET); // 取持仓唯一编号 SetResult(ticket, PositionGetDouble(POSITION_VOLUME), profit = PositionGetDouble(POSITION_PROFIT)); // 记录成交量与盈利 sl = PositionGetDouble(POSITION_SL); // 取止损价 if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) // 判断为多单 { if (ask < sl) ClosePosition(ticket); // 卖价低于多单止损则平 }else { if ((bid > sl) && (sl > 0)) ClosePosition(ticket); // 买价高于空单止损且止损有效则平 } Res += profit; // 累加盈利 return Res; // 返回总盈利 多单用 ask < sl 平仓,空单用 bid > sl 且 sl > 0 才平,后者多一道 sl > 0 的判断,能避开未设止损(sl=0)的持仓被误平。 在 MT5 策略测试器里把这段塞进 OnTester 或自定义统计函数,回测时能直接看到穿透止损平仓后的累计 profit 数值,外汇和贵金属杠杆高,滑点可能让 ask/bid 跳过 sl,实盘平仓概率会低于回测。

MQL5 / C++
ticket = PositionGetInteger(POSITION_TICKET);
SetResult(ticket, PositionGetDouble(POSITION_VOLUME), profit = PositionGetDouble(POSITION_PROFIT));
sl = PositionGetDouble(POSITION_SL);
if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
  {
   if (ask < sl) ClosePosition(ticket);
  }else
  {
   if ((bid > sl) && (sl > class="num">0)) ClosePosition(ticket);
  }
Res += profit;
class="kw">return Res;

「给挂单标上真实交易量」

在 C_ObjectsTrade 类里补一个 SetVolumePendent 函数,就能把挂单对应的交易量写到图表控件上。这个函数不返回任何值,只负责把体积换算成最小手数的倍数再填进编辑框。 调用发生在 C_Router 的 UpdatePosition 里:每扫到一个同品种挂单,先取 ORDER_VOLUME_CURRENT,再跑 SetVolumePendent(ul, vol)。这样图表上显示的挂单手数就不会停在默认值。 注意换算分母用的是 Terminal.GetVolumeMinimal(),如果券商最小手数从 0.01 改成 0.001,显示数值会差 10 倍,调参时要核对。外汇与贵金属杠杆高,挂单体积标错可能让你误判占用保证金,实盘前请在 MT5 策略测试器用历史 Tick 验证一次。 下面这段是核心调用链,逐行看逻辑: inline void 声明内联函数,避免多次调用开销。 SetVolumePendent 参数 ticket 是挂单编号,dVolume 是原始手数。 函数体内 m_EditInfo.SetTextValue 把文本写进挂载名对应的编辑框,MountName(ticket, IT_PENDING, EV_EDIT) 定位控件。 dVolume / Terminal.GetVolumeMinimal() 把绝对手数转成最小单位倍数。 def_ColorVolumeEdit 控制字体颜色,方便区分挂单与成交单。 UpdatePosition 里 for 循环从尾向前遍历订单,OrderGetTicket(i0) 取 ticket。 OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol() 只处理当前图表品种。 price/take/stop/vol 分别取开仓价、止盈、止损、当前体积。 CreateIndicatorTrade 先建价标和 TP/SL 标,中间插入 SetVolumePendent 补体积。

MQL5 / C++
class="kw">inline class="type">void SetVolumePendent(class="type">class="kw">ulong ticket, class="type">class="kw">double dVolume)
                {
                                m_EditInfo.SetTextValue(MountName(ticket, IT_PENDING, EV_EDIT), dVolume / Terminal.GetVolumeMinimal(), def_ColorVolumeEdit);
                }
class="type">void UpdatePosition(class="type">int iAdjust = -class="num">1)
{
class=class="str">"cmt">// ... Internal code ....
                for (class="type">int i0 = o; i0 >= class="num">0; i0--) if ((ul = OrderGetTicket(i0)) > class="num">0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol())
                {
                                price = OrderGetDouble(ORDER_PRICE_OPEN);
                                take = OrderGetDouble(ORDER_TP);
                                stop = OrderGetDouble(ORDER_SL);
                                bTest = CheckLimits(price);
                                vol = OrderGetDouble(ORDER_VOLUME_CURRENT);
class=class="str">"cmt">// ... Internal code...
                                CreateIndicatorTrade(ul, price, IT_PENDING);
                                SetVolumePendent(ul, vol);
                                CreateIndicatorTrade(ul, take, IT_TAKE);
                                CreateIndicatorTrade(ul, stop, IT_STOP);
                }
};

把 Close 按钮的事件收回到订单视图类

删除挂单或某个破位触发级别,唯一稳妥的入口是每个数值角上的 Close 按钮。原实现把点击事件挂错了地方,会在并发操作时报错或漏删,正确做法是在 C_OrderView 类内部接管 CHARTEVENT_OBJECT_CLICK。 下面这段是高亮替换后的核心分发逻辑:先由 GetInfosOrder 从 sparam 解析出 ticket 和类型,再按 EV_CLOSE 分支处理。若是挂单(IT_PENDING)就走 RemoveOrderPendent,避免直接调 OrderDelete 引发的上下文竞争。 还有一个隐蔽坑:交易员手滑删掉了已绑定持仓数据的图形对象。系统补了一段重置代码,一旦检测到不该删的对象消失,EA 会在下一 tick 快速重建被删对象,防止面板状态与真实账户脱节。外汇与贵金属杠杆高,这类对象不同步可能让你误判真实敞口。 视频里能看到当前面板跑起来的样子,另有几处次要改动未列,因为它们不改变事件链路本身。

MQL5 / C++
class="type">void DispatchMessage(class="type">int id, class="type">long lparam, class="type">class="kw">double dparam, class="type">class="kw">string sparam)
{
			class="type">class="kw">ulong	 ticket;
			class="type">class="kw">double	price, pp, pt, ps;
		eIndicatorTrade it;
		eEventType 	ev;
			
		class="kw">switch (id)
		{
class=class="str">"cmt">// ... Internal code...
				case CHARTEVENT_OBJECT_CLICK:
					if (GetInfosOrder(sparam, ticket, price, it, ev))
					{
						class="kw">switch (ev)
						{
							case EV_CLOSE:
								if (OrderSelect(ticket)) class="kw">switch (it)
								{
									case IT_PENDING:
										RemoveOrderPendent(ticket);
									class="kw">break;
								case IT_TAKE:

◍ 挂单与持仓的分支改单逻辑

这段代码片段处理两类对象的尾随修改:未成交挂单(用 OrderGetDouble 读原单字段)与已成交持仓(用 PositionSelectByTicket 定位)。挂单部分按类型分支,limit 单把开仓价维持原值、止损置 0、止盈保留;stop 单则开仓价不变、止盈置 0、止损保留。 持仓分支里 IT_RESULT 直接调 ClosePosition 平掉该 ticket,IT_TAKE 留作后续止盈处理入口。注意 ModifyOrderPendent 的第三、四参数顺序在 limit 和 stop 两种情况下是反的——limit 传 (open, 0, sl),stop 传 (open, tp, 0),写反会在 MT5 里报 4756 参数错误。 开 MT5 把这段塞进你的订单管理函数,先拿模拟账户跑 EURUSD 的挂单改单,确认两种分支都不会触发 4756。外汇与贵金属杠杆高,改单逻辑出错可能瞬间放大滑点风险,先用小仓位验证。

MQL5 / C++
ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), class="num">0, OrderGetDouble(ORDER_SL));
class="kw">break;
case IT_STOP:
ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), OrderGetDouble(ORDER_TP), class="num">0);
class="kw">break;
}
if (PositionSelectByTicket(ticket)) class="kw">switch (it)
{
case IT_RESULT:
ClosePosition(ticket);
class="kw">break;
case IT_TAKE:

「持仓修改与图表事件的分发逻辑」

这段代码片段展示了在 EA 或指标中,通过枚举分支对持仓止损止盈做定向修改的典型写法。IT_TAKE 分支调用 ModifyPosition(ticket, 0, PositionGetDouble(POSITION_SL)),意思是把挂单或持仓的获利目标清零、保留原止损;IT_STOP 分支则反过来,ModifyPosition(ticket, PositionGetDouble(POSITION_TP), 0) 保留原止盈、清零止损。 DispatchMessage 函数承担了图表事件到业务逻辑的路由。当收到 CHART_EVENT_OBJECT_DELETE 或 CHARTEVENT_CHART_CHANGE 时,先跑 SetPositionMinimalAxleX() 重算坐标轴,再调 UpdatePosition() 刷新持仓图形对象,避免手动删线或缩放后界面与实际仓位脱节。 在 MT5 里实测时,注意 PositionGetDouble(POSITION_SL) 返回的是当前持仓的真实止损价,若仓位未设止损会回吐 0;ModifyPosition 失败通常因价格偏离市价超限或交易上下文被占用,建议加 Print 查错码。外汇与贵金属杠杆高,这类自动改单逻辑若未设边界,可能在剧烈波动中反复发单。

MQL5 / C++
ModifyPosition(ticket, class="num">0, PositionGetDouble(POSITION_SL));
class="kw">break;
case IT_STOP:
ModifyPosition(ticket, PositionGetDouble(POSITION_TP), class="num">0);
class="kw">break;
}
class="kw">break;
class=class="str">"cmt">// ... Rest of the code...
class="type">void DispatchMessage(class="type">int id, class="type">long lparam, class="type">class="kw">double dparam, class="type">class="kw">string sparam)
{
   class="type">class="kw">ulong         ticket;
   class="type">class="kw">double        price, pp, pt, ps;
   eIndicatorTrade it;
   eEventType    ev;

   class="kw">switch (id)
   {
      case CHART_EVENT_OBJECT_DELETE:
      case CHARTEVENT_CHART_CHANGE:
         SetPositionMinimalAxleX();
         UpdatePosition();
         class="kw">break;
class=class="str">"cmt">// ... Rest of the code...

持仓移动模块暂缺待续

眼下的订单系统能跑通开仓与基础管理,但作者明确点出:负责移动持仓的部分还没写进去。少了这套逻辑,EA 在实盘里就很难做到直观的加减仓与追踪止损,指导性会打折扣。 当前附带文件 EA_-_Sistema_de_ordens_0_IV_r.zip 约 11.7 MB,是这一阶段的半成品源码;有读者反馈编译时需注释掉第 52 行 //C_Terminal 终端; 才能过。外汇与贵金属波动剧烈,用此类未完工 EA 测试务必先在策略测试器跑历史数据。 下一篇才会补上持仓移动系统。想验证的人,直接下载 ZIP 在 MT5 里打开工程,先确认编译报错点,再对照自己需要的移动止损逻辑预留接口。

常见问题

把每个控件的左上角坐标和尺寸定义成宏常量,再用循环或批量创建函数按宏赋值,避免手填坐标错位。
用图表可视区价格范围除以像素高度得出每像素对应点数,再把挂单价格减去最低价乘比例,得到 y 坐标。
可以,小布能读取当前图表可见价格区间并提示标签重绘逻辑,你按它的坐标换算建议改一行代码即可验证。
在创建对象时把 OBJPROP_SELECTABLE 设为 false,或统一遍历对象取消可选属性,线条就不会被点中。
报告只统计已平或当前单 symbol 对冲,账户级跨品种净敞口需另算,二者口径不同属正常现象。