使用内置指标

作为使用内置指标的简单入门示例,我们将使用iStochastic调用。该指标函数的原型如下:

int iStochastic(const string symbol, ENUM_TIMEFRAMES timeframe,
int Kperiod, int Dperiod, int slowing,
ENUM_MA_METHOD method, ENUM_STO_PRICE price)

如我们所看到的,除了标准参数 symboltime frame 外,随机指标还有几个特定参数:

  • Kperiod ― 计算 % K 线的柱线数量
  • Dperiod ― % D 线的主平滑周期
  • slowing ― 二次平滑周期(减速因子)
  • method ― 平均(平滑)方法
  • price ― 随机指标计算方法

让我们尝试创建一个自己的指标UseStochastic.mq5,该指标会将随机指标的值复制到其缓冲区中。由于随机指标有两个缓冲区,我们同样预留两个缓冲区:分别是“主线”和“信号线”。

#property indicator_separate_window
#property indicator_buffers 2
#property indicator_plots   2
   
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrBlue
#property indicator_width1  1
#property indicator_label1  "St'Main"
   
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrChocolate
#property indicator_width2  1
#property indicator_label2  "St'Signal"
#property indicator_style2  STYLE_DOT

在输入变量中,我们会提供所有必需的参数。

input int KPeriod = 5;
input int DPeriod = 3;
input int Slowing = 3;
input ENUM_MA_METHOD Method = MODE_SMA;
input ENUM_STO_PRICE StochasticPrice = STO_LOWHIGH;

接下来,我们将介绍用于指标缓冲区的数组以及用于描述符的全局变量。

double MainBuffer[];
double SignalBuffer[];
   
int Handle;

我们将在 OnInit中进行初始化。

int OnInit()
{
   IndicatorSetString(INDICATOR_SHORTNAME,
      StringFormat("Stochastic(%d,%d,%d)"KPeriodDPeriodSlowing));
   // binding of arrays as buffers
   SetIndexBuffer(0MainBuffer);
   SetIndexBuffer(1SignalBuffer);
   // getting the descriptor Stochastic
   Handle = iStochastic(_Symbol_Period,
      KPeriodDPeriodSlowingMethodStochasticPrice);
   return Handle == INVALID_HANDLE ? INIT_FAILED : INIT_SUCCEEDED;
}

现在,在 OnCalculate中,一旦句柄准备就绪,我们就需要使用 CopyBuffer 函数读取数据。

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &data[])
{
   // waiting for the calculation of the stochastic on all bars
   if(BarsCalculated(Handle) != rates_total)
   {
      return prev_calculated;
   }
   
   // copy data to our two buffers
   const int n = CopyBuffer(Handle00rates_total - prev_calculated + 1,
      MainBuffer);
   const int m = CopyBuffer(Handle10rates_total - prev_calculated + 1,
      SignalBuffer);
   
   return n > -1 && m > -1 ? rates_total : 0;
}

请注意,我们要调用两次CopyBuffer:分别针对每个缓冲区(第二个参数中的 0 和 1)。尝试读取一个不存在的缓冲区索引(例如 2),将会生成错误,且我们不会收到任何数据。

我们的指标并非特别有用,因为它没有为原始随机指标添加任何内容,也没有对其读数进行分析。另一方面,我们可以确保标准终端指标的线条与在 MQL5 中创建的指标线条完全一致(正如我们在完全自定义指标中所做的那样,也可以轻松添加水平位和精度设置,但这样一来就很难区分复制指标与原始指标)。

标准随机指标与基于 iStochastic 函数的自定义指标

标准随机指标与基于 iStochastic 函数的自定义指标

为了演示终端对指标的缓存机制,在 OnInit函数中添加了几行代码。

   double array[];
   Print("This is very first copy of iStochastic with such settings=",
      !(CopyBuffer(Handle0010array) > 0));

在这里,我们使用了一个与已知特性相关的技巧:在指标创建后需要一定时间进行计算,因此在获取句柄后无法立即从缓冲区读取数据。这种情况适用于“冷启动”,即当终端内存的缓存中尚未存在指定参数的指标时。如果存在现成的指标,则我们可以立即访问缓冲区。

编译新指标后,应在同一交易品种和时间范围的两个图表上加载该指标的两个副本。第一次加载时,日志中将显示标志为 true的消息(这是第一个副本),而第二次(以及后续多次,如果有多个图表)则会显示为false.。你也可以在图表上手动添加标准“Stochastic Oscillator”指标(使用默认设置或后续将在 Use Stochastic中应用的设置),然后再运行 Use Stochastic:此时我们同样应获得 false

