总结
金融市场的两种基本类型包括外汇市场和场外交易市场。我们可以使用现代元交易员和元编辑器工具来享受场外(场外)外汇交易,这些交易正在进一步改进。除了事务自动化,这些工具还可以使用历史数据对事务算法进行综合测试。
如何用我们自己的思维进行兑换交易?一些兑换终端有内置的编程语言。例如,流行的Transaq终端有一种ATF(超级交易设备)编程语言。但是,当然,它不能与MQL5相比较。此外,它没有任何策略测试功能。一个好的解决方案是在元交易者策略测试仪中获取交易数据并优化交易算法。
这可以通过创建定制品种来实现。本文在MetaTrader 5中详细描述了创建定制品种的过程。您所需要的只是从csv(txt)格式获取数据,并根据本文描述的步骤导入价格历史记录。
如果在数据格式上没有区别,那就很容易了。例如,让我们来看看finam。RU,一个与交换相关的流行网站资源。报价可在此处下载:
莫斯科交易所出口报价
Finam提供数据格式:
可用日期格式:“年-月-日”、“年-月-日”、“日-月-年”、“日/月/年”、“月/日/年”。我们的格式:
未提供我们需要的“yyyy.mm.dd”格式。所以芬兰。RU提供了很多不同的格式,但是没有一种是我们需要的。nbsp;
此外,还有许多其他的交换资源。其他网站提供的格式也可能不合适。我们需要一定数量的数据。但是,报价可以按不同的顺序存储,例如开盘价、收盘价、最高价和最低价。nbsp;
因此,我们的任务是将以随机顺序和不同格式提供的数据转换为所需的格式。这将为MetaTrader 5提供从任何资源接收数据的机会。然后,我们将使用MQL5工具根据接收到的数据创建定制品种,这将使我们能够执行测试。
进口报价有一些困难。
交换支持点差异、投标价格(ask)和投标价格(bid)。然而,在市场的深度上,所有这些价值都只是暂时存在的。此后,无论执行价格是什么,只写交易价格,即投标价格或投标价格。我们需要终点差。由于市场深度差异无法恢复,此处添加了固定点差异。如果需要点差异,可以用某种方式模拟它。本文介绍了一种根据特定分布规律对定制品种进行时间序列建模的方法。或者,您可以编写一个简单的函数来反映点差对波动性的依赖性,spread=f(高-低)。
按时间框架操作时,完全可以使用固定点差。这一点误差在很大的周期内是微乎其微的。然而,点差建模对于报价是非常重要的。可转换报价格式:
我们的格式:
除了最后一笔交易外,我们还需要设定投标价格(ask)和投标价格(bid)。数据以毫秒为单位排序。交易所只提供价格流。第一页上的数据更像是将大数据拆分成片段。没有外汇报价。它可以是出价,询问,或者两者兼而有之。此外,我们需要按时间人为地对事务进行排名,并添加毫秒。
因此,本文不涉及数据导入,而是如上所述的数据建模。因此,为了不误导您,我决定不按“竞价=竞价价(+point difference)=final transaction”的原则,逐条报价导入应用程序。当采用毫秒处理时,点差是非常重要的,因此在测试中需要选择合适的建模方法。
在此之后,需要几分钟时间才能按报价导入代码修改报价。只需用mqltick替换mqlrates结构。customRatesUpdate()函数需要替换为customTicksAdd()。
下一个相关点是不可能考虑所有可能的数据格式。例如,在写数字时,空格可以用作分隔符(1000000),或者逗号可以代替小数点(如3、14)。更糟的是,当数据分隔符和小数分隔符都是点或逗号时(如何区分它们)。这里只考虑最常见的格式。如果您需要处理非标准格式,您必须自己处理它们。
此外,交易所没有报价历史记录,只提供交易量。因此,在本文中,我们设置Exchange卷=VOL=TICKVOL。
本文分为两部分。第一部分介绍了代码描述。它使您熟悉代码,以便以后可以编辑它来处理非标准数据格式。第二部分包含一步一步的指南(用户手册)。它适用于对编程不感兴趣的人,但只需要使用所实现的函数。如果使用标准数据格式(尤其是finam)。作为来源),您可以立即转到第2部分。
第1部分代码说明
这里只提供部分代码。完整的代码可以在附件中找到。
首先,我们输入所需的参数,例如字符串中数据的位置、文件参数、品种名称等。
input int SkipString =1; // 要跳过的字符串数量 input string mark1 ="Time position and format"; // 时间 input DATE indate =yyyymmdd; // 源日期格式 input TIME intime =hhdmmdss; // 源时间格式 input int DatePosition =1; // 日期位置 input int TimePosition =2; // 时间位置 //------------------------------------------------------------------+ input string mark2 ="Price data position"; // 价格 input int OpenPosition =3; // 开盘价位置 input int HighPosition =4; // 最高价位置 input int LowPosiotion =5; // 最低价位置 input int ClosePosition =6; // 收盘价位置 input int VolumePosition =7; // 成交量位置 input string mark3 ="File parameters"; // 文件 //-------------------------------------------------------------------+ input string InFileName ="sb"; // 源文件名 input DELIMITER Delimiter =comma; // 分隔符 input CODE StrType =ansi; // 字符串类型 input string mark4 ="Other parameters"; // 其它 //-------------------------------------------------------------------+ input string spread ="2"; // 固定点差点数 input string Name ="SberFX"; // 您所创建的品种名称
为某些数据创建枚举。例如,对于日期和时间格式:
enum DATE { yyyycmmcdd, // yyyy.mm.dd yyyymmdd, // yyyymmdd yymmdd, // yymmdd ddmmyy, // ddmmyy ddslmmslyy, // dd/mm/yy mmslddslyy // mm/dd/yy // 其他格式在此处添加 }; enum TIME { hhmmss, // hhmmss hhmm, // hhmm hhdmmdss, // hh:mm:ss hhdmm // hh:mm // 其他格式在此处添加 };
如果未提供所需格式,请添加该格式。
然后打开源文件。为了方便编辑格式化数据,我建议将它们保存在csv文件中。同时,应将数据写入mqlrates结构,以便自动创建自定义品种。
// 打开输入文件 int out =FileOpen(InFileName,FILE_READ|StrType|FILE_TXT); if(out==INVALID_HANDLE) { Alert("Failed to open the file for reading"); return; } // 打开输出文件 int in =FileOpen(Name+"(f).csv",FILE_WRITE|FILE_ANSI|FILE_CSV); if(in==INVALID_HANDLE) { Alert("Failed to open the file for writing"); return; } //---插入标题字符串 string Caption ="<DATE>/t<TIME>/t<OPEN>/t<HIGH>/t<LOW>/t<CLOSE>/t<TICKVOL>/t<VOL>/t<SPREAD>"; FileWrite(in,Caption); //----------------------------------------------------------- string fdate="",ftime="",open=""; string high="",low="",close="",vol=""; int left=0,right=0; string str="",temp=""; for(int i=0;i<SkipString;i++) { str =FileReadString(out); i++; } MqlRates Rs[]; ArrayResize(Rs,43200,43200); // 一个月中有 43200 分钟 datetime time =0;
源文件必须保存到mql5/files目录。skipString外部变量表示要从文件头跳过的行数。为了能够使用空格和制表符作为分隔符,我们使用标志文件_TXT来打开文件。
然后我们需要从字符串中提取数据。在输入参数中指定位置。数字从1开始。我们以储蓄银行股票报价为例。
这里的日期位置是1,时间是2,依此类推。Skip字符串=1。
要解析字符串,我们可以使用StringSplit()函数。但最好开发自己的函数,以便更容易地监视源文件中的错误。可以将数据分析添加到这些函数中。不过,使用StringSplit()代码更容易。查找数据边界的第一个函数接收字符串、分隔符和位置。边界被写入变量A和B,作为引用传递。
//---搜索数据位置边界-----------------------------+ bool SearchBorders(string str,int pos,int &a,int &b,DELIMITER delim) { // 辅助变量 int left=0,right=0; int count=0; int start=0; string delimiter=""; //-------------------------------------------------------------------+ switch(delim) { case comma : delimiter =","; break; case tab : delimiter ="/t"; break; case space : delimiter =" "; break; case semicolon : delimiter =";"; break; } while(count!=pos||right!=-1) { right =StringFind(str,delimiter,start); if(right==-1&&count==0){Print("Wrong date");return false;} //Incorrect data if(right==-1) { right =StringLen(str)-1; a =left; b =right; break; } count++; if(count==pos) { a =left; b =right-1; return true; } left =right+1; start =left; } return true; }
现在让我们使用stringsubstr()函数来获取相应的数据。必须将接收到的值转换为所需的格式。为此,我们编写了日期和时间转换函数。例如,这是一个日期转换函数:
//---日期格式-------------------------------------------------+ //2017.01.02 string DateFormat(string str,DATE date) { string res=""; string yy=""; switch(date) { case yyyycmmcdd : //我们的格式 res =str; if(StringLen(res)!=10)res=""; // 检查日期格式 case yyyymmdd : res =StringSubstr(str,0,4)+"."+StringSubstr(str,4,2)+"."+StringSubstr(str,6,2); if(StringLen(res)!=10)res=""; // 检查日期格式 break; case yymmdd : yy =StringSubstr(str,0,2); if(StringToInteger(yy)>=70) yy ="19"+yy; else yy ="20"+yy; res =yy+"."+StringSubstr(str,2,2)+"."+StringSubstr(str,4,2); if(StringLen(res)!=10)res=""; // 检查日期格式 break; //---其他格式 (完整代码在文件当中)------------- //如有必要,添加其他格式的解析 default : break; } return res; }
如果不提供所需格式(如1月18日之前的日期),则应添加该格式。这里检查接收的数据是否符合所要求的格式(如果源文件中有错误),如果String Len(RES)!= 10)RES =“。但是数据分析不是一件容易的事情,因此需要一个单独的程序来进行更详细的分析。如果发生错误,函数返回RES=“”,然后跳过相应的行。
下面的代码可用于DDMYY类型的格式转换,其中的年份是以两位数写成的。值gt=70被转换为19 yy,并且小于它的值被转换为20 yy。
在格式转换之后,我们将数据写入相应的变量并编译最终的字符串。
while(!FileIsEnding(out)) { str =FileReadString(out); count++; //---fdate----------------------------- if(SearchBorders(str,DatePosition,left,right,Delimiter)) { temp =StringSubstr(str,left,right-left+1); fdate =DateFormat(temp,indate); if(fdate==""){Print("Error in string ",count);continue;} } else {Print("Error in string ",count);continue;} //---其他数据的处理方式类似
如果在函数searchborders、dateformat或timeformat中发现错误,则跳过该字符串,并使用print()函数输出其序列号。所有枚举和格式转换函数都位于单独的头文件formatfunctions中。MQH。nbsp;
然后生成字符串并输出。数据被分配给mqlrates结构的相应元素。
//-------------------------------------------------------------------+ str =fdate+","+ftime+","+open+","+high+","+low+","+close+","+vol+","+vol+","+Spread; FileWrite(in,str); //---填充 MqlRates -----------------------------------------------+ Rs[i].time =time; Rs[i].open =StringToDouble(open); Rs[i].high =StringToDouble(high); Rs[i].low =StringToDouble(low); Rs[i].close =StringToDouble(close); Rs[i].real_volume =StringToInteger(vol); Rs[i].tick_volume =StringToInteger(vol); Rs[i].spread =int(StringToInteger(Spread)); i++; //-------------------------------------------------------------------+ }
读取所有字符串后,动态数组将获得最终大小,文件将关闭:
ArrayResize(Rs,i); FileClose(out); FileClose(in);
现在,您可以创建自定义品种了。此外,我们还有一个csv文件,可以在metaeditor中直接编辑。基于这个csv文件,我们可以使用metatrader 5终端中的标准方法来创建自定义的变体。
使用MQL5创建定制品种
现在我们已经准备好了所有的数据,我们只需要添加定制的种类。
CustomSymbolCreate(Name); CustomRatesUpdate(Name,Rs);
customratesUpdate()函数用于导入引号,这意味着该程序不仅可以用于创建品种,还可以用于添加新数据。如果该品种已经存在,customSymbolCreate()将返回-1(负1),程序将继续执行,而报价将通过customRatesUpdate()函数更新。品种显示在市场观察窗口中,并以绿色突出显示。
现在我们可以打开图表以确保一切正常:
欧元兑美元图表
设定规格(品种属性)
当测试品种时,我们可能需要配置它们的特性(规格)。我已经编译了一个单独的规范包含文件,允许对属性进行简单的编辑。在这个文件中,在StUntRe规()函数中设置了各种属性。所有的属性都是从枚举符号枚举in FoFixOntIn,和枚举的枚举符号IfFuy双,以及枚举符号IfFixx字符串这里收集的。
void SetSpecifications(string Name) { //---整数属性------------------------------------- // CustomSymbolSetInteger(Name,SYMBOL_CUSTOM,true); // 布尔值表示此品种自定义 // CustomSymbolSetInteger(Name,SYMBOL_BACKGROUND_COLOR,clrGreen); // 在市场观察中此品种所用的背景颜色 // 其它整数属性 //---双精度属性 --------------------------------------------------- // CustomSymbolSetDouble(Name,SYMBOL_BID,0); // 竞买价,出售品种的最佳价格 // CustomSymbolSetDouble(Name,SYMBOL_BIDHIGH,0); // 每日最高的竞买价 // CustomSymbolSetDouble(Name,SYMBOL_BIDLOW,0); // 每日最低的竞买价 // 其它双精度属性 //---字符串属性-----------------------------------------------+ // CustomSymbolSetString(Name,SYMBOL_BASIS,""); // 自定义品种的基准资产名称 // CustomSymbolSetString(Name,SYMBOL_CURRENCY_BASE,""); // 品种的基准货币 // CustomSymbolSetString(Name,SYMBOL_CURRENCY_PROFIT,""); // 盈利货币 // 其它字符串属性 }
此函数在Cu饰eMulkCudio函数之后执行。目前还不知道这是什么类型的产品、期货、股票或期权。大多数属性不是必需的,并被注释掉。只有源代码中的某些行没有注释:
CustomSymbolSetInteger(Name,SYMBOL_CUSTOM,true); // 布尔值表示此品种是自定义的 CustomSymbolSetInteger(Name,SYMBOL_BACKGROUND_COLOR,clrGreen); // 在市场观察中此品种所用的背景颜色 CustomSymbolSetInteger(Name,SYMBOL_SELECT,true); // 布尔值表示在市场观察中选择了此品种 CustomSymbolSetInteger(Name,SYMBOL_VISIBLE,true); // 布尔值表示此品种已在市场观察中显示
出于测试目的,以下参数没有注释:最小交易量、增量交易量、价格增量、点大小,这些是必需的特性。这些特征是储蓄银行股票的典型特征。不同品种的性状不同。
CustomSymbolSetDouble(name,SYMBOL_POINT,0.01); // 点数 CustomSymbolSetDouble(name,SYMBOL_VOLUME_MIN,1); // 一笔成交最小交易量 CustomSymbolSetDouble(name,SYMBOL_VOLUME_STEP,1); // 最小交易量变化增量 CustomSymbolSetInteger(name,SYMBOL_DIGITS,2); // 小数位 CustomSymbolSetInteger(name,SYMBOL_SPREAD,2); // 点差的点数 CustomSymbolSetInteger(name,SYMBOL_SPREAD_FLOAT,false); // 布尔值表示浮动点差 CustomSymbolSetDouble(name,SYMBOL_TRADE_TICK_SIZE,0.01); // 最小价格变化
这是一种很好的方法,不必每次需要设置必要的属性时都重新编译代码。如果可以输入所需的参数,就更方便了。所以我不得不改变我的方法。纯文本文件规范中将提供各种属性。并可以手动编辑每个新品种。这样就不需要重新编译源代码。
在元编辑器中编辑文本文件更方便。这主要是因为metaeditor提供了参数和数据的高亮度显示。属性以以下格式写入:
0
数据用逗号分隔。字符串分析如下:
while(!FileIsEnding(handle)) { str =FileReadString(handle); //--- 跳过行 -----------------------+ if(str=="") continue; if(StringFind(str,"//")<10) continue; //------------------------------------------+ sub =StringSplit(str,u_sep,split); if(sub<2) continue; SetProperties(SName,split[0],split[1]); }
如果操作为空,或者在开始处(位置<;10)有注释符号“//”,则跳过该行。然后使用StringSplit()函数将字符串划分为子字符串。之后,字符串将传递给setproperties()函数,在该函数中设置了breed属性。功能代码结构:
void SetProperties(string name,string str1,string str2) { int n =StringTrimLeft(str1); n =StringTrimRight(str1); n =StringTrimLeft(str2); n =StringTrimRight(str2); if(str1=="SYMBOL_CUSTOM") { if(str2=="0"||str2=="false"){CustomSymbolSetInteger(name,SYMBOL_CUSTOM,false);} else {CustomSymbolSetInteger(name,SYMBOL_CUSTOM,true);} return; } if(str1=="SYMBOL_BACKGROUND_COLOR") { CustomSymbolSetInteger(name,SYMBOL_BACKGROUND_COLOR,StringToInteger(str2)); return; } if(str1=="SYMBOL_CHART_MODE") { if(str2=="SYMBOL_CHART_MODE_BID"){CustomSymbolSetInteger(name,SYMBOL_CHART_MODE,SYMBOL_CHART_MODE_BID);} if(str2=="SYMBOL_CHART_MODE_LAST"){CustomSymbolSetInteger(name,SYMBOL_CHART_MODE,SYMBOL_CHART_MODE_LAST);} return; } //--- 其他品种属性 }
如果用户在编辑时保留空白或制表符,则会为这些情况添加两个附加函数,即StringTrimLeft()和StringTrimRight()。
完整的代码可以在包含的文件属性中找到。MQH。
现在,所有的繁殖属性都是通过附加的文本文件设置的,对于其他的,它们需要重新编译。您可以检查下面附加的两个代码变体。第一个变量通过包含已被注释掉的文件来要求属性设置。
界面
为了便于编辑代码,请使用输入参数指定设置。如果没有可编辑的内容,我们可以考虑这个接口。对于最终版本,我开发了输入面板:
1
关于面板代码。此处使用的标准控件集来自以下头文件:
#include <Controls/Dialog.mqh> #include <Controls/Label.mqh> #include <Controls/Button.mqh> #include <Controls/ComboBox.mqh>
已为“确定”按钮创建事件处理程序。
//+------------------------------------------------------------------+ //| 事件处理 | //+------------------------------------------------------------------+ EVENT_MAP_BEGIN(CFormatPanel) ON_EVENT(ON_CLICK,BOK,OnClickButton) EVENT_MAP_END(CAppDialog) void CFormatPanel::OnClickButton(void) { // 上述属性 }
现在,将上述程序的几乎全部代码移到这个事件处理程序中。外部参数变为局部变量。
long SkipString =1; // 要跳过的字符串数量 DATE indate =yyyymmdd; // 源日期格式 TIME intime =hhdmmdss; // 源时间格式 int DatePosition =1; // 日期位置 int TimePosition =2; // 时间位置 // 其它参数
为每个控件编写了create()函数,因此在执行后将相应的值添加到控件列表中。例如,对日期格式执行以下操作:
//-----------多选框日期格式------------------------------------+ if(!CreateComboBox(CDateFormat,"ComDateFormat",x0,y0+h+1,x0+w,y0+2*h+1)) { return false; } CDateFormat.ListViewItems(6); CDateFormat.AddItem(" yyyy.mm.dd",0); CDateFormat.AddItem(" yyyymmdd",1); CDateFormat.AddItem(" yymmdd",2); CDateFormat.AddItem(" ddmmyy",3); CDateFormat.AddItem(" dd/mm/yy",4); CDateFormat.AddItem(" mm/dd/yy",5); CDateFormat.Select(1); }
然后,这些值从输入字段返回到相应的变量:
long sw; SkipString =StringToInteger(ESkip.Text()); sw =CDateFormat.Value(); switch(int(sw)) { case 0 :indate =yyyycmmcdd; break; case 1 :indate =yyyymmdd; break; case 2 :indate =yymmdd; break; case 3 :indate =ddmmyy; break; case 4 :indate =ddslmmslyy; break; case 5 :indate =mmslddslyy; break; } // 其他变量
nbsp;
这个版本已经实现了大量,所以如果需要编辑代码,应该使用输入版本。
第2部分逐步指南
本节逐步介绍创建自定义交换品种所需的操作。当可用报价具有任何标准格式且不需要编辑代码时,可以使用本指南。例如,如果报价是从finam.ru网站获得的。如果报价是非标准格式,您应该编辑第1部分中描述的代码。
因此,我们有一份包含金融产品交换报价的源文件。假设我们在本文开头从finam获得了它。别忘了我们需要一分钟的报价。
本文介绍了两种数据导入选项。您可以使用创建自定义符号脚本,以及创建符号面板智能交易系统,该系统具有一个输入面板。两个EAS的性能完全相同。例如,让我们看看使用输入面板进行操作。在这里提供的示例中,我们使用了来自莫斯科证券交易所的Berbank股票报价。报价附在下面的sb.csv文件中。
1。文件整理
首先,我们需要将报价文件保存到mql5/文件中。这与MQL5编程的概念有关,因为出于安全原因,文件的操作受到严格限制。找到所需目录的最简单方法是从MetaTrader打开它。在导航窗口中,右键单击文件夹,然后从关联菜单中选择“打开文件夹”。
2
源数据文件应保存到此文件夹中(程序文件的位置将在下一章中介绍)。现在可以在metaeditor中打开该文件。
3
添加规格。TXT到同一文件夹。它设置各种属性。
2。输入
下一步是确定数据格式和位置,选择文件属性并设置自定义变体的名称。下面显示了如何填充字段的示例。
4
数据应传输到面板。此版本使用固定点差异,因此不建模浮点差异。因此,您应该在这里输入适当的点差异。
5
填写完整的文件名,包括扩展名。
现在,在单击“OK”之前,请指定必要的产品种类规格。they can be found in the specifications.以前放在MQL5/文件中的TXT文件。
在元编辑器中编辑文本文件非常方便。主要原因是元编辑器支持数据突出显示。如果无法理解任何属性,请将光标悬停在其上,然后按F1。
6
属性以红色突出显示,值以绿色显示。未使用带注释的属性(//)并以绿色显示。请注意,逗号用于数据分离。编辑时不要删除属性。为了避免错误,您应该保留现有格式。
若要编辑属性,请取消注释所需属性(删除“/”)并设置适当的值。所附文件中设置的最小属性集:价格增量、点值、最小手数等。
莫斯科交易所的SBS银行股票需要所有这些功能(在源文件中)。其他金融产品需要不同的特性,所以您需要编辑属性。nbsp;
所需的最小属性集位于文件的开头。
通常,股票价格有两个小数位(符号_数字),点值等于0.01卢布。股票期货价格的小数点为0,点值为1卢布。请参阅moex.com上的规范。
设置完所有必需的属性后,单击“确定”。创建的自定义品种将显示在导航窗口中。在我的示例中,它以绿色突出显示。
7
打开图表检查:
8
一切都很好,所以现在您可以在策略测试仪中测试您的自定义品种。
定制品种设置的实施与标准品种相似。重要的一点是正确配置品种和规格。
例如,我们使用我们自己的数据来测试终端中可用的任何标准智能交易系统(移动平均在这里):
9
nbsp;
一切正常。如果需要添加新的报价单或更改属性,只需对现有品种重复上述操作。如果规范未更改,请单击“确定”,而不编辑属性。
文件
文件夹中的其他文件应存储在计算机中:
- CreateCustomSymbol脚本和代码:MQL5/脚本
- CREATESYMBOLSET智能交易系统和代码:MQL5/专家
- 包含文件格式函数,以及属性集,Nbsp规范:MQL5/包含
- 品种设置文件:MQL5/文件
本文由MetaQuotes Software Corp.翻译自俄语原文
,网址为https://www.mql5.com/ru/articles/5303。
MyFxtop迈投(www.myfxtop.com)-靠谱的外汇跟单社区,免费跟随高手做交易!
免责声明:本文系转载自网络,如有侵犯,请联系我们立即删除,另:本文仅代表作者个人观点,与迈投财经(www.myfxtop.cn)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。