请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  量化策略 帖子:3364748 新帖:12

利用模糊逻辑创建指标的简单示例

到此一游发表于:4 月 17 日 16:21回复(1)

简介

近年来,金融市场分析的多样化方法越来越受到交易者的欢迎。 我也想为此做出点贡献,讲一下如何通过几十行代码的编写来制作一个很好的指标。 而且,我还会简要地向您介绍一下模糊逻辑的基础知识。

如果您对此感兴趣,想要更深入地钻研,请参阅下述文献:

1.  Leonenkov А. "Fuzzy Simulation in MATLAB and fuzzyTECH" (俄语)
2.  Bocharnikov V."Fuzzy Technology: Mathematical Background. Simulation Practice in Economics" (俄语)。
3.  S.N. Sivanandam, S. Sumathi, S.N. Deepa. Introduction to Fuzzy Logic using MATLAB.
4.  C. Kahraman. Fuzzy Engineering Economics with Applications (Studies in Fuzziness and Soft Computing).

1. 模糊逻辑基础知识

有一些简单的表达,比如“……多一点……”、……太快了……”、……几乎没有……”等等,我们该怎样向计算机解释它们的意思呢? 实际上,通过模糊集理论要素(或是所谓的“隶属函数”)的使用,这是很可能实现的。А. Leonenkov 的书中有一个例子:

我们来讲解一下“热咖啡”一词的隶属函数: 咖啡的温度范围被认为是在 0 到 100 摄氏度之间,原因很简单,如果温度低于 0 度,它会变成冰;而如果温度高于 100 度,它又会蒸发。 非常明显,一杯 20 度的咖啡不能称为“热”,即,“热”分类中的隶属函数等于 0;而如果咖啡达到 70 度,它当然就属于“热”分类,因此,这种情况下的函数值就等于 1。

至于上述两种极端值之间的温度值,情况就不是那么明朗了。 有些人可能会认为一杯 55 度的咖啡“热”,但有些人却认为“不太热”。 此即“模糊性”。

虽然如此,我们还是可以想像出隶属函数的大致模样: 它是“单调递增”的:

上图所示为“分段线性”隶属函数。

由此,可通过下述解析式对该函数进行定义:

我们会在指标中使用此类函数。

2. 隶属函数

不管怎样,任何技术指标的任务,都是确定当前的市场状态(平缓、上升趋势、下降趋势),并生成进入/退出市场的信号。 如何利用隶属函数的帮助实现这些呢? 非常简单。

首先,我们需要定义边界条件。 我们采用下述边界条件: 对于«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) 的一个值。

定义函数之后,我们现在可以更进一步,去编写指标代码。


3. 创建程序代码

首先,我们定义要绘制的内容和方式。

隶属函数计算结果会线性呈现 - 分别为红色和蓝色。

算数平均值会以柱状图的形式、从零线显示,并根据作为结果的函数值,从五种颜色中选择一种上色。

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" (编译),瞧!

总结

还可以添加什么? 我在本文中讲到的是最基础的模糊逻辑方法。

这里有足够的空间容纳各种试验。 比如说,我们可以利用下述函数:

我觉得对于您来说,要为其编写解析式并找出合适条件,并不难。

祝您好运!

全部回复

0/140

量化课程

    移动端课程