
开发基于订单簿的交易系统(第一部分):指标
概述
让我们回顾一下市场深度是什么。这是一系列待执行的限价订单。这些订单代表了市场参与者的交易意图,通常不会导致实际交易。这是因为交易者有能力因各种原因取消之前下达的订单。这些原因可能包括市场状况的变化以及由此导致的失去以先前指定的价格和数量执行订单的兴趣。
函数 SymbolInfoInteger(_Symbol, SYMBOL_TICKS_BOOKDEPTH) 返回的值与订单簿的深度精确对应,并代表将填充要分析的价格水平的数组部分的一半。该数组的一半用于限价卖出订单的数量,另一半用于已下达的限价买入订单。根据文档,对于没有订单队列的资产,此属性的值为零。下图中可以看到一个示例,其中显示了深度为 10 的订单簿,其中显示了所有可用的价格水平。
需要注意的是,深度可以从交易品种获得,而不一定从市场深度获得。使用 SymbolInfoInteger 函数足以获取属性值,而无需借助 OnBookEvent 处理程序或相关函数(如 MarketBookAdd )。当然,我们可以通过计算 OnBookEvent 处理函数填充的 MqlBookInfo 数组中的元素数量来得出相同的结果,我们稍后将详细探讨。
您可能想知道为什么我们应该使用此指标,而不是简单地依赖 MetaTrader 5 的标准订单簿。以下是一些主要原因:
- 优化了图表空间利用率,允许自定义直方图大小及其在屏幕上的位置。
- 订单簿事件的呈现更清晰,提高了清晰度。
- 考虑到目前不支持本机测试,未来将实现基于磁盘的 BookEvent 事件存储机制,从而在策略测试器中实现可用性。
生成自定义交易品种
即使市场关闭或经纪商未传输给定交易品种的事件,此过程也将使我们能够测试指标。在这种情况下,将没有实时订单队列,这些事件也不会缓存在本地计算机上。在这个阶段,我们不会处理真实交易品种的过去事件,而是专注于为虚拟资产生成模拟的 BookEvent 数据。这是必要的,因为创建这样的资产和模拟事件对于使用 CustomBookAdd 函数至关重要。此函数专为自定义交易品种而设计。
下面是 CloneSymbolTicksAndRates 脚本,它将生成自定义交易品种。它根据文档进行了改编以满足我们的需求,首先定义一些常量,然后包含用于处理日期的标准 DateTime.mqh 库。请注意,自定义交易品种的名称将源自真实交易品种的命名法,该命名法通过 Symbol() 函数传递给脚本。因此,必须在要克隆的真实资产上运行该脚本。虽然也可以克隆自定义交易品种,但这样做似乎并不是特别有用。
#define CUSTOM_SYMBOL_NAME Symbol()+".C" #define CUSTOM_SYMBOL_PATH "Forex" #define CUSTOM_SYMBOL_ORIGIN Symbol() #define DATATICKS_TO_COPY UINT_MAX #define DAYS_TO_COPY 5 #include <Tools\DateTime.mqh>
插入到同一脚本的 OnStart() 函数中的以下片段创建了 “timemaster” 日期对象。它用于计算收集分时报价和柱形以进行克隆的时间段。根据我们定义的 DAYS_TO_COPY 常量,Bars 函数将复制源交易品种的最后五天。然后将该范围的相同初始时间转换为毫秒并由 CopyTicks 函数使用,从而完成交易品种的“克隆”。
CDateTime timemaster; datetime now = TimeTradeServer(); timemaster.Date(now); timemaster.DayDec(DAYS_TO_COPY); long DaysAgoMsc = 1000 * timemaster.DateTime(); int bars_origin = Bars(CUSTOM_SYMBOL_ORIGIN, PERIOD_M1, timemaster.DateTime(), now); int create = CreateCustomSymbol(CUSTOM_SYMBOL_NAME, CUSTOM_SYMBOL_PATH, CUSTOM_SYMBOL_ORIGIN); if(create != 0 && create != 5304) return; MqlTick array[] = {}; MqlRates rates[] = {}; int attempts = 0; while(attempts < 3) { int received = CopyTicks(CUSTOM_SYMBOL_ORIGIN, array, COPY_TICKS_ALL, DaysAgoMsc, DATATICKS_TO_COPY); if(received != -1) { if(GetLastError() == 0) break; } attempts++; Sleep(1000); }
该过程完成后,新交易品种应出现在市场观察列表中,名称为 <AtivodeOrigem>.C。此时,我们需要用这个合成交易品种打开一个新图表并进行下一步。
如果另一个合成交易品种已经存在,则可以重复使用,从而无需按照本节中的说明创建新的合成交易品种。最后,我们只需打开一个带有此自定义交易品种的新图表并运行我们将在此处开发的另外两个 MQL5 应用程序:指标和事件生成器脚本。我们将在以下部分提供所有详细信息。
用于测试的 BookEvent 类型事件生成器脚本
在对依赖于订单簿事件的指标执行回溯测试时,仅使用自定义交易品种并不能弥补在线订单簿分时报价序列的缺失。因此,我们需要生成模拟数据。为此,我们开发了以下脚本。
//+------------------------------------------------------------------+ //| GenerateBookEvent.mq5 | //| Daniel Santos | //+------------------------------------------------------------------+ #property copyright "Daniel Santos" #property version "1.00" #define SYNTH_SYMBOL_MARKET_DEPTH 32 #define SYNTH_SYMBOL_BOOK_ITERATIONS 20 #include <Random.mqh> //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double BidValue, tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); MqlBookInfo books[]; int marketDepth = SYNTH_SYMBOL_MARKET_DEPTH; CRandom rdn; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { if(!SymbolInfoInteger(_Symbol, SYMBOL_CUSTOM)) // if the symbol exists { Print("Custom symbol ", _Symbol, " does not exist"); return; } else BookGenerationLoop(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void BookGenerationLoop() { MqlRates BarRates_D1[]; CopyRates(_Symbol, PERIOD_D1, 0, 1, BarRates_D1); if(ArraySize(BarRates_D1) == 0) return; BidValue = BarRates_D1[0].close; ArrayResize(books, 2 * marketDepth); for(int j = 0; j < SYNTH_SYMBOL_BOOK_ITERATIONS; j++) { for(int i = 0, j = 0; i < marketDepth; i++) { books[i].type = BOOK_TYPE_SELL; books[i].price = BidValue + ((marketDepth - i) * tickSize); books[i].volume_real = rdn.RandomInteger(10, 500); books[i].volume_real = round((books[i].volume_real + books[j].volume_real) / 2); books[i].volume = (int)books[i].volume_real; //---- books[marketDepth + i].type = BOOK_TYPE_BUY; books[marketDepth + i].price = BidValue - (i * tickSize); books[marketDepth + i].volume_real = rdn.RandomInteger(10, 500); books[marketDepth + i].volume_real = round((books[marketDepth + i].volume_real + books[marketDepth + j].volume_real) / 2); books[marketDepth + i].volume = (int)books[marketDepth + i].volume_real; if(j != i) j++; } CustomBookAdd(_Symbol, books); Sleep(rdn.RandomInteger(400, 1000)); } } //+------------------------------------------------------------------+
我们没有使用标准的 MathRand() 函数,而是使用了另一种实现方式来生成 32 位随机数。做出这样的选择有几个原因,包括易于生成指定范围内的整数值 - 我们在本脚本中通过使用 RandomInteger(min, max) 函数利用了这一优势。
对于订单簿深度,我们选择了相对较大的值 32,这意味着每次迭代将生成 64 个价格水平。如果需要,可以将此值调整为较小的值。
该算法首先检查该交易品种是否是自定义交易品种。如果是,它将继续生成订单簿的每个元素,并根据指定的迭代次数在另一个循环中重复此过程。在此实现中,执行 20 次迭代,并在 400 毫秒和 1000 毫秒(相当于 1 秒)之间随机选择暂停时间。这种动态方法使分时报价的可视化更加逼真且更具视觉吸引力。
价格垂直锚定在每日时段的最后收盘价,如源交易品种所示。在这个参考点之上,有 32 个卖单级别,而在这个参考点之下,有 32 个买单级别。根据指标的标准配色方案,对应于卖单的直方图条具有红色色调,而买单则以浅蓝色表示。
连续级别之间的价格差异是根据交易品种的报价大小确定的,该大小是通过 SYMBOL_TRADE_TICK_SIZE 属性获得的。
显示市场深度变化的指标
库源代码
该指标是使用面向对象的编程开发的。创建 BookEventHistogram 类是为了管理订单簿直方图,处理其创建、更新以及类对象被销毁时柱形的删除。
以下是 BookEventHistogram 类的变量和函数声明:
class BookEventHistogram { protected: color histogramColors[]; //Extreme / Mid-high / Mid-low int bookSize; int currElements; int elementMaxPixelsWidth; bool showMessages; ENUM_ALIGN_MODE corner; string bookEventElementPrefix; public: MqlBookInfo lastBook[]; datetime lastDate; void SetAlignLeft(void); void SetCustomHistogramColors(color &colors[]); void SetBookSize(int value) {bookSize = value;} void SetElementMaxPixelsWidth(int m); int GetBookSize(void) {return bookSize;} void DrawBookElements(MqlBookInfo& book[], datetime now); void CleanBookElements(void); void CreateBookElements(MqlBookInfo& book[], datetime now); void CreateOrRefreshElement(int buttonHeigh, int buttonWidth, int i, color clr, int ydistance); //--- Default constructor BookEventHistogram(void); ~BookEventHistogram(void); };
并非所有函数都在此段中定义;但是,它们已在 BookEventHistogram.mqh 文件的其余代码行中完成。
在最重要的函数中,CreateBookElements 和 CreateOrRefreshElement 协同工作,以确保在必要时在创建新元素的同时更新现有元素。其余的函数用于保持属性最新或返回某些对象变量的值。
指标源代码:
代码的开头将绘图和缓冲区的数量定义为 3。更深入的分析将揭示,实际上,MQL5 指标的根结构缓冲区并没有被使用。但是,此声明有助于生成代码,以确保用户在指标初始化期间与某些属性进行交互。在这种情况下,我们的重点是颜色属性,其中输入方案旨在提供直观和用户友好的体验。
每个图都分配了两种颜色 — 一种用于买入订单,一种用于卖出订单。这组六种颜色用于根据预定义的标准确定每个分段的颜色。广义上讲,直方图中最大的部分被归类为“极值”,高于平均大小的部分被归类为“中高”,其余部分被归类为“中低”。
使用 PlotIndexGetInteger 函数获取颜色,该函数指定绘图以及绘图中应提取信息的位置。
#define NUMBER_OF_PLOTS 3 #property indicator_chart_window #property indicator_buffers NUMBER_OF_PLOTS #property indicator_plots NUMBER_OF_PLOTS //--- Invisible plots #property indicator_label1 "Extreme volume elements colors" #property indicator_type1 DRAW_NONE #property indicator_color1 C'212,135,114', C'155,208,226' //--- #property indicator_label2 "Mid-high volume elements colors" #property indicator_type2 DRAW_NONE #property indicator_color2 C'217,111,86', C'124,195,216' //--- #property indicator_label3 "Mid-low volume elements color" #property indicator_type3 DRAW_NONE #property indicator_color3 C'208,101,74', C'114,190,214' #include "BookEventHistogram.mqh" enum HistogramPosition { LEFT, //<<<< Histogram on the left RIGHT, //Histogram on the right >>>> }; enum HistogramProportion { A_QUARTER, // A quarter of the chart A_THIRD, // A third of the chart HALF, // Half of the chart }; input HistogramPosition position = RIGHT; // Indicator position input HistogramProportion proportion = A_QUARTER; // Histogram ratio (compared to chart width) //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ double volumes[]; color histogramColors[]; BookEventHistogram bookEvent;
接下来,我们介绍两个枚举,旨在为用户在加载指标时提供精确的选项。我们要确定直方图应该绘制在哪里:图表的右侧还是左侧。此外,用户必须指定直方图将占据图表宽度的比例:图表的四分之一、三分之一或一半。例如,如果图表宽度为 500 像素,并且用户选择半宽选项,则直方图条的大小范围可以是 0 到 250 像素。
最后,在源代码 BookEvents.mq5 中,OnBookEvent 和 OnChartEvent 函数将触发大部分直方图更新请求。OnCalculate 函数在算法中不起作用,仅保留用于 MQL 语法合规性。
使用脚本和指标
运行脚本和指标的正确顺序如下,以确保与迄今为止开发的资源保持一致:
- 在要克隆的真实交易品种图表上运行脚本 CloneSymbolTicksAndRates。
- -> BookEvents 指标(在生成的自定义交易品种的图表上)
- -> GenerateBookEvent 脚本(在生成的自定义交易品种的图表上)
BookEvent 被广播到目标自定义资产的所有图形实例。因此,指标和事件生成器脚本可以在单独的图表上或同一个图表内执行,只要它们引用相同的自定义交易品种。
下面的动画说明了此序列以及指标的功能。希望你喜欢它!
结论
市场深度无疑是执行快速交易的一个非常重要的因素,特别是在高频交易(HFT)算法中。它是经纪商为许多交易品种提供的一种市场事件。随着时间的推移,经纪商可能会扩大此类数据的覆盖范围和可用性,以涵盖更多资产。
但我认为单纯基于订单簿建立交易系统并不可取。相反,DOM 可以帮助识别流动性区域,并且可能与价格变动表现出一定的相关性。因此,将订单簿分析与其他工具和指标相结合是实现一致交易结果的谨慎方法。
该指标未来还有改进的空间,例如实现存储 BookEvent 数据的机制,然后在回测中使用这些数据,无论是手动交易还是自动策略。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/15748



