Summary: 深入MQL4 OrderSend函数,涵盖滑点处理、错误捕获、订单选择陷阱。附带完整EA骨架模板,助力开发稳健的智能交易系统。




MQL4 OrderSend深度剖析:滑点、错误处理与隐蔽陷阱



写过EA的人都知道,OrderSend()这个函数是通往市场的门禁卡。它是你的策略逻辑与真实执行之间的桥梁。我见过无数EA不是因为策略本身差劲而失败,而是因为OrderSend的逻辑写得不够健壮。这篇不是新手教程,我要深入那些曾让我吃尽苦头的执行细节。

函数原型



我们先看看它的调用方式。

``mql4
int OrderSend(
string symbol, // 品种
int cmd, // 订单类型 (OP_BUY, OP_SELL, 等)
double volume, // 手数
double price, // 价格
int slippage, // 最大滑点(点数)
double stoploss, // 止损价
double takeprofit, // 止盈价
string comment = NULL, // 注释
int magic = 0, // 魔术号
datetime expiration = 0, // 订单有效期(挂单)
color arrow_color = clrNONE // 图表上箭头颜色
);
`

看着很直观对吧?真正的魔鬼藏在细节里,尤其是
slipage(滑点)和stoploss/takeprofit(止损/止盈)价格的设置方式。

滑点设置的金科玉律



我见过最普遍的一个错误,就是把
slippage这个参数设成一个固定值。很多人就直接写个35就完事了。但如果市场正快速波动,或者正好有数据发布,会发生什么?订单被拒,你的EA逻辑崩溃,然后你就会错过这笔交易。

我的做法是,基于当前的点差来计算一个动态滑点。用静态滑点就像用个定时器,而不是看钟表,它根本无法适应实时变化。

`mql4
int slippage = (int)MathMax(MarketInfo(Symbol(), MODE_SPREAD) * 2, 20);
`

这能确保你的滑点至少是当前点差的两倍,或者最少20个点(取两者中较大的那个)。这能极大地降低因价格变动导致订单被拒的概率。我是在一个剥头皮EA在高波动期间实盘连续失败好几周之后,才深刻体会到这一点的。回测表现无可挑剔,实盘却一塌糊涂。就这一个改动,状况就完全扭转了。

全面的错误处理



很多EA暴露出来的另一个弱点是错误处理。大部分代码就是调用
OrderSend(),然后假设它肯定能成。一旦失败,EA常常就直接罢工了。这就是个灾难的开端。

我的做法是构建一套健壮的重复尝试和降级处理机制。我会用一个
while循环来尝试有限次数。

`mql4
bool PlaceOrder(int cmd, double volume, double price, int slippage, double stoploss, double takeprofit) {
int ticket = -1;
int attempts = 0;
int maxAttempts = 3;

while (attempts < maxAttempts && ticket < 0) {
attempts++;
ticket = OrderSend(Symbol(), cmd, volume, price, slippage, stoploss, takeprofit, "EA Order", magic, 0, clrNONE);
if (ticket < 0) {
int error = GetLastError();
Print("OrderSend failed on attempt ", attempts, ". Error: ", error, " - ", ErrorDescription(error));

// 如果价格变动,就刷新价格
if (error == ERR_PRICE_CHANGED || error == ERR_SERVER_BUSY || error == ERR_NO_CONNECTION) {
RefreshRates();
price = (cmd == OP_BUY) ? Ask : Bid;
Print("Price refreshed. New price: ", price);
Sleep(100);
} else if (error == ERR_MARKET_CLOSED) {
Print("Market is closed. Exiting.");
break; // 收盘就没必要再试了
} else {
// 对于其他错误,等一会儿再重试
Sleep(500);
}
}
}

if (ticket < 0) {
Print("Failed to place order after ", attempts, " attempts.");
return false;
}
return true;
}
`

关于错误处理的个人心得: 我不只是把错误代码打印出来。我用了一个函数把错误代码转成人类可读的字符串。当你在几千条日志里翻查的时候,这会让调试过程轻松一大截。我写了一个自定义的
ErrorDescription()函数来把标准MQL4错误码映射成文本。这是个投入小但回报巨大的工作。

隐藏陷阱:OrderSelect与魔术号



这里有个早期的坑。你调用
OrderSend()后,会得到一个订单号。没问题。但如果你正在用不同的魔术号管理着多个EA,你可能觉得你的OrdersTotal()循环是安全的。但如果没正确按魔术号和品种过滤,你就有可能修改或关闭了别人的订单。

`mql4
void CloseAllOpenOrders() {
for (int i = OrdersTotal() - 1; i >= 0; i--) {
if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
if (OrderMagicNumber() == magic && OrderSymbol() == Symbol()) {
if (OrderType() == OP_BUY) {
// 平仓
} else if (OrderType() == OP_SELL) {
// 平仓
}
}
}
}
}
`

更隐蔽的一点是:下单后,你需要确保能再次找到它。我习惯在
OrderSend()调用后,立即把订单选中,以确认它正确建立,并把订单号存下来备用。

一个完整的EA骨架



下面是一个融合了这些实践的基础EA框架。

`mql4
//+------------------------------------------------------------------+
//| OrderSendSkeleton.mq4 |
//| Copyright 2024, FXEAR.com |
//| https://www.fxear.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, FXEAR.com"
#property link "https://www.fxear.com"
#property version "1.00"
#property strict