现在让我们尝试基于标准指标开发一些原创性内容。以下UseM1MA.mq5指标用于在 M5 及更高时间范围(主要面向日间交易)上计算每根柱线的平均价格。它会累加在工作(更高)时间范围上中每根特定柱线时间戳范围内的 M1 柱线价格。这使得你能够比标准价格类型(CloseOpenMedianTypicalWeighted 等)更准确地估算柱线的有效价格。此外,我们还将提供在特定周期内对这些价格进行平均的可能性,但需做好心理准备,生成的线条可能不会特别平滑。

该指标将显示在主窗口中,且包含一个单独的缓冲区。可以通过 3 个参数更改设置:

input uint _BarLimit = 100// BarLimit
input uint BarPeriod = 1;
input ENUM_APPLIED_PRICE M1Price = PRICE_CLOSE;

BarLimit 设置用于计算的最近历史柱线数量。这一点很重要,因为与分钟级 M1 相比,时间范围很大的图表可能需要处理大量柱线(例如,在 24/7 交易中,一根日线 D1 包含 1440 根 M1 柱线)。这可能导致需要下载额外数据并等待同步完成。先用保守的默认设置(工作时间范围的 100 根柱线)进行测试,再将该参数设为 0,即表示无限制处理。

然而,即使将BarLimit设置为 0,指标也可能不会针对更早时间范围的全部可见历史进行计算:如果终端对图表中的柱线数量设有限制,则这也会影响对 M1 柱线的请求。换言之,分析深度取决于历史数据中允许的最大 M1 柱线数量所对应的时间范围。

BarPeriod 设置进行平均计算的更大时间范围的柱线数量。此处默认值为 1,这使得能够单独查看每根柱线的有效价格。

M1Price参数指定用于 M1 柱线计算的价格类型。

在全局范围内,为缓冲区、描述符和自更新标志定义数组,我们需要这些来等待构建“外部”M1 时间范围的时间序列。

double Buffer[];
   
int Handle;
int BarLimit;
bool PendingRefresh;
   
const string MyName = "M1MA (" + StringSubstr(EnumToString(M1Price), 6)
   + "," + (string)BarPeriod + "[" + (string)(PeriodSeconds() / 60) + "])";
const uint P = PeriodSeconds() / 60 * BarPeriod;

此外,还会在此处生成指标名称和平均周期PPeriodSeconds函数可返回当前时间范围中单根柱线内的秒数,使用该函数能够计算当前柱线内包含的 M1 柱线数量: PeriodSeconds() / 60 (60 秒为 M1 柱线的时长)。

通常通过 OnInit执行初始化操作。

int OnInit()
{
   IndicatorSetString(INDICATOR_SHORTNAMEMyName);
   IndicatorSetInteger(INDICATOR_DIGITS_Digits);
   
   SetIndexBuffer(0Buffer);
   
   Handle = iMA(_SymbolPERIOD_M1P0MODE_SMAM1Price);
   
   return Handle != INVALID_HANDLE ? INIT_SUCCEEDED : INIT_FAILED;
}

为获取更高时间范围柱线的平均价格,我们采用简单移动平均线,调用 iMA并设置为 MODE_SMA 模式。

下面给出的OnCalculate函数已进行简化处理。在第一次运行或历史数据发生更改时,我们会清空缓冲区并填充BarLimit变量(这是必需的,因为输入变量无法编辑,而我们希望将值 0 解释为可用于计算的最大柱线数量)。在后续调用中,仅从 prev_calculated位置开始的最后几根柱线(数量不超过 BarLimit)清除缓冲区元素。

int OnCalculate(ON_CALCULATE_STD_FULL_PARAM_LIST)
{
   if(prev_calculated == 0)
   {
      ArrayInitialize(BufferEMPTY_VALUE);
      if(_BarLimit == 0
      || _BarLimit > (uint)rates_total)
      {
         BarLimit = rates_total;
      }
      else
      {
         BarLimit = (int)_BarLimit;
      }
   }
   else
   {
      for(int i = fmax(prev_calculated - 1, (int)(rates_total - BarLimit));
         i < rates_total; ++i)
      {
         Buffer[i] = EMPTY_VALUE;
      }
   }

在从创建的 iMA指标读取数据之前,需要等待数据准备就绪:为此我们将 BarsCalculated 与 M1 柱线数量进行比较。

   if(BarsCalculated(Handle) != iBars(_SymbolPERIOD_M1))
   {
      if(prev_calculated == 0)
      {
         EventSetTimer(1);
         PendingRefresh = true;
      }
      return prev_calculated;
   }
   ...

如果数据尚未就绪,我们将启动一个计时器,在 1 秒后再次尝试读取。

接下来,进入算法的主要计算部分,因此如果计时器仍在运行,则必须停止计时器。如果下一个分时报价事件的发生速度快于 1 秒,而iMAM1 已经完成,则可能出现这种情况。此时,合理的做法是调用相应的函数 EventKillTimer。但它的行为存在一个细微差别:它不会清空指标类型 MQL 程序的事件队列,如果队列中已经存在一个计时器事件,则OnTimer处理程序仍会被调用一次。为避免图表的不必要更新,我们使用自己的变量 Pending Refresh控制这一过程,在此处将其赋值为 false

   ...
   Pending Refresh =false;// data is ready, the timer will idle
   ...

以下是 OnTimer处理程序中的实现方式:

void OnTimer()
{
   EventKillTimer();
   if(PendingRefresh)
   {
      ChartSetSymbolPeriod(0_Symbol_Period);
   }
}

让我们回到 OnCalculate并展示主要工作流程。

   for(int i = fmax(prev_calculated - 1, (int)(rates_total - BarLimit));
      i < rates_total; ++i)
   {
      static double result[1];
      
      // get the last bar M1 corresponding to the i-th bar of the current timeframe
      const datetime dt = time[i] + PeriodSeconds() - 60;
      const int bar = iBarShift(_SymbolPERIOD_M1dt);
      
      if(bar > -1)
      {
         // request MA value on M1
         if(CopyBuffer(Handle0bar1result) == 1)
         {
            Buffer[i] = result[0];
         }
         else
         {
            Print("CopyBuffer failed: "_LastError);
            return prev_calculated;
         }
      }
   }
   
   return rates_total;
}

