English Русский Español Deutsch 日本語 Português
在MQL5中的三维建模

在MQL5中的三维建模

MetaTrader 5示例 | 1 三月 2017, 09:19
8 560 0
Sergey Pavlov
Sergey Pavlov

可以使用三维建模分析复杂的过程和现象,并且预测它们的结果。

在金融市场上,3D建模可以用做提供时间序列三维表现形式的示例。时间序列是一种动态的系统,它会按时持续收到随机变量的值或者连续等差的数值(订单分时,柱,分形等等)。在本文中,我们将探讨时间序列和指标的三维可视化(参见图1)。

图 1. 时间序列的三维表现形式示例。

图 1. 时间序列的三维表现形式示例。

三维模型和二维显示的差别是,它包含了3D模型使用特定函数而在一个平面上的几何投影。在一个平面上创建三维图形,需要以下的步骤:

  • 建模 — 创建时间序列的三维数学模型;
  • 渲染 (可视化) — 创建一个根据所选模型的投影,
  • 在屏幕上显示结果图片。

本文的目的就是在三维空间中显示通常的图表。现在 MQL5 没有提供可用的三维建模方案,所以我们会从基本原则和方法开始,包括三维对象和坐标系统。许多读者可能会对这一主题有疑虑,因而跳过这部分文字,但是本文中的一些算法和方法可能在与三维可视化无关的其它任务中有用。


交互的图形对象

我们将从三维对象开始,MQL5 语言的强大功能可以使您操作二维对象并创建复杂的图形化表现,增加几个函数,就可以在 MetaGTrader 5 终端中使用三维图形了。

首先,我们需要在设计三维对象的基类时确定有哪些需求。

  1. 简单易用
  2. 高度持久性
  3. 独立性
  4. 交互性

简单易用

我们需要为开发人员和使用人员创建一个最小函数集,可以足够用于三维图形的主要功能。

高度持久性

三维对象在创建类实例的程序的整个生命周期中必须可以保存,它必须被保护,以免被意外或者故意删除,以及防止它的基础属性被修改。

独立性

对象必须够 "聪明" 可以自我调整以适应改变的条件(坐标系统的旋转,基本锚点的改变,等等) 对象必须正确处理到来的信息并回应发生的对应事件。

交互性

三维可视化要有可以改变三维模型观察点的功能 (坐标系统的旋转),所以我们需要创建功能来避免需要使用额外的控制面板或者类似的东西。严格说来,MQL5 语言中的图形对象已经有了用于交互的属性: 您可以选择一个对象,移动它,修改它的属性,等等。我们只需要稍微增强这样的属性来启用收集管理和交互,例如,如果我们改变了坐标的中心点,所有相关对象都必须自动地和正确地重新排布。

如果我们提供了所有这些需求,我们的三维对象就将变成一个可交互图形对象(interactive graphical object,IGO)。可交互图形对象必须与 MQL5 图形对象相关联,让我们从可交互图形对象的基类 CIGO 开始。

class CIGO
  {
protected:
   bool              on_event;      // 事件处理标志
   int               m_layer;       // IGO 所属的图层
   //---
   double            SetPrice(double def,int prop_modifier=0);
public:
   string            m_name;        // IGO 对象的名称
   double            m_price;       // IGO 对象的基本锚点[价格]
   double            m_angle;       // IGO 投射角度 [度数]
                     CIGO();
                    ~CIGO();
   //---
   virtual     // 方法: 创建 IGO
   void              Create(string name) {on_event=true;}
   virtual     // 方法: 重绘 IGO
   void              Redraw() {ChartRedraw();}
   virtual     // OnChartEvent 处理方法
   bool              OnEvent(const int id,         // 事件 ID
                             const long &lparam,   // long 类型(长整数型)的事件参数
                             const double &dparam, // double 类型(双精度浮点数型)的事件参数
                             const string &sparam, // string 类型(字符串型)的事件参数
                             int iparamemr=0,      // IGO 事件标识符
                             double dparametr=0.0);// IGO double 类型的事件参数
  };

基类包含了最少的栏位和方法,可以在子类中重载或者补充,我们将只详细探讨两个类方法: 虚拟方法 OnEvent() 用于处理 OnChartEvent 以及用于设置基础锚点的 SetPrice()。交互图形对象的主要原则就是在这些类中实现的。

方法: 处理到来的 OnEvent 事件。

