创建手动交易策略的模糊逻辑

Alexander Fedosov | 25 三月, 2016

简介

科技的迅猛发展,使得在现代外汇交易市场上使用自动化交易系统的趋势越来越显著。然而,许多交易者仍旧进行手动交易。两种交易方式的优缺点众所周知:自动交易对市场的变化缺乏弹性,相反,手动交易因为人性的特点容易太具有弹性。事实上,在这个对比中,这是同一种实质的两种极端情况。

先前的文章中我给出了例子,我试图通过应用模糊逻辑来弥补手工交易的缺点。例如,将应用于交易机器人或者指标的过度严格的策略进行模糊化处理。在本文中将讨论改进人工交易策略的方法。现在科技的应用,即使是最终决定在交易者手中的人工交易领域,也是利大于弊。

 

根据一个特定的条件,选择手动交易策略

许多现代交易者选择主观开仓及平仓,而非依赖自动交易系统。他们要求开发属于自己的交易策略,将所有市场行为的可能结果考虑在内。之后,你必须严格遵守它并且抵制任何由恐惧和贪婪引起的冲动。

因此,首先我们要有一个交易策略。让我向你描述一下我们打算构建策略的三个步骤。

阶段 1.

在第一阶段,作为一个例子,我选用三个指标来构建交易策略:

  1. 平均定向运动指数,ADX。这是一个用于确定当前趋势强度的趋势指标。
  2. 相对强度指数,RVI 振荡器。
  3. Bill Williams的加速度振荡器(AC)指标

选择普通视图和MQL4交易终端的设置:

图 1. 策略设置的一般视图

阶段 2.

在第二阶段,我们将找到使用选定交易标的的方法,交易条件以及开仓参数。

让我们从头开始。

为了更加清晰,这些设置将被标准化。

做多(买入信号)

  1. 绿色的ADX指标主线具有大于或对于30的值,而+DI值大于-DI。
  2. AC的值在当前柱形上增长,并且比前两个柱形上逐渐增大的值都要大。视觉上,有三列绿色直方图,每列都比前一列短,并且所有这三个直方图都位于负值区域。
  3. RVI信号(浅红色)线穿越主线(绿色),两者都在增长,但仍旧在零点以下。
  4. 我们买0.01手,设置止赢50点止损30点。
做空(卖出信号)
  1. 绿色的ADX指标主线具有大于或对于30的值,而+DI的值小于-DI。
  2. AC值在当前柱形上下跌,并且前两个柱形上AC值也持续下跌。视觉上,有三列红色直方图,每一列都比前一列低,三个的值都大于零。
  3. RVI信号线(浅红色)穿越主线(绿色),都为下降趋势但都在正值区域。
  4. 我们卖出0.01手,设置止赢50点止损30点。

阶段 3.

我们所有要做的工作是确定如何退出。作为退出条件,我们将设置如前所述的价格目标:即获得50点利润或者执行30点的止损。

因此,我们的交易策略现在确定了。我们已经设置好了开仓和平仓的条件,选定了指标以及它们的参数,确定了交易量以及目标。最后我们决定了退出市场的条件。

在下一阶段,我们打算在实际市场环境下检验创建的交易策略。我们需要理解的第一件事情是,没有长期来看理想的和绝对适合所有市场环境的策略。采用自动和手动的交易者经常审视他们的交易系统,在不同的市场条件下系统的表现有所不同。同时,使用初始条件的交易系统也经常能够有很好的表现。

例如,对于我们的系统,交易者可能注意到止赢的设置可以设的更大一些。这并不是因为他简单的希望获得更多利润,而是因为他不断地分析此系统,并且统计数据表明,在平仓之后,价格一直朝着预期的方向移动。因此,交易者可能会问:如何应用交易系统长生的统计数据和观察值来改进结果?

 

用模糊逻辑修正严格形式化的缺点

