封装市价买单函数 (OpenBuyOrder()
)
现在我们来创建一个用于开立市价买单的可重用函数。在设计这个函数时,我们与之前直接在 OnTick()
函数中编写的代码有所不同,主要出于以下考虑:
- 这个下单函数只负责发送开仓指令。它不包含平掉反向订单的逻辑(这应由调用它的策略逻辑处理),也不包含计算或修改止损/止盈的逻辑。
- 将 SL/TP 的计算和设置(特别是对于市价单需要通过
OrderModify
实现)分离出去,使得这个OpenBuyOrder
函数更加通用和灵活,不与任何特定的 SL/TP 计算方法绑定。后续我们将创建专门的函数来处理订单修改。 - 兼容性: 此函数发送的市价单不包含 SL/TP(将其设为 0),符合ECN/STP经纪商的要求。SL/TP 需要在调用此函数成功后,再通过
OrderModify
添加。 - 函数通过参数
argSymbol
接收交易品种,并使用MarketInfo()
获取价格,使其不仅限于当前图表,可以方便地用于跨品种交易。 - 内置包含了标准的交易环境和下单失败后的错误处理(获取错误码、描述并记录日志)。
- 明确返回值: 函数返回开仓成功后的订单号 (Ticket),或者在失败时返回 -1,便于调用者判断结果。
函数定义:
/**
* @brief 开立一个市价买单 (Buy Market Order), 不包含 SL/TP (用于 ECN 兼容)。
* @param argSymbol 交易品种名称 (e.g., "EURUSD", 或 Symbol())
* @param argLotSize 已验证和规范化的交易手数
* @param argSlippagePoints 已计算好的允许滑点 (单位: points)
* @param argMagicNumber 订单的魔术数字
* @param argComment 订单注释 (可选, 默认值为 "Buy Order")
* @return int 成功则返回订单号 (Ticket), 失败则返回 -1
*/
int OpenBuyOrder(string argSymbol, double argLotSize, int argSlippagePoints,
int argMagicNumber, string argComment = "Buy Order") // Comment 参数有默认值
{
// 1. 等待交易环境空闲 (假设 WaitForTradeContext 是封装好的函数)
if (!WaitForTradeContext()) return(-1);
// 2. 获取最新的 Ask 价格 (使用 MarketInfo)
RefreshRates(); // 确保 MarketInfo 获取的是最新价
double price = MarketInfo(argSymbol, MODE_ASK);
if (price <= 0) { // 简单价格验证
Print("无法获取品种 ", argSymbol, " 的 Ask 价格!");
return(-1);
}
// 3. 发送下单指令 (SL=0, TP=0, Expiration=0)
int ticket = OrderSend(argSymbol, OP_BUY, argLotSize, price,
argSlippagePoints, 0, 0, // 止损和止盈设为 0
argComment, argMagicNumber, 0, Green); // 无到期时间, 绿色箭头
// 4. 内置错误处理
if (ticket == -1)
{
// 调用封装的错误处理函数记录详情
HandleTradeError(StringFormat("开立市价买单 (%s)", argSymbol), GetLastError());
/*
// 或者直接处理:
int errorCode = GetLastError();
string errorDesc = ErrorDescription(errorCode); // 需要 #include <stdlib.mqh>
Alert("开买单失败: Error ", errorCode, ": ", errorDesc);
Print("OrderSend(Buy) failed: ", errorCode, " ", errorDesc,
" Symbol=", argSymbol, " Lots=", argLotSize, " Price=", price, " Slippage=", argSlippagePoints);
*/
}
// 5. 返回操作结果 (订单号或 -1)
return(ticket);
}
- 函数解释:
- 函数接收必要的参数,包括
argSymbol
(不再局限于当前图表)、argLotSize
(假设已验证)、argSlippagePoints
(已转换为 points)、argMagicNumber
以及带有默认值的argComment
。 - 在
OrderSend
调用中,使用MarketInfo(argSymbol, MODE_ASK)
获取指定品种的 Ask 价,而不是预定义变量Ask
。 - 止损 (
StopLoss
) 和止盈 (TakeProfit
) 参数被明确设置为 0。 - 包含了标准的错误处理逻辑块,如果
OrderSend
失败(返回 -1),则获取错误信息并进行报告。 - 最终返回
OrderSend
的结果(订单号或 -1)。
- 函数接收必要的参数,包括
- 卖单函数: 开立市价卖单的函数 (
OpenSellOrder
) 与此类似,主要区别是使用OP_SELL
和MarketInfo(argSymbol, MODE_BID)
获取开仓价格。
通过将下单逻辑封装成这样的函数,主程序的代码会变得更加简洁和专注于策略逻辑。
封装挂单下单函数
对于挂单,其下单逻辑与市价单有所不同,主要体现在可以直接在 OrderSend()
函数中设置止损 (SL)、止盈 (TP) 和订单到期时间。因此,封装挂单操作的函数会接收更多的参数。
设计思路:
- 包含 SL/TP/Expiration: 与市价单函数不同,挂单函数设计为直接接收并设置 SL, TP, 和 Expiration 参数。这符合大多数经纪商对挂单操作的支持,无需后续的
OrderModify
步骤来添加这些属性。 - 参数假设: 此函数假设调用者已经预先计算并验证了所有传入价格参数的有效性,包括:
- 挂单价格 (
argPendingPrice
) 满足距离当前市价的MODE_STOPLEVEL
要求。 - 止损价格 (
argStopLoss
) 和止盈价格 (argTakeProfit
) 满足相对于argPendingPrice
的MODE_STOPLEVEL
要求。 函数本身不包含这些价格验证逻辑,应在调用前完成。
- 挂单价格 (
- 默认参数: 为
argExpiration
和argComment
设置了默认值,提高了函数调用的灵活性。 - 内置错误处理: 同样包含交易环境和下单失败后的错误处理及日志记录。
封装买入止损挂单 (OpenBuyStopOrder
)
/**
* @brief 开立一个买入止损挂单 (Buy Stop Pending Order), 可直接设置 SL/TP 和到期时间。
* @param argSymbol 交易品种名称
* @param argLotSize 已验证和规范化的交易手数
* @param argPendingPrice 已验证的挂单触发价格 (必须 > Ask + StopLevel*Point)
* @param argStopLoss 已验证的止损价格 (必须 <= argPendingPrice - StopLevel*Point)
* @param argTakeProfit 已验证的止盈价格 (必须 >= argPendingPrice + StopLevel*Point, 或为 0)
* @param argSlippagePoints 已计算好的允许滑点 (points) - 对挂单成交时可能有用
* @param argMagicNumber 订单的魔术数字
* @param argExpiration 订单到期时间 (可选, datetime 类型, 0 表示永不过期)
* @param argComment 订单注释 (可选, 默认值为 "Buy Stop Order")
* @return int 成功则返回订单号 (Ticket), 失败则返回 -1
*/
int OpenBuyStopOrder(string argSymbol, double argLotSize, double argPendingPrice,
double argStopLoss, double argTakeProfit, int argSlippagePoints,
int argMagicNumber, datetime argExpiration = 0, // Expiration 默认值为 0
string argComment = "Buy Stop Order") // Comment 有默认值
{
// 1. 等待交易环境空闲
if (!WaitForTradeContext()) return(-1); // 假设 WaitForTradeContext 是封装好的等待函数
// 2. 发送挂单指令 (类型 OP_BUYSTOP, 直接包含 SL/TP/Expiration)
int ticket = OrderSend(argSymbol, OP_BUYSTOP, argLotSize, argPendingPrice,
argSlippagePoints, argStopLoss, argTakeProfit, // 直接传入 SL/TP
argComment, argMagicNumber, argExpiration, Green); // 传入到期时间
// 3. 错误处理
if (ticket == -1)
{
// 调用封装的错误处理函数,记录详细信息
HandleTradeError(StringFormat("设置买入止损挂单 (%s)", argSymbol), GetLastError());
/*
// 或者直接处理:
int errorCode = GetLastError();
string errorDesc = ErrorDescription(errorCode); // 需要 #include <stdlib.mqh>
Alert("设置 Buy Stop 失败: Error ", errorCode, ": ", errorDesc);
// 日志记录更详细参数
string logMessage = StringFormat(
"OrderSend(BuyStop) failed: Error %d: %s. Symbol=%s, Lots=%.2f, Price=%.5f, SL=%.5f, TP=%.5f, Exp=%s",
errorCode, errorDesc, argSymbol, argLotSize, argPendingPrice, argStopLoss, argTakeProfit,
TimeToStr(argExpiration, TIME_DATE|TIME_MINUTES) // 使用 TimeToStr 格式化时间
);
Print(logMessage);
*/
}
// 4. 返回订单号或 -1
return(ticket);
}
- 默认参数使用: 函数定义中
datetime argExpiration = 0
和string argComment = "Buy Stop Order"
指定了默认值。如果在调用该函数时,省略了这两个位于末尾的参数,MQL 会自动使用它们的默认值 (0 和 “Buy Stop Order”)。// 示例:调用时省略最后两个参数,将使用默认的到期时间(0)和注释("Buy Stop Order") // 假设其他变量已定义 int ticket = OpenBuyStopOrder(Symbol(), lotSizeToUse, pendingPrice, slPrice, tpPrice, g_slippagePoints, MagicNumber);
- 错误日志: 错误处理部分,使用
TimeToStr()
函数将datetime
类型的到期时间argExpiration
转换为易于阅读的字符串格式(例如 “YYYY.MM.DD HH:MM”)后记录到日志中。 - 其他挂单类型: 开立卖出止损 (
OP_SELLSTOP
)、买入限价 (OP_BUYLIMIT
) 和卖出限价 (OP_SELLLIMIT
) 订单的函数,其结构与OpenBuyStopOrder
函数完全相同,只需要修改OrderSend()
调用中的订单类型常量 (cmd
参数) 以及默认的注释字符串即可。这些函数的完整代码可以在原文的附录 D 中找到。
通过为不同类型的订单(市价单 vs 挂单)创建专门的、封装好的下单函数,可以使EA的主逻辑更加清晰,并提高了代码的模块化程度。