期货量化交易软件:创建多交易品种、多周期指标

本文详细解释了赫兹量化交易软件中指标的计算与绘图过程,涉及指标句柄的使用、时间序列数据的管理和CopyBuffer函数的作用。文章还介绍了资源高效计算策略,如OnCalculate函数和如何在多交易品种、多周期环境下处理数据和时间框架转换。
摘要由CSDN通过智能技术生成

基本原则

为了正确理解指标操作的逻辑,赫兹量化交易软件试着了解它是如何工作的。指标分为两部分:计算和绘图。这些部分中的每一个都对另一个一无所知。当创建指标时,终端子系统在图表上查找这样的指标是否存在。在这种情况下,它会查找具有相同名称和参数的指标。如果这样的指标已经在图表上运行,或者已经为此图表以编程方式创建,则终端将使用现有指标的句柄,而不是创建新指标。指标的绘图部分使用其句柄从计算部分接收所需的数据。可能存在多个绘图部件同时访问一个计算部件的情况。

计算部分的缓冲区将计算出的指标的数据存储在具有从现在到过去排列的数据的数组中。缓冲区数组索引0处的数据对应于当前图表数据:

添加图片注释,不超过 140 字(可选)

数组的每个单元存储一个柱形图的数据,该柱形图对应于计算指标的交易品种/周期的时间序列柱形图。因此,为了从指标计算部分的缓冲区中获得数据并将其显示在另一个交易品种/时间框架的图表上,您需要计算计算部分缓冲区数组中与柱形图的时间相对应的图表上的柱形图编号。应将获得的数据写入绘图部分缓冲区,以便将当前图表中与计算部分缓冲区中柱形图开盘价相匹配的所有柱形图添加到绘图缓冲区的相应单元格中。

例如,五分钟图表周期上的一个柱对应于一分钟图表上的五个柱。一分钟图表的所有这五个柱都必须填充与它们在时间上对应的五分钟柱的值。类似的算法用于在较高的时间框架图上呈现较低时段的数据。在这种情况下,来自计算部分缓冲器的单元格的所有柱,对应于较高TF图上的柱的时间,被绘制在绘制缓冲区的一个柱上。

当然,读数可能并不精确,因为最终该柱将仅代表最后一个较低TF柱的数据,该数据与相应较高TF柱的时间相匹配。这里的一切都取决于从较低时间段的计算部分缓冲区接收数据的方向,最后接收的数据将绘制在较高时间段图表的柱形图上。

CopyBuffer()函数从计算的指标的缓冲区中获取数据:

