- EA 交易的主要事件:OnTick
- 基本原理和概念:订单、交易和仓位
- 交易操作类型
- 订单类型
- 按价格和数量划分的订单执行模式
- 挂单到期日期
- 期货订单的保证金计算方法:OrderCalcMargin
- 估算交易操作的利润:OrderCalcProfit
- MqlTradeRequest 结构体
- MqlTradeCheckResult 结构体
- 请求验证:OrderCheck
- 请求发送结果:MqlTradeResult 结构体
- 发送交易请求:OrderSend 和 OrderSendAsync
- 买入和卖出操作
- 修改仓位的止损和/或止盈水平
- 跟踪止损
- 平仓:全部和部分
- 反向平仓:全部和部分
- 挂单
- 修改挂单
- 删除挂单
- 获取活动订单列表
- 订单特性(现行和历史)
- 用于读取活动订单特性的函数
- 按特性选择订单
- 获取仓位列表
- 仓位特性
- 用于读取仓位特性的函数
- 交易特性
- 从历史中选择订单和交易
- 用于从历史中读取订单特性的函数
- 用于从历史中读取交易特性的函数
- 交易类型
- OnTradeTransaction 事件
- 同步和异步请求
- OnTrade 事件
- 监测交易环境变化
- 创建多交易品种 EA 交易
- EA 交易的优势和局限性
- 在 MQL 向导中创建 EA 交易
修改仓位的止损和/或止盈水平
MQL 程序可以改变未平仓仓位的保护性 Stop Loss 和 Take Profit 价格水平。TRADE_ACTION_SLTP 元素(在 ENUM_TRADE_REQUEST_ACTIONS 枚举中)就是用于这个目的,也就是在填充 MqlTradeRequest 结构体时,应在 action 字段写入 TRADE_ACTION_SLTP。
这是唯一的必填字段。是否需要填写其他字段是由账户操作模式 ENUM_ACCOUNT_MARGIN_MODE 决定的。在对冲账户上,应填写 symbol 字段,但可以省略仓位订单号。相反,在对冲账户上,必须注明 position 仓位订单号,但可以省略交易品种。这是由于不同类型账户的仓位标识方式的特殊性。在净额结算期间,每个交易品种只能有一个仓位。
为了统一代码,建议填写这两个字段(如果有相关信息)。
保护价格水平在 sl 和 tp 字段中设置。可以只测试其中一个字段。要删除保护水平,请为其赋零值。
下表总结了根据计数模式填写字段的要求。必填字段标有星号,可选字段标有加号。
字段 |
净额结算 |
对冲 |
---|---|---|
action |
* |
* |
symbol |
* |
+ |
position |
+ |
* |
sl |
+ |
+ |
tp |
+ |
+ |
为了执行修改保护水平的操作,我们在 MqlTradeRequestSync 结构体中引入了 adjust 方法的几个重载。
struct MqlTradeRequestSync: public MqlTradeRequest
|
正如我们上面看到的,根据环境的不同,修改只能通过订单号或仓位交易品种来完成。在前两个原型中考虑了这些选项。
此外,由于该结构体可能已经用于先前的请求,它可能已经填充了 position 和 symbols 字段。然后可以用最后一个原型调用该方法。
我们还没有展示这三个方法的实现,因为很明显它必须有一个包含发送请求的公共主体。这一部分被设计成一个私有的辅助方法 _adjust,带有一整套选项。此处其代码用一些不影响工作逻辑的缩写给出。
private:
|
我们按照上面的规则填充该结构的所有字段,调用前面说明的 setSymbol 和 setSLTP 方法,然后向服务器发送请求。结果为成功 (true) 或错误 (false) 状态。
每个重载的 adjust 方法分别为请求准备源参数。在有仓位订单号的情况下是这样做的。
public:
|
此处,使用内置的 PositionSelectByTicket 函数,我们可以检查在终端的交易环境中是否存在仓位及其选择,这是后续读取其特性(在本例中为交易品种 (PositionGetString(POSITION_SYMBOL)))所必需的。那么通用的变体就叫 adjust。
按交易品种名称修改仓位时(仅适用于净额结算账户),可以使用另一个选项 adjust。
bool adjust(const string name, const double stop = 0, const double take = 0)
|
此处,仓位选择是使用内置的 PositionSelect 函数完成的,订单号是从其特性 (PositionGetInteger(POSITION_TICKET)) 中获得的。
所有这些功能将在关于 使用仓位 和 仓位特性的章节中详细讨论。
具有最少参数集的 adjust 方法版本,即仅具有 stop 和 take 水平,如下所示。
bool adjust(const double stop = 0, const double take = 0)
|
此代码确保在各种模式下正确填充 position 和 symbols 字段,或者出错时提前退出并在日志中记录错误消息。最后,调用私有版本的 _adjust,其通过 OrderSend 发送请求。
与 buy/sell 方法类似,adjust 方法集会“异步”工作:在它们完成时,只有请求发送状态是已知的,但是无法确认水平修改是否成功。我们知道,对于证券交易所而言,Take Profit 水平可以作为限价订单转发。因此,在 MqlTradeResultSync 结构体中,我们应提供“同步”等待,直到更改生效。
作为 MqlTradeResultSync::wait 方法形成的一般等待机制已经准备就绪,并已用于等待开仓。wait 方法可接收一个指针作为第一个参数,该指针指向另一个具有预定义原型 condition 的方法,以便在循环中进行轮询,直到满足所需的条件或发生超时。在这种情况下,这种 condition 兼容的方法应对该仓位的止损水平进行应用检查。
我们添加这样一个名为 adjusted 的新方法。
struct MqlTradeResultSync: public MqlTradeResult
|
当然,首先我们应检查 retcode 字段中的状态。如果有标准状态,我们将继续检查水平本身,将辅助方法 checkSLTP 传递给 wait。
struct MqlTradeResultSync: public MqlTradeResult
|
此代码可确保在终端的交易环境中使用 PositionSelectByTicket 通过订单号选择仓位,并读取仓位特性 POSITION_SL 和 POSITION_TP,这些特性应与请求中的特性进行比较。问题是,我们还没有获得对请求对象的访问权,必须以某种方式为标记为“.?.”的仓位传递一些值。
基本上,因为我们正在设计 MqlTradeResultSync 结构体,所以我们可以向其中添加 sl 和 tp 字段,并在发送请求之前用 MqlTradeRequestSync 中的值填充它们(内核并不“知道”我们添加的字段,在 OrderSend 调用期间不会对它们进行修改)。但是为了简单起见,我们将使用已经可用的值。MqlTradeResultSync 结构体中的 bid 和 ask 字段仅用于报告重新报价价格(TRADE_RETCODE_REQUOTE 状态),这与 TRADE_ACTION_SLTP 请求无关,因此我们可以将来自已完成的 MqlTradeRequestSync 的 sl 和 tp 存储在其中。
在 MqlTradeRequestSync 结构体的 completed 方法中这样做是合乎逻辑的,该方法使用预定义的超时启动对交易操作结果的阻塞等待。目前为止,其代码只有一个 TRADE_ACTION_DEAL 操作的分支。为了继续,让我们为 TRADE_ACTION_SLTP 添加一个分支。
struct MqlTradeRequestSync: public MqlTradeRequest
|
正如你所看到的,在根据请求设置仓位订单号和价格水平之后,我们可调用上面讨论的adjusted 方法来检查 wait(checkSLTP)。现在,我们可以返回到 MqlTradeResultSync 结构中的辅助方法 checkSLTP,并将其完善为最终形式。
struct MqlTradeResultSync: public MqlTradeResult
|
这样就完成了 MqlTradeRequestSync 和 MqlTradeResultSync 结构体用于 Stop Loss 和 Take Profit 修改操作的功能扩展。
记住这一点,让我们继续 EA 交易的示例 MarketOrderSend.mq5,我们在上一节中便已经开始。我们为其添加一个输入参数 Distance2SLTP,该参数允许你以点数为单位指定 Stop Loss 和 Take Profit 水平之间的距离。
input int Distance2SLTP = 0; // Distance to SL/TP in points (0 = no) |
当该参数为零时,将不设置任何保护水平。
在工作代码中,收到开仓确认后,我们可以计算 SL 和 TP 变量中的水平值,并执行同步修改:request.adjust(SL, TP) && request.completed()。
...
|
在成功的买入或卖出操作后第一次调用 completed 时,仓位订单号保存在请求结构体的 position 字段中。因此,要修改止损点,只有价格水平就足够了,并且仓位的交易品种和订单号已经存在于 request 中。
我们来尝试使用默认设置的 EA 交易执行买入操作,但 Distance2SLTP 设置为 500 点。
OK Order: #=1273913958
|
最后两行对应于 request 和 request.result 结构体(在函数结束时启动)内容日志的调试输出。在这些行中,有趣的是这些字段存储了来自两个查询的共生值:首先是进行了开仓,然后对其进行了修改。具体来说,请求中具有交易量 (0.01) 和价格 (1.10889) 的字段保留在 TRADE_ACTION_DEAL 之后,但是不会阻止 TRADE_ACTION_SLTP 的执行。理论上,通过重置两个请求之间的结构体很容易解决这个问题,但是,我们更喜欢让它们保持原样,因为在填充的字段中也有有用的字段:position 字段接收了我们请求修改所需的订单号。如果我们重置这个结构体,那么就需要引入一个变量作为该订单号的中间存储。
当然,在一般情况下,坚持严格的数据初始化策略是可取的,但是知道如何在特定的场景中使用它们(比如两个或多个预定义类型的相关请求)可以让你优化代码。
此外,在包含结果的结构体中,我们能在 Bid 和 Ask 价格字段中看到请求水平 sl 和 tp,这一点也不奇怪:它们是由 MqlTradeRequestSync::completed 方法写入的,目的是与实际的仓位变化进行比较。当执行请求时,系统内核只在 result 结构体中填充 retcode(已完成)、comment(“请求已执行”)和 request_id (26)。
接下来,我们将考虑另一个实施 跟踪止损的水平修改示例。