让我们以模糊逻辑的理论来看看系统中使用的指标。在我先前的文章中我试图传达它的主要优点 — 对于分析应用严格交易条件的策略部分,它能增加其弹性。模糊逻辑模糊化严格边界,给出一个更为宽泛的评价图景以及系统在其执行边界区域的响应。这里有一个更为合适的方法来应用我们的ADX指标。首先,在弱的、平稳的的和强的趋势之间进行严格区分,但是随后这些分类被迷糊化,不严格根据指标的值来确定趋势的强度。

但是回到我们的交易系统中并问问我们自己:这对我们有什么用?

想象一下,我们的交易者观察市场并发现他的第一个指标发出了信号:比如,ADX到达了32。他标记下来并等待其他两个指标的确认。不久AC指标发出了信号,此时ADX显示已经上升到了40。随后RVI信号线穿越了主线,这意味着所有三个条件被满足。ADX已经达到了45点。但是ADX的绝对值在我们的系统中并不重要。关键是它超过了30。因此,交易者遵循他的规则,并且开仓了,0.01手,止赢50点止损30点。

现在,我们来模拟另一种场景。一开始,情形的发展和第一个案例一样。ADX=32,AC产生了信号,ADX同时到达40点。但是最后一个开仓信号RVI出现时,ADX飙升到了55而非45。比较这两种情形,第二种信号更强,但是我们的交易者仍旧以相同的手数进行交易,并且止赢止损也一样。

此处我们遇到了策略的第一个缺点。仅仅评估信号的出现,而没有关注其质量。即使我们能够评估并定义其种类,但是定义的精度仍旧将在过渡区域中丢失。

因此我们如何才能明确ADX、RVI指标的某一特定情景,并且将其参数和我们的开仓量相结合呢?要实现这个目标,我们得执行以下步骤:

我们将从描述第一个输入变量 — 趋势强度的值开始。

1. 我们将设置4中趋势强度的类型:弱的, 温和的, 中等的剧烈的。它看上去像这样:

图 2. 趋势强度可视化分类

2. 为了设置输出信号的类型,需要确定ADX的类别是如何影响我们的头寸的。通常,趋势越强持续的时间越久。因此,我们将做相应处理:取决于当所有三个信号出现时ADX的位置,我们打算增加止赢10-50点。

下面的类别将被使用到获利目标值上,在我们策略的初始止赢50点基础上添加。

3. 在接下来的步骤中我们将描述由先前模糊集理论的成员函数设置的条件。四种趋势类型的描述如下:


图 3. 模糊理论对四种趋势的描述

如图3所示,每一个类别都由成员函数设置,诸如:弱趋势和强趋势由两个梯形函数设置,温和的和中等的趋势类型由两个三角形函数设置。

现在,我们同样定义RVI。

1. 让我们设置RVI的类型。有四种类型:弱的中等的强的以及剧烈的。它看上去像这样:

图 4. 相对活力指数的可视化分类

2. 现在,我们将介绍成员函数定义的分类。梯形函数将用于描述若的剧烈的两种类型,三角形函数将用于描述中等的强的两种类型。


图. 5. RVI指数类型的描述

同样,我们将盈利目标的值分为四类:第一和第四类(10-20和40-50点)使用梯形函数,剩下的两类(20-30和30-40点) — 使用三角形函数。这是我们描述的输入信号看上去的样子。


图. 6. 对于利润目标值类别的描述

 

采用面板界面来显示头寸修改的建议

当创建信息面板时,我们选择四个观察参数:

完整实现的交易策略总体看上去如下(图6)。

图. 7. 完整实现,整体展现,以及交易策略的设置

现在让我们研究下使用MQL4工具FuzzyNet库实现的这个面板。

我们实现并分析这个由MQL4工具生成的信息面板的关键逻辑块。

