外汇EA编写教程:MQL5 中创建订单号指标

简介

交易中,最好是能掌握尽可能多的信息,如此才能得到价格变动的详细图景。您可以使用订单号图表。我们试试在 MQL5 中创建一个订单号图表吧。

本文会讲到两个指标的创建:订单号价格图表,以及绘制包含一个指定订单号的烛形图的“订单号烛形图”图表。已考虑将每个指标接收到的价格值写入文件中,以构造客户端重启后要使用的指标数据(其它程序亦可使用该数据)。

创建订单号指标

我们一起在 MQL5 中编写一个于图表上绘制订单号数据的指标吧。图 1 所示为此类指标的一个示例:

图 1.订单号图表示例

此指标会标绘两条线:Bid 与 Ask 价格。每条线的绘制均可于该指标的选项中关闭。

指标会将从经纪人处接收到的当前交易品种的价格,以下述格式保存于一个文本文件中:Server 时间、Bid 价格及 Ask 价格:

2010.03.26 19:43:02 1.33955 1.33968

文件名称与金融工具名称相对应(比如 EURUSD.txt)。文件路径如下:MT5_Folder/MQL5/Files。文件的附加目录和文件名前缀可于指标选项中指定(存在多个指标时有用,附至交易品种相同的图表)。

想要创建一个指标,启动 MetaTrader 5 客户端并按 F4 键启动 MetaQuotes 语言编辑器。我们开始编写程序代码。

我们会指明:指标应于价格图表下方的某个独立窗口内标绘:

// 指标在独立窗口中展现
#property indicator_separate_window

因为要绘制两条指标线(分别为 Bid 和 Ask 价格),所以我们必须采用两个图形标绘:

// 使用2个图形绘制:卖价线和买价线
#property indicator_plots 2

我们必须指定两个指标的缓冲区,其中包含待于图表上标绘的数据:

//2个指标缓存
#property indicator_buffers 2

我们针对每一条指标线,分别定义其绘图类型 DRAW_LINE (线)、绘图风格 STYLE_SOLID (实线)以及文本标签 “Bid” 及 “Ask”:

//卖价线的绘图类型
#property indicator_type1 DRAW_LINE
// 卖价线的绘图颜色
#property indicator_color1 Red
// 卖价线的绘图风格
#property indicator_style1 STYLE_SOLID
// 卖价线的文本标签
#property indicator_label1 "Bid"
// 买价线的绘图类型
#property indicator_type2 DRAW_LINE
// 买价线的绘图颜色
#property indicator_color2 Blue
// 买价线的绘图风格
#property indicator_style2 STYLE_SOLID
// 买价线的文本标签
#property indicator_label2 "Ask"

 我们来指定输入变量,其值可由用户通过指标的选项菜单更改。

// BidLineEnable代表是否显示卖价线
input bool BidLineEnable=true; // 显示卖价线
// AskLineEnable代表是否显示买价线
input bool AskLineEnable=true; // 显示买价线
// path_prefix定义路径和文件名前缀
input string path_prefix=""; // 文件名前缀

BidLineEnable  AskLineEnable  变量允许您启用和禁用指标中 Bid 与 Ask 线的显示。path_prefix 变量允许您指定位于文件名前方的前缀。利用此变量,您还可以指定到某个子目录的路径,比如说,如果 path_prefix = “MyBroker/test_”,则文件路径如下:”MetaTrader5_Folder/MQL5/Files/MyBroker”,对应交易品种“欧元兑美元”,文件名会是 “test_EURUSD.txt”。

我们在全局层面声明那些会在指标各种函数中使用的变量,而这些变量的值则会在指标各次调用之间被保存:

// tick_stored变量是已存储报价的个数
int ticks_stored;
// BidBuffer[]和AskBuffer[]数组是指标的缓存
double BidBuffer[],AskBuffer[];

tick_stored 变量将用于存储可用报价的数量。BidBuffer[]AskBuffer[]  为动态数组,用作指标缓冲区;而图表上标绘为 Bid 与 ask 线的价格数据,则存储于这些缓冲区中。

OnInit 函数会指明包含标绘数据的 BidBuffer[] 与 AskBuffer[] 数组。我们指定:指标缓冲区值为零的数据,不得于图表上标绘。