函数将指定数量的指定指标缓冲区的数据接收到“buffer”数组中。 复制数据的元素(具有索引buffer_num的指标缓冲区)从开始位置开始计数,从现在到过去,也就是说,等于0的开始位置表示当前柱(当前柱的指标值)。 如果事先不知道要复制的数据量,建议使用动态数组作为目标数组缓冲区,因为CopyBuffer()会尝试将接收数组的大小分配到复制的数据的量。如果接收数组缓冲区是一个指标缓冲区(之前由SetIndexBufer()函数分配的用于存储指标值的数组),则允许部分复制。 如果需要将指标值部分复制到另一个数组(而不是指标缓冲区),则应使用一个中间数组,将所需数量复制到该数组中。从这个中间数组中,逐个成员将所需数量的值复制到接收数组的所需位置。 如果需要复制预定量的数据,建议使用静态分配的缓冲区,以避免不必要的内存重新分配。 接收数组的属性,即as_series=true或as_series=false,将被忽略:在复制过程中,最旧的元素将被复制到为数组分配的物理内存的开头。有三种函数选项。 按根据初始位置和所需元素的数量的访问 int CopyBuffer( int indicator_handle, // indicator handle int buffer_num, // indicator buffer index int start_pos, // starting point int count, // amount to copy double buffer[] // array the data to be copied to ); 按初始日期和所需元素数量访问 int CopyBuffer( int indicator_handle, // indicator handle int buffer_num, // indicator buffer index datetime start_time, // starting date int count, // amount to copy double buffer[] // array the data to be copied to ); 按所需时间间隔的初始日期和最终日期访问 int CopyBuffer( int indicator_handle, // indicator handle int buffer_num, // indicator buffer index datetime start_time, // starting date datetime stop_time, // end date double buffer[] // array the data to be copied to ); 参数 indicator_handle [in] 通过相关指标函数获得的指标句柄。 buffer_num [in] 指标缓冲区的编号。 start_pos [in] 复制的第一个元素的索引。 count [in] 复制的元素数。 start_time [in] 第一个元素对应的柱的时间。 stop_time [in] 最后一个元素对应的柱的时间。 buffer[] [out] double类型的数组。 返回值 复制的数组元素数,如果出现错误,则为-1。 注意 当从指标请求数据时,如果请求的时间序列尚未构建或应该从服务器下载,则函数会立即返回-1。在这种情况下,会启动所需数据的下载或构建。 当从EA或脚本请求数据时,如果终端在本地没有适当的数据,则会启动从服务器的下载。另外,如果可以从本地历史构建数据,但尚未准备好,则开始构建必要的时间序列。该函数返回超时到期时准备好的数量。

赫兹量化交易软件将使用该函数的第一个版本,即基于起始位置(y为循环索引)和所需元素数量的访问。

对象结构如下:

  1. 多交易品种、多周期指标的基类,包含所有指标通用的主要函数;

  2. 从基本对象派生的类,这些类按其类型描述每个指标;

  3. 指标的集合–您可以使用此类创建任何指标并将其添加到集合中。该类将为用户提供创建指标和从中接收数据的所有工具。

使用非当前图表中的数据时,为了避免时间序列被释放,您应该至少每两分钟访问一次此时间序列。在这种情况下,时间序列将被“保留”,这将加快对它的访问(不需要每次都等待数据同步)。在类构造函数中,赫兹量化交易软件将执行构建指标的时间序列的第一次调用。这将允许我们开始下载时间序列(如果无法在本地访问)。然后,每隔90秒,在基类计时器中,我们将访问时间序列来保存它。

资源高效计算

为了计算和显示指标,我们需要资源高效的计算。第一次启动时,将计算所有历史数据的指标,然后将计算所有后续分时的一个或两个柱形。

OnCalculate()处理函数具有预定义的常量变量,这些变量存储(时间序列或数组的)输入数据大小以及在上一次OnCalculate()调用 期间计算的数据量:

基于数据数组的计算 int OnCalculate( const int rates_total, // price[] array size const int prev_calculated, // number of processed bars at the previous call const int begin, // index number in the price[] array meaningful data starts from const double& price[] // array of values for calculation ); 基于当前时间段时间序列的计算 int OnCalculate( const int rates_total, // size of input timeseries const int prev_calculated, // number of processed bars at the previous call const datetime& time{}, // Time array const double& open[], // Open array const double& high[], // High array const double& low[], // Low array const double& close[], // Close array const long& tick_volume[], // Tick Volume array const long& volume[], // Real Volume array const int& spread[] // Spread array );

这种数据的存在使得能够快速、有效地计算指标。例如,让我们考虑“limit”值的计算:

 
 

//--- Number of bars for calculation int limit=rates_total-prev_calculated; //--- If limit > 1, then this is the first calculation or change in the history if(limit>1) { //--- specify all the available history for calculation limit=rates_total-1; //--- If the indicator has any buffers, initialize them here with empty values set for these buffers }

当指标首次启动时,我们有时间序列大小(rates_total)和上次调用的计算数据量(prev_calculated)。由于尚未计算指标,因此在第一次启动时先前计算的柱的值为零。因此,“limit”值将大于1(等于可用柱数减去零)。使用此值,我们将“limit”指定为rates_total-1——用于计算的整个可用历史。在这种情况下,我们还需要首先从指标缓冲区中删除所有先前绘制的数据,例如:

 
 

ArrayInitialize(Buffer,EMPTY_VALUE);

之后,将在主循环中计算整个历史,该循环从“limit”到零(包括零)运行:

 
 

for(int i=limit;i>=0;i--) { // Calculating indicator at each bar of the loop (i) }

请注意,使用此计算方法,计算中使用的所有数组和指标缓冲区本身都必须具有索引为时间序列的标志:

 
 

ArraySetAsSeries(array,true);

如果计算出的“limit”等于1,则这意味着图表上打开了一个新的柱:指标将在从1到0(包括1到0)的循环中计算时间序列的第一个和零个柱。

如果计算的“limit”等于0,则这意味着对当前分时的操作:指标将仅计算从0到0(包括0)的循环中时间序列的第零柱。

如果您需要从零柱深入历史数据进行计算(以免扩展时间序列数组和缓冲区),则循环将反转:

 
 

int i=fmax(0, prev_calculated-1); for(;i<rates_total;i++) { // Calculating indicator at each bar of the loop (i) }

在当前图表上运行资源高效计算是非常容易的。但是,如果您需要的不是当前图表中的数据,该怎么办?何时从计算部分复制整个数据数组,何时仅复制最后一个或两个条形图?

这里我们将使用函数Bars()BarsCalculated()。这些类似于预定义的常量指标变量rates_total和prev_calculated。它们返回指定交易品种/周期的柱数以及指标计算的数据量。由于指标是为其创建时刻指定的交易品种/周期构建的,因此计算的数据量也指的是该交易品种/周期。我们通过它的句柄来获取指标。

基于我们可以计算任何交易品种/周期需要复制多少个柱(以免在每个分时处复制整个数组)的事实,我们将在指标基类中创建与当前交易品种/周期完全相同的结构:

 
 

limit=rates_total-prev_calculated;

但是变量“limit”、“rates_total”和“prev_calculated”将是类的私有成员,并将从Bars()BarsCalculated()函数接收值。“limit”值将在每个分时上计算,如果为零,则仅复制最后两个数据柱(当前和以前的数据柱)的数据。如果“limit”等于1,则这意味着在指标交易品种/周期上打开一个新的柱,您需要将数组增加1,然后从指标缓冲区复制数据-也是两个。当“limit”大于1时,整个数组将从指标的计算部分缓冲区复制到类的接收数组缓冲区,因为这被认为是第一次启动或历史上有了更改。

此逻辑适用于交易日,即分时到达的时间。

假期需要一种不同的方式。这是一个孤立的案例。这里没有分时,Bars()函数经常返回零,并且不记录计算的指标柱数,即也为零。如果用于计算的源数据中有任何错误,则指标应返回零。这意味着它将等待到下一个分时,并尝试再次计算。但在假期,除了第一次启动外,没有其他活动。

在第一次启动时,指标将首先清除缓冲区数组,然后进行计算。但是,由于计算数据不足,或者仅仅因为prev_calculated将返回零值,计算可能会失败。指标将退出OnCalculate(),再次返回零。因此,如果您通过右键单击菜单更新图表(这被视为分时模拟),指标将再次看到它是错误计算的,并将再次初始化缓冲区,考虑到这是第一次启动。这种行为将继续,并且刚刚在图表上呈现的绘图缓冲区数据将不断被擦除。不过有一个解决方案。

如果在第一次启动时无法立即计算指标,那么您可以等待20-30秒,等待下一个分时,并以编程方式模拟分时。这可以使用ChartSetSymbolPeriod()函数来完成。如果调用它并指定当前图表交易品种/周期,将会重新计算图表上运行的指标。因此,即使没有分时,也可以计算图表上的指标。

二十秒的等待足以加载指标交易品种/周期所需的历史记录并进行计算。但我们需要一个标志,表明指标已经计算完毕(因为prev_calculated返回零),如果我们不设置计算成功标志,指标将再次清除其缓冲区。因此,我们可以通过简单地看到指标对象的计算柱数等于其交易品种/周期上的柱数来理解指标已经成功计算。如果Bars()返回零,那么我们可以用另一种方式找到所需交易品种/周期的可用柱数(不要忘记,我们谈论的是在当前图表上运行的另一个指标或 EA 交易中计算的多交易品种、多周期指标)。在SeriesInfoInteger()函数中,我们可以获得交易品种/周期的可用历史记录的开始和结束日期:

 
 

SeriesInfoInteger(symbol,period,SERIES_FIRSTDATE); // The very first date for a period and symbol at the moment SeriesInfoInteger(symbol,period,SERIES_LASTBAR_DATE); // Time of opening the last bar for the period and symbol

根据这两个日期和时间序列图表周期,即使Bars(symbol,period)或SeriesInfoInteger(symbol,period,SERIES_BARS_COUNT)返回零,我赫兹量化交易软件也可以轻松计算可用柱的数量。

在已经接收到所有数据并且已经正确地计算出指标之后,设置计算成功标志。在limit>1的条件下检查此标志(在这种情况下,需要使用“空”值初始化指示器缓冲区数组)。在第一次启动时,成功标志被重置,数组被初始化,并尝试计算非当前图表交易品种/周期上的指标。如果计算失败,请等待20秒(根据计时器),等待下一个分时。

如果是周末,则不会有分时,20秒后会发送一个命令,设置图表的当前交易品种和周期,以模拟分时。当重新启动计时器中的指标时(20秒后),数据应该已经加载,并且指标的计算应该没有错误。在这种情况下,将设置计算成功标志。下一次激活计时器时,会检查此标志,如果已设置,则不会模拟分时。有三种这样的尝试来绘制指标缓冲区。在三次尝试之后,成功标志被强制设置,并且模拟分时的尝试被停止。如果指示器无法计算三个模拟分时,则只能手动操作:使用右键菜单刷新,或者来回切换图表时间框架,以完成整个分时模拟过程,并再次加载数据。

这就是理论。让我们继续练习:创建多指标类。

MSTF 指标的基类

在终端文件夹\MQL5\Include\IndMSTF\中,让我们为CIndMSTF类创建一个新文件IndMSTF.mqh。类必须从标准库基本对象CObject继承。基本对象文件必须连接到创建的新类文件:

 
 

//+------------------------------------------------------------------+ //| IndMSTF.mqh | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- includes #include <Object.mqh> //+------------------------------------------------------------------+ //| Base class of the multi-symbol multi-period indicator | //+------------------------------------------------------------------+ class CIndMSTF : public CObject { private: protected: public: //--- Constructor/destructor CIndMSTF(); ~CIndMSTF(); };

在我们将方法添加到类的各个部分之前,赫兹量化交易软件添加一些宏替换、枚举和指标缓冲区的结构。

我们将需要第二个计时器来跟踪两个时间段:

  • 90秒计时器,之后我们将转到不在当前交易品种/周期上计算的指标的时间序列;

  • 20秒计时器,我们将在周末模拟分时来绘制指标。

让我们输入宏替换来设置这两个时间段的等待时间:

 
 

//--- includes #include <Object.mqh> //--- defines #define TIMER_COUNT_1 (90) // Timer 1 size. Must not be more than two minutes (120) #define TIMER_COUNT_2 (20) // Timer 2 size. 值太小会迅速触发分时模拟,这在活跃的市场中是不可取的

客户端中的不同标准指标属于不同的类别。为了让我们按类别对创建的指标进行排序或创建与任何类别相关的指标列表,让我们写一个不同指标类别的枚举:

 
 

//--- defines #define TIMER_COUNT_1 (90) // Timer 1 size. Must not be more than two minutes (120) #define TIMER_COUNT_2 (20) // Timer 2 size. Too small values quickly trigger tick emulation, which is not desirable in an active market //--- enums enum ENUM_IND_CATEGORY // Indicator categories { IND_CATEGORY_NONE, // Not set IND_CATEGORY_TREND, // Trend IND_CATEGORY_OSCILLATOR, // Oscillators IND_CATEGORY_VOLUME, // Volume IND_CATEGORY_WILLIAMS, // Bill Williams IND_CATEGORY_CUSTOM, // Custom };

  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值