//+------------------------------------------------------------------+
//| FuzzyNet面板
//+------------------------------------------------------------------+
#property copyright "Alexander Fedosov"
#property version "1.0"
#property strict
#property link "https://www.mql5.com/ru/users/alex2356/"
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_color1 Green
//+------------------------------------------------------------------+
//| 连接库
//+------------------------------------------------------------------+
#include <Math\FuzzyNet\MamdaniFuzzySystem.mqh>

我们定义初始属性并将库和模糊逻辑连接起来。我们将设置选项来配置信息面板在图表上的位置。我们将为当前所分析柱形的箭头指标,定义一个指标缓存和颜色(绿色)。

//--- 输入参数
input string  p1="==== Parameters ====";
input int fontSize=15;
input int adx_period=10;
input int rvi_period=10;
input int num_bar=0;
input int Screen_corner=4;
input color label_clr=Red;
input color textColor=Black;

我们来仔细分析下输入参数的第一块。它包含下面的元素:

输入参数的第二块模糊逻辑参数包含了描述所有输入(ADX趋势强度,RVI指数)和输出参数(获利目标点数的推荐值)的成员函数的大多数扩展性设置。

input string  p2="==== Fuzzy Logic Parameters ====";
//--- ADX
input double in_term1a = 20;
input double in_term1b = 30;
input double in_term1c = 40;
input double in_term1d = 45;
input double in_term2a = 40;
input double in_term2b = 50;
input double in_term2c = 60;
input double in_term3a = 50;
input double in_term3b = 60;
input double in_term3c = 70;
input double in_term4a = 60;
input double in_term4b = 70;
input double in_term4c = 100;
input double in_term4d = 120;
//--- RVI
input double in_term1a1 = -0.25;
input double in_term1b1 = 0.1;
input double in_term1c1 = 0.15;
input double in_term1d1 = 0.25;
input double in_term2a1 = 0.15;
input double in_term2b1 = 0.25;
input double in_term2c1 = 0.35;
input double in_term3a1 = 0.25;
input double in_term3b1 = 0.35;
input double in_term3c1 = 0.45;
input double in_term4a1 = 0.4;
input double in_term4b1 = 0.45;
input double in_term4c1 = 1;
input double in_term4d1 = 1.2;
//--- 输出
input double out_term1a = 5;
input double out_term1b = 10;
input double out_term1c = 15;
input double out_term1d = 22.5;
input double out_term2a = 17.5;
input double out_term2b = 25;
input double out_term2c = 32.5;
input double out_term3a = 27.5;
input double out_term3b = 35;
input double out_term3c = 42.5;
input double out_term4a = 37.5;
input double out_term4b = 45;
input double out_term4c = 50;
input double out_term4d = 60;
input double min_tp = 10;
input double max_tp = 50;


下一个块中,我们声明变量,标题名称,信息面板的模板(尺寸、位置、字体和其他),并且设置显示当前柱形标识(我们的例子中为箭头)的参数。

