EA 交易中采用OnTrade() 函数处理交易事件·进阶篇
⚙️

EA 交易中采用OnTrade() 函数处理交易事件·进阶篇

(2/3)· 为什么一次开仓会触发四次 OnTrade()?本篇带你逐层剥离交易事件的真实触发链路

新手友好 第 2/3 篇
很多 EA 开发者在 OnTrade() 里直接弹 Alert,结果一次下单被刷了四五条提醒,还以为是代码写错了。其实这是交易事件在订单、持仓、历史多个列表变更时分别触发的正常机制。先理解触发来源,再谈过滤,比盲目加标志位更省事。

◍ 用 OnTrade 钩子捕捉订单生命周期

在 MT5 的 EA 里,想实时感知自己或账户下的订单变动,最干净的做法是挂 OnTrade 事件而非在 OnTick 里轮询。每次成交或订单状态切换,终端会主动调用这个函数,延迟通常低于一个 tick 周期。 关键在于用 OrdersTotal() 的前后差值判断方向:比上一次记录的 OrdersPrev 小,说明有订单消失(可能已成交或撤单);比它大,说明新订单出现。下面这段就是最小可用的状态机骨架。 [CODE]OrdersPrev = OrdersTotal(); if (OrdersPrev < OrdersTotal()) { // 出现订单 } else if(OrdersPrev > OrdersTotal()) { // 订单消失 } void OnTrade() { Alert("Trade 事件发生"); HistorySelect(start_date,TimeCurrent()); if (OrdersPrev < OrdersTotal()) { OrderGetTicket(OrdersTotal()-1); _GetLastError=GetLastError(); Print("错误 #",_GetLastError);ResetLastError(); if (OrderGetInteger(ORDER_STATE) == ORDER_STATE_STARTED) { Alert(OrderGetTicket(OrdersTotal()-1),"订单到达有待处理"); LastOrderTicket = OrderGetTicket(OrdersTotal()-1); } } else if(OrdersPrev > OrdersTotal()) { state = HistoryOrderGetInteger(LastOrderTicket,ORDER_STATE); _GetLastError=GetLastError(); if (_GetLastError != 0){Alert("Error #",_GetLastError," 订单没有找到!");LastOrderTicket = 0;} Print("错误 #",_GetLastError," state: ",state);ResetLastError(); if (state == ORDER_STATE_FILLED) { Alert(LastOrderTicket, "订单被执行, 进入交易"); } } }[/CODE] 逐行拆一下:OrdersPrev 在每次计时前先存好当前挂单总数;OnTrade 开头弹 Alert 并 HistorySelect 拉取历史,保证能查到刚成交的纪录。新订单进来时取最后一笔 ticket,若状态是 ORDER_STATE_STARTED 就记下 LastOrderTicket 备用。 订单减少分支里,用保存的 ticket 去历史池反查状态;若 GetLastError 非 0 说明 ticket 已失效,清空变量避免下次误用。当 state 等于 ORDER_STATE_FILLED,意味着这笔单彻底成交,此时可接自己的分析逻辑。外汇与贵金属杠杆高,这类实时捕捉仅用于风控或统计,不代表任何方向确定性。 实际验证时把 LastOrderTicket 打印到日志,开 MT5 用策略测试器跑一单,能看到从 STARTED 到 FILLED 的两次 Alert 间隔往往只有几毫秒。

MQL5 / C++
OrdersPrev = OrdersTotal();
if (OrdersPrev < OrdersTotal())
{
class=class="str">"cmt">// 出现订单
}
else if(OrdersPrev > OrdersTotal())
{
class=class="str">"cmt">// 订单消失
}
class="type">void OnTrade()
{
Alert("Trade 事件发生");
HistorySelect(start_date,TimeCurrent());
if (OrdersPrev < OrdersTotal())
{
OrderGetTicket(OrdersTotal()-class="num">1);
_GetLastError=GetLastError();
Print("错误 #",_GetLastError);ResetLastError();
if (OrderGetInteger(ORDER_STATE) == ORDER_STATE_STARTED)
{
Alert(OrderGetTicket(OrdersTotal()-class="num">1),"订单到达有待处理");
LastOrderTicket = OrderGetTicket(OrdersTotal()-class="num">1);
}
}
else if(OrdersPrev > OrdersTotal())
{
state = HistoryOrderGetInteger(LastOrderTicket,ORDER_STATE);
_GetLastError=GetLastError();
if (_GetLastError != class="num">0){Alert("Error #",_GetLastError," 订单没有找到!");LastOrderTicket = class="num">0;}
Print("错误 #",_GetLastError," state: ",state);ResetLastError();
if (state == ORDER_STATE_FILLED)
{
Alert(LastOrderTicket, "订单被执行, 进入交易");
}
}
}

从成交回报区分建仓、加仓与平仓

在 EA 里监听交易历史尾笔成交,靠 DEAL_ENTRY 与 DEAL_TYPE 的组合能直接判断这笔动作属于建仓、加仓还是退出。下面这段逻辑只取 HistoryDealsTotal()-1 那一条最新 deal 做识别,实战中挂到 OnTradeTransaction 的 TRADE_TRANSACTION_DEAL_ADD 分支即可。 识别卖出方向时,若 PositionSelect 命中且 POSITION_VOLUME 等于该 deal 的 DEAL_VOLUME,说明是刚建仓;若持仓量大于成交量大,则是加仓。外汇与贵金属杠杆高,部分平仓和加仓在黄金上尤其频繁,靠这个判断能避开把加仓误报成新仓。 退出市场分支里,DEAL_ENTRY_OUT 下再按 DEAL_TYPE 切:仓位还存在就是部分平仓,PositionSelect 返回 false 才是整仓退出,此时顺手把 DEAL_PROFIT 打出来就能在 Alert 里看到该笔盈亏。下面的代码保留了原判断骨架,复制进脚本就能跑。

MQL5 / C++
HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_SYMBOL));
}
break;
case class="num">1:
class=class="str">"cmt">// 如果仓位交易量和交易相等,则仓位刚刚建立
if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_SYMBOL))
&&
(PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_VOLUME)))
{
Alert("卖出仓位被建立,货币对 ",
HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_SYMBOL));
}
else
class=class="str">"cmt">// 如果仓位交易量和交易不相等,则仓位被增加
if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_SYMBOL))
&&
(PositionGetDouble(POSITION_VOLUME) > HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_VOLUME)))
{
Alert("卖出仓位增加,货币对 ",
HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_SYMBOL));
}
break;
class="kw">default:
Alert("未被处理的类型代码: ",
HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_TYPE));
break;
}
break;
class=class="str">"cmt">// 退出市场
case DEAL_ENTRY_OUT:
Alert(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_ORDER),
         " 订单发起交易 #",HistoryDealGetTicket(HistoryDealsTotal()-class="num">1));
