EA 交易中采用OnTrade() 函数处理交易事件·进阶篇
(2/3)· 为什么一次开仓会触发四次 OnTrade()?本篇带你逐层剥离交易事件的真实触发链路
◍ 用 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 间隔往往只有几毫秒。
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 里看到该笔盈亏。下面的代码保留了原判断骨架,复制进脚本就能跑。
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 走的是部分平仓还是完全平仓分支,就能验证你当前的判定链。
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,验证自己钩子有没有挂对位置。
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 留作后续弹窗或推送。
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) {