English Русский Español Deutsch 日本語 Português
信息的存储和阅览

信息的存储和阅览

MetaTrader 4交易 | 19 一月 2016, 08:44
1 558 0
Andrey Khatimlianskii
Andrey Khatimlianskii

1. 简介

您是否花过很多时间来尝试在您EA交易所写的记录文件中寻找重要信息?或者, 也许您瞪着Comment()函数所显示的小小的单色字母而疲惫不堪?其实, 它们在交易中是很重要的. 如果您了解我的意思, 这篇文章就是为您而写.

以下就是我写这篇文章所要解决的主要问题:

  • 怎样创建一个函数, 它可以被EA交易使用把信息写进自己的记录文件中(和Print()函数相反)?
  • 怎样创建一个工具, 它可以更加美观地显示信息: 有颜色和字体的变化?


2. EA交易自己的记录文件

就像我说的那样, Print()函数在记录EA交易运行时生成的信息时并不总是那么方便. 特别是当有几个EA交易同时在一个相同终端上运行时就更加明显. 每个EA交易都把它自己的信息写到记录文件中, 这样就很难在其中找东西了. 这种情况下, 对此类信息的分析就不用提了:这非常费力和麻烦.

这个问题可以用比较简单的方法解决. 每个EA交易必须有它自己的记录文件. 信息都会被写进这些独立的记录文件, 而不是写进一个通用的文件中. 为了尽可能简单地使用此方式, 让我们把代码写成一个函数.

以下就是描述这个函数必须做到的:

  • 使用唯一名称来创建文件di
  • 当有需要时在其中写入信息
  • 当EA交易结束运行时关闭文件, 以便其他应用程序可以使用该文件

清晰明了. 唯一有争议的一点是是否有必要在写完每个记录时关闭文件. 一方面, 这样就可以在EA交易运行过程中使用其他应用程序打开此文件了. 但是, 另一方面, 这样对EA交易进行下一条记录可能是不利的, 因为文件可能被其他应用程序使用了. 如果出现这种情况, 信息可能就直接丢掉了. 这是不能允许的, 特别是有些程序打开文件只是为了读取, 而不愿影响 MetaTrader 的运行.

因为打开和关闭文件只进行一次, 对应的代码可以分别放到 init() 和 deinit() 函数中. 为了让它只占用最小的空间, 让我们把它们也做成函数:

int log_handle = -1;
 
//+------------------------------------------------------------------+
// void log_open( string ExpertName = "Expert" )
//
// 用于打开EA交易独立记录文件的函数.
// 创建文件的目录:
// "...\MetaTrader 4\experts\files\logs\ExpertName\"
// 文件名就是文件的记录日期, 格式为 "YYYY.MM.DD"
//+------------------------------------------------------------------+
void log_open( string ExpertName = "Expert" )
 {
     string log_name = "logs\\" + ExpertName + " (" + Symbol() + ", " + 
                   strPeriod( Period() ) + ")\\" + TimeToStr( LocalTime(),
                   TIME_DATE ) + ".txt";
  log_handle = FileOpen ( log_name, FILE_READ | FILE_WRITE, " " );
    
  if ( log_handle < 0 )
     { 
         int _GetLastError = GetLastError();
    Print( "FileOpen( ", log_name, ", FILE_READ | FILE_WRITE, \" \" ) - 错误 #", 
                     _GetLastError );
    return(-1);
   }
 }
string strPeriod( int intPeriod )
 {
     switch ( intPeriod )
     {
          case PERIOD_MN1: return("Monthly");
    case PERIOD_W1:  return("Weekly");
    case PERIOD_D1:  return("Daily");
    case PERIOD_H4:  return("H4");
    case PERIOD_H1:  return("H1");
    case PERIOD_M30: return("M30");
    case PERIOD_M15: return("M15");
    case PERIOD_M5:  return("M5");
    case PERIOD_M1:  return("M1");
    default:        return("UnknownPeriod");
   }
 }
 
//+------------------------------------------------------------------+
// log_close()
//
// 用于关闭EA交易自身记录文件的函数.
//+------------------------------------------------------------------+
void log_close()
 {
     if ( log_handle > 0 ) FileClose( log_handle );
 }
