English Русский Español Deutsch 日本語 Português
preview
带有图表交互控件的指标

带有图表交互控件的指标

MetaTrader 5指标 | 16 八月 2022, 07:43
1 103 0
Aleksandr Kononov
Aleksandr Kononov

概述

我自 2008 年起接触交易,一路而来,积累了相当多的知识。 现在我用这些知识来开发我的产品。 多年以来,我尝试了几十种不同的交易策略,并测试过数百种不同的指标,我得出了一些结论,我想在本文中与大家分享。 现在,和以前一样,MetaTrader 交易平台的品质是毋庸置疑的。 但世代变迁,用户对提供给他们的产品要求越来越挑剔。 因此,我认为创建一个可以轻松应用于任何指标的原则会更好。 该原则旨在用户和程序之间进行更多的交互。 


关于指标

MetaTrader 平台提供各种类型的交易程序。 这些是智能交易系统(EA)、脚本和指标。 我要谈及的是关于指标。

有不同类型的指标:支撑/阻力线、烛条分形或形态、交易量或证券交易所交易操作的其它数据、振荡器或直方图。 我们需要所有这些数据来揭示某种价格走势形态,而这种形态反过来又形成了一个交易信号。 形态出现的频率越高,信号越强。 

形态是在某些指标读数处重复发生的,或非常相似的价格走势。 例如,如果快速移动平均线向上穿过慢速移动平均线,而价格向下穿过,那么价格极有可能从这条线反弹并继续上涨。 这种形态其实用任何指标都能发现。 

每种指标,无论其用途如何,都能示意一段时间内的形态。 这就意味着信号是基于历史数据某个区域的过去价格走势形成的。 它可以是 10根 H1 的柱线,或 1000 根 D1 的柱线。 我们正在搜寻一种形态的时间段称为指标“周期”。

由于所有指标都是根据历史数据计算的,因此任何指标都有一个计算周期。 如果我们将周期值设置为 100,并将指标放在 H1 图表上,那么我们将得到指标基于最后 100 根 H1 蜡烛的计算结果。 指标周期保持不变,直至我们自行更改。 如果价格变动的性质发生变化,例如,走势变得更加波动,那么我们将不得不增加周期,从而令指标适应当前的行情状况。 

为此,我们需要转到指标设置,并选择一个新的周期值。 参数必须逐项更改。 步长应直观地选择 — 步长不宜太大,以免错过所需信号,也不应太小,以免耗费太多时间。 如果您在一天中经常这样做,那么您可以花大量的时间在上面。 事实上,操控指标是在预期结果、准确度、和交易者愿意花费的时间之间的一种折衷。

另一种选项是一次性打开足够的所需图表,每一个图表采用不同的指标设置。 在这种情况下,整个工作空间充斥着包含相同类型指标的窗口,这些指标仅是周期设置有所不同。 这种解决方案在工作空间安排方面并不完美。 不幸的是,这两种选项都极不方便。


问题的核心

我想用我在交易中长期使用的交易策略来强调这个问题。 我认为,许多交易者每天都面临着同样的问题。 有些人根本没有意识到这一点,认为这是理所当然的;而对某些人来说,这会造成不适,就如同我一样。 该策略基于移动平均线的指示。 它在趋势走势中运行良好。 其本质在于,如果趋势已经开始,那么它很可能会延续,交易信号条件是在价格在调整期间回踩慢速均线。 

为了计算慢速均线周期,我们需要在上一次调整期间查找快速均线和慢速均线的特定位置。 

pic_01

为了找到必要的周期,需要执行大量重复操作。 这种策略的特点是均线周期应始终相差两倍。 例如,如果快速均线周期为 20,慢速均线周期则应等于 40。 在这种情况下,您需要找到均线周期的最小值,即采用该周期值时,快速均线在调整期间不会与慢速均线交叉。 该图像分别显示的是 25 和 50 周期的均线。 正如我们所见,在上次调整期间,这些均线没有交叉。 所以我们可以进一步缩短它们的周期。 打开快速均线设置,并更改周期。 然后打开慢速均线设置,并更改周期。我们必须多次执行此操作,直到这些均线尽可能聚拢。

pic_03

两条均线还没有交叉。 但我们首先需要找到均线相交的极限位置,然后将它们的周期逐步增加。 因此,我们要再次将周期设置分别更改为 7 和 14。

  

pic_04