该方法处理当操作图表时来自客户终端的事件,此方法要回应四个标准事件: 删除图形对象, 修改图表大小或者属性,移动图形对象或者点击它,让我们详细讨论其中的每个事件。

  1. 删除一个图形对象。当这个事件发生时,图表上就不存在这个对象了 — 它已经被删除了。但是我们有保存的需求,所以对象必须被立刻恢复,也就是说,对象必须在删除之前做出处理,以同样的属性重新创建。注意: 图形对象是在这里删除的,不是CIGO类关联的实例,类的实例还是继续存在,并且它保存着被删除的图形对象的信息,所以我们可以轻松通过使用 Create() 方法来恢复对象。
  2. 修改图表对象或者属性。有许多这种类型的事件,包括新柱的出现,图表缩放的改变,时段的切换以及其它等等。回应这样的事件应该要根据更新后的环境重绘对象,使用的是 Redraw() 方法。请注意,当您切换到另一个时段时,类的实例会重新初始化并且会丢失保存在类栏位中的所创建的图形对象的数据,尽管图形对象依然存在于图表上。所以使用图形对象的属性来恢复类实例的栏位,就能够保存 IGO
  3. 移动一个图形对象。图形对象的交互性就是基于此属性的,当对象被移动时,它的基础锚点就改变了,在移动对象之前,我们需要选择它(通过鼠标双击),如果这个事件发生了,方法就返回 true, 否则返回 false。当我们对交互图形对象进行一系列操作时,我们需要这个值。请注意,如果我们需要一个不能移动的图形对象,我们应该在创建对象时禁止它能够被选中。
  4. 在图形对象上点击一次. 如果鼠标在其它对象上点击了,该对象应该取消被选择,以免意外移动,这样,在一个图表上只有一个 IGO 对象可以被选择。
    bool CIGO::OnEvent(const int id,
                       const long &lparam,
                       const double &dparam,
                       const string &sparam,
                       int iparamemr=0,
                       double dparametr=0.0)
      {
       bool res=false;
       if(on_event) // 允许事件的处理
         {
          // 删除一个图形对象
          if((ENUM_CHART_EVENT)id==CHARTEVENT_OBJECT_DELETE && sparam==m_name)
            {
             Create(m_name);
            }
          // 改变图表的大小或者使用属性对话框改变图表属性
          if((ENUM_CHART_EVENT)id==CHARTEVENT_CHART_CHANGE)
            {
             Redraw();
            }
          // 移动一个图形对象
          if((ENUM_CHART_EVENT)id==CHARTEVENT_OBJECT_DRAG)
             if(ObjectGetInteger(0,sparam,OBJPROP_SELECTED)==1 && sparam==m_name)
               {
                m_price=ObjectGetDouble(0,m_name,OBJPROP_PRICE);
                Redraw();
                res=true;   // 通知基础锚点有了改变
               }
          // 在图形对象外点击了鼠标
          if((ENUM_CHART_EVENT)id==CHARTEVENT_OBJECT_CLICK && sparam!=m_name)
            {
             ObjectSetInteger(0,m_name,OBJPROP_SELECTED,0);
             ChartRedraw();
            }
         }
       return(res);
      }

参数: 

 id

   [in] 事件的 id. 可以使用此方法处理9种类型的事件

 lparam

   [in] 长整数型(long)的事件参数。

 dparam

   [in] 双精度浮点型(double)的事件参数。

 sparam

   [in] 字符串类型(string)的事件参数。

 iparametr

   [in] 自定义事件标识符。

 dparametr

   [in] 自定义事件的长整数型参数。

返回值:

如果对象的基础锚点坐标发生了改变就返回 true,否则返回 false。 

 

方法: 设置基础锚点的坐标 SetPrice

把"Chart(图表)"坐标系统的 "price(价格)"值设为类实例的 m_price 栏位的值。

让我解释一下通过本方法改变了基础锚点坐标会发生什么,

  1. 在类的初始化过程中,m_price栏位 (基础点坐标)不包含任何输入数值,所以 m_price=NULL,当类实例被创建时,或者当图表的时段切换的时候,都会进行类的初始化,图形对象可能不在图表上,也可能存在于图表上,因为之前有程序调用或者在切换时段之后,所以图形对象的对应属性的值要赋给 m_price 栏位,
  2. 如果图形对象的名称 m_name 不存在,基础锚点的坐标也就在第一步之后没有被定义: m_price=NULL,在这种情况下,m_price 栏位设为默认的 def 数值,
double CIGO::SetPrice(double def,int prop_modifier=0)
  {
   if(m_price==NULL)             // 如果变量没有值
      m_price=ObjectGetDouble(0,m_name,OBJPROP_PRICE,prop_modifier);
   if(m_price==NULL)             // 如果没有坐标
      m_price=def;               // 默认值
   return(m_price);
  }

参数:

 def

   [in] 变量的默认值。

 prop_modifier

   [in] 图形对象所需属性的标识符。

返回值:

 基础锚点坐标的数值。  

现在我们看一下交互对象基类的子类,首先我们对用于创建三维模型的3D对象有兴趣,

 

C_OBJ_ARROW_RIGHT_PRICE 类: "Right Price Label(右侧价格标签)" 对象

派生于 CIGO 基类。 

//+------------------------------------------------------------------+
//| Class OBJ_ARROW_RIGHT_PRICE: 对象 "Right Price Label(右侧价格标签)"  |
//+------------------------------------------------------------------+
class C_OBJ_ARROW_RIGHT_PRICE:public CIGO
  {
public:
   virtual     // 方法: 创建对象
   void              Create(string name);
   virtual     // 方法: 重绘对象
   void              Redraw();
  };
