在MQL5中的三维建模·进阶篇
📘

在MQL5中的三维建模·进阶篇

第 2/2 篇

用角度趋势线当坐标轴的底层类

在 MT5 里想拿子类趋势线当 X/Y 轴基准,普通趋势线会被缩放和周期切换搞乱斜率;而 OBJ_TRENDBYANGLE 这类带角度的对象有个硬特性:图表缩放、切换 timeframe 时,设定好的角度恒定不变。这一点在官方对象类型说明里写得很清楚,也是把它选作坐标轴载体的关键理由。 下面这段 C_OBJ_TRENDBYANGLE 派生于 CIGO,核心是把角度趋势线封装成可创建、重绘、响应图表事件的类。m_bar 存的是第二锚点对应的柱编号,SetAngle 优先读对象已有角度,读不到才用传入默认值兜底。 [CODE]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 bool OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam,int iparamemr=0,double dparametr=0.0); };[/CODE] 构造函数把 m_bar 初始化为 c_bar(当前柱);Create 方法用 ObjectCreate 建 OBJ_TRENDBYANGLE,两端都开射线(RAY_RIGHT/RAY_LEFT 为 true),并靠 ObjectSetDouble 的 OBJPROP_ANGLE 让第二点坐标随角度自动算。Redraw 在图表变动时重设时间与价格锚点保角度。 OnEvent 处理了删除重建、缩放重绘、拖拽改角:拖线时实时取 OBJPROP_ANGLE 回写 m_angle 再 Create。外汇与贵金属图表用这套封装时波动剧烈,角度线仅作参考,实际触发信号概率随品种流动性变化。 别把文档当闲篇 MQL5 对象类型里像角度趋势线这种「抗缩放」属性,不翻文档很容易错过;自己写指标前先扫一遍对象枚举,可能省掉重写坐标逻辑的半天工。

MQL5 / C++
class C_OBJ_TRENDBYANGLE: class="kw">public CIGO
  {
 class="kw">protected:
   class="type">int                m_bar;   class=class="str">"cmt">// 关联第二个基本点的柱的编号
   class="type">class="kw">double SetAngle(class="type">class="kw">double def)
     {
      if(m_angle==NULL) class=class="str">"cmt">// 如果变量没有值
        m_angle=ObjectGetDouble(class="num">0,m_name,OBJPROP_ANGLE);
      if(m_angle==NULL)       class=class="str">"cmt">// 如果没有坐标
        m_angle=def;         class=class="str">"cmt">// 默认值
      class="kw">return(m_angle);
     }
 class="kw">public:
                     C_OBJ_TRENDBYANGLE();
   class="kw">virtual   class="type">void     Create(class="type">class="kw">string name,class="type">class="kw">double price,class="type">class="kw">double angle);
   class="kw">virtual   class="type">void     Redraw();
   class="kw">virtual   class="type">bool     OnEvent(class="kw">const class="type">int id,class="kw">const class="type">long &lparam,class="kw">const class="type">class="kw">double &dparam,class="kw">const class="type">class="kw">string &sparam,class="type">int iparamemr=class="num">0,class="type">class="kw">double dparametr=class="num">0.0);
  };
C_OBJ_TRENDBYANGLE::C_OBJ_TRENDBYANGLE()
  {
   m_bar=c_bar;
  }
class="type">void C_OBJ_TRENDBYANGLE::Create(class="type">class="kw">string name,class="type">class="kw">double price,class="type">class="kw">double angle)
  {
   class="type">class="kw">datetime time=T(class="num">0);
   class="type">class="kw">datetime deltaT=T(m_bar)-time;
   m_name=name;
   m_price=SetPrice(price);
   m_angle=SetAngle(angle);
   ObjectCreate(class="num">0,m_name,OBJ_TRENDBYANGLE,class="num">0,time,m_price,time+deltaT,m_price);
   ObjectSetInteger(class="num">0,m_name,OBJPROP_COLOR,clrISO);
   ObjectSetInteger(class="num">0,m_name,OBJPROP_WIDTH,class="num">0);
   ObjectSetInteger(class="num">0,m_name,OBJPROP_STYLE,styleISO);
   ObjectSetInteger(class="num">0,m_name,OBJPROP_RAY_RIGHT,true);
   ObjectSetInteger(class="num">0,m_name,OBJPROP_RAY_LEFT,true);
   ObjectSetInteger(class="num">0,m_name,OBJPROP_BACK,true);
   ObjectSetInteger(class="num">0,m_name,OBJPROP_SELECTABLE,true);
   ObjectSetDouble(class="num">0,m_name,OBJPROP_ANGLE,m_angle);
   ChartRedraw();
   on_event=true;
  }
