屏幕坐标与时间/价格的相互转换

由于图表工作区域的测量原理不同,因此需要在不同的测量单位之间进行重新计算。为此提供了两个函数。

bool ChartTimePriceToXY(long chartId, int window, datetime time, double price, int &x, int &y)

bool ChartXYToTimePrice(long chartId, int x, int y, int &window, datetime &time, double &price)

ChartTimePriceToXY函数将图表坐标从时间/价格表示(time/price)转换为以像素为单位的 X 和 Y 坐标(x/y)。ChartXYToTimePrice函数执行相反的操作:将 X 和 Y 坐标转换为时间和价格值。

这两个函数都需要在第一个参数chartId中指定图表 ID。此外,在 ChartTimePriceToXY中还需传入window 子窗口编号(该编号应在窗口数量范围内)。如果存在多个子窗口,每个子窗口都有各自的时间序列和沿垂直轴的刻度(根据参数条件,price参数称为"price")。

ChartXYToTimePrice函数中,window 参数是输出参数。该函数会将此参数与 timeprice 一同填充。这是因为像素坐标是整个屏幕通用的,其原点 x/y可能位于任意子窗口中。

时间、价格与屏幕坐标

时间、价格与屏幕坐标

如果成功完成,函数返回 true

请注意,在两种坐标系中,对应报价或屏幕坐标的可见矩形区域均有范围限制。因此,在特定初始数据下,可能会出现所获取的时间、价格或像素超出可见区域的情况。特别是,也可能获得负值。我们将在 图表事件一章中了解交互式重新计算示例。

在上一节中,我们了解了如何确定 MQL 程序的启动位置。尽管在物理上只有一个最终放置点,但其在报价坐标和屏幕坐标中的表示通常存在计算误差。两个新函数用于像素和价格/时间的双向转换,它们将帮助我们验证这一点。

修改后的脚本名为ChartXY.mq5。它大致可以分为 3 个阶段。在第一阶段,我们将像之前一样导出放置点的坐标。

void OnStart()
{
   const int w1 = PRTF(ChartWindowOnDropped());
   const datetime t1 = PRTF(ChartTimeOnDropped());
   const double p1 = PRTF(ChartPriceOnDropped());
   const int x1 = PRTF(ChartXOnDropped());
   const int y1 = PRTF(ChartYOnDropped());
   ...

在第二阶段,我们尝试在时间 (t2) 和价格 (p2) 期间转换屏幕坐标 x1y1,并将其与从上述 OnDropped 函数中获得的坐标进行比较。

   int w2;
   datetime t2;
   double p2;
   PRTF(ChartXYToTimePrice(0x1y1w2t2p2));
   Print(w2" "p2" "t2);
   PRTF(w1 == w2 && t1 == t2 && p1 == p2);
   ...

然后我们进行逆变换:使用获得的报价坐标 t1p1 来计算屏幕坐标 x2y2,并将其与原始值 x1y1 进行比较。

   int x2y2;
   PRTF(ChartTimePriceToXY(0w1t1p1x2y2));
   Print(x2" "y2);
   PRTF(x1 == x2 && y1 == y2);
   ...

正如我们将在后续示例日志中看到的,上述所有检查都将失败(值之间会存在细微差异)。所以,我们需要第三步。

我们重新计算变量名中带有后缀 2 的屏幕坐标和报价坐标,并将它们保存到带有新后缀 3 的变量中。然后,我们将第一阶段和第三阶段的所有值相互进行比较。

   int w3;
   datetime t3;
   double p3;
   PRTF(ChartXYToTimePrice(0x2y2w3t3p3));
   Print(w3" "p3" "t3);
   PRTF(w1 == w3 && t1 == t3 && p1 == p3);
   
   int x3y3;
   PRTF(ChartTimePriceToXY(0w2t2p2x3y3));
   Print(x3" "y3);
   PRTF(x1 == x3 && y1 == y3);
}

让我们在 XAUUSD, H1 图表上运行该脚本。以下是原始点数据。

ChartWindowOnDropped()=0 / ok
ChartTimeOnDropped()=2021.11.22 18:00:00 / ok
ChartPriceOnDropped()=1797.7 / ok
ChartXOnDropped()=234 / ok
ChartYOnDropped()=280 / ok

将像素转换为报价将得出以下结果。

ChartXYToTimePrice(0,x1,y1,w2,t2,p2)=true / ok
0 1797.16 2021.11.22 18:30:00
w1==w2&&t1==t2&&p1==p2=false / ok

在时间和价格上均存在差异。反向计算在精度方面也并非完美。

ChartTimePriceToXY(0,w1,t1,p1,x2,y2)=true / ok
232 278
x1==x2&&y1==y2=false / ok

精度损失是由于坐标轴上的值按照测量单位(尤其是像素和点)进行量化所导致的。

最后,最后一步证明上述误差并非函数本身的问题,因为循环重新计算会得到原始结果。

ChartXYToTimePrice(0,x2,y2,w3,t3,p3)=true / ok
0 1797.7 2021.11.22 18:00:00
w1==w3&&t1==t3&&p1==p3=true / ok
ChartTimePriceToXY(0,w2,t2,p2,x3,y3)=true / ok
234 280
x1==x3&&y1==y3=true / ok

在伪代码中,这可以通过以下等式表示:

ChartTimePriceToXY(ChartXYToTimePrice(XY)) = XY
ChartXYToTimePrice(ChartTimePriceToXY(TP)) = TP

ChartTimePriceToXY函数应用于ChartXYToTimePrice 的计算结果时,将得到原始坐标。反向转换同样如此:将 ChartXYToTimePrice函数应用于 ChartTimePriceToXY 的计算结果时,将完全匹配。

因此,如果对使用重新计算函数的算法有更高的精度要求,应谨慎考量这些算法的实现方式。

另一个使用 ChartWindowOnDropped的示例将在脚本 ChartIndicatorMove.mq5 中提供(见 管理图表上的指标章节)。