//+------------------------------------------------------------------+
//| 方法: 创建对象                           |
//+------------------------------------------------------------------+
void C_OBJ_ARROW_RIGHT_PRICE::Create(string name)
  {
   m_name=name;
   m_price=SetPrice((ChartGetDouble(0,CHART_PRICE_MAX)+ChartGetDouble(0,CHART_PRICE_MIN))/2);
   ObjectCreate(0,m_name,OBJ_ARROW_RIGHT_PRICE,0,0,0);
   ObjectSetInteger(0,m_name,OBJPROP_SELECTABLE,true);
   ObjectSetInteger(0,m_name,OBJPROP_WIDTH,1);
   ObjectSetInteger(0,m_name,OBJPROP_COLOR,clrISO);
//---
   ObjectSetInteger(0,m_name,OBJPROP_TIME,0,T(0));
   ObjectSetDouble(0,m_name,OBJPROP_PRICE,0,m_price);
//---
   ChartRedraw();
   on_event=true; // Allow event handling
  }
//+------------------------------------------------------------------+
//| 方法: 重绘对象                           |
//+------------------------------------------------------------------+
void C_OBJ_ARROW_RIGHT_PRICE::Redraw()
  {
   ObjectSetInteger(0,m_name,OBJPROP_TIME,0,T(0));
   ChartRedraw();
  }

这是最适合用于组织三维坐标系统中心的类,类的实例关联于当前的主,并且就在Z轴上。 

 

Class C_OBJ_TREND: "Trendline(趋势线)"对象 

派生于 CIGO 基类。

//+------------------------------------------------------------------+
//| Class OBJ_TREND: "Trendline(趋势线)"对象            |
//+------------------------------------------------------------------+
class C_OBJ_TREND:public CIGO
  {
public:
   virtual     // 方法: 创建对象
   void              Create(string name);
   virtual     // 方法: 重绘对象
   void              Redraw();
  };
//+------------------------------------------------------------------+
//| 方法: 创建对象                           |
//+------------------------------------------------------------------+
void C_OBJ_TREND::Create(string name)
  {
   m_name=name;
   m_price=(ChartGetDouble(0,CHART_PRICE_MAX)+ChartGetDouble(0,CHART_PRICE_MIN))/2;
   ObjectCreate(0,m_name,OBJ_TREND,0,0,0);
   ObjectSetInteger(0,m_name,OBJPROP_COLOR,clrISO);
   ObjectSetInteger(0,m_name,OBJPROP_WIDTH,0);
   ObjectSetInteger(0,m_name,OBJPROP_STYLE,styleISO);
   ObjectSetDouble(0,m_name,OBJPROP_PRICE,0,m_price);
   ObjectSetInteger(0,m_name,OBJPROP_TIME,0,T(0));
   ObjectSetDouble(0,m_name,OBJPROP_PRICE,1,m_price+1);
   ObjectSetInteger(0,m_name,OBJPROP_TIME,1,T(0));
   ObjectSetInteger(0,m_name,OBJPROP_RAY_RIGHT,true);
   ObjectSetInteger(0,m_name,OBJPROP_RAY_LEFT,true);
   ObjectSetInteger(0,m_name,OBJPROP_BACK,true);
   ObjectSetInteger(0,m_name,OBJPROP_SELECTABLE,false);
//---
   ChartRedraw();
   on_event=true; // Allow event handling
  }
//+------------------------------------------------------------------+
//| 方法: 重绘对象                           |
//+------------------------------------------------------------------+
void C_OBJ_TREND::Redraw()
  {
   ObjectSetInteger(0,m_name,OBJPROP_TIME,0,T(0));
   ObjectSetInteger(0,m_name,OBJPROP_TIME,1,T(0));
   ChartRedraw();
  }

适合用本类创建Z轴,本类只提供了最少的功能,但是对于3D建模来说也足够了,

 

Class C_OBJ_TRENDBYANGLE: "Trendline by angle(带角度的趋势线)" 对象 

派生于 CIGO 基类。

//+------------------------------------------------------------------+
//| Class OBJ_TRENDBYANGLE: the "角度趋势线" 对象          |
//+------------------------------------------------------------------+
class C_OBJ_TRENDBYANGLE:public CIGO
  {
protected:
   int               m_bar;   // 关联第二个基本点的柱的编号
   //---
   double SetAngle(double def)
     {
      if(m_angle==NULL) // 如果变量没有值
         m_angle=ObjectGetDouble(0,m_name,OBJPROP_ANGLE);
      if(m_angle==NULL)       // 如果没有坐标
         m_angle=def;         // 默认值
      return(m_angle);
     }
public:
                     C_OBJ_TRENDBYANGLE();
   virtual     // 方法: 创建对象
   void              Create(string name,double price,double angle);
   virtual     // 方法: 重绘对象
   void              Redraw();
   virtual     // OnChartEvent 处理方法
   bool              OnEvent(const int id,
                             const long &lparam,
                             const double &dparam,
                             const string &sparam,
                             int iparamemr=0,
                             double dparametr=0.0);
  };
//+------------------------------------------------------------------+
//| 构造函数                             |
//+------------------------------------------------------------------+
C_OBJ_TRENDBYANGLE::C_OBJ_TRENDBYANGLE()
  {
   m_bar=c_bar;
  }
