开发多币种 EA 交易(第 4 部分):虚拟挂单和保存状态·进阶篇
🛠️

开发多币种 EA 交易(第 4 部分):虚拟挂单和保存状态·进阶篇

(2/3)· 为什么终端重启后 EA 总从零开始,以及怎样用虚拟挂单补齐策略执行的可视化盲区

含代码示例实战向 第 2/3 篇
不少多币种 EA 在终端崩溃或切换设备后直接丢掉了挂单与持仓上下文,重新启动便按初始逻辑下重复单。把虚拟挂单和状态保存当可选功能,往往让回测里跑通的策略在实盘断点后续跑时彻底失真。

◍ 挂单方向与价格精度的配对逻辑

这段代码片段展示了根据 m_openDistance 符号决定挂单类型的分支结构:当距离小于 0 时,系统下达 BUY_LIMIT 单;否则若等于 0 则直接开虚拟 BUY 仓位。外汇与贵金属杠杆高,挂单触发受滑点影响,实盘可能偏离预期。 所有挂单的 price、sl、tp 都经过 NormalizeDouble(value, digits) 处理,目的是把价格对齐到品种小数位,避免 MT5 因精度不符拒绝成交。digits 通常来自 SymbolInfoInteger(_Symbol, SYMBOL_DIGITS),对 XAUUSD 多为 2,对 EURUSD 多为 5。 注意 BUY_LIMIT 分支里 sl、tp 同样用 NormalizeDouble 包裹,而市价开仓分支传了 0 作为 price 占位,由经纪商按市价撮合。复制这段代码到 MT5 策略测试器,改 m_openDistance 阈值,能直接观察不同分支的订单发送结果。

MQL5 / C++
         NormalizeDouble(sl, digits),
         NormalizeDouble(tp, digits),
         "", expiration);
      } else if(m_openDistance < class="num">0) {
         class=class="str">"cmt">// Set SELL LIMIT pending order
         res = m_orders[i].Open(m_symbol, ORDER_TYPE_BUY_LIMIT, m_fixedLot,
               NormalizeDouble(price, digits),
               NormalizeDouble(sl, digits),
               NormalizeDouble(tp, digits),
               "", expiration);
      } else {
         class=class="str">"cmt">// Open a class="kw">virtual SELL position
         res = m_orders[i].Open(m_symbol, ORDER_TYPE_BUY, m_fixedLot,
               class="num">0,
               NormalizeDouble(sl, digits),
               NormalizeDouble(tp, digits));
      }
      class="kw">break; class=class="str">"cmt">// and exit

「EA 状态落地:只存虚拟仓位还不够」

我们基于同一套类做了两个 EA:SimpleVolumesExpertSingle.mq5 只用来在策略测试器里优化单实例参数,SimpleVolumesExpert.mq5 内嵌多份策略副本、用前者的优参跑实盘。以后真账户只可能上第二个,所以状态保存只针对它——里面要涵盖几十个策略实例的运行时信息。 这里说的状态不是『策略集合长什么样』,那是写死的:每次启动 EA 里带指定参数的策略实例都一样。状态指的是启动后开的真实/虚拟仓、以及按价格算出来的中间量。终端重启后,EA 得认回自己的真实仓,并把虚拟仓和算值恢复出来;真实仓能从终端读,虚拟仓和算值必须自己落盘。 以这个简单策略为例,它不累积计算数据,状态完全由一组虚拟仓位+挂单构成。但光把 CVirtualOrder 对象数组写文件会出事:设想你跑了几个不同策略的 EA,每个都用 9 个策略实例 × 3 个虚拟仓对象 = 27 个 CVirtualOrder。万一某个 EA 把别的 EA 的 27 个对象读进来了,状态就串台了。 防串台的办法是在文件里带上 EA 自身参数、以及作为组件的策略参数,相当于给状态盖个戳。保存时机上,策略参数恒定不用老写;虚拟仓属性只在开/平操作后才变,所以那之后存一次就够了,每个 tick 或定时器硬存是多余开销。 实现上别把所有保存逻辑堆在 EA 顶层。对象是分层结构:EA → 策略 → 虚拟仓位。每一层都给自己加 Save() / Load(),顶层负责开文件,下层拿已开文件的描述符接着写。这样文件名只在 EA 层定一次,策略和虚拟仓层不操心 IO。

给虚拟仓位加个断点续跑的存档

