使用图表可视化交易(第一部分):选择分析时段
概述
在本文中,我们将从头开始编写一个脚本,以便在回顾分析交易决策的可视化交易情况。简而言之,如果我们进行手动交易,并为了提高交易表现而分析过去的市场入场点,我们希望尽可能减少花在手动历史分析和与之相关的纯技术工作上的时间:打开图表、在历史记录中搜索交易、在终端中手动保存截图、独立地为已完成的交易绘制止损(Stop Loss)和止盈(Take Profit)点位,以及收集有关已支付佣金和隔夜利息(swaps)的信息。我们即将开发的这个脚本将极大地减少所有的机械性工作。使用此脚本(可从下文附件中获取),您将大幅减少在技术工作上花费的时间,从而将更多精力用于分析交易决策上面。不想花时间重组项目的交易者可以在MQL5市场下载现成的脚本。脚本开发将分为两个部分,每个部分都会附上完整的源代码。
为什么我们需要回溯分析?
任何进入市场的人的主要目标都是以可控的风险获取长期利润。这种类型的业务可以与其他需要面对风险的业务相比较。然而,实践表明,实体经济中的大多数新建企业最终都会破产。在金融市场中,由于杠杆的使用和损失会完全波及所有可用资金,资金可能会更快地流失。在实体业务中,如果经营出现问题,你可以将生产设施转用于其他目的(如果它们没有被抵押),并重新开始。而在交易中,你的整个账户都需要承担所有损失。
多项统计数据证实了一个论点,即在没有风险控制且不合理使用杠杆的情况下投资金融市场,从资本投资风险的角度来看,这种投资可能是最危险的。根据美国证券交易委员会(发布号34-64874,文件编号:S7-30-11)在17年内进行的研究:
“大约70%的客户每个季度都会亏损,而零售客户平均在不到12个月的时间内便会损失掉全部投资。”
在之前关于风险平衡和风险管理重要性的系列文章中,我已经指出,不受控制的风险总是会导致资金损失,甚至原本盈利的策略也会因为没有风险管理而变成亏损。在本系列文章中,我们将探讨作为一个非常灵活实体的市场,它会在各种经济和政治因素的影响下随时间发生变化。为了更清晰地理解这一点,我们可以将这一论点简化为:市场至少可以分为两个阶段—保持横盘和趋势行情。因此,不断地分析您的交易活动,以确定所选策略是否适应当前市场形势,这一点非常重要。
许多算法交易者发现,某些策略在横盘市场中表现良好,但当市场开始变动时就会开始亏损。同样,适应趋势行情的策略在横盘市场中也会失去效率。在损失开始超过利润之前,创建能够识别市场阶段变化的算法需要大量的计算资源和时间。
这些问题迫使交易者不断自问:“我做得对吗?”“今天的回撤是否正常,还是我需要调整算法?”“我该如何提高交易结果?”这些问题的答案对于在市场中取得长期成功至关重要。寻找答案的方法各不相同:有些人使用策略优化器,有些人应用深度神经网络,有些人则依赖数学模型或多年的经验。所有这些方法都可能有效,因为市场本身就是最好的老师。
监控市场往往成为创新策略和投资理念的基础。在本文中,我们将创建一个脚本,它不仅有助于提高交易效率,还能基于数据分析为算法提供新理念。尽管我长期以来一直只使用EA进行交易,但分析历史交易对我来说仍是必需的事情。
接下来,我们编写一个脚本,它将极大地简化分析交易的常规过程。该脚本最终应能在打印屏幕上给我们输出信息,如图1所示。
图例1. 显示脚本数据
我们将使用用户输入的数据来运行脚本。
脚本输入
该脚本的功能应包括以下方面:能够自动将历史交易数据上传至图表打印屏幕,并允许为每个交易分别设置多个时间段并保存在不同文件中;同时,为用户提供选项,以便用户能够访问单个交易的数据或用户在指定时间段内所有交易的数据。此外,我还将尽可能为用户提供最大化自定义图表打印屏幕图形的功能。
为了让用户能够将脚本切换为卸载单个交易的数据或一段时间内的历史数据,我们首先需要提供一个自定义的数据类型,即以下的enum(枚举)类型:
enum input_method
{
Select_one_deal,
Select_period
};
自定义枚举的实现将仅包含之前提及的两个选项:选择一个交易和选择一个时间段。现在,我们可以在内存类的input应用全局级别声明输入:
input group "Enter deal ticket or select the period" input input_method inp_method = Select_period; // One deal or period?
为了方便起见,这里我们使用group关键字提供了一个命名块,以便在视觉上将每个重要参数与交易用户分隔开,并提供必要的解释。同时,我们将input内存类变量进行注释处理,以便用普通用户能够理解的文本注释替换变量名。
在图形界面中,如图2所示输入变量值。
图例2. 用于输入数据加载条件的用户界面
为了让用户能够快速地获取某条交易的信息,需要将inp_method变量设置为Select_one_deal对应的枚举值。之后,用户需要指明所需交易的编号(ticket)。通过以下形式声明输入块:
input group "For case 'Select_one_deal': Enter ticket of deal" input long inp_d_ticket = 4339491; // Ticket (global id)
让我们为变量设置一个默认值,以便为用户提供一个示例,并且顺便说明一下,这是基于全局编号显示的订单。通常,终端会在交易历史中显示这个编号,所以用户在这方面应该不会遇到困难。
但是,如果用户想要选择一个时间段进行分析,以便下载所有的交易,那么就需要提供与在交易历史中输入相对应的开始和结束值选择交易的时间段。可以基于全局级别通过以下输入来实现:
input group "For case 'Select_period': Enter period for analys" input datetime start_date = D'17.07.2023'; // Start date input datetime finish_date = D'19.07.2023'; // Finish date
用户将把样本的起始日期输入到start_date变量中,将样本期间的结束日期输入到finish_date变量中。为了方便起见,我们也用默认值来初始化这些变量。
现在我们已经明确了要分析的交易,是时候实现一个机制,让用户能够在多个图表上保存单个交易的数据。例如,如果交易者在手动交易时使用日线图来确定交易水平,并在M30图表上寻找入场点,那么使用这一机制将会非常便利。如果我们的脚本能够立即下载包含所有数据的图表(包括M30和D1),那么分析历史交易将会更加便捷。
我们也将通过输入来实现这个理念,并且不仅提供两个,而是提供四个交易图表,以扩展用户的能力,因为一些交易者在交易时会使用两个以上的图表,但很少有人使用四个以上的图表。在特殊情况下,可以多次运行脚本。为此,需要声明四个类型为ENUM_TIMEFRAMES的枚举标准变量,其中main_graph变量表示主要卸载时间周期,其余变量为辅助时间周期。让我们将代码编写成以下形式:
input group "Enter time frames. 'current' = don't use" input ENUM_TIMEFRAMES main_graph = PERIOD_D1; // Period of main chart input ENUM_TIMEFRAMES addition_graph = PERIOD_H1; // Period of addition chart input ENUM_TIMEFRAMES addition_graph_2 = PERIOD_CURRENT; // Period of addition chart #2 input ENUM_TIMEFRAMES addition_graph_3 = PERIOD_CURRENT; // Period of addition chart #3
用户所选的时间段在时间跨度上可能会有很大差异,事实上,这正是上述设置中提供这一功能的原因。因此,我们还需要在右侧进行图表位移,以便图像能够整齐地放置在图表上,方便用户使用。这意味着,如果我们截取每日图表的屏幕截图,那么交易后的几根K线可能就足以让我们看到之后的价格是如何变化的。对于较短的时间周期,我们需要一个相应设置,其值要高得多。因此,我们将在图表上显示这些位移,并采用如下代码中的默认值。
input group "Navigate settings in bars." input int bars_from_right_main = 15; // Shift from right on main chart input int bars_from_right_add = 35; // Shift from right on addition chart input int bars_from_right_add_2 = 35; // Shift from right on addition chart #2 input int bars_from_right_add_3 = 35; // Shift from right on addition chart #3
为了在MetaTrader 5终端中自定义图表以便保存屏幕截图,我们可以利用模板使用功能,或者通过实现图表操作代码并将每个值分配到单独的输入脚本中来配置所有的图表属性。我认为,在我的解决方案中,当使用图表设置时,不“膨胀”脚本输入部分,而是使用现成的模板来处理终端中的图表展示,将更为优雅且正确。因此,我们只需在每个时间框架的输入中写入预先准备好的模板名称,脚本就会与之配合工作。您还可以使用之前创建的模板,令显示界面更熟悉。
MetaTrader 5终端中的图表展示几乎都可以根据用户的喜好和品位进行自定义。通过在图表上按F8键,我们可以自定义显示模式、对象、颜色调色板等更多内容。通过为不同的显示配置创建模板,可以快速方便地更改图表设置。上下文菜单中的“图表”->“模板”->“保存模板/加载模板”选项允许我们快速更改价格图表的显示设置,甚至无需更改当前活动的图表窗口。因此,可以根据分析的时间周期数量设置多个图表显示,适配到几个变量中。
input group "Properties of charts. Enter the template name:" input string main_template = "dailyHistorytemp"; // Template name of main chart input string addition_template = "hourHistoryTemp"; // Template name of addition chart input string addition_template_2 = "hourHistoryTemp"; // Template name of addition chart #2 input string addition_template_3 = "hourHistoryTemp"; // Template name of addition chart #3
在终端输入的用户界面上,如图3所示:
图例 3. 模板输入的用户界面
既然我们已经确定了所有的设置标准,接下来我们添加与显示交易完整信息相关的设置,以便为用户提供分析其交易操作所需的全部信息。这些主要是与开仓价格、止损、止盈和连接线相对应的对象。相应的color类型变量将如下所示:
input group "Colors of deals line" input color clr_price_open = clrWhiteSmoke; // Color of price open label input color clr_price_close = clrWhiteSmoke; // Color of price close label input color clr_stop = clrRed; // Color of stop loss label input color clr_take = clrLawnGreen; // Color of take profit label input color clr_main = clrWhiteSmoke; // Color of deals trendline
通常来说,我们脚本中的所有输入如下:
#property copyright "Visit product page" #property link "https://www.mql5.com/ru/market/product/86223" #property version "1.00" #property description "Make an automatic printscreen with a full description of all transactions for the period or specify the ticket of the desired transaction." #property script_show_inputs enum input_method { Select_one_deal, Select_period }; input group "Enter deal ticket or select the period" input input_method inp_method = Select_period; // One deal or period? input group "For case 'Select_one_deal': Enter ticket of deal" input long inp_d_ticket = 4339491; // Ticket (global id) input group "For case 'Select_period': Enter period for analys" input datetime start_date = D'17.07.2023'; // Start date input datetime finish_date = D'19.07.2023'; // Finish date input group "Enter time frames. 'current' = don't use" input ENUM_TIMEFRAMES main_graph = PERIOD_D1; // Period of main chart input ENUM_TIMEFRAMES addition_graph = PERIOD_H1; // Period of addition chart input ENUM_TIMEFRAMES addition_graph_2 = PERIOD_CURRENT; // Period of addition chart #2 input ENUM_TIMEFRAMES addition_graph_3 = PERIOD_CURRENT; // Period of addition chart #3 input group "Navigate settings in bars." input int bars_from_right_main = 15; // Shift from right on main chart input int bars_from_right_add = 35; // Shift from right on addition chart input int bars_from_right_add_2 = 35; // Shift from right on addition chart #2 input int bars_from_right_add_3 = 35; // Shift from right on addition chart #3 input group "Properties of charts. Enter the template name:" input string main_template = "dailyHistorytemp"; // Template name of main chart input string addition_template = "hourHistoryTemp"; // Template name of addition chart input string addition_template_2 = "hourHistoryTemp"; // Template name of addition chart #2 input string addition_template_3 = "hourHistoryTemp"; // Template name of addition chart #3 input group "Colors of deals line" input color clr_price_open = clrWhiteSmoke; // Color of price open label input color clr_price_close = clrWhiteSmoke; // Color of price close label input color clr_stop = clrRed; // Color of stop loss label input color clr_take = clrLawnGreen; // Color of take profit label input color clr_main = clrWhiteSmoke; // Color of deals trendline
我们已经在全局范围定义了所有变量,现在可以开始在OnStart()脚本入口点实现代码。让我们首先定义所有必要的变量,用于存储、处理和显示将要发送到保存的打印屏幕文件的数据。在处理脚本的每个步骤时,我们都会通知用户。
首先,我们通知用户脚本已经开始,重置错误变量以便在出现问题时能够正确检查错误返回码,并为所有位置属性提供变量,同时为收集所有交易信息提供适当的存储空间。
Print("Script starts its work."); // notified ResetLastError(); // reset error string brok_name = TerminalInfoString(TERMINAL_COMPANY); // get broker name long account_num = AccountInfoInteger(ACCOUNT_LOGIN); // get account number //--- ulong ticket = 0; // ticket ENUM_DEAL_ENTRY entry = -1; // entry or exit long position_id = 0, PositionID[]; // main id int type = -1, arr_type[]; // deal type int magic = -1, arr_magic[]; // magic number ENUM_DEAL_REASON reason = -1, arr_reason[]; // reason datetime time_open = 0, arr_time_open[]; // deal open time datetime time_close = 0, arr_time_close[]; // close time string symbol, arr_symbol[]; // symbol string comment, arr_comment[]; // comment string externalID, arr_extermalID[]; // external id double stop_loss = 0, arr_stop_loss[]; // deal Stop Loss double take_profit = 0, arr_take_profit[]; // deal Take Profit double open = 0, arr_open[]; // open price double close = 0, arr_close[]; // close price double volume = 0, arr_volume[]; // position volume double commission = 0, arr_commission[]; // commission double swap = 0, arr_swap[]; // swap double profit = 0, arr_profit[]; // profit double fee = 0, arr_fee[]; // fee int res = -1; // user command
现在我们可以根据用户是否想要接收某一笔交易或某段时间内所有交易,将其所需信息打印到屏幕上来实现交易数据的收集。
按时间段选择历史数据
让我们通过“switch”逻辑选择运算符来实现用户选择,该运算符获取已输入的inp_method全局变量的值,并从Select_period情况变体开始处理,以收集某段时间内已完成交易的数据。
首先,通知用户在输入参数中选择分析某段时间内交易数据的选项。通知是使用MessageBox()预定义函数来调用消息窗口实现的。第三个参数是MB_OKCANCEL常量,它允许用户在点击“取消”后中断终端上的脚本执行。这很方便,因为如果用户在inp_method输入中意外选择了错误的选项,他们可以提前终止脚本,而无需等待其执行完毕。完整代码如下所示。
res = MessageBox("You have selected analysis for period. Continue?","",MB_OKCANCEL); // wait for user confirmation
我们将把处理按钮按下事件的结果放在res变量中,以实现中断脚本的机制。从技术上讲,最简单的中断方式是通过return语句。如果变量res包含IDCANCEL值(这意味着用户按下了相应的按钮),则执行return。该代码块通过以下形式的if条件逻辑选择运算符来表示。
if(res == IDCANCEL) // if interrupted by user { printf("%s - %d -> Scrypt was stoped by user.",__FUNCTION__,__LINE__); // notify return; // do not continue }
如果在这个阶段用户已经确认了选项的有效性,那么就开始收集指定历史时期内已完成交易的信息。我们将使用HistorySelect()预定义函数来选择历史交易。正是这个函数将接收用户在上面输入并确认的周期开始和结束时间的值。
在请求历史交易后,为了代码优化和用户方便,非常合适地是检查在用户指定的时间段内账户上是否存在交易。通过终端的预定义HistoryDealsTotal()函数,将获取到的历史交易数量放置到'total'变量中:
int total = HistoryDealsTotal(); // got the total number of deals
如果在指定时间段内没有可分析的内容,且未找到任何交易,那么应通知用户并停止脚本。该事件也通过if条件逻辑选择运算符来处理,在其中我们通过专家顾问(EA)日志和信息窗口通知用户指定时间段内没有交易。使用return运算符中断脚本,如下所示:
if(total <= 0) // if nothing found { printf("%s - %d -> No deals were found for the specified period.",__FUNCTION__,__LINE__); // notify MessageBox("No deals were found for the specified period: "+TimeToString(start_date)+"-"+TimeToString(finish_date)+". Script is done."); return; }
如果在指定时间段内有交易被发现,那么我们开始收集数据。使用for循环遍历从历史中获取的所有交易,如下所示:
for(int i=0; i<total; i++) // iterate through the number of deals
使用每个历史交易的唯一ID(即ticket)来选择并请求每个历史交易的数据。使用预定义的HistoryDealGetTicket()终端函数来获取它。该函数的参数将从0到total接收序列号,并返回唯一交易ID作为返回值,如下所示。不要忘记检查返回值的有效性。
//--- try to get deals ticket if((ticket=HistoryDealGetTicket(i))>0) // took the ticket
在获取到历史交易ID后,请求三个主要特征以收集交易中的常规持仓头寸数据。这些特征包括:历史交易所属的仓位ID、ENUM_DEAL_ENTRY特征(它告诉我们此交易是开仓还是平仓),以及DEAL_TYPE特征(它定义了订单的方向和类型)。这三个请求都通过预定义的终端函数HistoryDealGetInteger()来执行,如下所示:
//--- get deals properties position_id = HistoryDealGetInteger(ticket,DEAL_POSITION_ID); // took the main id entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket,DEAL_ENTRY); // entry or exit? type = (int)HistoryDealGetInteger(ticket,DEAL_TYPE); // deal type
请求这些数据的主要原因是,它们的值将决定我们从所有仓单中收集哪些仓位的数据。让我们回顾一下,一般仓位的数据是从与特定仓位相关的一组几个订单的数据中聚合而来,并根据仓位ID相互比较。
首先,我们需要筛选出所有与交易操作不直接相关的历史交易,例如:账户余额充值、资金提取、从经纪商处获得的奖金累计等。为此,请在代码中安排以下检查:
//--- check the deal type if(type == DEAL_TYPE_BUY || // if it is buy type == DEAL_TYPE_SELL || // if it is sell type == DEAL_TYPE_BUY_CANCELED || // if canceled buy type == DEAL_TYPE_SELL_CANCELED // if canceled sell )
一旦我们确认所接收的交易是买卖交易或其对应的平仓交易(这些可能因经纪账户类型或市场类型而异),我们就可以开始收集整个仓位的数据。考虑到在相应订单中存储仓位数据的特殊性,我们将从与开仓相关的订单中获取一些仓位特征,而在平仓相关的订单中获取其他特征。例如,仓位的财务结果数据通常可以合乎常理地在平仓订单中找到。为了更直观、清晰地展示这些数据,我们可以将其收集到以下表格中:
# | 开仓(DEAL_ENTRY_IN) | 平仓 (DEAL_ENTRY_OUT) |
---|---|---|
1 | 开仓价格 | 平仓价格 (DEAL_PRICE) |
2 | time_open (DEAL_TIME) | time_close (DEAL_TIME) |
3 | symbol (DEAL_SYMBOL) | reason (DEAL_REASON) |
4 | stop_loss (DEAL_SL) | swap (DEAL_SWAP) |
5 | take_profit (DEAL_TP) | profit (DEAL_PROFIT) |
6 | magic (DEAL_MAGIC) | fee (DEAL_FEE) |
7 | comment (DEAL_COMMENT) | - |
8 | externalID (DEAL_EXTERNAL_ID) | - |
9 | volume (DEAL_VOLUME) | - |
10 | commission (DEAL_COMMISSION) | - |
表格 1. 根据交易类型获取整个仓位数据的来源
在代码中,表1所示数据的请求和排序将如下所示:
if(entry == DEAL_ENTRY_IN) // if this is an entry { open = HistoryDealGetDouble(ticket,DEAL_PRICE); // take open price time_open =(datetime)HistoryDealGetInteger(ticket,DEAL_TIME); // take open time symbol=HistoryDealGetString(ticket,DEAL_SYMBOL); // take symbol stop_loss = HistoryDealGetDouble(ticket,DEAL_SL); // take Stop Loss take_profit = HistoryDealGetDouble(ticket,DEAL_TP); // take Take Profit magic = (int)HistoryDealGetInteger(ticket,DEAL_MAGIC); // take magic number comment=HistoryDealGetString(ticket,DEAL_COMMENT); // take comment externalID=HistoryDealGetString(ticket,DEAL_EXTERNAL_ID); // take external id volume = HistoryDealGetDouble(ticket,DEAL_VOLUME); // take volume commission = HistoryDealGetDouble(ticket,DEAL_COMMISSION); // take commission value } if(entry == DEAL_ENTRY_OUT) // if this is an exit { close = HistoryDealGetDouble(ticket,DEAL_PRICE); // take close price time_close =(datetime)HistoryDealGetInteger(ticket,DEAL_TIME); // take close time reason = (ENUM_DEAL_REASON)HistoryDealGetInteger(ticket,DEAL_REASON); // swap = HistoryDealGetDouble(ticket,DEAL_SWAP); // swap profit = HistoryDealGetDouble(ticket,DEAL_PROFIT); // profit fee = HistoryDealGetDouble(ticket,DEAL_FEE); // fee }
一旦初步获取了仓位数据,就需要组织一个容器来存储相关信息。在实现这一功能时,我们将为每个特征使用标准的一维数组。为了检查存储中是否存在某个仓位,我们将定义一个小的Find()模板函数。此函数将用于检查容器中是否存在某个仓位。逻辑是,我们将容器和要在其中查找的值作为函数参数传递。如我之前所述,我们将查找交易所属的仓位的ID。如果找到该仓位,函数应返回相应的索引。如果没有,则返回 -1。
考虑到单个仓位的每个属性需要以字符串、整数值或分数值等不同格式存储,将Find()函数声明为可通过模板重载是有意义的。MQL5编程语言允许我们通过template(模板)关键字灵活且方便地实现这一功能。这将使我们能够一次声明具有typename(类型名)可重载数据类型的函数模板,而编译器将自动为每个数据类型替换所需的实现。由于我们不会在那里传递自定义数据类型,因此不会出现不同类型之间的隐式转换问题,也无需进行任何运算符重载。下面展示了Find()自定义函数模板的实现。
template<typename A> int Find(A &aArray[],A aValue) { for(int i=0; i<ArraySize(aArray); i++) { if(aArray[i]==aValue) { return(i); // The element exists, return the element index } } return(-1); // No such element, return -1 }
使用已声明的Find()函数模板,通过检查当前仓位是否在存储中来完善逻辑。如果函数返回-1,则表示存储中没有该仓位,需要将其添加到存储中。在添加之前,应该先更改存储的维度:
//--- enter data into the main storage //--- check if there is such id if(Find(PositionID,position_id)==-1) // if there is no such deal yet, {
如果这样的数字(仓位ID)存在于存储中,那么我们就可以使用Find()函数返回的索引来访问仓位数据。这是因为,如果账户上同时交易多个金融品种,交易历史中的订单可能会以不同的顺序出现。例如,一个品种上的仓位可能在另一个品种较早的订单之后才被开立。相应地,它也可能比第一个交易品种的仓位更早被平仓。总的来说,关于在特定时间段内搜索和收集仓位信息的逻辑,可以概括如下:
case Select_period: // search within a period res = MessageBox("You have selected analysis for period. Continue?","",MB_OKCANCEL); // wait for user confirmation if(res == IDCANCEL) // if interrupted by user { printf("%s - %d -> Scrypt was stoped by user.",__FUNCTION__,__LINE__); // notify return; // stop } MessageBox("Please press 'Ok' and wait for the next message until script will be done."); // notify //--- select history data if(HistorySelect(start_date,finish_date)) // select the necessary period in history { int total = HistoryDealsTotal(); // got the total number of deals if(total <= 0) // if nothing found { printf("%s - %d -> No deals were found for the specified period.",__FUNCTION__,__LINE__); // notify MessageBox("No deals were found for the specified period: "+TimeToString(start_date)+"-"+TimeToString(finish_date)+". Script is done."); return; } for(int i=0; i<total; i++) // iterate through the number of deals { //--- try to get deals ticket if((ticket=HistoryDealGetTicket(i))>0) // took the ticket { //--- get deals properties position_id = HistoryDealGetInteger(ticket,DEAL_POSITION_ID); // took the main id entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket,DEAL_ENTRY); // entry or exit? type = (int)HistoryDealGetInteger(ticket,DEAL_TYPE); // entry or exit? //--- check the deal type if(type == DEAL_TYPE_BUY || // if it is buy type == DEAL_TYPE_SELL || // if it is sell type == DEAL_TYPE_BUY_CANCELED || // if canceled buy type == DEAL_TYPE_SELL_CANCELED // if canceled sell ) { //--- is it entry or exit? if(entry == DEAL_ENTRY_IN) // if this is an entry { open = HistoryDealGetDouble(ticket,DEAL_PRICE); // take open price time_open =(datetime)HistoryDealGetInteger(ticket,DEAL_TIME); // take open time symbol=HistoryDealGetString(ticket,DEAL_SYMBOL); // take symbol stop_loss = HistoryDealGetDouble(ticket,DEAL_SL); // take Stop Loss take_profit = HistoryDealGetDouble(ticket,DEAL_TP); // take Take Profit magic = (int)HistoryDealGetInteger(ticket,DEAL_MAGIC); // take magic number comment=HistoryDealGetString(ticket,DEAL_COMMENT); // take comment externalID=HistoryDealGetString(ticket,DEAL_EXTERNAL_ID); // take external id volume = HistoryDealGetDouble(ticket,DEAL_VOLUME); // take volume commission = HistoryDealGetDouble(ticket,DEAL_COMMISSION); // take commission value } if(entry == DEAL_ENTRY_OUT) // if this is an exit { close = HistoryDealGetDouble(ticket,DEAL_PRICE); // take close price time_close =(datetime)HistoryDealGetInteger(ticket,DEAL_TIME); // take close time reason = (ENUM_DEAL_REASON)HistoryDealGetInteger(ticket,DEAL_REASON); // reason swap = HistoryDealGetDouble(ticket,DEAL_SWAP); // swap profit = HistoryDealGetDouble(ticket,DEAL_PROFIT); // profit fee = HistoryDealGetDouble(ticket,DEAL_FEE); // fee } //--- enter data into the main storage //--- check if there is such id if(Find(PositionID,position_id)==-1) // if there is no such deal yet, { //--- change the size of containers ArrayResize(arr_time_open,ArraySize(arr_time_open)+1); ArrayResize(arr_time_close,ArraySize(arr_time_close)+1); ArrayResize(arr_symbol,ArraySize(arr_symbol)+1); ArrayResize(arr_stop_loss,ArraySize(arr_stop_loss)+1); ArrayResize(arr_take_profit,ArraySize(arr_take_profit)+1); ArrayResize(arr_open,ArraySize(arr_open)+1); ArrayResize(arr_close,ArraySize(arr_close)+1); ArrayResize(PositionID,ArraySize(PositionID)+1); ArrayResize(arr_magic,ArraySize(arr_magic)+1); ArrayResize(arr_extermalID,ArraySize(arr_extermalID)+1); ArrayResize(arr_comment,ArraySize(arr_comment)+1); ArrayResize(arr_volume,ArraySize(arr_volume)+1); ArrayResize(arr_commission,ArraySize(arr_commission)+1); ArrayResize(arr_reason,ArraySize(arr_reason)+1); ArrayResize(arr_swap,ArraySize(arr_swap)+1); ArrayResize(arr_profit,ArraySize(arr_profit)+1); ArrayResize(arr_fee,ArraySize(arr_fee)+1); PositionID[ArraySize(arr_time_open)-1]=position_id; if(entry == DEAL_ENTRY_IN) // if this is an entry, { arr_time_open[ ArraySize(arr_time_open)-1] = time_open; // deal time arr_symbol[ ArraySize(arr_symbol)-1] = symbol; // instrument symbol arr_stop_loss[ ArraySize(arr_stop_loss)-1] = stop_loss; // deal Stop Loss arr_take_profit[ ArraySize(arr_take_profit)-1] = take_profit; // deal Take Profit arr_open[ ArraySize(arr_open)-1] = open; // open price //--- arr_magic[ ArraySize(arr_magic)-1] = magic; // magic number arr_comment[ ArraySize(arr_comment)-1] = comment; // comment arr_extermalID[ ArraySize(arr_extermalID)-1] = externalID; // external id arr_volume[ ArraySize(arr_volume)-1] = volume; // volume arr_commission[ ArraySize(arr_commission)-1] = commission; // commission } if(entry == DEAL_ENTRY_OUT) // if this is an exit { arr_time_close[ ArraySize(arr_time_close)-1] = time_close; // close time arr_close[ ArraySize(arr_close)-1] = close; // close price //--- arr_reason[ ArraySize(arr_reason)-1] = reason; // reason arr_swap[ ArraySize(arr_swap)-1] = swap; // swap arr_profit[ ArraySize(arr_profit)-1] = profit; // profit arr_fee[ ArraySize(arr_fee)-1] = fee; // fee } } else { int index = Find(PositionID,position_id); // if found, search for the index if(entry == DEAL_ENTRY_IN) // if this is an entry { arr_time_open[index] = time_open; // deal time arr_symbol[index] = symbol; // symbol arr_stop_loss[index] = stop_loss; // deal Stop Loss arr_take_profit[index] = take_profit; // deal Take Profit arr_open[index] = open; // close price //--- arr_magic[index] = magic; // magic number arr_comment[index] = comment; // comment arr_extermalID[index] = externalID; // external id arr_volume[index] = volume; // volume arr_commission[index] = commission; // commission } if(entry == DEAL_ENTRY_OUT) // if this is an exit { arr_time_close[index] = time_close; // deal close time arr_close[index] = close; // deal close price //--- arr_reason[index] = reason; // reason arr_swap[index] = swap; // swap arr_profit[index] = profit; // profit arr_fee[index] = fee; // fee } } } } } } else { printf("%s - %d -> Error of selecting history deals: %d",__FUNCTION__,__LINE__,GetLastError()); // notify printf("%s - %d -> No deals were found for the specified period.",__FUNCTION__,__LINE__); // notify MessageBox("No deals were found for the specified period: "+TimeToString(start_date)+"-"+TimeToString(finish_date)+". Script is done."); return; } break;
第一部分的总结
在这篇文章中,我们探讨了历史交易分析对于金融市场安全且长期交易的重要性。分析的关键要素之一是研究历史交易,我们已经在脚本中开始实施这一研究。我们考虑了输入数据的形成,以及实现了在选定时间段内选择历史交易数据的算法。我们还实现了一个可重载的函数模板,以简化数据容器的处理。
在下一篇文章中,我们将完善脚本,同时考虑选择单笔交易数据的算法,以及绘制图表和实现将数据对象显示在图表上的代码。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/14903