void OnInit()
  {
   // BidBuffer[]是一个指标缓存数组
   SetIndexBuffer(0,BidBuffer,INDICATOR_DATA);
   // AskBuffer[]是一个指标缓存数组
   SetIndexBuffer(1,AskBuffer,INDICATOR_DATA);
   // 为卖价线设置EMPTY_VALUE
   PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0);
   // 买价线设置EMPTY_VALUE
   PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0);
  }

现在我们创建 OnCalculate 函数,并在其被调用时列出传递给该函数的所有参数:

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])

我们来声明变量:

// file_handle变量定义文件的句柄
// BidPosition和AskPosition是字符串中头寸的卖出和买价;
//line_string_len是从文件中读取的字符串的长度,i是循环计数器;
int file_handle,BidPosition,AskPosition,line_string_len,i;
// last_price_bid是最新的卖出报价
double last_price_bid=SymbolInfoDouble(Symbol(),SYMBOL_BID);
// last_price_ask是最新的买入报价
double last_price_ask=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
// filename是文件名,the file_buffer是一个字符串,
// 用来读取和写入字符串数据的缓存
string filename,file_buffer;

整数型 file_handle 变量会被用于存储文件操作中文件的句柄;BidPositionAskPosition – 会被用于存储字符串开始位置的 Bid 和 Ask 价格;line_string_len – 会被用于存储由文件读取的字符串长度;i  变量则会被用作一个循环计数器。上一次接收到的 Bid 和 Ask 价格值则被存储于 last_price_bid  与 last_price_ask 变量中。文件名字符串变量用于存储文件名,而 file_buffer 字符串则用于文件的读取或写入。

文件名由 path_prefix 变量、金融工具的名称以及 “.txt” 文件扩展名构造而成。与利用加法操作符序连相比,使用 StringConcatenate 函数更为可取,因为它运行更快、更节约内存。

// 由path_prefix变量和交易品种
// 以及“.Txt”构成的文件名
StringConcatenate(filename,path_prefix,Symbol(),".txt");

我们利用 FileOpen 函数打开文件以备后用:

// 打开一个文件供读写,编码类型ANSI,读共享模式
file_handle=FileOpen(filename,FILE_READ|FILE_WRITE|FILE_ANSI|FILE_SHARE_READ);

因为我们会读取并向文件写入,所以我们采用 FILE_READ 与 FILE_WRITE 标志。FILE_ANSI 标志表明会使用 ANSI 代码页(默认 Unicode),FILE_SHARE_READ 标志则意味着允许其它应用程序在使用它时共享其读取权限。

该指标初次启动时没有任何数据(或是图表时段已更改):

 // 在首次执行OnCalculate函数时,我们从文件中读取报价
 if(prev_calculated==0)
  {
   // 读取文件的首行并确定字符串的长度
   line_string_len=StringLen(FileReadString(file_handle))+2;
   // i如果文件很大(包含了大于rates_total/2个报价)
   if(FileSize(file_handle)>(ulong)line_string_len*rates_total/2)
     {
      // 设置文件指针,读取最近的rates_total/2个报价
      FileSeek(file_handle,-line_string_len*rates_total/2,SEEK_END);
      // 将文件指针移动到下一行的开头
      FileReadString(file_handle);
     }
   // 如果文件大小很小
   else
     {
      // 将文件指针移动到文件的开头
      FileSeek(file_handle,0,SEEK_SET);
     }
   // 重置报价计数器
   ticks_stored=0;
   // 一直读到文件结尾
   while(FileIsEnding(file_handle)==false)
    {
      // 从文件中读取一个字符串
      file_buffer=FileReadString(file_handle);
      // 如果字符串长度大于6,处理该字符串
      if(StringLen(file_buffer)>6)
        {
         // 在该行中找到卖价的开始位置
         BidPosition=StringFind(file_buffer," ",StringFind(file_buffer," ")+1)+1;
         // 在该行中找到买价的开始位置
         AskPosition=StringFind(file_buffer," ",BidPosition)+1;
         // 如果要绘制卖价线,将其加到BidBuffer[]数组中
         if(BidLineEnable) 
         BidBuffer[ticks_stored]=StringToDouble(StringSubstr(file_buffer,BidPosition,AskPosition-BidPosition-1));
         // 如果要绘制买价线,将其加到AskBuffer[]数组中
         if(AskLineEnable) 
         AskBuffer[ticks_stored]=StringToDouble(StringSubstr(file_buffer,AskPosition));
         // 报价计数器值加一
         ticks_stored++;
        }
     }
  }

