价格离散性:为什么限价单总差一点不触发
Tick 跳跃、点差与滑点,如何让你的「精确触发」永远差一步
一、价格不是连续的:最小报价单位
报价由最小变动单位 Point(_Point)和 TickSize 决定。例如 EURUSD 的 _Point = 0.00001、TickSize = 0.00001;而某些品种 TickSize 可能大于 Point(如部分指数每跳 0.1 点)。挂单价格必须落在这些离散网格上,否则券商会拒绝或自动对齐,导致你以为挂成功了、实际却没挂上。
Digits(报价小数位)只是显示精度,真正约束挂单的是 TickSize,二者在非常规品种上可能不一致。下单前永远以 TickSize 为基准对齐,而不是只看几位小数。
二、为什么限价单要对齐网格
用 OrderSend 下 Limit/Stop 单时,价格若不在合法网格上,MT5 会返回无效价格错误(ERR_INVALID_PRICE)。正确做法是先用 SymbolInfoInteger(_Symbol, SYMBOL_TRADE_TICK_SIZE) 取出步长,再用归一化函数对齐,再下单。这一步是 EA 稳定性里最容易踩、也最该前置处理的细节。
double tick = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_TICK_SIZE);
double PriceAlign(double p)
{
double v = MathRound(p / tick) * tick;
return NormalizeDouble(v, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
}
// 在 Ask 下方 10 个 tick 挂 BuyLimit
double bl = PriceAlign(SymbolInfoDouble(_Symbol, SYMBOL_ASK) - 10 * tick);
三、成交量步长同样离散
手数不是任意值:SYMBOL_VOLUME_STEP 规定最小增减量(常见 0.01 手),SYMBOL_VOLUME_MIN / SYMBOL_VOLUME_MAX 规定上下限。下单位手数必须按步长归一化并夹在范围内,否则同样报非法手数。很多「部分平仓失败」也是因为剩余手数低于 MIN 或不符合 STEP。
四、市价单在跳空时的危险
重大数据或休市后开盘常出现跳空,市价单(Market)会按当时最优可成交价成交,滑点可能远超预期。限价单(Limit)则只在你指定的价格或更优成交,能在跳空时保护你——但也可能因此不成交,错失行情。两者没有绝对优劣,取决于你更怕滑点还是更怕踏空。
五、StopLimit:限价止损单
普通 Stop 单触发后变市价单;StopLimit 触发后变 Limit 单,只在触发价到限价之间成交,避免跳空滑点,但可能不成交。适合对成交价格敏感、不愿接受滑点的场景,例如重要支撑跌破后的反手,不想在跳空缺口底部被迫接刀。
使用 StopLimit 要设好「触发价」与「限价」的偏离:偏离太小容易不触发,太大又失去保护意义,需结合品种波动率和点差实测确定。
六、Exchange 与 OTC 的点差结构差异
外汇(OTC)是做市商报价,点差随流动性浮动;而交易所品种(如部分股票、期货)是撮合成交,价格是离散挂单。理解你交易的市场结构,才能正确设置偏离(deviation)参数——OTC 的滑点容忍要留足,撮合市场的限价则要严格对齐档位。
七、滑点的量化与承受
滑点不是抽象概念,它直接吃掉收益。可用成交价与预期价的差 × 手数 × 每点价值算出每次滑点的金钱成本,再汇总到回测里看它占总盈利的比例。若占比过高,说明策略对成交太敏感,必须降频或降仓。
// 估算单笔滑点成本(以 tick 计)
double slippageCost(double dealtPrice, double expectedPrice, double lots, double pointVal)
{
double slipTicks = MathAbs(dealtPrice - expectedPrice) /
SymbolInfoInteger(_Symbol, SYMBOL_TRADE_TICK_SIZE);
return slipTicks * lots * pointVal; // 近似金钱成本
}
八、实战下单检查清单
- 下单前对齐价格到 TickSize 网格
- 对齐手数到 VOLUME_STEP,并夹在 MIN~MAX 之间
- 限价单留出合理偏离,避免永不触发
- 跳空时段优先 Limit / StopLimit 而非 Market
- 用 GetLastError() 记录拒单原因并分类处理