现在我们有了一个打开的文件, 可以在里面写信息了. 为此:
  • 把光标移动到文件末尾, 这样不会丢失信息;
  • 把记录时间写在每行开头, 这将有助于分析;
  • 把文字写到文件中;
  • 把文件存储在磁盘上;如果EA交易意外出错退出, 数据也不会丢失

以下就是满足上面需求的函数:

//+------------------------------------------------------------------+
// log( string text )
//
// 把文本行写入EA交易自身记录文件的函数.
//+------------------------------------------------------------------+
void log( string text )
 {
     int _GetLastError = 0;
  if ( log_handle < 0 )
     {
         Print( "写入记录错误!文本: ", text );
    return(-1);
   }
    
     //---- 把文件指针移动到文件末尾
    if ( !FileSeek ( log_handle, 0, SEEK_END ) )
      {
         _GetLastError = GetLastError();
    Print( "FileSeek ( " + log_handle + ", 0, SEEK_END ) - 错误 #", 
          _GetLastError );
    return(-1);
  }
    //---- 如果EA要写的一行内容不包含换行符, 
    //---- 在一行的开始部分增加记录时间
    if( text != "\n" && text != "\r\n" )
         text = StringConcatenate( TimeToStr( LocalTime(), TIME_SECONDS ), 
                             " - - - ", text );
    if( FileWrite ( log_handle, text ) < 0 )
          {
             _GetLastError = GetLastError();
     Print( "FileWrite ( ", log_handle, ", ", text, " ) - Error #", 
           _GetLastError );
     return(-1);
    }
    
    //---- 把写下的文字写到磁盘上
    FileFlush( log_handle );
 }

现在, Print 这个词可以在所有的EA交易中使用log来替换了, 另外不要忘记调用 log_open 和 log_close 函数.

这是一个使用log.mq4包含文件的非常简单的EA交易:

#include <log.mq4>
 
int init()
  {
    log_open( "log_test" );
    log( "记录文件已经被成功打开, EA交易开始工作..." );
    return(0);
  }
int deinit()
  {
    log( "关闭记录文件, EA交易结束工作..." );
    log_close();
    return(0);
  }
int start()
  {
    log( "新订单: Bid = " + DoubleToStr( Bid, Digits ) );
    return(0);
  }

3. 信息的显示

现在, 当我们解决了记录文件的问题之后, 我们可以开始"装饰"显示的信息了.

首先, 让我们考虑实现这个任务的所有可能方法. 在 MQL4 中, Comment() 函数是用于显示信息的, 但是它不适合以上描述的需求. 所以我们必须找到其他的解决方案. 一个很好的例子是含有文本的对象. 有两类这样的对象: "文本(Text)" 和 "文本标签(Text Label)". 它们之间的基本区别就是, "文本"绑定于图表的坐标(价格和时间), 而"文本标签"绑定的是窗口坐标. 因为我们需要信息在图表移动或者缩放改变的时候依然停留在原点上, 所以我们会使用"文本标签".

在MQL4中有一些函数用于创建和控制对象, 所有的名字都以Object开头. 让我们看看它们如何服务于我们的目标:

  • bool ObjectCreate(...) – 创建一个对象;
  • bool ObjectDelete(...) – 使用过后删除对象;
  • bool ObjectSet(...) – 修改对象属性, 例如锚点 (x,y);
  • bool ObjectSetText(...) – 显示文字;
  • void ObjectsRedraw() – 在文本改变之后重绘对象.

这就是我们要做的:

  • 在EA交易的 init() 函数中, 创建用于显示信息的对象;
  • 在EA交易的 deinit() 函数中, 删除所有创建的对象;
  • 在 start() 函数里, 可以修改文字, 字体颜色和所创建对象的大小.

我们将会再一次得到3个函数, 每个都完成自己的任务.


在写代码之前, 我想先说一下"文本标签"使用中的一个令人不愉快的局限. 它只能是一行, 也就是说, 它不能包含任何换行字符. 但是如果信息能够分成几行显示会更好. 这就是为什么我们会创建多个对象, 然后在其中分配数据. 我制作了5"行", 但是您可以使用任何其他的行数.

另外, 显示文字的长度也有一个限制. 所以我增加了第二"列", 也就是说, 右侧又多了5行.

这就是用于创建对象的 info_init() 函数看起来的样子:

