近年来,金融市场分析的多样化方法越来越受到交易者的欢迎。 我也想为此做出点贡献,讲一下如何通过几十行代码的编写来制作一个很好的指标。 而且,我还会简要地向您介绍一下模糊逻辑的基础知识。
如果您对此感兴趣,想要更深入地钻研,请参阅下述文献:
1. Leonenkov А. "Fuzzy Simulation in MATLAB and fuzzyTECH" (俄语)
有一些简单的表达,比如“……多一点……”、……太快了……”、……几乎没有……”等等,我们该怎样向计算机解释它们的意思呢? 实际上,通过模糊集理论要素(或是所谓的“隶属函数”)的使用,这是很可能实现的。А. Leonenkov 的书中有一个例子:
我们来讲解一下“热咖啡”一词的隶属函数: 咖啡的温度范围被认为是在 0 到 100 摄氏度之间,原因很简单,如果温度低于 0 度,它会变成冰;而如果温度高于 100 度,它又会蒸发。 非常明显,一杯 20 度的咖啡不能称为“热”,即,“热”分类中的隶属函数等于 0;而如果咖啡达到 70 度,它当然就属于“热”分类,因此,这种情况下的函数值就等于 1。
至于上述两种极端值之间的温度值,情况就不是那么明朗了。 有些人可能会认为一杯 55 度的咖啡“热”,但有些人却认为“不太热”。 此即“模糊性”。
虽然如此,我们还是可以想像出隶属函数的大致模样: 它是“单调递增”的:
上图所示为“分段线性”隶属函数。
由此,可通过下述解析式对该函数进行定义:
我们会在指标中使用此类函数。
不管怎样,任何技术指标的任务,都是确定当前的市场状态(平缓、上升趋势、下降趋势),并生成进入/退出市场的信号。 如何利用隶属函数的帮助实现这些呢? 非常简单。
首先,我们需要定义边界条件。 我们采用下述边界条件: 对于«100% 上升趋势»而言,会是穿越时间周期为 2、基于典型价格(最高价+最低价+收盘价)/3、轨道线上边界参数为 8,0.08,SMA,Close (收盘) 的 EMA;而对于«100% 下降趋势»而言,则会是穿越带有轨道线下边界参数的相同的 EMA。 位于上述两个条件之间的一切,均被假定为平缓。我们再添加一个带有 32、0.15、SMA 和 Close 参数的轨道线。
结果是我们会得到两个完全相同的隶属函数。 如果两个函数都等于 1,买入信号就会被激活;而如果两个信号都等于 -1,则卖出信号会被激活。因为从 -1 到 1 的范围方便构建图表,所以作为结果的图表会被作为两个函数的算术平均值 F(x)= (f1(x)+f2(x))/2 来获取。
此为其图表样式:
本例中的隶属函数会有如下的图形呈示:
经过分析,可如下编写:
,
a 和 b 分别是包络线的上边界和下边界,而 х 则是 EMA(2) 的一个值。
定义函数之后,我们现在可以更进一步,去编写指标代码。
首先,我们定义要绘制的内容和方式。
隶属函数计算结果会线性呈现 - 分别为红色和蓝色。
算数平均值会以柱状图的形式、从零线显示,并根据作为结果的函数值,从五种颜色中选择一种上色。
DRAW_COLOR_HISTOGRAM 绘图风格会用于其中。
我们在柱形图上,绘制蓝色和红色矩形作为买入/退出信号,而它们的值则等于 1 或 -1。
现在,是时候运行MetaEditor了,再打开New(新建)->Custom Indicator(自定义指标)->Next...填写 "Parameters" (参数)字段:
创建缓冲区:
点击 "Finish"(结束) 按钮后,我们会收到一份源代码,并实施改进。
首先,我们来定义缓冲区的数量。 其中有七个已通过向导创建完毕(5 个数据缓冲区,2 个颜色缓冲区)。 我们还需要 5 个。
#property indicator_minimum -1.4 // 设置小数值 #property indicator_maximum 1.4 //由于某些原因,EA向导忽略小数部分 #property indicator_buffers 12 // 将值从7改变为12(增加5个缓存)我们来编辑输入参数:
input string txt1="----------"; input int Period_Fast=8; input ENUM_MA_METHOD Method_Fast = MODE_SMA; /*平滑方式*/ //移动平均平滑方式 input ENUM_APPLIED_PRICE Price_Fast = PRICE_CLOSE; input double Dev_Fast=0.08; input string txt2="----------"; input int Period_Slow=32; input ENUM_MA_METHOD Method_Slow = MODE_SMA; input ENUM_APPLIED_PRICE Price_Slow = PRICE_CLOSE; input double Dev_Slow=0.15; /*偏差参数*/ input string txt3="----------"; input int Period_Signal=2; input ENUM_MA_METHOD Method_Signal = MODE_EMA; input ENUM_APPLIED_PRICE Price_Signal = PRICE_TYPICAL; input string txt4="----------";
在已声明的变量后注释非常方便。 注释文本被插入到指标参数窗口当中。
创建列表的可能性也非常有用:
保存指标句柄和缓冲区的变量:
int Envelopes_Fast; // 快速轨道线 int Envelopes_Slow; // 慢速轨道线 int MA_Signal; // 信号线 double Env_Fast_Up[]; // 快速轨道线上轨 double Env_Fast_Dn[]; // 快速轨道线下轨 double Env_Slow_Up[]; // Slow 慢速轨道线上轨 double Env_Slow_Dn[]; // Slow 慢速轨道线下轨 double Mov_Sign[]; // 信号线
现在转到 OnInit() 函数。
我们来完善一些: 指定指标名称,并移除多余的十进制零:
IndicatorSetInteger(INDICATOR_DIGITS,1); // 设置显示精度,我们不需要过分精确的值 string name; //指标名称 StringConcatenate(name, "FLE ( ", Period_Fast, " , ", Dev_Fast, " | ", Period_Slow, " , ", Dev_Slow, " | ", Period_Signal, " )"); IndicatorSetString(INDICATOR_SHORTNAME,name);
再添加缺失的缓冲区:
SetIndexBuffer(7,Env_Fast_Up,INDICATOR_CALCULATIONS); SetIndexBuffer(8,Env_Fast_Dn,INDICATOR_CALCULATIONS); SetIndexBuffer(9,Env_Slow_Up,INDICATOR_CALCULATIONS); SetIndexBuffer(10,Env_Slow_Dn,INDICATOR_CALCULATIONS); SetIndexBuffer(11,Mov_Sign,INDICATOR_CALCULATIONS);
INDICATOR_CALCULATIONS 参数意味着该缓冲区数据仅用于中间计算。 不会显示于图表上。
注意带颜色缓冲区的指标的声明方式:
SetIndexBuffer(4,SignalBuffer1,INDICATOR_DATA); // 首先是所有指标缓存 SetIndexBuffer(5,SignalBuffer2,INDICATOR_DATA); //这是颜色柱状图2,它有2 个数据缓存 SetIndexBuffer(6,SignalColors,INDICATOR_COLOR_INDEX);// 然后是颜色缓存。
填写句柄:
Envelopes_Fast = iEnvelopes(NULL,0,Period_Fast,0,Method_Fast,Price_Fast,Dev_Fast); Envelopes_Slow = iEnvelopes(NULL,0,Period_Slow,0,Method_Slow,Price_Slow,Dev_Slow); MA_Signal = iMA(NULL,0,Period_Signal,0,Method_Signal,Price_Signal);
有关 OnInit() 函数的所有工作均已完毕。
现在,我们来创建将用于计算隶属函数值的函数:
double Fuzzy(double x,double a, double c) { double F; if (a<x) F=1; // 100% 上升趋势 else if (x<=a && x>=c) F=(1-2*(a-x)/(a-c));// 平坦 else if (x<c) F=-1; // 100% 下降趋势 return (F); }
准备就绪。 变量与缓冲区已声明,句柄亦已分配。
现在该处理 OnCalculate() 基本函数了。
首先,我们将必要指标的值都写入中间缓冲区。 利用 CopyBuffer() 函数:
CopyBuffer(Envelopes_Fast, // 指标句柄 UPPER_LINE, // 指标缓存 0, // 开始点 0 - 从最开始 rates_total, // 要拷贝多少 - 所有 Env_Fast_Up); // 值所写入的缓存 // - 剩下的实现方式类似 CopyBuffer(Envelopes_Fast,LOWER_LINE,0,rates_total,Env_Fast_Dn); CopyBuffer(Envelopes_Slow,UPPER_LINE,0,rates_total,Env_Slow_Up); CopyBuffer(Envelopes_Slow,LOWER_LINE,0,rates_total,Env_Slow_Dn); CopyBuffer(MA_Signal,0,0,rates_total,Mov_Sign);
我们必须在这里添加计算优化的代码(仅执行最后一个柱的重新计算):
// 声明开始变量来存储柱形的索引,指标缓存 // 将从中进行重算。 int start; if (prev_calculated==0) // 如果还没有柱形被计算 { start = Period_Slow; // 不是所有的指标都计算到这个值了,因此,没必要执行此代码 } else start=prev_calculated-1; for (int i=start;i<rates_total;i++) { // 剩余的所有代码将写在这里 }
剩下的代码不多了。
设置 x、a、b 参数,执行隶属函数值的计算,并将其写入相应缓冲区:double x = Mov_Sign[i]; // 信号 //设置第一个成员函数的参数: double a1 = Env_Fast_Up[i]; // 上边界 double b1 = Env_Fast_Dn[i]; // <s1设置第一个成员函数的值并将它写入缓存 Rule1Buffer[i] = Fuzzy(x,a1,b1); // 设置第二个成员函数的参数: double a2 = Env_Slow_Up[i]; // 上边界 double b2 = Env_Slow_Dn[i]; // <s1设置第二个成员函数的值并将它写入缓存 Rule2Buffer[i] = Fuzzy(x,a2,b2);
建立了两条指标线。
我们现在来计算结果值。
ResultBuffer[i] = (Rule1Buffer[i]+Rule2Buffer[i])/2;
然后,再将柱形图涂上对应的颜色: 因为我们有五种颜色,所以 ResultColors[i] 的值从 0 到 4。
一般来讲,可能颜色的数量有 64 种。所以,这可是发挥个人创新能力的一次好机会。
for (int ColorIndex=0;ColorIndex<=4;ColorIndex++) { if (MathAbs(ResultBuffer[i])>0.2*ColorIndex && MathAbs(ResultBuffer[i])<=0.2*(ColorIndex+1)) { ResultColors[i] = ColorIndex; break; } }
接下来我们要绘制信号矩形。 采用 DRAW_COLOR_HISTOGRAM2 绘图风格。
它拥有两个带柱形图的数据缓冲区,和一个在前两者之间建立的颜色缓冲区。
数据缓冲区的值始终相同: 1.1 和 1.3 作为买入信号,-1.1 和 -1.3 作为卖出信号。
EMPTY_VALUE 意味着没有信号。
if (ResultBuffer[i]==1) { SignalBuffer1[i]=1.1; SignalBuffer2[i]=1.3; SignalColors[i]=1; } else if (ResultBuffer[i]==-1) { SignalBuffer1[i]=-1.1; SignalBuffer2[i]=-1.3; SignalColors[i]=0; } else { SignalBuffer1[i]=EMPTY_VALUE; SignalBuffer2[i]=EMPTY_VALUE; SignalColors[i]=EMPTY_VALUE; }
点击 "Compile" (编译),瞧!
还可以添加什么? 我在本文中讲到的是最基础的模糊逻辑方法。
这里有足够的空间容纳各种试验。 比如说,我们可以利用下述函数:
我觉得对于您来说,要为其编写解析式并找出合适条件,并不难。
祝您好运!
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...