现在我们得到交汇。 这意味着已经找到了一个极端位置,现在需要将周期继续逐步增加。 再次打开每条均线的设置,并更改周期值。

pic_05

最终结果,找到了所需的均线参数组合,并成功生成了信号。 然而,搜索的准备工作花费了一些时间,而且我被迫从事单调、甚至乏味的举动。 不要忘记,这只是单一货币对的一个时间框。 为了在不同的时间帧内跟踪多种货币对,必须经常执行此类操作。 我的经验表明,当采用分钟级时间帧时,这个过程至少占用了一半的工作时间。 这种策略可能相当流行。 我认为,许多交易者在日常交易中都采用过类似的东西。

该策略的缺点是显而易见的。 事实上,这样更改指标设置的持续操作不光需要花费大量时间之外,它们还分散了人们对主要交易活动的注意力。 当您选择新的均线参数时,另一货币对或另一时间帧上已形成的信号可能根本不会被注意到。 此外,您还可能无法及时完成交易,错失获得最大利润,或者还会略有亏损。 除此之外,经过几个小时的此类工作,任何人都会开始感到疲劳。 单调的行为让交易者放松了警惕,增加了出错的风险,更不用说花费的时间了。 在电脑显示器前工作了几年后,您开始珍惜时间,明白这些看似微不足道的事情的重要性。


界面原理

界面背后的私立是直接从图表中用鼠标滚轮滚动指标周期,而无需进入设置。 我们将使用按钮来激活滚动模式。

从所附文件中下载 "Button CCI" 指标的源代码来试用,或下载含有其它功能的最新免费版本,并通过以下链接查看其设置的详细说明。

问题很清楚。 显然,我们需要以某种方式加速设定周期。 只有一种方法可以做到这一点 — 自动化流程。 可以创建一种算法,来自行选择周期值。 但是算法仍然需要自己的计算周期! 这意味着我们必须为算法手工设置需用到的蜡烛数量,来选择移动平均周期! 这是一种元周期整理。 这将解决在选择所需周期上耗费大量时间的问题,但由于其复杂性,该解决方案亦有其缺点。 这种方式曾在自由动态双重移动平均指标中实现了。 在未来,我计划对其进行改进,令其更易于管理,并对用户输入的设置变化做出响应。

以我的观点,另一种方法更简单,同时也更具交互性。 它涉及由交易者改变交易周期,而且是以一种更为便捷的形式。 毕竟,无论算法多么智能,交易者仍然更清楚他们自己需要什么样的指标设置,我们的任务就是帮助他们,并促成这一过程。 为了修改周期,我们将使用鼠标滚轮滚动。 滚动模式本身将通过打开图表右侧的按钮激活。


GIF_01

该图展示了通过单次移动改变指标周期是多么容易。 只需激活所需指标的按钮,并沿所需方向滚动鼠标滚轮即可。 您还可以设置必要的周期更改步骤。 您还可以通过简单地激活所需按钮,一次性更改多个指标的周期。 这些功能对于上述策略非常有用。 例如,您可以设定步骤,将快速均线更改为 1,慢速均线更改为 2,针对两条均线启用滚动,并在保持比率为 1/2 的同时轻松更改其周期。