/////////////////////////////////////////////////////////////////////////////////
// void info_init()
//
// 创建用于显示信息的对象
/////////////////////////////////////////////////////////////////////////////////
void info_init()
  {
    for( int row = 0; row <= 4; row ++ )
      {
        _LabelCreate( StringConcatenate( "InfoLabel_0", row ),   4, 15 + 15*row );
        _LabelCreate( StringConcatenate( "InfoLabel_1", row ), 270, 15 + 15*row );
      }
  }
 
/////////////////////////////////////////////////////////////////////////////////
// void _LabelCreate ( string _Name, int _XDistance, int _YDistance, int _Corner = 0 )
//
// 创建 "Text Label" 名字为 _Name.
// 坐标: х = _XDistance, у = _YDistance, corner = _Corner.
/////////////////////////////////////////////////////////////////////////////////
void _LabelCreate ( string _Name, int _XDistance, int _YDistance, int _Corner = 0 )
  {
    int _GetLastError;
 
    if( !ObjectCreate( _Name, OBJ_LABEL, 0, 0, 0 ) )
      {
        _GetLastError = GetLastError();
        if ( _GetLastError != 4200 )
          {
            Print( "ObjectCreate( \"", _Name, "\", OBJ_LABEL,0,0,0 ) - 错误 #",
                   _GetLastError );
            return(-1);
          }
      }
    if( !ObjectSet( _Name, OBJPROP_CORNER, _Corner ) )
      {
        _GetLastError = GetLastError();
        Print( "ObjectSet( \"", _Name, "\", OBJPROP_CORNER, ", _Corner, 
                                       " ) - 错误 #", _GetLastError );
      }
    if( !ObjectSet( _Name, OBJPROP_XDISTANCE, _XDistance ) )
      {
        _GetLastError = GetLastError();
        Print( "ObjectSet( \"", _Name, "\", OBJPROP_XDISTANCE, ", _XDistance, 
                                             " ) - 错误 #", _GetLastError );
      }
    if( !ObjectSet( _Name, OBJPROP_YDISTANCE, _YDistance ) )
      {
        _GetLastError = GetLastError();
        Print( "ObjectSet( \"", _Name, "\", OBJPROP_YDISTANCE, ", _YDistance, 
                                             " ) - 错误 #", _GetLastError );
      }
    if( !ObjectSetText ( _Name, "", 10 ) )
      {
        _GetLastError = GetLastError();
        Print( "ObjectSetText( \"", _Name, "\", \"\", 10 ) - Error #", _GetLastError );
      }
  }

您可以看到, 创建对象的名称为 "InfoLabel_" + 对象编号 (左边一列从 00 到 04 而右边一列从10 到 14). 对象的锚点是左上角. 前面有意留了一些空间, 因为很多用户习惯在那里查看 OHLC (当前柱的信息). 我创建的行间距离是15, 这对通常大小的文字是足够的. 这样, 初始化过程就结束了, 让我们开始去初始化. 它们看起来很接近:

/////////////////////////////////////////////////////////////////////////////////
// void info_deinit()
//
// 删除由 info_init() 函数创建的对象
/////////////////////////////////////////////////////////////////////////////////
void info_deinit()
 {
     int _GetLastError;
   for ( int row = 0; row <= 4; row ++ )
      {
          if ( !ObjectDelete( StringConcatenate( "InfoLabel_0", row ) ) )
           {
                   _GetLastError = GetLastError();
       Print( "ObjectDelete( \"", StringConcatenate( "InfoLabel_0", row ), 
                                        "\" ) - 错误 #", _GetLastError );
      }
          if( !ObjectDelete( StringConcatenate( "InfoLabel_1", row ) ) )
               {
                  _GetLastError = GetLastError();
       Print( "ObjectDelete( \"", StringConcatenate( "InfoLabel_1", row ), 
                                       "\" ) - 错误 #", _GetLastError );
      }
       }
 }

现在, 最有趣的事情就是显示信息了.

这样, 我们有了10个对象, 它们已经可以"接受"文字并显示它们了. 让我们考虑一下怎样才能使显示函数的使用尽可能简单.

首先, 使用指定数字, 而不是对象名称会更加简单.

其次, 只有两个参数必须使用: 对象编号和显示文字的编号. 其他参数都是可选的, 因为文本颜色或字体大小不是经常需要改变. 在此, 我们必须考虑如果参数被跳过应该怎样处理. 有两个选择:

  • 使用参数默认值;
  • 或者使用参数前一次的值.

