程序库: OnTickMulti - 页 5

 
Stanislav Korotky #:

为了编写一个完整的测试用例,我需要了解具体的实际应用场景——我们是否只根据时间精确到毫秒的交易信号进行交易?

不是。目标是始终确保所有交易品种的环境信息都是最新的。在我给出的示例中,OnTimer中始终满足实时性条件。开仓操作仅用于演示。
 
fxsaber #:
不。目标是始终确保所有指标的当前状态。在我给出的示例中,OnTimer中始终满足当前状态的条件。开仓操作仅作为演示。

好的。

目前尚不清楚,在 OnTickMulti 事件中的实现为何会“丢失”一个 tick,或者换句话说, 为什么 SymbolInfoTick 返回的价格与预期不符(我之前推测,这可能是由于指标中的自定义事件调度导致的延迟) ——为了确保实验的纯净性,我会尝试在不使用“间谍”指标的情况下进行测试——仅通过常规的 OnTick 事件对所有符号调用 SymbolInfoTick/CopyTicks,至少针对相同的戳时间。

至于 OnTimer,我对此有些疑问(如有错误,请指正):

void OnTimer()
{
  if (TimeMsc++ == 1759280400081) // 2025.10.01 01:00:00.081
    PositionOpen();
}

我们无法保证处理程序能在1毫秒内执行完毕,因此仅对计数器进行简单递增并不能保证保持初始同步,也就是说 与服务器时间的校验必须每次都严格在运行时实时进行(在开仓后,或进行其他比计数器递增更复杂的计算之后,如果存在此类情况的话)。 换言之,当需要开立大量头寸时,上述优化方案将行不通。

此外,初始同步(计数器的初始化)——在我看来——也并非100%完美。

ulong TimeMsc = TimeTradeServer() * 1000 + (inTimer && EventSetMillisecondTimer(1));

假设在测试环境中,服务器时间确实不包含毫秒,但这样的代码在在线环境中如何运行?为什么我们要加1毫秒?我还是会从时钟 tick 中获取服务器时间。

这并非吹毛求疵,只是对“时效性条件始终成立”这一假设产生了一些疑虑。

 
Stanislav Korotky #:

仅对计数器进行递增并不能保证保持最初的同步状态

在测试环境中可以保证。

但这样的代码在在线环境中如何运行?

在线环境中不存在此类问题,因为发送到终端的所有数据都是参考性的:由于延迟,这些数据已不再准确。

那么,为什么我们要增加1毫秒呢?

因为第一个 OnTimer 不会立即触发,而是在设定的间隔后才会被调用。

 
Stanislav Korotky #:

目前尚不清楚,OnTickMulti 事件中的代码在何处“丢失”了 tick,或者换句话说,为何 SymbolInfoTick 返回的价格与预期不符

OnTickMulti 肯定与此无关,因为某个符号的内置 SymbolInfoTick 函数会返回正确的 tick 数据,而对于其他符号则不会。


原因仅在于 tick 数据的传输顺序。

关于交易、自动交易系统及交易策略测试的论坛

库:OnTickMulti

fxsaber,2025年9月30日 09:24

时间相同的报价数据不会同时到达,而是依次到达。如果时间较早的欧元/美元(EURUSD)报价数据先到达,那么此时还无法得知时间相同的英镑/美元(GBPUSD)报价数据(它将作为第二条到达)的具体情况。 因此,在第一个EURUSD报价到达时,第二个GBPUSD报价根本不存在,只有前一个GBPUSD报价的数据。
 
fxsaber #:

因为第一个 OnTimer 将在指定的间隔后被调用,而不是立即被调用。

我设计了一种快速更新所有数据的方法。

#include <fxsaber\OnTickMulti\OnTickMulti.mqh> //https://www.mql5.com/zh/code/47647

#include <MT4Orders.mqh> //https://www.mql5.com/zh/code/16006

void PositionOpen()
{
  for (uint i = ArraySize(OnTickMultiObject.Symbols); (bool)i--;)
  {
    const string SymbName = OnTickMultiObject.Symbols[i];
    
    OrderSend(SymbName, OP_BUY, 1, SymbolInfoDouble(SymbName, SYMBOL_ASK), 0, 0, 0);
  }
}

long CurrentTime = 0;

// 多字符 OnTick。
void OnTickMulti( const string &Symb, const uint &Index )
{  
  MqlTick Tick;
  
  if (SymbolInfoTick(Symb, Tick) && (Tick.time_msc > CurrentTime))
  {
    CurrentTime = Tick.time_msc;
    
    EventSetMillisecondTimer(1);
  }  
}

void OnTimer()
{
  if (CurrentTime == 1759280400081) // 2025.10.01 01:00:00.081
    PositionOpen();
    
  EventKillTimer();
}

这种机制使得即使在普通模式下(单币种模式且未启用 OnTickMulti),也能仅处理最新数据。例如,EURUSD 存在多个时间相同的报价点。通过数据刷新,系统将处理最新的一条报价点——即该序列中的最后一条。


附注:这也是创建自定义符号的 另一个原因——仅将时间相同的连续 tick 中最后一个写入历史数据。这样一来,在单币种模式下将始终保持数据更新。

Библиотеки: TicksShort
Библиотеки: TicksShort
  • 2025.09.26
  • www.mql5.com
Статьи и техническая библиотека по автоматическому трейдингу: Библиотеки: TicksShort
 
Rorschach #:

但开盘价其实是一样的,尽管所有股票的开盘时间都相同。

正因为这种行为,导致一些系统无法进行正常的测试。

我重新回顾了一下开盘价测试模式。关键在于,尽管名称如此,但在该模式下,测试器会生成4个OHLC tick,而不是从名称上直观预期的1个。 在这4个控制点中,对于专家顾问,仅提取第一个O并调用OnTick;而对于指标,还会根据K线方向,针对HLC或LHC额外调用3次OnCalculate,并附带相应价格的Tick数据。 这三个额外数据点的时间被人为设定为与K线末尾的三秒时间相同。这意味着,间谍指标会针对每个符号发送多个事件,而非仅发送一个。对于使用开盘价模式的用户来说,可能需要考虑这一点。

此外,我还观察到一个异常现象(在根据书中内容编写的类似“间谍”指标中添加了调试功能):来自上一根K线的新增符号的Tick事件不知为何会在新K线上重复出现, 随后才触发监视器的 OnCalculate 方法,并收到包含更新后价格的附加符号新 tick 事件。 因此,为了获得附加符号的最新价格,需要在事件本身中包含时间戳,并且不要重复处理已经处理过的事件。我在发送时是这样做的:

int OnCalculate(const int rates_total, const int prev_calculated, const int, const double &price[])
{
   MqlTick tick;
   SymbolInfoTick(_Symbol, tick);
   EventChartCustom(Chart, Message, Index, (double)tick.time_msc, NULL);
  
   return rates_total;
}

而在接收时(此处展示的是单个附加符号的情况,若涉及多个附加符号,则需要使用 timestamp[] 数组!):

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   if(id == CHARTEVENT_CUSTOM + Message)
   {
      static long timestamp;
      long event = (long)dparam;
      if(event > timestamp)
      {
         OnSymbolTick((int)lparam);
         timestamp = event;
      }
   }
}

需要注意的是,当存在毫秒值相同的报价时,系统会优先处理第一个报价,而非最后一个。

 
Rorschach #:
最简单的同步方法
本以为时间序列同步是一个已被深入研究的问题,应该有现成的算法,结果上网一查,却一无所获