int scaleX=55,scaleY=25,offsetX=35;
//---声明指标名称的数组
string signalName[]={"ADX_val:","RVI_val:","TP_plus:","TP_prc:"};
double adx,adx_di_minus,adx_di_plus,rvi,rvi_sig,mdm;
double Buffer[];
//+------------------------------------------------------------------+
//| 自定义指标初始化函数                               
//+------------------------------------------------------------------+
int OnInit()
  {
   if(fontSize>15 || fontSize<8)
     {
      Print("ERROR: Incorrect fontSize. Must be 8-15.");
      Alert("ERROR: Incorrect fontSize. Must be 8-15.");
      return(0);
     }
   if(Screen_corner>4 || Screen_corner<1)
     {
      Print("ERROR: Incorrect Screen_corner. Must be 1-4.");
      Alert("ERROR: Incorrect Screen_corner. Must be 1-4.");
      return(0);
     }
//---
   SetIndexStyle(0,DRAW_ARROW,EMPTY,1);
   SetIndexArrow(0,234);
   SetIndexBuffer(0,Buffer);
   ArrayInitialize(Buffer,0.0);
//---
   for(int y=0;y<4;y++)
     {
      ObjectCreate("lb_ind_nm"+string(y),OBJ_LABEL,0,0,0,0,0);
      //--- 改变角落的位置 
      ObjectSet("lb_ind_nm"+string(y),OBJPROP_SELECTABLE,false);
      ObjectSet("lb_ind_nm"+string(y),OBJPROP_CORNER,Screen_corner);
      ObjectSet("lb_ind_nm"+string(y),OBJPROP_XDISTANCE,offsetX-30);
      ObjectSet("lb_ind_nm"+string(y),OBJPROP_YDISTANCE,y*scaleY+20);
      ObjectSetText("lb_ind_nm"+string(y),signalName[y],fontSize,"Tahoma",label_clr);
     }
//---
   for(int y=0;y<4;y++)
     {
      ObjectCreate("lb_ind0"+string(y),OBJ_LABEL,0,0,0,0,0);
      //---改变角落位置
      ObjectSet("lb_ind0"+string(y),OBJPROP_SELECTABLE,false);
      ObjectSet("lb_ind0"+string(y),OBJPROP_CORNER,Screen_corner);
      ObjectSet("lb_ind0"+string(y),OBJPROP_XDISTANCE,scaleX+offsetX);
      ObjectSet("lb_ind0"+string(y),OBJPROP_YDISTANCE,y*scaleY+20);
      ObjectSetText("lb_ind0"+string(y),"",fontSize,"Tahoma",textColor);
     }
   return(INIT_SUCCEEDED);
  }


现在,让我们看看ADX和RVI指标的信号处理主要模块

条件被设置为指标值满足出现买和卖的信号。当满足时,这些值通过mamdani(double t, double v)函数进行处理并显示在面板上。所有这些有如下形式:当前指标值显示我们接收到一个买入或者卖出信号;建议的止赢(以点数及数值计)。

//+------------------------------------------------------------------+
//| 自定义指标迭代函数                                                  
//+------------------------------------------------------------------+
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[])
  {
   Buffer[num_bar]=High[num_bar]+20*_Point;
   adx=NormalizeDouble(iADX(_Symbol,PERIOD_CURRENT,adx_period,PRICE_CLOSE,MODE_MAIN,num_bar),_Digits);
   adx_di_plus=NormalizeDouble(iADX(_Symbol,PERIOD_CURRENT,adx_period,PRICE_CLOSE,MODE_PLUSDI,num_bar),_Digits);
   adx_di_minus=NormalizeDouble(iADX(_Symbol,PERIOD_CURRENT,adx_period,PRICE_CLOSE,MODE_MINUSDI,num_bar),_Digits);
//---
   rvi=NormalizeDouble(iRVI(_Symbol,PERIOD_CURRENT,rvi_period,MODE_MAIN,num_bar),_Digits);
   rvi_sig=NormalizeDouble(iRVI(_Symbol,PERIOD_CURRENT,rvi_period,MODE_SIGNAL,num_bar),_Digits);
//---   
   if(adx>30 && adx_di_plus>adx_di_minus && rvi>rvi_sig && rvi<-0.1)
     {
      mdm=MathCeil(mamdani(adx,MathAbs(rvi)));
      ObjectSetText("lb_ind00","buy_signal: "+DoubleToString(adx,_Digits),fontSize,"Tahoma",textColor);
      ObjectSetText("lb_ind01","buy_signal: "+DoubleToString(rvi,_Digits),fontSize,"Tahoma",textColor);
      ObjectSetText("lb_ind02",DoubleToString(mdm,0),fontSize,"Tahoma",textColor);
      ObjectSetText("lb_ind03",DoubleToString(tp_prc(mdm),_Digits),fontSize,"Tahoma",textColor);
     }
   else if(adx>30 && adx_di_plus<adx_di_minus && rvi<rvi_sig && rvi>0.1)
     {
      mdm=MathCeil(mamdani(adx,rvi));
      ObjectSetText("lb_ind00","sell_signal: "+DoubleToString(adx,_Digits),fontSize,"Tahoma",textColor);
      ObjectSetText("lb_ind01","sell_signal: "+DoubleToString(rvi,_Digits),fontSize,"Tahoma",textColor);
      ObjectSetText("lb_ind02",DoubleToString(mdm,0),fontSize,"Tahoma",textColor);
      ObjectSetText("lb_ind03",DoubleToString(tp_prc(mdm),_Digits),fontSize,"Tahoma",textColor);
     }
   else
     {
      ObjectSetText("lb_ind00","no_signal",fontSize,"Tahoma",textColor);
      ObjectSetText("lb_ind01","no_signal",fontSize,"Tahoma",textColor);
      ObjectSetText("lb_ind02"," - ",fontSize,"Tahoma",textColor);
      ObjectSetText("lb_ind03"," - ",fontSize,"Tahoma",textColor);
     }
   return(rates_total);
  }