CVirtualAdvisor 里新增两个成员:m_name 存 EA 名(构造时写死,可视化测试下拼上幻数和 .test 后缀),m_lastSaveTime 记上次落盘时间。仅在虚拟仓位组成或实际交易量修正后才值得写文件,所以保存前要先比大小——若 m_lastSaveTime 小于接收者对象里的“最后一次变动时间”,说明状态脏了,才执行保存。 非可视化模式的单测或优化直接跳过保存;可视模式无论优化还是单测都落盘,这样策略测试器里也能验证文件写入对不对。EA 层 Save() 先写当前时间与策略数,再循环调各策略的保存,最后用虚拟仓位变动时间刷新 m_lastSaveTime,下次变动前不再写任何字节。 Load() 是反向操作:先读保存时间和策略数,若与 EA 内已挂策略数量不一致就判定文件错误直接退出,一致才逐策略委托读取。注意不能在构造函数里调 Load(),因为那时策略实例还没挂进 EA——必须等 OnInit() 里全部添加完毕再调一次。 下面这段类声明与构造、Tick 逻辑可以直接抄进 VirtualAdvisor.mqh:构造里用 StringFormat 拼出 "名称-幻数.test.csv" 这类文件名,Tick() 末尾挂 Save() 调用,非可视测试自动不吃后缀。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| Class of the EA handling class="kw">virtual positions(orders)&nbsp;&nbsp;            |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class</span> CVirtualAdvisor : <span class="keyword">class="kw">public</span> CAdvisor {
<span class="keyword">class="kw">protected</span>:
&nbsp;&nbsp; ...
&nbsp;&nbsp;
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">string</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_name;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// EA name</span>
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">datetime</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_lastSaveTime;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// Last save time</span>
<span class="keyword">class="kw">public</span>:
&nbsp;&nbsp; CVirtualAdvisor(<span class="keyword">class="type">class="kw">ulong</span> p_magic = <span class="number">class="num">1</span>, <span class="keyword">class="type">class="kw">string</span> p_name = <span class="class="type">class="kw">string">""</span>); <span class="comment">class=class="str">"cmt">// Constructor</span>
&nbsp;&nbsp; ...
&nbsp;&nbsp; <span class="keyword">class="kw">virtual</span> <span class="keyword">class="type">bool</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Save();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// Save status</span>
&nbsp;&nbsp; <span class="keyword">class="kw">virtual</span> <span class="keyword">class="type">bool</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Load();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// Load status</span>
};
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| Constructor&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
CVirtualAdvisor::CVirtualAdvisor(<span class="keyword">class="type">class="kw">ulong</span> p_magic = <span class="number">class="num">1</span>, <span class="keyword">class="type">class="kw">string</span> p_name = <span class="class="type">class="kw">string">""</span>) :
&nbsp;&nbsp; ...
&nbsp;&nbsp; m_lastSaveTime(<span class="number">class="num">0</span>) {
&nbsp;&nbsp; m_name = <span class="functions">StringFormat</span>(<span class="class="type">class="kw">string">"%s-%d%s.csv"</span>,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (p_name != <span class="class="type">class="kw">string">""</span> ? p_name : <span class="class="type">class="kw">string">"Expert"</span>),
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p_magic,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (<span class="functions">MQLInfoInteger</span>(<span class="macro">MQL_TESTER</span>) ? <span class="class="type">class="kw">string">".test"</span> : <span class="class="type">class="kw">string">""</span>)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;);
};
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| OnTick event handler&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;     |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">void</span> CVirtualAdvisor::Tick(<span class="keyword">class="type">void</span>) {
<span class="comment">class=class="str">"cmt">// Receiver handles class="kw">virtual positions</span>
&nbsp;&nbsp; m_receiver.Tick();
<span class="comment">class=class="str">"cmt">// Start handling in strategies</span>
&nbsp;&nbsp; CAdvisor::Tick();
<span class="comment">class=class="str">"cmt">// Adjusting market volumes</span>
&nbsp;&nbsp; m_receiver.Correct();
<span class="comment">class=class="str">"cmt">// Save status</span>
&nbsp;&nbsp; <span style="background-class="type">class="kw">color:rgb(class="num">216, class="num">232, class="num">194);">Save()</span>;
}
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| Save status&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;          |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">bool</span> CVirtualAdvisor::Save() {

◍ 状态落盘与重载的触发边界

