
在MQL5中的三维建模
可以使用三维建模分析复杂的过程和现象,并且预测它们的结果。
在金融市场上,3D建模可以用做提供时间序列三维表现形式的示例。时间序列是一种动态的系统,它会按时持续收到随机变量的值或者连续等差的数值(订单分时,柱,分形等等)。在本文中,我们将探讨时间序列和指标的三维可视化(参见图1)。
图 1. 时间序列的三维表现形式示例。
三维模型和二维显示的差别是,它包含了3D模型使用特定函数而在一个平面上的几何投影。在一个平面上创建三维图形,需要以下的步骤:
- 建模 — 创建时间序列的三维数学模型;
- 渲染 (可视化) — 创建一个根据所选模型的投影,
- 在屏幕上显示结果图片。
本文的目的就是在三维空间中显示通常的图表。现在 MQL5 没有提供可用的三维建模方案,所以我们会从基本原则和方法开始,包括三维对象和坐标系统。许多读者可能会对这一主题有疑虑,因而跳过这部分文字,但是本文中的一些算法和方法可能在与三维可视化无关的其它任务中有用。
交互的图形对象
我们将从三维对象开始,MQL5 语言的强大功能可以使您操作二维对象并创建复杂的图形化表现,增加几个函数,就可以在 MetaGTrader 5 终端中使用三维图形了。
首先,我们需要在设计三维对象的基类时确定有哪些需求。
- 简单易用
- 高度持久性
- 独立性
- 交互性
简单易用
我们需要为开发人员和使用人员创建一个最小函数集,可以足够用于三维图形的主要功能。
高度持久性
三维对象在创建类实例的程序的整个生命周期中必须可以保存,它必须被保护,以免被意外或者故意删除,以及防止它的基础属性被修改。
独立性
对象必须够 "聪明" 可以自我调整以适应改变的条件(坐标系统的旋转,基本锚点的改变,等等) 对象必须正确处理到来的信息并回应发生的对应事件。
交互性
三维可视化要有可以改变三维模型观察点的功能 (坐标系统的旋转),所以我们需要创建功能来避免需要使用额外的控制面板或者类似的东西。严格说来,MQL5 语言中的图形对象已经有了用于交互的属性: 您可以选择一个对象,移动它,修改它的属性,等等。我们只需要稍微增强这样的属性来启用收集管理和交互,例如,如果我们改变了坐标的中心点,所有相关对象都必须自动地和正确地重新排布。
如果我们提供了所有这些需求,我们的三维对象就将变成一个可交互图形对象(interactive graphical object,IGO)。可交互图形对象必须与 MQL5 图形对象相关联,让我们从可交互图形对象的基类 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 事件。
该方法处理当操作图表时来自客户终端的事件,此方法要回应四个标准事件: 删除图形对象, 修改图表大小或者属性,移动图形对象或者点击它,让我们详细讨论其中的每个事件。
- 删除一个图形对象。当这个事件发生时,图表上就不存在这个对象了 — 它已经被删除了。但是我们有保存的需求,所以对象必须被立刻恢复,也就是说,对象必须在删除之前做出处理,以同样的属性重新创建。注意: 图形对象是在这里删除的,不是CIGO类关联的实例,类的实例还是继续存在,并且它保存着被删除的图形对象的信息,所以我们可以轻松通过使用 Create() 方法来恢复对象。
- 修改图表对象或者属性。有许多这种类型的事件,包括新柱的出现,图表缩放的改变,时段的切换以及其它等等。回应这样的事件应该要根据更新后的环境重绘对象,使用的是 Redraw() 方法。请注意,当您切换到另一个时段时,类的实例会重新初始化并且会丢失保存在类栏位中的所创建的图形对象的数据,尽管图形对象依然存在于图表上。所以使用图形对象的属性来恢复类实例的栏位,就能够保存 IGO。
- 移动一个图形对象。图形对象的交互性就是基于此属性的,当对象被移动时,它的基础锚点就改变了,在移动对象之前,我们需要选择它(通过鼠标双击),如果这个事件发生了,方法就返回 true, 否则返回 false。当我们对交互图形对象进行一系列操作时,我们需要这个值。请注意,如果我们需要一个不能移动的图形对象,我们应该在创建对象时禁止它能够被选中。
- 在图形对象上点击一次. 如果鼠标在其它对象上点击了,该对象应该取消被选择,以免意外移动,这样,在一个图表上只有一个 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 栏位的值。
让我解释一下通过本方法改变了基础锚点坐标会发生什么,
- 在类的初始化过程中,m_price栏位 (基础点坐标)不包含任何输入数值,所以 m_price=NULL,当类实例被创建时,或者当图表的时段切换的时候,都会进行类的初始化,图形对象可能不在图表上,也可能存在于图表上,因为之前有程序调用或者在切换时段之后,所以图形对象的对应属性的值要赋给 m_price 栏位,
- 如果图形对象的名称 m_name 不存在,基础锚点的坐标也就在第一步之后没有被定义: m_price=NULL,在这种情况下,m_price 栏位设为默认的 def 数值,
{
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);
}
本来是打算使用"趋势线"的子类来用作 X 和 Y 轴,但是很清楚的是 "Trendline by angle(角度趋势线)" 是完成这个目标的完美方案。在阅读MQL5语言文档的时候我发现这个强大的工具: 当绘制角度趋势线的时候,就算时段切换或者图表的缩放改变的时候,角度还是保持不变。
结论: 仔细阅读文档,您将会发现很多有用的函数和工具。
图形内存 (GM)
文章"直方图形式的统计分布, 无需指标缓冲区和数组" 文章中提供了图形内存描述和一些实例,在此我就重复一下基本原则: 图形对象的属性可以保存所需的信息在其它对象和程序函数中使用。
下面的类的创建就是为了更简单地编程和使用图形内存:
{
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类的实例可以实现从 A 到 B 重要数据的传输;
- 而在创建B 对象的时候, 也会创建BB的图形内存类实例,现在 A 对象就可以访问 B 对象的所需信息了;
- 等等。对象的数量没有限制,而每个对象都可以使用图形界面类的实例来收到和发送信息。
图形内存可以类比为一个公告板,在其中可以提供某些信息以及发布通知。这种方法可以提供交互图形对象之间数据的交换。在三维建模设计的时候,需要哪些数据来正确进行3D对象数据的编程呢,最重要的信息就是对象的坐标系统,因为当旋转或者移动坐标的中心时,整个三维对象"包"都回改变属性和它们在三维空间的位置,
参见在交互坐标系统类CUSC中图形内存是怎样使用的。
坐标系统
三维可视化分析使您可以在三维空间中分析数据: 例如,为一个或多个变量的源数据序列(观察)构造一个三维图片,所限的变量通过X轴表示, 观察结果由Y轴表示,而变量值(用于此观察)沿着 Z 轴绘出。
这样的三维图表可以用于显示多个变量的序列值。
与二维多线形图相比,三维表示的主要优点是,使用三维图片可以更容易发现数据序列中的孤立的值。通过使用正确的角度,它可以通过使用交互式的旋转来选择,图表线不会相互覆盖也不会像在二维多线形图中那样压线。
在我们进行三维建模之前,让我们看一下坐标系统,我们必须在其中进行操作。MQL5语言为开发者提供了 "Chart(图表)" 和 "Canvas(画布)"两种坐标系统,我们将再实现一种三维系统 (一种轴侧投影)。所以,让我们探讨它们每一个的差别和目标。
图 2. "Chart(图表)"坐标系统。
"图表"坐标系统 (图2)是一种二维坐标系统,用于显示价格数据,时间序列和指标。水平轴上是时间尺度,它是从左向右的,垂直轴表示金融资产的价格,大多数 MQL5 图形对象是运行于此坐标系统中的,当前的价格和柱形位于相同的垂直轴上,而当新柱出现时,图表自动向左边移动。
图 3. "Canvas(画布)"坐标系统。
"画布"坐标系统(图 3). 屏幕上的每个点对应着一个像素,点的坐标是从图表的左上角开始计数的,这个系统也是二维的,它用于不与时间序列关联的对象,在本文中我们将不会使用这个系统。
图 4. 三维坐标系统。
三维坐标系统(图 4). 在这种坐标系统中,有三个彼此垂直的轴,然而,在"画布"坐标系统中XYZ轴之间的夹角看起来不等于90度,比如, X 和 Y 形成的夹角是120度,夹角可能会不同,但是在本文中我们会使用上述值。
Z轴关联着当前柱,而轴的尺度对应着"图表"坐标系统的"价格"尺度,这很方便,因为不需要单独为Z轴创建一个独立的尺度,我们可以用"价格"尺度代替,所以 Z=price。
X 轴的方向是从左向右的,也就是说和"图表"坐标系统中的"时间"尺度相反。Bar(柱)变量的值将在本文后面介绍;所以任何柱在X轴上的投射都等于它的值 (X=Bar),
Y 轴用于二维XZ数据序列,例如,我们可以在这个轴上画出b1>Open(开盘价), Close(收盘价), High(最高价) 和 Low(最低价)时间序列的线,而每条线都位于独立的平面上,通过连接这些线在与YZ平面平行的点,我们将得到一个网格,也就是说,一个三维时间序列对象(参见图5)。
图 5. 三维坐标系统中的一个3D对象。
交互的坐标系统: Class CUSC
派生于 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轴旋转,并且可以从初始点移动。
{
//--- 三维坐标系统的中心
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 方法: 处理到来的消息
该方法处理当操作图表时来自客户终端的事件,该方法只回应一个标准事件: 移动图形对象。其它的时间会随后转发到坐标系统编程中的类的所有实例。
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点的坐标。
{
datetime time; // "图表"系统中的一个坐标
double price; // "图表"系统中的一个坐标
};
一个sPointCoordinates类型的变量可以在一个Z()函数的调用中取得一个3D点在图表坐标系统的坐标。
Z 方法: 计算 Z 坐标。
计算 Z 坐标的方法。
{
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表面
作为实例,让我们探讨一下基于以下函数构建一个三维平面:
#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表面。
图 7. 绘制一个3D移动平均.
这里是对怀疑者的回答:为什么我们需要三维建模并且它能如何帮助交易呢?
当然,MetaTrader 5 终端是设计用于在金融市场上做交易的,交易者和自动交易系统的开发者主要的兴趣在于交易算法和创建获利的策略,我们讨论的技术还在它们的婴儿期,并不能给您带来特别的交易结果,但是三维建模可以用于帮您向潜在投资者展示您的交易想法和策略。另外,终端允许创建反应市场信息流的动态三维模型,以及随着时间改变它们的外观(通过重建), 这样就可以创建3D指标。
这和Windows类似: 第一个版本的系统很慢并且弱,Norton Commander 的爱好者还质疑过图形界面,现在 NC 又在哪里了呢?
结论
- 三维建模在任何编程环境中都是一项比较困难的任务,但是 MQL5 语言的开发者可以积极一些,他们已经给使用者提供了强大的功能,可以在交易终端中实现三维显示。
- 我们已经得到了3D对象类编程的第一个开发库,三维建模的范围很广,一篇文章是无法全面介绍它的功能的。希望三维建模的追随者能够建议在交易中和在MQL5编程中3D开发的方向。
- 本文中创建的三维建模工具对技术分析的新方向的开发可能有很大影响: 三维指标和它们的分析。
- 渲染的速度还有些问题,但是它们在本阶段的库的开发中不是关键。
- 本文中提出的3D对象创建的原则可能可以迁移到 OpenCL。
注意事项:
库文件应该保存到 ..\Include\3D 文件夹。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/2828
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。




对文章中的例子做了一些更正。
太好了!
我正好需要 3D 建模。
你能告诉我如何把这些都拧到 MT4 上吗?
我知道了。3D 建模的亮点在于按角度 使用趋势线。
太好了!
我正好需要 3D 建模。
你能告诉我如何把这些都拧到 MT4 上吗?
我知道了。三维建模的亮点是按角度 使用趋势线。
MT4 也可以添加这种功能,但我还不明白这有什么意义。即使在 MT5 中,ObjectGetValueByTime() 函数中的坐标计算也不够精确。
3D 和Tick 成交量 分析
MachineLearning EA
所有代码都不适合放在存档中
这里是指向 github 的链接
指标示例
https://github.com/zephyrr/MLEA/tree/master/MQL5/Indicators/Examples
专家示例
https://github.com/zephyrrr/MLEA/tree/master/MQL5/Experts
导出到 MySQL
https://github.com/xenu256/MT4toMySQL
关于诺贝尔经济学奖得主的 "奇迹
https://www.youtube.com/watch?v=OmDhTUYkkdE& t=388s