//+------------------------------------------------------------------+
//| 方法: 创建对象                          |
//+------------------------------------------------------------------+
void C_OBJ_TRENDBYANGLE::Create(string name,double price,double angle)
  {
   datetime time=T(0);
   datetime deltaT=T(m_bar)-time;
   m_name=name;
   m_price=SetPrice(price);
   m_angle=SetAngle(angle);
   ObjectCreate(0,m_name,OBJ_TRENDBYANGLE,0,time,m_price,time+deltaT,m_price);
   ObjectSetInteger(0,m_name,OBJPROP_COLOR,clrISO);
   ObjectSetInteger(0,m_name,OBJPROP_WIDTH,0);
   ObjectSetInteger(0,m_name,OBJPROP_STYLE,styleISO);
   ObjectSetInteger(0,m_name,OBJPROP_RAY_RIGHT,true);
   ObjectSetInteger(0,m_name,OBJPROP_RAY_LEFT,true);
   ObjectSetInteger(0,m_name,OBJPROP_BACK,true);
   ObjectSetInteger(0,m_name,OBJPROP_SELECTABLE,true);
//--- 修改趋势线的斜率,线的第二个点的坐标
//--- 会根据新的角度自动确定
   ObjectSetDouble(0,m_name,OBJPROP_ANGLE,m_angle);
   ChartRedraw();
//---
   on_event=true; // 允许事件处理
  }
//+------------------------------------------------------------------+
//| 方法: 重绘对象                           |
//+------------------------------------------------------------------+
void C_OBJ_TRENDBYANGLE::Redraw()
  {
   ObjectSetInteger(0,m_name,OBJPROP_TIME,T(0));
   ObjectSetInteger(0,m_name,OBJPROP_TIME,1,T(0)+T(m_bar)-T(0));
   ObjectSetDouble(0,m_name,OBJPROP_PRICE,m_price);
   ObjectSetDouble(0,m_name,OBJPROP_ANGLE,m_angle);
   ChartRedraw();
  }
//+------------------------------------------------------------------+
//| OnChartEvent 处理方法                         |
//+------------------------------------------------------------------+
bool C_OBJ_TRENDBYANGLE::OnEvent(const int id,
                                 const long &lparam,
                                 const double &dparam,
                                 const string &sparam,
                                 int iparamemr=0,
                                 double dparametr=0.0)
  {
//---
   bool res=false;
   if(on_event) // 允许事件的处理
     {
      // Deleting a graphical object
      if((ENUM_CHART_EVENT)id==CHARTEVENT_OBJECT_DELETE && sparam==m_name)
        {
         Create(m_name,m_price,m_angle);
        }
      // 删除图形对象
      if((ENUM_CHART_EVENT)id==CHARTEVENT_CHART_CHANGE)
        {
         Redraw();
        }
      // 移动图形对象
      if((ENUM_CHART_EVENT)id==CHARTEVENT_OBJECT_DRAG)
        {
         //---
         if(ObjectGetInteger(0,sparam,OBJPROP_SELECTED)==1 && sparam==m_name)
           {
            m_angle=ObjectGetDouble(0,m_name,OBJPROP_ANGLE);
            Create(m_name,m_price,m_angle);
            res=true;   // 通知基本锚点已经改变
           }
         if(iparamemr==Event_1)// 收到基本点改变的消息
           {
            m_price=dparametr;
            Create(m_name,m_price,m_angle);
           }
         if(iparamemr==Event_2)// 收到关于基本角度改变的消息
           {
            m_angle=dparametr;
            Create(m_name,m_price,m_angle);
           }
        }
      // 在图形对象外部点击了鼠标
      if((ENUM_CHART_EVENT)id==CHARTEVENT_OBJECT_CLICK && sparam!=m_name)
        {
         ObjectSetInteger(0,m_name,OBJPROP_SELECTED,0);
         ChartRedraw();
        }
     }
   return(res);
  }

本来是打算使用"趋势线"的子类来用作 XY 轴,但是很清楚的是 "Trendline by angle(角度趋势线)" 是完成这个目标的完美方案。在阅读MQL5语言文档的时候我发现这个强大的工具: 当绘制角度趋势线的时候,就算时段切换或者图表的缩放改变的时候,角度还是保持不变。

结论: 仔细阅读文档,您将会发现很多有用的函数和工具。 

 

图形内存 (GM)

文章"直方图形式的统计分布, 无需指标缓冲区和数组" 文章中提供了图形内存描述和一些实例,在此我就重复一下基本原则: 图形对象的属性可以保存所需的信息在其它对象和程序函数中使用。

下面的类的创建就是为了更简单地编程和使用图形内存:

