纺锤形图表指标
简介
纺锤图属于所谓的量-价图表。量-价图是使用一个货币对的量和一个或多个价格实时数据绘制的。诸如市场概况的东西出现了。搜索了互联网没有发现多少有价值的信息,大部分是关于图表构成方面的内容。因此我们可以认为这种图表相对来说是近期才出现的,因此值得关注。
我从要求创建这个指标的读者那里收到了关于图表的一些信息。考虑到时间有限以及实现的复杂度,指标的开发延迟了。
纺锤图看上去跟日本蜡烛图很相似,开盘收盘价格以及最低和最高价格在图标上都有所呈现。除此之外,还使用成交量加权移动平均(VWMA)和成交量比率(VR),由此形成的形状看上去像一个纺锤(图.1)
图 1. 比较蜡烛图和纺锤图
我们从图1中可见,两个增加的参数(VWMA—成交量加权移动平均和VR—成交量比率)只是对图表的补充,形成一个看上去线每个人从小就熟悉的,陀螺一样的形状。这就是所谓的“纺锤”。
VR和VWMA是如何形成的。成交量加权移动平均(VWMA)是一种移动平均,并且使用公式(1)计算得到
其中 P — 价格,V — 成交量。大约听上去像这样:“成交量加权移动平均等于期间内成交量和价格之积的总和除以期间内的总成交量”。
成交量比例(VR)是一种移动平均,但是在图表上的显示方式不同,首先因为它没有价格值范围,其次它同前一个周期的市场情况相关,这就是为何它最好作为tick成交量显示在独立的图表上或者作为每个纺锤的宽度。它由公式(2)计算而得:
其中V — 成交量。它是“成交量比例等于当前成交量除以选定周期的成交量算数平均值”。
因此,所有这些操作完成后我们得到一个“纺锤形”图表(图.2)。
图 2. 纺锤形图表
很自然的我们会问:“为何图2中的纺锤不像图1中填充满的”?这个问题将在下一章节中阐述 — 绘图的基本原理。
绘图的基本原理
MQL5中自定义指标有七种绘制类型(线,线段,柱状图,箭头,填充区域,柱形以及蜡烛图),但是没有一种能够满足绘制纺锤图的要求。这就意味着需要一种自定义类型。不幸的是没有内置的绘制风格设计器,但有许多其他函数可以用来创建你自己的,在速度、功能和绘制质量上有别于内置的图形的风格。
我们决定使用"Bitmap"对象来组织形成自定义风格。这样做的缺点是,首先较高的内存占用和绘制相对复杂,因此回导致速度和稳定性的下降。"Bitmap" 对象相较于其他对象的优点是能够将绘制限定在特定的区域,还能够使用透明效果以及使用对象的一部分。这些就是绘制纺锤图的好处。
“纺锤”如图1所示,“纺锤”体是完全被填充的。绘制很复杂,需要通过“Bitmap”对象来实现。绘制过程如图3描述:
图 3. 使用“Bitmap”对象绘制一个“纺锤”形的技术表述
图3显示了“填充纺锤形”的三种技术表示方式此处:
- p — "Bitmap" 对象的坐标
- x — 用于绘图的图像角标
- 罗马数字标示要绘制的“纺锤”。
因此,要绘制第一个纺锤(图.3.a),称之为“胖乎乎的菱形”,需要四个“Bitmap”类型对象(图.3,a; 块:I, II, III, IV)。由于菱形的类型不同,它的宽度和高度(例如,Open, Close, VWMA 和 VR)需要选择不同角度的图片,3,a;角度:x1,x2,x3,x4)。图片是一个BMP格式的正方形位图,图中一条射线以一个特定的角度从其中一个最靠近的边界上延伸出来,并且将其分为两块区域:填充的和透明的。
特定图像的计算将在下面讨论。然而我们已经知道,要以不同的宽度和高度(例如 Open,Close,VWMA和VR)绘制这个模型,一种颜色就将需要360个位图(基于),两种颜色要720副位图。
第二个纺锤图(图.3,b)要复杂很多,尽管绘制的这个形状(称为“箭头”)只包含两部分。考虑到开盘和收盘价之间的距离,这里还有更多的角度组合。进一步绘图可以在备选之一的存在下被忽略(图.3,c)。
在第三种情况下(Fig.3,c)绘图分四个部分实现,前两个(I和II)和绘制“胖乎乎的菱形”时一样,而后两者(III和 IV)覆盖前者的冗余部分。 这种实现可以覆盖相邻的纺锤形,并且可以结合背景。像在“胖乎乎的菱形”中一样总共有180块以及180个需要覆盖的块。
总的来说,如果考虑非常耗资源的单一图表背景,一个纺锤形的实现需要900个位图。
现在考虑一个非填充的、简单并且能快速绘制的“纺锤”形版本(图.2)。不考虑图表的背景,位图的数量是360(一种颜色180个,另一种颜色180个)。
图 4. 使用“位图”对象绘制一个“非填充纺锤形”的技术表示。
正如前述,“非填充纺锤形”由四张图片绘制而成,代表不同角度(0到180)的彩色线条。无需绘制360个位图,因为角度随对象坐标的不同而不同。只有两个坐标(图.4,p1和p2):每个点两个对象。
让我再次解释一下为何此处能使用较少的位图。想象一下在图4(a)中有一个对称的菱形,那么I部分可以被IV部分替代。要做到这一点需要将坐对象的标点从右上角改为左上角。因此只需要180个形同颜色的对象,并根据使用的位置改变坐标。
现在我们从数学和几何学的角度来准确的说明。研究绘制一个“未填充纺锤形”(图.5 和图.6)的计算过程和图片的选择。
图. 5. “小胖菱形”的计算过程
图5展示了我们已经熟悉的“小胖菱形”(a)和其右侧(b)的部分。当Open,Close,VWMA 和 VR已知的情况下,所有标记的距离(a, b, c, d)很容易计算,例如:
- a = Close - VWMA,
- b = VWMA - Open,
- c = Close - Open,
- d = VR / 2.
已知a, b, d,,使用公式 3.1 和 3.3,就可以计算右侧三角形的斜边 e 和 f。因此,在一个直角三角形中,一个直角边和一个斜边的比值等于对角的正弦,使用公式3.2和3.4计算角x1和x2的正弦值。接下来,使用一张表格或者计算器查找角x1和x2,然后通过x2计算x3。 使用相同的方法来绘制“箭头”形状。
图. 6. “箭头”图形的数学计算
在谈论了绘图的基础知识之后,我们来分析下指标代码。
指标代码
在写代码前,有必要准备好指标所需的图形资源 — 带有透明背景的540x540像素的BMP格式位图。位图含有从一个角延伸出来的射线。在前89个位图中射线从左上角延伸而出,角度从1到89度,在后89个位图中射线从左下角延伸而出,角度从91到179度(1到89度为水平方向)。角度为1,90,180的位图大小分别为1x540像素和540х1像素,并无需透明背景。
总共有362副位图 — 181副一个颜色,181副另一个颜色(位图1和181是一样的)。文件名根据线的颜色(红色 — 首字母为“r”,蓝色 — 首字母为“b”)和所在的角来选取。
第一部分
第一部分代码到OnInit函数为止。考虑所有步骤
- 指定特定的参数(#property),此处 — 11个缓存及4类绘图形状(一个柱状和3条线)。
- 将绘图资源纳入可执行文件(#resource)中,如上面提到的有许多资源文件 — 362个。请注意,每个文件必须添加到单独的行,否则将无法加载。因此,绝大多数用省略号代替。
- 接下来是输入参数列表,包括用到的变量及缓存。
//+------------------------------------------------------------------+ //| SPC.mq5 | //| Azotskiy Aktiniy ICQ:695710750 | //| https://login.mql5.com/zh/users/aktiniy | //+------------------------------------------------------------------+ #property copyright "Azotskiy Aktiniy ICQ:695710750" #property link "https://login.mql5.com/zh/users/aktiniy" #property version "1.00" #property indicator_separate_window #property indicator_buffers 11 #property indicator_plots 4 //--- #property indicator_label1 "Shadow" #property indicator_type1 DRAW_COLOR_HISTOGRAM2 #property indicator_color1 clrRed,clrBlue,clrGray #property indicator_style1 STYLE_SOLID #property indicator_width1 1 //--- #property indicator_label2 "Open" #property indicator_type2 DRAW_LINE #property indicator_color2 clrRed #property indicator_style2 STYLE_SOLID #property indicator_width2 1 //--- #property indicator_label3 "Close" #property indicator_type3 DRAW_LINE #property indicator_color3 clrBlue #property indicator_style3 STYLE_SOLID #property indicator_width3 1 //--- #property indicator_label4 "VWMA" #property indicator_type4 DRAW_LINE #property indicator_color4 clrMagenta #property indicator_style4 STYLE_SOLID #property indicator_width4 1 //--- 加载资源文件 #resource "\\Images\\for_SPC\\b0.bmp"; #resource "\\Images\\for_SPC\\b1.bmp"; #resource "\\Images\\for_SPC\\b2.bmp"; #resource "\\Images\\for_SPC\\b3.bmp"; //... //... //... #resource "\\Images\\for_SPC\\b176.bmp"; #resource "\\Images\\for_SPC\\b177.bmp"; #resource "\\Images\\for_SPC\\b178.bmp"; #resource "\\Images\\for_SPC\\b179.bmp"; #resource "\\Images\\for_SPC\\b180.bmp"; #resource "\\Images\\for_SPC\\r0.bmp"; #resource "\\Images\\for_SPC\\r1.bmp"; #resource "\\Images\\for_SPC\\r2.bmp"; #resource "\\Images\\for_SPC\\r3.bmp"; //... //... //... #resource "\\Images\\for_SPC\\r176.bmp"; #resource "\\Images\\for_SPC\\r177.bmp"; #resource "\\Images\\for_SPC\\r178.bmp"; #resource "\\Images\\for_SPC\\r179.bmp"; #resource "\\Images\\for_SPC\\r180.bmp"; //+------------------------------------------------------------------+ //| 绘图类型 | //+------------------------------------------------------------------+ enum type_drawing { spindles=0, // 纺锤形 line_histogram=1, // 线和柱状图 }; //+------------------------------------------------------------------+ //| 价格类型 | //+------------------------------------------------------------------+ enum type_price { open=0, // Open high=1, // High low=2, // Low close=3, // Close middle=4, // Middle }; //--- 输入参数 input long magic_numb=65758473787389; // Magic 数字 input type_drawing type_draw=0; // 指标绘制类型 input int period_VR=10; // 成交量比例周期 input int correct_VR=4; // 成交量比例修正数 input int period_VWMA=10; // 成交量加权移动平均周期 input int spindles_num=1000; // 纺锤形数量 input type_price type_price_VWMA=0; // 用于绘制成交量加权移动平均的价格类型 // open=0; high=1; low=2; close=3; middle=4 //--- 输出参数 int ext_period_VR=0; int ext_correct_VR; int ext_period_VWMA=0; int ext_spin_num=0; int long_period=0; //--- 图表参数变量 double win_price_max_ext=0; // 图表最大值 double win_price_min_ext=0; // 图表最小值 double win_height_pixels_ext=0; // 高度(像素) double win_width_pixels_ext=0; // 宽度(像素) double win_bars_ext=0; // 宽度(以柱形数计) //--- 辅助变量 int end_bar; //--- 指标缓存 double Buff_up[]; // 直方图上方坐标点缓存 double Buff_down[]; // 直方图下方坐标点缓存 double Buff_color_up_down[]; // 直方图颜色缓存 double Buff_open_ext[]; // 开盘价输出缓存 double Buff_close_ext[]; // 收盘价输出缓存 double Buff_VWMA_ext[]; // 成交量加权移动平均输出缓存 double Buff_open[]; // 开盘价缓存 double Buff_close[]; // 收盘价缓存 double Buff_VWMA[]; // 成交量加权移动平均缓存 double Buff_VR[]; // 成交量比例缓存 double Buff_time[]; // 柱形开始时间缓存
可以看到只有一些输入参数:
- Magic 数字 — 用于区分指标;
- 指标绘制类型 — 可能是典型的类型(纺锤形)或者是以线条形式展现;
- 成交量比例周期 — 绘制VR所用的周期;
- 成交量比例修正数 — 因为VR影响宽度,可以通过这个参数调节它。
- 成交量加权移动平均周期 — 用于绘制VWMA的周期;
- 纺锤数量 — 显示的纺锤数量可以减少来降低系统加载开销;
- 用于绘制成交量加权移动平均的价格类型 — 选择绘制VWMA的价格类型。
输出变量用于验证和对输入参数的调整。图表参数变量随指标窗口的改变而改变。详细信息请看下一版块。
以指标缓存的声明作为第一部分的结束。此处有11个缓存。
OnInit函数
//+------------------------------------------------------------------+ //| 自定义指标初始化函数 | //+------------------------------------------------------------------+ int OnInit() { //--- 检查输入变量 if(period_VR<=0) { ext_period_VR=10; // 改变变量值 Alert("Volume Ratio formation period was input incorrectly and has been changed."); } else ext_period_VR=period_VR; if(correct_VR<=0) { ext_correct_VR=10; // 改变变量值 Alert("Volume Ratio correction number was input incorrectly and has been changed."); } else ext_correct_VR=correct_VR; if(period_VWMA<=0) { ext_period_VWMA=10; // 改变变量值 Alert("Volume Weighted Moving Average formation period was input incorrectly and has been changed."); } else ext_period_VWMA=period_VWMA; if(spindles_num<=0) { ext_spin_num=10; // 改变变量值 Alert("Number of spindles was input incorrectly and has been changed."); } else ext_spin_num=spindles_num; //--- 查找绘制图表所需的最长的周期 if(ext_period_VR>ext_period_VWMA)long_period=ext_period_VR; else long_period=ext_period_VWMA; //--- 指标缓存映射 SetIndexBuffer(0,Buff_up,INDICATOR_DATA); SetIndexBuffer(1,Buff_down,INDICATOR_DATA); SetIndexBuffer(2,Buff_color_up_down,INDICATOR_COLOR_INDEX); SetIndexBuffer(3,Buff_open_ext,INDICATOR_DATA); SetIndexBuffer(4,Buff_close_ext,INDICATOR_DATA); SetIndexBuffer(5,Buff_VWMA_ext,INDICATOR_DATA); SetIndexBuffer(6,Buff_open,INDICATOR_CALCULATIONS); SetIndexBuffer(7,Buff_close,INDICATOR_CALCULATIONS); SetIndexBuffer(8,Buff_VWMA,INDICATOR_CALCULATIONS); SetIndexBuffer(9,Buff_VR,INDICATOR_CALCULATIONS); SetIndexBuffer(10,Buff_time,INDICATOR_CALCULATIONS); //--- 设置指标名称 IndicatorSetString(INDICATOR_SHORTNAME,"SPC "+IntegerToString(magic_numb)); PlotIndexSetString(0,PLOT_LABEL,"SPC"); //--- 设置精度 IndicatorSetInteger(INDICATOR_DIGITS,_Digits+1); //--- 设置开始绘制指标的起始柱形 PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,long_period+1); //--- 禁止显示指标的当前值 PlotIndexSetInteger(0,PLOT_SHOW_DATA,false); PlotIndexSetInteger(1,PLOT_SHOW_DATA,false); PlotIndexSetInteger(2,PLOT_SHOW_DATA,false); PlotIndexSetInteger(3,PLOT_SHOW_DATA,false); //--- 设置不需要显示的值 PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0); PlotIndexSetDouble(2,PLOT_EMPTY_VALUE,0); PlotIndexSetDouble(3,PLOT_EMPTY_VALUE,0); //--- 创建要用到的对象 if(type_draw==0) { for(int x=0; x<=ext_spin_num; x++) { ObjectCreate(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"1",OBJ_BITMAP,ChartWindowFind(),__DATE__,0); ObjectCreate(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"2",OBJ_BITMAP,ChartWindowFind(),__DATE__,0); ObjectCreate(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"3",OBJ_BITMAP,ChartWindowFind(),__DATE__,0); ObjectCreate(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"4",OBJ_BITMAP,ChartWindowFind(),__DATE__,0); } } //--- return(INIT_SUCCEEDED); }
现在我们检查输入参数的正确性,如果有必要使用前述声明的变量(输出变量)修正它们。找出前面使用的最长的时间周期,初始化缓存以及配置指标的展现样式。在一个短数组(受“纺锤数量”参数的限制)中创建一个用于处理的图形对象。
OnChartEvent 函数
//+------------------------------------------------------------------+ //| ChartEvent函数 | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- 键盘事件 if(id==CHARTEVENT_KEYDOWN) { if(lparam==82) { if(ChartGetDouble(0,CHART_PRICE_MAX,ChartWindowFind())>0)// 检查图表上呈现的数据 { if(func_check_chart()==true) { if(type_draw==0)func_drawing(true,ext_spin_num,end_bar); } } } } }
此函数将“R”(代码82)同图表更新(或者说是重绘)操作绑定。它用于当指标窗口大小改变时调整(重绘)图表。因为当窗口尺寸改变时图像会被拉伸。通常价格变化时需重新绘制图表,但有时需要快速更新绘图。这个函数就是用于此的。
函数本身完全由if-else条件操作符组成,并且包含一个用于检查指标窗口变化的函数(func_check_chart),以及一个图表绘制函数(func_drawing)。
指标窗口检查函数
//+------------------------------------------------------------------+ //| 图标检查函数 | //+------------------------------------------------------------------+ bool func_check_chart() { //--- 响应变量 bool x=false; //--- 找出图表尺寸 int win=ChartWindowFind(); // 因为指标在子窗口中显示,定义子窗口 double win_price_max=ChartGetDouble(0,CHART_PRICE_MAX,win); // 图表的最大值 double win_price_min=ChartGetDouble(0,CHART_PRICE_MIN,win); // 图表的最小值 double win_height_pixels=(double)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,win); // 高度(像素) double win_width_pixels=(double)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS,win); // 宽度(像素) double win_bars=(double)ChartGetInteger(0,CHART_WIDTH_IN_BARS,win); // 宽度(以柱形数计) //--- 检查值是否改变 int factor=(int)MathPow(10,_Digits);// 设置double型转换成int型的转换因子 if(int(win_price_max*factor)!=int(win_price_max_ext*factor)) { win_price_max_ext=win_price_max; x=true; } if(int(win_price_min*factor)!=int(win_price_min_ext*factor)) { win_price_min_ext=win_price_min; x=true; } if(int(win_height_pixels*factor)!=int(win_height_pixels_ext*factor)) { win_height_pixels_ext=win_height_pixels; x=true; } if(int(win_width_pixels*factor)!=int(win_width_pixels_ext*factor)) { win_width_pixels_ext=win_width_pixels; x=true; } if(int(win_bars*factor)!=int(win_bars_ext*factor)) { win_bars_ext=win_bars; x=true; } if(func_new_bar(PERIOD_CURRENT)==true) { x=true; } return(x); }
此函数用于生成改变指标窗口尺寸的信号。首先使用函数(ChartGetInteger 和 ChartGetDouble)找出窗口的当前参数(以价格和像素表示的高度和宽度),然后将其同先前声明的全局变量(图表参数变量)进行比较。
图表绘制控制函数
//+------------------------------------------------------------------+ //| 绘图函数 | //+------------------------------------------------------------------+ void func_drawing(bool type_action,// 修正类型: 0-最近2个, 1-所有 int num, // 展现的纺锤形数量 int end_bar_now) // 当前最近的柱形 { int begin; if(end_bar_now>num)begin=end_bar_now-num; else begin=long_period+1; //--- 找到最大的VR值 double VR_max=0; for(int x=begin; x<end_bar_now-1; x++) { if(Buff_VR[x]<Buff_VR[x+1])VR_max=Buff_VR[x+1]; else VR_max=Buff_VR[x]; } //--- 计算尺寸 double scale_height=win_height_pixels_ext/(win_price_max_ext-win_price_min_ext); double scale_width=win_width_pixels_ext/win_bars_ext; //--- 绘图(x - 对象的部分名称,y - 绘制的数据数组索引) if(type_action==false)// false - 更新最近的两个纺锤 { for(int x=num-2,y=end_bar_now-2; y<end_bar_now; y++,x++) { func_picture("SPC"+IntegerToString(magic_numb)+IntegerToString(x),Buff_open[y],Buff_close[y],datetime(Buff_time[y]),Buff_VR[y],VR_max,Buff_VWMA[y],ext_correct_VR,scale_height,scale_width); } } //--- if(type_action==true)// true - 更新所有纺锤形 { for(int x=0,y=begin; y<end_bar_now; y++,x++) { func_picture("SPC"+IntegerToString(magic_numb)+IntegerToString(x),Buff_open[y],Buff_close[y],datetime(Buff_time[y]),Buff_VR[y],VR_max,Buff_VWMA[y],ext_correct_VR,scale_height,scale_width); } } ChartRedraw(); }
此函数是绘制图形的控制函数。输入参数是:修正参数(最近2个或者全部),纺锤形总数,最近的一个纺锤形。此处修正参数仅用于当最近的纺锤形中价格变化需修正时使用,并且保证其他纺锤形不受影响以提升指标性能。
接下来计算起始柱形。如果可用柱形的数据比纺锤形数量少,那么从最大的图标信息周期开始绘制。
然后找到最大的成交量比例(将被用作为参数传入func_picture函数,下面讨论)并计算绘图尺寸。根据修正参数调用纺锤形的修正循坏(图形对象此前已通过func_picture函数创建好了)。
图形绘制函数
//+------------------------------------------------------------------+ //| Picture 函数 | //+------------------------------------------------------------------+ void func_picture(string name, // 对象名称 double open, // 柱形的开盘价 double close, // 柱形的收盘价 datetime time, // 柱形起始时间 double VR, // 成交量比例 double VR_maximum, // 成交量比例的最大值 double VWMA, // 成交量加权移动平均值 int correct, // 成交量比例修正参数 double scale_height,// 高度尺寸(像素/价格) double scale_width) // 宽度尺寸(像素/柱形) { string first_name;// 用于绘图的文件的文件名首字母 string second_name_right;// 用于绘制右边图形的文件名的剩余部分 string second_name_left; // 用于绘制左边图形的文件名的剩余部分 double cathetus_a;// 直角边 a double cathetus_b;// 直角边 b double hypotenuse;// 斜边 int corner;// 角 //--- 找到柱形开始和结束的“角” cathetus_b=int(VR/VR_maximum/correct*scale_width);// 宽度(像素) //图像 540 if(open<=close) first_name="r";// 上涨bar或者 Doji if(open>close) first_name="b"; // 下跌bar //--- if(open<VWMA)// VWMA 在开盘价之上 { cathetus_a=int((VWMA-open)*scale_height); hypotenuse=MathCeil(MathSqrt(MathPow(cathetus_a,2)+MathPow(cathetus_b,2))); if(hypotenuse<=0) hypotenuse=1; corner=int(180-(MathArcsin(cathetus_b/hypotenuse)*360/(M_PI*2))); second_name_right=IntegerToString(corner); second_name_left=IntegerToString(180-corner); func_obj_mod(name+"1","::Images\\for_SPC\\"+first_name+second_name_right+".bmp",int(cathetus_b+1),int(cathetus_a+1),540-int(cathetus_a+2),ANCHOR_LEFT_LOWER,time,open); func_obj_mod(name+"2","::Images\\for_SPC\\"+first_name+second_name_left+".bmp",int(cathetus_b+1),int(cathetus_a+1),0,ANCHOR_RIGHT_LOWER,time,open); } if(open>VWMA)// VWMA在开盘价之下 { cathetus_a=int((open-VWMA)*scale_height); hypotenuse=MathCeil(MathSqrt(MathPow(cathetus_a,2)+MathPow(cathetus_b,2))); if(hypotenuse<=0) hypotenuse=1; corner=int((MathArcsin(cathetus_b/hypotenuse)*360/(M_PI*2))); second_name_right=IntegerToString(corner); second_name_left=IntegerToString(180-corner); func_obj_mod(name+"1","::Images\\for_SPC\\"+first_name+second_name_right+".bmp",int(cathetus_b+1),int(cathetus_a+1),0,ANCHOR_LEFT_UPPER,time,open); func_obj_mod(name+"2","::Images\\for_SPC\\"+first_name+second_name_left+".bmp",int(cathetus_b+1),int(cathetus_a+1),540-int(cathetus_a+2),ANCHOR_RIGHT_UPPER,time,open); } if(open==VWMA)// VWMA在开盘价格水平上 { func_obj_mod(name+"1","::Images\\for_SPC\\"+first_name+"90"+".bmp",int(cathetus_b+1),2,0,ANCHOR_LEFT,time,open); func_obj_mod(name+"2","::Images\\for_SPC\\"+first_name+"90"+".bmp",int(cathetus_b+1),2,0,ANCHOR_RIGHT,time,open); } if(close<VWMA)// VWMA在收盘价之上 { cathetus_a=int((VWMA-close)*scale_height); hypotenuse=MathCeil(MathSqrt(MathPow(cathetus_a,2)+MathPow(cathetus_b,2))); if(hypotenuse<=0) hypotenuse=1; corner=int(180-(MathArcsin(cathetus_b/hypotenuse)*360/(M_PI*2))); second_name_right=IntegerToString(corner); second_name_left=IntegerToString(180-corner); func_obj_mod(name+"3","::Images\\for_SPC\\"+first_name+second_name_right+".bmp",int(cathetus_b+1),int(cathetus_a+1),540-int(cathetus_a+2),ANCHOR_LEFT_LOWER,time,close); func_obj_mod(name+"4","::Images\\for_SPC\\"+first_name+second_name_left+".bmp",int(cathetus_b+1),int(cathetus_a+1),0,ANCHOR_RIGHT_LOWER,time,close); } if(close>VWMA)// VWMA 在收盘价之下 { cathetus_a=int((close-VWMA)*scale_height); hypotenuse=MathCeil(MathSqrt(MathPow(cathetus_a,2)+MathPow(cathetus_b,2))); if(hypotenuse<=0) hypotenuse=1; corner=int((MathArcsin(cathetus_b/hypotenuse)*360/(M_PI*2))); second_name_right=IntegerToString(corner); second_name_left=IntegerToString(180-corner); func_obj_mod(name+"3","::Images\\for_SPC\\"+first_name+second_name_right+".bmp",int(cathetus_b+1),int(cathetus_a+1),0,ANCHOR_LEFT_UPPER,time,close); func_obj_mod(name+"4","::Images\\for_SPC\\"+first_name+second_name_left+".bmp",int(cathetus_b+1),int(cathetus_a+1),540-int(cathetus_a+2),ANCHOR_RIGHT_UPPER,time,close); } if(close==VWMA)// VWMA在收盘价格水平上 { func_obj_mod(name+"3","::Images\\for_SPC\\"+first_name+"90"+".bmp",int(cathetus_b+1),2,0,ANCHOR_LEFT,time,close); func_obj_mod(name+"4","::Images\\for_SPC\\"+first_name+"90"+".bmp",int(cathetus_b+1),2,0,ANCHOR_RIGHT,time,close); } }
绘图的“核心”— 图形对象的位图计算和替换函数。就是在这个函数中一个柱形用到的位图(例如,四副位图)被计算出来用于绘制纺锤形。在func_obj_mod函数的帮助下改变图形对象的位图(所有图形对象都在代码开始时的OnInit函数中创建)。
当前修正柱形的参数传入此函数,其中就有先前提到的最大成交量比例,用于直角边b相关参数的计算(图.5,b;长度为d)。
接下来添加辅助变量(首字母是颜色,左侧和右侧文件名的剩余部分 — 文件名中的角度,直角边a和b,斜边和角),纺锤形的颜色由条件操作符if定义。然后根据同成交量加权移动平均(WVMA)相关的开盘和收盘价格水平,以及基于图5和图6涉及的公式来计算位图(4副位图),并且使用func_obj_mod函数来修改图形对象。
对象修改函数
//+------------------------------------------------------------------+ //| 函数 Obj Mod | //+------------------------------------------------------------------+ void func_obj_mod(string name, // 对象名称 string file, // 文件资源路径 int pix_x_b, // X轴可见范围 int pix_y_a, // Y轴可见范围 int shift_y, // Y轴偏移 ENUM_ANCHOR_POINT anchor,// 坐标 datetime time, // 时间坐标 double price) // 价格坐标 { ObjectSetString(0,name,OBJPROP_BMPFILE,file); ObjectSetInteger(0,name,OBJPROP_XSIZE,pix_x_b);// X轴可见范围 ObjectSetInteger(0,name,OBJPROP_YSIZE,pix_y_a);// Y轴可见范围 ObjectSetInteger(0,name,OBJPROP_XOFFSET,0);// X轴无偏移 ObjectSetInteger(0,name,OBJPROP_YOFFSET,shift_y);// 设置Y轴偏移 ObjectSetInteger(0,name,OBJPROP_BACK,false);// 在前端显示 ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);// 禁用拖拽模式 ObjectSetInteger(0,name,OBJPROP_SELECTED,false); ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);// 隐藏图形对象的名称 ObjectSetInteger(0,name,OBJPROP_ANCHOR,anchor);// 设置坐标点 ObjectSetInteger(0,name,OBJPROP_TIME,time);// 设置时间坐标 ObjectSetDouble(0,name,OBJPROP_PRICE,price);// 设置价格坐标 }
函数很简单,它替换传入改变对象属性的函数的值。主要的可变属性是对象的位图,可见范围和坐标位置。
OnCalculate函数
//+------------------------------------------------------------------+ //| 自定义指标迭代函数 | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- 检查历史周期数据的可用性 if(rates_total<long_period) { Alert("VR or VWMA period is greater than history data or history data is not loaded."); return(0); } //--- 查询位置 int position=prev_calculated-1; if(position<long_period)position=long_period; // 改变位置 //--- 缓存计算的主循环 for(int i=position; i<rates_total; i++) { //--- 填充直方图缓存 Buff_up[i]=high[i]; Buff_down[i]=low[i]; if(open[i]<close[i])Buff_color_up_down[i]=0;// 上涨柱形 if(open[i]>close[i])Buff_color_up_down[i]=1;// 下跌柱形 if(open[i]==close[i])Buff_color_up_down[i]=2;// Doji 柱形 //--- 填充辅助缓存 Buff_open[i]=open[i]; Buff_close[i]=close[i]; Buff_time[i]=double(time[i]); //--- 计算成交量比例 double mid_vol=0; int x=0; for(x=i-ext_period_VR; x<=i; x++) { mid_vol+=double(tick_volume[x]); } mid_vol/=x; Buff_VR[i]=tick_volume[i]/mid_vol; // 计算 VR //--- 计算成交量加权移动平均 long vol=0; double price_vol=0; x=0; switch(type_price_VWMA) { case 0: { for(x=i-ext_period_VWMA; x<=i; x++) { price_vol+=double(open[x]*tick_volume[x]); vol+=tick_volume[x]; } } break; //--- case 1: { for(x=i-ext_period_VWMA; x<=i; x++) { price_vol+=double(high[x]*tick_volume[x]); vol+=tick_volume[x]; } } break; //--- case 2: { for(x=i-ext_period_VWMA; x<=i; x++) { price_vol+=double(low[x]*tick_volume[x]); vol+=tick_volume[x]; } } break; //--- case 3: { for(x=i-ext_period_VWMA; x<=i; x++) { price_vol+=double(close[x]*tick_volume[x]); vol+=tick_volume[x]; } } break; //--- case 4: { for(x=i-ext_period_VWMA; x<=i; x++) { double price=(open[x]+high[x]+low[x]+close[x])/4; price_vol+=double(price*tick_volume[x]); vol+=tick_volume[x]; } } break; } Buff_VWMA[i]=price_vol/vol; // 计算 VWMA //--- if(type_draw==1) { Buff_open_ext[i]=Buff_open[i]; Buff_close_ext[i]=Buff_close[i]; Buff_VWMA_ext[i]=Buff_VWMA[i]; } else { //--- 减小未使用数组的大小 ArrayResize(Buff_open_ext,1); ArrayResize(Buff_close_ext,1); ArrayResize(Buff_VWMA_ext,1); //--- 未使用数组归零 ZeroMemory(Buff_open_ext); ZeroMemory(Buff_close_ext); ZeroMemory(Buff_VWMA_ext); } } end_bar=rates_total;// 定义最近柱形的编号 //--- if(ChartGetDouble(0,CHART_PRICE_MAX,ChartWindowFind())>0 && type_draw==0)// 检查开始绘图要用的指标窗口可用数据 { func_drawing(func_check_chart(),ext_spin_num,end_bar); } //---返回prev_calculated的值用于下次调用 return(rates_total); }
指标的计算和缓存数据填充的标准函数。首先验证VR和VWMA周期数据的有效性,如果有误的话会显示一个消息。然后找到填充缓存的起始位置。代表柱状最高和最低价的缓存被填充。此后,成交量比例(VR)以及成交量加权平均(VWMA)缓存被计算出来并根据公式1和2(在Introduction章节定义)。
其他函数
为了使指标运行起来更加恰当,给出一个新柱形定义函数(func_new_bar)以及指标反初始化函数(OnDeinit)。
func_new_bar函数决定新的柱形在图表上如何显示,并配合func_check_chart函数一起使用。
//+------------------------------------------------------------------+ //| New Bar 函数 | //+------------------------------------------------------------------+ bool func_new_bar(ENUM_TIMEFRAMES period_time) { static datetime old_times; // 存储旧值的变量 bool res=false; // 分析结果变量 datetime new_time[1]; // 新的柱形开始时间 int copied=CopyTime(_Symbol,period_time,0,1,new_time); // 将最近一个柱形的时间复制到new_time cell中 if(copied>0) // 复制数据 { if(old_times!=new_time[0]) // 如果保存的柱形开始时间不等于新的柱形开始时间 { if(old_times!=0) res=true; // 如果不是首次加载,true = new bar old_times=new_time[0]; // 保存柱形开始时间 } } return(res); } //+------------------------------------------------------------------+
此函数已经在之前的文章中介绍过了在此不再赘述。
函数OnInit中的OnDeinit函数删除早前创建的图形对像。该函数是指标的标准函数,在从图表上移除指标时被调用。
//+------------------------------------------------------------------+ //| OnDeinit | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- 删除已使用过的对象 if(type_draw==0) { for(int x=0; x<=ext_spin_num; x++) { ObjectDelete(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"1"); ObjectDelete(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"2"); ObjectDelete(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"3"); ObjectDelete(0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"4"); } } }
至此指标代码结束。如果你对这个此有任何疑问,请通过文章下的评论或者私信随时与我联系。
智能交易系统和交易策略
在研究一个交易策略前,先测试下EA如何基于此指标进行工作。测试EA将只使用一个纺锤形来分析其运作原理。不使用VR(成交量比例)。也就是说我们分析的是仅由一个纺锤组成的一系列模式。共有30种组合,详见图7:
图. 7. 纺锤形可能的样式
纺锤类型可以被分为3个大类和1个子类(图.7)。我们按照纺锤形的价格运动方向、开盘和收盘价格以及成交量加权平均移动水平的不同来分类。
假设纺锤形的第一个不同点是颜色,例如,考察周期内上升或下跌(图7,列1)。在图7的第一列中,(0) — 上升(红色) 和 (1) — 下跌(蓝色)。下一列显示了纺锤体B(开盘和收盘价格)及相关引线S(最高和最低价格)的不同。当前不同的样例仅分为三类(图7,列2)。第三列将VWMA(成交量移动平均)水平同最高及最低价(High 和 Low)进行比较。它可以位于最高低价之上(1),最低价之下(2),及最高和最低价之间(3)。在第三列纺锤形(3)也可以根据VWMA周期的最高和最低价来分类,因此形成了图7中的列3-3(由列3(3)衍生而来)。
给出了上述不同特性的所有可能的组合,我们得到30种纺锤形。
所有图7中的纺锤形将被分配智能交易系统中一个函数的对应返回结果,代码如下。
EA的参数
所有的代码都实现为函数形式,为了减少代码量,函数通过子函数来调用,从而构成了一个函数分层树。在代码开始部分声明的输入变量是指标的参数,以及交易量、止损和纺锤形的30种模式。用于指标句柄和用于确定模式形态的数据缓存变量在代码结尾处声明。
//+------------------------------------------------------------------+ //| EASPC.mq5 | //| Azotskiy Aktiniy ICQ:695710750 | //| https://login.mql5.com/zh/users/aktiniy | //+------------------------------------------------------------------+ #property copyright "Azotskiy Aktiniy ICQ:695710750" #property link "https://login.mql5.com/zh/users/aktiniy" #property version "1.00" //+------------------------------------------------------------------+ //| 绘图类型 | //+------------------------------------------------------------------+ enum type_drawing { spindles=0, // 纺锤形 line_histogram=1, // 线和柱状图 }; //+------------------------------------------------------------------+ //| 价格类型 | //+------------------------------------------------------------------+ enum type_price { open=0, // Open high=1, // High low=2, // Low close=3, // Close middle=4, // Middle }; //--- 输入参数 input long magic_numb=65758473787389; // Magic 数字 input type_drawing type_draw=1; // 指标绘制类型 input int period_VR=10; // 成交量比例周期 input int correct_VR=4; // 成交量比例修正数 input int period_VWMA=10; // 成交量加权移动平均周期 input int spindles_num=10; // 纺锤形数量 input type_price type_price_VWMA=0; // 用于绘制成交量加权移动平均的价格类型 // open=0; high=1; low=2; close=3; middle=4 input double lot=0.01; // 下单量 input int stop=1000; // 止损 //--- input char p1=1; // 执行模式 1-买, 2-卖, 3-平仓, 4-不执行任何操作 input char p2=1; input char p3=1; input char p4=1; input char p5=1; input char p6=1; input char p7=1; input char p8=1; input char p9=1; input char p10=1; input char p11=1; input char p12=1; input char p13=1; input char p14=1; input char p15=1; input char p16=1; input char p17=1; input char p18=1; input char p19=1; input char p20=1; input char p21=1; input char p22=1; input char p23=1; input char p24=1; input char p25=1; input char p26=1; input char p27=1; input char p28=1; input char p29=1; input char p30=1; //--- int handle_SPC; // 指标句柄 long position_type; // 持仓类型 //--- 指标值缓存 double Buff_up[3]; // 柱状图上方坐标点位的缓存 double Buff_down[3]; // 直方图下方坐标点缓存 double Buff_color_up_down[3]; // 直方图的颜色缓存 double Buff_open_ext[3]; // 开盘价缓存 double Buff_close_ext[3]; // 收盘价缓存 double Buff_VWMA_ext[3]; // 成交量加权移动平均缓存在OnInit函数中进行指标句柄初始。
//+------------------------------------------------------------------+ //| EA初始化函数 | //+------------------------------------------------------------------+ int OnInit() { //--- handle_SPC=iCustom(_Symbol,PERIOD_CURRENT,"SPC.ex5",magic_numb,type_draw,period_VR,correct_VR,period_VWMA,spindles_num,type_price_VWMA); //--- return(INIT_SUCCEEDED); }
将订单发送至服务器的函数
这类函数有两个:一个用于开仓,一个用于平仓。它们都是基于MQL5文档中的例子,并且包括交易请求结构体和对OrderSend函数的调用来进一步分析结果结构体。
//+------------------------------------------------------------------+ //| Send Order 函数 | //+------------------------------------------------------------------+ bool func_send_order(ENUM_ORDER_TYPE type_order,// 下单类型 double volume) // 下单量 { bool x=false; // 返回变量 //--- 声明用于发送订单的变量 MqlTradeRequest order_request={0}; MqlTradeResult order_result={0}; //--- 设置用于发送订单的变量 order_request.action=TRADE_ACTION_DEAL; order_request.deviation=3; order_request.magic=555; order_request.symbol=_Symbol; order_request.type=type_order; order_request.type_filling=ORDER_FILLING_FOK; order_request.volume=volume; if(type_order==ORDER_TYPE_BUY) { order_request.price=SymbolInfoDouble(_Symbol,SYMBOL_ASK); order_request.sl=order_request.price-(_Point*stop); } if(type_order==ORDER_TYPE_SELL) { order_request.price=SymbolInfoDouble(_Symbol,SYMBOL_BID); order_request.sl=order_request.price+(_Point*stop); } //--- 发送订单 bool y=OrderSend(order_request,order_result); if(y!=true)Alert("Order sending error."); //--- 检查结果 if(order_result.retcode==10008 || order_result.retcode==10009) x=true; return(x); } //+------------------------------------------------------------------+ //| 删除挂单函数 | //+------------------------------------------------------------------+ bool func_delete_position() { bool x=false; //--- 标记需要操作的订单 PositionSelect(_Symbol); double vol=PositionGetDouble(POSITION_VOLUME); long type=PositionGetInteger(POSITION_TYPE); ENUM_ORDER_TYPE type_order; if(type==POSITION_TYPE_BUY)type_order=ORDER_TYPE_SELL; else type_order=ORDER_TYPE_BUY; //--- 声明用于发送订单的变量 MqlTradeRequest order_request={0}; MqlTradeResult order_result={0}; //--- 设置用于发送订单的变量 order_request.action=TRADE_ACTION_DEAL; order_request.deviation=3; order_request.magic=555; order_request.symbol=_Symbol; order_request.type=type_order; order_request.type_filling=ORDER_FILLING_FOK; order_request.volume=vol; if(type_order==ORDER_TYPE_BUY)order_request.price=SymbolInfoDouble(_Symbol,SYMBOL_ASK); if(type_order==ORDER_TYPE_SELL)order_request.price=SymbolInfoDouble(_Symbol,SYMBOL_BID); //--- 发送订单 bool y=OrderSend(order_request,order_result); if(y!=true)Alert("Order sending error."); //--- 检查结果 if(order_result.retcode==10008 || order_result.retcode==10009) x=true; return(x); }
决定图表上新柱形展现的辅助函数func_new_bar也包含在代码中。它在上面已经极少过了无需在此赘述。
在介绍完所有标准函数之后,我们来看看算法的“核心”部分。
所有操作都在OnTick函数中实现。首先通过CopyBuffer函数填充用于计算的缓存变量。接着,检查当前货币对是否有持仓,这对于另一个用于订单(持仓)放置和移除的函数是有必要的。之后准备用于定义模式的变量。它们是实体 — 开盘和收盘价之间的距离,引线 — 当前周期的最高和最低价之间的距离,以及之后传给func_one函数的它们之间的比例(图.7,列2)。
然后函数func_two和func_three被分别用到图 7中列3和 3-3上。此后根据图7,列1,使用switch操作符来检查纺锤形的颜色。根据afun_1_1变量的值及基于实体和引线长短的图7列2,当下一个switch操作符切换到func_pre_work(后面讨论)函数时,我们就得到了一个决策树。
//+------------------------------------------------------------------+ //| EA的ontick函数 | //+------------------------------------------------------------------+ void OnTick() { //--- if(func_new_bar(PERIOD_CURRENT)==true) { //--- 复制指标缓存 CopyBuffer(handle_SPC,0,1,3,Buff_up); CopyBuffer(handle_SPC,1,1,3,Buff_down); CopyBuffer(handle_SPC,2,1,3,Buff_color_up_down); CopyBuffer(handle_SPC,3,1,3,Buff_open_ext); CopyBuffer(handle_SPC,4,1,3,Buff_close_ext); CopyBuffer(handle_SPC,5,1,3,Buff_VWMA_ext); //--- 分析情景 //--- 检查是否存在订单 if(PositionSelect(_Symbol)==true) { position_type=PositionGetInteger(POSITION_TYPE); // BUY=0, SELL=1 } else { position_type=-1; // 当前货币对上无持仓 } //--- 准备要比较的值 double body=Buff_open_ext[2]-Buff_close_ext[2]; body=MathAbs(body); double shadow=Buff_up[2]-Buff_down[2]; shadow=MathAbs(shadow); if(shadow==0)shadow=1;// 防止除数为0 double body_shadow=body/shadow; //--- 函数返回值变量 char afun_1_1=func_one(body_shadow); char afun_2_1=func_two(Buff_up[2],Buff_down[2],Buff_VWMA_ext[2]); char afun_3_1=func_three(Buff_open_ext[2],Buff_close_ext[2],Buff_VWMA_ext[2]); //--- switch(int(Buff_color_up_down[2])) { case 0: { switch(afun_1_1) { case 1: func_pre_work(afun_2_1,afun_3_1,p1,p2,p3,p4,p5); break; case 2: func_pre_work(afun_2_1,afun_3_1,p6,p7,p8,p9,p10); break; case 3: func_pre_work(afun_2_1,afun_3_1,p11,p12,p13,p14,p15); break; } } break; case 1: { switch(afun_1_1) { case 1: func_pre_work(afun_2_1,afun_3_1,p16,p17,p18,p19,p20); break; case 2: func_pre_work(afun_2_1,afun_3_1,p21,p22,p23,p24,p25); break; case 3: func_pre_work(afun_2_1,afun_3_1,p26,p27,p28,p29,p30); break; } } break; } } }
func_pre_work函数继续使已形成的决策树分叉。我们得到图7.列3(变量f_2)和列3-3(变量f_3)的代码实现, switch直到决策树的最后一个函数 — func_work结束。
//+------------------------------------------------------------------+ //| 函数 Pre Work | //+------------------------------------------------------------------+ void func_pre_work(char f_2, // 函数Two的返回结果 char f_3, // 函数Three的结果 char pat_1, // 模式 1 char pat_2, // 模式 2 char pat_3_1, // 模式 3_1 char pat_3_2, // 模式 3_2 char pat_3_3) // 模式 3_3 { switch(f_2) { case 1: //1 func_work(pat_1); break; case 2: //2 func_work(pat_2); break; case 3: { switch(f_3) { case 1: //3_1 func_work(pat_3_1); break; case 2: //3_2 func_work(pat_3_2); break; case 3: //3_3 func_work(pat_3_3); break; } } break; } }
func_work函数根据四个所选项之一:买入,卖出,平仓和无,来确定所需执行的操作。最后的控制交由函数func_send_order和func_delete_position。
//+------------------------------------------------------------------+ //| 函数 Work | //+------------------------------------------------------------------+ void func_work(char pattern) { switch(pattern) { case 1: // 买 if(position_type!=-1)func_delete_position(); func_send_order(ORDER_TYPE_BUY,lot); break; case 2: // 卖 if(position_type!=-1)func_delete_position(); func_send_order(ORDER_TYPE_SELL,lot); break; case 3: // 平仓 if(position_type!=-1)func_delete_position(); break; case 4: // 无操作 break; } }
先前提到的最后几个函数是:func_one,func_two 和 func_three。它们将以价格形势传递的数据转换成整型数据到switch表达式中。如果你回到图7,函数func_one — 是列2的实现,函数func_two — 是列3的实现以及函数func_three — 是列3-3的实现。这些函数的返回值是1,2和3,也对应于图7上的标号。
//+------------------------------------------------------------------+ //| 函数 func_one | //+------------------------------------------------------------------+ char func_one(double body_shadow_in) { char x=0; // 返回变量 if(body_shadow_in<=(double(1)/double(3))) x=1; if(body_shadow_in>(double(1)/double(3)) && body_shadow_in<=(double(2)/double(3))) x=2; if(body_shadow_in>(double(2)/double(3)) && body_shadow_in<=1) x=3; return(x); } //+------------------------------------------------------------------+ //| 函数 func_two | //+------------------------------------------------------------------+ char func_two(double up,// high [Buff_up] double down,// low [Buff_down] double VWMA) // VWMA [Buff_VWMA_ext] { char x=0; // 返回变量 if(VWMA>=up) x=1; if(VWMA<=down) x=2; else x=3; return(x); } //+------------------------------------------------------------------+ //| 函数 func_three | //+------------------------------------------------------------------+ char func_three(double open,// open [Buff_open_ext] double close,// close [Buff_close_ext] double VWMA) // VWMA [Buff_VWMA_ext] { char x=0; // 返回变量 if(open>=VWMA && close>=VWMA) x=1; if(open<=VWMA && close<=VWMA) x=2; else x=3; return(x); } //+------------------------------------------------------------------+
至此,EA已经准备好可以用于测试了。首先定义参数:
- 货币对和时间框架 — EURUSD, H1;
- 测试时间跨度 — с 01.01.2013 по 01.01.2015 (2 года);
- 止损 — 1000;
- 服务器 — MetaQuotes-Demo.
只对所做的操作(例如,买,卖,平仓及无)和VWMA周期进行优化。因此我们将找到对应每种模式获利最多的操作以及什么样的VWMA周期最适合H1时间框架。
策略测试器的设置如图8中所示:
图. 8. 策略测试器的设置
如前所述,优化只在根据模式所做的操作和VWMA周期(从10到500个柱形)上进行,如图9:
图. 9. 参数优化
在优化过程中我们得到一幅图,图10:
图. 10. 优化图
根据优化结果,我们获利138.71,交易量大小为0.01手初始存款10000,回撤2.74%(大约28个单位),如图11:
图. 11. 优化结果
让我们把交易量提升至0.1手,初始存款降到1000,使用前次优化结果进行第二次测试。为了提升测试精度,我们将交易模式改为OHLC on M1,得到图12:
图. 12. 测试结果(历史数据回测)
2年时间共执行了742笔交易(每天大约3笔),最大回撤是$252,净利润是 — 1407,大约每个月$60(总投如的6%)。理论上来说一切看上去都非常好,但不保证实际中是否也会取得一样好的效果。
当然这个EA还需要进一步的自改和优化,也许需要引入附加的纺锤形模型及VR水平。这留给后来者思考,但是即使只有这点参数的EA,已经显示出了这个指标相当诱人的结果。
在使用指标的时候,交易策略非常简单 — 当出现向上箭头时买入,当出现向下箭头时卖出。菱形是一种Doji,它预示着反转。这在图13中已经清楚的显示了:
图.13. 指标操作
从图13中可见,指标直到数字1处一直绘制向上的箭头,然后一个蓝色的菱形出现了,这有可能预示着趋势运动方向的改变。趋势改变,价格下跌直到数字2处,之后出现一个红色的菱形,这也是趋势改变的先兆,之后果然发生了。
总结
我对这个指标所知的信息破少,但这不妨碍我对它独创性的浓厚兴趣。也许让我三思的代码实现的复杂性也是影响我的因素。时间不会白花,我希望这个指标对许多人有用。我仍未这个主题还未被完全开发发掘出来,因为它涉及的面非常宽泛,但我想终论坛中的朋友终究会进一步深入研究它。需要引起特别关注的是EA策略的探讨和修改,即使原始的方案展现出了较好的结果。欢迎读者在本文中进行任何评论和讨论,以及给我私信。
这是又一篇关于指标的文章,如果任何人有关于新的、有趣的指标的想法,请私信我。我不能保证帮你实现,但是我一定会认真考虑并或许给出些建议。我的下一篇文章将完全转换一个主题,但我不打算在此提前谈论它,因为它还仅仅是一个想法,代码还在规划阶段。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/1844