
根据指定的分布法则为自定义品种的时间序列建模
内容
简介
MetaTrader 5 交易终端允许在操作中创建和使用自定义品种。 交易者有能力测试自己的货币对和其它金融产品。 本文提出了根据指定的分布法则创建和删除自定义品种,生成逐笔报价和柱线的方法。
它还提出了模拟趋势和各种图表形态的方法。 提到的现成脚本可用来操作最小设置的自定义品种,没有 MQL5 编程技能的交易者也能够充分发挥自定义品种的潜力。
创建和删除自定义品种
这篇早期的 文章 提出了一种基于现有品种在 MetaTrader 5 的“品种”窗口中创建自定义品种的方法。
我们建议依据最小配置的简单设置自动执行此过程。
该脚本有四个输入参数:
- 自定义品种的名称,
- 货币对或金融产品的简称,
- 货币对或金融产品的全称,
- 如果品种是基于基准品种创建的,此为基准货币或金融产品的简称,
//+------------------------------------------------------------------+ //| CreateSymbol.mq5 | //| Aleksey Zinovik | //| | //+------------------------------------------------------------------+ #property copyright "Aleksey Zinovik" #property script_show_inputs #property version "1.00" //+------------------------------------------------------------------+ //| 脚本程序开始函数 | //+------------------------------------------------------------------+ input string SName="ExampleCurrency"; input string CurrencyName="UCR"; input string CurrencyFullName="UserCurrency"; input string BaseName="EURUSD"; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnStart() { ResetLastError(); // 创建品种 if(!CustomSymbolCreate(SName,"\\Forex")) { if(SymbolInfoInteger(SName,SYMBOL_CUSTOM)) Print("Symbol ",SName," already exists!"); else Print("Error creating symbol. Error code: ",GetLastError()); } else { if(BaseName=="")// create new { // 字符串类型属性 if((SetProperty(SName,SYMBOL_CURRENCY_BASE,CurrencyName,"")) && // 基准货币 (SetProperty(SName,SYMBOL_CURRENCY_PROFIT,"USD",""))&& // 盈利货币 (SetProperty(SName,SYMBOL_CURRENCY_MARGIN,"USD",""))&& // 保证金货币 (SetProperty(SName,SYMBOL_DESCRIPTION,CurrencyName,""))&& // 品种的字符串描述(全名) (SetProperty(SName,SYMBOL_BASIS,"","")) && // 自定义品种的底层资产的名称 (SetProperty(SName,SYMBOL_FORMULA,"","")) && // 自定义品种的定价公式 (SetProperty(SName,SYMBOL_ISIN,"","")) && // ISIN 系统中交易品种的名称 (SetProperty(SName,SYMBOL_PAGE,"","")) && // 包含品种信息的网页 // 整数类型属性 (SetProperty(SName,SYMBOL_CHART_MODE,SYMBOL_CHART_MODE_BID,"")) && // 按竞买价绘制图表 (SetProperty(SName,SYMBOL_SPREAD,3,"")) && // 点差 (SetProperty(SName,SYMBOL_SPREAD_FLOAT,true,"")) && // 浮动点差 (SetProperty(SName,SYMBOL_DIGITS,5,"")) && // 精度 (SetProperty(SName,SYMBOL_TICKS_BOOKDEPTH,10,"")) && // 预置订单深度 (SetProperty(SName,SYMBOL_BACKGROUND_COLOR,White,""))&& // 市场观察中品种所用的背景颜色 (SetProperty(SName,SYMBOL_TRADE_MODE,SYMBOL_TRADE_MODE_FULL,""))&& // 订单执行类型:完全访问权限 (SetProperty(SName,SYMBOL_TRADE_EXEMODE,SYMBOL_TRADE_EXECUTION_INSTANT,""))&& // 成交执行模式:即时执行 (SetProperty(SName,SYMBOL_ORDER_GTC_MODE,SYMBOL_ORDERS_GTC,""))&& // 止损和止盈订单限期:最佳直至取消 (SetProperty(SName,SYMBOL_FILLING_MODE,SYMBOL_FILLING_FOK,""))&& // 订单执行模式:填单或终止 (SetProperty(SName,SYMBOL_EXPIRATION_MODE,SYMBOL_EXPIRATION_GTC,""))&& // 订单限期模式:无限定时间,直至明确取消 (SetProperty(SName,SYMBOL_ORDER_MODE,127,"")) && // 订单类型:所有订单类型 (SetProperty(SName,SYMBOL_TRADE_CALC_MODE,SYMBOL_CALC_MODE_FOREX,""))&& // 计算合约价值的方法 (SetProperty(SName,SYMBOL_MARGIN_HEDGED_USE_LEG,false,""))&& // 使用较大的分支计算对冲保证金 (SetProperty(SName,SYMBOL_SWAP_MODE,SYMBOL_SWAP_MODE_POINTS,""))&& // 掉期利率计算模型:以点数为单位计算掉期利率 (SetProperty(SName,SYMBOL_SWAP_ROLLOVER3DAYS,WEDNESDAY,"")) && // 掉期日:周三 (SetProperty(SName,SYMBOL_OPTION_MODE,0,"")) && // 期权类型 (SetProperty(SName,SYMBOL_OPTION_RIGHT,0,"")) && // 期权加权 (SetProperty(SName,SYMBOL_TRADE_STOPS_LEVEL,0,"")) && // 设置止损订单与当前收盘价的最小间距 (SetProperty(SName,SYMBOL_TRADE_FREEZE_LEVEL,0,"")) && // 交易操作的冻结间距(以点数为单位) (SetProperty(SName,SYMBOL_START_TIME,0,"")) && // 该品种交易开始日期(通常用于期货) (SetProperty(SName,SYMBOL_EXPIRATION_TIME,0,"")) && // 该品种交易结束日期(通常用于期货) // 双精度类型属性 (SetProperty(SName,SYMBOL_OPTION_STRIKE,0,"")) && // 期权执行价格 (SetProperty(SName,SYMBOL_SESSION_PRICE_LIMIT_MAX,0,"")) && // 时段的最低允许价格数值 (SetProperty(SName,SYMBOL_SESSION_PRICE_LIMIT_MIN,0,"")) && // 时段的最高允许价格数值 (SetProperty(SName,SYMBOL_SESSION_PRICE_SETTLEMENT,0,"")) && // 当前时段的结算价格 (SetProperty(SName,SYMBOL_TRADE_ACCRUED_INTEREST,0,"")) && // 应计利息(债券) (SetProperty(SName,SYMBOL_TRADE_FACE_VALUE,0,"")) && // 面值(债券) (SetProperty(SName,SYMBOL_TRADE_LIQUIDITY_RATE,0,"")) && // 流动资金率(用于抵押品种) (SetProperty(SName,SYMBOL_TRADE_TICK_SIZE,0.00001,"")) && // 最低价格变化 (SetProperty(SName,SYMBOL_TRADE_TICK_VALUE,1,"")) && // 逐笔报价值 (SetProperty(SName,SYMBOL_TRADE_CONTRACT_SIZE,100000,"")) && // 交易合约规模 (SetProperty(SName,SYMBOL_POINT,0.00001,"")) && // 点数值 (SetProperty(SName,SYMBOL_VOLUME_MIN,0.01,"")) && // 成交执行的最小交易量 (SetProperty(SName,SYMBOL_VOLUME_MAX,500.00,"")) && // 成交执行的最大交易量 (SetProperty(SName,SYMBOL_VOLUME_STEP,0.01,"")) && // 成交执行的最小交易量变动间距 (SetProperty(SName,SYMBOL_VOLUME_LIMIT,0,"")) && // 此品种单向(买入或卖出)持仓和挂单的最大允许总交易量 (SetProperty(SName,SYMBOL_MARGIN_INITIAL,0,"")) && // 初始保证金 (SetProperty(SName,SYMBOL_MARGIN_MAINTENANCE,0,"")) && // 维持保证金 (SetProperty(SName,SYMBOL_MARGIN_HEDGED,100000,"")) && // 一个品种的一手反向持仓的合约或保证金大小 (SetProperty(SName,SYMBOL_SWAP_LONG,-0.7,"")) && // 多头的掉期利率 (SetProperty(SName,SYMBOL_SWAP_SHORT,-1,""))) // 空头的掉期利率 Print("Symbol ",SName," created successfully"); else Print("Error setting symbol properties. Error code: ",GetLastError()); } else// 依据基准品种创建 { if((SetProperty(SName,SYMBOL_CURRENCY_BASE,CurrencyName,"")) && (SetProperty(SName,SYMBOL_CURRENCY_PROFIT,"",BaseName)) && (SetProperty(SName,SYMBOL_CURRENCY_MARGIN,"",BaseName)) && (SetProperty(SName,SYMBOL_DESCRIPTION,CurrencyFullName,"")) && (SetProperty(SName,SYMBOL_BASIS,"",BaseName)) && (SetProperty(SName,SYMBOL_FORMULA,"",BaseName)) && (SetProperty(SName,SYMBOL_ISIN,"",BaseName)) && (SetProperty(SName,SYMBOL_PAGE,"",BaseName)) && (SetProperty(SName,SYMBOL_CHART_MODE,0,BaseName)) && (SetProperty(SName,SYMBOL_SPREAD,0,BaseName)) && (SetProperty(SName,SYMBOL_SPREAD_FLOAT,0,BaseName)) && (SetProperty(SName,SYMBOL_DIGITS,0,BaseName)) && (SetProperty(SName,SYMBOL_TICKS_BOOKDEPTH,0,BaseName)) && (SetProperty(SName,SYMBOL_BACKGROUND_COLOR,0,BaseName)) && (SetProperty(SName,SYMBOL_TRADE_MODE,0,BaseName)) && (SetProperty(SName,SYMBOL_TRADE_EXEMODE,0,BaseName)) && (SetProperty(SName,SYMBOL_ORDER_GTC_MODE,0,BaseName)) && (SetProperty(SName,SYMBOL_FILLING_MODE,0,BaseName)) && (SetProperty(SName,SYMBOL_EXPIRATION_MODE,0,BaseName)) && (SetProperty(SName,SYMBOL_ORDER_MODE,0,BaseName)) && (SetProperty(SName,SYMBOL_TRADE_CALC_MODE,0,BaseName)) && (SetProperty(SName,SYMBOL_MARGIN_HEDGED_USE_LEG,0,BaseName)) && (SetProperty(SName,SYMBOL_SWAP_MODE,0,BaseName)) && (SetProperty(SName,SYMBOL_SWAP_ROLLOVER3DAYS,0,BaseName)) && (SetProperty(SName,SYMBOL_OPTION_MODE,0,BaseName)) && (SetProperty(SName,SYMBOL_OPTION_RIGHT,0,BaseName)) && (SetProperty(SName,SYMBOL_TRADE_STOPS_LEVEL,0,BaseName)) && (SetProperty(SName,SYMBOL_TRADE_FREEZE_LEVEL,0,BaseName)) && (SetProperty(SName,SYMBOL_START_TIME,0,BaseName)) && (SetProperty(SName,SYMBOL_EXPIRATION_TIME,0,BaseName)) && (SetProperty(SName,SYMBOL_OPTION_STRIKE,0,BaseName)) && (SetProperty(SName,SYMBOL_SESSION_PRICE_LIMIT_MAX,0,BaseName)) && (SetProperty(SName,SYMBOL_SESSION_PRICE_LIMIT_MIN,0,BaseName)) && (SetProperty(SName,SYMBOL_SESSION_PRICE_SETTLEMENT,0,BaseName)) && (SetProperty(SName,SYMBOL_TRADE_ACCRUED_INTEREST,0,BaseName)) && (SetProperty(SName,SYMBOL_POINT,0,BaseName)) && (SetProperty(SName,SYMBOL_TRADE_CONTRACT_SIZE,0,BaseName)) && (SetProperty(SName,SYMBOL_TRADE_FACE_VALUE,0,BaseName)) && (SetProperty(SName,SYMBOL_TRADE_LIQUIDITY_RATE,0,BaseName)) && (SetProperty(SName,SYMBOL_TRADE_TICK_SIZE,0,BaseName)) && (SetProperty(SName,SYMBOL_TRADE_TICK_VALUE,0,BaseName)) && (SetProperty(SName,SYMBOL_VOLUME_MIN,0,BaseName)) && (SetProperty(SName,SYMBOL_VOLUME_MAX,0,BaseName)) && (SetProperty(SName,SYMBOL_VOLUME_STEP,0,BaseName)) && (SetProperty(SName,SYMBOL_VOLUME_LIMIT,0,BaseName)) && (SetProperty(SName,SYMBOL_MARGIN_INITIAL,0,BaseName)) && (SetProperty(SName,SYMBOL_MARGIN_MAINTENANCE,0,BaseName)) && (SetProperty(SName,SYMBOL_MARGIN_HEDGED,0,BaseName)) && (SetProperty(SName,SYMBOL_SWAP_LONG,0,BaseName)) && (SetProperty(SName,SYMBOL_SWAP_SHORT,0,BaseName))) Print("Symbol ",SName," created successfully"); else Print("Error setting symbol properties. Error code: ",GetLastError()); } if(SymbolSelect(SName,true)) Print("Symbol ",SName," selected in Market Watch"); else Print("Error selecting symbol in Market Watch. Error code: ",GetLastError()); } } // 用于设置品种属性的函数 bool SetProperty(string SymName,ENUM_SYMBOL_INFO_STRING SProp,string PropValue,string BaseSymName) { ResetLastError(); if(BaseSymName=="") { if(CustomSymbolSetString(SymName,SProp,PropValue)) return true; else Print("Error setting symbol property: ",SProp,". Error code: ",GetLastError()); } else { string SValue=SymbolInfoString(BaseSymName,SProp); if(CustomSymbolSetString(SymName,SProp,SValue)) return true; else Print("Error setting symbol property: ",SProp,". Error code: ",GetLastError()); } return false; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool SetProperty(string SymName,ENUM_SYMBOL_INFO_INTEGER IProp,long PropValue,string BaseSymName) { ResetLastError(); if(BaseSymName=="") { if(CustomSymbolSetInteger(SymName,IProp,PropValue)) return true; else Print("Error setting symbol property: ",IProp,". Error code: ",GetLastError()); } else { long IValue=SymbolInfoInteger(BaseSymName,IProp); if(CustomSymbolSetInteger(SymName,IProp,IValue)) return true; else Print("Error setting symbol property: ",IProp,". Error code: ",GetLastError()); } return false; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool SetProperty(string SymName,ENUM_SYMBOL_INFO_DOUBLE DProp,double PropValue,string BaseSymName) { ResetLastError(); if(BaseSymName=="") { if(CustomSymbolSetDouble(SymName,DProp,PropValue)) return true; else Print("Error setting symbol property: ",DProp,". Error code: ",GetLastError()); } else { double DValue=SymbolInfoDouble(BaseSymName,DProp); if(CustomSymbolSetDouble(SymName,DProp,DValue)) return true; else Print("Error setting symbol property: ",DProp,". Error code: ",GetLastError()); } return false; } //+------------------------------------------------------------------+
我们来详细考查脚本的代码。 首先,尝试运用 CustomSymbolCreate 函数创建品种:
if(!CustomSymbolCreate(SName,"\\Forex")) { if(SymbolInfoInteger(SName,SYMBOL_CUSTOM)) Print("Symbol ",SName," already exists!"); else Print("Error creating symbol. Error code: ",GetLastError()); }
该品种会在 Custom/Forex 文件夹中创建。 如果您想在“Custom”文件夹中创建自定义子文件夹(自定义组),请在 CustomSymbolCreate 函数的第二个参数中指定其名称。
接下来,为所创建品种设置属性。 如果未定义 BaseName 参数,则以用户定义参数为该品种设置。 例如,所列 EURUSD 货币对的属性:
// 字符串类型属性 if((SetProperty(SName,SYMBOL_CURRENCY_BASE,CurrencyName,"")) && // 基准货币 (SetProperty(SName,SYMBOL_CURRENCY_PROFIT,"USD",""))&& // 盈利货币 (SetProperty(SName,SYMBOL_CURRENCY_MARGIN,"UCR",""))&& // 保证金货币 ...
出于便利起见,这些属性分为几组。 首先,设置字符串类型的属性,然后设置整数,之后设置双精度。 如果这些属性设置成功,则会向日志写入有关成功创建品种的消息。 否则,会将设置品种属性时发生的错误代码写入日志。
如果 BaseName 参数的值不为空,则从基准品种的属性复制品种属性,其名称由 BaseName 参数定义。 例如,它可以是 EURUSD,USDCAD,GBPUSD 等等。
品种的属性由 SetProperty 函数设置,该函数在主脚本函数的代码之后进行了描述。 其它脚本中不使用此函数; 因此,它不会移至单独的包含类。
对于字符串,整数和双精度类型的属性,将创建 SetProperty 函数的单独实例。 函数 CustomSymbolSetString, CustomSymbolSetInteger, CustomSymbolSetDouble 用于设置自定义品种的属性。 函数 SymbolInfoString, SymbolInfoInteger, SymbolInfoDouble 用于获取基准品种的属性。
成功为创建的自定义品种设置属性之后,使用 SymbolSelect 函数在市场观察中选择它:
if(SymbolSelect(SName,true)) Print("Symbol ",SName," selected in Market Watch"); else Print("Error selecting symbol in Market Watch. Error code: ",GetLastError());
若要打开已创建品种的图表,必须为该品种加载逐笔报价或柱线。 用于生成逐笔报价和柱线的脚本将在下面讨论。
现在考查删除自定义品种的过程。 如果您在“品种”选项卡上选择想要删除的自定义品种,则您将不能始终执行此操作:
图例 1. 尝试删除市场观察中选择的品种
若要删除品种,必须将其从市场观察中删除 — 这可通过双击品种窗口中的品种来完成。 与此同时,您要删除的品种不应有正打开的图表和持仓。 因此,必须手动关闭此品种的所有图表和持仓。 这不是一个快速的过程,特别是如果为此品种打开了许多图表。 以下实现的小脚本,作为删除品种的示例(该脚本在文章附加的 DeleteSymbol.mq5 文件中):
//+------------------------------------------------------------------+ //| DeleteSymbol.mq5 | //| Aleksey Zinovik | //| | //+------------------------------------------------------------------+ #property copyright "Aleksey Zinovik" #property script_show_inputs #property version "1.00" //+------------------------------------------------------------------+ //| 脚本程序开始函数 | //+------------------------------------------------------------------+ input string SName="ExampleCurrency"; //+------------------------------------------------------------------+ //| 脚本程序开始函数 | //+------------------------------------------------------------------+ void OnStart() { ResetLastError(); if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))// 如果品种存在 { if(!CustomSymbolDelete(SName))// 尝试删除 { if(SymbolInfoInteger(SName,SYMBOL_SELECT))// 如果在市场观察里已选择它 { if(SymbolSelect(SName,false))// 尝试禁用并删除 { if(!CustomSymbolDelete(SName)) Print("Error deleting symbol ",SName,". Error code: ",GetLastError()); else Print("Symbol ",SName," deleted successfully"); } else { // 尝试关闭该品种图表 int i=0; long CurrChart=ChartFirst(); int i_id=0; long ChartIDArray[]; while(CurrChart!=-1) { // 循环遍历图表列表并存储标识符,来打开品种 SName 的图表 if(ChartSymbol(CurrChart)==SName) { ArrayResize(ChartIDArray,ArraySize(ChartIDArray)+1); ChartIDArray[i_id]=CurrChart; i_id++; } CurrChart=ChartNext(CurrChart); } // 关闭品种 SName 的所有图表 for(i=0;i<i_id;i++) { if(!ChartClose(ChartIDArray[i])) { Print("Error closing chart of symbol ",SName,". Error code: ",GetLastError()); return; } } // 禁用并删除品种 if(SymbolSelect(SName,false)) { if(!CustomSymbolDelete(SName)) Print("Error deleting symbol ",SName,". Error code: ",GetLastError()); else Print("Symbol ",SName," deleted successfully"); } else Print("Error disabling symbol ",SName," in Market Watch. Error code: ",GetLastError()); }//一并结束 SymbolSelect } // if(SymbolSelect(SName,false)) 则结束 else Print("Error deleting symbol ",SName,". Error code: ",GetLastError()); } else Print("Symbol ",SName," deleted successfully"); } else Print("Symbol ",SName," does not exist"); } //+------------------------------------------------------------------+
这是脚本的执行顺序:
- 首先检查是否存在名称为 SName 的品种,
- 如果找到该品种,尝试使用 CustomSymbolDelete 函数删除该品种,
- 如果无法删除该品种,请尝试使用 SimbolSelect 函数在市场观察中禁用该品种,
- 如果无法在市场观察中禁用该品种,请关闭该品种的所有已打开图表。
为此,循环遍历所有打开的图表并存储品名为 SName 打开的图表标识符:
while(CurrChart!=-1) { // 循环遍历图表列表并存储标识符,来打开品种 SName 的图表 if(ChartSymbol(CurrChart)==SName) { ArrayResize(ChartIDArray,ArraySize(ChartIDArray)+1); ChartIDArray[i_id]=CurrChart; i_id++; } CurrChart=ChartNext(CurrChart); }
关闭存储在 ChartIDArray 数组中所有标识符图表:
for(i=0;i<i_id;i++) { if(!ChartClose(ChartIDArray[i])) { Print("Error closing chart of symbol ",SName,". Error code: ",GetLastError()); return; } }
- 关闭所有图表后,尝试再次禁用市场观察中的品种并删除该品种,否则会在日志中打印错误消息。
如您所见,该脚本不提供自动关闭所选品种的持仓。 这样做是为了随机启动脚本而不会影响用户的交易操作。 如果您要删除品种的持仓,则需要事先手动将它们平仓。
生成逐笔报价和柱线
品种创建之后,需要为其加载交易历史数据:您可以使用策略测试器中内置的逐笔报价生成模式加载柱线并测试智能交易系统和指标,或者基于加载的逐笔报价生成新的逐笔报价和柱线并执行测试。 早前 文章 中展示的根据已有价格数据加载逐笔报价和柱线的方法。 本文将根据给定的分布法则提出自动生成逐笔报价和柱线的脚本。
这是柱线生成脚本的代码(脚本文件是 GetCandle.mq5):
//+------------------------------------------------------------------+ //| GetCandle.mq5 | //| Aleksey Zinovik | //| | //+------------------------------------------------------------------+ #property copyright "Aleksey Zinovik" #property link "" #property version "1.00" #property script_show_inputs #include </Math/Stat/Beta.mqh> #include </Math/Stat/Binomial.mqh> #include </Math/Stat/Cauchy.mqh> #include </Math/Stat/ChiSquare.mqh> #include </Math/Stat/Exponential.mqh> #include </Math/Stat/F.mqh> #include </Math/Stat/Gamma.mqh> #include </Math/Stat/Geometric.mqh> #include </Math/Stat/Hypergeometric.mqh> #include </Math/Stat/Logistic.mqh> #include </Math/Stat/Lognormal.mqh> #include </Math/Stat/NegativeBinomial.mqh> #include </Math/Stat/NoncentralBeta.mqh> #include </Math/Stat/NoncentralChiSquare.mqh> #include </Math/Stat/NoncentralF.mqh> #include </Math/Stat/NoncentralT.mqh> #include </Math/Stat/Normal.mqh> #include </Math/Stat/Poisson.mqh> #include </Math/Stat/T.mqh> #include </Math/Stat/Uniform.mqh> #include </Math/Stat/Weibull.mqh> //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ enum Distribution { Beta, Binomial, Cauchy, ChiSquare, Exponential, F, Gamma, Geometric, Hypergeometric, Logistic, Lognormal, NegativeBinomial, NoncentralBeta, NoncentralChiSquare, NoncentralF, NoncentralT, Normal, Poisson, T, Uniform, Weibull }; //+------------------------------------------------------------------+ //| 脚本程序开始函数 | //+------------------------------------------------------------------+ /*input params*/ input string SName="ExampleCurrency"; input datetime TBegin=D'2018.01.01 00:00:00'; // 柱线生成的开始时间 input datetime TEnd=D'2018.02.01 00:00:00'; // 柱线生成的结束时间 input int BarForReplace=1000; // 替换后的柱线数量 input double BaseOCHL=1; // OCHL 价格的基准数值 input double dOCHL=0.001; // OCHL 价格变化的比例因子 input ulong BaseRealVol=10000; // 基准交易量数值 input ulong dRealVol=100; // 实际交易量变化的比例因子 input ulong BaseTickVol=100; // 基准交易量数值 input ulong dTickVol=10; // 逐笔报价变化的比例因子 input ulong BaseSpread=0; // 基准点差数值 input ulong dSpread=1; // 点差变化的比例因子 input Distribution DistOCHL=Normal; // OCHL 价格的分布类型 input Distribution DistRealVol = Normal; // 实际交易量的分布类型 input Distribution DistTickVol = Normal; // 逐笔报价交易量的分布类型 input Distribution DistSpread = Uniform; // 点差的分布类型 input bool DiffCandle=false; // 生成不同类型的蜡烛 input double DistOCHLParam1=0; // OCHL 价格分布的参数1 input double DistOCHLParam2=1; // OCHL 价格分布的参数2 input double DistOCHLParam3=0; // OCHL 价格分布的参数3 input double DistRealParam1=0; // 实际交易量分布的参数1 input double DistRealParam2=1; // 实际交易量分布的参数2 input double DistRealParam3=0; // 实际交易量分布的参数3 input double DistTickParam1=0; // 逐笔报价交易量分布的参数1 input double DistTickParam2=1; // 逐笔报价交易量分布的参数2 input double DistTickParam3=0; // 逐笔报价交易量分布的参数3 input double DistSpreadParam1=0; // 点差分布的参数1 input double DistSpreadParam2=50; // 点差分布的参数2 input double DistSpreadParam3=0; // 点差分布的参数3 input bool FiveDayOfWeek=true; // true - 不要在周末生成逐笔报价 /*----输入参数----*/ int i_bar=0; // 分钟柱线的计数器 MqlRates MRatesMin[]; // 存储柱线的数组 MqlDateTime StructCTime; // 数据处理的时间结构 int DistErr=0; // 错误编号 bool IsErr=false; // 错误 double DistMass[4]; // 存储生成的 OCHL 数值的数组 int ReplaceBar=0; // 替换柱线的数量 double BValue[1]; // 复制最后收盘价的数组 //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnStart() { int i=0; // 循环计数器 double MaxVal,MinVal; // 最高价和最低价数值 int i_max,i_min; // DistMass 数组中最大值和最小值的索引 datetime TCurrent=TBegin; BValue[0]=BaseOCHL; if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))// 如果品种存在 { while(TCurrent<=TEnd) { if(FiveDayOfWeek) { TimeToStruct(TCurrent,StructCTime); if(!((StructCTime.day_of_week!=0) && (StructCTime.day_of_week!=6))) { if(StructCTime.day_of_week==0) TCurrent=TCurrent+86400-(StructCTime.hour*3600+StructCTime.min*60+StructCTime.sec); else TCurrent=TCurrent+2*86400-(StructCTime.hour*3600+StructCTime.min*60+StructCTime.sec); if(TCurrent>=TEnd) { if(ReplaceBar==0) Print("No trades in the specified range"); return; } } } ArrayResize(MRatesMin,ArraySize(MRatesMin)+1); MRatesMin[i_bar].open=0; MRatesMin[i_bar].close=0; MRatesMin[i_bar].high=0; MRatesMin[i_bar].low=0; // 填充开盘价 if(i_bar>0) MRatesMin[i_bar].open=MRatesMin[i_bar-1].close; else { if((CopyClose(SName,PERIOD_M1,TCurrent-60,1,BValue)==-1)) MRatesMin[i_bar].open=NormalizeDouble(BaseOCHL+dOCHL*GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3),_Digits); else MRatesMin[i_bar].open=BValue[0]; } // 生成最高价, 最低价 MaxVal=2.2250738585072014e-308; MinVal=1.7976931348623158e+308; i_max=0; i_min=0; for(i=0;i<3;i++) { DistMass[i]=NormalizeDouble(BaseOCHL+dOCHL*GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3),_Digits); if(IsErrCheck(DistErr)) return; if(MaxVal<DistMass[i]) { MaxVal=DistMass[i]; i_max=i; } if(MinVal>DistMass[i]) { MinVal=DistMass[i]; i_min=i; } } if(MaxVal<MRatesMin[i_bar].open) MRatesMin[i_bar].high=MRatesMin[i_bar].open; else MRatesMin[i_bar].high=MaxVal; if(MinVal>MRatesMin[i_bar].open) MRatesMin[i_bar].low=MRatesMin[i_bar].open; else MRatesMin[i_bar].low=MinVal; // 填充收盘价 for(i=0;i<3;i++) if((i!=i_max) && (i!=i_min)) { MRatesMin[i_bar].close=DistMass[i]; break; } // 生成交易量, 点差 MRatesMin[i_bar].real_volume=(long)(BaseRealVol+dRealVol*GetDist(DistRealVol,DistRealParam1,DistRealParam2,DistRealParam3)); if(IsErrCheck(DistErr)) return; MRatesMin[i_bar].tick_volume=(long)(BaseTickVol+dTickVol*GetDist(DistTickVol,DistTickParam1,DistTickParam2,DistTickParam3)); if(IsErrCheck(DistErr)) return; MRatesMin[i_bar].spread=(int)(BaseSpread+dSpread*GetDist(DistSpread,DistSpreadParam1,DistSpreadParam2,DistSpreadParam3)); if(IsErrCheck(DistErr)) return; // 存储时间 MRatesMin[i_bar].time=TCurrent; if(DiffCandle) { i=MathRand()%5; switch(i) { case 0:// 十字星 { MRatesMin[i_bar].close=MRatesMin[i_bar].open; break; } case 1:// 锤头 { if(MRatesMin[i_bar].open>MRatesMin[i_bar].close) MRatesMin[i_bar].high=MRatesMin[i_bar].open; else { if(MRatesMin[i_bar].open<MRatesMin[i_bar].close) MRatesMin[i_bar].high=MRatesMin[i_bar].close; } break; } case 2:// 星 { if(MRatesMin[i_bar].open>MRatesMin[i_bar].close) MRatesMin[i_bar].low=MRatesMin[i_bar].close; else { if(MRatesMin[i_bar].open<MRatesMin[i_bar].close) MRatesMin[i_bar].low=MRatesMin[i_bar].open; } break; } case 3:// Maribozu { if(MRatesMin[i_bar].open>MRatesMin[i_bar].close) { MRatesMin[i_bar].high=MRatesMin[i_bar].open; MRatesMin[i_bar].low=MRatesMin[i_bar].close; } else { if(MRatesMin[i_bar].open<MRatesMin[i_bar].close) { MRatesMin[i_bar].high=MRatesMin[i_bar].close; MRatesMin[i_bar].low=MRatesMin[i_bar].open; } } break; } default: break; } } // 检查替换柱线的时机 if(i_bar>=BarForReplace-1) { ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar].time); TCurrent=TCurrent+60; BValue[0]=MRatesMin[i_bar].close; i_bar=0; ArrayFree(MRatesMin); } else { i_bar++; TCurrent=TCurrent+60; } } if(i_bar>0) { i_bar--; ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar].time); } } else Print("Symbol ",SName," does not exist"); } //+------------------------------------------------------------------+ void ReplaceHistory(datetime DBegin,datetime DEnd) { ReplaceBar=CustomRatesReplace(SName,DBegin,DEnd,MRatesMin); if(ReplaceBar<0) Print("Error replacing bars. Error code: ",GetLastError()); else PrintFormat("Price history for period: %s to %s generated successfully. Created %i bars, added (replaced) %i bars",TimeToString(DBegin),TimeToString(DEnd),i_bar+1,ReplaceBar); } //+------------------------------------------------------------------+ double GetDist(Distribution d,double p1,double p2,double p3) { double res=0; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ switch(d) { /*Beta 分布*/ //p1,p2 - 第一和第二个参数 case Beta: {res=MathRandomBeta(p1,p2,DistErr); break;} /*二项分布*/ //p1 - 测试次数, p2 - 每次测试成功的概率 case Binomial: {res=MathRandomBinomial(p1,p2,DistErr); break;}; /*柯西分布*/ //p1 - 位置系数, p2 - 比例系数 case Cauchy: {res=MathRandomCauchy(p1,p2,DistErr); break;}; /*卡方分布*/ //p1 - 自由度 case ChiSquare: {res=MathRandomChiSquare(p1,DistErr); break;}; /*指数分布*/ //p1 - 分布的参数(lambda) case Exponential: {res=MathRandomExponential(p1,DistErr); break;}; /*费舍尔分布*/ //p1, p2 - 自由度 case F: {res=MathRandomF(p1,p2,DistErr); break;}; /*Gamma 分布*/ //p1 - 分布参数(整数), p2 - 比例系数 case Gamma: {res=MathRandomGamma(p1,p2,DistErr); break;}; /*几何分布*/ //p1 - 成功概率(在测试中事件发生) case Geometric: {res=MathRandomGeometric(p1,DistErr); break;}; /*超几何分布*/ //p1 - 对象总数, p2 - 具有所需特征的对象数量, p3 - 样本中的对象数量 case Hypergeometric: {res=MathRandomHypergeometric(p1,p2,p3,DistErr); break;}; /*逻辑分布*/ //p1 - 期望值, p2 - 比例系数 case Logistic: {res=MathRandomLogistic(p1,p2,DistErr); break;}; /*对数正态分布*/ //p1 - 预期值的对数, p2 - 标准差的对数 case Lognormal: {res=MathRandomLognormal(p1,p2,DistErr); break;}; /*负二项分布*/ //p1 - 成功测试的次数, p2 - 成功的概率 case NegativeBinomial: {res=MathRandomNegativeBinomial(p1,p2,DistErr); break;}; /*非中心 Beta 分布*/ //p1,p2 - 第一和第二个参数, p3 - 非中心参数 case NoncentralBeta: {res=MathRandomNoncentralBeta(p1,p2,p3,DistErr); break;}; /*非中心卡方分布*/ //p1 - 自由度, p2 - 非中心参数 case NoncentralChiSquare: {res=MathRandomNoncentralChiSquare(p1,p2,DistErr); break;}; /*非中心 F-分布*/ //p1, p2 - 自由度, p3 - 非中心参数 case NoncentralF: {res=MathRandomNoncentralF(p1,p2,p3,DistErr); break;}; /*非中心 t-分布*/ //p1 - 自由度, p2 - 非中心参数 case NoncentralT: {res=MathRandomNoncentralT(p1,p2,DistErr); break;}; /*正态分布*/ //p1 - 期望值, p2 - 标准差 case Normal: {res=MathRandomNormal(p1,p2,DistErr); break;}; /*泊松分布*/ //p1 - 期望值 case Poisson: {res=MathRandomPoisson(p1,DistErr); break;}; /*学生 t-分布*/ //p1 - 自由度 case T: {res=MathRandomT(p1,DistErr); break;}; /*统一分布*/ //p1 - 范围的下限, p2 - 范围的上限 case Uniform: {res=MathRandomUniform(p1,p2,DistErr); break;}; /*威布尔分布*/ //p1 - 形状参数, p2 - 比例参数 case Weibull: {res=MathRandomWeibull(p1,p2,DistErr); break;}; } if(DistErr!=0) return -1; else return res; } //+------------------------------------------------------------------+ bool IsErrCheck(int Err) { // 生成伪随机数时检查错误 switch(DistErr) { case(1): { MessageBox("Specified distribution parameters are not real numbers","Input parameters error",MB_ICONWARNING); return true; } case(2): { MessageBox("Specified distribution parameters are invalid","Input parameters error",MB_ICONWARNING); return true; } case(4): { MessageBox("Zero divide error","Input parameters error",MB_ICONWARNING); return true; } } return false; } //+------------------------------------------------------------------+
考查脚本的工作。 该脚本使用 Statistics 目录中的标准库文件,该文件库实现各种统计分布。 创建 Distribution 枚举,以便在形成每根柱线时选择生成伪随机数的分布法则(类型)。
伪随机数用来通过以下公式生成收盘价,最高价,最低价,实际交易量,交易量,点差:
(1)
其中 P(i) - 参数值, Base - 参数的基准值, step - 伪随机数变化的比例系数 (步幅), DistValue(i) - 根据指定的分布法则生成的伪随机变量。 参数 Base 和 step 由用户设置。 例如,考查形成开盘价的代码:
if(i_bar>0) MRatesMin[i_bar].open=MRatesMin[i_bar-1].close; else { if((CopyClose(SName,PERIOD_M1,TCurrent-60,1,BValue)==-1)) MRatesMin[i_bar].open=NormalizeDouble(BaseOCHL+dOCHL*GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3),_Digits); else MRatesMin[i_bar].open=BValue[0]; }
如果在启动脚本之前缺少柱线,或者由于某种原因 CopyClose 函数无法复制价格历史数据的最后一根柱线,则根据上述公式形成第一根柱线的开盘价:
- BaseOCHL - OCHL 的基准价,
- dOCHL - OCHL 价格变动的比例系数。
对于后续柱线,开盘价等于先前的收盘价,即新柱线的开盘价位于前一根柱线的收盘价。
伪随机变量值的生成由 GetDist 函数处理,该函数将分布类型和其参数值作为输入。
创建 IsErrCheck 函数来处理生成伪随机变量期间发生的错误。 作为输入,该函数接收 GetDist 函数执行期间确定的错误代码。 如果发生错误,则会中断脚本的执行,并在日志中显示错误消息。 在脚本末尾给出了 GetDist 和 IsErrCheck 函数的代码。
脚本生成分钟逐笔报价,并具有以下功能:
1) 仅在指定的时间范围内生成逐笔报价,有可能在周末不生成逐笔报价(输入参数 FiveDayOfWeek=true)
以下代码实现了在周末禁用生成逐笔报价:
if(FiveDayOfWeek) { TimeToStruct(TCurrent,StructCTime); if(!((StructCTime.day_of_week!=0) && (StructCTime.day_of_week!=6))) { if(StructCTime.day_of_week==0) TCurrent=TCurrent+86400-(StructCTime.hour*3600+StructCTime.min*60+StructCTime.sec); else TCurrent=TCurrent+2*86400-(StructCTime.hour*3600+StructCTime.min*60+StructCTime.sec); if(TCurrent>=TEnd) { if(ReplaceBar==0) Print("No trades in the specified range"); return; } } }
如果当前时间是周末,则转移到下一个交易日。
2) 该脚本允许部分替换柱线,并在更换生成的柱线后释放内存。
在BarForReplace 变量中指定数组清零后生成逐笔报价柱线的数量。 柱线替换在以下代码中实现:
if(i_bar>=BarForReplace) { ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar-1].time); i_bar=0; ArrayFree(MRatesMin); }
创建 ReplaceHistory 函数用于替换柱线,其代码在脚本末尾给出。 柱线由 CustomRatesReplace函数替换。 替换柱线后,计数器归零,并且释放存储所创建柱线的 MRatesMin 动态数组的缓冲区。
3) 该脚本允许生成不同类型的蜡烛
生成不同类型的蜡烛如下实现 (参数DiffCande = true):
if(DiffCandle) { i=MathRand()%5; switch(i) { case 0:// 十字星 { MRatesMin[i_bar].close=MRatesMin[i_bar].open; break; } case 1:// 锤头 { if(MRatesMin[i_bar].open>MRatesMin[i_bar].close) MRatesMin[i_bar].high=MRatesMin[i_bar].open; else { if(MRatesMin[i_bar].open<MRatesMin[i_bar].close) MRatesMin[i_bar].high=MRatesMin[i_bar].close; } break; } case 2:// 星 { if(MRatesMin[i_bar].open>MRatesMin[i_bar].close) MRatesMin[i_bar].low=MRatesMin[i_bar].close; else { if(MRatesMin[i_bar].open<MRatesMin[i_bar].close) MRatesMin[i_bar].low=MRatesMin[i_bar].open; } break; } case 3:// Maribozu { if(MRatesMin[i_bar].open>MRatesMin[i_bar].close) { MRatesMin[i_bar].high=MRatesMin[i_bar].open; MRatesMin[i_bar].low=MRatesMin[i_bar].close; } else { if(MRatesMin[i_bar].open<MRatesMin[i_bar].close) { MRatesMin[i_bar].high=MRatesMin[i_bar].close; MRatesMin[i_bar].low=MRatesMin[i_bar].open; } } break; } default: break; } }
因此,该脚本允许以相同的概率产生常规的多头或空头蜡烛,“十字星”,“锤头”,“星”和“maribozu”。
我们来演示脚本操作。 为此,请使用以下输入参数运行脚本:
图例 1. 脚本的输入参数
作为脚本执行的结果,将出现一个新品种 ExampleCurrency。 在脚本执行过程中生成了 33121 根分钟柱线。 图例 2 显示 ExampleCurrency 品种的分钟图表片段。
图例 2. ExampleCurrency 品种的分钟图表
有时可能没有足够的分钟柱线来测试智能交易系统或指标,则测试是基于实际或模拟的逐笔报价上执行的。
考查一个模拟逐笔报价和基于模拟逐笔报价生成分钟柱线的脚本。 文章附带的 GetTick.mq5 文件中提供了该脚本的完整代码。 此为带有注释的 OnStart() 函数的代码:
void OnStart() { if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))// 如果品种存在 { MqlDateTime StructCTime; long TBeginMSec=(long)TBegin*1000; // 生成逐笔报价的开始时间,单位为毫秒 long TEndMSec=(long)TEnd*1000; // 生成逐笔报价的结束时间,单位为毫秒 int ValMsec=0; // 生成随机时间偏移的变量,单位为毫秒 int SumSec=0; // 秒数计数器 int SumMSec=0; // 毫秒计数器 int PrevTickCount=0; // 保存一分钟内先前逐笔报价次数的变量 datetime TCurrent=TBegin; bool NewMinute=false; // 从逐笔报价生成开始时间复制价格数值 if(CopyClose(SName,PERIOD_M1,TCurrent-60,1,BValue)==-1) BValue[0]=Base; // 填充 LastTick 结构 LastTick.ask=BValue[0]; LastTick.bid=BValue[0]; LastTick.last=BValue[0]; LastTick.volume=baseVol; while(TBeginMSec<=TEndMSec) { if(FiveDayOfWeek) { TimeToStruct(TCurrent,StructCTime); if((StructCTime.day_of_week==0) || (StructCTime.day_of_week==6)) { if(StructCTime.day_of_week==0) { TCurrent=TCurrent+86400; TBeginMSec=TBeginMSec+86400000; } else { TCurrent=TCurrent+2*86400; TBeginMSec=TBeginMSec+2*86400000; } if(TBeginMSec>=TEndMSec) break; } } GetTick(TCurrent,TBeginMSec); if(IsErrCheck(DistErr)) return; i_tick++; if(RandomTickTime) { // 生成随机时间偏移 ValMsec=(int)((MathRand()%30000)/(MaxTickInMinute*0.25)+1); SumSec=SumSec+ValMsec; SumMSec=SumMSec+ValMsec; if(i_tick-PrevTickCount>=MaxTickInMinute) { TimeToStruct(TCurrent,StructCTime); StructCTime.sec=0; TCurrent=StructToTime(StructCTime)+60; TBeginMSec=TBeginMSec+60000-SumSec+ValMsec; SumSec=0; SumMSec=0; NewMinute=true; } else { if(SumSec>=60000) { // 将每分钟逐笔报价的计数器归零 SumSec=SumSec-60000*(SumSec/60000); NewMinute=true; } // 生成新的逐笔报价时间 TBeginMSec=TBeginMSec+ValMsec; if(SumMSec>=1000) { TCurrent=TCurrent+SumMSec/1000; SumMSec=SumMSec-1000*(SumMSec/1000); } } } else { TBeginMSec=TBeginMSec+60000/MaxTickInMinute; SumSec=SumSec+60000/MaxTickInMinute; SumMSec=SumMSec+60000/MaxTickInMinute; if(SumMSec>=1000) { TCurrent=TCurrent+SumMSec/1000; SumMSec=SumMSec-1000*(SumMSec/1000); } if(SumSec>=60000) { SumSec=SumSec-60000*(SumSec/60000); NewMinute=true; } } if(NewMinute) { // 将新柱线添加到数组中 ArrayResize(MRatesMin,ArraySize(MRatesMin)+1); if(ArraySize(MRatesMin)==1)// 如果是第一分钟 { MRatesMin[i_bar].open=NormalizeDouble(LastTick.bid,_Digits); MRatesMin[i_bar].tick_volume=(long)i_tick; } else { MRatesMin[i_bar].open=NormalizeDouble(MTick[PrevTickCount-1].bid,_Digits); MRatesMin[i_bar].tick_volume=(long)i_tick-PrevTickCount; } MRatesMin[i_bar].close=NormalizeDouble(MTick[i_tick-1].bid,_Digits); if(ValHigh>MRatesMin[i_bar].open) MRatesMin[i_bar].high=NormalizeDouble(ValHigh,_Digits); else MRatesMin[i_bar].high=MRatesMin[i_bar].open; if(ValLow<MRatesMin[i_bar].open) MRatesMin[i_bar].low=NormalizeDouble(ValLow,_Digits); else MRatesMin[i_bar].low=MRatesMin[i_bar].open; MRatesMin[i_bar].real_volume=(long)MTick[i_tick-1].volume; MRatesMin[i_bar].spread=(int)MathRound(MathAbs(MTick[i_tick-1].bid-MTick[i_tick-1].ask)/_Point); TimeToStruct(MTick[i_tick-1].time,StructCTime); StructCTime.sec=0; MRatesMin[i_bar].time=StructToTime(StructCTime); i_bar++; PrevTickCount=i_tick; ValHigh=2.2250738585072014e-308; ValLow=1.7976931348623158e+308; NewMinute=false; if(i_bar>=BarForReplace) { ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar-1].time); LastTick.bid=MTick[i_tick-1].bid; LastTick.ask=MTick[i_tick-1].ask; LastTick.last=MTick[i_tick-1].last; LastTick.volume=MTick[i_tick-1].volume; i_tick=0; i_bar=0; PrevTickCount=0; ArrayFree(MRatesMin); ArrayFree(MTick); } } }//循环结束 if(i_bar>0) ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar-1].time); } else Print("Symbol ",SName," does not exist"); }
在 OnStart() 函数的开头初始化变量。 在脚本的主循环中执行逐笔报价和柱线的生成:
while(TBeginMSec<=TEndMSec)
{
...
}
在循环开始时,如果 FiveDayOfWeek = true,则禁用周末生成逐笔报价,而逐笔报价时间偏移 1 天(如果逐笔报价时间对应于周日)或 2 天(如果逐笔报价时间对应周六):
if(FiveDayOfWeek)
{
...
}
接下来,使用 GetTick 函数生成逐笔报价:
GetTick(TCurrent,TBeginMSec); if(IsErrCheck(DistErr)) return; i_tick++;
如果在生成逐笔报价期间发生错误(函数 IsErrCheck 的值= true),则将中断脚本执行。 上述 GetCandle 函数的代码中已对 IsErrCheck 函数进行了描述。
我们来考查一下 GetTick 函数:
void GetTick(datetime TDate,long TLong) { ArrayResize(MTick,ArraySize(MTick)+1); // 填充新时间 MTick[i_tick].time=TDate; MTick[i_tick].time_msc=TLong; // 用前一个数值填充当前逐笔报价 if(ArraySize(MTick)>1) { MTick[i_tick].ask=MTick[i_tick-1].ask; MTick[i_tick].bid=MTick[i_tick-1].bid; MTick[i_tick].volume=MTick[i_tick-1].volume; MTick[i_tick].last=MTick[i_tick-1].last; } else { MTick[i_tick].ask=LastTick.ask; MTick[i_tick].bid=LastTick.bid; MTick[i_tick].last=LastTick.last; MTick[i_tick].volume=LastTick.volume; } // 填充当前逐笔报价 if(RandomTickValue) { double RBid=MathRandomUniform(0,1,DistErr); double RAsk=MathRandomUniform(0,1,DistErr); double RVolume=MathRandomUniform(0,1,DistErr); if(RBid>=0.5) { if(i_tick>0) MTick[i_tick].bid=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep; MTick[i_tick].last=MTick[i_tick].bid; MTick[i_tick].flags=10; if(RAsk>=0.5) { MTick[i_tick].ask=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep; MTick[i_tick].flags=MTick[i_tick].flags+4; } if(RVolume>=0.5) { MTick[i_tick].volume=(ulong)(baseVol+GetDist(DistVolume,DistVolumeParam1,DistVolumeParam2,DistVolumeParam3)*dStepVol); MTick[i_tick].flags=MTick[i_tick].flags+16; } } else { if(RAsk>=0.5) { MTick[i_tick].ask=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep; MTick[i_tick].flags=4; if(RVolume>=0.5) { MTick[i_tick].volume=(ulong)(baseVol+GetDist(DistVolume,DistVolumeParam1,DistVolumeParam2,DistVolumeParam3)*dStepVol); MTick[i_tick].flags=MTick[i_tick].flags+16; } } } }// if(RandomTickValue) 则结束 //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ else { MTick[i_tick].bid=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep; MTick[i_tick].ask=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep; MTick[i_tick].last=MTick[i_tick].bid; MTick[i_tick].volume=(ulong)(baseVol+GetDist(DistVolume,DistVolumeParam1,DistVolumeParam2,DistVolumeParam3)*dStepVol); MTick[i_tick].flags=30; }// if(RandomTickValue) 则结束 // 存储所生成的分钟柱线最大值和最小值 if(MTick[i_tick].bid>ValHigh) ValHigh=MTick[i_tick].bid; if(MTick[i_tick].bid<ValLow) ValLow=MTick[i_tick].bid; }//结束
作为输入,该函数采用的逐笔报价时间为日期格式,以毫秒为单位。 首先,当前逐笔报价填充前一次逐笔报价的数值,然后当前逐笔报价值将按如下修改:
1) 如果 RandomTickValue 参数值为 true,则每个竞卖价,竞买价和交易量参数将以概率 0.5 进行修改。 为此,生成 3 个均匀分布的随机变量:
double RBid=MathRandomUniform(0,1,DistErr); double RAsk=MathRandomUniform(0,1,DistErr); double RVolume=MathRandomUniform(0,1,DistErr);
如果 RBid>0.5,RAsk>0.5 - 根据所描述的公式(1)和/或 GetCandle 脚本修改竞买价 和/或 竞卖价。 如果竞卖价或竞买价已经改变,且 RVolume > 0.5,则根据公式(1)执行交易量修改。 每当更改时,为最后价格分配给竞买价。
2) 如果 RandomTickValue = false, 参数竞卖价,竞买价和交易量根据公式(1)计算。
逐笔报价标志(flags)的值设置如下:
- 竞买价变化 - flags=flags+2
- 竞卖价变化 - flags=flags+4
- 最后价格变化 - flags=flags+8
- 交易量变化 - flags=flags+16
在竞卖价,竞买价或交易量修改后,竞买价的最大值和最小值保存在变量 ValHigh 和 ValLow 当中。 变量 ValHigh 和 ValLow 的数值用于生成分钟柱线的最高价和最低价。
我们来继续考查 OnStart() 函数代码。
一旦生成当前逐笔报价后,将形成新的逐笔报价显现时间:
1) 如果参数 RandomTickTime = true, 则新逐笔报价时间按以下方式形成:
ValMsec=(int)((MathRand()%30000)/(MaxTickInMinute*0.25)+1);
接下来,该函数检查新分钟柱线的出现,并根据生成的秒数调整当前时间。 在每根分钟柱线中可生成的逐笔报价数量受到 MaxTickInMinute 变量的限制。 如果生成的逐笔报价数量超过 MaxTickInMinute 变量的值,则秒计数器(SumSec)和毫秒计数器(SumMSec)归零 ,新分钟柱线形成(NewMinute = true)。
2) 如果参数 RandomTickTime = false, 则每分钟柱线中生成的逐笔报价数量与 MaxTickInMinute 变量中指定的数量相同。
基于所生成的逐笔报价的分钟柱线如下执行:
- 分钟装修按数组加 1
ArrayResize(MRatesMin,ArraySize(MRatesMin)+1);
- 生成当前柱线的新开盘价和交易量:
if(ArraySize(MRatesMin)==1)// 如果是第一分钟 { MRatesMin[i_bar].open=NormalizeDouble(LastTick.bid,_Digits); MRatesMin[i_bar].tick_volume=(long)i_tick; } else { MRatesMin[i_bar].open=NormalizeDouble(MTick[PrevTickCount-1].bid,_Digits); MRatesMin[i_bar].tick_volume=(long)i_tick-PrevTickCount; }
当形成第一根分钟柱线时,将上一次逐笔报价的竞买价分配给新柱线的开盘价(由“ReplaceHistory”函数执行逐笔报价和柱线的替换),或在逐笔报价尚未形成时,以竞买价的基准值(参数 Base)替换柱线开盘价。 当形成后续的分钟柱线时,将前一根分钟柱线(收盘价)的最后一次逐笔报价中的竞买价分配为柱线开盘价。 规范化舍入会以当前运行脚本的图表品种的精度为准。
- 形成收盘价 - 最后一次逐笔报价的规范化竞买价:
MRatesMin[i_bar].close=NormalizeDouble(MTick[i_tick-1].bid,_Digits);
- 形成最高价和最低价。 考虑到开盘价可能大于最高价(ValHigh),或小于所生成逐笔报价的最小(ValLow)竞买价:
if(ValHigh>MRatesMin[i_bar].open) MRatesMin[i_bar].high=NormalizeDouble(ValHigh,_Digits); else MRatesMin[i_bar].high=MRatesMin[i_bar].open; if(ValLow<MRatesMin[i_bar].open) MRatesMin[i_bar].low=NormalizeDouble(ValLow,_Digits); else MRatesMin[i_bar].low=MRatesMin[i_bar].open;
- value and spread values are formed:
MRatesMin[i_bar].real_volume=(long)MTick[i_tick-1].volume; MRatesMin[i_bar].spread=(int)MathRound(MathAbs(MTick[i_tick-1].bid-MTick[i_tick-1].ask)/_Point);
当前柱线的交易量分配为最后一次逐笔报价的交易量,点差计算为最后一次逐笔报价的竞买价和竞卖价之间的差值。
- 柱线开盘时间分配为最后一次逐笔报价的创建时间,秒数清零:
TimeToStruct(MTick[i_tick-1].time,StructCTime); StructCTime.sec=0; MRatesMin[i_bar].time=StructToTime(StructCTime);
当前分钟柱线参数的形成过程的结论,分钟柱线计数器 (变量 i_bar) 增加,变量 ValHigh 和 ValLow 分配为双精度类型的最大值和最小值,分钟柱线标志 (NewMinute) 重置。 接下来,检查是否需要替换所形成的分钟柱线和逐笔报价的数量。 形成并替换后,柱线数量设置在 BarForReplace 变量中。
退出主循环后,替换剩余的柱线,如果有的话:
if(i_bar>0) ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar-1].time);
替换逐笔报价和柱线在 ReplaceHistory 函数中实现。 逐笔报价由 CustomTicksReplace 函数替换,柱线由 CustomRatesReplace 函数替换。
因此,上述 GetTick 脚本允许根据指定的分布法则生成逐笔报价,并使用逐笔报价形成分钟柱线并将生成的数据加载到自定义品种的价格历史中。
模拟趋势
上一节中考查过的 GetCandle 和 GetTick 脚本允许生成价格历史,且价格没有强烈波动,即比较平稳。 为了形成更复杂的行情情况并利用它们测试智能交易系统和指标,有必要模拟趋势价格变动。
出于这些目的,已创建了脚本 GetCandleTrend(附加在文章中的 GetCandleTrend.mq5)和 GetTickTrend(附加在文章中的 GetTickTrend.mq5)。 它们允许根据给定的价格走势法则模拟上升和下降趋势。 GetCandleTrend 脚本设计用于生成增加或减少分钟柱线。 GetTickTrend 脚本生成增加或减少的逐笔报价,然后形成分钟柱线,类似于 GetCandleTrend 脚本。
我们来考查 GetCandleTrend 脚本的操作。 分钟柱线的生成类似于 GetCandle 脚本中的分钟柱线,因此,仅考虑趋势生成方法。 脚本输入数据包含以下趋势参数:
input TrendModel TModel = Linear; // 趋势模型 input TrendType TType = Increasing; // 趋势类型 (递增/递减,随机) input double RandomTrendCoeff=0.5; // 趋势系数 (如果 RandomTrendCoeff<0.5 下跌趋势占主导地位; 如果 RandomTrendCoeff>0.5 - 上涨) input double Coeff1=0.1; // 趋势模型系数 k1 input double Coeff2=0.1; // 趋势模型系数 k2 input double Coeff3=0.1; // 趋势模型系数 k3 input int CountCandle=60; // 趋势方向随机变化的间隔(以柱线为单位)
系统将提示用户从 TrendModel 枚举中选择趋势模型设定:
enum TrendModel
{
Linear,
Hyperbolic,
Exp,
Power,
SecondOrderPolynomial,
LinearAndPeriodic,
LinearAndStochastic
};
以及 TrendType 枚举中的趋势类型设定:
enum TrendType
{
Increasing,
Decreasing,
Random
};
趋势是根据以下模型形成的:线性,双曲线,指数,幂,抛物线,线性周期和线性随机。 生成趋势的公式列于表 1 中:
趋势模型 | 公式 |
---|---|
线性 | |
双曲线 | |
指数 | |
幂 | |
抛物线 | |
线性周期 | |
线性随机 |
T(i) - 当前趋势值; k1, k2, k3 - 影响趋势增长 (下降) 的系数; N(0,1) - 随机变量按正态规律分布,期望值为零,单位方差。
作为趋势类型,用户可以选择递增,递减或随机趋势。 随机趋势是指在给定数量的蜡烛(蜡烛数量在 Countcandle 参数中设置)后其方向发生变化的趋势。
趋势形成在 ChooseTrend 函数中实现:
double ChooseTrend() { switch(TType) { case 0: return NormalizeDouble(BValue[0]+dOCHL*(GetTrend()+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)),_Digits); case 1: return NormalizeDouble(BValue[0]+dOCHL*(-GetTrend()+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)),_Digits); case 2: { if((i_trend%CountCandle==0) && (i_trend!=0)) { if(i_bar!=0) BValue[0]=MRatesMin[i_bar-1].close; LastRand=MathRandomUniform(0,1,DistErr); i_trend=0; } if(LastRand>RandomTrendCoeff) return NormalizeDouble(BValue[0]+dOCHL*(GetModelTrend()+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)),_Digits); else return NormalizeDouble(BValue[0]+dOCHL*(-GetModelTrend()+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)),_Digits); } default:return 0; } }
在随机趋势的情况下,对于 CountCandle 参数中设置的每 N 分钟蜡烛,生成的随机变量 LastRand 均匀分布在 0 到 1 的范围内。 如果 LastRand 值大于 RandomTrendCoeff 参数,则趋势为升序,否则 — 降序。 RandomTrendCoeff 概率允许改变趋势变化的概率。 如果 RandomTrendCoeff<0.5 上升的趋势为主导,如果 RandomTrendCoeff>0.5 — 下降。
按指定模型生成趋势在 GetModelTrend 函数中实现:
double GetModelTrend() { switch(TModel) { case Linear: return Coeff1+Coeff2*i_trend; case Hyperbolic: { if(i_trend==0) return Coeff1; else return Coeff1+Coeff2/i_trend; } case Exp: { if(i_trend==0) return Coeff1; else return Coeff1+MathExp(Coeff2*i_trend); } case Power:return Coeff1+MathPow((double)i_trend,Coeff2); case SecondOrderPolynomial:return Coeff1+Coeff2*i_trend+Coeff3*i_trend*i_trend; case LinearAndPeriodic: return Coeff1*i_trend+sin(Coeff2*i_trend)+cos(Coeff3*i_trend); case LinearAndStochastic: { LastValue=Coeff1*i_trend+MathSqrt(Coeff2*(1-MathPow(exp(-Coeff3),2)))*MathRandomNormal(0,1,DistErr)+exp(-Coeff3)*LastValue; return LastValue; } default: return -1; } }
表 1 中显示的趋势模型在此函数中实现。
我们来研究针对不同的趋势模型生成价格图表。 使用以下参数,运行 GetTrendCandle 脚本来生成线性趋势:
图例 3. GetTrendCandle 脚本的参数
脚本执行后,打开 ExampleCurrency 品种的分钟图表:
图例 4. ExampleCurrency 品种的线性趋势模型分钟图表
从图表中可以看出,形成了线性趋势,可以通过系数 k1 和 k2 的变化来改变趋势倾斜角(增加/减少的速率)。
在脚本参数中指定 双曲线趋势模型: TModel = Hyperbolic, Coeff1 = 1, Coeff2 = 1000。 运行脚本后,将获得以下图表:
图例 4. ExampleCurrency 品种的双曲线趋势模型分钟图表
该模型具有以下特征:由于双曲函数属于反函数类,因此在选择上升趋势(TType = 增加)时趋势将下降。
我们来研究 指数趋势模型: TModel =Exp, Coeff1 = 1, Coeff2 = 0,1。 运行脚本后,将获得以下图表:
图例 5. ExampleCurrency 品种的指数趋势模型分钟图表
正如人们所预料的那样,随着蜡烛大小的增加,图表呈指数级增长趋势。
考查其它趋势模型:
幂趋势模型: TModel =Power, Coeff1 = 1, Coeff2 = 2。
图例 6. ExampleCurrency 品种的幂趋势模型分钟图表
可以看出,该图表与指数趋势模型类似,但增加更为平滑。
抛物线趋势模型: TModel = SecondOrderPolynomial, Coeff1 = 1, Coeff2 = 0,05, Coeff3 = 0,05。
图例 7. ExampleCurrency 品种的抛物线趋势模型分钟图表
抛物线模型与幂模型类似,但其趋势以更高的速率增加/减少。
线性周期趋势模型: TModel = LinearAndPeriodic, Coeff1 = 0.05, Coeff2 = 0,1, Coeff3 = 0,1。
图例 8. ExampleCurrency 品种的线性周期趋势模型分钟图表
在线性周期模型中,随着趋势的增加或减少,它根据周期法则改变其方向。
线性随机趋势模型: TModel = LinearAndStochastic, Coeff1 = 0.05, Coeff2 = 1, Coeff3 = 1。
图例 8. ExampleCurrency 品种的线性随机趋势模型分钟图表
在线性随机模型中,趋势通过围绕直线的随机波动而增大或减小,直线的倾斜度由系数 k1 定义。
上面讨论的趋势模型仅在分钟时间帧内具有特定的属性。 为这些模型生成的价格图表在时间帧 M15,M30,H1 及更高层次上看起来像线性函数。 为了在 M1 以外的时间帧上获得方向变化的趋势,有必要选择随机趋势类型(TType =随机),并指定在其之后尝试改变趋势方向的分钟蜡烛的数量。
使用以下参数运行脚本: TModel = LinearAndStochastic, TType = Random, Coeff1 = 0.05, Coeff2 = 1, Coeff3 = 1, RandomTrendCoeff=0.5, CountCandle=60。 获得以下 H1 时间帧的图表:
图例 9. ExampleCurrency 品种趋势随机变化的小时图表
设置参数 RandomTrendCoeff = 0.7,并运行脚本:
图例 10. ExampleCurrency 品种,RandomTrendCoeff = 0.7,趋势随机变化小时图表
如您所见,存在下降趋势,将 RandomTrendCoeff 更改为 0.3,则得到上升趋势:
图例 10. ExampleCurrency 品种,RandomTrendCoeff = 0.3,趋势随机变化小时图表
因此,可以使用 GetCandleTrend 脚本在更高时间帧内模拟趋势,同时生成分钟柱线。
GetTickTrend 脚本能够生成逐笔报价,并用其形成分钟柱线。 它还具有与 GetCandleTrend 脚本相同的功能。
模拟图表形态
图表形态广泛用于行情的技术分析。 许多交易者使用典型形态来搜索入场或离场时机。 此外,还开发了各种指标和智能系统,用于分析价格图表上的形态。
在本章节中,我们将展示如何使用上述脚本创建图表形态。 例如,研究创建“双顶”和“双底”形态的过程。 形态的外观如下图所示:
图例 11. "双顶" 形态
图例 12. "双底" 形态
利用 GetCandleTrend 脚本创建形态。 形态将在 H1 周期里形成。 若要形成每个形态,必须使用不同的输入参数运行 GetCandleTrend 脚本四次。 选择以下时间间隔,如图 11 和 12 所示为 t1-t5:
- t1 - 00:00 02.01.2018
- t2 - 13:00 02.01.2018
- t3 - 13:00 03.01.2018
- t4 - 13:00 04.01.2018
- t5 - 00:00 05.01.2018
以下脚本设置可生成“双顶”形态:
- 柱线生成开始时间: 00:00 02.01.2018
- 柱线生成结束时间: 12:00 02.01.2018
- 趋势模型: LinearAndStochastic
- 趋势类型: Random
- 趋势系数: 0.15
- 趋势模型系数 k1: 0.15
- 趋势模型系数 k2: 1
- 趋势模型系数 k3: 1
- 趋势方向随机变化的区间: 60
运行 #2:
- 柱线生成开始时间: 13:00 02.01.2018
- 柱线生成结束时间: 12:00 03.01.2018
- 趋势系数: 0.85
- 柱线生成开始时间: 13:00 03.01.2018
- 柱线生成结束时间: 12:00 04.01.2018
- 趋势系数: 0.15
- 柱线生成开始时间: 13:00 04.01.2018
- 柱线生成结束时间: 00:00 05.01.2018
- 趋势系数: 0.85
因此,在运行 GetCandleTrend 脚本四次后,将获得图例 13 中所示的价格图表。
图例 13. H1 周期的模拟“双顶”形态
模拟“双底”形态与此类似。 为此,按照“双顶”形态指定的设置运行 GetCandleTrend 脚本四次,仅需更改趋势因子:第一次运行时为 0.85,之后运行时为 0.15,0.85,0.15。 脚本的结果如图例 14 所示。
图例 14. H1 周期的模拟“双底”形态
与此类似,可以模拟其它形态。 最精确的形态是在分钟图表上获得的。 若要在其它时间帧内形成形态,必须在 GetCandleTrend 脚本的参数“Interval of random change in the trend direction”中指定所选时间帧包含的分钟蜡烛数量。 例如,对于 H1 时间帧 - 60,对于 H4 时间帧 - 240。
结束语
自定义品种是测试智能系统和指标的便捷而有用的工具。 在本文中,创建并研究了具有以下功能的脚本:
1) 创建和删除自定义品种
基于存在的品种,或手工指定属性的新品种创建自定义品种的方法。 实现删除品种的脚本能够根据品种关闭所有图表并将其从市场观察中删除。
2) 生成逐笔报价和柱线
脚本能够在指定的时间间隔内生成分钟柱线,并能够在周末(非交易)日生成柱线。 实现了生成不同类型的蜡烛:多头蜡烛或空头蜡烛,“十字星”,“锤头”,“星”和“maribozu”。 在生成大型装修按和逐笔报价数组时,还可以实现部分柱线和逐笔报价的替换以节省内存。
3) 模拟趋势
脚本能够根据不同的模型模拟趋势:线性,双曲线,指数,幂,抛物线,线性周期和线性随机。 也可在不同周期模拟趋势。
4) 模拟图表形态
使用 GetCandleTrend 脚本创建“双顶”和“双底”形态的一个展示示例。
提议的脚本可用于从分钟柱线和逐笔报价创建自定义价格历史记录,并测试和优化智能系统和指标。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/4566
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.



Я не сталкивался с такой проблемой.Все созданные пользовательские символы и открытые графики сохраняются после перезагрузки MT5.Возможно, вам стоит обновить вашу версию MT5.
Я не сталкивался с такой проблемой.Все созданные пользовательские символы и открытые графики сохраняются после перезагрузки MT5.Возможно, вам стоит обновить вашу версию MT5.
这是一个英语论坛。
请仅使用英语发帖。
如有必要,请使用翻译工具。
在 GetCandle.mq5 脚本中,所有这些行
可以替换为
#include <Math\Stat\Stat.mqh>