优点

    拥有此界面的指标具有许多优点。
    • 首先,交易者可以像往常一样调整指标周期,而且花费的时间和精力要少得多。
    • 其次,这样的界面直观且易于使用。 在长时间使用此类指标后,您不愿再返回标准界面。
    • 此外,使用此类指标令您仅用一个窗口就够了,将若干个相同类型的指标精简为一个。 这样就能令工作屏幕免于杂乱。
    • 另一个优点是,由于更加关注流程本身,而不是指标设置,交易品质有所提高。
    • 在快速变化的市场中搜索信号的速度也会加快,这有助于提高交易者的生产率。

    缺点

    有了众多优点,这样的界面也有其缺点。

    • 首先,由于 MQL4 语言的技术限制,很难在 MetaTrader 4 中复制它。
    • 其次,当对大量数据(超过 20000 根柱线)参与计算时,指标速度明显减慢。

    但并不经常需要历史分析,您可以限制日常使用的计算数据量。这些缺点并不严重,不会影响 MetaTrader 5 中此类指标的日常使用。


    基于移动均线创建具有周期滚动功能的指标

    基于上述情况,我决定创建一个具有最方便界面的指标,将用户从常规操作中解救出来。 此外,目标是为大多数指标制定最简单的原则,以便在任意其它指标上能轻松复制类似的界面。

    我决定以最流行的指标 — 移动平均线 — 为例。 这个思路的复杂性在于,如果我们想动态地改变周期,那么我们必须同时用到几十甚至几百个指标。 鉴于系统是通过一个短名称和一组参数来区分一个窗口中加载的指标,因此在每次更改步骤中,“period” 参数在每组参数中总是不同的,并且为每个不同周期的同一指标创建一个新的指标句柄,这会显著降低操作速度。 在寻找解决方案时,我在论坛上遇到大量对这种方式的批评。 经验丰富的程序员表示,这种实现在语言级别是禁止的,而且通常是不可能的,因为内存溢出是不可避免的(我在 MetaTrader 4 中经常遇到这种情况)。 确实,对于 MetaTrader 4 来说,这是无解的。

    然而,在 MQL5 中,只需针对图表上的每个指标创建一次句柄,然后在需要时访问它。 换言之,即使我们在每次即时报价来临时都创建一个新句柄,它也不会真的重新创建,而是提取已经创建的句柄。 这就让内存管理变得更容易了。 但它可以预先在 OnInit 中创建所有所需的句柄,如此我们就可以完全不用顾及这个问题。 唯一的限制在于,我们需要预先定义周期的可变化范围,以便在初始化指标时创建所有必要的句柄。 最大限制周期由交易者预先在 period_ ma_max 参数中设置。 遵循该步骤,我们就能避免计算不必要的句柄,从而节省更多内存。 该步骤也是由交易者设定的。 默认值为 1。

    int OnInit()
    {
    //Create all necessary handles
    for(int i=1; i<=period_ma_max; i+=step)
        handle=iMA(_Symbol,_Period,i,0,method_MA,price_MA);
         
    }
    
    

    现在我们需要创建实现滚动周期的功能。 为此,我们先创建一个图表按钮,使用鼠标滚轮激活周期滚动模式。

    //+------------------------------------------------------------------+
    //|              CREATING BUTTON IN OnInit()                          
    //+------------------------------------------------------------------+
     
       ObjectDelete(0,name);
       ObjectCreate(0,name,OBJ_BUTTON,0,0,0);
       ObjectSetString(chart_ID,name,OBJPROP_TEXT,text);
       ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr_Text_Button);
       ObjectSetInteger(chart_ID,name,OBJPROP_BGCOLOR,clr_Button);
       ObjectSetInteger(chart_ID,name,OBJPROP_CORNER,CORNER_RIGHT_UPPER);
       ObjectSetInteger(chart_ID,name,OBJPROP_XDISTANCE,distance_X);
       ObjectSetInteger(chart_ID,name,OBJPROP_YDISTANCE,distance_Y);
       ObjectSetInteger(chart_ID,name,OBJPROP_XSIZE,size_X);
       ObjectSetInteger(chart_ID,name,OBJPROP_YSIZE,size_Y);
       ObjectSetInteger(chart_ID,name,OBJPROP_STATE,true);
       ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,true);
       ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,true);
       ObjectSetInteger(chart_ID,name,OBJPROP_STATE,press_Button);
       ObjectSetInteger(chart_ID,name,OBJPROP_ZORDER,1);
    
       ChartRedraw();
    
    

    当在图表上加载两个或多个指标时,它们的按钮会在同一位置创建,这很不方便。 因此,在后续添加每个指标时,我们需要沿某个坐标轴移动按钮。 为此,您首先需要准确计算我们的指标中有多少已经加载到图表。 首先,我们找到窗口中的指标总数:

    int      total_Indicators=ChartIndicatorsTotal(0,0),
             this_indicators=0;
    
    

    接下来,从中选择我们的指标:

    for(int i=0; i<total_Indicators; i++)
         {
          if(ChartIndicatorName(0,0,i)=="Button_MA")
             this_indicators++;
         }
    IndicatorSetString(INDICATOR_SHORTNAME,"Button_MA")
    
    

    依据短名称搜索指标的方式,我们应该始终记住,如果我们在循环后创建指标短名称,那么我们加载到图表中的当前指标的名称才会与其文件名相对应。 在这种情况下,如果文件名与 INDICATOR_SHORTNAME 不同,则不会计算循环中的当前指标。

    现在沿 X 轴移动按钮:

    distance_X=size_X*this_indicators;

    之后,加载到图表中的所有指标的按钮将显示为一行。 不幸的是,当向一张图表添加多个指标,或至少更改设置中的其它指标输入时,用户必须重命名按钮。 这是因为 MetaTrader 5 禁止在一个窗口中添加拥有相同输入的指标。 对此我们无能为力,故此我们必须将手工更改设置的操作转移给交易者。 因为更改设置并非始终必要,所以我们将按钮名称移动到指标设置。 这样,我们就能一箭双雕:我们即可更改指标输入,并为同一图表上的对象提供不同的名称。 此外,拥有不同名称的按钮能更丰富、更方便地提供信息。 

    input string  name_Line = "MA_1"; 
    string   name = name_Line,
             text = name_Line;
    
    

    现在,当用户重命名每个按钮时,指标和按钮本身很容易添加到窗口中,按钮上显示的文本与它们的名称相对应。 按钮都准备好了。 现在我们需要创建一个滚动周期的机制。 激活滚轮滚动和鼠标按钮单击事件。

    ChartSetInteger(0,CHART_EVENT_MOUSE_WHEEL,1);
    ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,1);
    
    
    

    接下来,我们要用到 OnChartEvent() 函数。 检查按钮是否按下。 如果没有,则在其上显示其名称。 如果按下按钮,激活指标周期滚动模式,并禁用图表本身的滚动。 相应地,如果未按下按钮,则恢复启用图表滚动。


    void OnChartEvent(const int id,
                      const long &lparam,
                      const double &dparam,
                      const string &sparam)
      {
    //---
       if(!press_Button)
          ObjectSetString(chart_ID,name,OBJPROP_TEXT,text);
       if(id==CHARTEVENT_OBJECT_CLICK)
         {
          if(sparam==name)
            {
             press_Button=ObjectGetInteger(chart_ID,name,OBJPROP_STATE);
             if(press_Button)
               {
                scroll_Button=true;
                ChartSetInteger(0,CHART_MOUSE_SCROLL,0);
                ChartRedraw();
               }
             else
               {
                scroll_Button=false;
                ChartSetInteger(0,CHART_MOUSE_SCROLL,1);
                ChartRedraw();
               }
            }
         }
      }
    
    

    如果激活了周期滚动模式,则检查鼠标滚轮状态。 取决于其滚动方向,我们递增或递减周期。 当滚动值达到 +120 或 -120 时,将触发滚轮的最大值。

    if(scroll_Button)
          if(id==CHARTEVENT_MOUSE_WHEEL)
            {
             int delta = (int)dparam;
             if(delta>119)
                if(period_ma<period_ma_max)
                  {
                   period_ma+=step;
                   ChartRedraw();
                  }
             //
             if(delta < -119)
                if(period_ma>step)
                  {
                   period_ma-=step;
                   ChartRedraw();
                  }
    
    

    在此,我们采用一个新的周期访问指标句柄,并重新绘制指标曲线(“total” 在 OnCalculate 中定义)。 此外,把按钮文本从名称更改为当前周期值。

    handle = iMA(_Symbol,_Period,period_ma,0,method_MA,price_MA);
          
             ArraySetAsSeries(Label1Buffer,true);
             for(int i = 0; i<total; i++)
               {
                double MA[];
                CopyBuffer(handle,0,i,1,MA);
                Label1Buffer[i]=MA[0];
           
                ChartRedraw();
    
               }
             ArraySetAsSeries(Label1Buffer,false);
             if(press_Button)
                ObjectSetString(chart_ID,name,OBJPROP_TEXT,IntegerToString(period_ma));
            }
    
    

    在此,我们重新绘制了指标曲线,这样我们就不必等待新的即时报价来临,就能显示以新周期计算的指标线。 故此,无论新的即时报价是否来临,指标线总是立即重建。

    另一个无法绕过的限制涉及按钮。 如果启用了拖放模式,则禁用按钮模式,反之亦然。 因此,我们只有一次机会把按钮拖动到更方便的位置。 禁用拖放模式后,我们将不再启用它。 在下一次初始化期间,按钮将不会记忆其之前位置,并恢复到其在正常时的原始位置。

    if(id==CHARTEVENT_OBJECT_CLICK)
         {
          if(sparam==name)
            {
             long select = ObjectGetInteger(chart_ID,name,OBJPROP_SELECTED);
             if(select==0)
               {
                ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,false);
                ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,false);
                flag_drag=false;
               }
             ChartRedraw();
             }
          }
    
    

    主指示线是在 OnCalculate() 中呈现;如常

    total=rates_total-1;
       if(Limit_for_Calc)
          if(total>Limit)
             total=Limit;
       handle = iMA(_Symbol,_Period,period_ma,0,method_MA,price_MA);
       if(handle==INVALID_HANDLE)
         {
          Print("invalid handle ");
          return(rates_total);
         }
       ResetLastError();
       while(BarsCalculated(handle)<=0)
         {
          // indicator values are not calculated yet, try next time
          return(0);
         }
    
       for(int i = prev_calculated>0?prev_calculated-1:0; i<rates_total; i++)
         {
          double MA[];
          CopyBuffer(handle,0,time[i],1,MA);
          Label1Buffer[i]=MA[0]+level;
         }
    
    
    此处,我们应在滚动时,限制依据最新数据重新计算指标线,从而节省了计算资源。 为了做到这一点,交易者选择是否要在设置中启用计算限制,以及计算范围将取多少根柱线。
    input bool    Limit_for_Calc = false;
    input int     Limit = 20000;
    
    


    结果

    我们已分析了指标开发的要点。 其结果是一个完全成熟的指标,绝不逊于标准指标,同时更为方便。 然而,它仍然有一些缺陷。 例如,我曾 经历了一次性能衰退,起因是历史数据超过 20,000 根柱线。 但历史分析并不经常需要,您可以限制日常计算所用的柱线数量。 手工重命名按钮,以及无法多次拖动按钮也非常不方便。

    总体而言,目标已经达成了 — 指标在滚动鼠标滚轮时平稳地变换周期,而滚动功能只需按下图表上的一个按钮即可打开和关闭。


    结束语

    这样的界面虽然并非没有缺陷,但仍然比标准界面更方便。 它允许交易者以最小的代价和时间获得必要的指标设置,从工作区中删除相同类型的指标,并在有限时间内,应对快速变化的行情,提供更准确的信号。 

    以此类界面创建的指标更易于使用、更直观、且更省时。 它们能够令您专注于交易,从而提高交易品质和生产率。 

    此外,这种方式不仅允许您获取周期,还可令您获取关联到按钮或面板上的任何其它设置。 这些设置可以包括颜色、线宽、对象大小等。 除了滚动之外,您还可以使用拖动对象、热键、或简单地将光标悬停在对象或图表区域上。 其中一个功能已在 Button MA 指标中实现。 它允许您使用周期滚动,不仅通过按下按钮,还可以简单地将鼠标悬停于其上。 此外,即使您更改了时间帧,它也会在拖动后记住按钮位置。

    本文覆盖了基于移动平均线创建周期滚动指标的基本步骤。 在文后的附件中,您可以找到基于 CCI 的现成指标的代码。 它甚至更简单,因为图表上的按钮不需要重命名,无论其数量。 它们的名称会根据设置按钮的数量自动编入索引。

    GIF_02


    本文由MetaQuotes Ltd译自俄文
    原文地址: https://www.mql5.com/ru/articles/10770

    附加的文件 |
    Button_CCI.mq5 (28.95 KB)
    DoEasy. 控件(第 6 部分):面板控件,自动调整容器大小来适应内部内容 DoEasy. 控件(第 6 部分):面板控件,自动调整容器大小来适应内部内容
    在本文中,我将继续研究面板 WinForms 对象,并实现自动调整大小,以便适应位于面板内的 Dock 对象的常规大小。 此外,我将向品种函数库对象添加新属性。
    学习如何基于 OBV 设计交易系统 学习如何基于 OBV 设计交易系统
    这是一篇新文章,将针对初学者继续我们的系列,介绍如何基于一些流行指标设计交易系统。 我们将学习一个新的指标,即能量潮(OBV),我们将学习如何使用并基于它来设计交易系统。
    数据科学与机器学习(第 03 部分):矩阵回归 数据科学与机器学习(第 03 部分):矩阵回归
    这一次,我们的模型是由矩阵构建的,它更具灵活性,同时它允许我们构建更强大的模型,不仅可以处理五个独立变量,但凡我们保持在计算机的计算极限之内,它还可以处理更多变量,这篇文章肯定会是一篇阅读起来很有趣的文章。
    神经网络变得轻松(第十五部分):利用 MQL5 进行数据聚类 神经网络变得轻松(第十五部分):利用 MQL5 进行数据聚类
    我们继续研究聚类方法。 在本文中,我们将创建一个新的 CKmeans 类来实现最常见的聚类方法之一:k-均值。 在测试期间,该模型成功地识别了大约 500 种形态。