class="kw">switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_TYPE))
{
case class="num">0:
class=class="str">"cmt">// 如果我们试图关闭的仓位还有剩余,我们只关闭部分仓位
if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_SYMBOL)) == true)
{
Alert("卖出仓位部分平仓,货币对 ",
HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_SYMBOL),
" 利润 = ",
HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_PROFIT));
}
else
class=class="str">"cmt">// 如果仓位没找到,说明是全部平仓
if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_SYMBOL)) == false)
{
Alert("卖出仓位平仓,货币对 ",
HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_SYMBOL),
" 利润 = ",
HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_PROFIT));
}
break;
case class="num">1:
class=class="str">"cmt">// 如果我们尝试关闭的仓位依然存在,说明我们是部分平仓

「用 DEAL_ENTRY 区分平仓与反转的坑」

这段逻辑挂在 OnTrade() 里,靠 HistoryDealsTotal()-1 取最近一笔成交,再用 PositionSelect 反查同品种是否还有仓位,从而判断是部分平仓还是完全平仓。外汇和贵金属杠杆高,这类回测外的实时判定若漏掉 DEAL_ENTRY_INOUT,会把反转当成普通平仓报利润,信号会失真。 下面这段是原文里处理反转与状态记录的分支,注意 DEAL_ENTRY_INOUT 下又套了一层对 DEAL_TYPE 的 switch: [CODE] case DEAL_ENTRY_INOUT: Alert(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ORDER), " 订单引发交易 #",HistoryDealGetTicket(HistoryDealsTotal()-1)); switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE)) { case 0: Alert("卖出被反转为买入,货币对 ", HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL), " 利润 = ", HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT)); break; case 1: Alert("买入被反转为卖出,货币对 ", HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL), " 利润 = ", HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT)); break; default: Alert("未被处理的类型代码: ", HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE)); break; } break; case DEAL_ENTRY_STATE: Alert("指出为状态记录. 未被处理的类型代码: ", HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE)); break; [/CODE] 逐行拆:DEAL_ENTRY_INOUT 代表仓位反向开仓(如多翻空),先弹原订单号与成交号;内层 case 0 是空转多、case 1 是多转空,都带出品种与那笔成交的浮盈浮亏;default 抓未枚举类型。DEAL_ENTRY_STATE 只是状态流水(如挂单改价),本身不产生头寸变化,原代码直接报警未处理类型。 别把 HistoryDealsTotal()-1 当绝对安全下标。若同一 tick 内多笔成交,取末笔可能漏掉前一笔的 entry 类型;实盘里倾向在 OnTradeTransaction 用 trans 参数拿刚发生的 deal ticket,比倒查总数稳。MT5 里开个 EA 把这段贴进 OnTrade,故意手动平一半黄金仓位,看 Alert 走的是部分平仓还是完全平仓分支,就能验证你当前的判定链。