class CGM
  {
private:
public:
   string            m_name;        // 图形对象的名称
                     CGM(){}
                    ~CGM(){}
   void              Create(string name) {m_name=name;}
   // 读取 OBJPROP_PRICE 属性
   double            R_OBJPROP_PRICE(int prop_modifier=0)
     {
      return(ObjectGetDouble(0,m_name,OBJPROP_PRICE,prop_modifier));
     }
   // 读取 OBJPROP_TIME 属性
   datetime          R_OBJPROP_TIME(int prop_modifier=0)
     {
      return((datetime)ObjectGetInteger(0,m_name,OBJPROP_TIME,prop_modifier));
     }
   // 读取 OBJPROP_ANGLE 属性
   double            R_OBJPROP_ANGLE()
     {
      return(ObjectGetDouble(0,m_name,OBJPROP_ANGLE));
     }
   // 读取 OBJPROP_TEXT 属性
   string            R_OBJPROP_TEXT()
     {
      return(ObjectGetString(0,m_name,OBJPROP_TEXT));
     }
   // 返回指定时间的价格数值
   double            R_ValueByTime(datetime time)
     {
      return(ObjectGetValueByTime(0,m_name,time));
     }
   // 写入 OBJPROP_TEXT 属性
   void              W_OBJPROP_TEXT(string text)
     {
      ObjectSetString(0,m_name,OBJPROP_TEXT,text);
     }
  };

您可以从代码中看到,它包含了读取和写入图形对象属性的方法,这个类在创建的时候与一个图形对象关联,并且它允许接收/传送用于任何交互图形对象所需的信息,例如,

  • 我们已经创建了一个3D对象叫做 A, 它所关联的图形内存类实例是AA
  • 现在我们创建了一个3D对象叫做 B, 它可以通过AA来取得A对象的所需信息。在 A 和 B 之间没有直接的关联, 但是AA类的实例可以实现从 AB 重要数据的传输;
  • 而在创建B 对象的时候, 也会创建BB的图形内存类实例,现在 A 对象就可以访问 B 对象的所需信息了;
  • 等等。对象的数量没有限制,而每个对象都可以使用图形界面类的实例来收到和发送信息。

图形内存可以类比为一个公告板,在其中可以提供某些信息以及发布通知。这种方法可以提供交互图形对象之间数据的交换。在三维建模设计的时候,需要哪些数据来正确进行3D对象数据的编程呢,最重要的信息就是对象的坐标系统,因为当旋转或者移动坐标的中心时,整个三维对象"包"都回改变属性和它们在三维空间的位置,

参见在交互坐标系统类CUSC中图形内存是怎样使用的。

坐标系统

三维可视化分析使您可以在三维空间中分析数据: 例如,为一个或多个变量的源数据序列(观察)构造一个三维图片,所限的变量通过X轴表示, 观察结果由Y轴表示,而变量值(用于此观察)沿着 Z 轴绘出。

这样的三维图表可以用于显示多个变量的序列值。

与二维多线形图相比,三维表示的主要优点是,使用三维图片可以更容易发现数据序列中的孤立的值。通过使用正确的角度,它可以通过使用交互式的旋转来选择,图表线不会相互覆盖也不会像在二维多线形图中那样压线。 

在我们进行三维建模之前,让我们看一下坐标系统,我们必须在其中进行操作。MQL5语言为开发者提供了 "Chart(图表)" 和 "Canvas(画布)"两种坐标系统,我们将再实现一种三维系统 (一种轴侧投影)。所以,让我们探讨它们每一个的差别和目标。

图 2. "Chart(图表)"坐标系统。

图 2. "Chart(图表)"坐标系统。

"图表"坐标系统 (图2)是一种二维坐标系统,用于显示价格数据,时间序列和指标。水平轴上是时间尺度,它是从左向右的,垂直轴表示金融资产的价格,大多数 MQL5 图形对象是运行于此坐标系统中的,当前的价格和柱形位于相同的垂直轴上,而当新柱出现时,图表自动向左边移动。

图 3. "Canvas(画布)"坐标系统。

图 3. "Canvas(画布)"坐标系统。

"画布"坐标系统(图 3). 屏幕上的每个点对应着一个像素,点的坐标是从图表的左上角开始计数的,这个系统也是二维的,它用于不与时间序列关联的对象,在本文中我们将不会使用这个系统。

图 4. 三维坐标系统。

图 4. 三维坐标系统。

三维坐标系统(图 4). 在这种坐标系统中,有三个彼此垂直的轴,然而,在"画布"坐标系统中XYZ轴之间的夹角看起来不等于90度,比如, XY 形成的夹角是120度,夹角可能会不同,但是在本文中我们会使用上述值。

Z轴关联着当前柱,而轴的尺度对应着"图表"坐标系统的"价格"尺度,这很方便,因为不需要单独为Z轴创建一个独立的尺度,我们可以用"价格"尺度代替,所以 Z=price

X 轴的方向是从左向右的,也就是说和"图表"坐标系统中的"时间"尺度相反。Bar(柱)变量的值将在本文后面介绍;所以任何柱在X轴上的投射都等于它的值 (X=Bar),

Y 轴用于二维XZ数据序列,例如,我们可以在这个轴上画出b1>Open(开盘价), Close(收盘价), High(最高价)Low(最低价)时间序列的线,而每条线都位于独立的平面上,通过连接这些线在与YZ平面平行的点,我们将得到一个网格,也就是说,一个三维时间序列对象(参见图5)。 

