在前面已经接触过 OrderSelect() 函数。本节我们将探讨如何结合使用 OrderSelect() 函数与 MQL 中的循环运算符(for 循环和 while 循环),来实现对订单池(即当前所有持仓订单和挂单的集合)的遍历操作,以便检索和管理订单信息。掌握这种遍历方法是实现许多高级订单管理功能的基础,例如批量平仓、追踪止损、统计持仓订单数量等等。
for 循环运算符
for 循环通常用于执行已知或预定次数的代码块重复。其基本结构包含三个部分,用分号隔开,并写在一对圆括号内:for (初始化表达式; 循环条件; 迭代表达式)。
- 初始化表达式: 在循环开始前仅执行一次,通常用于声明并初始化一个循环计数器变量。
- 循环条件 : 每次循环迭代开始前进行判断。如果条件为
true,则执行循环体{}内的代码;如果为false,则跳出for循环,执行循环后面的代码。 - 迭代表达式: 在每次循环体执行完毕后执行一次,通常用于修改计数器变量的值(递增或递减),为下一次条件判断做准备。
// 这个 for 循环将执行 3 次
for (int counter = 1; counter <= 3; counter++) // 初始化 counter=1; 条件 counter<=3; 每次循环后 counter+1
{
// 这里是需要重复执行的代码块
Print("For 循环次数: ", counter);
}
// 循环结束后,程序继续执行这里的代码
int counter = 1: 声明并初始化计数器counter为 1。counter <= 3: 循环条件。只要counter小于或等于 3,就继续循环。counter++: 每次循环体执行结束后,将counter的值加 1。(counter--表示减 1,counter = counter + 2表示加 2,等等)。- 注意:
for语句圆括号内的三个表达式之间用分号分隔,但第三个表达式(迭代表达式)后面没有分号。
while 循环运算符
while 循环提供了一种基于条件进行循环的更简单直接的方式。它尤其适用于不确定循环需要执行多少次,只知道当某个条件满足时就应继续循环的情况。其基本结构是 while (循环条件)。
循环条件: 在每次循环迭代开始前进行判断。如果条件为 true,则执行循环体 {} 内的代码;如果为 false,则跳出 while 循环。
bool needToContinue = true; // 用一个布尔变量控制循环
int loopCounter = 0;
while (needToContinue == true) // 当 needToContinue 为 true 时,就一直循环
{
// 循环体代码
Print("While 循环执行中...");
loopCounter++;
// !!! 关键:循环体内必须包含能最终改变条件的逻辑 !!!
if (loopCounter >= 5) // 例如,当循环执行了 5 次后
{
needToContinue = false; // 将条件变量置为 false,下一次循环判断时就会终止
}
}
- 如果
while循环的条件永远为true(例如,在上面的例子中如果忘记了if (loopCounter >= 5)这个判断和needToContinue = false;这行代码),程序将陷入无限循环,这会导致 EA 停止响应或耗尽系统资源。因此,务必确保循环体内部存在能最终使循环条件变为false的出口逻辑。
while 循环也可以用来模拟 for 循环的行为(虽然不太常见),只需在循环外部初始化计数器,并在循环体内部手动更新计数器即可:
int counter = 1; // 在循环外初始化计数器
while (counter <= 3) // 设置循环条件
{
// 循环体代码...
Print("While 模拟 For 循环次数: ", counter);
counter++; // 在循环体内部手动递增计数器
}
这段 while 循环代码的执行效果与前面介绍的 for 循环完全相同,也会执行三次。
当循环次数已知或容易计算时,通常使用 for 循环更方便、结构更清晰。当循环的持续依赖于某个动态变化的条件,而事先不确定具体会执行多少次时,使用 while 循环更符合逻辑。在下一节,我们将看到如何使用 for 循环来遍历订单池。
订单遍历循环
现在我们将学习如何使用 for 循环来遍历(即逐个访问)当前账户中所有未平仓的订单和挂单(统称为订单池)。这是进行批量订单管理(如批量平仓、修改止损、统计信息等)的基础。
以下是遍历订单池的标准代码结构:
// 使用 for 循环遍历所有当前订单 (包括持仓和挂单)
int totalOrders = OrdersTotal(); // 先获取当前订单总数
// 从第一个订单 (索引 0) 开始,到最后一个订单 (索引 totalOrders - 1) 结束
// 注意:循环条件是 i < totalOrders (等价于 i <= totalOrders - 1)
for (int i = 0; i < totalOrders; i++)
{
// 按位置索引 i 选择订单 (默认在 MODE_TRADES 池中选择)
if (OrderSelect(i, SELECT_BY_POS) == true) // 必须检查 OrderSelect 是否成功选中
{
// --- 订单成功选中 ---
// 在这里可以调用 Order*() 函数获取该订单的信息
// 例如:
// int ticket = OrderTicket();
// string symbol = OrderSymbol();
// int type = OrderType();
// ...
// 根据需要评估条件并处理该订单 (例如打印信息、判断是否关闭等)
// if (OrderSymbol() == "EURUSD" && OrderMagicNumber() == 12345) { ... }
}
// else { Print("OrderSelect 按位置 ", i, " 失败!"); } // 可选:处理选择失败的情况
}
// 循环结束,已遍历完当前时刻的所有订单
代码解释:
-
for (int i = 0; i < OrdersTotal(); i++):int i = 0: 在循环开始前,声明并初始化一个整数计数器i为 0。这个i将用作订单在池中的位置索引 (position index)。i < OrdersTotal(): 这是循环得以继续执行的条件。OrdersTotal()是一个 MQL 内置函数,它返回当前活动订单池中订单的总数量。只要计数器i的值严格小于订单总数,循环就会执行。i++: 在每次循环体{}内的代码执行完毕之后,计数器i的值会自动加 1。
-
订单池与 0 索引:
- 订单池可以看作是一个包含当前所有活动订单(持仓单+挂单)的列表。
- 这个列表的索引是从 0 开始的。列表中的第一个订单(通常是下单时间最早的那个)索引为 0,第二个订单索引为 1,以此类推,最后一个(最新下的)订单索引为
OrdersTotal() - 1。 - 理解循环条件: 如果
OrdersTotal()返回 3(表示有 3 个订单),它们的索引分别是 0, 1, 2。循环需要覆盖这三个索引。当i从 0 增加到 2 时,i < 3条件都为真,循环执行。当i增加到 3 时,3 < 3条件变为假,循环终止。因此i < OrdersTotal()或i <= OrdersTotal() - 1都是正确的循环条件。
-
OrderSelect(i, SELECT_BY_POS):- 这是循环体内的关键操作。它尝试选中订单池中位置索引为
i的那个订单。 - 第二个参数
SELECT_BY_POS表明我们是根据位置而非订单号来选择。 - 返回值检查:
OrderSelect()可能因各种原因(如索引越界,虽然在此循环中理论上不会发生)而失败并返回false。因此,必须检查其返回值,只有在返回true时,后续的Order*()函数才能获取到正确的订单信息。
- 这是循环体内的关键操作。它尝试选中订单池中位置索引为
-
循环流程: 这个
for循环会依次将i设为 0, 1, 2, …, 直到OrdersTotal() - 1,并在每次迭代中尝试选中对应位置的订单,让您有机会在循环体内对每个订单进行检查和处理。
关于订单关闭与索引序列
- 当未结订单池中的一个订单被关闭时,池中任何较新的订单(即索引比被关闭订单大的订单)的订单索引将被递减(自动向前移动填补空位)。
- 示例: 如果有订单 0, 1, 2。当订单 0 被关闭后,原来的订单 1 会变成新的索引 0,原来的订单 2 会变成新的索引 1。
- 潜在问题: 如果您在从 0 开始递增(从前往后)遍历的过程中,关闭或删除了某个订单(例如,在
i=1时关闭了当前选中的订单),那么原本在i=2位置的订单会立刻移动到i=1的位置。但此时循环的下一次迭代i会变成 2,您就会错过处理那个刚刚移动到i=1位置的订单,导致漏单。 - 为了避免这个问题,当需要在循环内部执行关闭或删除订单的操作时,最安全可靠的做法是从后往前遍历订单池。即,循环应写为:
for (int i = OrdersTotal() - 1; i >= 0; i--) // 从最后一个订单往前遍历到索引 0 { if (OrderSelect(i, SELECT_BY_POS)) { // 在这里关闭或删除订单是安全的 // 因为即使删除了 i,也不会影响接下来要处理的 i-1, i-2 ... 这些订单的索引 } }
我们将在后续讨论批量平仓等场景时详细应用这种倒序遍历的方法。目前,只需理解递增遍历在内部删单时存在的问题即可。