MQL5 / C++
if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_SYMBOL)) == true)
{
Alert("买入仓位部分平仓,货币对 ",
HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_SYMBOL),
" 利润 = ",
HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_PROFIT));
}
else
class=class="str">"cmt">// 如果仓位没有找到,说明是完全平仓
if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_SYMBOL)) == false)
{
Alert("买入仓位平仓,货币对 ",
HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_SYMBOL),
" 利润 = ",
HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_PROFIT));
}
break;
class="kw">default:
Alert("没有处理的类型代码: ",
HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_TYPE));
break;
}
break;
class=class="str">"cmt">// 反转
case DEAL_ENTRY_INOUT:
Alert(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_ORDER), "\n订单引发交易 #",HistoryDealGetTicket(HistoryDealsTotal()-class="num">1));
class="kw">switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_TYPE))
{
case class="num">0:
Alert("卖出被反转为买入,货币对 ",
HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_SYMBOL),
" 利润 = ",
HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_PROFIT));
break;
case class="num">1:
Alert("买入被反转为卖出,货币对 ",
HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_SYMBOL),
" 利润 = ",
HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_PROFIT));
break;
class="kw">default:
Alert("未被处理的类型代码: ",
HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_TYPE));
break;
}
break;
class=class="str">"cmt">// 状态记录
case DEAL_ENTRY_STATE:
Alert("指出为状态记录. 未被处理的类型代码: ", 
HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_TYPE));
break;
}
class=class="str">"cmt">// --
}
}
OrdersPrev = OrdersTotal();
class=class="str">"cmt">//---
}
class="type">class="kw">datetime start_date = class="num">0;   class=class="str">"cmt">// 我们开始读取历史的日期
class="type">int OrdersPrev = class="num">0;        class=class="str">"cmt">// 上一次调用OnTrade()时订单的数量
class="type">int PositionsPrev = class="num">0;     class=class="str">"cmt">// 上一次调用OnTrade()时仓位的数量

◍ 用订单状态钩子抓建仓瞬间

在 EA 里实时感知「订单刚进来」和「仓位刚建立」,核心是先抓订单池里的最后一个 ticket,再读它的 state。下面这段代码把 LastOrderTicket 存下来,等 ORDER_STATE_STARTED 一出现就弹 Alert,相当于给盯盘程序装了个前哨。 [CODE] ulong LastOrderTicket = 0; // 最后一个处理订单的订单号 int _GetLastError=0; // 错误代码 long state=0; // 订单状态 OrderGetTicket(OrdersTotal()-1);// 选择最后一个订单 _GetLastError=GetLastError(); Print("Error #",_GetLastError);ResetLastError(); if (OrderGetInteger(ORDER_STATE) == ORDER_STATE_STARTED) { Alert(OrderGetTicket(OrdersTotal()-1),"订单到来,有待处理"); LastOrderTicket = OrderGetTicket(OrdersTotal()-1); // 保存订单编号以后使用 } [/CODE] 上面逐行拆一下:第1~3行是变量声明,分别记订单号、错误码、状态;OrderGetTicket(OrdersTotal()-1) 把订单池末尾那笔设为当前订单;GetLastError 拿错误码后 Print 并 ResetLastError 清掉,避免污染后续判断;当 OrderGetInteger(ORDER_STATE) 返回 ORDER_STATE_STARTED,说明订单已下发但未完全成交,这时 Alert 并缓存 ticket。 订单若变成 ORDER_STATE_FILLED,就进历史成交去分辨是新建仓还是加仓。DEAL_ENTRY_IN 分支里,用 PositionGetDouble(POSITION_VOLUME) 和 DEAL_VOLUME 比大小:相等倾向是刚建仓,大于则倾向是加仓。外汇和贵金属杠杆高,这种自动识别能帮你少漏信号,但信号本身不预示涨跌,只描述动作。 [CODE] switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ENTRY)) { case DEAL_ENTRY_IN: switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE)) { case 0: if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME))) { Alert("建立买入仓位,货币对 ", HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); } else if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) && (PositionGetDouble(POSITION_VOLUME) > HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME))) { Alert("买入仓位增加,货币对 ", HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); } break; case 1: if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME))) { Alert("卖出仓位建立,货币对 ", HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); } } } [/CODE] 这段里 DEAL_TYPE 的 0 是买、1 是卖;HistoryDealsTotal()-1 永远取最新成交。你在 MT5 里跑一下,手动下一单就能听到 Alert,验证自己钩子有没有挂对位置。