图 5. 三维坐标系统中的一个3D对象。

图 5. 三维坐标系统中的一个3D对象。

 

交互的坐标系统: Class CUSC

派生于 CIGO 基类。 

class CUSC:public CIGO
  {
private:
   datetime          m_prev_bar;
   datetime          m_next_bar;
   //---
   C_OBJ_ARROW_RIGHT_PRICE Centr;   // 声明一个 C_OBJ_ARROW_RIGHT_PRICE 类的实例
   C_OBJ_TREND       AxisZ;         // 声明一个 C_OBJ_TREND 类的实例
   C_OBJ_TRENDBYANGLE AxisY,AxisX;  // 声明 C_OBJ_TRENDBYANGLE 类的实例
   //---
   CGM               gCentr;        // 声明 CGM 类的实例
   CGM               gAxisY,gAxisX; // 声明 CGM 类的实例

public:
                     CUSC();
                    ~CUSC();
   //--- 计算 Z 坐标
   sPointCoordinates Z(double price,   // "图表"坐标系统的价格
                       int barX,       // 在X轴上的偏移
                       int barY);      // 在Y轴上的偏移
   //--- 新柱
   bool on_bar()
     {
      m_next_bar=T(0);
      if(m_next_bar>m_prev_bar)
        {
         m_prev_bar=m_next_bar;
         return(true);
        }
      return(false);
     }
   //---
   virtual     // 方法: 创建一个 IGO 对象
   void              Create(string name);
   virtual     // OnChartEvent 处理方法
   void              OnEvent(const int id,
                             const long &lparam,
                             const double &dparam,
                             const string &sparam);
  };

让我们仔细探讨类的以下方法: Create(), OnEvent(), 和 Z()

Create 方法: 创建一个三维坐标系统

创建一个交互坐标系统的3D对象,交互性包括: 坐标系统可以围绕Z轴旋转,并且可以从初始点移动。

void CUSC::Create(string name)
  {
//--- 三维坐标系统的中心
   Centr.Create("Axis XYZ");        // 创建坐标系统的中心
   gCentr.Create(Centr.m_name);     // 创建图形内存中的一个对象
   m_price=gCentr.R_OBJPROP_PRICE();
//--- Z 轴
   AxisZ.Create("Z轴");          // 创建坐标系统中的 Z 轴
//--- Y 轴
   AxisY.Create("Y轴",                 // 创建坐标系统中的 Y 轴
                gCentr.R_OBJPROP_PRICE(), // 从GM中取得数值
                30);                      // 把轴的夹角设为30度
   gAxisY.Create(AxisY.m_name);           // 创建一个图形内存对象
   m_angle=gAxisY.R_OBJPROP_ANGLE();
//--- X 轴
   AxisX.Create("X轴",                       // 创建坐标系统中的 X 轴
                gCentr.R_OBJPROP_PRICE(),       // 从GM中取得数值
                gAxisY.R_OBJPROP_ANGLE()+ISO);  // 从GM中取得数值并增加ISO角度
   gAxisX.Create(AxisX.m_name);                 // 创建图形内存对象
//---
   ChartRedraw();
   on_event=true; // 允许处理事件
  }

参数:

 name

   [in] 坐标系统的名称。

返回值:

 没有返回值。如果成功,就创建一个交互的坐标系统。

 

The OnEvent 方法: 处理到来的消息

该方法处理当操作图表时来自客户终端的事件,该方法只回应一个标准事件: 移动图形对象。其它的时间会随后转发到坐标系统编程中的类的所有实例。

void CUSC::OnEvent(const int id,
                   const long &lparam,
                   const double &dparam,
                   const string &sparam)
  {
   if(on_event) // 允许处理事件
     {
      //--- 传送 OnChartEvent 事件
      AxisZ.OnEvent(id,lparam,dparam,sparam);
      //---
      if(Centr.OnEvent(id,lparam,dparam,sparam))
        {// 移动图形对象
         AxisY.OnEvent(id,lparam,dparam,sparam,Event_1,gCentr.R_OBJPROP_PRICE());
         AxisX.OnEvent(id,lparam,dparam,sparam,Event_1,gCentr.R_OBJPROP_PRICE());
        }
      else
        {
         if(AxisY.OnEvent(id,lparam,dparam,sparam))
           {// 改变图形对象的角度
            AxisX.OnEvent(id,lparam,dparam,sparam,Event_2,gAxisY.R_OBJPROP_ANGLE()+ISO);
           }
         else
           {
            if(AxisX.OnEvent(id,lparam,dparam,sparam))
              {// 改变图形对象的角度
               AxisY.OnEvent(id,lparam,dparam,sparam,Event_2,gAxisX.R_OBJPROP_ANGLE()-ISO);
              }
           }
        }
      ChartRedraw();
      m_price=gCentr.R_OBJPROP_PRICE();
      m_angle=gAxisY.R_OBJPROP_ANGLE();
     }
  }