这个函数创建模糊逻辑系统。它包含两个来自指标的输入信号 — 趋势强度(每一个都由成员函数描述的四种模式组成),以及一个输出信号。连结输入和输出信号的四条规则也包含在此系统中。

//+------------------------------------------------------------------+
//| 基于模糊逻辑创建和计算系统的函数
//+------------------------------------------------------------------+
double mamdani(double t,double v)
  {
   double res=0;
//--- Mamdani Fuzzy 系统  
   MamdaniFuzzySystem *fsSignal=new MamdaniFuzzySystem();
//--- 为系统创建输入参数
   FuzzyVariable *fsTrend=new FuzzyVariable("trend",30.0,100.0);
   FuzzyVariable *fsVigor=new FuzzyVariable("vigor",0.1,1.0);
//--- ADX
   fsTrend.Terms().Add(new FuzzyTerm("low", new TrapezoidMembershipFunction(in_term1a, in_term1b, in_term1c, in_term1d)));
   fsTrend.Terms().Add(new FuzzyTerm("moderate", new TriangularMembershipFunction(in_term2a, in_term2b, in_term2c)));
   fsTrend.Terms().Add(new FuzzyTerm("medium", new TriangularMembershipFunction(in_term3a, in_term3b, in_term3c)));
   fsTrend.Terms().Add(new FuzzyTerm("high",new TrapezoidMembershipFunction(in_term4a, in_term4b, in_term4c, in_term4d)));
   fsSignal.Input().Add(fsTrend);
//--- RVI
   fsVigor.Terms().Add(new FuzzyTerm("low", new TrapezoidMembershipFunction(in_term1a1, in_term1b1, in_term1c1, in_term1d1)));
   fsVigor.Terms().Add(new FuzzyTerm("medium", new TriangularMembershipFunction(in_term2a1, in_term2b1, in_term2c1)));
   fsVigor.Terms().Add(new FuzzyTerm("high", new TriangularMembershipFunction(in_term3a1, in_term3b1, in_term3c1)));
   fsVigor.Terms().Add(new FuzzyTerm("higher",new TrapezoidMembershipFunction(in_term4a1, in_term4b1, in_term4c1, in_term4d1)));
   fsSignal.Input().Add(fsVigor);
//--- 创建输出
   FuzzyVariable *fvSignal=new FuzzyVariable("signal",min_tp,max_tp);
   fvSignal.Terms().Add(new FuzzyTerm("low_take", new TrapezoidMembershipFunction(out_term1a, out_term1b, out_term1c, out_term1d)));
   fvSignal.Terms().Add(new FuzzyTerm("mod_take", new TriangularMembershipFunction(out_term2a, out_term2b, out_term2c)));
   fvSignal.Terms().Add(new FuzzyTerm("med_take", new TriangularMembershipFunction(out_term3a, out_term3b, out_term3c)));
   fvSignal.Terms().Add(new FuzzyTerm("high_take", new TrapezoidMembershipFunction(out_term4a, out_term4b, out_term4c, out_term4d)));
   fsSignal.Output().Add(fvSignal);
//--- 创建四个Mamdani模糊逻辑规则
   MamdaniFuzzyRule *rule1 = fsSignal.ParseRule("if (trend is low) and (vigor is low) then signal is low_take");
   MamdaniFuzzyRule *rule2 = fsSignal.ParseRule("if (trend is moderate) and (vigor is medium) then signal is mod_take");
   MamdaniFuzzyRule *rule3 = fsSignal.ParseRule("if (trend is medium) and (vigor is high) then signal is med_take");
   MamdaniFuzzyRule *rule4 = fsSignal.ParseRule("if (trend is high) and (vigor is higher) then signal is high_take");
//--- 向系统中添加四个Mamdani模糊逻辑规则
   fsSignal.Rules().Add(rule1);
   fsSignal.Rules().Add(rule2);
   fsSignal.Rules().Add(rule3);
   fsSignal.Rules().Add(rule4);
//--- 设置输入值
   CList *in=new CList;
   Dictionary_Obj_Double *p_od_adx=new Dictionary_Obj_Double;
   Dictionary_Obj_Double *p_od_rvi=new Dictionary_Obj_Double;
   p_od_adx.SetAll(fsTrend,t);
   p_od_rvi.SetAll(fsVigor,v);
   in.Add(p_od_adx);
   in.Add(p_od_rvi);
//--- 获取结果
   CList *result;
   Dictionary_Obj_Double *p_od_out;
   result=fsSignal.Calculate(in);
   p_od_out=result.GetNodeAtIndex(0);
   res=NormalizeDouble(p_od_out.Value(),_Digits);
//---
   delete in;
   delete result;
   delete fsSignal;
   return res;
  }