我们会将可通过文件读取的报价的数量限制为图表上柱数的一半。首先,我们从文件中读取字符串并确定其长度。行末尾有两个带 10 与 13 代码的附加字符(”newline” 与 “carriage return”),所以我们必须将行长度加 2。

我们假设文件中其余行的平均长度相同。如果文件长度大于 rates_total/2 数量一行的长度结果(即,如果文件包含的报价数量比 rates_total/2 多),则我们只会读取 rates_total/2 最后报价。一定要如此,我们设置远处的文件指针,等于通过 rates_total/2 (来自文件末尾)得出的字符串长度结果,然后读取文件的一行,并将文件指针对准一行的开头。

注意:我们利用 if 操作符来对比两个值,它们存在不同的类型:文件长度为 ulong 类型,而右侧的表达式则具备 int 类型。因此,我们将右侧表达式的显性类型转换为 ulong 类型。

如果文件中包含的报价比 rates_total/2 少,则我们会将文件指针转到文件开头。

我们将报价计数器设置为零,并从文件读取行,直至抵达文件末尾。字符串处理针对那些长度大于 6 个字符的字符串执行 – 此为最小字符串长度,其中包括日期、时间、bid、ask 对应的字符以及它们之间的分隔符。我们从某个字符串提取出 Bid 和 Ask 值,如果对应行应予标绘,则从文件读取,再增加报价计数器的计数。

如果之前读取过该数据,则我们利用 FileSeek 函数将文件指针移至文件末尾(新数据被写入到文件中)。我们利用 StringConcatenate 函数生成字符串,并将利用 FileWrite 函数将其写入一个文件。如果对应行应予标绘并增加报价计数器的计数,则我们会将新的 Bid 与 Ask 价格值添加到 BidBuffer[] 和 AskBuffer[] 数组。

  // 如果数据已经被读取过
else
  {
   // 将文件指针移动到文件末端
   FileSeek(file_handle,0,SEEK_END);
   // 形成一个要写入文件的字符串
   StringConcatenate(file_buffer,TimeCurrent()," ",DoubleToString(last_price_bid,_Digits)," ",DoubleToString(last_price_ask,_Digits));
   // 将一个字符串写入文件
   FileWrite(file_handle,file_buffer);
   // 如果要绘制卖价线,将最新的卖价加到BidBuffer[]数组中
   if(BidLineEnable) BidBuffer[ticks_stored]=last_price_bid;
   // 如果要绘制买价线,将最新的买价加到AskBuffer[]数组中
   if(AskLineEnable) AskBuffer[ticks_stored]=last_price_ask;
   // 报价计数器值加一
   ticks_stored++;
  }

有人可能会问,你为什么不从 OnInit 函数内的某个文件读取数据呢?原因如下:BidBuffer[]AskBuffer[] 动态数组长度尚未定义,它会在 OnCalculate 函数被调用时指定。

我们关闭之前打开的文件:

// 关闭文件
FileClose(file_handle);

从文件读取或向 BidBuffer[]AskBuffer[] 数组添加之后,如果报价计数器大于等于图表上的柱数,则会有半数的旧报价被移除,其余报价也会被移离其原位。

// 如果报价数量大于等于图表中的柱形图
if(ticks_stored>=rates_total)
  {
   // 移除前半部分tick_stored/2个报价,转移剩余报价
   for(i=ticks_stored/2;i<ticks_stored;i++)
     {
      // 如果要绘制卖价线,将BidBuffer[]数组后tick_stored/2个元素移到前面来
      if(BidLineEnable) BidBuffer[i-ticks_stored/2]=BidBuffer[i];
      // 如果要绘制买价线,将AskBuffer[]数组后tick_stored/2个元素移到前面来
      if(AskLineEnable) AskBuffer[i-ticks_stored/2]=AskBuffer[i];
     }
   // 改变计数器的值
   ticks_stored-=ticks_stored/2;
  }

指标缓冲区的 BidBuffer[] 与 AskBuffer[] 数组并非时间序列,所以最近的元素索引为 ticks_stored-1,而最近的图表索引为 rates_total-1。为将它们捏合为同一层级,我们利用 PlotIndexSetInteger 函数来平移指数行:

// 移动卖价线使其和价格图表对齐
PlotIndexSetInteger(0,PLOT_SHIFT,rates_total-ticks_stored);
// 移动买价线使其和价格图表对齐
PlotIndexSetInteger(1,PLOT_SHIFT,rates_total-ticks_stored);

