等量/范围图表
等量图表是基于相邻柱线交易量相等原理的柱线图表。 在常规图表上,每根新柱线均以指定的时间间隔形成,其与时间帧大小相匹配。 在等量图表上,当价格变动或实际交易量的总和达到预设值时,每根柱线均视为已形成。 之后,程序开始计算下一根柱线的额度。 当然,在计算交易量时价格走势依然受控,因此您会在图表上得到通常的四个价格值:开盘价、最高价、最低价和收盘价。
尽管等量图表上的水平轴仍然表示时序,但每根柱线的时间戳是任意的,且取决于每个时间区间的波动性(交易的数量或大小)。 许多交易者认为,与固定时间帧相比,这种柱形法方法能更准确地描述不断变化的行情。
不幸的是,赫兹股票量化都未提供开箱即用的等量图表。 它们应以特殊方式生成。
在 赫兹股票量化当中,这可以利用离线图表来达成。 等量图表复查中介绍了该方法。
而在 赫兹股票量化 当中,相同的算法可利用自定义品种来实现。 为了简化任务,我们借用指定文章中的非交易型智能交易系统,并令其与赫兹股票量化 MQL API 适配。
原始文件 EqualVolumeBars.mq4 已重命名为 EqualVolumeBars.mq5,并对其进行了少许修改。 特别是,描述输入参数的 'extern' 关键字已替换为 'input'。 用一个 StartDate 参数取代了 StartYear 和 StartMonth 两个参数。 在 赫兹股票量化 中设置非标准时间帧的 CustomPeriod 参数现在不需要了,故将其删除。
请注意,赫兹股票量化 的交易量是全部即时报价的交易量,即,它们代表柱线内即时报价的数量(价格变动次数)。 原本的想法是处理 M1 柱线(带有其即时报价量),或含有其他经纪商提供的即时报价的外部 csv 文件,为了计数每次输入的即时报价,以及一旦达到指定的即时报价次数后,立即形成新的等量柱线。 柱线被写入一个 hst 文件,该文件可以在 赫兹股票量化中作为脱机图表打开。
读取 csv 文件并写入 hst 文件的相关代码在赫兹股票量化 中已不再需要。 取而代之,我们可用自定义品种 API 读取真实的即时报价历史记录,并形成柱线。 此外,赫兹股票量化 允许经纪商提供真实的交易量和即时报价(针对交易所金融产品,但它们通常不适用于外汇金融产品)。 如果启用此模式,则构建等量柱线时可以不依据即时报价次数,而是依据实际交易量。
FromM1 输入参数判断 EA 是否需要处理 M1 柱线(默认为 “true”),亦或是即时报价历史记录(“false”)。 开始处理即时报价时,请勿选择距离太远的起点,因为这可能需要大量时间和磁盘空间。 如果您已采用即时报价记录操作,那么您要了解您的 PC 能力和可用资源。
以相同的方式绘制等范围柱线。 然而,当价格覆盖了指定的点数时,将在此处开立一根新的柱线。 请注意,这些柱线仅在即时报价模式下可用(FromM1 == false)。
Chart type — EqualTickVolumes, EqualRealVolumes, RangeBars — 由 WorkMode 输入参数设置。
最方便的方法是利用 Symbol 函数(由 fxsaber 开发)来操控自定义品种。 可用 #include 指令将其连接到智能交易系统:
#include <Symbol.mqh>
现在,我们可以基于当前图表品种创建自定义品种。 如下完成:
if(!SymbolSelect(symbolName, true)) { const SYMBOL Symb(symbolName); Symb.CloneProperties(_Symbol); if(!SymbolSelect(symbolName, true)) { Alert("Can't select symbol:", symbolName, " err:", GetLastError()); return INIT_FAILED; } }
其中 symbolName 是含有自定义品种名称的字符串。
与所有自定义品种管理相关的初始化片段,和许多其他辅助任务(特别是重置现有历史记录,使用新的自定义品种打开图表)均会以类似方式在所有程序中执行。 您可以在下面的附件中查看相关的源代码。 我将在本文中忽略它们,因为它们只是次要内容。
当出现一根新的等量柱线,或当前的等量柱线变化时,将调用 WriteToFile 函数。 经调用 赫兹股票量化 中的 CustomRatesUpdate 来实现此函数:
void WriteToFile(datetime t, double o, double l, double h, double c, long v, long m = 0) { MqlRates r[1]; r[0].time = t; r[0].open = o; r[0].low = l; r[0].high = h; r[0].close = c; r[0].tick_volume = v; r[0].spread = 0; r[0].real_volume = m; int code = CustomRatesUpdate(symbolName, r); if(code < 1) { Print("CustomRatesUpdate failed: ", GetLastError()); } }
令人惊讶的是,M1 柱线周期(FromM1 = true 模式)与 MQL4 版本几乎相同,这意味着只需修改 WriteToFile 函数,我们就可以得到 MQL5 版本的 M1 柱线函数代码。 唯一需要更改的部分是 RefreshWindow 中即时报价的生成。 在 赫兹股票量化之中,这是通过发送 Windows 消息来模拟脱机图表上的即时报价柱线来完成的。 赫兹股票量化则利用 CustomTicksAdd 函数:
void RefreshWindow(const datetime t) { MqlTick ta[1]; SymbolInfoTick(_Symbol, ta[0]); ta[0].time = t; ta[0].time_msc = ta[0].time * 1000; if(CustomTicksAdd(symbolName, ta) == -1) { Print("CustomTicksAdd failed:", GetLastError(), " ", (long) ta[0].time); ArrayPrint(ta); } }
即时报价生成会在自定义品种图表上调用 OnTick 事件,其允许在此类图表上运行智能交易系统进行交易。 不过,这项技术需要采取一些额外的措施,我们稍后再加以研究。
从即时报价历史中生成等量柱线的模式(FromM1 = false)要复杂一些。 这需要调用标准 CopyTicks/CopyTicksRange 函数读取真实的即时报价。 所有这些功能都已在 TicksBuffer 类中实现。
#define TICKS_ARRAY 10000 class TicksBuffer { private: MqlTick array[]; int tick; public: bool fill(ulong &cursor, const bool history = false) { int size = history ? CopyTicks(_Symbol, array, COPY_TICKS_ALL, cursor, TICKS_ARRAY) : CopyTicksRange(_Symbol, array, COPY_TICKS_ALL, cursor); if(size == -1) { Print("CopyTicks failed: ", GetLastError()); return false; } else if(size == 0) { if(history) Print("End of CopyTicks at ", (datetime)(cursor / 1000)); return false; } cursor = array[size - 1].time_msc + 1; tick = 0; return true; } bool read(MqlTick &t) { if(tick < ArraySize(array)) { t = array[tick++]; return true; } return false; } };
在 TICKS_ARRAY 片段的 “fill” 方法中请求即时报价,然后将其添加到“数组”中,然后调用 “read” 方法逐一读取。 该方法所实现的操控即时报价历史记录的算法类似于 M1 历史记录柱线(附件中提供了完整的源代码)。
TicksBuffer tb; while(tb.fill(cursor, true) && !IsStopped()) { MqlTick t; while(tb.read(t)) { ... // New or first bar if(IsNewBar() || now_volume < 1) { WriteToFile(...); } } }
每次启动智能交易系统时,它都会调用 “Reset” 函数清除指定自定义品种的现有历史记录(如果存在的话)。 如有必要,可改进此行为,即保存历史记录,并在前一此生成结束的位置继续生成柱线。