本文提供了一种走势延续模型的程序化定义。 主要思路是定义两个波浪 — 主浪和修正浪。 对于极值点,我应用分形以及“潜在”分形 — 尚未形成分形的极值点。 接着,我将尝试收集有关波浪走势的统计数据。 数据将加载到 CSV 文件。
文章中所述的走势延续模型由两个波浪组成:主浪和修正浪。 图例 1 是该模型的示意性描述。 AB 是主浪,BC 是校正浪,而 CD 是走势主趋势的延续。
图例 1. 走势延续模型
在图表上,这看起来如下:
图例 2. AUDJPY H4 上的走势延续模型
模型识别原理如表 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 | 入场点时效性原则 - 只应在入场点形成的确定时刻开仓 |
首先,我们需要包含 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 操作来说已经足够了。
在此,我开始运用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. 常规参数 (结束) | //+------------------------------------------------------------------+
数组最初在 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 更新分形数据 (结束) | //+------------------------------------------------------------------+
我们回到走势延续模型。 为此,我们需要回顾 图例 2。
АВ 段是主浪,而 ВС 是修正浪。 根据模型识别原理,修正浪应始终以极值结束,因为这是一个分形。 在图像上,它被标记为 С。 应该从这一点开始搜索极值,直到检测到其余的极值。 然而,在入场的那一刻,形成的(确认的)分形可能已不存在。 所以,当柱线极值高于/低于前两根柱线时,我们需要寻找一种情况 — 这种柱线的高/低将形成点 С。 另外,请记住,在入场之时,修正走势的高/低点(点 С)可以位于零号或索引大于零号的柱线上。
表 2 示意极值定义的顺序。
表 2. 极值定义序列
# | 对于下跌趋势 | 对于上涨趋势 |
---|---|---|
1 | 寻找修正走势高位(点 С) | 寻找修正走势低位(点 С) |
2 | 自修正走势的高点寻找下一个顶端极值(点 А) | 自修正走势的低点寻找下一个底端极值(点 А) |
3 | 在点 C 和 A 之间寻找点 В(修正走势低点) | 在点 C 和 A 之间寻找点 В(修正走势高点) |
//+------------------------------------------------------------------+ //| 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 |
在本章节中,我仅说明本文中所描述模型的最基本条件特征。
表 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. 模型识别条件 (结束) | //+------------------------------------------------------------------+
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 对于买入 (结束) | //+---------------------------------------------------------------------------+
应在操作周期定义入场点 — 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. 描述市场准入条件 (结束) | //+------------------------------------------------------------------+
在此阶段,我们将所有先前创建的条件和控制组合到一个逻辑变量中。
//+------------------------------------------------------------------+ //| 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. 描述交易条件 (结束) | //+------------------------------------------------------------------+
交易操作的运作可分为:
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. 处理交易操作 (结束) | //+------------------------------------------------------------------+
首先,您需要确定一组统计指标:
必要的假设,最大盈利点是在开仓位置之后形成的主要周期的第一个上/下分形的高/低位。
首先,我们需要在策略测试器中测试 EA 操作。 为了测试,我选择了 AUDJPY,区间为 2018.01.01-2018.08.29。 选择 D1 为主要时间帧,而 H6 用与操作时间帧。 每笔成交风险 — $100。 持仓移至盈亏平衡点 1/2, 设置止盈 — 1/3 (风险/盈利)。
图例 4. EA 输入
测试后,将报告保存在 CSV 文件中。 在终端本地文件夹中,创建新的 report.csv 文件。 复制报告数据并写入文件(从“订单”部分)。 我们应该删除与平仓相关的行,如图例 5 所示:
图例 5. 从报告中删除与平仓相关的行
复制列:
因此,report.csv 文件将如下所示:
图例 6. report.csv 文件内容
现在,我们需要创建一个从 report.csv 文件中读取数据的脚本,并创建新的 file_stat.csv 文件以及其它统计信息:
为了解决这个任务,我借用了 “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 文件将显示在终端的本地文件夹中:
图例 7. file_stat.csv 文件内容
在本文中,我们分析了以编程方式确定走势延续模型的方法之一。 该方法的关键思路是在不采用任何指标的情况下搜索修正走势高/低极值。 然后基于发现的极值检测模型的连续点。
我们还讨论了将测试结果写入数组及其后续处理,根据策略测试器中的测试结果收集统计数据的方法。 我相信,有可能开发一套更有效的收集和处理统计数据的方法。 不过,这种方法对我来说似乎最简单和全面。
请记住,本文描述了定义模型的最低要求,最重要的是,EA 提供的最小控制集合。 对于实盘交易,应该扩展控制集合。
以下是走势延续模型识别的示例:
图例 8. 走势延续模型识别样品
图例 9. 走势延续模型识别样品
图例 10. 走势延续模型识别样品
图例 11. 走势延续模型识别样品
# | 名称 | 类型 | 说明 |
---|---|---|---|
1 | Trade.mqh | 类库 | 交易操作类 |
2 | MQL5_POST_final | 智能交易系统 | EA 定义走势延续模型 |
3 | Report_read | 脚本 | 收集统计数据的脚本 |
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程