最近接收到的价格值被存储到 BidBuffer[] 和 AskBuffer[],索引为 rates_total-1 (如果对应行应予标绘),以令其于指标窗口的左上角显示。

// 如果要绘制卖价线,将值存入BidBuffer []数组的最后一个元素中
// 指标窗口中显示最新的卖价  
if(BidLineEnable) BidBuffer[rates_total-1]=last_price_bid;
// 如果要绘制买价线,将值存入AskBuffer []数组的最后一个元素中  
// 在指标窗口中显示最新的买价
if(AskLineEnable) AskBuffer[rates_total-1]=last_price_ask;

OnCalculate 函数完全靠 rates_total 的返回来执行(您可以返回任何非零数值),此函数的代码以一个花括号作为结尾。

// 从OnCalculate()返回一个非零值  
return(rates_total);
}

订单号指标已经编写完成。本文结尾处链接可供下载该指标的完整源代码。

创建“订单号烛形图”指标

现在我们来编写一个标绘所谓的“订单号烛形图”的指标。传统的烛形图中每个烛形都有对应的指定时间段,“订单号烛形图”则拥有不同的结构:每个烛形都有一些预定义的、从经纪人处接收的订单号数量(等量烛形)。该指标如图 2 所示:

图 2.“订单号烛形”指标

“订单号烛形图”指标以及上面所说的订单号指标,会将所有传入的报价写入文件。数据格式与文件位置详情均相同。文件路径、名称前缀、某烛形的订单号数量以及价格类型(Bid 或 Ask),均可于指标选项中指定。

想要创建一个指标,启动 MetaTrader 5 客户端并按 F4 键启动 MetaQuotes 语言编辑器。

我们指定其应于某独立窗口内标绘:

// 指标将在独立的窗口中绘制
#property indicator_separate_window

此指标只有一个图形标绘:彩色烛形。

// 只用到一个绘图形状,彩色蜡烛线
#property indicator_plots 1

我们需要四个缓冲区来显示彩色烛形和存储价格数据值(开盘价、最高价、最低价和收盘价),以及每个烛形的价格值。我们还需要一个附加缓冲区来存储烛形的颜色索引。

// 我们需要4个缓存存储开盘、最高、最低和收盘价格,以及1个颜色索引缓存
#property indicator_buffers 5

我们来指定绘图类型:DRAW_COLOR_CANDLES – 彩色烛形。

// 确定绘图类型:彩色蜡烛线
#property indicator_type1 DRAW_COLOR_CANDLES

我们来指定烛形中应用的颜色:

// 确定蜡烛线的颜色
#property indicator_color1 Gray,Red,Green

我们来创建枚举类型的 price_types,其中包含下述各值中的一个:Bid 或 Ask:

/ / 声明枚举值
enum price_types
  (
   Bid,
   Ask
  )

我们指定输入参数,该参数可由用户通过指标的选项菜单进行修改:

// 输入参数ticks_in_candle确定
// 一根蜡烛线的tick数量
input int ticks_in_candle=16; //蜡烛线的tick数量
// price_types类型的输入参数applied_price,表示 
// 用于计算指标的数据:买价或卖价。
input price_types applied_price=0; // 价格
// path_prefix输入参数确定了路径和文件名的前缀
input string path_prefix=""; // 文件名前缀

ticks_in_candle 变量会指定某烛形对应的订单号数量。applied_price 变量会指明某个用于构造烛形图的价格的类型:Bid 或 Ask。历史订单号数据文件的目录和文件名前缀,可在 path_prefix 变量中指定。

应于指标各次调用之间保存的带值变量,均于全局层面声明。

// ticks_stored变量保存报价的数量
int ticks_stored;
// TTicksBuffer []数组用来存储价格
// OpenBuffer [], HighBuffer [], LowBuffer [] 以及 CloseBuffer [] 数组
// 用来存储蜡烛线的开盘、最高、最低和收盘价
// ColorIndexBuffer []数组用来存储彩色蜡烛线的索引
double TicksBuffer[],OpenBuffer[],HighBuffer[],LowBuffer[],CloseBuffer[],ColorIndexBuffer[];

tick_stored 变量用于存储可用报价的数量。TicksBuffer[] 数组用于存储接收到的报价,OpenBuffer[]、HighBuffer[]、LowBuffer[]CloseBuffer[] 数组则用于存储将于图表上标绘的烛形价格(开盘、最高、最低和收盘)。ColorIndexBuffer[] 数组用于存储烛形图的颜色索引。