input int MagicNumber = 12345;
input double LotSize = 0.1;
input int StopLossPoints = 200;
input int TakeProfitPoints = 400;

int magic;

//+------------------------------------------------------------------+
//| 初始化函数 |
//+------------------------------------------------------------------+
int OnInit() {
magic = MagicNumber;
return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Tick函数 |
//+------------------------------------------------------------------+
void OnTick() {
// 每根K线只下一次单
static datetime lastBarTime = 0;
if (lastBarTime == Time[0]) return;
lastBarTime = Time[0];

// 简单的入场逻辑:价格高于MA20则做多,低于则做空
double ma20 = iMA(Symbol(), 0, 20, 0, MODE_SMA, PRICE_CLOSE, 1);

if (Ask > ma20 && !IsOrderExists(OP_BUY)) {
double price = Ask;
double sl = price - StopLossPoints Point;
double tp = price + TakeProfitPoints
Point;
int slippage = (int)MathMax(MarketInfo(Symbol(), MODE_SPREAD) 2, 20);

bool success = PlaceOrder(OP_BUY, LotSize, price, slippage, sl, tp);
if (success) Print("Buy order placed successfully.");
}

if (Bid < ma20 && !IsOrderExists(OP_SELL)) {
double price = Bid;
double sl = price + StopLossPoints
Point;
double tp = price - TakeProfitPoints Point;
int slippage = (int)MathMax(MarketInfo(Symbol(), MODE_SPREAD)
2, 20);

bool success = PlaceOrder(OP_SELL, LotSize, price, slippage, sl, tp);
if (success) Print("Sell order placed successfully.");
}
}

//+------------------------------------------------------------------+
//| 下单函数 |
//+------------------------------------------------------------------+
bool PlaceOrder(int cmd, double volume, double price, int slippage, double stoploss, double takeprofit) {
int ticket = -1;
int attempts = 0;
int maxAttempts = 3;

while (attempts < maxAttempts && ticket < 0) {
attempts++;
ticket = OrderSend(Symbol(), cmd, volume, price, slippage, stoploss, takeprofit, "EA Order", magic, 0, clrNONE);
if (ticket < 0) {
int error = GetLastError();
Print("OrderSend failed on attempt ", attempts, ". Error: ", error);

if (error == ERR_PRICE_CHANGED || error == ERR_SERVER_BUSY || error == ERR_NO_CONNECTION) {
RefreshRates();
price = (cmd == OP_BUY) ? Ask : Bid;
Sleep(100);
} else {
Sleep(500);
}
}
}

if (ticket < 0) {
Print("Failed to place order after ", attempts, " attempts.");
return false;
}
return true;
}

//+------------------------------------------------------------------+
//| 检查是否存在某类型订单 |
//+------------------------------------------------------------------+
bool IsOrderExists(int type) {
for (int i = OrdersTotal() - 1; i >= 0; i--) {
if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
if (OrderSymbol() == Symbol() && OrderMagicNumber() == magic && OrderType() == type) {
return true;
}
}
}
return false;
}
`

关于滑点的新视角



市面上关于MQL4回测的资料大多假设滑点是恒定的。这是一个巨大的简化。我的经验是,回测里的滑点很少是真实滑点。一个常被忽略的细节是,回测引擎可能用的是“完美”执行模型,而真实市场的滑点是流动性和波动性的函数。

一个可以在前进测试中模拟可变滑点的简单公式是:

滑点 = 基础滑点 + (波动飙升因子 (ATR / 点差))

这个公式把平均真实波幅和点差纳入了考量。这是为了模拟波动性高的时候,点差扩大,滑点也随之增加的现象。这是我通过反复试验摸索出来的一个公式,在标准MQL4文档里找不到。它能让你的EA对真实市场条件更有韧性。

权威来源的引述



“市场冲击和执行成本,或许是区分策略在回测中盈利与否、在实战中亏损与否的最重要因素。” – Robert Pardo, 《交易策略的评估与优化》, 2008年。*


这句话完美地诠释了为什么我们需要给予滑点足够的重视。

免责声明: 金融市场交易具有高风险。过往表现不代表未来结果。本文提供的代码仅用于教育目的,在用于真实账户前,应在模拟环境中进行充分测试。

本文首发于FXEAR.com,原创内容,未经授权禁止转载。
``