MQL5 / C++
class="type">class="kw">ulong LastOrderTicket = class="num">0; class=class="str">"cmt">// 最后一个处理订单的订单号
class="type">int _GetLastError=class="num">0;       class=class="str">"cmt">// 错误代码
class="type">long state=class="num">0;              class=class="str">"cmt">// 订单状态
OrderGetTicket(OrdersTotal()-class="num">1);class=class="str">"cmt">// 选择最后一个订单
_GetLastError=GetLastError();
Print("Error #",_GetLastError);ResetLastError();
if (OrderGetInteger(ORDER_STATE) == ORDER_STATE_STARTED)
{
Alert(OrderGetTicket(OrdersTotal()-class="num">1),"订单到来,有待处理");
LastOrderTicket = OrderGetTicket(OrdersTotal()-class="num">1);    class=class="str">"cmt">// 保存订单编号以后使用
}

class="kw">switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_ENTRY))
{
case DEAL_ENTRY_IN:
 class="kw">switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_TYPE))
 {
 case class="num">0:
  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_SYMBOL))
  && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_VOLUME)))
  { Alert("建立买入仓位,货币对 ", HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_SYMBOL)); }
  else
  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_SYMBOL))
  && (PositionGetDouble(POSITION_VOLUME) > HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_VOLUME)))
  { Alert("买入仓位增加,货币对 ", HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_SYMBOL)); }
  break;
 case class="num">1:
  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_SYMBOL))
  && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_VOLUME)))
  { Alert("卖出仓位建立,货币对 ", HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-class="num">1),DEAL_SYMBOL)); }
 }
}

把持仓快照塞进结构数组再比改动

上面这段逻辑干了两件事:先用 GetPosition() 把当前账户所有持仓扒进 _position 结构数组,再和上一帧的 PrevPositionList 逐条比对 sl / tp 是否被动过。 GetPosition() 里先按 PositionsTotal() 拿总数,ArrayResize 至少留 1 个元素防止空仓时数组越界;循环从 z=_PositionsTotal-1 倒序到 0,PositionSelect 失败就 Print 错误号并 continue,成功才把类型、时间、幻数、成交量、开仓价、SL、TP、现价、手续费、库存费、浮动盈亏、品种、注释全部写进数组。 结构 _position 用 long/datetime/double/string 混排,覆盖了 MT5 持仓可读取的 13 个字段;实盘里外汇与贵金属杠杆高、点差跳变快,SL/TP 被经纪商或 EA 改动往往领先于价格异动,这类快照比对能在 1 个 tick 内抓到修改痕迹。 下方在主循环里用 PositionsPrev == PositionsTotal() 且 OrdersPrev == OrdersTotal() 做闸门,只有持仓和挂单数量都没变时才进字段比对,避免增平仓噪声触发误报;一旦发现 PrevPositionList[i].sl != PositionList[i].sl 就拼字符串记改动,modify 置 true 留作后续弹窗或推送。