让我们看看最后一个模块— “附加函数”。第一个 — tp_prc(double take)将以点数计的止赢值转换为当前货币价格的数值值。第二个函数定义当前货币对的报价小数位数。

//+------------------------------------------------------------------+
//| 确定获利目标的函数
//+------------------------------------------------------------------+
double tp_prc(double take)
  {
   int tip;
   double opr,tp;
   take+=50;
   adx_di_plus=NormalizeDouble(iADX(_Symbol,PERIOD_CURRENT,adx_period,PRICE_CLOSE,MODE_PLUSDI,num_bar),_Digits);
   adx_di_minus=NormalizeDouble(iADX(_Symbol,PERIOD_CURRENT,adx_period,PRICE_CLOSE,MODE_MINUSDI,num_bar),_Digits);
//---
   if(adx_di_plus>adx_di_minus)
      tip=0;
   else if(adx_di_plus<adx_di_minus)
      tip=1;
//---
   switch(tip)
     {
      case 0:
         opr=Ask;
         break;
      case 1:
         opr=Bid;
         break;
     }
   if(MathMod(tip,2.0)==0.0)
     {
      tp=opr+take*Dig()*_Point;
     }
   else
     {
      tp=opr-take*Dig()*_Point;
     }
   return(tp);
  }
//+------------------------------------------------------------------+
//|  返回小数位数的函数
//+------------------------------------------------------------------+
int Dig()
  {
   return((_Digits==5 || _Digits==3 || _Digits==1)?10:1);
  }
//+------------------------------------------------------------------+

我还想提醒你们在测试时请注意对参数设置的修正,以及在模糊逻辑参数模块中对参数正确性的预先效验。我建议使用图3、5、6中的初始图形展现形式,因为不正确的参数会导致报错以及正系统的错误运行。请当心!

 

总结

我们将总结我们所学到的知识。

我们讨论了通过应用模糊逻辑来改进手动交易策略的可能性。使用样例来揭示如果改进现有交易策略的更多细节,使用模糊逻辑来实现并弥补已知缺陷。