分享

走势延续模型

 Levy_X 2018-12-03

1. 概述

本文提供了一种走势延续模型的程序化定义。 主要思路是定义两个波浪 — 主浪和修正浪。 对于极值点,我应用分形以及“潜在”分形 — 尚未形成分形的极值点。 接着,我将尝试收集有关波浪走势的统计数据。 数据将加载到 CSV 文件。


2. 模型描述 - 常规特点

文章中所述的走势延续模型由两个波浪组成:主浪和修正浪。 图例 1 是该模型的示意性描述。 AB 是主浪,BC 是校正浪,而 CD 是走势主趋势的延续。

走势延续模型

图例 1. 走势延续模型

在图表上,这看起来如下:

AUDJPY H4 上的走势延续模型

图例 2. AUDJPY H4 上的走势延续模型


3. 图表上的模型识别原理

模型识别原理如表 1 所示。

表 1. 走势延续模型在趋势背景下的识别原理

下跌趋势的模型识别原理  # 上涨趋势的模型识别原理
1 极值柱线是其高/低值高于/低于前两根柱线高/低值的那根柱线 1 极值柱线是其高/低值高于/低于前两根柱线高/低值的那根柱线
2 修正浪应始终按照顶端极值的存在结束(点 С - 参见 图例 1图例 2 2 修正浪应始终按照底端极值的存在结束(点 С - 参见 图例 1图例 2
 3 修正浪的持续时间不能太长,应限制在几根柱线。  3 修正浪的持续时间不能太长,应限制在几根柱线。
 4 修正走势的高点 (点 С - 参见 图例 1图例 2) 应低于主要走势的高点 (点 A - 参见 图例 1图例 2)  4 修正走势的低点 (点 С - 参见 图例 1图例 2) 应高于主要走势的低点 (点 A - 参见 图例 1图例 2)
 5 入场点时效性原则 - 只应在入场点形成的确定时刻开仓  5 入场点时效性原则 - 只应在入场点形成的确定时刻开仓


4. 算法构造和编写代码

1. 输入参数,OnInit() 函数和初始变量声明

首先,我们需要包含 CTrade 类,以便简化对交易操作的访问:

//--- 包含文件 #include <Trade\Trade.mqh> //--- 进行交易操作的对象 CTrade  trade;

Next, define input parameters:

//--- 输入参数 input ENUM_TIMEFRAMES base_tf;  //基准周期时间帧 input ENUM_TIMEFRAMES work_tf;  //操作周期时间帧 input double SummRisk=100;      //每笔成交的总风险 input double sar_step=0.1;      //设置抛物线步幅 input double maximum_step=0.11; //设置抛物线最大步幅 input bool TP_mode=true;        //允许设置止盈 input int M=2;                  //利润与风险比率 input bool Breakeven_mode=true; //允许将持仓移动到盈亏平衡点 input double breakeven=1;       //利润与止损比率

在基准周期上,EA 定义入场方向,而操作周期用于定义入场点。

程序根据每笔成交的总风险计算手数。

EA 还可以根据指定的利润与风险比率(М 参数)设置止盈,并根据指定的利润与止损比率(breakeven 参数)将持仓移至盈亏平衡点。

在描述输入参数之后,为 base_tf 和 work_tf 时间帧声明指标句柄和数组变量:

//--- 声明指标句柄的变量 int Fractal_base_tf,Fractal_work_tf;             //iFractals 指标句柄 int Sar_base_tf,Sar_work_tf;                     //iSar 指标句柄 //--- 为 base_tf 声明数组 double High_base_tf[],Low_base_tf[];             //用于存储柱线高/低价格的数组 double Close_base_tf[],Open_base_tf[];           //用于存储柱线收盘/开盘价格的数组 datetime Time_base_tf[];                         //用于存储柱线开盘时间的数组 double Sar_array_base_tf[];                      //用于存储 iSar (抛物线) 指标价格的数组 double FractalDown_base_tf[],FractalUp_base_tf[];//用于存储 iFractals 指标价格的数组 //--- 为 work_tf 声明数组 double High_work_tf[],Low_work_tf[]; double Close_work_tf[],Open_work_tf[]; datetime Time_work_tf[]; double Sar_array_work_tf[]; double FractalDown_work_tf[],FractalUp_work_tf[];;

EA 应用了两个指标:用于定义极值部分的分形,和用于持仓尾随停止的抛物线。 我还将采用抛物线在 work_tf 操作时间帧内定义一个入场点。

然后在 OnInit() 函数中接收指标句柄,并用初始数据填充数组。

int OnInit()   { //--- 获取 iSar 指标句柄    Sar_base_tf=iSAR(Symbol(),base_tf,sar_step,maximum_step);    Sar_work_tf=iSAR(Symbol(),work_tf,sar_step,maximum_step); //--- 获取 iFractals 指标句柄    Fractal_base_tf=iFractals(Symbol(),base_tf);    Fractal_work_tf=iFractals(Symbol(),work_tf); //--- 设置 base_tf 数组的顺序为时间序列    ArraySetAsSeries(High_base_tf,true);    ArraySetAsSeries(Low_base_tf,true);    ArraySetAsSeries(Close_base_tf,true);    ArraySetAsSeries(Open_base_tf,true);    ArraySetAsSeries(Time_base_tf,true);;    ArraySetAsSeries(Sar_array_base_tf,true);    ArraySetAsSeries(FractalDown_base_tf,true);    ArraySetAsSeries(FractalUp_base_tf,true); //--- 初始并填充 base_tf 数组    CopyHigh(Symbol(),base_tf,0,1000,High_base_tf);    CopyLow(Symbol(),base_tf,0,1000,Low_base_tf);    CopyClose(Symbol(),base_tf,0,1000,Close_base_tf);    CopyOpen(Symbol(),base_tf,0,1000,Open_base_tf);    CopyTime(Symbol(),base_tf,0,1000,Time_base_tf);    CopyBuffer(Sar_base_tf,0,TimeCurrent(),1000,Sar_array_base_tf);    CopyBuffer(Fractal_base_tf,0,TimeCurrent(),1000,FractalUp_base_tf);    CopyBuffer(Fractal_base_tf,1,TimeCurrent(),1000,FractalDown_base_tf); //--- 设置 work_tf 数组的顺序为时间序列    ArraySetAsSeries(High_work_tf,true);    ArraySetAsSeries(Low_work_tf,true);    ArraySetAsSeries(Close_work_tf,true);    ArraySetAsSeries(Open_work_tf,true);    ArraySetAsSeries(Time_work_tf,true);    ArraySetAsSeries(Sar_array_work_tf,true);    ArraySetAsSeries(FractalDown_work_tf,true);    ArraySetAsSeries(FractalUp_work_tf,true); //--- 初始并填充 work_tf 数组    CopyHigh(Symbol(),work_tf,0,1000,High_work_tf);    CopyLow(Symbol(),work_tf,0,1000,Low_work_tf);    CopyClose(Symbol(),work_tf,0,1000,Close_work_tf);    CopyOpen(Symbol(),work_tf,0,1000,Open_work_tf);    CopyTime(Symbol(),work_tf,0,1000,Time_work_tf);    CopyBuffer(Sar_work_tf,0,TimeCurrent(),1000,Sar_array_work_tf);    CopyBuffer(Fractal_work_tf,0,TimeCurrent(),1000,FractalUp_work_tf);    CopyBuffer(Fractal_work_tf,1,TimeCurrent(),1000,FractalDown_work_tf); //---    return(INIT_SUCCEEDED);   }

首先,我们收到 指标'句柄,然后在 时间序列 中定义数组的顺序,并用数据填充数组。 我相信 1000 根柱线的数据对于 EA 操作来说已经足够了。

2. 一般参数

在此,我开始运用OnTick() 函数操作。

在“常规参数”部分中,我通常写入市价数据并声明设置仓位的变量。

// ------------------------------------------------------------------ //| 1. 常规参数 (开始) | // ------------------------------------------------------------------ //--- 市价数据market data //品种价格中的小数位数    int Digit=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); //定义当前品种的价格容量    double f=1;    if(Digit==5) {f=100000;}    if(Digit==4) {f=10000;}    if(Digit==3) {f=1000;}    if(Digit==2) {f=100;}    if(Digit==1) {f=10;} //---    double spread=SymbolInfoInteger(Symbol(),SYMBOL_SPREAD)/f;//考虑到价格容量,将点差降低到分数值    double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID);//数据依据竞买价    double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK);//数据依据竞卖价    double CostOfPoint=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_VALUE);//数据依据逐笔报价 //--- 用于设置仓位的手数计算变量    double RiskSize_points;//用于存储当前持仓的止损大小的变量    double CostOfPoint_position;//存储当前持仓的点数价格(参考每笔成交的风险)的变量    double Lot;//用于存储持仓手数的变量    double SLPrice_sell,SLPrice_buy;//存储止损价位的变量 //--- 用于存储柱线编号上数据的变量    int bars_base_tf=Bars(Symbol(),base_tf);    int bars_work_tf=Bars(Symbol(),work_tf); //--- 用于存储持仓数据的变量    string P_symbol; //持仓品种    int P_type,P_ticket,P_opentime;//开仓类型, 单号和时间 // ------------------------------------------------------------------ //| 1. 常规参数 (结束) | // ------------------------------------------------------------------

3. 更新数组数据

数组最初在 OnInit() 函数中填充,但数组数据应始终保持相关性。 在每次逐笔报价中填充数组意味着系统负载太重,会大大减慢操作。 因此,建议出现新柱线时再重新填充数组。

为此,使用以下结构:

   static datetime LastBar_base_tf=0;//用于定义新柱线的变量    datetime ThisBar_base_tf=(datetime)SeriesInfoInteger(_Symbol,base_tf,SERIES_LASTBAR_DATE);//当前柱线时间    if(LastBar_base_tf!=ThisBar_base_tf)//如果时间不匹配,则表明出现一根新柱线      {          //在此填充数组      }

使用这种方法,零号柱线的数据会丢失,因此,我已经为索引 #0 柱线上的数据包含了单独的数组。

我们还应该用分形数据分别更新数组。 每当第 #0 柱线的极值高于或低于前两个极值点时,应重新填充它们。

填充数组的示例在下面提供。

1. 出现新柱线时填充数组

首先,在出现新柱线时填充数组:

// ------------------------------------------------------------------ //| 2.1 出现新柱线时填充数组(开始) | // ------------------------------------------------------------------ //--- 针对 base_tf //--- 设置数组顺序为时间序列    ArraySetAsSeries(High_base_tf,true);    ArraySetAsSeries(Low_base_tf,true);    ArraySetAsSeries(Close_base_tf,true);    ArraySetAsSeries(Open_base_tf,true);    ArraySetAsSeries(Time_base_tf,true);    ArraySetAsSeries(Sar_array_base_tf,true);    ArraySetAsSeries(FractalDown_base_tf,true);    ArraySetAsSeries(FractalUp_base_tf,true); //--- 填充数组    static datetime LastBar_base_tf=0;//用于定义新传入柱线的变量    datetime ThisBar_base_tf=(datetime)SeriesInfoInteger(_Symbol,base_tf,SERIES_LASTBAR_DATE);//当前柱线开盘时间    if(LastBar_base_tf!=ThisBar_base_tf)//如果时间不匹配,则表明出现一根新柱线      {       CopyHigh(Symbol(),base_tf,0,1000,High_base_tf);       CopyLow(Symbol(),base_tf,0,1000,Low_base_tf);       CopyClose(Symbol(),base_tf,0,1000,Close_base_tf);       CopyOpen(Symbol(),base_tf,0,1000,Open_base_tf);       CopyTime(Symbol(),base_tf,0,1000,Time_base_tf);       CopyBuffer(Sar_base_tf,0,TimeCurrent(),1000,Sar_array_base_tf);       CopyBuffer(Fractal_base_tf,0,TimeCurrent(),1000,FractalUp_base_tf);       CopyBuffer(Fractal_base_tf,1,TimeCurrent(),1000,FractalDown_base_tf);       LastBar_base_tf=ThisBar_base_tf;      } //--- 针对 work_tf //--- 设置数组顺序为时间序列    ArraySetAsSeries(High_work_tf,true);    ArraySetAsSeries(Low_work_tf,true);    ArraySetAsSeries(Close_work_tf,true);    ArraySetAsSeries(Open_work_tf,true);    ArraySetAsSeries(Time_work_tf,true);    ArraySetAsSeries(Sar_array_work_tf,true);    ArraySetAsSeries(FractalDown_work_tf,true);    ArraySetAsSeries(FractalUp_work_tf,true); //--- 填充数组    static datetime LastBar_work_tf=0;//用于定义新柱线的变量    datetime ThisBar_work_tf=(datetime)SeriesInfoInteger(_Symbol,work_tf,SERIES_LASTBAR_DATE);//当前柱线开盘时间    if(LastBar_work_tf!=ThisBar_work_tf)//如果时间不匹配,则表明出现一根新柱线      {       CopyHigh(Symbol(),work_tf,0,1000,High_work_tf);       CopyLow(Symbol(),work_tf,0,1000,Low_work_tf);       CopyClose(Symbol(),work_tf,0,1000,Close_work_tf);       CopyOpen(Symbol(),work_tf,0,1000,Open_work_tf);       CopyTime(Symbol(),work_tf,0,1000,Time_work_tf);       CopyBuffer(Sar_work_tf,0,TimeCurrent(),1000,Sar_array_work_tf);       CopyBuffer(Fractal_work_tf,0,TimeCurrent(),1000,FractalUp_work_tf);       CopyBuffer(Fractal_work_tf,1,TimeCurrent(),1000,FractalDown_work_tf);       LastBar_work_tf=ThisBar_work_tf;      } // ------------------------------------------------------------------ //| 2.1 出现新柱线时填充数组 (结束end) | // ------------------------------------------------------------------

2. 使用柱线 #0 的数据填充数组

索引 #1 或更高柱线上的数据现在始终保持相关,而索引 #0 柱线上的数据仍然过时。 我已经为存储零号柱线上的数据包含了单独数组:

// ------------------------------------------------------------------ //| 2.2 用 #0 柱线上的数据填充数组 (开始) | // ------------------------------------------------------------------ //--- 针对 base_tf //--- 声明数组    double High_base_tf_0[],Low_base_tf_0[];    double Close_base_tf_0[],Open_base_tf_0[];    datetime Time_base_tf_0[];    double Sar_array_base_tf_0[]; //--- 设置数组顺序为时间序列    ArraySetAsSeries(High_base_tf_0,true);    ArraySetAsSeries(Low_base_tf_0,true);    ArraySetAsSeries(Close_base_tf_0,true);    ArraySetAsSeries(Open_base_tf_0,true);    ArraySetAsSeries(Time_base_tf_0,true);    ArraySetAsSeries(Sar_array_base_tf_0,true); //--- 填充数组    CopyHigh(Symbol(),base_tf,0,1,High_base_tf_0);    CopyLow(Symbol(),base_tf,0,1,Low_base_tf_0);    CopyClose(Symbol(),base_tf,0,1,Close_base_tf_0);    CopyOpen(Symbol(),base_tf,0,1,Open_base_tf_0);    CopyTime(Symbol(),base_tf,0,1,Time_base_tf_0);    CopyBuffer(Sar_base_tf,0,TimeCurrent(),1,Sar_array_base_tf_0); //--- 针对 work_tf //--- 声明数组    double High_work_tf_0[],Low_work_tf_0[];    double Close_work_tf_0[],Open_work_tf_0[];    datetime Time_work_tf_0[];    double Sar_array_work_tf_0[]; //--- 设置数组顺序为时间序列    ArraySetAsSeries(High_work_tf_0,true);    ArraySetAsSeries(Low_work_tf_0,true);    ArraySetAsSeries(Close_work_tf_0,true);    ArraySetAsSeries(Open_work_tf_0,true);    ArraySetAsSeries(Time_work_tf_0,true);    ArraySetAsSeries(Sar_array_work_tf_0,true); //--- 填充数组    CopyHigh(Symbol(),work_tf,0,1,High_work_tf_0);    CopyLow(Symbol(),work_tf,0,1,Low_work_tf_0);    CopyClose(Symbol(),work_tf,0,1,Close_work_tf_0);    CopyOpen(Symbol(),work_tf,0,1,Open_work_tf_0);    CopyTime(Symbol(),work_tf,0,1,Time_work_tf_0);    CopyBuffer(Sar_work_tf,0,TimeCurrent(),1,Sar_array_work_tf_0); // ------------------------------------------------------------------ //| 2.2 用 #0 柱线上的数据填充数组 (结束) | // ------------------------------------------------------------------

3. 更新分形数据

应更新含有分形数据的数组。 每次 #0 柱线的极值都高于或低于前两根时,应重新填充数组:

// ------------------------------------------------------------------ //| 2.3 更新分形数据 (开始) | // ------------------------------------------------------------------ //--- 针对 base_tf    if(High_base_tf_0[0]>High_base_tf[1] && High_base_tf_0[0]>High_base_tf[2])      {       CopyBuffer(Fractal_base_tf,0,TimeCurrent(),1000,FractalUp_base_tf);      }    if(Low_base_tf_0[0]<Low_base_tf[1] && Low_base_tf_0[0]<Low_base_tf[2])      {       CopyBuffer(Fractal_base_tf,1,TimeCurrent(),1000,FractalDown_base_tf);      } //--- 针对 work_tf    if(High_work_tf_0[0]>High_work_tf[1] && High_work_tf_0[0]>High_work_tf[2])      {       CopyBuffer(Fractal_work_tf,0,TimeCurrent(),1000,FractalUp_work_tf);      }    if(Low_work_tf_0[0]<Low_work_tf[1] && Low_work_tf_0[0]<Low_work_tf[2])      {       CopyBuffer(Fractal_work_tf,1,TimeCurrent(),1000,FractalDown_work_tf);      } // ------------------------------------------------------------------ //| 2.3 更新分形数据 (结束) | // ------------------------------------------------------------------

4. 搜索极值

我们回到走势延续模型。 为此,我们需要回顾 图例 2

АВ 段是主浪,而 ВС 是修正浪。 根据模型识别原理,修正浪应始终以极值结束,因为这是一个分形。 在图像上,它被标记为 С。 应该从这一点开始搜索极值,直到检测到其余的极值。 然而,在入场的那一刻,形成的(确认的)分形可能已不存在。 所以,当柱线极值高于/低于前两根柱线时,我们需要寻找一种情况 — 这种柱线的高/低将形成点 С。 另外,请记住,在入场之时,修正走势的高/低点(点 С)可以位于零号或索引大于零号的柱线上。

表 2 示意极值定义的顺序。

表 2. 极值定义序列

# 对于下跌趋势 对于上涨趋势
1 寻找修正走势高位(点 С) 寻找修正走势低位(点 С)
2 自修正走势的高点寻找下一个顶端极值(点 А) 自修正走势的低点寻找下一个底端极值(点 А)
3 在点 C 和 A 之间寻找点 В(修正走势低点) 在点 C 和 A 之间寻找点 В(修正走势高点)
1. 搜索下跌趋势的极值
// ------------------------------------------------------------------ //| 3.1 搜索下跌趋势极值 (开始) | // ------------------------------------------------------------------ //--- 声明变量    int High_Corr_wave_downtrend_base_tf;//修正走势的高位(点 С)    int UpperFractal_downtrend_base_tf;  //下一个顶端极值栏线(点 А)    int Low_Corr_wave_downtrend_base_tf; //修正走势低位柱线(B点) //--- //--- 寻找修正走势高位(点 С)    if(High_base_tf_0[0]>High_base_tf[1] && High_base_tf_0[0]>High_base_tf[2])      {       High_Corr_wave_downtrend_base_tf=0;      }    else      {       for(n=0; n<(bars_base_tf);n )         {          if(High_base_tf[n]>High_base_tf[n 1] && High_base_tf[n]>High_base_tf[n 2])             break;         }       High_Corr_wave_downtrend_base_tf=n;      } //--- //--- 自修正走势的高位寻找下一个极值(点 А)    for(n=High_Corr_wave_downtrend_base_tf 1; n<(bars_base_tf);n )      {       // --- 如果是非空值,则终止循环       if(FractalUp_base_tf[n]!=EMPTY_VALUE)          break;      }    UpperFractal_downtrend_base_tf=n; //--- //--- 在点 C 和 A 之间找到点 B(修正走势低点)    int CountToFind_arrmin=UpperFractal_downtrend_base_tf-High_Corr_wave_downtrend_base_tf;    Low_Corr_wave_downtrend_base_tf=ArrayMinimum(Low_base_tf,High_Corr_wave_downtrend_base_tf,CountToFind_arrmin); // ------------------------------------------------------------------ //| 3.1 搜索下跌趋势的极值 (结束) | // ------------------------------------------------------------------

2. 搜索上涨趋势的极值

// ------------------------------------------------------------------ //| 3.2 搜索上涨趋势极值 (开始) | // ------------------------------------------------------------------ //--- 声明变量    int Low_Corr_wave_uptrend_base_tf;//修正走势的低位(点 С)    int LowerFractal_uptrend_base_tf;  //下一个底端极值栏线(点 А)    int High_Corr_wave_uptrend_base_tf; //修正走势高位(点 B) //--- //--- 寻找修正走势低位(点 С)    if(Low_base_tf_0[0]<Low_base_tf[1] && Low_base_tf_0[0]<Low_base_tf[2])      {       Low_Corr_wave_uptrend_base_tf=0;      }    else      {       //寻找回滚低点       for(n=0; n<(bars_base_tf);n )         {          if(Low_base_tf[n]<Low_base_tf[n 1] && Low_base_tf[n]<Low_base_tf[n 2])             break;         }       Low_Corr_wave_uptrend_base_tf=n;      } //--- //--- 从修正走势低点,寻找下一个底端极值点(点 А)    for(n=Low_Corr_wave_uptrend_base_tf 1; n<(bars_base_tf);n )      {       if(FractalDown_base_tf[n]!=EMPTY_VALUE)          break;      }    LowerFractal_uptrend_base_tf=n; //--- //--- 在点 C 和 A 之间找到点 B(修正走势高点) int CountToFind_arrmax=LowerFractal_uptrend_base_tf-Low_Corr_wave_uptrend_base_tf; High_Corr_wave_uptrend_base_tf=ArrayMaximum(High_base_tf,Low_Corr_wave_uptrend_base_tf,CountToFind_arrmax); // ------------------------------------------------------------------ //| 3.2 搜索上涨趋势的极值 (结束) | // ------------------------------------------------------------------

3. 将修正浪的高/低值约化到统一变量

因此,我们已发现了极值柱线索引。 但我们还需要参考柱线的价格和时间值。 为了引用修正浪的高点值或低点值,我们必须使用两个不同的数组,因为修正浪的高点或低点可以在零号索引柱线上,也可以在索引大于零号的柱线上。 这对于操作来说很不方便,因此使用 if 运算符 将它们的值带入公共变量会更合理。

// ---------------------------------------------------------------------------------- //| 3.3 将修正浪的高/低值代入公共变量 (开始) | // ---------------------------------------------------------------------------------- //--- 声明变量    double High_Corr_wave_downtrend_base_tf_double,Low_Corr_wave_uptrend_base_tf_double;    datetime High_Corr_wave_downtrend_base_tf_time,Low_Corr_wave_uptrend_base_tf_time; //--- 针对 High_Corr_wave_downtrend_base_tf    if(High_Corr_wave_downtrend_base_tf==0)      {       High_Corr_wave_downtrend_base_tf_double=High_base_tf_0[High_Corr_wave_downtrend_base_tf];       High_Corr_wave_downtrend_base_tf_time=Time_base_tf_0[High_Corr_wave_downtrend_base_tf];      }    else      {       High_Corr_wave_downtrend_base_tf_double=High_base_tf[High_Corr_wave_downtrend_base_tf];       High_Corr_wave_downtrend_base_tf_time=Time_base_tf[High_Corr_wave_downtrend_base_tf];      } //-- 针对 Low_Corr_wave_uptrend_base_tf    if(Low_Corr_wave_uptrend_base_tf==0)      {       Low_Corr_wave_uptrend_base_tf_double=Low_base_tf_0[Low_Corr_wave_uptrend_base_tf];       Low_Corr_wave_uptrend_base_tf_time=Time_base_tf_0[Low_Corr_wave_uptrend_base_tf];      }    else      {       Low_Corr_wave_uptrend_base_tf_double=Low_base_tf[Low_Corr_wave_uptrend_base_tf];       Low_Corr_wave_uptrend_base_tf_time=Time_base_tf[Low_Corr_wave_uptrend_base_tf];      } // --------------------------------------------------------------------------------- //| 3.3 将修正浪的高/低值代入公共变量 (结束) | // ---------------------------------------------------------------------------------

因此,修正浪的高/低价格和时间值被写入变量。 无需每次访问不同的数组。

如果我们汇总搜索极值的工作,事实证明可根据模型识别概念发现了点 A,B 和 C(见表 4 和表 5)。

表 4. 下跌趋势的点 А,В 和 С 的值

参数 点 A 值 点 B 值 点 C 值
柱线索引 UpperFractal_downtrend_base_tf Low_Corr_wave_downtrend_base_tf High_Corr_wave_downtrend_base_tf
时间值 Time_base_tf[UpperFractal_downtrend_base_tf] Time_base_tf[Low_Corr_wave_downtrend_base_tf] High_Corr_wave_downtrend_base_tf_time
价格值 High_base_tf[UpperFractal_downtrend_base_tf] Low_base_tf[Low_Corr_wave_downtrend_base_tf] High_Corr_wave_downtrend_base_tf_double

表 5. 上涨趋势的点 А,В 和 С 的值

参数 点 A 值 点 B 值 点 C 值
柱线索引 LowerFractal_uptrend_base_tf High_Corr_wave_uptrend_base_tf Low_Corr_wave_uptrend_base_tf
时间值 Time_base_tf[LowerFractal_uptrend_base_tf] Time_base_tf[High_Corr_wave_uptrend_base_tf] Low_Corr_wave_uptrend_base_tf_time
价格值 Low_base_tf[LowerFractal_uptrend_base_tf] High_base_tf[High_Corr_wave_uptrend_base_tf] Low_Corr_wave_uptrend_base_tf_double


5. 模型识别条件

在本章节中,我仅说明本文中所描述模型的最基本条件特征。

表 6. 用于识别走势延续模型的最小条件集合

# 下跌趋势条件 上涨趋势条件
1 修正浪高点(点 C)低于其后极值的高点(点 А) 校正浪低点(点 C)高于其后的极值低点(点 А)
2 修正浪低点索引(点 В)超过高点索引(点 С) 修正浪高点索引(点 В)超过低点索引(点 С)
3 修正走势持续时间为 2 到 6 根柱线(从点 В 开始的柱线数) 修正走势持续时间为 2 到 6 根柱线(从点 В 开始的柱线数)

用于所描述模型识别条件的代码提供如下。 条件收集在两个逻辑变量中:一个是下跌趋势,另一个是上涨趋势:

// ------------------------------------------------------------------ //| 4. 描述模型识别条件 (开始) | // ------------------------------------------------------------------ //--- 对于下跌趋势 /*1. 修正浪高点(点 C)低于其后极值的高点(点 А)*/ /*2. 修正浪低点索引(点 В)超过高点索引(点 С)*/ /*3. 修正走势持续时间为 2 到 6 根柱线(从点 В 开始的柱线数)*/    bool Model_downtrend_base_tf=(                                  /*1.*/High_Corr_wave_downtrend_base_tf_double<High_base_tf[UpperFractal_downtrend_base_tf] &&                                  /*2.*/Low_Corr_wave_downtrend_base_tf>High_Corr_wave_downtrend_base_tf &&                                  /*3.*/Low_Corr_wave_downtrend_base_tf>=1 && Low_Corr_wave_downtrend_base_tf<=6                                  ); //--- 对于上涨趋势 /*1. 修正浪低点(点 C)高于其后的极值低点(点 А)*/ /*2. 修正浪高点索引(点 В)超过低点索引(点 С)*/ /*3. 修正走势持续时间为 2 到 6 根柱线(从点 В 开始的柱线数)*/    bool Model_uptrend_base_tf=(                                /*1.*/Low_Corr_wave_uptrend_base_tf_double>Low_base_tf[LowerFractal_uptrend_base_tf] &&                                /*2.*/High_Corr_wave_uptrend_base_tf>Low_Corr_wave_uptrend_base_tf &&                                /*3.*/High_Corr_wave_uptrend_base_tf>=1 && High_Corr_wave_uptrend_base_tf<=6                                ); // ------------------------------------------------------------------ //| 4. 模型识别条件 (结束) | // ------------------------------------------------------------------

6. 创建控制

EA 应至少执行三次检查。

前两次检查验证入场的时效性。 第三次则确认在一个模型中只开一仓,即它确保没有重复开仓。

参见图例 3. 虚线标记入场点所在的开仓区域 — 位于点 В 和 С 之间。 当价格突破点B 的价位时,不建议稍后入场,因为这会增加风险。 这是程序应该执行的第一次检查。

走势延续模型

图例 3. AUDJPY H4 上的走势延续模型

在某些情况下,价格可能突破点 В 再回落到开仓区域。 这种情况不能考虑进行交易。 这是程序应该进行的第二次检查。 最后,为了避免多次开仓,我们需要引入限制:1 个模型 — 1 笔持仓。 这是程序应该执行的第三次检查。

1. 在开仓区域形成入场点控制

这一切都很简单:对于卖出模型,竞买价应该超过修正走势低点(点 В)。 对于买入模型,竞买价应该低于修正走势高点(点 В)。

// ------------------------------------------------------------------------ //| 5.1 在开仓区域形成入场点控制 (开始) | // ------------------------------------------------------------------------ //--- 对于下跌趋势 bool First_downtrend_control_bool=(bid>=Low_base_tf[Low_Corr_wave_downtrend_base_tf]); //--- 对于上涨趋势 bool First_uptrend_control_bool=(bid<=High_base_tf[High_Corr_wave_uptrend_base_tf]); // ------------------------------------------------------------------------ //| 5.1 在开仓区域形成入场点控制 (结束) | // ------------------------------------------------------------------------

2. 价格回滚到开仓区域的控制

为了实现这种控制,我们应当从当前索引开始直到修正走势的高/低点柱线(点 В),定义具有最低“低”价(对于卖出)的柱线,或具有最高“高”价(对于买入)的柱线。 为实现此目的,若是卖出模型则采用 ArrayMinimum() 函数,而买入模型则为 ArrayMaximum() 函数。

进而,将修正走势的低/高索引(点 В),与得自 ArrayMinimum()ArrayMaximum() 函数的索引进行比较。 如果它们匹配,则修正走势没有突破低/高点,在交易之间可以考虑入场情况。 如果索引不一致,则走势已经提前开始,且开仓也为时已晚。

// ------------------------------------------------------------------------------ //| 5.2 价格回滚至开仓区域的控制 (开始) | // ------------------------------------------------------------------------------ //--- 对于下跌趋势 //在 #0 柱线和修正走势的低点之间寻找价格最低的柱线    int Second_downtrend_control_int=ArrayMinimum(Low_base_tf,0,Low_Corr_wave_downtrend_base_tf 1); //如果当前柱线的低位低于修正走势的低位    if(Low_base_tf_0[0]<Low_base_tf[Second_downtrend_control_int])      {       Second_downtrend_control_int=0; //this means the minimum is on bar 0      } //如果价格最低的柱线与修正走势的低点匹配,则这是相同的柱线 //这意味着价格没有超越开仓区域    bool Second_downtrend_control_bool=(Second_downtrend_control_int==Low_Corr_wave_downtrend_base_tf); //--- //--- 对于上涨趋势 //在 #0 柱线和修正走势的高点之间寻找价格最高的柱线    int Second_uptrend_control_int=ArrayMaximum(High_base_tf,0,High_Corr_wave_uptrend_base_tf 1);    //如果当前柱线的高位超越修正走势的高位    if(High_base_tf_0[0]>High_base_tf[Second_uptrend_control_int])      {       Second_uptrend_control_int=0;//this means maximum on bar 0      } //如果价格最高的柱线与修正走势的高点匹配,则这是相同的柱线 //这意味着价格没有超越开仓区域    bool Second_uptrend_control_bool=(Second_uptrend_control_int==High_Corr_wave_uptrend_base_tf); // ----------------------------------------------------------------------------- //| 5.2 价格回滚至开仓区域的控制 (结束) | // -----------------------------------------------------------------------------

3. 在单个模型中消除重复仓位

此控制用于限制持仓的数量。 其背后的思路:一个模型 — 对于一笔持仓。 对持仓进行逐一分析。 如果在当前图表上已有一笔持仓,则从入场点定义最接近该笔持仓的极值柱线 - 根据交易类型则为修正走势的高/低点(从入场点开始的点 С)。

之后,检测到的柱线时间 — 修正走势的高/低点(从入场点开始的点 С)— 与当前修正走势的高/低点的时间(当前点 С)进行比较。 如果它们匹配,则不应开仓,因为没有符合此模型的仓位。

创造卖出控制:

// --------------------------------------------------------------------------- //| 5.3.1 对于卖出 (开始) | // --------------------------------------------------------------------------- //--- 声明变量    int Bar_sell_base_tf,High_Corr_wave_downtrend_base_tf_sell;    bool Third_downtrend_control_bool=false; //--- 迭代全部持仓    if(PositionsTotal()>0)      {       for(i=0;i<=PositionsTotal();i )         {          if(PositionGetTicket(i))            {             //--- 定义仓位品种,时间和类型             P_symbol=string(PositionGetString(POSITION_SYMBOL));             P_type=int(PositionGetInteger(POSITION_TYPE));             P_opentime=int(PositionGetInteger(POSITION_TIME));             //--- 如果持仓品种与当前图表匹配,且交易类型为“卖出”             if(P_symbol==Symbol() && P_type==1)               {                //--- 寻找开仓所在的柱线                Bar_sell_base_tf=iBarShift(Symbol(),base_tf,P_opentime);                //--- 从中寻找修正走势高点                //如果在当前柱线上有开仓,                if(Bar_sell_base_tf==0)                  {                   //且当前柱线是一个极值                   if(High_base_tf_0[Bar_sell_base_tf]>High_base_tf[Bar_sell_base_tf 1] && High_base_tf_0[Bar_sell_base_tf]>High_base_tf[Bar_sell_base_tf 2])                     {                      High_Corr_wave_downtrend_base_tf_sell=Bar_sell_base_tf;//修正走势高点等于当前柱线                     }                   else                     {                      //如果当前柱线不是极值,则启动循环搜索极值                      for(n=Bar_sell_base_tf; n<(bars_base_tf);n )                        {                         if(High_base_tf[n]>High_base_tf[n 1] && High_base_tf[n]>High_base_tf[n 2])//如果发现极值                            break;//循环中断                        }                      High_Corr_wave_downtrend_base_tf_sell=n;                     }                   //--- 描述控制条件                   Third_downtrend_control_bool=(                                                 /*1. 发现自开仓以来修正走势高点的时间                                                  匹配当前修正走势高点的时间*/Time_base_tf[High_Corr_wave_downtrend_base_tf_sell]==High_Corr_wave_downtrend_base_tf_time                                                 );                  }                //--- 如果开仓并非在当前柱线上                if(Bar_sell_base_tf!=0 && Bar_sell_base_tf!=1000)                  {                   //--- 启动循环检测极值柱线                   for(n=Bar_sell_base_tf; n<(bars_base_tf);n )                     {                      //--- 如果找到极值                      if(High_base_tf[n]>High_base_tf[n 1] && High_base_tf[n]>High_base_tf[n 2])                         break;//循环中断                     }                   High_Corr_wave_downtrend_base_tf_sell=n;                  }                Third_downtrend_control_bool=(                                              /*1. 发现自开仓以来修正走势高点的时间                                                  匹配当前修正走势高点的时间*/Time_base_tf[High_Corr_wave_downtrend_base_tf_sell]==High_Corr_wave_downtrend_base_tf_time                                              );               }            }         }      } // --------------------------------------------------------------------------- //| 5.3.1 对于卖出 (结束) | // ---------------------------------------------------------------------------
创造买入控制:
// --------------------------------------------------------------------------- //| 5.3.2 对于买入 (开始) | // --------------------------------------------------------------------------- //--- 声明变量    int Bar_buy_base_tf,Low_Corr_wave_uptrend_base_tf_buy;    bool Third_uptrend_control_bool=false; //--- 迭代全部持仓    if(PositionsTotal()>0)      {       for(i=0;i<=PositionsTotal();i )         {          if(PositionGetTicket(i))            {             //定义仓位品种,类型和时间             P_symbol=string(PositionGetString(POSITION_SYMBOL));             P_type=int(PositionGetInteger(POSITION_TYPE));             P_opentime=int(PositionGetInteger(POSITION_TIME));             //如果持仓品种与当前图表和买入交易类型一致             if(P_symbol==Symbol() && P_type==0)               {                //寻找开仓所在的柱线                Bar_buy_base_tf=iBarShift(Symbol(),base_tf,P_opentime);                //从中寻找修正走势低点                //如果在当前柱线上有开仓,                if(Bar_buy_base_tf==0)                  {                  //且当前柱线是一个极值                   if(Low_base_tf_0[Bar_buy_base_tf]<Low_base_tf[Bar_buy_base_tf 1] && Low_base_tf_0[Bar_buy_base_tf]<Low_base_tf[Bar_buy_base_tf 2])                     {                      Low_Corr_wave_uptrend_base_tf_buy=Bar_buy_base_tf;                     }                   else                     {                     //如果当前柱线不是极值,则启动循环搜索极值                      for(n=Bar_buy_base_tf; n<(bars_base_tf);n )                        {                         if(Low_base_tf[n]<Low_base_tf[n 1] && Low_base_tf[n]<Low_base_tf[n 2])//如果发现极值                            break;//循环中断                        }                      Low_Corr_wave_uptrend_base_tf_buy=n;                     }                   //--- 描述控制条件                     Third_uptrend_control_bool=(                                                /*1. 发现自开仓以来修正走势低点的时间                                                  匹配当前修正走势低点的时间*/Time_base_tf[Low_Corr_wave_uptrend_base_tf_buy]==Low_Corr_wave_uptrend_base_tf_time                                                );                  }                //--- 如果开仓并非在当前柱线上                if(Bar_buy_base_tf!=0 && Bar_buy_base_tf!=1000)                  {                   //--- 启动循环检测极值柱线                   for(n=Bar_buy_base_tf; n<(bars_base_tf);n )                     {                      //--- 如果找到极值                      if(Low_base_tf[n]<Low_base_tf[n 1] && Low_base_tf[n]<Low_base_tf[n 2])                         break;//循环中断                     }                   Low_Corr_wave_uptrend_base_tf_buy=n;                  }                  //--- 描述控制条件                  Third_uptrend_control_bool=(                                             /*1. 发现自开仓以来修正走势低点的时间                                                  匹配当前修正走势低点的时间*/Time_base_tf[Low_Corr_wave_uptrend_base_tf_buy]==Low_Corr_wave_uptrend_base_tf_time                                             );               }            }         }      } // --------------------------------------------------------------------------- //| 5.3.2 对于买入 (结束) | // ---------------------------------------------------------------------------

7. 描述入场条件

应在操作周期定义入场点 — work_tf。 这对于及时入场是必要的,并且如果可能的话,能够减少风险的点数。 抛物线指标的读数作为信号:如果当前柱线上的指标值超过当前柱线的高点,而在前一根柱线上,指标值低于同一柱线的低点,则是卖出时间。 对于买入,案例相反。

// ------------------------------------------------------------------ //| 6. 描述市场准入条件 (开始) | // ------------------------------------------------------------------ //--- 对于卖出    bool PointSell_work_tf_bool=(                                 /*1. #1 柱线低点超越 iSar[1]*/Low_work_tf[1]>Sar_array_work_tf[1] &&                                 /*2. #0 柱线高点低于 iSar[0]*/High_work_tf_0[0]<Sar_array_work_tf_0[0]                                 ); //--- 对于买入    bool PointBuy_work_tf_bool=(                                /*1. #1 柱线高点低于 iSar*/High_work_tf[1]<Sar_array_work_tf[1] &&                                /*2. #0 柱线低点超越 iSar[0]*/Low_work_tf_0[0]>Sar_array_work_tf_0[0]                                ); // ------------------------------------------------------------------ //| 6. 描述市场准入条件 (结束) | // ------------------------------------------------------------------

8. 交易条件

在此阶段,我们将所有先前创建的条件和控制组合到一个逻辑变量中。

// ------------------------------------------------------------------ //| 7. 描述交易条件 (开始) | // ------------------------------------------------------------------ //--- 对于卖出    bool OpenSell=(                   /*1. 模型形成*/Model_downtrend_base_tf==true &&                   /*2. 控制 1 允许开仓*/First_downtrend_control_bool==true &&                   /*3. 控制 2 允许开仓*/Second_downtrend_control_bool==true &&                   /*4. 控制 3 允许开仓*/Third_downtrend_control_bool==false &&                   /*5. 入场点 work_tf*/PointSell_work_tf_bool==true                   ); //--- 对于卖出    bool OpenBuy=(                  /*1. 模型形成*/Model_uptrend_base_tf==true &&                  /*2. 控制 1 允许开仓*/First_uptrend_control_bool==true &&                  /*3. 控制 2 允许开仓*/Second_uptrend_control_bool==true &&                  /*4. 控制 3 允许开仓*/Third_uptrend_control_bool==false &&                  /*5. 入场点 work_tf*/PointBuy_work_tf_bool==true                  ); // ------------------------------------------------------------------ //| 7. 描述交易条件 (结束) | // ------------------------------------------------------------------

9. 处理交易操作

交易操作的运作可分为:

  • 设置仓位;
  • 设置止盈;
  • 持仓移至盈亏平衡点。

1. 设定仓位

// ------------------------------------------------------------------ //| 8. 处理交易操作 (开始) | // ------------------------------------------------------------------ //--- 定义止损位    SLPrice_sell=High_Corr_wave_downtrend_base_tf_double spread;    SLPrice_buy=Low_Corr_wave_uptrend_base_tf_double-spread; // ------------------------------------------------------------------ //| 8.1 设置仓位 (开始) | // ------------------------------------------------------------------ //--- 对于卖出    if(OpenSell==true)      {       RiskSize_points=(SLPrice_sell-bid)*f;//止损定义为整数点数       if(RiskSize_points==0)//检查除零         {          RiskSize_points=1;         }       CostOfPoint_position=SummRisk/RiskSize_points;//参考止损定义仓位价格点数       Lot=CostOfPoint_position/CostOfPoint;//计算开仓手数       //开仓       trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,NormalizeDouble(Lot,2),bid,NormalizeDouble(SLPrice_sell,5),0,'');      } //--- 对于买入    if(OpenBuy==true)      {       RiskSize_points=(bid-SLPrice_buy)*f;//止损定义为整数点数       if(RiskSize_points==0)//检查除零         {          RiskSize_points=1;         }       CostOfPoint_position=SummRisk/RiskSize_points;//参考止损定义仓位价格点数       Lot=CostOfPoint_position/CostOfPoint;//计算开仓手数       //开仓       trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,NormalizeDouble(Lot,2),ask,NormalizeDouble(SLPrice_buy,5),0,'');      } // ------------------------------------------------------------------ //| 8.1 设置仓位 (结束) | // ------------------------------------------------------------------

2. 设定止盈

// ------------------------------------------------------------------ //| 8.2 设置止盈 (开始) | // ------------------------------------------------------------------    if(TP_mode==true)      {       if(PositionsTotal()>0)         {          for(i=0;i<=PositionsTotal();i )            {             if(PositionGetTicket(i))               {               //获取持仓值                SL_double=double (PositionGetDouble(POSITION_SL));                OP_double=double (PositionGetDouble(POSITION_PRICE_OPEN));                TP_double=double (PositionGetDouble(POSITION_TP));                P_symbol=string(PositionGetString(POSITION_SYMBOL));                P_type=int(PositionGetInteger(POSITION_TYPE));                P_profit=double (PositionGetDouble(POSITION_PROFIT));                P_ticket=int (PositionGetInteger(POSITION_TICKET));                P_opentime=int(PositionGetInteger(POSITION_TIME));                if(P_symbol==Symbol())                  {                   if(P_type==0 && TP_double==0)                     {                      double SL_size_buy=OP_double-SL_double;//定义止损点数                      double TP_size_buy=SL_size_buy*M;//止损乘以输入中设置的比率                      double TP_price_buy=OP_double TP_size_buy;//定义止盈位                      //修改持仓                      trade.PositionModify(PositionGetInteger(POSITION_TICKET),SL_double,NormalizeDouble(TP_price_buy,5));                     }                   if(P_type==1 && TP_double==0)                     {                      double SL_size_sell=SL_double-OP_double;//定义止损点数                      double TP_size_sell=SL_size_sell*M;//止损乘以输入中设置的比率                      double TP_price_sell=OP_double-TP_size_sell;//定义止盈位                      //修改持仓                      trade.PositionModify(PositionGetInteger(POSITION_TICKET),SL_double,NormalizeDouble(TP_price_sell,5));                     }                  }               }            }         }      } // ------------------------------------------------------------------ //| 8.2 设置止盈 (结束) | // ------------------------------------------------------------------

3. 将持仓移至盈亏平衡点

// ------------------------------------------------------------------ //| 8.3 持仓移至盈亏平衡点 (开始) | // ------------------------------------------------------------------    double Size_Summ=breakeven*SummRisk;//定义止盈位,之后应将持仓移至盈亏平衡点    if(Breakeven_mode==true && breakeven!=0)      {       if(PositionsTotal()>0)         {          for(i=0;i<=PositionsTotal();i )            {             if(PositionGetTicket(i))               {               //获取持仓值                SL_double=double (PositionGetDouble(POSITION_SL));                OP_double=double (PositionGetDouble(POSITION_PRICE_OPEN));                TP_double=double (PositionGetDouble(POSITION_TP));                P_symbol=string(PositionGetString(POSITION_SYMBOL));                P_type=int(PositionGetInteger(POSITION_TYPE));                P_profit=double (PositionGetDouble(POSITION_PROFIT));                P_ticket=int (PositionGetInteger(POSITION_TICKET));                P_opentime=int(PositionGetInteger(POSITION_TIME));                if(P_symbol==Symbol())                  {                   if(P_type==0 && P_profit>=Size_Summ && SL_double<OP_double)                     {                      trade.PositionModify(PositionGetInteger(POSITION_TICKET),OP_double,TP_double);                     }                   if(P_type==1 && P_profit>=Size_Summ && SL_double>OP_double)                     {                      trade.PositionModify(PositionGetInteger(POSITION_TICKET),OP_double,TP_double);                     }                  }               }            }         }      } // ------------------------------------------------------------------ //| 8.3 持仓移至盈亏平衡点 (结束) | // ------------------------------------------------------------------ // ------------------------------------------------------------------ //| 8. 处理交易操作 (结束) | // ------------------------------------------------------------------

5. 收集统计数据

首先,您需要确定一组统计指标:

  1. 品种;
  2. 成交类型;
  3. 入场时间;
  4. 开仓价格;
  5. 止损价位;
  6. 止损大小;
  7. 最大盈利价位;
  8. 最大盈利大小;
  9. 成交区间。

必要的假设,最大盈利点是在开仓位置之后形成的主要周期的第一个上/下分形的高/低位。

首先,我们需要在策略测试器中测试 EA 操作。 为了测试,我选择了 AUDJPY,区间为 2018.01.01-2018.08.29。 选择 D1 为主要时间帧,而 H6 用与操作时间帧。 每笔成交风险 — $100。 持仓移至盈亏平衡点 1/2, 设置止盈 — 1/3 (风险/盈利)。

EA 输入

图例 4. EA 输入

测试后,将报告保存在 CSV 文件中。 在终端本地文件夹中,创建新的 report.csv 文件。 复制报告数据并写入文件(从“订单”部分)。 我们应该删除与平仓相关的行,如图例 5 所示:

从报告中删除与平仓相关的行

图例 5. 从报告中删除与平仓相关的行

复制列:

  1. 开仓时间;
  2. 品种;
  3. 类型;
  4. 价格;
  5. 止损。

因此,report.csv 文件将如下所示:

report.csv 文件内容

图例 6. report.csv 文件内容

现在,我们需要创建一个从 report.csv 文件中读取数据的脚本,并创建新的 file_stat.csv 文件以及其它统计信息:

  1. 止损大小;
  2. 最大盈利价位;
  3. 最大盈利大小;
  4. 成交区间的柱线数。

为了解决这个任务,我借用了 “MQL5 编程基础:文件” 一文中 “使用分隔符读取文件到数组”章节的现成解决方案。 我还添加了数组,并用 file_stat.csv 文件中存储的列数值填充数组。

创建一个新脚本,在 OnStart() 函数下编写读取文件并写入数组的代码:

// ------------------------------------------------------------------ //| 读取数据至数组的函数 (开始) | // ------------------------------------------------------------------ bool ReadFileToArrayCSV(string FileName,SLine  &Lines[])   {    ResetLastError();    int h=FileOpen(FileName,FILE_READ|FILE_ANSI|FILE_CSV,';');    if(h==INVALID_HANDLE)      {       int ErrNum=GetLastError();       printf('File open error %s # %i',FileName,ErrNum);       return(false);      }    int lcnt=0; // 计算字符串的变量    int fcnt=0; // 计算字符串字段的变量    while(!FileIsEnding(h))      {       string str=FileReadString(h);       // 新字符串(新结构数组元素)       if(lcnt>=ArraySize(Lines))         { // 结构数组完全填满          ArrayResize(Lines,ArraySize(Lines) 1024); // 将数组大小增加 1024 个元素         }       ArrayResize(Lines[lcnt].field,64);// 更改结构中的数组大小       Lines[lcnt].field[0]=str; // 分配第一个字段的值                                 // 开始读取字符串中的剩余字段       fcnt=1; // 字段数组中的一个元素被占用       while(!FileIsLineEnding(h))         { // 读取字符串中的其余字段          str=FileReadString(h);          if(fcnt>=ArraySize(Lines[lcnt].field))            { // 字段数组完全填满             ArrayResize(Lines[lcnt].field,ArraySize(Lines[lcnt].field) 64); // 将数组大小增加 64 个元素            }          Lines[lcnt].field[fcnt]=str; // 分配下一个字段的值          fcnt ; // 增加字段计数器         }       ArrayResize(Lines[lcnt].field,fcnt); // 根据实际的字段数改变字段数组大小       lcnt ; // 增加字符串计数器      }    ArrayResize(Lines,lcnt); // 根据实际的字符串数更改结构数组(字符串)    FileClose(h);    return(true);   } // ------------------------------------------------------------------ //| 读取数据至数组的函数 (结束) | // ------------------------------------------------------------------

接下来,指定输入:

#property script_show_inputs //--- 输入 input ENUM_TIMEFRAMES base_tf;  //基准周期时间帧 input double sar_step=0.1;      //设置抛物线步幅 input double maximum_step=0.11; //设置抛物线最大步幅 //--- 声明指标句柄的变量 int Fractal_base_tf;             //iFractal 指标句柄 //--- 声明 base_tf 变量 double High_base_tf[],Low_base_tf[];             //用于存储柱线高/低价格的数组 double FractalDown_base_tf[],FractalUp_base_tf[];//用于存储 iFractall 指标价格的数组 //--- 数组结构 struct SLine   {    string            field[];   };

OnStart() 函数内部,获取 iFractals 指标句柄,声明并填充高/低价格数组。 我们还需要在 for 循环使用 bars_base_tf 变量,并使用 f 变量来存储价格数字容量,具体取决于品种价格中的小数位数。 此变量用于将止损和最大盈利值转换为整数。

//--- 获取 iFractal 指标句柄    Fractal_base_tf=iFractals(Symbol(),base_tf); //--- 设置 base_tf 数组的顺序为时间序列    ArraySetAsSeries(High_base_tf,true);    ArraySetAsSeries(Low_base_tf,true);    ArraySetAsSeries(FractalDown_base_tf,true);    ArraySetAsSeries(FractalUp_base_tf,true); //--- 初始填充 base_tf 数组    CopyHigh(Symbol(),base_tf,0,1000,High_base_tf);    CopyLow(Symbol(),base_tf,0,1000,Low_base_tf);    CopyBuffer(Fractal_base_tf,0,TimeCurrent(),1000,FractalUp_base_tf);    CopyBuffer(Fractal_base_tf,1,TimeCurrent(),1000,FractalDown_base_tf); //--- 用于存储柱线编号上数据的变量    int bars_base_tf=Bars(Symbol(),base_tf); //品种价格中的小数位数    int Digit=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); //定义当前品种的价格容量    double f=1;    if(Digit==5) {f=100000;}    if(Digit==4) {f=10000;}    if(Digit==3) {f=1000;}    if(Digit==2) {f=100;}    if(Digit==1) {f=10;}

接下来,声明数组和变量:

//--- 声明变量和数组    int i,j,n; //循环变量    datetime opentime[];//用于存储仓位设定时间的数组    string symbol[];//用于存储品种的数组    string type[];//用于存储成交类型的数组    string openprice[];//存储开仓价格的数组    string  sl_price[];//存储止损价位的数组    int index[];//存储开仓所在柱线索引的数组    int down_fractal[];//存储低位分形指数的数组    int up_fractal[];//存储高位分形指数的数组    double sl_size_points[];//存储止损大小点数的数组    string maxprofit_price[];//存储最大利润价位的数组    double maxprofit_size_points[];//存储最大利润值的数组    int duration[];//存储波浪持续柱线数的数组    bool maxprofit_bool[];//确保不可由止损激活仓位的数组    int maxprofit_int[];//定义最小/最大柱线的数组。 它与 maxprofit_bool[] 一起使用

在此之后,继续将文件中的数据读取到数组中:

   SLine lines[];    int size=0;    if(!ReadFileToArrayCSV('report.csv',lines))      {       Alert('Error, see details in the \'Experts\'' tab);      }    else      {       size=ArraySize(lines);       ArrayResize(opentime,ArraySize(lines));       ArrayResize(symbol,ArraySize(lines));       ArrayResize(type,ArraySize(lines));       ArrayResize(openprice,ArraySize(lines));       ArrayResize(sl_price,ArraySize(lines));       ArrayResize(index,ArraySize(lines));       ArrayResize(down_fractal,ArraySize(lines));       ArrayResize(up_fractal,ArraySize(lines));       ArrayResize(sl_size_points,ArraySize(lines));       ArrayResize(maxprofit_price,ArraySize(lines));       ArrayResize(maxprofit_size_points,ArraySize(lines));       ArrayResize(duration,ArraySize(lines));       ArrayResize(maxprofit_bool,ArraySize(lines));       ArrayResize(maxprofit_int,ArraySize(lines));       for(i=0;i<size;i )         {          for(j=0;j<ArraySize(lines[i].field);j=j 5)//按开仓时间列选择字段            {             opentime[i]=(datetime)(lines[i].field[j]);//将数据写入数组            }          for(j=1;j<ArraySize(lines[i].field);j=j 4)//按品种列选择字段            {             symbol[i]=(lines[i].field[j]);//将数据写入数组            }          for(j=2;j<ArraySize(lines[i].field);j=j 3)//按成交类型列选择字段            {             type[i]=(lines[i].field[j]);//将数据写入数组            }          for(j=3;j<ArraySize(lines[i].field);j=j 2)//按开仓价格列选择字段            {             openprice[i]=(lines[i].field[j]);//将数据写入数组            }          for(j=4;j<ArraySize(lines[i].field);j=j 1)//按止损列选择字段            {             sl_price[i]=(lines[i].field[j]);//将数据写入数组            }         }      } //-----------------------------------------------------

openrpice[] 和 sl_price[] 数组具有字符串数据类型。 若要在计算中使用它们,请使用 StringToDouble() 函数将它们转换为 double 类型。 但是,在这种情况下会丢失小数。 为避免这种情况,请使用 StringReplace() 函数将逗号替换为小数点:

   for(i=0;i<size;i )      {       StringReplace(openprice[i],',','.');       StringReplace(sl_price[i],',','.');      }

然后定义开仓所在柱线的索引:

//--- 定义开仓所在柱线的索引    for(i=0;i<size;i )      {       index[i]=iBarShift(Symbol(),PERIOD_D1,opentime[i]);//将数据写入数组      }

之后,找到最接近开仓所在的下位和上位分形:

//--- 搜索卖出分形    for(i=0;i<size;i )      {       if(type[i]=='sell')         {          for(n=index[i];n>0;n--)            {             if(FractalDown_base_tf[n]!=EMPTY_VALUE)                break;            }          down_fractal[i]=n;         }      } //--- 搜索买入分形    for(i=0;i<size;i )      {       if(type[i]=='buy')         {          for(n=index[i];n>0;n--)            {             if(FractalUp_base_tf[n]!=EMPTY_VALUE)                break;            }          up_fractal[i]=n;         }      }

接下来,以点数为单位定义止损,并将点数转换为整数:

//--- 止损点数    for(i=0;i<size;i )      {       if(type[i]=='sell')         {          sl_size_points[i]=(StringToDouble(sl_price[i])-StringToDouble(openprice[i]))*f;         }       if(type[i]=='buy')         {          sl_size_points[i]=(StringToDouble(openprice[i])-StringToDouble(sl_price[i]))*f;         }      }

根据先前检测到的分形,您可以确定最大利润价位。 但首先,我们需要确保持仓不会因止损而过早平仓。 查验代码:

//--- 确保在达到最大利润之前,不会由止损平仓 //--- 对于卖出    for(i=0;i<size;i )      {       if(type[i]=='sell')         {          for(n=index[i];n>down_fractal[i];n--)            {             if(High_base_tf[n]>=StringToDouble(sl_price[i]))                break;            }          maxprofit_int[i]=n;          maxprofit_bool[i]=(n==down_fractal[i]);         }      } //--- 对于买入    for(i=0;i<size;i )      {       if(type[i]=='buy')         {          for(n=index[i];n>up_fractal[i];n--)            {             if(Low_base_tf[n]<=StringToDouble(sl_price[i]))                break;            }          maxprofit_int[i]=n;          maxprofit_bool[i]=(n==up_fractal[i]);         }      }

现在您可以编写用于确定最大利润价位的代码:

//--- 最大盈利价位    for(i=0;i<size;i )      {       if(type[i]=='sell' && maxprofit_bool[i]==true)         {          maxprofit_price[i]=(string)Low_base_tf[down_fractal[i]];         }       if(type[i]=='sell' && maxprofit_bool[i]==false)         {          maxprofit_price[i]='';         }       if(type[i]=='buy' && maxprofit_bool[i]==true)         {          maxprofit_price[i]=(string)High_base_tf[up_fractal[i]];         }       if(type[i]=='buy' && maxprofit_bool[i]==false)         {          maxprofit_price[i]='';         }      }

然后您可以确定最大利润的大小。 如果控制被激活,止损的利润将为负数值:

   for(i=0;i<size;i )      {       if(type[i]=='sell' && maxprofit_bool[i]==true)         {          maxprofit_size_points[i]=(StringToDouble(openprice[i])-Low_base_tf[down_fractal[i]])*f;         }       if(type[i]=='sell' && maxprofit_bool[i]==false)         {          maxprofit_size_points[i]=sl_size_points[i]*-1;         }       if(type[i]=='buy' && maxprofit_bool[i]==true)         {          maxprofit_size_points[i]=(High_base_tf[up_fractal[i]]-StringToDouble(openprice[i]))*f;         }       if(type[i]=='buy' && maxprofit_bool[i]==false)         {          maxprofit_size_points[i]=sl_size_points[i]*-1;         }      }

最后,我们定义开仓所在柱线与最大利润之间的持续时间(以柱线为单位)。 如果由止损激活平仓控制,则持续时间被定义为开仓所在柱线与触发止损柱线之间的差。:

//--- 计算成交持续柱线数    for(i=0;i<size;i )      {       if(type[i]=='sell' && maxprofit_bool[i]==true)         {          duration[i]=index[i]-(int)down_fractal[i];         }       if(type[i]=='sell' && maxprofit_bool[i]==false)         {          duration[i]=index[i]-maxprofit_int[i];         }       if(type[i]=='buy' && maxprofit_bool[i]==true)         {          duration[i]=index[i]-(int)up_fractal[i];         }       if(type[i]=='buy' && maxprofit_bool[i]==false)         {          duration[i]=index[i]-maxprofit_int[i];         }      }

之后,我们将小数点替换回逗号以便正确显示参数:

   for(i=0;i<size;i )      {       StringReplace(openprice[i],'.',',');       StringReplace(sl_price[i],'.',',');       StringReplace(maxprofit_price[i],'.',',');      }

现在,剩下的只是将获得的数据写入 file_stat.csv 文件:

//--- 将数据写入新的统计文件    int h=FileOpen('file_stat.csv',FILE_READ|FILE_WRITE|FILE_ANSI|FILE_CSV,';'); //--- 开仓检查    if(h==INVALID_HANDLE)      {       Alert('Error opening file!');       return;      }    else      {       FileWrite(h,                 /*1 品种*/'Symbol',                 /*2 成交类型*/'Deal type',                 /*3 入场时间*/'Open time',                 /*4 开仓价格*/'Open price',                 /*5 止损价位*/'SL',                 /*6 止损大小*/'SL size',                 /*7 最大盈利价位*/'Max profit level',                 /*8 最大盈利值*/'Max profit value',                 /*9 持续期*/'Duration in bars');       //--- 转至结束       FileSeek(h,0,SEEK_END);       for(i=0;i<size;i )         {          FileWrite(h,                    /*1 品种*/symbol[i],                    /*2 成交类型*/type[i],                    /*3 入场时间*/TimeToString(opentime[i]),                    /*4 开仓价格*/openprice[i],                    /*5 止损价位*/sl_price[i],                    /*6 止损大小*/NormalizeDouble(sl_size_points[i],2),                    /*7 最大盈利价位*/maxprofit_price[i],                    /*8 最大盈利大小*/NormalizeDouble(maxprofit_size_points[i],2),                    /*9 持续期*/duration[i]);         }      }    FileClose(h);    Alert('file_stat.csv file created');

检查:在输入中设置基准时间帧周期后(在我的情况下为 D1),启动图表上的脚本。 之后,具有以下参数集合的新 file_stat.csv 文件将显示在终端的本地文件夹中:

file_stat.csv 文件内容
 

图例 7. file_stat.csv 文件内容

6. 结束语

在本文中,我们分析了以编程方式确定走势延续模型的方法之一。 该方法的关键思路是在不采用任何指标的情况下搜索修正走势高/低极值。 然后基于发现的极值检测模型的连续点。

我们还讨论了将测试结果写入数组及其后续处理,根据策略测试器中的测试结果收集统计数据的方法。 我相信,有可能开发一套更有效的收集和处理统计数据的方法。 不过,这种方法对我来说似乎最简单和全面。

请记住,本文描述了定义模型的最低要求,最重要的是,EA 提供的最小控制集合。 对于实盘交易,应该扩展控制集合。

以下是走势延续模型识别的示例:

模型识别

图例 8. 走势延续模型识别样品

模型识别

图例 9. 走势延续模型识别样品

趋势延续模型识别样品

图例 10. 走势延续模型识别样品

趋势延续模型识别样品

图例 11. 走势延续模型识别样品

本文中使用的程序

# 名称 类型 说明
1 Trade.mqh 类库 交易操作类
2 MQL5_POST_final 智能交易系统 EA 定义走势延续模型
3 Report_read 脚本 收集统计数据的脚本

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多