虚拟策略跑在 MT5 里最怕一件事:终端重启后仓位逻辑全丢。这段实现把策略状态写进以 tab 分隔的 CSV 文件,只在「距上次改动时间有新变更、且不在优化模式、且非静默回测(或正开可视化测试)」时落盘,避免回测噪声污染实盘状态文件。 保存函数先用 FileOpen 以 FILE_WRITE 打开同名文件,依次写入最后变更时间、策略数组长度,再遍历每个 CVirtualStrategy 调用各自 Save。写完后把 m_lastSaveTime 同步为 s_lastChangeTime,并打印带时分秒的 OK 日志;打开失败则置 res=false 并输出 LastError,方便你直接在专家日志里抓文件权限或路径问题。 加载侧对称:FileIsExist 命中且同样避开优化/静默测试时,以 FILE_READ 打开并 FileReadDatetime 读回 m_lastSaveTime。这套判定意味着你若在策略测试器里跑非可视化回测,状态既不会存也不会读——想验证持久化逻辑,必须开可视化模式或直接在实盘/模拟盘环境跑。外汇与贵金属杠杆高,状态误载可能引发重复下单,上线前务必用可视化测试过一遍读写。

MQL5 / C++
  class="type">bool res = true;
  class=class="str">"cmt">// Save status if:
  if(true
        class=class="str">"cmt">// later changes appeared
        && m_lastSaveTime < CVirtualReceiver::s_lastChangeTime
        class=class="str">"cmt">// currently, there is no optimization
        && !MQLInfoInteger(MQL_OPTIMIZATION)
        class=class="str">"cmt">// and there is no testing at the moment or there is a visual test at the moment
        && (!MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_VISUAL_MODE))
     ) {
      class="type">int f = FileOpen(m_name, FILE_CSV | FILE_WRITE, &class="macro">#x27;\t&class="macro">#x27;);
      if(f != INVALID_HANDLE) {  class=class="str">"cmt">// If file is open, save
        FileWrite(f, CVirtualReceiver::s_lastChangeTime);  class=class="str">"cmt">// Time of last changes
        FileWrite(f, ArraySize(m_strategies));             class=class="str">"cmt">// Number of strategies
        class=class="str">"cmt">// All strategies
        FOREACH(m_strategies, ((CVirtualStrategy*) m_strategies[i]).Save(f));
        FileClose(f);
        class=class="str">"cmt">// Update the last save time
        m_lastSaveTime = CVirtualReceiver::s_lastChangeTime;
        PrintFormat(__FUNCTION__" | OK at %s to %s",
                    TimeToString(m_lastSaveTime, TIME_DATE | TIME_MINUTES | TIME_SECONDS), m_name);
      } else {
        PrintFormat(__FUNCTION__" | ERROR: Operation FileOpen for %s failed, LastError=%d",
                    m_name, GetLastError());
        res = false;
      }
  }
  class="kw">return res;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Load status                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CVirtualAdvisor::Load() {
  class="type">bool res = true;
  class=class="str">"cmt">// Load status if:
  if(true
        class=class="str">"cmt">// file exists
        && FileIsExist(m_name)
        class=class="str">"cmt">// currently, there is no optimization
        && !MQLInfoInteger(MQL_OPTIMIZATION)
        class=class="str">"cmt">// and there is no testing at the moment or there is a visual test at the moment
        && (!MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_VISUAL_MODE))
     ) {
      class="type">int f = FileOpen(m_name, FILE_CSV | FILE_READ, &class="macro">#x27;\t&class="macro">#x27;);
      if(f != INVALID_HANDLE) {  class=class="str">"cmt">// If the file is open, then load
        m_lastSaveTime = FileReadDatetime(f);   class=class="str">"cmt">// Last save time

「从文件复原策略状态的那几行关键代码」

EA 重启最怕丢状态:挂着的虚拟仓位、策略内部计数全没,等于裸奔。上面这段把上次保存的时间戳和策略数量先读出来,再逐条 Load,不匹配就直接报错退出,逻辑很硬。 注意 f_strategiesCount 是用 StringToInteger(FileReadString(f)) 拿的,文件里第一串就是策略数;紧接着拿 ArraySize(m_strategies) 做相等判断,不一致就打印『expected but found』。这一步若不过,后面 Load 根本不会跑。 OnInit 里写死了 CStrategy *strategies[9],下标 0 到 8 共 9 个实例,然后 new CVirtualAdvisor 并 FOREACH 添加,最后 expert.Load() 尝试恢复。外汇与贵金属杠杆高,状态恢复失败可能让仓位管理逻辑错乱,实盘前务必在 MT5 策略测试器里断点跟一遍 Load 返回值。

MQL5 / C++
PrintFormat(__FUNCTION__" | LAST SAVE at %s", TimeToString(m_lastSaveTime, TIME_DATE | TIME_MINUTES | TIME_SECONDS));
class=class="str">"cmt">// Number of strategies
class="type">long f_strategiesCount = StringToInteger(FileReadString(f));
class=class="str">"cmt">// Does the loaded number of strategies match the current one?
res = (ArraySize(m_strategies) == f_strategiesCount);
if(res) {
  class=class="str">"cmt">// Load all strategies
  FOREACH(m_strategies, res &= ((CVirtualStrategy*) m_strategies[i]).Load(f));
  if(!res) {
    PrintFormat(__FUNCTION__" | ERROR loading strategies from file %s", m_name);
  }
} else {
  PrintFormat(__FUNCTION__" | ERROR: Wrong strategies count(%d expected but %d found in file %s)",
    ArraySize(m_strategies), f_strategiesCount, m_name);
}
FileClose(f);
} else {
  PrintFormat(__FUNCTION__" | ERROR: Operation FileOpen for %s failed, LastError=%d", m_name, GetLastError());
  res = false;
}
  }
  class="kw">return res;
}
CVirtualAdvisor    *expert;                    class=class="str">"cmt">// EA object
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit() {
class=class="str">"cmt">// Create and fill the array of strategy instances
  CStrategy *strategies[class="num">9];
  strategies[class="num">0] = ...
  ...
  strategies[class="num">8] = ...
class=class="str">"cmt">// Create an EA handling class="kw">virtual positions
  expert = new CVirtualAdvisor(magic_, "SimpleVolumes");
class=class="str">"cmt">// Add strategies to the EA
  FOREACH(strategies, expert.Add(strategies[i]));
class=class="str">"cmt">// Load the previous state if available  
  expert.Load();
  class="kw">return(INIT_SUCCEEDED);
}

给策略类补上存取和序列化

在交易策略基类里加三个东西就能让虚拟策略可持久化:Save() 把状态写进文件,Load() 按原顺序读回,~ 重载成一元运算符把对象压成字符串。字符串格式直接是「类名(虚拟仓位数组长度)」,例如 CVirtualStrategy(3) 表示当前挂了 3 个虚拟仓位。 写入时先 FileWrite 把 ~this 的字符串落盘,再用 FOREACH 挨个调每个虚拟仓位的 Save()。读取端先做一道校验:只有文件里读出的参数字符串和 ~this 完全相等,才继续 Load 每个仓位,避免版本错配把历史状态塞进新逻辑。 改动落地的文件是工作目录下的 VirtualStrategy.mqh。MT5 里开个脚本循环跑 Save/Load,断点看 m_orders 数组长度是否和字符串里的数字对得上,就能验证这套机制没漂。外汇与贵金属品种波动大,虚拟回测和实盘状态分离存储时尤其要防参数串档。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Class of a trading strategy with class="kw">virtual positions                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CVirtualStrategy : class="kw">public CStrategy {
  ...
class="kw">public:
  ...
  class="kw">virtual class="type">bool      Load(const class="type">int f);   class=class="str">"cmt">// Load status
  class="kw">virtual class="type">bool      Save(const class="type">int f);   class=class="str">"cmt">// Save status
  class="type">class="kw">string class="kw">operator~();                    class=class="str">"cmt">// Convert object to class="type">class="kw">string
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Convert an object to a class="type">class="kw">string                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string CVirtualStrategy::class="kw">operator~() {
  class="kw">return StringFormat("%s(%d)", class="kw">typename(this), ArraySize(m_orders));
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Save status                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CVirtualStrategy::Save(const class="type">int f) {
  class="type">bool res = true;
  FileWrite(f, ~this); class=class="str">"cmt">// Save parameters
  class=class="str">"cmt">// Save class="kw">virtual positions(orders) of the strategy
  FOREACH(m_orders, res &= m_orders[i].Save(f));
  class="kw">return res;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Load status                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CVirtualStrategy::Load(const class="type">int f) {
  class="type">bool res = true;
  class=class="str">"cmt">// Current parameters are equal to read parameters
  res = (~this == FileReadString(f));
  
  class=class="str">"cmt">// If yes, then load the class="kw">virtual positions(orders) of the strategy
  if(res) {
    FOREACH(m_orders, res &= m_orders[i].Load(f));
  }
  class="kw">return res;
}
把断点续跑交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到 EA 虚拟挂单触发与状态落盘的可视痕迹,你只需核对策略是否按预期续跑。

常见问题

触发时只需改对象类型属性,不必跨类销毁再新建,减少多币种并行下的额外开销与出错面。
可以,小布盯盘对应品种页已内置虚拟订单与仓位的可视化诊断,便于快速定位未触发或到期残留的挂单。
它引入到期关闭原因,分时报价需单独判到期,而止损止盈水平对未触发挂单不应生效。
至少含各虚拟挂单类型、开价、到期、触发状态及对应仓位上下文,否则重启后易重复开单。
虚拟仓位依市价自动定开价,挂单则需显式指定触发价,否则无法判断价位触发条件。