English Русский Español Deutsch 日本語 Português
走势延续模型 - 搜索图表和执行统计

走势延续模型 - 搜索图表和执行统计

MetaTrader 5示例 | 3 十二月 2018, 07:08
3 415 0
Almat Kaldybay
Almat Kaldybay
  1. 概述
  2. 模型描述 - 常规特点
  3. 图表上的模型识别原理
  4. 算法构造和编写代码
    1. 输入参数,OnInit() 函数和初始变量声明
    2. 一般参数
    3. 更新数组数据
      1. 出现新柱线时填充数组
      2. 使用柱线 #0 的数据填充数组
      3. 更新分形数据
    4. 搜索极值
      1. 搜索下跌趋势的极值
      2. 搜索上涨趋势的极值
      3. 将修正浪的高/低值约化到统一变量
    5. 模型识别条件
    6. 创建控制
      1. 在开仓区域形成入场点控制
      2. 价格回滚到开仓区域的控制
      3. 在单个模型中消除重复仓位
    7. 描述入场条件
    8. 交易条件
    9. 处理交易操作
      1. 设定仓位
      2. 设定止盈
      3. 将持仓移至盈亏平衡点
  5. 收集统计数据
  6. 结束语


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 脚本 收集统计数据的脚本

本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/4222

附加的文件 |
MQL5.zip (128.44 KB)
使用限价订单替代止盈且无需修改 EA 的原始代码 使用限价订单替代止盈且无需修改 EA 的原始代码
使用限价订单来替代传统的止盈是论坛讨论的长期话题。 这种方法的优点是什么?如何在您的交易中实施? 在本文中,我将向您介绍我对此主题的看法。
EA 遥控方法 EA 遥控方法
交易机器人的主要优势在于能够在远程 VPS 服务器上每天 24 小时不间断工作。 但有时候有必要干预它们的工作,而此刻可能无法直接访问服务器。 是否可以遥控管理 EA? 本文提出了一种通过外部命令控制 EA 的选项。
跳空缺口 - 是能够获利的策略还是五五开? 跳空缺口 - 是能够获利的策略还是五五开?
这篇文章详细讨论了跳空缺口 — 前一时间段的收盘价和后一时间段的开盘价之间的较大差距, 以及对日柱方向的预测。还探讨了通过系统DLL使用 GetOpenFileName 函数的问题。
100 个最佳优化递次(第 1 部分)。 开发优化分析器 100 个最佳优化递次(第 1 部分)。 开发优化分析器
本文详细阐述了运用若干种可能选项开发选择最佳优化递次的应用程序。 该应用程序能够通过各种因素来筛选优化结果。 优化递次始终写入数据库,因此您总能无需重新优化即可选择新的机器人参数。 此外,您可在单个图表上查看所有优化递次,计算参数 VaR 比率,并构建递次与特定比率集和的交易结果的正态分布图。 以及,自优化伊始(或从选定日期到另一个选定日期)开始动态构建一些计算比率的图形。