class="type">void C_OBJ_TRENDBYANGLE::Redraw()
  {
   ObjectSetInteger(class="num">0,m_name,OBJPROP_TIME,T(class="num">0));
   ObjectSetInteger(class="num">0,m_name,OBJPROP_TIME,class="num">1,T(class="num">0)+T(m_bar)-T(class="num">0));
   ObjectSetDouble(class="num">0,m_name,OBJPROP_PRICE,m_price);
   ObjectSetDouble(class="num">0,m_name,OBJPROP_ANGLE,m_angle);
   ChartRedraw();
  }
class="type">bool C_OBJ_TRENDBYANGLE::OnEvent(class="kw">const class="type">int id,class="kw">const class="type">long &lparam,class="kw">const class="type">class="kw">double &dparam,class="kw">const class="type">class="kw">string &sparam,class="type">int iparamemr=class="num">0,class="type">class="kw">double dparametr=class="num">0.0)
  {
   class="type">bool res=class="kw">false;
   if(on_event)
     {
      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(class="num">0,sparam,OBJPROP_SELECTED)==class="num">1 && sparam==m_name)
           {m_angle=ObjectGetDouble(class="num">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(class="num">0,m_name,OBJPROP_SELECTED,class="num">0);ChartRedraw();}
     }
   class="kw">return(res);
  }

「用图形内存打通对象间数据」

MT5 里图形对象的属性本身就能当存储区用,不必另开指标缓冲区和数组。把对象名交给一个轻量类,就能随时读 OBJPROP_PRICE、OBJPROP_TIME、OBJPROP_ANGLE 这些字段,在别的对象或程序函数里复用。 下面这个类把常用读写封装好了,关联一个图形对象名即可。它不持有数据,只做属性桥接: 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); } }; 逐行看:m_name 存的是图形对象名;Create() 把类绑定到具体对象。R_OBJPROP_PRICE 用 ObjectGetDouble 取价格坐标,prop_modifier 可定位多节点对象里的第几个点。R_OBJPROP_TIME 借 ObjectGetInteger 拿时间,并强转为 datetime。R_OBJPROP_ANGLE 读旋转角,R_OBJPROP_TEXT 读文本,R_ValueByTime 按时间反查值,W_OBJPROP_TEXT 写文本——全程零数组。 设想建了 3D 对象 A,绑定实例 AA;再建 B,绑定 BB。B 通过 AA 调 A 的属性,A 通过 BB 取 B 的状态,两者无直接引用却完成交换。对象数量不设限,每个都能借图形内存收发信息,像块公共公告板。 做三维建模时,最要紧的是坐标系统:旋转或平移中心,整组 3D 对象包的位置属性全变。交互坐标系统类 CUSC 里就用了这套图形内存来同步坐标,开 MT5 把上面类贴进 EA,建两个对象互读角度,能直接验证传输是否通。外汇与贵金属杠杆高,这类对象逻辑仅用于界面与辅助计算,不替代风控。

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

◍ 先理清三种坐标系再谈建模

做三维可视化之前,得先认清楚 MT5 里实际存在的几套坐标参照。MQL5 原生提供「图表」和「画布」两套二维系统,我们要自建的是第三套——轴侧投影的三维系统,三者目标不同,混用会直接画歪。 「图表」坐标系统是价格数据的老家:横轴从左向右是时间尺度,纵轴是金融资产价格,绝大多数 MQL5 图形对象都跑在这套里;新柱生成时整张图自动左移,当前价和柱体共用一条纵轴。 「画布」坐标系统以屏幕像素计点,原点在图表左上角,也是二维,但专门伺候那些不跟时间序列绑定的对象。本文的三维建模用不到它,知道有这回事就行。 自建的三维系统里三根轴彼此垂直,但在画布投影上 X 与 Y 夹角取 120 度(本文固定此值,非真实 90 度视觉)。Z 轴直接复用图表系统的价格尺度,即 Z=price,省去单独建尺度;X 轴方向和图表时间轴相反,任一根柱在 X 上的投影等于它的 Bar 值(X=Bar)。Y 轴用来铺 Open/Close/High/Low 等独立平面序列,平行 YZ 面连线后就能织出三维时间序列网格。 外汇与贵金属杠杆高、跳空频繁,三维网格里孤立值虽更易肉眼抓出,但轴侧夹角导致的视觉压缩可能放大误判,上机前先在历史数据回放里转几个角度验证。

