一、为什么订单管理函数是EA开发的核心
订单管理函数是任何智能交易系统(EA)中最关键的组成部分。它们根据策略生成的信号执行实际交易。如果不掌握OrderSend、OrderModify和OrderSelect,你的EA就无法将市场分析转化为真实的交易动作。这些函数连接着你的交易逻辑和经纪商执行系统。
二、订单管理函数完整速查表
| 函数名 | 功能说明 | 返回值 | 核心参数 |
|--------|----------|--------|----------|
| OrderSend() | 开立市价单或挂单 | 票据号(int)或-1 | symbol, cmd, volume, price, slippage, stoploss, takeprofit, comment, magic, expiration |
| OrderModify() | 修改止损、止盈或挂单价格 | bool (true/false) | ticket, price, stoploss, takeprofit, expiration |
| OrderSelect() | 选择订单进行后续操作 | bool (true/false) | index, select, pool |
三、OrderSelect() - 获取订单信息的前置函数
在读取或修改任何订单之前,你必须先使用OrderSelect()选择该订单。这个函数将订单数据复制到程序环境中,供后续的OrderStopLoss()、OrderTakeProfit()、OrderOpenPrice()和OrderModify()等函数使用。
OrderSelect语法和参数
```mql4
bool OrderSelect(int index, int select, int pool = MODE_TRADES);
```
参数对照表:
| 参数 | 可选值 | 说明 |
|------|--------|------|
| index | 订单序号或票据号 | 取决于select参数 |
| select | SELECT_BY_POS (0) 或 SELECT_BY_TICKET (1) | 选择方式 |
| pool | MODE_TRADES (0) 或 MODE_HISTORY (1) | 订单池(SELECT_BY_TICKET时忽略) |
OrderSelect使用示例
```mql4
// 方式1:按持仓池中的序号选择
for(int i = 0; i < OrdersTotal(); i++) {
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
// 现在可以访问订单属性
Print("订单票据号:", OrderTicket());
Print("订单类型:", OrderType());
Print("开仓价:", OrderOpenPrice());
}
}
// 方式2:按票据号选择(最可靠)
int targetTicket = 12345;
if(OrderSelect(targetTicket, SELECT_BY_TICKET)) {
Print("找到订单#", targetTicket);
Print("盈利:", OrderProfit());
Print("止损:", OrderStopLoss());
} else {
Print("订单未找到,错误码:", GetLastError());
}
// 方式3:从历史池中选择(已关闭订单)
for(int i = 0; i < OrdersHistoryTotal(); i++) {
if(OrderSelect(i, SELECT_BY_POS, MODE_HISTORY)) {
Print("已关闭订单票据号:", OrderTicket());
Print("平仓价:", OrderClosePrice());
Print("平仓时间:", OrderCloseTime());
}
}
// 完整获取订单信息
void GetOrderInfo(int ticket) {
if(OrderSelect(ticket, SELECT_BY_TICKET)) {
Print("========== 订单信息 ==========");
Print("票据号:", OrderTicket());
Print("品种:", OrderSymbol());
Print("类型:", OrderType());
Print("手数:", OrderLots());
Print("开仓价:", OrderOpenPrice());
Print("开仓时间:", OrderOpenTime());
Print("止损:", OrderStopLoss());
Print("止盈:", OrderTakeProfit());
Print("盈利:", OrderProfit());
Print("隔夜利息:", OrderSwap());
Print("手续费:", OrderCommission());
Print("注释:", OrderComment());
Print("魔术号:", OrderMagicNumber());
if(OrderCloseTime() > 0) {
Print("平仓价:", OrderClosePrice());
Print("平仓时间:", OrderCloseTime());
}
Print("========================================");
}
}
// 最佳实践:访问订单数据前始终先调用OrderSelect
double GetOrderStopLoss(int ticket) {
if(OrderSelect(ticket, SELECT_BY_TICKET)) {
return OrderStopLoss();
}
return 0; // 选择失败返回0
}
```
四、OrderSend() - 开立订单和挂单
OrderSend是开立市价单(买入/卖出)和放置挂单的主要函数。它向服务器发送交易请求,成功时返回票据号。
OrderSend语法和参数
```mql4
int OrderSend(
string symbol, // 交易品种
int cmd, // 操作类型
double volume, // 手数
double price, // 订单价格
int slippage, // 最大滑点
double stoploss, // 止损价位
double takeprofit, // 止盈价位
string comment = NULL, // 订单注释
int magic = 0, // 魔术号
datetime expiration = 0, // 挂单有效期
color arrow_color = clrNONE // 图表箭头颜色
);
```
操作类型(cmd参数)
| 常量 | 值 | 说明 |
|------|-----|------|
| OP_BUY | 0 | 市价买入 |
| OP_SELL | 1 | 市价卖出 |
| OP_BUYLIMIT | 2 | 买入限价挂单 |
| OP_SELLLIMIT | 3 | 卖出限价挂单 |
| OP_BUYSTOP | 4 | 买入止损挂单 |
| OP_SELLSTOP | 5 | 卖出止损挂单 |
开立市价单 - 完整示例
```mql4
// 开立市价买入订单(带完整验证)
int OpenBuyOrder(double lotSize, double stopLossPoints, double takeProfitPoints, int magic) {
// 验证交易条件
if(!IsTradeAllowed()) {
Print("交易未开启");
return -1;
}
// 获取当前市场价格
double price = Ask;
double bid = Bid;
int digits = (int)MarketInfo(Symbol(), MODE_DIGITS);
int stopLevel = (int)MarketInfo(Symbol(), MODE_STOPLEVEL);
// 计算并规范化止损和止盈
double stopLoss = 0;
double takeProfit = 0;
if(stopLossPoints > 0) {
stopLoss = NormalizeDouble(price - stopLossPoints * Point(), digits);
// 检查最小距离要求
if((price - stopLoss) / Point() < stopLevel) {
Print("止损距离太近,最小要求:", stopLevel);
stopLoss = NormalizeDouble(price - stopLevel * Point(), digits);
}
}
if(takeProfitPoints > 0) {
takeProfit = NormalizeDouble(price + takeProfitPoints * Point(), digits);
}
// 验证价格规范化
if(price != NormalizeDouble(price, digits)) {
Print("价格未规范化");
return -1;
}
// 发送订单
int ticket = OrderSend(
Symbol(), // 品种
OP_BUY, // 操作类型
lotSize, // 手数
price, // 价格
30, // 滑点
stopLoss, // 止损
takeProfit, // 止盈
"EA买入订单", // 注释
magic, // 魔术号
0, // 有效期(市价单为0)
clrGreen // 箭头颜色
);
// 处理结果
if(ticket < 0) {
int error = GetLastError();
Print("OrderSend失败,错误码:", error);
HandleOrderError(error);
} else {
Print("买入订单已开立,票据号:", ticket);
Print("价格:", price, " 止损:", stopLoss, " 止盈:", takeProfit);
}
return ticket;
}
// 开立市价卖出订单
int OpenSellOrder(double lotSize, double stopLossPoints, double takeProfitPoints, int magic) {
double price = Bid;
int digits = (int)MarketInfo(Symbol(), MODE_DIGITS);
int stopLevel = (int)MarketInfo(Symbol(), MODE_STOPLEVEL);
double stopLoss = 0;
double takeProfit = 0;
if(stopLossPoints > 0) {
stopLoss = NormalizeDouble(price + stopLossPoints * Point(), digits);
if((stopLoss - price) / Point() < stopLevel) {
stopLoss = NormalizeDouble(price + stopLevel * Point(), digits);
}
}
if(takeProfitPoints > 0) {
takeProfit = NormalizeDouble(price - takeProfitPoints * Point(), digits);
}
int ticket = OrderSend(
Symbol(),
OP_SELL,
lotSize,
price,
30,
stopLoss,
takeProfit,
"EA卖出订单",
magic,
0,
clrRed
);
if(ticket < 0) {
Print("卖出订单失败,错误码:", GetLastError());
}
return ticket;
}
```
放置挂单
```mql4
// 放置买入限价单
int PlaceBuyLimit(double limitPrice, double lotSize, double stopLossPoints, double takeProfitPoints, int magic, datetime expiration = 0) {
int digits = (int)MarketInfo(Symbol(), MODE_DIGITS);
int stopLevel = (int)MarketInfo(Symbol(), MODE_STOPLEVEL);
// 验证挂单距离
double currentPrice = Ask;
if((currentPrice - limitPrice) / Point() < stopLevel) {
Print("限价太接近市价,最小要求:", stopLevel);
return -1;
}
double normalizedPrice = NormalizeDouble(limitPrice, digits);
double stopLoss = 0;
double takeProfit = 0;
if(stopLossPoints > 0) {
stopLoss = NormalizeDouble(limitPrice - stopLossPoints * Point(), digits);
}
if(takeProfitPoints > 0) {
takeProfit = NormalizeDouble(limitPrice + takeProfitPoints * Point(), digits);
}
int ticket = OrderSend(
Symbol(),
OP_BUYLIMIT,
lotSize,
normalizedPrice,
0, // 挂单忽略滑点
stopLoss,
takeProfit,
"买入限价EA",
magic,
expiration,
clrBlue
);
return ticket;
}
// 放置卖出止损单
int PlaceSellStop(double stopPrice, double lotSize, double stopLossPoints, double takeProfitPoints, int magic, datetime expiration = 0) {
int digits = (int)MarketInfo(Symbol(), MODE_DIGITS);
int stopLevel = (int)MarketInfo(Symbol(), MODE_STOPLEVEL);
if((stopPrice - Bid) / Point() < stopLevel) {
Print("止损价太接近市价");
return -1;
}
double normalizedPrice = NormalizeDouble(stopPrice, digits);
int ticket = OrderSend(
Symbol(),
OP_SELLSTOP,
lotSize,
normalizedPrice,
0,
0, // 止损可稍后设置
0, // 止盈可稍后设置
"卖出止损EA",
magic,
expiration,
clrOrange
);
return ticket;
}
```
五、OrderModify() - 修改止损、止盈和挂单
OrderModify用于修改现有订单的参数。对于市价单,可以修改止损和止盈价位;对于挂单,还可以修改开仓价格和有效期。
OrderModify语法
```mql4
bool OrderModify(
int ticket, // 订单票据号
double price, // 新开仓价(仅挂单)
double stoploss, // 新止损价位
double takeprofit, // 新止盈价位
datetime expiration, // 新有效期(仅挂单)
color arrow_color = clrNONE // 图表箭头颜色
);
```
移动止损实现(使用OrderModify)
```mql4
// 专业的移动止损函数
bool ApplyTrailingStop(int ticket, int trailingPoints) {
if(!OrderSelect(ticket, SELECT_BY_TICKET)) {
Print("OrderSelect失败,票据号:", ticket);
return false;
}
// 仅处理市价单
int orderType = OrderType();
if(orderType > OP_SELL) {
return false; // 跳过挂单
}
double openPrice = OrderOpenPrice();
double currentStop = OrderStopLoss();
double currentPrice = (orderType == OP_BUY) ? Bid : Ask;
double newStop = 0;
// 计算新止损价位
if(orderType == OP_BUY) {
double profitPoints = (currentPrice - openPrice) / Point();
if(profitPoints > trailingPoints) {
newStop = NormalizeDouble(currentPrice - trailingPoints * Point(), Digits);
}
} else { // OP_SELL
double profitPoints = (openPrice - currentPrice) / Point();
if(profitPoints > trailingPoints) {
newStop = NormalizeDouble(currentPrice + trailingPoints * Point(), Digits);
}
}
// 检查是否需要修改
if(newStop > 0 && newStop != currentStop) {
// 验证最小距离
int stopLevel = (int)MarketInfo(Symbol(), MODE_STOPLEVEL);
if(orderType == OP_BUY && (currentPrice - newStop) / Point() < stopLevel) {
newStop = NormalizeDouble(currentPrice - stopLevel * Point(), Digits);
}
if(orderType == OP_SELL && (newStop - currentPrice) / Point() < stopLevel) {
newStop = NormalizeDouble(currentPrice + stopLevel * Point(), Digits);
}
// 执行修改
bool result = OrderModify(
ticket,
OrderOpenPrice(),
newStop,
OrderTakeProfit(),
0, // 不修改有效期
clrYellow
);
if(result) {
Print("移动止损已更新,票据号:", ticket, " 新止损:", newStop);
} else {
Print("移动止损失败,错误码:", GetLastError());
}
return result;
}
return false;
}
// 同时修改止损和止盈
bool ModifyStopLossTakeProfit(int ticket, double newStopLoss, double newTakeProfit) {
if(!OrderSelect(ticket, SELECT_BY_TICKET)) {
return false;
}
double openPrice = OrderOpenPrice();
int digits = (int)MarketInfo(Symbol(), MODE_DIGITS);
int stopLevel = (int)MarketInfo(Symbol(), MODE_STOPLEVEL);
int orderType = OrderType();
// 验证止损距离
if(orderType == OP_BUY) {
if(newStopLoss > 0 && (openPrice - newStopLoss) / Point() < stopLevel) {
Print("止损太近,已调整为最小距离");
newStopLoss = NormalizeDouble(openPrice - stopLevel * Point(), digits);
}
} else {
if(newStopLoss > 0 && (newStopLoss - openPrice) / Point() < stopLevel) {
Print("止损太近,已调整为最小距离");
newStopLoss = NormalizeDouble(openPrice + stopLevel * Point(), digits);
}
}
bool result = OrderModify(
ticket,
openPrice,
newStopLoss,
newTakeProfit,
0,
clrBlue
);
if(!result) {
Print("修改失败,错误码:", GetLastError());
}
return result;
}
// 修改挂单价格
bool ModifyPendingOrderPrice(int ticket, double newPrice) {
if(!OrderSelect(ticket, SELECT_BY_TICKET)) {
return false;
}
// 验证是否为挂单
int orderType = OrderType();
if(orderType < OP_BUYLIMIT) {
Print("市价单不能修改价格");
return false;
}
double normalizedPrice = NormalizeDouble(newPrice, Digits);
int stopLevel = (int)MarketInfo(Symbol(), MODE_STOPLEVEL);
// 验证与市价的距离
if(orderType == OP_BUYLIMIT || orderType == OP_BUYSTOP) {
if((normalizedPrice - Ask) / Point() < stopLevel) {
Print("新价格太接近市价");
return false;
}
} else {
if((Bid - normalizedPrice) / Point() < stopLevel) {
Print("新价格太接近市价");
return false;
}
}
return OrderModify(
ticket,
normalizedPrice,
OrderStopLoss(),
OrderTakeProfit(),
OrderExpiration(),
clrPurple
);
}
```
六、完整订单管理系统(含错误处理)
```mql4
//+------------------------------------------------------------------+
//| 完整订单管理系统 |
//+------------------------------------------------------------------+
class OrderManager {
private:
int magicNumber;
void HandleError(int errorCode) {
switch(errorCode) {
case 1: Print("ERR_NO_RESULT - 无返回结果"); break;
case 2: Print("ERR_COMMON_ERROR - 通用错误"); break;
case 129: Print("ERR_INVALID_PRICE - 无效价格"); break;
case 130: Print("ERR_INVALID_STOPS - 无效止损(距离太近)"); break;
case 134: Print("ERR_NOT_ENOUGH_MONEY - 资金不足"); break;
case 138: Print("ERR_REQUOTE - 需要重新报价"); break;
case 145: Print("ERR_TRADE_MODIFY_DENIED - 修改被拒绝"); break;
case 146: Print("ERR_TRADE_CONTEXT_BUSY - 交易上下文繁忙"); break;
case 147: Print("ERR_TRADE_EXPIRATION_DENIED - 有效期被拒绝"); break;
case 148: Print("ERR_TRADE_TOO_MANY_ORDERS - 订单过多"); break;
case 149: Print("ERR_TRADE_HEDGE_PROHIBITED - 禁止对冲"); break;
case 4108: Print("ERR_TRADE_POSITION_NOT_FOUND - 持仓未找到"); break;
default: Print("未知错误:", errorCode);
}
}
public:
OrderManager(int magic) { magicNumber = magic; }
// 开立市价单(带重试机制)
int OpenMarketOrder(int orderType, double lotSize, double slPoints, double tpPoints) {
int maxRetries = 3;
int retryCount = 0;
while(retryCount < maxRetries) {
RefreshRates(); // 更新市场价格
double price = (orderType == OP_BUY) ? Ask : Bid;
int digits = (int)MarketInfo(Symbol(), MODE_DIGITS);
int stopLevel = (int)MarketInfo(Symbol(), MODE_STOPLEVEL);
double sl = 0, tp = 0;
if(slPoints > 0) {
if(orderType == OP_BUY) {
sl = NormalizeDouble(price - MathMax(slPoints, stopLevel) * Point(), digits);
} else {
sl = NormalizeDouble(price + MathMax(slPoints, stopLevel) * Point(), digits);
}
}
if(tpPoints > 0) {
if(orderType == OP_BUY) {
tp = NormalizeDouble(price + tpPoints * Point(), digits);
} else {
tp = NormalizeDouble(price - tpPoints * Point(), digits);
}
}
int ticket = OrderSend(
Symbol(), orderType, lotSize, price, 30,
sl, tp, "OrderMgr", magicNumber, 0, clrNONE
);
if(ticket > 0) {
Print("订单已开立:", ticket);
return ticket;
}
int error = GetLastError();
HandleError(error);
// 致命错误不重试
if(error == 134 || error == 148) break;
retryCount++;
Sleep(1000 * retryCount);
}
return -1;
}
// 关闭市价单
bool CloseOrder(int ticket) {
if(!OrderSelect(ticket, SELECT_BY_TICKET)) return false;
double closePrice = (OrderType() == OP_BUY) ? Bid : Ask;
bool result = OrderClose(ticket, OrderLots(), closePrice, 30, clrRed);
if(!result) {
Print("关闭失败,错误码:", GetLastError());
}
return result;
}
// 按魔术号关闭所有订单
int CloseAllOrders() {
int closed = 0;
for(int i = OrdersTotal() - 1; i >= 0; i--) {
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
if(OrderMagicNumber() == magicNumber && OrderSymbol() == Symbol()) {
if(CloseOrder(OrderTicket())) {
closed++;
}
}
}
}
Print("已关闭", closed, "个订单");
return closed;
}
// 获取所有开仓订单的票据号
int[] GetOpenTickets() {
int tickets[];
ArrayResize(tickets, 0);
for(int i = 0; i < OrdersTotal(); i++) {
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
if(OrderMagicNumber() == magicNumber && OrderSymbol() == Symbol()) {
int size = ArraySize(tickets);
ArrayResize(tickets, size + 1);
tickets[size] = OrderTicket();
}
}
}
return tickets;
}
};
```
七、常见OrderSend错误码及解决方案
| 错误码 | 说明 | 解决方法 |
|--------|------|----------|
| 129 | 无效价格 | 始终使用NormalizeDouble()规范化价格,使用MarketInfo(MODE_DIGITS)获取小数位数 |
| 130 | 无效止损 | 检查MODE_STOPLEVEL,确保止损距离大于最小值 |
| 134 | 资金不足 | 减少手数或检查可用保证金 |
| 138 | 需要重新报价 | 增加滑点参数或刷新报价 |
| 146 | 交易上下文繁忙 | 使用Sleep()延迟后重试,或采用单线程方式 |
| 147 | 有效期被拒绝 | 如果不支持挂单有效期,设置expiration=0 |
| 148 | 订单过多 | 先关闭部分订单再开新单 |
八、订单管理最佳实践清单
参考来源:
9. 下一步
第12篇将讲解订单选择与遍历技巧 – 循环遍历订单的完整指南、按品种/魔术号/类型筛选订单、批量处理的实用方法。