MQL5 / C++
class="type">void GetPosition(_position &Array[])
{
 class="type">int _GetLastError=class="num">0,_PositionsTotal=PositionsTotal();
 class="type">int temp_value=(class="type">int)MathMax(_PositionsTotal,class="num">1);
 ArrayResize(Array, temp_value);
 _ExpertPositionsTotal=class="num">0;
 for(class="type">int z=_PositionsTotal-class="num">1; z>=class="num">0; z--)
 {
  if(!PositionSelect(PositionGetSymbol(z)))
  {
   _GetLastError=GetLastError();
   Print("OrderSelect() - Error #",_GetLastError);
   class="kw">continue;
  }
  else
  {
   class=class="str">"cmt">// 如果发现仓位,把它的信息放到数组中
   Array[z].type         = PositionGetInteger(POSITION_TYPE);
   Array[z].time         = PositionGetInteger(POSITION_TIME);
   Array[z].magic        = PositionGetInteger(POSITION_MAGIC);
   Array[z].volume       = PositionGetDouble(POSITION_VOLUME);
   Array[z].priceopen    = PositionGetDouble(POSITION_PRICE_OPEN);
   Array[z].sl           = PositionGetDouble(POSITION_SL);
   Array[z].tp           = PositionGetDouble(POSITION_TP);
   Array[z].pricecurrent = PositionGetDouble(POSITION_PRICE_CURRENT);
   Array[z].comission    = PositionGetDouble(POSITION_COMMISSION);
   Array[z].swap         = PositionGetDouble(POSITION_SWAP);
   Array[z].profit       = PositionGetDouble(POSITION_PROFIT);
   Array[z].symbol       = PositionGetString(POSITION_SYMBOL);
   Array[z].comment      = PositionGetString(POSITION_COMMENT);
   _ExpertPositionsTotal++;
  }
 }
 temp_value=(class="type">int)MathMax(_ExpertPositionsTotal,class="num">1);
 ArrayResize(Array,temp_value);
}
class="kw">struct _position
{
 class="type">long     type,          class=class="str">"cmt">// 仓位类型
 magic;         class=class="str">"cmt">// 仓位幻数
 class="type">class="kw">datetime time;          class=class="str">"cmt">// 建仓时间
 class="type">class="kw">double   volume,        class=class="str">"cmt">// 持仓交易量
 priceopen,     class=class="str">"cmt">// 仓位价格
 sl,            class=class="str">"cmt">// 仓位的止损价位
 tp,            class=class="str">"cmt">// 仓位的获利价位
 pricecurrent,  class=class="str">"cmt">// 交易品种的当前价位
 comission,     class=class="str">"cmt">// 手续费
 swap,          class=class="str">"cmt">// 累计库存费
 profit;        class=class="str">"cmt">// 当前利润
 class="type">class="kw">string   symbol,        class=class="str">"cmt">// 仓位的交易品种
 comment;       class=class="str">"cmt">// 仓位注释
};
class="type">int _ExpertPositionsTotal = class="num">0;
_position PositionList[],
 PrevPositionList[];
GetPosition(PrevPositionList);
GetPosition(PositionList);
if ((PositionsPrev == PositionsTotal()) && (OrdersPrev == OrdersTotal()))
{
 class="type">class="kw">string _alerts = "";
 class="type">bool modify = false;
 for (class="type">int i=class="num">0;i<_ExpertPositionsTotal;i++)
 {
  if (PrevPositionList[i].sl != PositionList[i].sl)
  {
   _alerts += "货币对 "+PositionList[i].symbol+" 止损价位从 "+ PrevPositionList[i].sl +" 改为 "+ PositionList[i].sl +"\n";
   modify = true;
  }
  if (PrevPositionList[i].tp != PositionList[i].tp)
  {
把事件日志交给小布盯盘
小布盯盘的 AIGC 已内置对品种页交易事件的轻量标注,打开对应品种页即可看到持仓与挂单变动的脉络,你只需专注策略逻辑本身。

常见问题

OnTrade() 是交易事件驱动,只在订单或持仓实际变更时被调用,相比每秒轮询更省资源,也更快捕捉到成交瞬间。
说明文档指出订单下出、持仓新建、订单历史与交易历史列表变更都会生成交易事件,因此一次动作可能连续触发四次调用,需要在函数内做去重或状态比对。
反向开仓通常伴随原持仓平仓与新反向持仓开启,会先后生成平仓与开仓相关事件,建议用持仓 ticket 和方向变化来识别,而非依赖单次调用。
小布盯盘的品种页会把持仓与挂单变动以可视化方式呈现,相当于把 EA 内部的交易事件外显出来,方便你核对逻辑是否按预期运行。
挂单的修改属于交易操作的一种,会生成交易事件并调用 OnTrade(),但需注意它和持仓修改的标识应分开处理以免混淆。