OnInit 函数指明 OpenBuffer[]、HighBuffer[]、LowBuffer[]CloseBuffer[] 数组会用作某指标的缓冲区,ColorIndexBuffer[] 数组中包含一个烛形图颜色索引,而 TicksBuffer[] 数组则用于中间计算:

void OnInit()
  {
   // OpenBuffer[]数组是一个指标缓存
   SetIndexBuffer(0,OpenBuffer,INDICATOR_DATA);
   // HighBuffer[]数组是一个指标缓存
   SetIndexBuffer(1,HighBuffer,INDICATOR_DATA);
   // LowBuffer[]数组是一个指标缓存
   SetIndexBuffer(2,LowBuffer,INDICATOR_DATA);
   // CloseBuffer[]数组是一个指标缓存
   SetIndexBuffer(3,CloseBuffer,INDICATOR_DATA);
   // ColorIndexBuffer[]数组是颜色索引的缓存
   SetIndexBuffer(4,ColorIndexBuffer,INDICATOR_COLOR_INDEX);
   // TicksBuffer[]数组用来存储中间计算值
   SetIndexBuffer(5,TicksBuffer,INDICATOR_CALCULATIONS);

接下来,我们指定 OpenBuffer[]、HighBuffer[]、LowBuffer[]、CloseBuffer[] 和 ColorIndexBuffer[] 数组为时间序列(即最近的数据索引为 0):

   // 设置OpenBuffer[]数组为时间序列
   ArraySetAsSeries(OpenBuffer,true);
   // 设置HighBuffer[]数组为时间序列
   ArraySetAsSeries(HighBuffer,true);
   // 设置LowBuffer[]数组为时间序列
   ArraySetAsSeries(LowBuffer,true);
   // 设置CloseBuffer[]数组为时间序列
   ArraySetAsSeries(CloseBuffer,true);
   // 设置ColorIndexBuffer []数组为时间序列
   ArraySetAsSeries(ColorIndexBuffer,true);

此指标缓冲区的值为 0,不得于图表上标绘:

   // 开盘价(第0个图形)的空值不应被绘制
   PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0);
   // 最高价(第1个图形)的空值不应被绘制
   PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0);
   // 最低价(第2个图形)的空值不应被绘制
   PlotIndexSetDouble(2,PLOT_EMPTY_VALUE,0);
   // 收盘价(第3个图形)的空值不应被绘制
   PlotIndexSetDouble(3,PLOT_EMPTY_VALUE,0);

OnInit 函数的编写已经完成,我们利用一个花括号来作为此函数的结尾。

