我们用赫兹量化来做交易关于交易这是一件挺好奇的事情。 我们经常看到市场在某些价格区域堆积,当买入或卖出方触发止损时,价格会快速波动。 这种走势可以通过时序与交易(Times & Trade)。 我们在之前的文章时序与交易(I)和 时序与交易(II)中曾研究过这些。 在这些文章中,我们研究了如何创建一个替代图形系统,来读取和分析已执行的订单流。 如果您仔细观察,您会注意到,在某些时刻,价格往往会回到堆积区域,在该处它并不打算即刻离开。 但当我们观察价格成交量指标时,很难判定这个特定区域的价格在最近会有多大变化。 该指标已在文章添加价格成交量(I)中实现。 使用它,我们可以通过简单地更改分析起点,来分析最近的相对走势,而通过调整下图中所示对象的值也可完成:
但这实际上这有点不切实际,因为我们要与主要时间帧挂钩,即,如果您有一个 60 分钟时间帧图表,您将无法掌握低于该时间帧的价格走势。 您必须切换到较低的时间帧才能调整分析点。 但在交易期货合约时,大多数交易员实际上都采用较短的时间帧,例如 5、10 或 30 分钟,因此调整分析起点没有问题。 但正如我之前解释的那样,有时价格会因为触发止损而退出堆积,这种回报通常在不到 5 分钟内发生。 在这种情况下,图表上会出现一个上、下阴影较长的烛条。 在这种情况下,价格动作会告诉我们所发生的事件是什么样的市场声音,从下面烛条上的箭头指示可以看到这种走势:
典型的买方测试动作,或做空单触发停止
典型的卖方测试动作,或做多单触发停止
这种走势类型频繁发生,分析每个价格区间的成交量非常重要,因为它能够了解市场是正在测试、亦或趋势是真的在逆转。 但是,若用前面提出的成交量指标,不可能正确或迅速地做到这一点。
然而,我们可以针对指标对象类进行一个小的修改,以便更清楚地了解发生了什么。 这将为给定时间段内发生的交易显示痕迹。
2. 实现
分析之前首先要做的是跟踪时间设置多久,您也许会设置 60、45、30、19、7 还是 1分钟。 尽管如此,我们建议使用足够倍数的值,以便跟踪系统真正有用。 出于实际原因,我们将采用 30 分钟跟踪来实现它,因此我们将在以下代码行中定义它:
#define def_MaxTrailMinutes 30
但为什么要恰恰是 30 分钟? 实际上,跟踪系统每分钟执行一次,但最长跟踪时间为 30 分钟。 即,您将始终有 30 分钟的跟踪,例如,当跟踪切换到第 31 分钟时,则首个交易分钟将不再显示。 它是如何实现的? 所用的捕获系统如下所示:
inline void SetMatrix(MqlTick &tick) { int pos; if ((tick.last == 0) || ((tick.flags & (TICK_FLAG_BUY | TICK_FLAG_SELL)) == (TICK_FLAG_BUY | TICK_FLAG_SELL))) return; pos = (int) ((tick.last - m_Infos.FirstPrice) / Terminal.GetPointPerTick()) * 2; pos = (pos >= 0 ? pos : (pos * -1) - 1); if ((tick.flags & TICK_FLAG_BUY) == TICK_FLAG_BUY) m_InfoAllVaP[pos].nVolBuy += tick.volume; else if ((tick.flags & TICK_FLAG_SELL) == TICK_FLAG_SELL) m_InfoAllVaP[pos].nVolSell += tick.volume; m_InfoAllVaP[pos].nVolDif = (long)(m_InfoAllVaP[pos].nVolBuy - m_InfoAllVaP[pos].nVolSell); m_InfoAllVaP[pos].nVolTotal = m_InfoAllVaP[pos].nVolBuy + m_InfoAllVaP[pos].nVolSell; m_Infos.MaxVolume = (m_Infos.MaxVolume > m_InfoAllVaP[pos].nVolTotal ? m_Infos.MaxVolume : m_InfoAllVaP[pos].nVolTotal); m_Infos.CountInfos = (m_Infos.CountInfos == 0 ? 1 : (m_Infos.CountInfos > pos ? m_Infos.CountInfos : pos)); m_Infos.Momentum = macroGetMin(tick.time); m_Infos.Momentum = (m_Infos.Momentum > (def_MaxTrailMinutes - 1) ? m_Infos.Momentum - def_MaxTrailMinutes : m_Infos.Momentum); if (m_Infos.memMomentum != m_Infos.Momentum) { for (int c0 = 0; c0 <= m_Infos.CountInfos; c0++) m_TrailG30[m_Infos.Momentum].nVolume[c0] = 0; m_Infos.memMomentum = m_Infos.Momentum; } m_TrailG30[m_Infos.Momentum].nVolume[pos] += tick.volume; }
添加到对象类源代码中的行以高亮显示 — 它们实现了成交量跟踪捕获。 下面的几行保证了跟踪将按预期进行。
m_Infos.Momentum = macroGetMin(tick.time); m_Infos.Momentum = (m_Infos.Momentum > (def_MaxTrailMinutes - 1) ? m_Infos.Momentum - def_MaxTrailMinutes : m_Infos.Momentum);
跟踪捕捉系统已准备就绪。 现在我们需要做出新的决定。 请记住,跟踪会每 1 分钟捕获一次。 如此这般,我们就能在 1 分钟内看到每个价格范围的成交量。 若我们长久以这种方式绘制图表,您也许会考虑执行以下操作:
较浅的色调代表时间较近的成交量,这也许是一个好主意...
虽然这似乎是一个好主意,但当成交量较低或走势非常快时,即使所表达的成交量只是片刻,它实际上也可能不可见,因为在进行绘制时是依据迄今为止发现的最大成交量。 因此,您也许希望以稍微不同的方式来绘制,从而解决这个问题,如此它看起来是这样的:
每种颜色表示成交量跟踪中的特定周期。
这有助于分析成交量上非常狭窄的波带,并调整第一种情况下偶尔出现的问题。 但是,我们仍然会遇到调整问题,譬如当某时刻成交量相对于整体成交量难以表现时。 此外,必须仔细选择每个时段的颜色,从而在极其活跃的交易期间不会混淆分析。
因此,此处我们将采用一个更简单的模型,该模型可以再次调整,以便分析不同时期的走势。 然而,请记住上述问题。 这将由您决定。 然后轨迹将显示如下:
我们在这里看到了一条纯净的轨迹。 当它发生时,我们应该一并分析"时序与交易“和价格行为,以了解正在发生的事情。
无论如何,为了更改成交量显示,唯一需要修改的是以下函数:
void Redraw(void) { uint x, y, y1, p; double reason = (double) (m_Infos.MaxVolume > m_WidthMax ? (m_WidthMax / (m_Infos.MaxVolume * 1.0)) : 1.0); double desl = Terminal.GetPointPerTick() / 2.0; ulong uValue; Erase(); p = m_WidthMax - 8; for (int c0 = 0; c0 <= m_Infos.CountInfos; c0++) { if (m_InfoAllVaP[c0].nVolTotal == 0) continue; ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, m_Infos.FirstPrice + (Terminal.GetPointPerTick() * (((c0 & 1) == 1 ? -(c0 + 1) : c0) / 2)) + desl, x, y); y1 = y + Terminal.GetHeightBar(); FillRectangle(p + 2, y, p + 8, y1, macroColorRGBA(m_InfoAllVaP[c0].nVolDif > 0 ? m_Infos.ColorBuy : m_Infos.ColorSell, m_Infos.Transparency)); FillRectangle((int)(p - (m_InfoAllVaP[c0].nVolTotal * reason)), y, p, y1, macroColorRGBA(m_Infos.ColorBars, m_Infos.Transparency)); uValue = 0; for (int c1 = 0; c1 < def_MaxTrailMinutes; c1++) uValue += m_TrailG30[c1].nVolume[c0]; FillRectangle((int) (p - (uValue * reason)), y, p, y1, macroColorRGBA(clrRoyalBlue, m_Infos.Transparency)); } C_Canvas::Update(); };
为了更准确,只需要修改高亮显示出的代码。 您可以试演它,直到您得到想要的结果。 除了高亮显示的部分外,不需要修改类中的任何其它内容。 编译程序,并在图表上运行后,您将看到如下内容:
正在上传…重新上传取消
解决渲染问题
虽然代码没有任何特别问题,但在调整图表大小时存在一个小缺陷:当将最大化的图表调整到任意其它尺寸,然后返回到最大化时,一些对象会丢失,表现不符合预期,并定位在错误的位置。 没有多少事情需要解决。 问题出在下面的代码中 — 我们在以前的文章中曾用过它。
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { Chart.DispatchMessage(id, lparam, dparam, sparam); VolumeAtPrice.DispatchMessage(id, sparam); switch (id) { case CHARTEVENT_CHART_CHANGE: Terminal.Resize(); WallPaper.Resize(); TimesAndTrade.Resize(); break; } ChartRedraw(); }
有一个非常简单的修改,但您也许会想“我什么都没看到 — 代码是正确的”。 乍一看,我也没有发现任何错误,只是代码仍然存在运行时错误。 但是当我加上一些额外的功能时,我注意到了问题,而这正是我上面描述的问题。 为了解决这个问题,需要对代码进行如下修改:
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { switch (id) { case CHARTEVENT_CHART_CHANGE: Terminal.Resize(); WallPaper.Resize(); TimesAndTrade.Resize(); break; } Chart.DispatchMessage(id, lparam, dparam, sparam); VolumeAtPrice.DispatchMessage(id, sparam); ChartRedraw(); }
它听起来可能很愚蠢,但要理解为什么,只需查看整个函数代码和高亮显示的部分。 现在系统既然已经修复,我们就可以推进到下一步。
添加额外资源
我们现在要添加的函数非常简单,许多人可能觉得没有理由去实现它,但实现它会有助于处理订单,包括开仓、移动、或只是观察价格成交量指标。
首先要做的是更改价格行调整代码所归属的类。 这段代码来自 C_OrderView 类,并植入 C_Terminal 类,但对于这一点,它也经历了一些小的变化,因为它开始操控类本身的变量。 下面是新代码的样子。
double AdjustPrice(const double arg) { double v0, v1; if(m_Infos.TypeSymbol == OTHER) return arg; v0 = (m_Infos.TypeSymbol == WDO ? round(arg * 10.0) : round(arg)); v1 = fmod(round(v0), 5.0); v0 -= ((v1 != 0) || (v1 != 5) ? v1 : 0); return (m_Infos.TypeSymbol == WDO ? v0 / 10.0 : v0); };