参数: 

 id

   [in] 事件的 id. 可以使用此方法处理9种类型的事件

 lparam

   [in] 长整数型(long)的事件参数。

 dparam

   [in] 双精度浮点型(double)的事件参数。

 sparam

   [in] 字符串类型(string)的事件参数。

 iparametr

   [in] 自定义事件标识符。

 dparametr

   [in] 自定义事件的长整数型参数。

返回值:

 没有返回值。

 

用于接收坐标数字的结构 (sPointCoordinates).

用于保存"图表"坐标系统的坐标值。该结构允许取得3D点的坐标。 

struct sPointCoordinates
  {
   datetime          time;    // "图表"系统中的一个坐标
   double            price;   // "图表"系统中的一个坐标
  };

一个sPointCoordinates类型的变量可以在一个Z()函数的调用中取得一个3D点在图表坐标系统的坐标。

 

Z 方法: 计算 Z 坐标。 

计算 Z 坐标的方法。

sPointCoordinates CUSC::Z(double price,int barX,int barY)
  {
   sPointCoordinates res;
   res.price=0;
   res.time=0;
   double dX,dY;
   dX=0;
   dX=gAxisX.R_ValueByTime(T(barX))-m_price;
   dY=0;
   dY=gAxisY.R_ValueByTime(T(-barY))-m_price;
   res.price=price+dX-dY;
   res.time=T(barX-barY);
   return(res);
  }

参数: 

 price

   [in] 3D对象的Z坐标。

 barX

   [in] 3D对象的 X 坐标。该数值是柱数。

 barY

   [in] 3D对象的 Y 坐标。该数值是柱数。

返回值:

  如果函数成功,返回 sPointCoordinates 类型的变量值。

 

创建一个网格形式的3D表面

作为实例,让我们探讨一下基于以下函数构建一个三维平面:

fun[i][j]=close[i*StepX]-_Point*j*j
该方程没有实际意义,它只是用于我们类功能的简单演示,代码很清晰,它很容易理解以及由不同技能水平的程序员在各种实验中使用。运行这个指标就能使用给定的函数在图表上得到一个3D网格 (参见图 6). 您可以通过改变Y轴和X轴之间的角度来改变观察点,对象实际上是围绕Z轴旋转的。移动坐标中心将会改变模型的颜色: 红色 — 节点的值在Z轴上在中心0点以上,蓝色 — 在坐标中心下方。
//--- 声明常数
#define  StepX    10    // 在X轴上的步长 [柱数]
#define  StepY    5     // 在Y轴上的步长 [柱数]
#define  _X       50    // 在X轴上的点的数量
#define  _Y       15    // 在Y轴上的点的数量
#define  W1       1     // 线的宽度
#define  W2       3     // 最后的线的宽度
//--- 包含类的文件
#include <3D\USC.mqh>
//---
#property indicator_chart_window
//--- 用于指标计算的缓冲区数量
#property indicator_buffers 0
//--- 指标的图形序列数量
#property indicator_plots   0
//---
CUSC        USC;
double fun[_X][_Y];
//+------------------------------------------------------------------+
//| 自定义指标初始化函数                        |
//+------------------------------------------------------------------+
int OnInit()
  {
   USC.Create("3D");
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| 自定义指标迭代函数                         |
//+------------------------------------------------------------------+
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[])
  {
   ArraySetAsSeries(close,true);
//--- 3D 表面函数
   for(int i=0;i<_X;i++)
      for(int j=0;j<_Y;j++)
        {
         fun[i][j]=close[i*StepX]-_Point*j*j;
         //fun[i][j]=close[i*StepX]-_Point*j*j*i/7;
        }

////--- X 线
   for(int i=1;i<_X;i++)
      for(int j=0;j<_Y;j++)
        {
         sPointCoordinates a0=USC.Z(fun[(i-1)][j], // 函数
                                    (i-1)*StepX,   // X
                                    -j*StepY);     // Y
         sPointCoordinates a1=USC.Z(fun[i][j],     // 函数
                                    i*StepX,       // X
                                    -j*StepY);     // Y
         string name="line x "+"x"+(string)i+"y"+(string)+j;
         ObjectCreate(0,name,OBJ_TREND,0,a0.time,a0.price,a1.time,a1.price);
         if(fun[i][j]>USC.m_price && fun[i-1][j]>USC.m_price)
            ObjectSetInteger(0,name,OBJPROP_COLOR,clrRed);
         else
            ObjectSetInteger(0,name,OBJPROP_COLOR,clrBlue);
         ObjectSetInteger(0,name,OBJPROP_WIDTH,W1);
         if(j==0 || j==_Y-1)
            ObjectSetInteger(0,name,OBJPROP_WIDTH,W2);
         ObjectSetInteger(0,name,OBJPROP_STYLE,STYLE_SOLID);
         ObjectSetInteger(0,name,OBJPROP_RAY_RIGHT,false);
         ObjectSetInteger(0,name,OBJPROP_RAY_LEFT,false);
         ObjectSetInteger(0,name,OBJPROP_BACK,true);
         ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
        }
////--- Y 线
   for(int i=0;i<_X;i++)
      for(int j=1;j<_Y;j++)
        {
         sPointCoordinates a0=USC.Z(fun[i][j-1],   // 函数
                                    i*StepX,       // X
                                    -(j-1)*StepY); // Y
         sPointCoordinates a1=USC.Z(fun[i][j],     // 函数
                                    i*StepX,       // X
                                    -j*StepY);     // Y
         string name="line y "+"x"+(string)i+"y"+(string)+j;
         ObjectCreate(0,name,OBJ_TREND,0,a0.time,a0.price,a1.time,a1.price);
         ObjectSetInteger(0,name,OBJPROP_COLOR,clrGreen);
         ObjectSetInteger(0,name,OBJPROP_WIDTH,1);
         ObjectSetInteger(0,name,OBJPROP_STYLE,STYLE_SOLID);
         ObjectSetInteger(0,name,OBJPROP_RAY_RIGHT,false);
         ObjectSetInteger(0,name,OBJPROP_RAY_LEFT,false);
         ObjectSetInteger(0,name,OBJPROP_BACK,true);
         ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
        }
//--- 返回 prev_calculated 的值用于下一次调用
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| ChartEvent 函数                          |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   USC.OnEvent(id,lparam,dparam,sparam);
  }

