构建三线突破图表指标
介绍
之前的文章研究了 点数图, Kagi 以及 Renko 图表。持续发表了一系列关于 20 世纪图表的文章,这次我们要谈的是三线突破图表,或者准确地说,关于通过程序代码来实现它。有关这张图表来源的信息非常少。我猜想它始于日本。在美国学到它们是在 1994 年出版的 Steve Nison 的 "Beyond Candlesticks(超越蜡烛条)" 。
除了上面提到的图表,并未谈及三线突破图表的构建时间范围。它基于确定时间帧中最后形成的收盘价格, 它可以过滤相对以前行情的小幅价格波动。
在 Steve Nison 著作 "Beyond Candlesticks(超越蜡烛条)" 中描述了 11 种绘制这个图表的原理(p. 185). 我已经将它们合并成三条。
- 原理一: 为了构造,选择初始价格, 依据市场涨跌, 绘制阳线或阴线。它将标记出一个新的最低或最高。
- 原理二: 当新的价格下跌低于最低或超过最高, 我们绘制阴线或阳线。
- 原理三: 绘制与之前移动方向相反的线, 必须经过最低或最高。在同一时刻, 如果有多于一个的相同的线, 则最低或最高的计算基于它们中的两根 (如果有两根连续相同线) 或三根 (如果有三根或更多连续相同线)。
让我们近距离看一个经典的基于历史数据的构造图表例子 (图例. 1)。
图例.1 构造一个三线突破图表示例 (EURUSD H1 27.06.2014)
图例. 1 在左侧呈现了一个蜡烛条图表,以及右侧的三线突破图表。这是 EURUSD 图表, 时间帧为 H1。图表的开始日期是 27.06.2014 起始价位 1.3613 (烛线的收盘时间 00:00), 则烛线 (01:00) 收盘于 1.3614, 形成了三线突破图表的第一根阳线。随后的空头烛线 (02:00) 形成一个阳线, 收盘于 1.3612 (收盘价低于之前的最低)。
后来多头烛条将价格推动到 1.3619 (03:00) 标记位置, 形成了一个新高,以及柱线。位于 04:00 的下跌烛线未能低于最低,并且它没有影响构造。位于 05:00 的烛线收盘于 1.3623, 标记了一个新高 (新的阳线)。
现在延续下降趋势, 我们需要经过两个最低 (1.3613), 但多头未放弃它们的位置,因此形成了一个新高 1.3626 (06:00)。后来多头试图扭转上行趋势两个小时, 但是相同的趋势持续,到达并创新高 1.3634 (09:00)。多头领先。现在绘制一根阳线, 经过三个最低价 (1.3626; 1.3623 和 1.3619)。
正如我们看到的, 在随后的三小时,空头在市场中占优, 下跌到位置 1.3612 (12:00)。它体现为一个新的阳线。但是,随后的五小时表明多头正在赢回自己的位置,并带领市场重回 1.3641,经过前高 1.3626,并在 17:00 形成新的阳线。空头在 18:00 未能超越前低,并且在随后五小时多头带领市场到达 1.3649,每小时形成一根新的阳线。
图表构造基础
在我们得到代码之前,我们要说说指标本身,并找出是什么使它与众不同。三线突破很明显,就像其它指标,是专为高效率的市场分析,以及寻找新策略提供便利。我相信你一定想知道,此处是否有什么新奇。事实上,还真有一些。该指标可以改变计算的价格类型。它覆盖了所有四个标准价格条。构造图表的经典设计是仅针对一个类型,现代型则迎合所有四种价格类型 (开盘, 最高, 最低和收盘)。它修改了经典图表构造的外观,通过添加 "阴影" 使得它们看起来像日本蜡烛条,即加入了图表的视觉观感。
现代版本的特性还可以设置当数据缺失时优先同步价格数据。
图表构造的现代类型如图例 2 所示:
图例.2 修改后的基于四种价格类型图表
由于现代构造结合了四种不同价格类型的三线突破图表,它可以很自然地找到价格之间的差异。为了避免它,及时同步数据是必需的。价格同步带来两个变化: 完整 (图例. 2 右侧) 和局部 (图例. 2 左侧)。完整同步代表一个过滤的局部, 其中所有数据绘制在图表上,而且缺失数据被设置中的指定优选价格替代。在完整同步模式中,缺失数据被简单地省略,并且仅绘制数据完整的烛条。
另一项创新是周期分离,可便利的切分信号。正如您所知,周期分隔符可以在图表设置中启用。在指标中可以根据指定设置改变时间帧。不像 MetaTrader 5 中的图表, 通过垂直虚线分隔周期, 在此指标中通过一条变色垂直线表示(蜡烛, 图例. 3):
图例.3 指标中的周期分隔符
另外附加实现了技术指标 iMA, 其依据主图表的价格建立, 但是它可以及时与指标数据同步。因此,数据由均线过滤 (图例. 4):
图例.4 内部均线
该指标还有一个特点,可以设置绘图的最小移动点数,以及所需反转的线数。它也是滤波器的作用。
指标代码
指标的算法相当简单,它有三个阶段:复制数据,基于该复制数据进行计算,以及填充指标缓冲器(构造图表基于所接收的数据)。代码依据它们的内部以及输入数据联系被划分功能。让我们近距离观查这些代码。
1. 指标输入参数
该指标的序言中包含了图形结构的声明。在指标中它们有两个: 图表 "ABCTB" (DRAW_COLOR_CANDLES) 和附加均线 "LINE_TLB" (DRAW_LINE)。因此,有六个缓冲器。以下 enum(枚举) 类型用于提高界面设置以及设置本身:
- magic_numb - 魔幻号类型为 long。它是一个唯一数字来表示指标。如果有必要时,可以转换类型为 string 并带修正参数;
- time_frame - 计算时间范围, 类型 ENUM_TIMEFRAMES, 它是主要参数 (指标时间帧);
- time_redraw - 图表更新周期, 类型 ENUM_TIMEFRAMES。它是图表进行重计算时采用的时间帧。为了快速重绘图表,在键盘上按下 "R" 键 - 指标的集成控制;
- first_date_start - 起始日期, 类型 datetime。主要参数,是复制数据和绘图的起始点;
- chart_price - 进行计算的价格类型 (0-收盘价, 1-开盘价, 2-最高价, 3-最低价)。对于经典构造图表,必须选择一个价格类型。正如已经提到的那样,当修改构造启用时,该参数将被忽略;
- step_min_f - 新列的最小步长 (>0, 类型 int) 或绘制线所需的跳跃距离;
- line_to_back_f - 显示反转的线数 (>0, 类型 int)。经典类型建议为三线用于显示反转;
- chart_type - 图表构造类型 (0-经典, 1-修改), 类型 select。它是构造类型的开关;
- chart_color_period - 当开始新周期时的变化颜色 (boolean 类型)。用于在新周期开始时改变线颜色;
- chart_synchronization - 仅在完整同步基础上构造图表 (boolean 类型, 若为 true, 则在构造图表之前执行完整同步,删除所有缺失数值);
- chart_priority_close - 收盘价优先 (类型 select, 有四种变化。它表示局部同步时以收盘价优先,并忽略完整的;
- chart_priority_open - 开盘价优先。此处同样应用;
- chart_priority_high - 最高价优先。此处同样应用;
- chart_priority_low - 最低价优先。此处同样应用;
- ma_draw - 绘制均线 (boolean 类型, 若为 true, 则绘制 均线);
- ma_price - 构造均线的价格类型, 可以是 ENUM_APPLIED_PRICE 之一;
- ma_method - 构造类型, 可以是 ENUM_MA_METHOD 之一;
- ma_period - 均线 周期;
然后我们声明缓存区数组, 用于计算的变量和结构。
//+------------------------------------------------------------------+ //| ABCTB.mq5 | //| "Azotskiy Aktiniy ICQ:695710750" | //| "" | //+------------------------------------------------------------------+ // ABCTB - Auto Build Chart Three Line Break #property copyright "Azotskiy Aktiniy ICQ:695710750" #property link "" #property version "1.00" #property indicator_separate_window #property indicator_buffers 6 #property indicator_plots 2 //--- plot ABCTB #property indicator_label1 "ABCTB" #property indicator_type1 DRAW_COLOR_CANDLES #property indicator_color1 clrBlue,clrRed,clrGreenYellow #property indicator_style1 STYLE_SOLID #property indicator_width1 1 //--- plot LINE_TLB #property indicator_label2 "LINE_TLB" #property indicator_type2 DRAW_LINE #property indicator_color2 clrRed #property indicator_style2 STYLE_SOLID #property indicator_width2 1 //--- Price type for calculation enum type_price { close=0, // Close open=1, // Open high=2, // Hight low=3, // Low }; //--- type of chart construction enum type_build { classic=0, // Classic modified=1, // Modified }; //--- priority enum priority { highest_t=4, // Highest high_t=3, // High medium_t=2, // Medium low_t=1, // Low }; //--- input parameters input long magic_numb=65758473787389; // Magic number input ENUM_TIMEFRAMES time_frame=PERIOD_CURRENT; // Calculation time range input ENUM_TIMEFRAMES time_redraw=PERIOD_M1; // Period of chart updates input datetime first_date_start=D'2013.03.13 00:00:00'; // Start date input type_price chart_price=close; // Price type for calculation (0-Close, 1-Open, 2-High, 3-Low) input int step_min_f=4; // Minimum step for a new column (>0) input int line_to_back_f=3; // Number of lines to display a reversal(>0) input type_build chart_type=classic; // Type of chart construction (0-classic, 1-modified) input bool chart_color_period=true; // Changing color for a new period input bool chart_synchronization=true; // Constructing a chart only upon complete synchronization input priority chart_priority_close=highest_t; // Priority of the closing price input priority chart_priority_open=highest_t; // Priority of the opening price input priority chart_priority_high=highest_t; // Priority of the maximum price input priority chart_priority_low=highest_t; // Priority of the minimum price input bool ma_draw=true; // Draw the average input ENUM_APPLIED_PRICE ma_price=PRICE_CLOSE; // Price type for constructing the average input ENUM_MA_METHOD ma_method=MODE_EMA; // Construction type input int ma_period=14; // Averaging period //--- indicator buffers //--- buffer of the chart double ABCTBBuffer1[]; double ABCTBBuffer2[]; double ABCTBBuffer3[]; double ABCTBBuffer4[]; double ABCTBColors[]; //--- buffer of the average double LINE_TLBBuffer[]; //--- variables MqlRates rates_array[];// bar data array for analysis datetime date_stop; // current date datetime date_start; // start date variable for calculation //+------------------------------------------------------------------+ //| Struct Line Price | //+------------------------------------------------------------------+ struct line_price// structure for storing information about the past lines { double up; // value of the high price double down;// value of the low price }; //+------------------------------------------------------------------+ //| Struct Line Information | //+------------------------------------------------------------------+ struct line_info// structure for storing information about the shared lines { double up; double down; char type; datetime time; }; line_info line_main_open[]; // data on the opening prices chart line_info line_main_high[]; // data on the maximum prices chart line_info line_main_low[]; // data on the minimum prices chart line_info line_main_close[]; // data on the closing prices chart //+------------------------------------------------------------------+ //| Struct Buffer Info | //+------------------------------------------------------------------+ struct buffer_info// structure for storing data for filling a buffer { double open; double high; double low; double close; char type; datetime time; }; buffer_info data_for_buffer[];// data for filling the modified construction buffer datetime array_datetime[]; // array for storing information of the time for every line int time_array[3]; // array for the function func_date_color datetime time_variable; // variable for the function func_date_color bool latch=false; // variable-latch for the function func_date_color int handle; // handle of the indicator iMA int step_min; // variable of the minimum step int line_to_back; // variable of the number of lines to display a reversal
2. 函数 OnInit
所有 指标缓存区 声明在函数 OnInit 中,以及指标数组设置如同 时间序列。
然后我们设置不会在图表上反映出来的指标数值, 设置 名称, 指定精度以及 删除当前数值,因为它们会造成图表过载。此处,我们还设置了指标 iMA 句柄,并检查输入数据的正确性。在出错的情况下,则输出相应的消息,并将数值改为最小。
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping //--- buffers for a chart SetIndexBuffer(0,ABCTBBuffer1,INDICATOR_DATA); ArraySetAsSeries(ABCTBBuffer1,true); SetIndexBuffer(1,ABCTBBuffer2,INDICATOR_DATA); ArraySetAsSeries(ABCTBBuffer2,true); SetIndexBuffer(2,ABCTBBuffer3,INDICATOR_DATA); ArraySetAsSeries(ABCTBBuffer3,true); SetIndexBuffer(3,ABCTBBuffer4,INDICATOR_DATA); ArraySetAsSeries(ABCTBBuffer4,true); SetIndexBuffer(4,ABCTBColors,INDICATOR_COLOR_INDEX); ArraySetAsSeries(ABCTBColors,true); //--- buffer for constructing the average SetIndexBuffer(5,LINE_TLBBuffer,INDICATOR_DATA); ArraySetAsSeries(LINE_TLBBuffer,true); //--- set the values that are not going to be reflected on the chart PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0); // for the chart PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0); // for the average //--- set the indicator appearance IndicatorSetString(INDICATOR_SHORTNAME,"ABCTB "+IntegerToString(magic_numb)); // name of the indicator //--- accuracy of display IndicatorSetInteger(INDICATOR_DIGITS,_Digits); //--- prohibit displaying the results of the indicator current value PlotIndexSetInteger(0,PLOT_SHOW_DATA,false); PlotIndexSetInteger(1,PLOT_SHOW_DATA,false); //--- handle=iMA(_Symbol,time_frame,ma_period,0,ma_method,ma_price); if(step_min_f<1) { step_min=1; Alert("Minimum step for a new column must be greater than zero"); } else step_min=step_min_f; //--- if(line_to_back_f<1) { line_to_back=1; Alert("The number of lines to display a reversal must be greater than zero"); } else line_to_back=line_to_back_f; //--- return(INIT_SUCCEEDED); }
3. 复制数据函数
由于指标设计为与所有四种价格类型工作,则复制所有数据至关重要,包括时间。在 MQL5 中有一个结构名为 MqlRates。它用于存储有关交易时段的开始时间,价格,交易量和点差信息。
该函数的输入参数是起始和结束日期,时间帧及 MqlRates 类型的目标数组。如果复制成功此函数返回 true。数据将复制到中间数组。已计算缺失数据加上一个时段将复制至此,数据将被永久更新。如果复制到中间数组成功,则数据将被复制到数组中,以保证该函数地正确工作。
//+------------------------------------------------------------------+ //| Func All Copy | //+------------------------------------------------------------------+ bool func_all_copy(MqlRates &result_array[],// response array ENUM_TIMEFRAMES period, // timeframe datetime data_start, // start date datetime data_stop) // end date { //--- declaration of auxiliary variables bool x=false; // variable for the function response int result_copy=-1; // copied data count //--- adding variables and arrays for calculation static MqlRates interim_array[]; // temporary dynamic array for storing copied data static int bars_to_copy; // number of bars for copying static int bars_copied; // number of copied bars since the start date //--- find out the current number of bars in the time range bars_to_copy=Bars(_Symbol,period,data_start,data_stop); //--- count the number of bars to be copied bars_to_copy-=bars_copied; //--- if it is not the first time when data is being copied if(bars_copied>0) { bars_copied--; bars_to_copy++; } //--- change the size of the receiving array ArrayResize(interim_array,bars_to_copy); //--- copy data to a temporary array result_copy=CopyRates(_Symbol,period,0,bars_to_copy,interim_array); //--- check the result of copying data if(result_copy!=-1) // if copying to the temporary array was successful { ArrayCopy(result_array,interim_array,bars_copied,0,WHOLE_ARRAY); // copy the data from the temporary array to the main one x=true; // assign the positive response to the function bars_copied+=result_copy; // increase the value of the copied data } //--- return(x); }
4. 计算数据函数
此函数是数据计算的原型,用于构造经典的三线突破图表。正如已经提到的,该函数只计算数据,并将其插入代码开始处声明的结构类型 line_info 的指定数组。
此函数包含了另外两个函数: func_regrouping (重组函数) 和 func_insert (插入函数)。我们开始看看它们:
4.1. 重组函数
此函数重组相同方向的连续线的信息。它受限于传递到它的数组的尺寸,或是来自指标设置参数 line_to_back_f (显示反转的线数) 的精确值。所以,每次当控制传递到函数时,全部有关标识线的接收数据向下移动一点,并且索引为 0 的线会被填充新数值。
这就是突破所需线的信息如何被存储 (在经典构造为三线情况下)。
//+------------------------------------------------------------------+ // Func Regrouping | //+------------------------------------------------------------------+ void func_regrouping(line_price &input_array[],// array for regrouping double new_price, // new price value char type) // type of movement { int x=ArraySize(input_array);// find out the size of the array for regrouping for(x--; x>0; x--) // regrouping loop { input_array[x].up=input_array[x-1].up; input_array[x].down=input_array[x-1].down; } if(type==1) { input_array[0].up=new_price; input_array[0].down=input_array[1].up; } if(type==-1) { input_array[0].down=new_price; input_array[0].up=input_array[1].down; } }
4.2. 插入函数
该函数插入数值至响应数组。该代码很简单,不需要详细解释。
//+------------------------------------------------------------------+ // Func Insert | //+------------------------------------------------------------------+ void func_insert(line_info &line_m[], // target array line_price &line_i[], // source array int index, // array element being inserted char type, // type of the target column datetime time) // date { line_m[index].up=line_i[0].up; line_m[index].down=line_i[0].down; line_m[index].type=type; line_m[index].time=time; }
该用于计算数据的函数通常分为三个部分。第一部分,根据分析并依照操作符 switch 复制数据至中间数组。仅关注被复制价格。第二部分运行一个测试来计算数据数组所需的空间。然后该数据数组 line_main_array[], 初始化后传递到函数用于响应, 经历了一个变化。第三部分,轮流填充调整数据数组。
//+------------------------------------------------------------------+ //| Func Build Three Line Break | //+------------------------------------------------------------------+ void func_build_three_line_break(MqlRates &input_array[], // array for analysis char price_type, // type of the price under analysis (0-Close, 1-Open, 2-High, 3-Low) int min_step, // minimum step for drawing a line int line_back, // number of lines for a reversal line_info &line_main_array[]) // array for return (response) of the function { //--- calculate the size of the array for analysis int array_size=ArraySize(input_array); //--- extract data required for calculation to an intermediate array double interim_array[];// intermediate array ArrayResize(interim_array,array_size);// adjust the intermediate array to the size of the data switch(price_type) { case 0: // Close { for(int x=0; x<array_size; x++) { interim_array[x]=input_array[x].close; } } break; case 1: // Open { for(int x=0; x<array_size; x++) { interim_array[x]=input_array[x].open; } } break; case 2: // High { for(int x=0; x<array_size; x++) { interim_array[x]=input_array[x].high; } } break; case 3: // Low { for(int x=0; x<array_size; x++) { interim_array[x]=input_array[x].low; } } break; } //--- enter the variables for storing information about current situation line_price passed_line[];// array for storing information about the latest prices of the lines (type structure line_price) ArrayResize(passed_line,line_back+1); int line_calc=0;// number of lines int line_up=0;// number of the last ascending lines int line_down=0;// number of the last descending lines double limit_up=0;// upper limit necessary to pass double limit_down=0;// lower limit necessary to pass /* Fill variables informing of the current situation with the first values */ passed_line[0].up=interim_array[0]; passed_line[0].down=interim_array[0]; //--- start the first loop to calculate received data for filling a buffer for drawing for(int x=0; x<array_size; x++) { if(line_calc==0)// no lines have been drawn { limit_up=passed_line[0].up; limit_down=passed_line[0].down; if(interim_array[x]>=limit_up+min_step*_Point)// the upper limit has been passed { func_regrouping(passed_line,interim_array[x],1);// regroup line_calc++;// update the line counter line_up++; } if(interim_array[x]<=limit_down-min_step*_Point)// the lower limit has been passed { func_regrouping(passed_line,interim_array[x],-1);// regroup line_calc++;// update the line counter line_down++; } } if(line_up>line_down)// last ascending line (lines) { limit_up=passed_line[0].up; limit_down=passed_line[(int)MathMin(line_up,line_back-1)].down; if(interim_array[x]>=limit_up+min_step*_Point)// the upper limit has been passed { func_regrouping(passed_line,interim_array[x],1);// regroup line_calc++;// update the line counter line_up++; } if(interim_array[x]<limit_down)// the lower limit has been passed { func_regrouping(passed_line,interim_array[x],-1);// regroup line_calc++;// update the line counter line_up=0; line_down++; } } if(line_down>line_up)// last descending line (lines) { limit_up=passed_line[(int)MathMin(line_down,line_back-1)].up; limit_down=passed_line[0].down; if(interim_array[x]>limit_up)// the upper limit has been passed { func_regrouping(passed_line,interim_array[x],1);// regroup line_calc++;// update the line counter line_down=0; line_up++; } if(interim_array[x]<=limit_down-min_step*_Point)// the lower limit has been passed { func_regrouping(passed_line,interim_array[x],-1);// regroup line_calc++;// update the line counter line_down++; } } } ArrayResize(line_main_array,line_calc);// change the size of the target array //--- zeroise variables and fill with the the initial data line_calc=0; line_up=0; line_down=0; passed_line[0].up=interim_array[0]; passed_line[0].down=interim_array[0]; //--- start the second loop to fill a buffer for drawing for(int x=0; x<array_size; x++) { if(line_calc==0)// no lines have been drawn { limit_up=passed_line[0].up; limit_down=passed_line[0].down; if(interim_array[x]>=limit_up+min_step*_Point)// the upper limit has been passed { func_regrouping(passed_line,interim_array[x],1);// regroup func_insert(line_main_array,passed_line,line_calc,1,input_array[x].time); line_calc++;// update the line counter line_up++; } if(interim_array[x]<=limit_down-min_step*_Point)// the lower limit has been passed { func_regrouping(passed_line,interim_array[x],-1);// regroup func_insert(line_main_array,passed_line,line_calc,-1,input_array[x].time); line_calc++;// update the line counter line_down++; } } if(line_up>line_down)// last ascending line (lines) { limit_up=passed_line[0].up; limit_down=passed_line[(int)MathMin(line_up,line_back-1)].down; if(interim_array[x]>=limit_up+min_step*_Point)// the upper limit has been passed { func_regrouping(passed_line,interim_array[x],1);// regroup func_insert(line_main_array,passed_line,line_calc,1,input_array[x].time); line_calc++;// update the line counter line_up++; } if(interim_array[x]<limit_down)// the lower limit has been passed { func_regrouping(passed_line,interim_array[x],-1);// regroup func_insert(line_main_array,passed_line,line_calc,-1,input_array[x].time); line_calc++;// update the line counter line_up=0; line_down++; } } if(line_down>line_up)// last descending line (lines) { limit_up=passed_line[(int)MathMin(line_down,line_back-1)].up; limit_down=passed_line[0].down; if(interim_array[x]>limit_up)// the upper limit has been passed { func_regrouping(passed_line,interim_array[x],1);// regroup func_insert(line_main_array,passed_line,line_calc,1,input_array[x].time); line_calc++;// update the line counter line_down=0; line_up++; } if(interim_array[x]<=limit_down-min_step*_Point)// the lower limit has been passed { func_regrouping(passed_line,interim_array[x],-1);// regroup func_insert(line_main_array,passed_line,line_calc,-1,input_array[x].time); line_calc++;// update the line counter line_down++; } } } }
5. 图表构造函数
此函数的目的是依据选择的构造参数 (经典或修改) 计算图表数据并将显示数据填充到指标缓存区。如同之前函数, 此图表构造函数也有三个附加函数。它们是彩色函数, 同步函数以及均线函数。让我们来讨论更多它们的细节。
5.1. 彩色函数
此函数仅有一个输入参数 - 时间。该函数的响应是一个布尔变量。如果传递的数据是周期的边界,则该函数将返回 true。由于周期依赖于选择的时间帧,函数通过条件操作符 if 有一个内置的周期分隔。在周期被选择之后,它会检查是否一个新的周期已经开始。它是通过将日期转化为 MqlDateTime 结构并进行比较来完成。对于时间帧大于等于 H2, 日期值的变化指示新的周期开始。自时间帧 H12 至 D1,包含月份的变化,而在 W1 和 MN 之间,我们检查年份的变化。
不幸地, 结构 MqlDateTime 未包含当前周的信息。这个问题可以通过创建由变量 time_variable 表示的初始点来解决。依线向前,从这个日期中扣除一周的秒数。
//+------------------------------------------------------------------+ // Func Date Color | //+------------------------------------------------------------------+ bool func_date_color(datetime date_time) // input date { bool x=false;// response variable int seconds=PeriodSeconds(time_frame);// find out the calculation time range MqlDateTime date; TimeToStruct(date_time,date);// convert data if(latch==false) // check the state of the latch { MqlDateTime date_0; date_0=date; date_0.hour=0; date_0.min=0; date_0.sec=0; int difference=date_0.day_of_week-1; datetime date_d=StructToTime(date_0); date_d=date_d-86400*difference; time_variable=date_d; latch=true;// lock the latch } if(seconds<=7200)// period is less than or equal to H2 { if(time_array[0]!=date.day) { x=true; time_array[0]=date.day; } } if(seconds>7200 && seconds<=43200)// period is greater than H2 but less than or equal to H12 { if(time_variable>=date_time) { x=true; time_variable=time_variable-604800; } } if(seconds>43200 && seconds<=86400)// period is greater than H12 but less than or equal to D1 { if(time_array[1]!=date.mon) { x=true; time_array[1]=date.mon; } } if(seconds>86400)// period W1 or MN { if(time_array[2]!=date.year) { x=true; time_array[2]=date.year; } } return(x); }
5.2. 同步函数
同步函数有六个输入参数: 它们中的四个是优先价格, 布尔参数是完整或局部同步,以及它自己的分析数组。该函数被分成两部分: 完整与局部同步情况。
完整同步进行的三个阶段:
- 计算数组元素, 满足所有四个价格类型包含数据的条件。
- 在相同条件下,复制元素至中间阵列。
- 从中间数组复制并以参数传递。
局部同步更加复杂。
传递一维数组结构数组并转化为二维, 其中第一个索引表示顺序,而第二个则是价格类型。之后介绍的是一个有四个元素的一维数组。价格优先级复制到该数组,然后数组进行排序,以确定优先顺序。之后,我们根据优先权使用循环 for 以及条件操作符 if 进行分派。与此同时, 如果优先权相同, 则价格序列如下: 收盘, 开盘, 最高, 最低。一旦操作符 if 发现第一个优先数值, 则循环 for 取代之前创建的二维数组中的所有 0 值等等。
//+------------------------------------------------------------------+ // Func Synchronization | //+------------------------------------------------------------------+ void func_synchronization(buffer_info &info[], bool synchronization, char close, char open, char high, char low) { if(synchronization==true)// carry out a complete synchronization { int calc=0;// count variable for(int x=0; x<ArraySize(info); x++)// count complete data { if(info[x].close!=0 && info[x].high!=0 && info[x].low!=0 && info[x].open!=0)calc++; } buffer_info i_info[]; // enter a temporary array for copying ArrayResize(i_info,calc);// change the size of the temporary array calc=0; for(int x=0; x<ArraySize(info); x++)// copy data into the temporary array { if(info[x].close!=0 && info[x].high!=0 && info[x].low!=0 && info[x].open!=0) { i_info[calc]=info[x]; calc++; } } ZeroMemory(info); // clear the target array ArrayResize(info,calc); // change the size of the main array for(int x=0; x<calc; x++)// copy data from the temporary array to the main one { info[x]=i_info[x]; } } if(synchronization==false) // change zero values to priority ones { int size=ArraySize(info); // measure the size of the array double buffer[][4]; // create a temporary array for calculation ArrayResize(buffer,size); // change the size of the temporary array for(int x=0; x<size; x++) // copy data into the temporary array { buffer[x][0]=info[x].close; buffer[x][1]=info[x].open; buffer[x][2]=info[x].high; buffer[x][3]=info[x].low; } char p[4];// enter an array for sorting by the order p[0]=close; p[1]=open; p[2]=high; p[3]=low;// assign variables for further sorting ArraySort(p); // sort int z=0,v=0; // initialize frequently used variables for(int x=0; x<4; x++)// taking into account the results of the sorting, look through all variables and substitute them according to the priority { if(p[x]==close)// priority is for the closing prices { for(z=0; z<size; z++) { for(v=1; v<4; v++) { if(buffer[z][v]==0)buffer[z][v]=buffer[z][0]; } } } if(p[x]==open)// priority is for the opening prices { for(z=0; z<size; z++) { for(v=0; v<4; v++) { if(v!=1 && buffer[z][v]==0)buffer[z][v]=buffer[z][1]; } } } if(p[x]==high)// priority is for the maximum prices { for(z=0; z<size; z++) { for(v=0; v<4; v++) { if(v!=2 && buffer[z][v]==0)buffer[z][v]=buffer[z][2]; } } } if(p[x]==low)// priority is for the minimum prices { for(z=0; z<size; z++) { for(v=0; v<3; v++) { if(buffer[z][v]==0)buffer[z][v]=buffer[z][3]; } } } } for(int x=0; x<size; x++)// copy data from the temporary array back { info[x].close=buffer[x][0]; info[x].open=buffer[x][1]; info[x].high=buffer[x][2]; info[x].low=buffer[x][3]; } } }
5.3. 均线函数
它是最简单的函数。使用 OnInit 函数中接收到的指标句柄, 我们复制数值, 与传递到函数的参数日期相对应。之后这个数值作为响应返回到这个函数。
//+------------------------------------------------------------------+ // Func MA | //+------------------------------------------------------------------+ double func_ma(datetime date) { double x[1]; CopyBuffer(handle,0,date,1,x); return(x[0]); }
该绘制图表的函数,通常分为两个部分:经典绘图与修改的。该函数有两个输入参数:价格类型用于构造 (在修改模式被忽略) 和构造类型 (经典和修改)。
在最开始,指标缓存区被清除,然后,依照构造类型分为两部分。第一部分(我们正在谈论的修改构造)开始调用该函数用来计算所有四个价格类型。然后,我们创建一个通用的数据数组,即我们复制所使用的数据,接收调用函数计算后的数据。之后,接收到的数据数组被排序,且复制数据被清除。在全局级别声明数组 array data_for_buffer[] 之后, 将会填充基于与下列数据同步的连续日期。填充指标缓存区是修改构造的最后阶段。
第二部分(经典构造)则简单很多。起初,数据计算的函数被调用,然后填满指标缓存区。
//+------------------------------------------------------------------+ //| Func Chart Build | //+------------------------------------------------------------------+ void func_chart_build(char price, // price type for chart construction char type) // type of chart construction { //--- Zeroise the buffers ZeroMemory(ABCTBBuffer1); ZeroMemory(ABCTBBuffer2); ZeroMemory(ABCTBBuffer3); ZeroMemory(ABCTBBuffer4); ZeroMemory(ABCTBColors); ZeroMemory(LINE_TLBBuffer); if(type==1)// construct a modified chart (based on all price types) { func_build_three_line_break(rates_array,0,step_min,line_to_back,line_main_close);// data on closing prices func_build_three_line_break(rates_array,1,step_min,line_to_back,line_main_open);// data on opening prices func_build_three_line_break(rates_array,2,step_min,line_to_back,line_main_high);// data on maximum prices func_build_three_line_break(rates_array,3,step_min,line_to_back,line_main_low);// data on minimum prices //--- calculate data arrays int line_main_calc[4]; line_main_calc[0]=ArraySize(line_main_close); line_main_calc[1]=ArraySize(line_main_open); line_main_calc[2]=ArraySize(line_main_high); line_main_calc[3]=ArraySize(line_main_low); //--- gather the date array int all_elements=line_main_calc[0]+line_main_calc[1]+line_main_calc[2]+line_main_calc[3];// find out the number of all elements datetime datetime_array[];// enter the array for copying ArrayResize(datetime_array,all_elements); int y[4]; ZeroMemory(y); for(int x=0;x<ArraySize(datetime_array);x++)// copy data into the array { if(x<line_main_calc[0]) { datetime_array[x]=line_main_close[y[0]].time; y[0]++; } if(x<line_main_calc[0]+line_main_calc[1] && x>=line_main_calc[0]) { datetime_array[x]=line_main_open[y[1]].time; y[1]++; } if(x<line_main_calc[0]+line_main_calc[1]+line_main_calc[2] && x>=line_main_calc[0]+line_main_calc[1]) { datetime_array[x]=line_main_high[y[2]].time; y[2]++; } if(x>=line_main_calc[0]+line_main_calc[1]+line_main_calc[2]) { datetime_array[x]=line_main_low[y[3]].time; y[3]++; } } ArraySort(datetime_array);// sort the array //--- delete replicated data from the array int good_info=1; for(int x=1;x<ArraySize(datetime_array);x++)// count useful information { if(datetime_array[x-1]!=datetime_array[x])good_info++; } ArrayResize(array_datetime,good_info); array_datetime[0]=datetime_array[0];// copy the first element as it is the pattern in the beginning of comparison good_info=1; for(int x=1;x<ArraySize(datetime_array);x++)// fill the new array with useful data { if(datetime_array[x-1]!=datetime_array[x]) { array_datetime[good_info]=datetime_array[x]; good_info++; } } //--- fill the buffer for drawing (colored candles) int end_of_calc[4];// variables of storing information about the last comparison ZeroMemory(end_of_calc); ZeroMemory(data_for_buffer); ArrayResize(data_for_buffer,ArraySize(array_datetime));// change the size of the declared global array for storing data before passing it to a buffer for(int x=0; x<ArraySize(array_datetime); x++) { data_for_buffer[x].time=array_datetime[x]; for(int s=end_of_calc[0]; s<line_main_calc[0]; s++) { if(array_datetime[x]==line_main_close[s].time) { end_of_calc[0]=s; if(line_main_close[s].type==1)data_for_buffer[x].close=line_main_close[s].up; else data_for_buffer[x].close=line_main_close[s].down; break; } } for(int s=end_of_calc[1]; s<line_main_calc[1]; s++) { if(array_datetime[x]==line_main_open[s].time) { end_of_calc[1]=s; if(line_main_open[s].type==1)data_for_buffer[x].open=line_main_open[s].down; else data_for_buffer[x].open=line_main_open[s].up; break; } } for(int s=end_of_calc[2]; s<line_main_calc[2]; s++) { if(array_datetime[x]==line_main_high[s].time) { end_of_calc[2]=s; data_for_buffer[x].high=line_main_high[s].up; break; } } for(int s=end_of_calc[3]; s<line_main_calc[3]; s++) { if(array_datetime[x]==line_main_low[s].time) { end_of_calc[3]=s; data_for_buffer[x].low=line_main_low[s].down; break; } } } //--- start the function of synchronizing data func_synchronization(data_for_buffer,chart_synchronization,chart_priority_close,chart_priority_open,chart_priority_high,chart_priority_low); //--- preparatory actions before starting the function func_date_color ZeroMemory(time_array); time_variable=0; latch=false; //--- fill the buffer for drawing candles for(int x=ArraySize(data_for_buffer)-1,z=0; x>=0; x--) { ABCTBBuffer1[z]=data_for_buffer[x].open; ABCTBBuffer2[z]=data_for_buffer[x].high; ABCTBBuffer3[z]=data_for_buffer[x].low; ABCTBBuffer4[z]=data_for_buffer[x].close; if(ABCTBBuffer1[z]<=ABCTBBuffer4[z])ABCTBColors[z]=0; if(ABCTBBuffer1[z]>=ABCTBBuffer4[z])ABCTBColors[z]=1; if(func_date_color(data_for_buffer[x].time)==true && chart_color_period==true)ABCTBColors[z]=2; if(ma_draw==true)LINE_TLBBuffer[z]=func_ma(data_for_buffer[x].time); z++; } } else// construct a classic chart (based on one price type) { func_build_three_line_break(rates_array,price,step_min,line_to_back,line_main_close);// find data on selected prices ArrayResize(array_datetime,ArraySize(line_main_close)); //--- preparatory actions before starting the function func_date_color ZeroMemory(time_array); time_variable=0; latch=false; //--- the buffer for drawing candles for(int x=ArraySize(line_main_close)-1,z=0; x>=0; x--) { ABCTBBuffer1[z]=line_main_close[x].up; ABCTBBuffer2[z]=line_main_close[x].up; ABCTBBuffer3[z]=line_main_close[x].down; ABCTBBuffer4[z]=line_main_close[x].down; if(line_main_close[x].type==1)ABCTBColors[z]=0; else ABCTBColors[z]=1; if(func_date_color(line_main_close[x].time)==true && chart_color_period==true)ABCTBColors[z]=2; if(ma_draw==true)LINE_TLBBuffer[z]=func_ma(line_main_close[x].time); z++; } } }
6. 整合函数
此功能联合所有控制指标元素。首先,当前日期被定义,之后复制数据函数和图表构造函数被调用。
//+------------------------------------------------------------------+ //| Func Consolidation | //+------------------------------------------------------------------+ void func_consolidation() { //--- defining the current date date_stop=TimeCurrent(); //--- copying data for analysis func_all_copy(rates_array,time_frame,first_date_start,date_stop); //--- basic construction of the chart func_chart_build(chart_price,chart_type); ChartRedraw(); }
7. 键盘控制和自动控构造制函数
这些函数被设计为通过按键盘上的 "R" 键 (OnChartEvent) 或按照选定的时间范围 (OnCalculate) 自动重绘指标。后者则通过新柱线函数 (func_new_bar) 进行分析,func_new_bar 是一个曾描述过的 IsNewBar 函数简化版。
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { //--- if(func_new_bar(time_redraw)==true) { func_consolidation(); }; //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- event of a keystroke if(id==CHARTEVENT_KEYDOWN) { if(lparam==82) //--- the key "R" has been pressed { func_consolidation(); } } } //+------------------------------------------------------------------+ //| Func New Bar | //+------------------------------------------------------------------+ bool func_new_bar(ENUM_TIMEFRAMES period_time) { //--- static datetime old_times; // variable of storing old values bool res=false; // variable of the analysis result datetime new_time[1]; // time of a new bar //--- int copied=CopyTime(_Symbol,period_time,0,1,new_time); // copy the time of the last bar to the cell new_time //--- if(copied>0) // everything is ок. data copied { if(old_times!=new_time[0]) // if the old time of the bar is not equal to the new one { if(old_times!=0) res=true; // if it is not the first start, then new bar = true old_times=new_time[0]; // remember the time of the bar } } //--- return(res); }
在这一点上,我们必须完成描述指标的代码,并谈谈使用它的方式。
使用指标和交易策略的例子
让我们开始基于经典图表构造来分析主要策略。
1. 白与黑线作为买卖信号
大致我们可以谈论两个规则:
- 规则一: 买,当三根连续阳线;卖,当三根连续阴线。三根连续线说明趋势正在出现。
- 规则二: 卖, 当反转线下跌且低于三根连续阳线;买, 当反转线高于三根连续阴线。
让我们看看图例. 6,显示的是EURUSD 从2013年开始的经典构造(所分析的时间范围如图例. 5 描绘)。
图例.5 分析时间范围 EURUSD H1
图例.6 三线突破图表经典构造 EURUSD H1, 开始自 2013, 收盘价格
在图表上 (图例. 6) 我们可以清晰看到在点 1 和 2 之间的信号 (规则一) , 即起始卖点。在这种情况下,按照四位数字报价,收入超过 200 点。随后的点 4 说明有利买入 (规则二)。截至收盘,在点 5 的利润为 40 点,而我们的盈亏平衡点处于收盘价点 6。
在点 6 我们可以看到卖信号 (规则二)。在收盘价点 7,我们获得 10 点利润,在收盘价点 8 则盈亏平衡。点 8 和 9 不可以考虑作为信号,因为它们既不满足规则一,也不满足规则二。我们可以在点 10 (规则一) 买入; 我们也可以在收盘价点 11 获得 20 点利润,或在点 12 盈亏平衡。所有数字均四舍五入。
在最好的情况下,使用这种策略,我们可以产生 270 点的利润,这很令人印象深刻。同时,在指定的时间范围内有强烈的走势从而影响利润。在最坏的情况下,交易可能会导致盈亏平衡,这其实还不坏。
值得一提的是,当情况既符合规则一也符合规则二,我们需要在相同趋势中,等待一个代表趋势反转的行确认。
2. 等距通道,支撑与阻力线
另一种交易策略是运用三线突破图表进行技术分析。让我们看看图例. 7:
图例. 7 等距通道, 支撑与阻力线, GBPUSD H1, 时间范围自 01.03.2014 至 01.05.2014
在图例. 7 中您可以看到下降等距通道以红色线绘制,上升等距通道为蓝色,而支撑与阻力线以黑色绘制。很显然,所述第一阻力线正切入支撑线。
3. 蜡烛条形态
一个修改过的图表 (两线突破) 时间帧为 M30,货币对为 USDCAD,开始自 2013,看上去很有趣。
我们可以区分日本蜡烛条形态作为合理的信号 (图例. 8)。
图例. 8 修改的三线突破图表, USDCAD M30, 开始自 2013, 两线突破
在图表开始,我们可以看到反转形态 "Engulfing(吞噬)" 位于标记 1 之下。它由两根烛台组成: 红色和前面的蓝色。上升趋势线之后,市场走低到 2 号位置,是一个烛台反转形态 "Hammer (锤)"。在这个点上,市场改变方向。同样的情况出现在形态 3 处("Spinning Top -- 陀螺")。随后反转形态 "Kharami (孕育线)" (位置 4 ) 显示为烛条 4 以及它旁边的长阳线。形态 6 也由两根烛条组成 (形态 "Engulfing--吞噬") 但不像第一根类似模型,它令市场反方向翻转。
因此,可以得出结论,使用该指标分析是可以接受的,但它有这样的缺点,如信号较少,以及可能有显著的回撤。这一策略当然需要进一步发展。
4. 移动均线
局部修改,如添加移动均线,仅绘制线条,可为分析给与新的机会。
让我们看看图例. 9:
图例.9 分析移动均线, EURUSD H4, 三线突破图表, 经典构造, 自 01.01.2014 至 01.07.2014
在图例. 9 的上半部描绘了一幅给予最高价的经典构造图表,以及均线 (平均周期为 90, 最低价, 平滑平均)。图的下半部显示一个基于最低价的经典构造图表,以及均线 (平均周期为 90, 最高价, 平滑平均)。
所以,在图例. 9 的上半部分,均线可以被认为是支撑线,在下半部,与此相反,为阻力线。如果在两个图表中的价格均低于平均水平,则目前市场上呈下降趋势,此刻最好卖出。当价格上升到高于平均水平,此刻是时候买入。此策略的一个缺点是,它是只适用于长线交易。
结论
最后,我可以说,三线中断图表始终给出良好的信号,或者在最坏的情况下,也会导致盈亏平衡。实践证明,最好在一个长线趋势中应用,因此,我不建议在短线交易使用这种图表。如果任何人有关于如何使用它进行交易的新思路,我很乐意讨论它。
通常情况下,我试着来详细揭示代码。再次,若有任何扩展,返工或优化的想法,请为本文书写评论。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/902