在MT5里搭一套可旋转的三维坐标类

CUSC 这个类从 CIGO 派生,本质是把图表上的价格与柱索引映射成一套带 Z 轴的空间坐标,并且支持鼠标拖动中心、绕 Z 轴转视角。它内部挂了 C_OBJ_ARROW_RIGHT_PRICE 做中心锚点,C_OBJ_TREND 做 Z 轴,两个 C_OBJ_TRENDBYANGLE 分别当 Y、X 轴,Y 轴写死 30 度夹角,X 轴则在 Y 轴角度上再加一个 ISO 常量,等距投影的味道很重。 Create() 不返回任何值,只负责把上述对象逐个建出来并写进图形内存(CGM)。关键几行是 gCentr.R_OBJPROP_PRICE() 取中心价格、AxisY 建完立刻读回 m_angle,最后 on_event=true 放开事件响应。你在 MT5 里调这类代码时,若坐标轴不出来,先查 ChartRedraw() 是否被调用以及 on_event 有没有置位。 [CODE] 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度

gAxisY.Create(AxisY.m_name); // 创建一个图形内存对象 m_angle=gAxisY.R_OBJPROP_ANGLE(); // 读回Y轴角度 //--- 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 ; // 允许处理事件 } [/CODE] OnEvent() 只认标准图表事件里的“移动图形对象”。中心被拖了,就带着 gCentr 的价格去更新 Y、X 轴;动的是 Y 轴,则把 X 轴角度重算为 Y 角加 ISO,反过来动 X 轴就减 ISO,保证两轴相对夹角不变。外汇与贵金属行情跳空多,这类拖拽式坐标在实盘高波动时段可能因为点差和重绘延迟出现轻微错位,验证时建议先开 EURUSD 的 M5 慢行情跑通。 Z() 是真正算空间落点的函数:用 gAxisX.R_ValueByTime(T(barX)) 减中心价得 dX,gAxisY 同理得 dY,返回的价格 = 原 price + dX - dY,时间则直接取 T(barX-barY)。也就是说,一个 3D 点若 barX=10、barY=4,时间轴会落到当前柱往前 6 根的位置,价格再叠两次轴向偏移。 [CODE] 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; // X轴方向相对中心的价格偏移 dY= 0 ; dY=gAxisY.R_ValueByTime(T(-barY))-m_price; // Y轴方向相对中心的价格偏移 res.price=price+dX-dY; // 最终价格坐标 res.time=T(barX-barY); // 最终时间坐标 return (res); } [/CODE] sPointCoordinates 就两个字段:datetime time 与 double price,足够把“第几根柱、什么价”的图表坐标和这套伪三维系统接起来。想要快速验证,直接 new 一个 CUSC 实例、Create 后拖两下中心,再对几个 barX/barY 组合打印 Z() 的返回值,就能看清投影逻辑。

