调整自定义品种数据
为了实现我们的订单系统,赫兹量化交易软件最初需要三个基本值:最小交易量、最小跳价值和最小跳价大小。这些值类型中目前只实现了一种,且其实现并不完全符合要求,因为也许会发生未在配置文件中设置该值的情况。这令我们创建合成品种的工作复杂化,其仅涉及模拟可能的市场走势。
如果没有所需的调整,当我们以后操控该订单系统时,其中的数据也许会不一致。赫兹量化交易软件需要确保正确配置此数据。这将令我们尝试在系统中实现某些东西时避免出现问题,毕竟代码已经相当长了。因此,我们将开始纠正错误之处,从而避免下一阶段工作中的问题。如果我们有问题,就让它们具有不同的性质。订单系统实际上不会与创建市场回放/模拟服务的服务进行交互。我们会遇到的唯一信息是图表和品种名称,仅此而已。至少这是我现在的意图。我不知道我们是否真的会成功。
对于这种场景,我们要做的第一件事就是初始化我们绝对需要的三个值。不过,它们都会设置为零。我们按部就班地研究这个问题。首先,我们需要修复我们的问题。这是在以下代码中完成的:
C_Replay(const string szFileConfig) { m_ReplayCount = 0; m_dtPrevLoading = 0; m_Ticks.nTicks = 0; Print("************** Market Replay Service **************"); srand(GetTickCount()); GlobalVariableDel(def_GlobalVariableReplay); SymbolSelect(def_SymbolReplay, false); CustomSymbolDelete(def_SymbolReplay); CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay), _Symbol); CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX); CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX); SymbolSelect(def_SymbolReplay, true); CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0.0); CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation"); m_IdReplay = (SetSymbolReplay(szFileConfig) ? 0 : -1); }
在此,我们将初始值设置为零,但作为福利,赫兹量化交易软件还将提供自定义品种的描述。这不是必需的,但如果您打开一个包含品种列表的窗口,并看到一个独有名称的品种,这可能会很有趣。您大概已经注意到,我们将不再使用以前存在的变量。变量会在特定位置声明,如下面的代码所示:
class C_FileTicks { protected: struct st00 { MqlTick Info[]; MqlRates Rate[]; int nTicks, nRate; bool bTickReal; }m_Ticks; double m_PointsPerTick; private : int m_File;
现在,出现此变量的所有点都应引用品种中包含和定义的值。现在我们有了新代码,但基本上这个值在贯穿回放/模拟系统中的两处被提及。第一处如下所示:
inline long RandomWalk(long pIn, long pOut, const MqlRates &rate, MqlTick &tick[], int iMode) { double vStep, vNext, price, vHigh, vLow, PpT; char i0 = 0; PpT = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE); vNext = vStep = (pOut - pIn) / ((rate.high - rate.low) / PpT); vHigh = rate.high; vLow = rate.low; for (long c0 = pIn, c1 = 0, c2 = 0; c0 < pOut; c0++, c1++) { price = tick[c0 - 1].last + (PpT * ((rand() & 1) == 1 ? -1 : 1)); price = tick[c0].last = (price > vHigh ? price - PpT : (price < vLow ? price + PpT : price)); switch (iMode) { case 0: if (price == rate.close) return c0; break; case 1: i0 |= (price == rate.high ? 0x01 : 0); i0 |= (price == rate.low ? 0x02 : 0); vHigh = (i0 == 3 ? rate.high : vHigh); vLow = (i0 ==3 ? rate.low : vLow); break; case 2: break; } if ((int)floor(vNext) < c1) { if ((++c2) <= 3) continue; vNext += vStep; if (iMode == 2) { if ((c2 & 1) == 1) { if (rate.close > vLow) vLow += PpT; else vHigh -= PpT; }else { if (rate.close < vHigh) vHigh -= PpT; else vLow += PpT; } } else { if (rate.close > vLow) vLow = (i0 == 3 ? vLow : vLow + PpT); else vHigh = (i0 == 3 ? vHigh : vHigh - PpT); } } } return pOut; }
由于我们不想在多处重复相同的代码,因此我们使用局部变量来帮助我们。不过,原理是相同的:我们指的是品种内定义的值。该值引用的第二处位于 C_Replay 类之中。不过,出于实际原因,赫兹量化交易软件要做的事情与上面所示的内容略有不同,与我们创建随机游走时相反。在图表中显示和使用信息往往会降低性能,因为有太多不必要的调用。这是因为在创建随机游走期间,每根柱线将产生三次访问。
但是一旦它被创建,它就可以包含数千个跳价,所有这些都是在三次调用中创建的。这往往会在呈现和绘图过程中略微降低性能,但我们来看看在实践中这样做是如何发挥作用的。当我们使用真正的跳价文件时,即我们回放,这样的降速不会出现。这是因为当使用真实数据时,系统不需要任何额外的信息来绘制 1-分钟柱线,以及将信息传输到市场观察窗口中的跳价图表。我们在之前的两篇文章中对此进行了研究。
但是,赫兹量化交易软件要使用 1-分钟柱线来生成跳价时,即执行模拟时,我们需要知道跳价大小,如此这些信息才可以帮助服务创建合适的走势模型。这种走势将在“市场观察”窗口中可见。但是此信息并非创建柱线所需,因为转换是在 C_FileTicks 类中执行的。
添加图片注释,不超过 140 字(可选)
知道了这个细节,我们必须研究生成指定图表的函数,且还要检查在执行过程中将收到多少次调用。以下是模拟期间所用的函数:
inline void CreateBarInReplay(const bool bViewMetrics, const bool bViewTicks) { #define def_Rate m_MountBar.Rate[0] bool bNew; MqlTick tick[1]; static double PointsPerTick = 0.0; if (m_MountBar.memDT != macroRemoveSec(m_Ticks.Info[m_ReplayCount].time)) { PointsPerTick = (PointsPerTick == 0.0 ? SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) : PointsPerTick); if (bViewMetrics) Metrics(); m_MountBar.memDT = (datetime) macroRemoveSec(m_Ticks.Info[m_ReplayCount].time); def_Rate.real_volume = 0; def_Rate.tick_volume = 0; } bNew = (def_Rate.tick_volume == 0); def_Rate.close = (m_Ticks.Info[m_ReplayCount].volume_real > 0.0 ? m_Ticks.Info[m_ReplayCount].last : def_Rate.close); def_Rate.open = (bNew ? def_Rate.close : def_Rate.open); def_Rate.high = (bNew || (def_Rate.close > def_Rate.high) ? def_Rate.close : def_Rate.high); def_Rate.low = (bNew || (def_Rate.close < def_Rate.low) ? def_Rate.close : def_Rate.low); def_Rate.real_volume += (long) m_Ticks.Info[m_ReplayCount].volume_real; def_Rate.tick_volume += (m_Ticks.Info[m_ReplayCount].volume_real > 0 ? 1 : 0); def_Rate.time = m_MountBar.memDT; CustomRatesUpdate(def_SymbolReplay, m_MountBar.Rate); if (bViewTicks) { tick = m_Ticks.Info[m_ReplayCount]; if (!m_Ticks.bTickReal) { static double BID, ASK; double dSpread; int iRand = rand(); dSpread = PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? PointsPerTick : 0 ) : 0 ); if (tick[0].last > ASK) { ASK = tick[0].ask = tick[0].last; BID = tick[0].bid = tick[0].last - dSpread; } if (tick[0].last < BID) { ASK = tick[0].ask = tick[0].last + dSpread; BID = tick[0].bid = tick[0].last; } } CustomTicksAdd(def_SymbolReplay, tick); } m_ReplayCount++; #undef def_Rate }
于此我们声明了一个静态局部变量。这是为了避免不必要的调用修复跳价大小的函数。在服务生存周期和运行时,这样的捕获只会发生一次,而变量仅在指定位置使用。故此,将其扩展到此函数之外是没有意义的。但要注意,实际使用变量的这处只在我们使用模拟模式时才可用。在回放模式下,该变量没有实际用途。
这也解决了跳价大小的问题。还有两个问题亟待解决。不过,跳价的问题尚未彻底解决。初始化存在问题。我们将在解决其它两个问题的同时解决它,因为方式将是相同的。
最后要创建的细节
问题是我们实际上应该调整什么。的确,我们可以在自定义品种中调整若干事项。但其中大多并非我们的目的所需。我们只需关注我们真正需要的东西。我们还需要并对其进行设置,如此在我们需要这些信息时,我们就能以简单而通用的方式获得它。我之所以这样说,是因为我们很快就会开始创建一个订单系统,但我不确定它是否真的会那么快发生。任何情况下,我希望我们的 EA 与回放/模拟兼容,并且适合在真实市场中使用,既可是模拟账户,亦或真实账户。为此,我们需要一些含有所需信息的组件,且与真实市场中存在的级别相同。
在这种情况下,我们需要用零值初始化它们。这可确保自定义品种的这些值与实际交易品种中的值一致。此外,将值初始化为零意味着我们可以稍后测试它们,并且它令实现和测试品种配置中可能的错误的工作变得更加容易。
C_Replay(const string szFileConfig) { m_ReplayCount = 0; m_dtPrevLoading = 0; m_Ticks.nTicks = 0; Print("************** Market Replay Service **************"); srand(GetTickCount()); GlobalVariableDel(def_GlobalVariableReplay); SymbolSelect(def_SymbolReplay, false); CustomSymbolDelete(def_SymbolReplay); CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay), _Symbol); CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX); CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX); SymbolSelect(def_SymbolReplay, true); CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0); CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0); CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0); CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation"); m_IdReplay = (SetSymbolReplay(szFileConfig) ? 0 : -1); }
在此,我们设置未来真正需要的那些值,就在不久。这些值是跳价大小、跳价值、和交易量(在本例中为步数)。但由于该步数通常对应于应使用的最小交易量,故在我看来设置它替代最小交易量没有任何问题。还因为在下一阶段,这个步数对我们来说更加重要。还有另一个原因:我尝试调整最小交易量,但由于某种原因我无法这样做。MetaTrader 5 简单地忽略了我们需要设置最小交易量的事实。
一旦完成之后,我们还需要做些事情,并检查这些值是否已真的被初始化。这是在下一段代码中完成的:
bool ViewReplay(ENUM_TIMEFRAMES arg1) { #define macroError(A) { Print(A); return false; } u_Interprocess info; if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0) macroError("Configuração do ativo não esta completa, falta declarar o tamanho do ticket."); if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0) macroError("Configuração do ativo não esta completa, falta declarar o valor do ticket."); if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0) macroError("Configuração do ativo não esta completa, falta declarar o volume mínimo."); if (m_IdReplay == -1) return false; if ((m_IdReplay = ChartFirst()) > 0) do { if (ChartSymbol(m_IdReplay) == def_SymbolReplay) { ChartClose(m_IdReplay); ChartRedraw(); } }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0); Print("Aguardando permissão do indicador [Market Replay] para iniciar replay ..."); info.u_Value.IdGraphic = m_IdReplay = ChartOpen(def_SymbolReplay, arg1); ChartApplyTemplate(m_IdReplay, "Market Replay.tpl"); ChartRedraw(m_IdReplay); GlobalVariableDel(def_GlobalVariableIdGraphics); GlobalVariableTemp(def_GlobalVariableIdGraphics); GlobalVariableSet(def_GlobalVariableIdGraphics, info.u_Value.df_Value); while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750); return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != "")); #undef macroError }
为了避免不必要的重复,我们将使用宏定义。它将显示一条错误消息,并以系统故障消息结束。在此,我们逐一检查必须在配置文件中声明和初始化的值。如果其中任何一个尚未初始化,系统将通知用户正确配置自定义品种。不这样的话,回放/模拟服务将无法继续起作用。从这一刻起,可以认为它能起作用,并能为订单系统提供必要的数据,在本例中是正在创建的 EA,以便正确建模或回放行情。这将令我们能够模拟发送订单。期货量化交易软件:开发回放系统必要的调整