我想第二个选择会更有用: 默认参数值可能在每个EA交易的代码中都会改变, 而最后使用的参数值会在每次使用函数后被自动保存.

因而, 这就是我们的函数:

void info( int LabelNumber, string Text, color Color = -1, 
              double FontSize = -1.0, string Font = "-1" )
 {
     //---- 定义对象名称对象名称
      string LabelName;
  if ( LabelNumber < 10 )
          LabelName = StringConcatenate( "InfoLabel_0", LabelNumber );
  else
         LabelName = StringConcatenate( "InfoLabel_" , LabelNumber );
 
  //---- 如果没有指定额外参数, 
    //---- 给他们设为最后使用值
    if ( Color < 0 ) 
          Color = lastusedColor;
  if ( FontSize < 0 ) 
     FontSize = lastusedFontSize;
  if ( Font == "-1" ) 
     Font = lastusedFont;
 
  //---- 保存最后使用的值
    lastusedColor = Color;
  lastusedFontSize = FontSize;
  lastusedFont = Font;

为了避免最后使用的变量值出现空值, 让我们在它们的声明后立即赋值:

color lastusedColor = Black;
double lastusedFontSize = 9.0;
string lastusedFont = "Arial";

您也许会注意到, 最后使用的变量声明于函数体外. 这样可以防止它们的数值在每次调用info()函数时被清零.

现在让我们显示新的文字并重绘对象. 这必须马上做到, 因为当有变化发生时, 对象的显示也要马上更新:

 //---- 显示新的文字
    if( !ObjectSetText( LabelName, Text, FontSize, Font, Color ) )
      {
        int _GetLastError = GetLastError();
        Print( "ObjectSetText( \"", LabelName,"\", \"", Text, "\", ", FontSize, ", ", Font, 
                                             ", ", Color, " ) - 错误 #", _GetLastError );
      }
    //---- 重绘对象
    ObjectsRedraw();
  }

全部三个函数的完整代码在附件文件中, 它的名字是 info.mq4.

现在让我们看看得到了什么:




我想这非常好.

最终, 我们可以再多创造一个便利 - 一个用于清除全部信息的函数. 它被称为 info_clear(). 我想您知道怎样使用它.

void info_clear()
 {
     for ( int n = 0;  n < 5;  n ++ ) 
       info( n, "" );
  for (     n = 10; n < 15; n ++ ) 
       info( n, "" );
 }

4. 总结

本文描述了使用记录文件以及显示信息的替代性方法. 我们创建了两个包含文件并安放于"experts/include" 文件夹: log.mq4 和 info.mq4. 它们可以在任何EA交易中使用.

本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/1405

附加的文件 |
info.mq4 (4.5 KB)
info_test_eng.mq4 (1.24 KB)
log.mq4 (3.19 KB)
log_test_eng.mq4 (0.76 KB)
专家系统"解说员". 在MQL4程序中嵌入指标的实际应用 专家系统"解说员". 在MQL4程序中嵌入指标的实际应用
本文描述了在MQL4编程中技术指标的使用.
EA交易, 脚本程序和指标的同步 EA交易, 脚本程序和指标的同步
本文介绍了开发捆绑程序, 即可能同时包含EA交易, 脚本程序和指标的程序集合的必要性以及通用原则.
MQL4 作为交易者的工具, 还是高级技术分析 MQL4 作为交易者的工具, 还是高级技术分析
交易首先是对可能性的计算. 有一句谚语, 懒惰是进步的引擎, 这也揭示了指标以及交易系统被开发出来的原因. 绝大多数交易新手学习的都是"成型"的交易理论. 但是, 如果够幸运的话, 还有更多的没有被发现的市场奥秘和用于分析价格走向的工具, 例如那些还没有实现的技术指标或者数学和统计学工具包. 非常感谢比尔.威廉姆斯对市场运行理论的贡献. 虽然,也许现在休息是太早了些.
市场理论 市场理论
逻辑上完整的市场理论应该包含所有品类的商品和服务市场,像外汇这种微观和宏观市场到目前为止还不包括其中。本文介阐释基于盈利分析的新市场理论的精髓。揭示了当前价格变化的运行机制和原则,即通过形成能对实际价格产生控制影响的虚拟价格链,来找到最优定价。市场趋势的形成和改变机制在这里将得到阐释。