表面中平行于XZ和YZ平面的线是分开独立绘制的,您可以尝试改变在每个轴上节点的数量以及函数本身。

图 6. 绘制一个3D表面。

图 6. 绘制一个3D表面。

图 7. 绘制一个3D移动平均.

图 7. 绘制一个3D移动平均. 

这里是对怀疑者的回答:为什么我们需要三维建模并且它能如何帮助交易呢?

当然,MetaTrader 5 终端是设计用于在金融市场上做交易的,交易者和自动交易系统的开发者主要的兴趣在于交易算法和创建获利的策略,我们讨论的技术还在它们的婴儿期,并不能给您带来特别的交易结果,但是三维建模可以用于帮您向潜在投资者展示您的交易想法和策略。另外,终端允许创建反应市场信息流的动态三维模型,以及随着时间改变它们的外观(通过重建), 这样就可以创建3D指标。

这和Windows类似: 第一个版本的系统很慢并且弱,Norton Commander 的爱好者还质疑过图形界面,现在 NC 又在哪里了呢?

结论

  1. 三维建模在任何编程环境中都是一项比较困难的任务,但是 MQL5 语言的开发者可以积极一些,他们已经给使用者提供了强大的功能,可以在交易终端中实现三维显示。
  2. 我们已经得到了3D对象类编程的第一个开发库,三维建模的范围很广,一篇文章是无法全面介绍它的功能的。希望三维建模的追随者能够建议在交易中和在MQL5编程中3D开发的方向。
  3. 本文中创建的三维建模工具对技术分析的新方向的开发可能有很大影响: 三维指标和它们的分析。
  4. 渲染的速度还有些问题,但是它们在本阶段的库的开发中不是关键。
  5. 本文中提出的3D对象创建的原则可能可以迁移到 OpenCL。

注意事项:

库文件应该保存到 ..\Include\3D 文件夹。

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

附加的文件 |
GM.mqh (3.83 KB)
IGO.mqh (29.88 KB)
testIGO.mq5 (10.01 KB)
USC.mqh (14.9 KB)
ZUP - 通用之字折线构造 Pesavento 形态。图形界面 ZUP - 通用之字折线构造 Pesavento 形态。图形界面
自 ZUP 平台的第一版本发布以来已过了十年时间, 期间它经历了多次变化和改进。结果就是, 如今我们为 MetaTrader 4 提供了一个独特的图形插件, 您可以快速、便捷地分析行情数据。本文介绍如何使用 ZUP 指标平台的图形界面。
根据特定的价格变化自动侦测极值点 根据特定的价格变化自动侦测极值点
与图形模式相关的交易策略自动化需要能够在图表中搜索极值点以备进一步处理和解释,现有的工具并不能一直提供这种功能。本文中描述的方法可以在图表上找到极值点,这里讨论的工具在有趋势和平盘市场上都一样有效,取得的结果不会被所选时段很大影响,也不会只针对特定的缩放尺度。
图形界面 X: 多行文本框控件 (集成编译 8) 图形界面 X: 多行文本框控件 (集成编译 8)
讨论多行文本框控件。不同于 OBJ_EDIT 类型的图形对象, 这一版本没有输入字符数量的限制。它还添加了将文本框转换为简单文本编辑器的模式, 其内可以使用鼠标或键盘移动光标。
在您的网站上免费嵌入 MetaTrader 4/5 网页版终端并赚取利润 在您的网站上免费嵌入 MetaTrader 4/5 网页版终端并赚取利润
交易者会非常熟悉 WebTerminal, 它允许直接从浏览器在金融市场上交易。将 WebTerminal 小部件添加到您的网站 — 这样做是绝对免费的。如果您有网站, 您可开始向经纪商引荐潜在客户 — 我们已为您准备好了一个即用型的网页版解决方案。您需要做的所有事情就是将一个 iframe 嵌入您的网站。