MQL5 / C++
class CUSC: class="kw">public CIGO   { class="kw">private :    class="type">class="kw">datetime           m_prev_bar;    class="type">class="kw">datetime           m_next_bar;    class=class="str">"cmt">//---    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 类的实例 class="kw">public :                     CUSC();                    ~CUSC();    //--- 计算 Z 坐标    sPointCoordinates Z( class="type">class="kw">double price,   // "图表"坐标系统的价格                      class="type">int barX,       // 在X轴上的偏移                      class="type">int barY);       // 在Y轴上的偏移    //--- 新柱    class="type">bool on_bar()      {       m_next_bar=T( class="num">0 );        if (m_next_bar>m_prev_bar)         {          m_prev_bar=m_next_bar;          class="kw">return ( true );         }        class="kw">return ( class="kw">false );      }    //---    class="kw">virtual      // 方法: 创建一个 IGO 对象    class="type">void               Create( class="type">class="kw">string name);    class="kw">virtual      // OnChartEvent 处理方法    class="type">void               OnEvent( class="kw">const class="type">int id,                             class="kw">const class="type">long &lparam,                             class="kw">const class="type">class="kw">double &dparam,                             class="kw">const class="type">class="kw">string &sparam);   };

「用网格函数在MT5画出3D表面」

想直观验证一套自写类的绘图能力,最省事的办法是用一个无实际含义的演示函数:fun[i][j]=close[i*StepX]-_Point*j*j。它只是把收盘价按X轴步长抽点,再沿Y轴用最小报价单位做二次衰减,纯粹用来跑通3D网格的生成逻辑。 代码里把X轴设为50个点、步长10根K线,Y轴15个点、步长5根K线,也就是整张网覆盖约500根历史柱。运行指标后,绕Z轴旋转视角能看到红蓝分色:节点值高于坐标中心的显示红色,低于的显示蓝色,移动中心即换色。 X向和Y向的连线是分开画的,X线按函数值高于中心与否染红或蓝,首尾两条加粗到宽度3;Y线统一绿色宽度1。你可以直接改_Y或StepY,或者把那行被注释的 fun[i][j]=close[i*StepX]-_Point*j*j*i/7 放开,看曲面扭曲方式怎么变。 这类三维建模当前在外汇/贵金属实战里还偏展示性质,直接拿来出信号容易踩坑,市场高杠杆下模型看着漂亮不等于能扛回撤。它更适合做动态策略演示:随着Tick重建网格,相当于把资金曲线或波动率做成可旋转的3D指标给旁人看。

MQL5 / C++
class=class="str">"cmt">//--- 声明常数
class="macro">#define   StepX     class="num">10      class=class="str">"cmt">// 在X轴上的步长 [柱数]
class="macro">#define   StepY     class="num">5       class=class="str">"cmt">// 在Y轴上的步长 [柱数]
class="macro">#define   _X        class="num">50      class=class="str">"cmt">// 在X轴上的点的数量
class="macro">#define   _Y        class="num">15      class=class="str">"cmt">// 在Y轴上的点的数量
class="macro">#define   W1        class="num">1       class=class="str">"cmt">// 线的宽度
class="macro">#define   W2        class="num">3       class=class="str">"cmt">// 最后的线的宽度
class=class="str">"cmt">//--- 包含类的文件
class="macro">#include <3D\USC.mqh>
class=class="str">"cmt">//---
class="macro">#class="kw">property indicator_chart_window
class=class="str">"cmt">//--- 用于指标计算的缓冲区数量
class="macro">#class="kw">property indicator_buffers class="num">0
class=class="str">"cmt">//--- 指标的图形序列数量
class="macro">#class="kw">property indicator_plots   class="num">0
class=class="str">"cmt">//--- CUSC        USC;
class="type">class="kw">double fun[_X][_Y];
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标初始化函数                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   USC.Create("3D"); class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标迭代函数                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(class="kw">const class="type">int rates_total,
                class="kw">const class="type">int prev_calculated,
                class="kw">const class="type">class="kw">datetime &time[],
                class="kw">const class="type">class="kw">double &open[],
                class="kw">const class="type">class="kw">double &high[],
                class="kw">const class="type">class="kw">double &low[],
                class="kw">const class="type">class="kw">double &close[],
                class="kw">const class="type">long &tick_volume[],
                class="kw">const class="type">long &volume[],
                class="kw">const class="type">int &spread[])
  {
   ArraySetAsSeries(close,true);
class=class="str">"cmt">//--- 3D 表面函数
   for(class="type">int i=class="num">0;i<_X;i++)
       for(class="type">int j=class="num">0;j<_Y;j++)
        {
         fun[i][j]=close[i*StepX]-_Point*j*j;
         class=class="str">"cmt">//fun[i][j]=close[i*StepX]-_Point*j*j*i/class="num">7;
        }
class=class="str">"cmt">////--- X 线
   for(class="type">int i=class="num">1;i<_X;i++)
       for(class="type">int j=class="num">0;j<_Y;j++)
        {
         sPointCoordinates a0=USC.Z(fun[(i-class="num">1)][j], class=class="str">"cmt">// 函数
                                    (i-class="num">1)*StepX,   class=class="str">"cmt">// X
                                    -j*StepY);     class=class="str">"cmt">// Y
         sPointCoordinates a1=USC.Z(fun[i][j],     class=class="str">"cmt">// 函数
                                    i*StepX,       class=class="str">"cmt">// X
                                    -j*StepY);     class=class="str">"cmt">// Y
         class="type">class="kw">string name="line x "+"x"+(class="type">class="kw">string)i+"y"+(class="type">class="kw">string)+j;
         ObjectCreate(class="num">0,name,OBJ_TREND,class="num">0,a0.time,a0.price,a1.time,a1.price);
         if(fun[i][j]>USC.m_price && fun[i-class="num">1][j]>USC.m_price)
             ObjectSetInteger(class="num">0,name,OBJPROP_COLOR,clrRed);
         else
             ObjectSetInteger(class="num">0,name,OBJPROP_COLOR,clrBlue);
         ObjectSetInteger(class="num">0,name,OBJPROP_WIDTH,W1);
         if(j==class="num">0||j==_Y-class="num">1)
             ObjectSetInteger(class="num">0,name,OBJPROP_WIDTH,W2);
         ObjectSetInteger(class="num">0,name,OBJPROP_STYLE,STYLE_SOLID);
         ObjectSetInteger(class="num">0,name,OBJPROP_RAY_RIGHT,class="kw">false);
         ObjectSetInteger(class="num">0,name,OBJPROP_RAY_LEFT,class="kw">false);
         ObjectSetInteger(class="num">0,name,OBJPROP_BACK,true);
         ObjectSetInteger(class="num">0,name,OBJPROP_SELECTABLE,class="kw">false);
        }
class=class="str">"cmt">////--- Y 线
   for(class="type">int i=class="num">0;i<_X;i++)
       for(class="type">int j=class="num">1;j<_Y;j++)
        {
         sPointCoordinates a0=USC.Z(fun[i][j-class="num">1],   class=class="str">"cmt">// 函数
                                    i*StepX,       class=class="str">"cmt">// X
                                    -(j-class="num">1)*StepY); class=class="str">"cmt">// Y
         sPointCoordinates a1=USC.Z(fun[i][j],     class=class="str">"cmt">// 函数
                                    i*StepX,       class=class="str">"cmt">// X
                                    -j*StepY);     class=class="str">"cmt">// Y
         class="type">class="kw">string name="line y "+"x"+(class="type">class="kw">string)i+"y"+(class="type">class="kw">string)+j;
         ObjectCreate(class="num">0,name,OBJ_TREND,class="num">0,a0.time,a0.price,a1.time,a1.price);
         ObjectSetInteger(class="num">0,name,OBJPROP_COLOR,clrGreen);
         ObjectSetInteger(class="num">0,name,OBJPROP_WIDTH,class="num">1);
         ObjectSetInteger(class="num">0,name,OBJPROP_STYLE,STYLE_SOLID);
         ObjectSetInteger(class="num">0,name,OBJPROP_RAY_RIGHT,class="kw">false);
         ObjectSetInteger(class="num">0,name,OBJPROP_RAY_LEFT,class="kw">false);
         ObjectSetInteger(class="num">0,name,OBJPROP_BACK,true);
         ObjectSetInteger(class="num">0,name,OBJPROP_SELECTABLE,class="kw">false);
        }
class=class="str">"cmt">//--- 返回 prev_calculated 的值用于下一次调用
   class="kw">return(rates_total);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ChartEvent 函数                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(class="kw">const class="type">int id,
                  class="kw">const class="type">long &lparam,
                  class="kw">const class="type">class="kw">double &dparam,
                  class="kw">const class="type">class="kw">string &sparam)
  {
   USC.OnEvent(id,lparam,dparam,sparam);
  }

◍ 把三维库放进 Include 文件夹就能跑

MQL5 已经给了在交易终端里做三维显示的底子,本文给出的第一版 3D 对象类开发库把三维指标和分析的新方向往前推了一步,但渲染速度在这个阶段还不是关键瓶颈。 库文件要落到 ..\Include\3D 目录:GM.mqh(3.83 KB)、IGO.mqh(29.88 KB)、USC.mqh(14.9 KB)以及 testIGO.mq5(10.01 KB)都在这个包里,直接解压进去就能在 MT5 里编译测试。 有读者在讨论里提到,3D 建模的实用亮点可能是按角度挂趋势线,但即便在 MT5 里 ObjectGetValueByTime() 的坐标计算也还不够精确,外汇和贵金属行情受杠杆与跳空影响大,三维工具只是辅助视角,实盘前务必在策略测试器里跑一遍验证。 想试的人现在就可以开 MT5 把 testIGO.mq5 拖进编辑器,看三维对象能不能在你的品种上正常刷新,再决定要不要往自己的指标里接。

常见问题

先定义好以角度趋势线为基准的坐标转换方法,把空间点映射到两条趋势线构成的斜交系,再封装成独立类统一管理旋转与缩放。
用图形内存区存放对象间的公共参数,创建时写入、读取时直取,避免重复计算与跨对象传参混乱。
可以把代码贴给小布,它会按价格行为场景帮你梳坐标变换步骤,标出可能翻转或错位的地方,省去手动逐行验。
屏幕坐标、价格坐标、自建三维坐标混用会导致画出来的面扭曲或点击命中错位,先画张对照表再编码最稳。
确认主文件用 #include 引了正确路径,且库里所有对象创建函数都做了存在性判断,空对象直接调会静默失败。