该指标的操作通过以下 EURUSD,H1 图表展示:蓝色线条对应默认设置。每个值通过对 60 根柱线 M1 的 PRICE_CLOSE 进行平均计算得出。橙色线条额外采用 5 根 H1 柱线的平滑处理,且基于 M1 PRICE_TYPICAL 计算得出。

EURUSD,H1 上的 UseM1MA 指标的两个实例

EURUSD,H1 上的 UseM1MA 指标的两个实例

本书展示了UseM1MASimple.mq5的简化版。我们将以下细节留在后台处理:对最后一根(未完成的)柱线进行平均计算、空柱线(即没有对应 M1 数据的柱线)的处理、PLOT_DRAW_BEGIN 特性的正确设置,以及对新柱形出现时平均值计算短期延迟现象的控制。完整版可在文件 UseM1MA.mq5中获取。

作为基于标准指标构建指标的最后一个示例,我们来分析对 IndUnityPercent.mq5指标的改进,该指标曾在 多货币和多时间范围指标章节中介绍过。第一个版本使用 Close价格进行计算,通过CopyBuffer 获取这些价格。在新版 UseUnityPercentPro.mq5中,让我们将该方法替换为读取iMA 指标数据。这将允许我们实现以下新功能:

  • 某个给定周期的平均价格
  • 选择平均值计算方法
  • 选择用于计算的价格类型

源代码的改动是最小的。我们为 iMA句柄添加了 3 个新参数和一个全局数组:

input ENUM_APPLIED_PRICE PriceType = PRICE_CLOSE;
input ENUM_MA_METHOD PriceMethod = MODE_EMA;
input int PricePeriod = 1;
...   
int Handles[];

在辅助函数 InitSymbols中(该函数从 OnInit 调用,用于解析包含工作交易品种列表的字符串),我们为新数组添加了内存分配(其 SymbolCount 大小由该列表确定)。

string InitSymbols()
{
   SymbolCount = StringSplit(Instruments, ',', Symbols);
   ...
   ArrayResize(HandlesSymbolCount);
   ArrayInitialize(HandlesINVALID_HANDLE);
   ...
   for(int i = 0i < SymbolCounti++)
   {
      ...
      Handles[i] = iMA(Symbols[i], PERIOD_CURRENTPricePeriod0,
         PriceMethodPriceType);
   }
}

在同一函数末尾,我们将创建所需附属指标的描述符。

在执行主要计算的 Calculate函数中,我们会将以下形式的调用:

CopyClose(Symbols[j], _Periodtime0time1w);

替换为:

CopyBuffer(Handles[j], 0time0time1w); // j-th handle, 0-th buffer

为清晰起见,我们还为指标的简称补充了三个新参数。

   IndicatorSetString(INDICATOR_SHORTNAME,
      StringFormat("Unity [%d] %s(%d,%s)"workCurrencies.getSize(),
      StringSubstr(EnumToString(PriceMethod), 5), PricePeriod,
      StringSubstr(EnumToString(PriceType), 6)));

结果如下:

适用于主要外汇货币对的 UseUnityPercentPro 多交易品种指标

适用于主要外汇货币对的 UseUnityPercentPro 多交易品种指标

此处显示的是一篮子 8 种主要外汇货币(默认设置),采用 11 根柱线并基于typical价计算出的平均值。两条粗线对应当前图表中货币的相对价值:欧元以蓝色标示,美元以绿色标示。