赫兹量化——从头开始开发智能交易系统 添加价格成交量

文章探讨了如何通过时序与交易分析市场堆积区域,以及价格因止损触发的波动。作者提出了一种改进的成交量指标,以更清晰地了解价格走势,特别是对于期货合约交易。通过跟踪30分钟内的交易,代码实现对成交量的可视化,帮助识别市场测试和趋势反转。文章还解决了图表调整大小时的渲染问题,并讨论了代码的优化。
摘要由CSDN通过智能技术生成

我们用赫兹量化来做交易关于交易这是一件挺好奇的事情。 我们经常看到市场在某些价格区域堆积,当买入或卖出方触发止损时,价格会快速波动。 这种走势可以通过时序与交易(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);
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值