到了编写 OnCalculate 函数的时候了。我们来指定要被传递至此函数的所有参数:

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {

我们来声明将在 OnInit 函数中使用的变量。

//file_handle变量定义文件的句柄
// BidPosition和AskPosition是字符串中头寸的卖出和买价;
// line_string_len是从文件中读取的字符串的长度,
// CandleNumber确定开盘、最高、最低和收盘价的蜡烛线的编号
// i - 循环计数器;
int file_handle,BidPosition,AskPosition,line_string_len,CandleNumber,i;
// last_price_bid变量是最近接收到的卖价
double last_price_bid=SymbolInfoDouble(Symbol(),SYMBOL_BID);
// last_price_ask变量是最近接收到的买价
double last_price_ask=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
// filename是文件名,the file_buffer是一个字符串, 
// 用来读取和写入字符串数据的缓存
string filename,file_buffer;

整数型 file_handle 变量用于存储文件操作中文件的句柄;BidPositionAskPosition – 用于存储字符串开始位置的 Bid 和 Ask 价格;line_string_len – 是由文件读取的字符串长度; CandleNumber – 是算出的烛形索引; i 变量则用作一个循环计数器。

最近接收到的 Bid 和 Ask 价格值则被存储于 last_price_bid 与 last_price_ask 双型变量中。filename 字符串型变量用于存储文件名,file_buffer 是文件操作中使用的一种字符串。

TicksBuffer[] 数组的大小并非自动设置,这一点与作为指标缓冲区的 OpenBuffer[]、HighBuffer[]、LowBuffer[]、CloseBuffer[] 及 ColorIndexBuffer[] 数组不同。所以,我们将 TicksBuffer[] 数组设置为与 OpenBuffer[]、HighBuffer[]、LowBuffer[]、CloseBuffer[]ColorIndexBuffer[] 数组相同的大小。

// 设置TicksBuffer[]数组的大小
ArrayResize(TicksBuffer,ArraySize(CloseBuffer));

根据 path_prefix 变量、金融工具的名称以及 “.txt” 文件扩展名制备文件名。

// 由path_prefix变量和交易品种
// 以及“.Txt”构成的文件名
StringConcatenate(filename,path_prefix,Symbol(),".txt");

我们利用前一个指标的相关描述,利用参数打开文件。

// 打开一个文件供读写,编码类型ANSI,读共享模式
file_handle=FileOpen(filename,FILE_READ|FILE_WRITE|FILE_ANSI|FILE_SHARE_READ);

如果 OnCalculate 函数被第一次调用,且 TicksBuffer[] 数组中没有任何数据,我们则从文件读取它们:

if(prev_calculated==0)
  {
   // 读取文件的首行并确定字符串的长度
   line_string_len=StringLen(FileReadString(file_handle))+2;
   // 如果文件很大(包含了大于rates_total/2个报价)
   if(FileSize(file_handle)>(ulong)line_string_len*rates_total/2)
     {
      // 设置文件指针,读取最近的rates_total/2个报价
      FileSeek(file_handle,-line_string_len*rates_total/2,SEEK_END);
      // 将文件指针移动到下一行的开头
      FileReadString(file_handle);
     }
   // 如果文件大小很小
   else
     {
      // 将文件指针移动到文件的开头
      FileSeek(file_handle,0,SEEK_SET);
     }
   // 重置报价计数器
   ticks_stored=0;
   //一直读到文件结尾
   while(FileIsEnding(file_handle)==false)
     {
      // 从文件中读取一个字符串
      file_buffer=FileReadString(file_handle);
      // 如果字符串长度大于6,处理该字符串
      if(StringLen(file_buffer)>6)
        {
         // 在行中找到卖价的开始位置
         BidPosition=StringFind(file_buffer," ",StringFind(file_buffer," ")+1)+1;
          //在行中找到买价的开始位置
         AskPosition=StringFind(file_buffer," ",BidPosition)+1;
         // 如果使用了卖价,将其加到TicksBuffer[]数组中
         if(applied_price==0)
         TicksBuffer[ticks_stored]=StringToDouble(StringSubstr(file_buffer,BidPosition,AskPosition-BidPosition-1));
         // 如果使用了买价,将其加到TicksBuffer[]数组中
         if(applied_price==1)
         TicksBuffer[ticks_stored]=StringToDouble(StringSubstr(file_buffer,AskPosition));
         // 报价计数器值加一
         ticks_stored++;
        }
     }
  }

从文件读取报价的内容此前已有详述,与前一个指标相同。

如果报价在进入 TicksBuffer[] 数组之前已被读取,我们就会向文件写入一个新价格值,向 TicksBuffer[] 数组下达一个新价格并增加报价计数器的计数:

// 如果数据已经被读取过
else
  {
   // 将文件指针移动到文件末端
   FileSeek(file_handle,0,SEEK_END);
   // 形成一个要写入文件的字符串
   StringConcatenate(file_buffer,TimeCurrent()," ",DoubleToString(last_price_bid,_Digits)," ",DoubleToString(last_price_ask,_Digits));
   // 将一个字符串写入文件
   FileWrite(file_handle,file_buffer);
   // 如果使用了卖价,将最新的卖价加到TicksBuffer[]数组中
   if(applied_price==0) TicksBuffer[ticks_stored]=last_price_bid;
   // 如果使用了买价,将最新的买价加到TicksBuffer[]数组中
   if(applied_price==1) TicksBuffer[ticks_stored]=last_price_ask;
   // 报价计数器值加一
   ticks_stored++;
  }

文件的关闭:

// 关闭文件
FileClose(file_handle);

如果存储的报价数量达到或超过了价格图表上的柱数,我们就会移除半数的旧数据并平移其余数据:

// 如果报价数量大于等于图表中的柱形图的数量
if(ticks_stored>=rates_total)
  {
   // 移除前半部分tick_stored/2个报价,转移剩余报价
   for(i=ticks_stored/2;i<ticks_stored;i++)
     {
      // 将数据移到TicksBuffer[]数组开头的tick_stored/2个元素中
      TicksBuffer[i-ticks_stored/2]=TicksBuffer[i];
     }
   // 改变报价计数器
   ticks_stored-=ticks_stored/2;
  }

我们来计算每个烛形的 OHLC 值,并将这些值置入相应的指标缓冲区:

   // 为CandleNumber分配一个无效的蜡烛线数字
   CandleNumber=-1;
   // 查找所有能构成蜡烛线的价格数据
   for(i=0;i<ticks_stored;i++)
     {
      //如果这个蜡烛线已经形成
      if(CandleNumber==(int)(MathFloor((ticks_stored-1)/ticks_in_candle)-MathFloor(i/ticks_in_candle)))
        {
         // 当前报价仍就是当前蜡烛线的收盘价
         CloseBuffer[CandleNumber]=TicksBuffer[i];
         // 如果当前价格大于当前蜡烛线的最高价,
          // 那么它将是蜡烛线的新高价
         if(TicksBuffer[i]>HighBuffer[CandleNumber]) HighBuffer[CandleNumber]=TicksBuffer[i];
         // 如果当前价格小于当前蜡烛线的最低价,那么它将是蜡烛线的新低价 
          //那么它将是蜡烛线的新低价
         if(TicksBuffer[i]<LowBuffer[CandleNumber]) LowBuffer[CandleNumber]=TicksBuffer[i];
         // 如果蜡烛线看涨,它的颜色索引将为2(绿色)
         if(CloseBuffer[CandleNumber]>OpenBuffer[CandleNumber]) ColorIndexBuffer[CandleNumber]=2;
         // 如果蜡烛线看跌,它的颜色索引将为1(红色)
         if(CloseBuffer[CandleNumber]<OpenBuffer[CandleNumber]) ColorIndexBuffer[CandleNumber]=1;
         // 如果开盘和收盘价一样,它的颜色索引将为0(灰色)
         if(CloseBuffer[CandleNumber]==OpenBuffer[CandleNumber]) ColorIndexBuffer[CandleNumber]=0;
        }
      // 如果这个蜡烛线还没被计算过
      else
        {
         // 让我们来确定蜡烛线的索引
         CandleNumber=(int)(MathFloor((ticks_stored-1)/ticks_in_candle)-MathFloor(i/ticks_in_candle));
         // 当前报价是蜡烛线的开盘价
         OpenBuffer[CandleNumber]=TicksBuffer[i];
         // 当前报价是蜡烛线的最高价
         HighBuffer[CandleNumber]=TicksBuffer[i];
         // 当前报价是蜡烛线的最低价
         LowBuffer[CandleNumber]=TicksBuffer[i];
         // 当前报价是蜡烛线的收盘价
         CloseBuffer[CandleNumber]=TicksBuffer[i];
         // 此蜡烛线的颜色索引将为0(灰色)
         ColorIndexBuffer[CandleNumber]=0;
        }
     }

OnCalculate 函数的执行要靠返回非零值来完成,也就是说,TicksBuffer[] 数组已有数据,根本没有必要在下次调用此函数时读取。我们用后花括号作为此函数的结尾。

 // 从OnCalculate()返回一个非零值 
 return(rates_total);
}

本文末尾处有一个可下载此指标完整源代码的链接。

总结

我们已于本文中创建两个指标:订单号图表指标及“订单号烛形图”指标。

本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/60

附加的文件 |

下载ZIP
tickindicator.mq5
(12.68 KB)
tickcolorcandles.mq5
(16.96 KB)

 

 


MyFxtop迈投-靠谱的外汇跟单社区,免费跟随高手做交易!

 

免责声明:本文系转载自网络,如有侵犯,请联系我们立即删除,另:本文仅代表作者个人观点,与迈投财经无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。

著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。

風險提示

MyFxtops邁投所列信息僅供參考,不構成投資建議,也不代表任何形式的推薦或者誘導行為。MyFxtops邁投非外匯經紀商,不接觸妳的任何資金。 MYFXTOPS不保證客戶盈利,不承擔任何責任。從事外彙和差價合約等金融產品的槓桿交易具有高風險,損失有可能超過本金,請量力而行,入市前需充分了解潛在的風險。過去的交易成績並不代表以後的交易成績。依據各地區法律法規,MyFxtops邁投不向中國大陸、美國、加拿大、朝鮮居民提供服務。

邁投公眾號

聯繫我們

客服QQ:981617007
Email: service@myfxtop.com

MyFxtops 邁投