- EA 交易的主要事件:OnTick
- 基本原理和概念:订单、交易和仓位
- 交易操作类型
- 订单类型
- 按价格和数量划分的订单执行模式
- 挂单到期日期
- 期货订单的保证金计算方法:OrderCalcMargin
- 估算交易操作的利润:OrderCalcProfit
- MqlTradeRequest 结构体
- MqlTradeCheckResult 结构体
- 请求验证:OrderCheck
- 请求发送结果:MqlTradeResult 结构体
- 发送交易请求:OrderSend 和 OrderSendAsync
- 买入和卖出操作
- 修改仓位的止损和/或止盈水平
- 跟踪止损
- 平仓:全部和部分
- 反向平仓:全部和部分
- 挂单
- 修改挂单
- 删除挂单
- 获取活动订单列表
- 订单特性(现行和历史)
- 用于读取活动订单特性的函数
- 按特性选择订单
- 获取仓位列表
- 仓位特性
- 用于读取仓位特性的函数
- 交易特性
- 从历史中选择订单和交易
- 用于从历史中读取订单特性的函数
- 用于从历史中读取交易特性的函数
- 交易类型
- OnTradeTransaction 事件
- 同步和异步请求
- OnTrade 事件
- 监测交易环境变化
- 创建多交易品种 EA 交易
- EA 交易的优势和局限性
- 在 MQL 向导中创建 EA 交易
用于从历史中读取交易特性的函数
为了读取交易特性,函数组按 特性类型组织,具体分为整数型、实数型和字符串型。在调用函数之前,你需要选择所需的 历史时期 ,从而确保所有函数的第一个参数 (ticket) 中传递的订单号交易的可用性。
每种类型的特性都有两种形式:直接返回值和通过引用写入变量。第二种形式返回 true 表示成功。第一种形式仅在出错时返回 0。错误代码在 _LastError 变量中。
整数型及其兼容的特性类型(datetime 和枚举)可以使用 HistoryDealGetInteger 函数获得。
long HistoryDealGetInteger(ulong ticket, ENUM_DEAL_PROPERTY_INTEGER property)
bool HistoryDealGetInteger(ulong ticket, ENUM_DEAL_PROPERTY_INTEGER property,
long &value)
实数型特性由 HistoryDealGetDouble 函数读取。
double HistoryDealGetDouble(ulong ticket, ENUM_DEAL_PROPERTY_DOUBLE property)
bool HistoryDealGetDouble(ulong ticket, ENUM_DEAL_PROPERTY_DOUBLE property,
double &value)
对于字符串型特性,提供了 HistoryDealGetString 函数。
string HistoryDealGetString(ulong ticket, ENUM_DEAL_PROPERTY_STRING property)
bool HistoryDealGetString(ulong ticket, ENUM_DEAL_PROPERTY_STRING property,
string &value)
DealMonitor 类 (DealMonitor.mqh) 可提供交易特性的统一读数,其组织方式与OrderMonitor 和 PositionMonitor 完全相同。基础类为 DealMonitorInterface,继承自模板 MonitorInterface(我们在 读取活动订单特性的函数一节中进行说明)。正是在这个水平,ENUM_DEAL_PROPERTY 枚举的特定类型被指定为模板参数和stringify 方法的特定实现。
#include <MQL5Book/TradeBaseMonitor.mqh>
|
下面的 DealMonitor 类有点类似于最近为了处理历史数据而修改的 OrderMonitor 类。除了应用 HistoryDeal 函数而不是 HistoryOrder 函数之外,应注意的是,对于交易,不需要在在线环境中检查订单号,因为交易只存在于历史中。
class DealMonitor: public DealMonitorInterface
|
基于 DealMonitor 和 TradeFilter,很容易创建交易筛选器 (DealFilter.mqh)。注意,TradeFilter 作为许多实体的基础类,在 按特性选择订单一节中进行了说明。
#include <MQL5Book/DealMonitor.mqh>
|
作为使用历史的通用示例,我们来考虑仓位历史恢复脚本 TradeHistoryPrint.mq5。
TradeHistoryPrint
该脚本可为当前图表交易品种构建历史。
我们首先需要交易和订单的筛选器。
#include <MQL5Book/OrderFilter.mqh>
|
我们将从交易中提取仓位 ID,并基于仓位 ID 来请求订单的详细信息。
可以查看整个历史,也可以查看特定仓位的历史,为此,我们将提供一个模式选择和一个输入字段,用作输入变量中的标识符。
enum SELECTOR_TYPE
|
必须要记住,对久远的账户历史进行采样可能是一种开销,因此我们希望在处理的 EA 交易中提供对所获得的历史处理结果以及最后处理时间戳的缓存。后续每次分析历史订单时,可以不从头开始,而是从一个记忆中的时刻开始这个过程。
为了以视觉上吸引人的方式显示以列对齐的历史信息,将其表示为一个结构数组是有意义的。但是,我们的筛选器已经支持查询存储在特殊结构中的数据,也就是元组。因此,我们可应用一个技巧:描述我们的应用程序结构体,并观察元组的规则:
- 第一个字段的名称必须为 _1;该名称可在排序算法中选择性使用。
- 返回字段数量的 size 函数必须在结构体中说明。
- 该结构体应具有一个模板方法 assign,用于根据 MonitorInterface 派生的已传递监视器对象的特性填充字段。
在标准元组中,assign 方法的说明如下:
template<typename M>
|
作为第一个参数,可接收一个数组,其特性 ID 对应于我们关注的字段。事实上,这是由调用代码传递给筛选器的 select 方法 (TradeFilter::select) 的数组,然后通过引用传递给 assign。但是,由于我们现在创建的不是一些标准元组,而是我们自己的结构体,这些结构“知道”其字段的应用特性,我们可以将带有特性标识符的数组留在结构本身内,而不是在筛选器与同一结构体的 assign 方法之间来回传递。
具体来说,为了请求交易,我们用 8 个字段对 DealTuple 结构体进行了说明。其标识符将在 fields 静态数组中指定。
struct DealTuple
|
这种方法将标识符和字段放在一起,将相应的值存储在一个地方,这样更容易理解和维护源代码。
用特性值填充字段需要稍微修改(简化)一下 assign 方法,该方法用于从 fields 数组(而不是从输入参数)获取 ID。
struct DealTuple
|
同时,我们将 ENUM_DEAL_TYPE 和 ENUM_DEAL_ENTRY 枚举的数字元素转换为用户友好的字符串。当然,这只在日志记录时需要。对于编程分析,这些类型应保持原样。
因为我们已经在其元组中开发了一个新版本的 assign 方法,所以你需要在 TradeFilter 类中为其添加一个新版本的 select 方法。这项创新肯定会对其他程序有用,因此我们可将其直接引入 TradeFilter,而不是引入某个新的派生类。
template<typename T,typename I,typename D,typename S>
|
注意,在用特定类型的代码调用模板方法之前,编译器并不会实现所有模板方法。因此,TradeFilter 中存在这种模式并不意味着必须包含任何元组头文件或必须描述类似的结构(除非你要使用)。
因此,如果在更早的时候,要使用标准元组选择交易,我们必须这样编写:
#include <MQL5Book/Tuples.mqh>
|
有了定制的结构体,一切都简单多了:
DealFilter filter;
|
类似于 DealTuple 结构体,我们来描述一下 OrderTuple 的 10 字段结构体。
struct OrderTuple
|
现在,实现脚本的主要函数 OnStart 准备就绪。首先,我们将描述交易和订单筛选器的对象。
void OnStart()
|
根据输入变量,我们可选择整个历史或特定仓位。
if(PositionID == 0 || Type == TOTAL)
|
接下来,我们将在一个数组中收集所有仓位标识符,或者保留一个由用户指定的标识符。
ulong positions[];
|
辅助函数 ArrayUnique 可在数组中留下不重复的元素。前提是对源数组进行排序。
此外,在遍历仓位过程中,我们可请求与每个仓位相关的交易和订单。交易按 DealTuple 结构体的第一个字段进行排序,即按时间排序。也许最有趣的是计算仓位的盈利/亏损。为此,我们对所有交易的 profit 字段的值进行求和。
for(int i = 0; i < n; ++i)
|
此代码不会分析交易特性中的佣金 (DEAL_COMMISSION)、掉期 (DEAL_SWAP) 和费用 (DEAL_FEE)。在真正的 EA 交易中,可能需要进行这些分析(取决于策略的需求)。我们将在 测试多币种 EA 交易一节中看到交易历史分析的另一个示例,到时我们会考虑这一要求。
你可以将脚本的结果与终端中的历史选项卡上的表进行比较:其“盈利”列显示了每个仓位的净利润(掉期、佣金和费用在相邻的列内,但需要手动将其包括在内)。
务必要注意的是,仅当在设置中选择了整个历史时,ORDER_TYPE_CLOSE_BY 类型的订单才会在两个仓位中显示。如果选择了一个特定的仓位,系统只会将这样的订单计入其中一个仓位(在交易请求中首先指定的仓位,在 position 字段内),而不会计入第二个仓位(在 position_by 中指定)。
以下是一个历史较短的交易品种的脚本结果示例。
Positions total: 3 Position: 1 1253500309 Profit:238.150000 [_1] [deal] [order] [type] [in_out] [volume] [price] [profit] [0] 2022.02.04 17:34:57 1236049891 1253500309 "BUY" "IN" 1.00000 76.23900 0.00000 [1] 2022.02.14 16:28:41 1242295527 1259788704 "SELL" "OUT" 1.00000 76.42100 238.15000 Order details: [_1] [setup] [done] [type] [volume] [open] [current] » » [sl] [tp] [comment] [0] 1253500309 2022.02.04 17:34:57 2022.02.04 17:34:57 "BUY" 1.00000 76.23900 76.23900 » » 0.00 0.00 "" [1] 1259788704 2022.02.14 16:28:41 2022.02.14 16:28:41 "SELL" 1.00000 76.42100 76.42100 » » 0.00 0.00 "" Position: 2 1253526613 Profit:878.030000 [_1] [deal] [order] [type] [in_out] [volume] [price] [profit] [0] 2022.02.07 10:00:00 1236611994 1253526613 "BUY" "IN" 1.00000 75.75000 0.00000 [1] 2022.02.14 16:28:40 1242295517 1259788693 "SELL" "OUT" 1.00000 76.42100 878.03000 Order details: [_1] [setup] [done] [type] [volume] [open] [current] » » [sl] [tp] [comment] [0] 1253526613 2022.02.04 17:55:18 2022.02.07 10:00:00 "BUY_LIMIT" 1.00000 75.75000 75.67000 » » 0.00 0.00 "" [1] 1259788693 2022.02.14 16:28:40 2022.02.14 16:28:40 "SELL" 1.00000 76.42100 76.42100 » » 0.00 0.00 "" Position: 3 1256280710 Profit:4449.040000 [_1] [deal] [order] [type] [in_out] [volume] [price] [profit] [0] 2022.02.09 13:17:52 1238797056 1256280710 "BUY" "IN" 2.00000 74.72100 0.00000 [1] 2022.02.14 16:28:39 1242295509 1259788685 "SELL" "OUT" 2.00000 76.42100 4449.04000 Order details: [_1] [setup] [done] [type] [volume] [open] [current] » » [sl] [tp] [comment] [0] 1256280710 2022.02.09 13:17:52 2022.02.09 13:17:52 "BUY" 2.00000 74.72100 74.72100 » » 0.00 0.00 "" [1] 1259788685 2022.02.14 16:28:39 2022.02.14 16:28:39 "SELL" 2.00000 76.42100 76.42100 » » 0.00 0.00 "" |
在净额结算账户上增加仓位(两笔“IN”交易)及其冲销(一笔较大数量的“INOUT”交易)的情况如以下片段所示。
Position: 5 219087383 Profit:0.170000 [_1] [deal] [order] [type] [in_out] [volume] [price] [profit] [0] 2022.03.29 08:03:33 215612450 219087383 "BUY" "IN" 0.01000 1.10011 0.00000 [1] 2022.03.29 8:04:05 215612451 219087393 "BUY" "IN" 0.01000 1.10009 0.00000 [2] 2022.03.29 08:04:29 215612457 219087400 "SELL" "INOUT" 0.03000 1.10018 0.16000 [3] 2022.03.29 08:04:34 215612460 219087403 "BUY" "OUT" 0.01000 1.10017 0.01000 Order details: [_1] [setup] [done] [type] [volume] [open] [current] » » [sl] [tp] [comment] [0] 219087383 2022.03.29 08:03:33 2022.03.29 08:03:33 "BUY" 0.01000 0.0000 1.10011 » » 0.00 0.00 "" [1] 219087393 2022.03.29 8:04:05 2022.03.29 8:04:05 "BUY" 0.01000 0.0000 1.10009 » » 0.00 0.00 "" [2] 219087400 2022.03.29 08:04:29 2022.03.29 08:04:29 "SELL" 0.03000 0.0000 1.10018 » » 0.00 0.00 "" [3] 219087403 2022.03.29 8:04:34 2022.03.29 8:04:34 "BUY" 0.01000 0.0000 1.10017 » » 0.00 0.00 "" |
我们将考虑部分历史,以对冲账户反向平仓的特定仓位为例。首先可以单独查看第一个仓位:PositionID=1276109280。无论 Type 输入参数如何,该仓位都将完整显示。
Positions total: 1 Position: 1 1276109280 Profit:-0.040000 [_1] [deal] [order] [type] [in_out] [volume] [price] [profit] [0] 2022.03.07 12:20:53 1258725455 1276109280 "BUY" "IN" 0.01000 1.08344 0.00000 [1] 2022.03.07 12:20:58 1258725503 1276109328 "SELL" "OUT_BY" 0.01000 1.08340 -0.04000 Order details: [_1] [setup] [done] [type] [volume] [open] [current] » » [sl] [tp] [comment] [0] 1276109280 2022.03.07 12:20:53 2022.03.07 12:20:53 "BUY" 0.01000 1.08344 1.08344 » » 0.00 0.00 "" [1] 1276109328 2022.03.07 12:20:58 2022.03.07 12:20:58 "CLOSE_BY" 0.01000 1.08340 1.08340 » » 0.00 0.00 "#1276109280 by #1276109283" |
还可以看到第二个仓位:PositionID=1276109283。但是,如果 Type 等于 "position",为了选择历史的片段,可使用 HistorySelectByPosition 函数,结果是只有一个退出订单(尽管事实上有两个交易)。
Positions total: 1 Position: 1 1276109283 Profit:0.000000 [_1] [deal] [order] [type] [in_out] [volume] [price] [profit] [0] 2022.03.07 12:20:53 1258725458 1276109283 "SELL" "IN" 0.01000 1.08340 0.00000 [1] 2022.03.07 12:20:58 1258725504 1276109328 "BUY" "OUT_BY" 0.01000 1.08344 0.00000 Order details: [_1] [setup] [done] [type] [volume] [open] [current] » » [sl] [tp] [comment] [0] 1276109283 2022.03.07 12:20:53 2022.03.07 12:20:53 "SELL" 0.01000 1.08340 1.08340 » » 0.00 0.00 "" |
如果我们将 Type 设置为“整个历史”,将会出现一个 "CLOSE_BY" 订单。
Positions total: 1 Position: 1 1276109283 Profit:0.000000 [_1] [deal] [order] [type] [in_out] [volume] [price] [profit] [0] 2022.03.07 12:20:53 1258725458 1276109283 "SELL" "IN" 0.01000 1.08340 0.00000 [1] 2022.03.07 12:20:58 1258725504 1276109328 "BUY" "OUT_BY" 0.01000 1.08344 0.00000 Order details: [_1] [setup] [done] [type] [volume] [open] [current] » » [sl] [tp] [comment] [0] 1276109283 2022.03.07 12:20:53 2022.03.07 12:20:53 "SELL" 0.01000 1.08340 1.08340 » » 0.00 0.00 "" [1] 1276109328 2022.03.07 12:20:58 2022.03.07 12:20:58 "CLOSE_BY" 0.01000 1.08340 1.08340 » » 0.00 0.00 "#1276109280 by #1276109283" |
使用这样的设置,可完整选择历史,但是筛选器只留下那些在 ORDER_POSITION_ID 或 ORDER_POSITION_BY_ID 特性中找到指定仓位标识符的订单。为了用逻辑 OR 构成条件,在 TradeFilter 类中添加了 IS::OR_EQUAL 元素。你可以另外研究一下。