English Русский Español Deutsch 日本語 Português
preview
构建K线图趋势约束模型(第一部分):针对EA和技术指标

构建K线图趋势约束模型(第一部分):针对EA和技术指标

MetaTrader 5交易 | 5 十一月 2024, 14:14
287 0
Clemence Benjamin
Clemence Benjamin

内容

  1. 简介
  2. 较长时间框架K线图的解析
  3. 策略开发(均线交叉)及代码
  4. 约束条件的合理性及其应用,并附上代码
  5. 使用此代码的好处
  6. 结论


    简介

    作为使用均线定义市场趋势的替代方法,较长时间框架下K线图的上涨或下跌特征可以为市场方向提供有价值的见解。例如,在D1(日线)或H4(4小时线)K线图中,M1(1分钟线)时间框架甚至更小的价格波动中发生着大量的潜在活动,这些活动共同塑造了K线图的形态。通过利用D1(日线)下看涨K线图带来的买入机会,并在看跌阶段卖出,交易者可以获得优势。将此与较短时间框架中的原生技术指标相结合,有助于精确确定入场点,为交易者提供战略优势。在处理看涨日K线图时,交易者应耐心等待有利的市场条件形成,再自信地顺应趋势。

    本文旨在使用MQL5代码有效地将当前K线图分类为看涨或看跌,并设定条件:仅在K线图看跌时卖出,看涨时买入。

    本模型旨在将信号发生器限制为仅产生与当前K线图趋势一致的信号。想象一下,一道篱笆会根据动物的体型大小来限制某些动物进入你的院子,而允许其他动物进入。我们正在应用类似的概念来过滤掉部分信号,仅保留最优的信号。该模型通过分析高一级时间框架的K线图和市场趋势,有效地创建了一个虚拟屏障,只允许符合主流趋势的信号通过。这种选择性过滤过程提高了生成信号的准确性和可靠性,确保仅向用户呈现最有利的交易机会。

    阅读完本文后,你应当能够:

    1. 了解在可用的微观时间框架中整个D1(日线)K线图的价格行为。
    2. 创建包含较长时间框架趋势约束条件的均线交叉指标缓冲区(Buffer)。
    3. 理解从给定策略中筛选出最佳信号的概念。

    较长时间框架K线图的解析

    Boom 500 index, D1 candlestick anatomy M5,13.04.24

    图1.1:在M5时间框架下,对Boom 500综合指数的D1(日线)分析

    上述图片展示了最左侧的一个D1(日线)K线图红色柱体,以及右侧在每日时段分隔符之间显示的M5(5分钟线)价格走势。受日线K线图看跌性质的影响,出现了一个明显的下跌趋势,这表明卖出概率较高。该设置强调根据日线K线图执行交易,这反映了以更高时间框架的趋势来进行约束的理念。

    策略开发(均线交叉)

     开发交易策略需要结合分析、测试和持续的优化。成功的交易策略应基于对市场的深刻理解,以及一套明确的规则和指南。随着市场条件的变化,不断监控和调整策略以适应新趋势和机会至关重要。通过持续分析数据、回溯测试不同方法并根据需要进行调整,交易者可以提高在市场中的成功率。

    在我们继续探讨下面的均线交叉策略之前,以下总结策略开发的关键点:

    1. 明确目标和风险承受能力
    2. 了解市场
    3. 选择交易风格  
    4. 制定入场和出场规则  
    5. 实施风险管理策略
    6. 回溯测试策略
    7. 优化和完善
    8. 实盘交易前进行模拟交易
    9.  监控和评估
      接下来,我们将开发一个基本的均线交叉指标,该指标在交叉发生时显示箭头并触发通知。以下是我们策略开发的标准步骤:
      1. 设置策略条件(在本例中为EMA7上穿或下穿EMA21)
      2. 设置指标的显示样式,可以是箭头或MetaTrader 5中可用的任何几何形状
      3. (可选)如果指标将由用户自定义,则设置输入参数

      为了专注于本文的主题——约束算法,我决定在这里只提供最终代码而不做过多解释。以下程序已准备好进行编译并生成买入和卖出信号。在代码之后,我们将在mt5图表上分析结果,并确定约束算法将解决的问题。

      //Indicator Name: Trend Constraint
      #property copyright "Clemence Benjamin"
      #property link      "https://mql5.com"
      #property version   "1.00"
      #property description "A model that seek to produce sell signal when D1 candle is Bearish only and  buy signal when it is Bullish"
      
      //--- indicator settings
      #property indicator_chart_window
      #property indicator_buffers 2
      #property indicator_plots 2
      
      #property indicator_type1 DRAW_ARROW
      #property indicator_width1 5
      #property indicator_color1 0xFFAA00
      #property indicator_label1 "Buy"
      
      #property indicator_type2 DRAW_ARROW
      #property indicator_width2 5
      #property indicator_color2 0x0000FF
      #property indicator_label2 "Sell"
      
      #define PLOT_MAXIMUM_BARS_BACK 5000
      #define OMIT_OLDEST_BARS 50
      
      //--- indicator buffers
      double Buffer1[];
      double Buffer2[];
      
      double myPoint; //initialized in OnInit
      int MA_handle;
      double MA[];
      int MA_handle2;
      double MA2[];
      double Low[];
      double High[];
      
      void myAlert(string type, string message)
        {
         if(type == "print")
            Print(message);
         else if(type == "error")
           {
            Print(type+" | Trend constraint @ "+Symbol()+","+IntegerToString(Period())+" | "+message);
           }
         else if(type == "order")
           {
           }
         else if(type == "modify")
           {
           }
        }
      
      // Custom indicator initialization function                         
      int OnInit()
        {   
         SetIndexBuffer(0, Buffer1);
         PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
         PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(), PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+1, OMIT_OLDEST_BARS+1));
         PlotIndexSetInteger(0, PLOT_ARROW, 241);
         SetIndexBuffer(1, Buffer2);
         PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
         PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(), PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+1, OMIT_OLDEST_BARS+1));
         PlotIndexSetInteger(1, PLOT_ARROW, 242);
         //initialize myPoint
         myPoint = Point();
         if(Digits() == 5 || Digits() == 3)
           {
            myPoint *= 10;
           }
         MA_handle = iMA(NULL, PERIOD_CURRENT, 7, 0, MODE_EMA, PRICE_CLOSE);
         if(MA_handle < 0)
           {
            Print("The creation of iMA has failed: MA_handle=", INVALID_HANDLE);
            Print("Runtime error = ", GetLastError());
            return(INIT_FAILED);
           }
         
         MA_handle2 = iMA(NULL, PERIOD_CURRENT, 21, 0, MODE_EMA, PRICE_CLOSE);
         if(MA_handle2 < 0)
           {
            Print("The creation of iMA has failed: MA_handle2=", INVALID_HANDLE);
            Print("Runtime error = ", GetLastError());
            return(INIT_FAILED);
           }
         
         return(INIT_SUCCEEDED);
        }
      
      //Custom indicator iteration function                              
      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[])
        {
         int limit = rates_total - prev_calculated;
         //--- counting from 0 to rates_total
         ArraySetAsSeries(Buffer1, true);
         ArraySetAsSeries(Buffer2, true);
         //--- initial zero
         if(prev_calculated < 1)
           {
            ArrayInitialize(Buffer1, EMPTY_VALUE);
            ArrayInitialize(Buffer2, EMPTY_VALUE);
           }
         else
            limit++;
         
         if(BarsCalculated(MA_handle) <= 0) 
            return(0);
         if(CopyBuffer(MA_handle, 0, 0, rates_total, MA) <= 0) return(rates_total);
         ArraySetAsSeries(MA, true);
         if(BarsCalculated(MA_handle2) <= 0) 
            return(0);
         if(CopyBuffer(MA_handle2, 0, 0, rates_total, MA2) <= 0) return(rates_total);
         ArraySetAsSeries(MA2, true);
         if(CopyLow(Symbol(), PERIOD_CURRENT, 0, rates_total, Low) <= 0) return(rates_total);
         ArraySetAsSeries(Low, true);
         if(CopyHigh(Symbol(), PERIOD_CURRENT, 0, rates_total, High) <= 0) return(rates_total);
         ArraySetAsSeries(High, true);
         //--- main loop
         for(int i = limit-1; i >= 0; i--)
           {
            if (i >= MathMin(PLOT_MAXIMUM_BARS_BACK-1, rates_total-1-OMIT_OLDEST_BARS)) continue; //omit some old rates to prevent "Array out of range" or slow calculation   
            
            //Indicator Buffer 1
            if(MA[i] > MA2[i]
            && MA[i+1] < MA2[i+1] //Moving Average crosses above Moving Average
            )
              {
               Buffer1[i] = Low[i]; //Set indicator value at Candlestick Low
              }
            else
              {
               Buffer1[i] = EMPTY_VALUE;
              }
            //Indicator Buffer 2
            if(MA[i] < MA2[i]
            && MA[i+1] > MA2[i+1] //Moving Average crosses below Moving Average
            )
              {
               Buffer2[i] = High[i]; //Set indicator value at Candlestick High
              }
            else
              {
               Buffer2[i] = EMPTY_VALUE;
              }
           }
         return(rates_total);
        }
      //copy the code to meta editor to compile it

        2024年04月12日,EURUSD测试结果图表显示,在M1(1分钟)时间框架下观察到一个周期的开始。交叉点显而易见,箭头标明了它们的位置——红色箭头代表卖出信号,蓝色箭头代表买入信号。然而,从更广的视角来看,存在一个明显的下降趋势,显示出D1(日图)K线图呈现空头态势。叉指标正在发出两种信号,忽略了当前的主流趋势,这构成了一个重大问题。相互矛盾的信号给试图驾驭市场的交易者带来了巨大挑战。虽然M1时间框架表明可能存在短期机会,但日K线图上总体呈现的下降趋势让人对任何上涨行情的持续性感到担忧。在本文中,我们将通过将信号限制在D1趋势内来解决这一问题。

        移动平均线交叉信号

        FIG 1.2 : 添加限制之前的均线交叉指标测试结果

        日K线图的结果为空头,由均线交叉条件产生了买入和卖出箭头。许多信号被视为虚假或逆势,这可以通过结合更高时间框架的趋势约束来纠正。以下表格是根据当天开盘至收盘的图表信息创建的。 

        信号类型 数量
        卖出信号 29
        买入信号 28
        总计 57
        虚假和逆势信号 41
        正确的信号 25


        约束的合理性及其应用

        想象一下,如果将玉米粒和高粱混合在一起,其中一种是比另一种更粗糙的谷物。为了将它们分开,我们会使用一个筛子。这个动作将玉米粒限制住,只允许高粱通过,这体现了一种控制。这个比喻与正在研究的高时间框架约束概念相吻合。它就像一个过滤器,筛选出某些信号,只保留与当前趋势一致的信号。高时间框架约束,就像筛子一样,精炼了我们的关注点,使我们能够分辨出市场趋势的关键因素。通过过滤掉噪音,我们可以更好地把握市场运行的潜在动态,从而做出更明智的决策。这种战略方法提高了我们在复杂金融环境中畅行的能力,确保我们与总体方向保持一致,就像从玉米粒中分离出的高粱一样,揭示了市场运动的真正本质。

        定义日K线图的性质作为我们之前代码的趋势约束条件
        • 我通过比较前一天的收盘价(这类似于当天的开盘价)和较短时间框架一分钟(M1)K线图的收盘价,来定义当前的市场情绪是多头还是空头。
        • 对于多头形态的K线图:

          前一根一分钟(M1)K线图的收盘价 >= 前一根日(D1)K线图的收盘价

          对于空头形态的K线图:

          前一根一分钟(M1)K线的收盘价 <= 前一根日(D1)K线的收盘价

          数学逻辑表明,如果日(D1)K线图是多头趋势,那么我们只会收到买入信号;相反,如果日(D1)K线图是空头趋势,那么我们只会收到卖出信号。

          我们选择较低时间框架的收盘价作为与D1开盘价进行比较的基准点,而不是使用其他因素,如买入价和卖出价或当日的收盘价,因为根据这种策略和指标风格,后者在图表上不会显示所需的箭头。因此,通过关注较低时间框架的收盘价与D1开盘价的关系,我们可以有效地使我们的策略与图表上所需的视觉提示和指标保持一致。这种方法提高了我们交易决策的清晰度和精确度,确保了分析和决策过程的更加简洁和高效。  

        • 让我们来看看代码。代码结构清晰,易于理解。
         if(Close[1+barshift_M1[i]] >= Open[1+barshift_D1[i]] //Candlestick Close >= Candlestick Open
              )
                {
                 Buffer1[i] = Low[i]; //Set indicator value at Candlestick Low
                }
              else
                {
                 Buffer1[i] = EMPTY_VALUE;
                }

        上面的代码表示的是日(D1)K线图的多头条件。请记住,对于空头条件,逻辑是相反的。

        • 最后,我展示最终的代码,其中约束条件与均线交叉指标无缝集成。
        /Indicator Name: Trend Constraint
        #property copyright "Clemence Benjamin"
        #property link      "https://mql5.com"
        #property version   "1.00"
        #property description "A model that seek to produce sell signal when D1 candle is Bearish only and  buy signal when it is Bullish"
        
        //--- indicator settings
        #property indicator_chart_window
        #property indicator_buffers 2
        #property indicator_plots 2
        
        #property indicator_type1 DRAW_ARROW
        #property indicator_width1 5/
        #property indicator_color1 0xFFAA00
        #property indicator_label1 "Buy"
        
        #property indicator_type2 DRAW_ARROW
        #property indicator_width2 5
        #property indicator_color2 0x0000FF
        #property indicator_label2 "Sell"
        
        #define PLOT_MAXIMUM_BARS_BACK 5000
        #define OMIT_OLDEST_BARS 50
        
        //--- indicator buffers
        double Buffer1[];
        double Buffer2[];
        
        double myPoint; //initialized in OnInit
        int MA_handle;
        double MA[];
        int MA_handle2;
        double MA2[];
        double Close[];
        double Close2[];
        double Low[];
        double High[];
        
        void myAlert(string type, string message)
          {
           if(type == "print")
              Print(message);
           else if(type == "error")
             {
              Print(type+" | Trend constraint @ "+Symbol()+","+IntegerToString(Period())+" | "+message);
             }
           else if(type == "order")
             {
             }
           else if(type == "modify")
             {
             }
          }
        
        // Custom indicator initialization function                         
        int OnInit()
          {   
           SetIndexBuffer(0, Buffer1);
           PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
           PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(), PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+1, OMIT_OLDEST_BARS+1));
           PlotIndexSetInteger(0, PLOT_ARROW, 241);
           SetIndexBuffer(1, Buffer2);
           PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
           PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(), PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+1, OMIT_OLDEST_BARS+1));
           PlotIndexSetInteger(1, PLOT_ARROW, 242);
           //initialize myPoint
           myPoint = Point();
           if(Digits() == 5 || Digits() == 3)
             {
              myPoint *= 10;
             }
           MA_handle = iMA(NULL, PERIOD_CURRENT, 7, 0, MODE_EMA, PRICE_CLOSE);
           if(MA_handle < 0)
             {
              Print("The creation of iMA has failed: MA_handle=", INVALID_HANDLE);
              Print("Runtime error = ", GetLastError());
              return(INIT_FAILED);
             }
           
           MA_handle2 = iMA(NULL, PERIOD_CURRENT, 21, 0, MODE_EMA, PRICE_CLOSE);
           if(MA_handle2 < 0)
             {
              Print("The creation of iMA has failed: MA_handle2=", INVALID_HANDLE);
              Print("Runtime error = ", GetLastError());
              return(INIT_FAILED);
             }
           
           return(INIT_SUCCEEDED);
          }
        
        // Custom indicator iteration function                              
        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[])
          {
           int limit = rates_total - prev_calculated;
           //--- counting from 0 to rates_total
           ArraySetAsSeries(Buffer1, true);
           ArraySetAsSeries(Buffer2, true);
           //--- initial zero
           if(prev_calculated < 1)
             {
              ArrayInitialize(Buffer1, EMPTY_VALUE);
              ArrayInitialize(Buffer2, EMPTY_VALUE);
             }
           else
              limit++;
           
           datetime TimeShift[];
           if(CopyTime(Symbol(), PERIOD_CURRENT, 0, rates_total, TimeShift) <= 0) return(rates_total);
           ArraySetAsSeries(TimeShift, true);
           int barshift_M1[];
           ArrayResize(barshift_M1, rates_total);
           int barshift_D1[];
           ArrayResize(barshift_D1, rates_total);
           for(int i = 0; i < rates_total; i++)
             {
              barshift_M1[i] = iBarShift(Symbol(), PERIOD_M1, TimeShift[i]);
              barshift_D1[i] = iBarShift(Symbol(), PERIOD_D1, TimeShift[i]);
           }
           if(BarsCalculated(MA_handle) <= 0) 
              return(0);
           if(CopyBuffer(MA_handle, 0, 0, rates_total, MA) <= 0) return(rates_total);
           ArraySetAsSeries(MA, true);
           if(BarsCalculated(MA_handle2) <= 0) 
              return(0);
           if(CopyBuffer(MA_handle2, 0, 0, rates_total, MA2) <= 0) return(rates_total);
           ArraySetAsSeries(MA2, true);
           if(CopyClose(Symbol(), PERIOD_M1, 0, rates_total, Close) <= 0) return(rates_total);
           ArraySetAsSeries(Close, true);
           if(CopyClose(Symbol(), PERIOD_D1, 0, rates_total, Close2) <= 0) return(rates_total);
           ArraySetAsSeries(Close2, true);
           if(CopyLow(Symbol(), PERIOD_CURRENT, 0, rates_total, Low) <= 0) return(rates_total);
           ArraySetAsSeries(Low, true);
           if(CopyHigh(Symbol(), PERIOD_CURRENT, 0, rates_total, High) <= 0) return(rates_total);
           ArraySetAsSeries(High, true);
           //--- main loop
           for(int i = limit-1; i >= 0; i--)
             {
              if (i >= MathMin(PLOT_MAXIMUM_BARS_BACK-1, rates_total-1-OMIT_OLDEST_BARS)) continue; //omit some old rates to prevent "Array out of range" or slow calculation   
              
              if(barshift_M1[i] < 0 || barshift_M1[i] >= rates_total) continue;
              if(barshift_D1[i] < 0 || barshift_D1[i] >= rates_total) continue;
              
              //Indicator Buffer 1
              if(MA[i] > MA2[i]
              && MA[i+1] < MA2[i+1] //Moving Average crosses above Moving Average
              && Close[1+barshift_M1[i]] >= Close2[1+barshift_D1[i]] //Candlestick Close >= Candlestick Close
              )
                {
                 Buffer1[i] = Low[i]; //Set indicator value at Candlestick Low
                }
              else
                {
                 Buffer1[i] = EMPTY_VALUE;
                }
              //Indicator Buffer 2
              if(MA[i] < MA2[i]
              && MA[i+1] > MA2[i+1] //Moving Average crosses below Moving Average
              && Close[1+barshift_M1[i]] <= Close2[1+barshift_D1[i]] //Candlestick Close <= Candlestick Close
              )
                {
                 Buffer2[i] = High[i]; //Set indicator value at Candlestick High
                }
              else
                {
                 Buffer2[i] = EMPTY_VALUE;
                }
             }
           return(rates_total);
          }

        上述代码的结果非常完美。请看下图。

        应用于均线交叉指标的趋势约束

        FIG 1.4: 采用趋势约束后结果令人惊讶。

        信号类型 数量
        卖出信号 27
        买入信号 1
        总计 28
        虚假和逆势信号 3
        正确的信号 25

        从上面的表格中,我们注意到采用较长时间框架的趋势作为过滤器,对结果产生了积极的影响,成功次数多于失败次数,使其成为EA的理想选择。比较现在和先前的结果表格,我们发现成功的信号被保留了,而所有虚假信号都被减少了。信号准确性的提高表明我们的数据分析方法呈现出良好的态势。通过优化算法和标准,我们有效地减少了虚假信号的出现,提高了结果的总体可靠性。展望未来,这一进展无疑将有助于我们做出更明智的决策,并改善策略的结果。


        使用此代码的好处

        采用较长时间框架趋势来施加限制的优势在于提高了市场方向的清晰度,减少了过度交易的倾向,并培养了更自律的交易决策方法。这种方法还可以提供更广阔的视角,帮助交易者避免陷入短期波动,使他们能够将策略与长期趋势保持一致。通过关注大局,交易者能够更有效地过滤掉噪音,并根据潜在趋势做出更明智的决策。这种方法鼓励耐心和对市场动态的更深入理解,最终带来更加一致和盈利的交易结果。此外,采用较长时间框架的趋势做限制还可以作为风险管理的重要工具,允许交易者根据对市场条件的战略评估,为入场和出场点设定明确的水平。

        总之,

        • 信号生成指标的准确性提高
        • 更好的风险管理
        • 盈利能力增强
        • 工作量减少
        • 信号数量减少


        结论

        无论多头还是空头市场,长时间框架的K线图对短时间框架的趋势都有重大影响,基本上引导着市场。根据各种研究,我建议在较短时间框架的日线多头趋势下买入,空头趋势下卖出。就我个人而言,在EA和指标开发中整合这种用长时间框架的趋势做限制的概念,对于持续获得积极结果至关重要。现在出现了一个问题:我们应该放弃移动平均线作为定义趋势的工具吗?或许在接下来深入探讨和完善这一概念的文章中,我们会找到答案。附件是源代码文件,您可以在Meta编辑器中查看,以及MetaTrader 5平台中的Ex5文件。


        本文由MetaQuotes Ltd译自英文
        原文地址: https://www.mql5.com/en/articles/14347

        开发回放系统(第 44 部分):Chart Trader 项目(三) 开发回放系统(第 44 部分):Chart Trader 项目(三)
        在上一篇文章中,我介绍了如何操作模板数据以便在 OBJ_CHART 中使用。在那篇文章中,我只是概述了这一主题,并没有深入探讨细节,因为在那个版本中,这项工作是以非常简单的方式完成的。这样做是为了更容易解释内容,因为尽管很多事情表面上很简单,但其中有些并不那么明显,如果不了解最简单、最基本的部分,就无法真正理解全局。
        练习开发交易策略 练习开发交易策略
        在本文中,我们将尝试开发自己的交易策略。任何交易策略都必须以某种统计优势为基础。而且,这种优势应该长期存在。
        如何构建和优化基于波动率的交易系统(Chaikin volatility-CHV) 如何构建和优化基于波动率的交易系统(Chaikin volatility-CHV)
        在本文中,我们将介绍另一个基于波动率的指标——蔡金波动率(Chaikin Volatility)。在了解到蔡金波动率的使用方法和构建方式之后,我们将学习如何构建自定义指标。我们将分享一些可用的简单策略,并对其进行测试,以了解哪个策略更优。
        种群优化算法:人工多社区搜索对象(MSO) 种群优化算法:人工多社区搜索对象(MSO)
        这是上一篇研究社群概念文章的延续。本文使用迁徙和记忆算法探讨社群的演化。结果将有助于理解社区系统的演化,并将其应用于优化和寻找解。