EA交易, 脚本程序和指标的同步

Сергей Ковалев | 19 一月, 2016

简介

使用MQL4开发并在MetaTrader 4客户终端中运行的有三种程序:
- EA交易;
- 脚本程序;
- 指标.

它们中的每一种都是为了解决某一范围的问题. 让我们给这些程序一个简要的描述.


1. MQL4 用户程序的简要描述

1.1. EA交易
EA交易是用于实现获利交易策略的主要类型的程序. EA交易的突出特点如下所列:
1. 能够使用支持交易的内建函数.
2. 能够人工修改外部设定.
3. 根据规则, 特别函数start()会不断运行. 它是按每次订单时刻运行的. 当有新的订单来临的时候, 此函数用到的整个环境的参数都会被更新. 例如, 变量 bidask 都会有新的数值. 代码执行完毕后, 也就是 - 运行到操作符 return, start() 函数就会结束它的操作并休眠直到有新的订单来临.

1.2. 脚本程序
脚本程序和EA交易非常类似, 但是它们的特性有一点不同. 脚本程序的主要特性罗列如下:
1. 脚本程序也可以使用交易函数.
2. 外部设定的参数在脚本内不能改变.
3. 脚本程序的主要特性是它的规则, 脚本程序的特别函数, start()在被附加到图表并初始化后只会运行一次.

EA交易和脚本程序附加到某个交易品种的图表后, 它们没有特别的子窗口.

1.3. 指标
与EA交易和脚本程序不同, 指标有另外的意义:
1. 指标的主要特性是, 它可以根据其实现的原则绘制连续的曲线.
2. 指标中不能使用交易函数.
3. 指标是按订单时刻运行的.
4. 根据参数实现的不同, 指标既可以在交易品种主窗口中, 也可以在自己的子窗口中运行.

以上我们列出了自定义程序的主要特征, 下面我们会进一步研究.

我们可以看到, 没有一种自定义程序具有程序的所有属性: EA交易和脚本程序不能绘图, 而指标不能交易.

如果我们的交易系统需要在交易过程中使用自定义程序的全部属性, 唯一的方案是同时使用EA交易, 脚本程序和指标.


2. 问题声明

让我们研究一下需要同时使用全部自定义程序的标准条件.

2.1. 时效性
任何用户的操控必须立即执行. 基于EA交易的程序并不总能满足这个目标. EA交易的主要缺点就是它对外部行为不够敏感. 有此局限的原因很简单: EA交易的基本代码是按订单时刻运行的. 如果用户命令EA交易关闭一个订单而EA却在等待下一个订单时刻, 那么会发生什么呢?这个问题的答案依赖于EA交易是怎样实现的. 有些情况下, 命令会被执行, 但是有些延迟.

程序也可以这样组织, EA交易的主要代码连续执行而不会在订单之间中断. 为此目标, 需要重新组织特别函数

start()

在其中实现一个无限循环, 然后把程序的主要代码都放在其中. 如果在每个循环的开始都强制更新环境信息, 整个复杂工作就可以成功完成. 循环EA交易的不便之处就是不能打开设置面板了. 在EA交易中使用循环 - 然后你不能对它进行设置.

这种想法也可以通过使用脚本程序实现. 也就是在脚本程序内使用一个无限循环. 但是在脚本程序中不能配置参数.

交易系统的可自定义和执行用户命令的时效性在连续运行模式下只能通过同时使用EA交易做设置, 而使用脚本程序做立即执行.

2.2. 感知能力
在一些情况下, 需要从交易中获取信息. 例如, 每个交易者可能都想知道, 在某个时刻(例如重要新闻发布的前两分钟), 交易中心已经把设置挂单时接受的最小距离从正常的10个点改成20个点了. 另外, 作为规则, 交易者希望知道交易服务器拒绝执行订单的原因. 这些以及其他一些有用信息可以在指标窗口中用文字显示. 同样, 在持续运行的情况下, 旧的信息会不断上移而为从交易系统获得的新信息腾出空间. 在这种情况下, 就需要把用于显示的指标绑定到进行交易的EA交易或脚本程序中.

2.3. 控件
如果您使用的交易系统包含增强的界面, 其控件(图形对象)最好放在指标窗口中. 这样我们就能确保价格烛形趋势不会覆盖我们的控件, 从而不会影响我们的操控.

2.4. 系统需求
在本例中, 最终产品的主要需求是同步的操作, 所以, 开发一个基于全部三种程序的系统, 需要把任务分配到它的全部组件中. 根据我们系统中的每一类程序的特性, 我们可以如下定义它们的属性:

脚本程序 - 提供包含分析与交易函数的基本代码;

EA交易 - 提供设置面板;

指标 - 提供子窗口区域用于显示控件和信息.


3. 软件解决方案

让我们立即指出, 在最小需求范围内基于这三个组件的程序结构. 如果您打算在实际中使用此应用程序, 您必须自己详细了解它的分析和交易操作功能. 但是以下提供的资料已经对开发结构方面足够多了.

3.1. EA 交易
让我们详细了解EA交易所包含的内容以及它如何工作.

// Expert.mq4
//====================================================== 包含头文件 =======
#include <stdlib.mqh>
#include <stderror.mqh> 
#include <WinUser32.mqh> 
//======================================================================
#include <Peremen_exp.mq4>  // EA交易变量的描述.
#include <Metsenat_exp.mq4> // EA变量预先定义. 
#include <Del_GV_exp.mq4>   
// 删除EA交易创建的全局变量.
#include <Component_exp.mq4> // 检查组件是否可用.
#include <Component_uni.mq4> 
// 指标中关于组件不可用的信息.
//======================================================================
//
// 
//======================================================================
int init()  
  {
    Fishka=1;          // 我们在init()函数中  
    Metsenat_exp();   // 预先定义EA变量.
    Component_exp();  // 检查组件是否可用
    return;
 } 
//=====================================================================
int start() 
  { 
    Fishka=2;         // 我们在 start() 函数中 
    Component_exp();  // 检查组件是否可用
    return;                                                                 
 }
//=====================================================================
int deinit() 
  {   
    Fishka=3;         // 我们在 deinit() 函数中 
    Component_exp();  // 检查组件是否可用
    Del_GV_exp();     // 删除EA交易中的全局变量.
    return;
 }
//======================================================================

在特别函数 init() 中, 使用了两个函数 - Metsenat_exp() 和 Component_exp()

Metsenat_exp()

- 预先定义一些变量的函数.

// Metsenat_exp.mq4
//=================================================================
int Metsenat_exp()
 {
//============================================ 预先定义 =====
   Symb     = "_"+Symbol();
   GV       = "MyGrafic_GV_";
//============================================= 全局变量 ====
   GV_Ind_Yes = GV+"Ind_Yes"   +Symb;      
// 0/1 确认指标已经载入
   GV_Scr_Yes = GV+"Scr_Yes"   +Symb;      
// 0/1 确认脚本已经载入
//-------------------------------------------- 公开使用 ----
   GV_Exp_Yes = GV+"Exp_Yes"   +Symb;     
   GlobalVariableSet(GV_Exp_Yes, 1 ); 
   GV_Extern  = GV+"Extern"    +Symb;     
   GlobalVariableSet(GV_Extern,  1 ); 
//  用 AAA 作为例子:
   GV_AAA     = GV+"AAA"       +Symb;     
GlobalVariableSet(GV_AAA,   AAA ); 
//==================================================================
   return;
 }
//====================== 模块结束 =============================

整个应用程序维护的任务之一就是追踪全部组件的可用性. 这就是为什么全部组件(脚本程序, EA交易和指标) 互相之间必须追踪, 如果一个组件不可用, 就要停止工作并通知用户. 为此, 每个程序都通过在起始阶段发布全局变量通知其可用. 在提供的例子中, 在EA交易的 Metsenat_exp() 函数中, 将如下实现:

   GV_Exp_Yes = GV+"Exp_Yes"   +Symb;     
   GlobalVariableSet(GV_Exp_Yes, 1 );

Metsenat_exp() 函数被EA交易的 init() 函数调用, 也就是说, 它只在载入或者修改外部变量值的时候使用一次. 脚本程序必须'知道'改变的设置, 这就是为什么EA交易通过修改全局变量GV_Extern的值来通知脚本程序:

   GV_Extern  = GV+"Extern"    +Symb;     
   GlobalVariableSet(GV_Extern,  1 );


Component_exp()

- 一个用于完备性控制的函数. 更多的场景依赖于使用Component_exp()函数的EA交易的特殊函数.

// Component_exp.mq4 
//===============================================================================================
int Component_exp()
 {
//===============================================================================================
   while( Fishka < 3 &&     // 我们在 init()函数或者 start()函数中..
      (GlobalVariableGet(GV_Ind_Yes)!=1 || 
       GlobalVariableGet(GV_Scr_Yes)!=1)) 
    {                            // ..而程序尚不可用.
      Complect=0;                // 因为如果有一个不可用, 就会出错
      GlobalVariableSet(GV_Exp_Yes, 1); 
// 通知EA交易可用 
//-----------------------------------------------------------------------------------------------
      if(GlobalVariableGet(GV_Ind_Yes)==1 && 
         GlobalVariableGet(GV_Scr_Yes)!=1)
        {//如果指标可用而脚本程序不可用, 则..
         Graf_Text = "脚本程序尚未安装.";  
// 消息文字
         Component_uni();                             
// 把消息文字写到指标窗口.
        }
//-----------------------------------------------------------------------------------------------
      Sleep(300);
    }
//===============================================================================================
     if(Complect==0)
    {
      ObjectDelete("Necomplect_1"); 
// 删除不需要的通知组件不可用的信息  
      ObjectDelete("Necomplect_2");       
      ObjectsRedraw();              // 为了快速删除 
      Complect=1;        // 如果我们离开了循环, 说明所有组件都可用
    }
//===============================================================================================
   if(Fishka == 3 && GlobalVariableGet(GV_Ind_Yes)==1)
// 我们在 deinit() 函数中, 并且有空间写入指标信息
    { 
//-----------------------------------------------------------------------------------------------
      if(GlobalVariableGet(GV_Scr_Yes)!=1)  // 如果没有可用脚本程序,
       {
         Graf_Text = "EA组件和脚本程序没有安装";
// 信息 (因为我们正在退出)
         Component_uni();     // 在指标窗口写文字信息
       }
//-----------------------------------------------------------------------------------------------
    }
//===============================================================================================
   return;
 }
//===================== 模块结束 =======================================================

脚本程序和指标的可用追踪是基于对应全局变量的读取的 - GV_Scr_Yes 和 GV_Ind_Yes. 如果组件中有不可用的, 将会一直无限循环直到达到完备性, 即指标和脚本程序全部安装完毕. 应用程序将会通过Component_uni()函数通知用户当前的状态. 它是包含在所有组件中的通用函数.

// Component_uni.mq4
//================================================================
int Component_uni()
 {
//================================================================
//----------------------------------------------------------------
   Win_ind = WindowFind("Indicator");                 
// 我们的指标窗口编号是多少?
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   ObjectCreate ( "Necomplect_1", OBJ_LABEL, Win_ind, 0, 0  );
// 在指标窗口中创建一个对象
   ObjectSet    ( "Necomplect_1", OBJPROP_CORNER,        3  );
// 相对右下角的坐标
   ObjectSet    ( "Necomplect_1", OBJPROP_XDISTANCE,   450  );
// X方向坐标..
   ObjectSet    ( "Necomplect_1", OBJPROP_YDISTANCE,    16  );
// Y方向坐标..
   ObjectSetText("Necomplect_1", Graf_Text,10,"Courier New",Tomato);
// 文本, 字体, 和颜色
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
   Graf_Text = "应用程序无法工作.";
 // 消息文字
   ObjectCreate ( "Necomplect_2", OBJ_LABEL, Win_ind, 0, 0);
// 在指标窗口中创建一个对象
   ObjectSet    ( "Necomplect_2", OBJPROP_CORNER,        3);
// 相对右下角的坐标
   ObjectSet    ( "Necomplect_2", OBJPROP_XDISTANCE,   450);
// Х方向坐标..
   ObjectSet    ( "Necomplect_2", OBJPROP_YDISTANCE,     2);
// Y方向坐标..
   ObjectSetText("Necomplect_2", Graf_Text,10,"Courier New",Tomato);
// 文字, 字体, 颜色
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
   ObjectsRedraw();                                // 重绘.
   return;
//================================================================
 }
//===================== 模块结束 ============================

一旦应用程序结束, EA交易的控制将从循环部分转到之后的代码, 这时已经不需要的关于组件不完整的消息将会被删除.

当EA被卸载时, 特别函数 deinit() 也会调用 Component_exp()函数, 但是是为了别的目的 - 通知关于当前时刻的卸载事件.

在EA交易的 deinit() 函数中, 也会调用Del_GV_exp() 函数.

它用于删除EA交易中创建的全部全局变量. 按照不成文的规矩, 每个程序在卸载时都要"清扫战场", 也就是说把之前创建的全局变量和图形对象删除.

// Del_GV_exp.mq4
//=================================================================
int Del_GV_exp()
 {
//=================================================================
   GlobalVariableDel(GV_Exp_Yes      ); 
   GlobalVariableDel(GV_Extern       ); 
   GlobalVariableDel(GV_AAA          ); 
//=================================================================
   return;
 }
//====================== 模块结束 ============================

这样, EA交易就开始工作并且在各个阶段都追踪了其它两个组件的可用性: 一次在 init(), 一次在 deinit(), 另外还有每个订单时刻 - 在 start(). 这样EA的创建就能够用于完成我们的任务了 - 创建可用的控制面板. 作为示例, 文件描述的变量包含AAA变量以及它对应的全局变量GV_AAA, 它的值从脚本程序中读取.

为了详细了解这些是如何工作的, 让我们考虑脚本程序的结构.

3.2. 脚本程序

脚本程序代码:

// Script.mq4
//==================================================== include ====
#include <stdlib.mqh> 
#include <stderror.mqh>
#include <WinUser32.mqh>
//=================================================================
#include <Peremen_scr.mq4>       
// 文件所描述的脚本程序变量. 
#include <Metsenat_scr.mq4>      
// 脚本程序中预先定义变量.   
#include <Mess_graf_scr.mq4>     
// 图形消息列表.
#include <Novator_scr.mq4>       
// 环境扫描, 从一些变量取得新数值
#include <Del_GV_scr.mq4>        
// 删除由脚本程序创建的全部全局变量
#include <Component_scr.mq4>     
// 检查组件是否可用.
#include <Component_uni.mq4>     
// 指标中关于组件不可用的信息.
#include <Del_Obj_scr.mq4>       
// 删除由组合程序创建的全部对象.
#include <Work_scr.mq4>          
// 脚本程序中的主工作函数. 
//=================================================================
//
// 
//=================================================================
int init()  
 {
   Fishka = 1;                                // 我们在 init() 函数中 
   Metsenat_scr();       // 预先定义脚本程序中的变量.
   return;
 }
//================================================================
int start()  
 {
   Fishka = 2;                               // 我们在 start() 函数中 
   while(true)
    {
      Component_scr();  // 检查组件是否可用
      Work_scr();       // 脚本程序的主要工作函数.
    }
   return;
 }
//=================================================================
int deinit() 
 {
   Fishka = 3;                                // 我们在 deinit() 函数中 
   Component_scr();      // 检查组件是否可用
   Del_Obj_scr();          // 删除创建的图形对象
   Del_GV_scr();        // 删除脚本程序中的全局变量.
   return;
 }
//==================================================================
此代码的基础是在特殊函数 start() 中的无限循环. 在脚本代码中, 使用了类似的名称和内容. 我们要注意它们的特别之处. 在每个循环的开端, 会调用Component_scr()

函数.

// Component_scr.mq4   
//====================================================================
int Component_scr()
 {
//====================================================================
   Iter=0;                               // 循环计数清零
   while (Fishka <3 &&              // 我们在 init() 函数中或 start() 函数中
      (GlobalVariableGet(GV_Ind_Yes)!=1 || 
       GlobalVariableGet(GV_Exp_Yes)!=1)) 
    {                                 // 直到程序可用
      GlobalVariableSet(GV_Scr_Yes, 1);               
// 声明脚本可用
//--------------------------------------------------------------------
      Iter++;                                    // 迭代计数器
      if(Iter==1)                         // 跳过第一次迭代
       {
         Sleep(500);
         continue;
       }
//--------------------------------------------------------------------
      if(Iter==2)             // 在第二次迭代开始衡量
       {
         Complect=0; // 程序不可用, 不完整
         for (i=0;i<=31;i++)ObjectDelete(Name_Graf_Text[i]);
// 删除全部字符串
// 再次可以插入一个函数来清除交易队列.
       }
//--------------------------------------------------------------------
      if(GlobalVariableGet(GV_Ind_Yes)==1 && 
          GlobalVariableGet(GV_Exp_Yes)!=1)
       {                       // 如果有指标, 但是没有EA
         Graf_Text = "EA交易没有安装."; 
// 消息文字 
         Component_uni();                             
// 把文本信息写到指标窗口.
       }
//-----------------------------------------------------------------
      Sleep(300);
    }
//-----------------------------------------------------------------
   if(Complect==0)                // 结束时开始处理.
    {
      ObjectDelete("Necomplect_1"); 
// 删除不需要的消息..
      ObjectDelete("Necomplect_2"); 
// ..关于组件不完整        
      Mess_graf_scr(1);
// 通知用户完整性
      if( IsExpertEnabled()) 
// 按钮可用
       {
         Mess_graf_scr(3000);
         Knopka_Old = 1;
       }
      if(!IsExpertEnabled()) 
// 按钮被禁用
       {
         Mess_graf_scr(4000);
         Knopka_Old = 0;
       }
      Complect=1; 
// 达到了最小安装完整性
      Redraw = 1; 
// For quick deletion 
    }
//====================================================================
   if(Fishka == 3 && GlobalVariableGet(GV_Ind_Yes)==1)      
// 我们在 deinit() 函数中  
    {
      for(i=0;i<=31;i++)ObjectDelete(Name_Graf_Text[i]);    
// 删除全部字符串
//--------------------------------------------------------------------
      if(GlobalVariableGet(GV_Exp_Yes)!=1)                 
// 有指标, 但是没有EA
         Graf_Text="EA交易和脚本程序组件没有安装.";
// 消息 (因为我们正在卸载)
      if(GlobalVariableGet(GV_Exp_Yes)==1)
// 如果指标和EA都有..
         Graf_Text="脚本程序没有安装."; 
// 消息 (因为我们正在卸载)
      Component_uni();   // 在指标窗口中写入消息.
//--------------------------------------------------------------------
      ObjectsRedraw();                    // 为了快速删除 
    }
//====================================================================
   return;
 }
//====================== 模块结束 ===============================

对脚本程序的第一个要求就是它可以持续运行. 在外部变量的更新过程中, EA交易通过了完整安装. 当在EA设置面板上按下OK按钮, 它会被卸载并把操控权交给 deinit(), 然后再很快重新载入, 并陆续执行 init() 和 start() 函数. 然后, EA交易运行一段时间, 再在deinit()函数中删除确认其可用性的全局变量.

脚本程序不会假定EA交易还没有被完全载入, 所以Component_scr() 包含一段代码避免在第一次迭代时作出决定:

      Iter++;                         // 迭代计数器
      if(Iter==1)              // 跳过第一次迭代
        {
          Sleep(500);
          continue;
        }

500毫秒在大多数情况下是足够让EA交易载入的. 如果您在EA交易的init()函数中使用了更加耗时的代码, 时间必须被延长. 如果在第二次迭代时还是没有侦测到EA交易, 就会认为EA交易完全不可用, 然后脚本程序会停止运行.

      Complect = 0;    // 有个程序不可用, 不完备

在之前的段落中, 使用了"脚本程序停止运行"的表述. 在我们的例子中, 没有代码用于此种现象, 因为它超出了我们这篇文章的主题范围. 在您可以调用对应函数的地方, 代码中有一段注释.

// 在此, 可以插入交易计数器清零的代码.

在真正工作程序的Work_scr() 函数中, 除了我们例子中用到的函数, 其他函数用于一些订单事件的处理. 例如, 如果您的程序用于修改几个订单, 它必然会包含一个数组, 如果当前订单时刻有一些交易发生则在数组中保存执行的交易.

如果某个时刻出现了不完备的现象(例如, EA交易或者脚本程序异常退出), 而此时有这样的交易队列, 就可以通过把上面说到的交易队列清零或者根据情况修改某些变量以阻止交易.

脚本程序的无限循环部分也调用Work_scr()函数. 这是脚本程序的主函数部分, 主要代码应当放置其中.

// Work_scr.mq4
//=================================================================
int Work_scr()  
 {
   Novator_scr();
//-----------------------------------------------------------------
   // 整个应用程序的基本代码.
//------------------------------------------------ 例如 ----
   if(New_Tick==1)                             // 在每一个新订单时刻
    {                                                                    
      Alert ("ААА 当前数值 = ", AAA);
    }                                                                    
//-----------------------------------------------------------------
   if(Redraw==1)
    {
      ObjectsRedraw();                    // 立即显示
      Redraw=0;                         // 去掉对象重绘标志
    }
//-----------------------------------------------------------------
   Mess_graf_scr(0);
   Sleep(1);                           // 假如重新载入
   return;
 }
//====================== 模块结束 ============================

Work_scr() 包含了 Novator_scr() 函数, 它的意图是更新在基本代码中使用的环境变量.

// Novator_scr.mq4  
//===================================================================
int Novator_scr()  
 {
//===================================================================
//---------------------------------------- 更新设置 -----
   if(GlobalVariableGet(GV_Extern)==1) 
// EA交易中有更新
    {
      Metsenat_scr();         // 脚本程序变量更新.
      Mess_graf_scr(5000);    // 新设定的消息.
      Redraw=1;               // 在循环末尾重绘.
    }                                                                    
//--------------------------------- EA 按钮状态 -----------------
   Knopka = 0;                                         // 重设
   if( IsExpertEnabled()) Knopka = 1; 
// 检查按钮的真实状态
 
   if(Knopka==1 && Knopka_Old==0) 
// 如果状态改为"打开"
    {
      Knopka_Old = 1;                // 这是旧的
      Mess_graf_scr(3);              // 把改变通知用户
    }
   if(Knopka==0 && Knopka_Old==1) 
// 如果状态是"关闭"
    {
      Knopka_Old = 0;                 // 这是旧的
      Mess_graf_scr(4);              // 把改变通知用户
    }
//-------------------------------------------------- 新订单时刻 --------
   New_Tick=0;                              // 首先清零
   if (RefreshRates()==true) New_Tick=1; 
// 如果您知道怎样做, 抓住新订单时刻很简单
//--------------------------------------------------------------------
//====================================================================
   return;
 }
//=====================; 模块结束 ===============================

让我们仔细研究此函数的必要性. 我们在文章的开头提到, 每次EA交易载入时, 或者当其变量更新时, 其中的Metsenat_exp() 函数会把 GV_Extern 变量的值设为 1. 对脚本程序来说, 它的意思是设置必须要更新. 为此, Novator_scr() 函数包含如下代码段:

//---------------------------------------- 更新设置 ----
   if (GlobalVariableGet(GV_Extern)==1) 
// EA中发生了更新
    {
      Metsenat_scr();              // 更新脚本设置.
      Mess_graf_scr(5000);         // 新设置消息.
      Redraw=1;                    // 在循环末尾重绘.
    }

以上的变量值会在此分析, 如果有必要更新, 会调用Metsenat_scr() 函数, 它会进行更新工作 (从全局变量中读取新的数值).

// Metsenat_scr.mq4
//===================================================================
int Metsenat_scr()
  {
//========================================================== int ====
//======================================================= double ====
//======================================================= string ====
      MyGrafic    = "MyGrafic_";
    Mess_Graf   = "Mess_Graf_";
    Symb        = "_"+Symbol();
    GV          = "MyGrafic_GV_";
//=============================================== 全局变量 ====
     GV_Ind_Yes  = GV+"Ind_Yes" +Symb;       
// 0/1 确认指标已经载入
     GV_Exp_Yes  = GV+"Exp_Yes" +Symb;       
// 0/1 确认EA交易已经载入
//-------------------------------------------------- 发布 -----
     GV_Scr_Yes  = GV+"Scr_Yes" +Symb;     
    GlobalVariableSet(GV_Scr_Yes,          1 ); 
    GV_Extern   = GV+"Extern"  +Symb;     
    GlobalVariableSet(GV_Extern,           0 ); 
//--------------------------------------------------- 读取 -------
                                             //  以 AAA 为例:
     GV_AAA      = GV+"AAA"     +Symb;     
   AAA  = GlobalVariableGet(GV_AAA); 
//===================================================================
     return;
 }
//======================== 模块结束 ============================

Metsenat_scr() 函数会把全局变量 GV_Extern 的值设为0. 在其后的历史中, 此变量会保持为 0 直到用户打开EA交易的设置窗口.

需要注意的是, 尽管在修改了设定后, EA交易会通过卸载和载入再次重新运行, 脚本程序并不会在用户修改设定前后停止工作. 这样, 把EA交易和脚本程序联合使用就能够达到应用程序在允许用户修改设定时连续运行的要求, 也就是说, 控制了过程.

在函数Novator_scr()后面的代码段中, 控制了EA的按钮来使它可以进行交易. 然后又探测到了新的订单时刻. 如果您的交易系统打算使用那些类似的参数, Novator_scr() 函数就可以用于做这样的计算.

例如, 您可以完成此函数, 写一段代码来侦测是否有新柱出现, 检查是否有重要事件的时间到来, 监测是否交易规则有所改变(例如, 点差, 止损单的最小距离等), 以及分析函数开始运行之前做些其他的计算准备等.

组成程序的主要部分的函数并没有在Work_scr()函数中显示. 在叫做在大型程序中考虑订单的文章中, 我们使用了Terminal() 函数来分析订单. 如果在您的交易系统中采取相同的分析原则, 应该把Terminal() 函数包含在Work_scr() 函数中并且紧跟在Novator_scr()函数之后.

脚本程序还准备了额外一个辅助函数 - Mess_graf_scr(), 它是用于在指标窗口中显示信息的.

// Mess_graf_scr.mq4
//====================================================================
int Mess_graf_scr(int Mess_Number)
 {
//====================================================================
   if(Mess_Number== 0)        // 在每次循环中都要进行
    {
      if(Time_Mess>0 && GetTickCount()-Time_Mess>15000) 
// 打印的颜色会过期 
       {                       // ..15秒后, 文字行的颜色变灰
         ObjectSet(Name_Graf_Text[1],OBJPROP_COLOR,Gray);
// 最后两行
         ObjectSet(Name_Graf_Text[2],OBJPROP_COLOR,Gray);
// 最后两行
         Time_Mess=0;         // 不要使用颜色的额外标志
         Redraw=1;            // 重画
       }
      return;                 // 退出 
    }
//--------------------------------------------------------------------
   Time_Mess=GetTickCount(); // 记住消息的发布时间
   Por_Nom_Mess_Graf++;      // 行计数. This is just a name part.
   Stroka_2=0;            // 假设信息为一行
   if(Mess_Number>1000)      
// 假如出现了大数字, 数字是有含义的, 
// 说明前一行是来自相同的消息 
// 颜色不能变灰 
    {
      Mess_Number=Mess_Number/1000; 
      Stroka_2=1; 
    } 
//====================================================================
   switch(Mess_Number)
    {
//--------------------------------------------------------------------
      case 1:
         Graf_Text = "所有所需的组件都已安装.";
         Color_GT = LawnGreen; 
         break;
//--------------------------------------------------------------------
      case 2:
         Graf_Text = " ";
         break;
//--------------------------------------------------------------------
      case 3:
         Graf_Text = "EA交易可用.";
         Color_GT = LawnGreen; 
         break;
//--------------------------------------------------------------------
      case 4:
         Graf_Text = "EA交易禁用.";
         Color_GT = Tomato; 
         break;
//--------------------------------------------------------------------
      case 5:
         Graf_Text = "EA交易设置更新.";
         Color_GT = White; 
         break;
//---------------------------------------------------- 默认 -------
      default:
         Graf_Text = "默认行 "+ DoubleToStr( Mess_Number, 0);
         Color_GT = Tomato;
         break;
    }
//====================================================================
   ObjectDelete(Name_Graf_Text[30]); 
// 如果取代了30个对象, 把它删除
   int Kol_strok=Por_Nom_Mess_Graf;
   if(Kol_strok>30) Kol_strok=30;
//-----------------------------------------------------------------
   for(int lok=Kol_strok;lok>=2;lok--)
// 遍历图形文本名称 
    {
      Name_Graf_Text[lok]=Name_Graf_Text[lok-1];        
// 重新赋值 (规范化)
      ObjectSet(Name_Graf_Text[lok],OBJPROP_YDISTANCE,2+14*(lok-1));
//修改Y值 (规范化)
      if(lok==3 || lok==4 || (lok==2 && Stroka_2==0))
         ObjectSet(Name_Graf_Text[lok],OBJPROP_COLOR,Gray);
//老的消息行变灰.. 
    }
//-------------------------------------------------------------------
   Graf_Text_Number=DoubleToStr( Por_Nom_Mess_Graf, 0); 
//信息编号部分与名称部分结合
   Name_Graf_Text[1] = MyGrafic + Mess_Graf + Graf_Text_Number;
// 生成信息名称.
   Win_ind= WindowFind("Indicator");                    
//指标窗口编号多少?
 
   ObjectCreate ( Name_Graf_Text[1],OBJ_LABEL, Win_ind,0,0);
// 在指标窗口中创建一个对象
   ObjectSet    ( Name_Graf_Text[1],OBJPROP_CORNER, 3   );  
// ..右下角坐标
   ObjectSet    ( Name_Graf_Text[1],OBJPROP_XDISTANCE,450); 
// ..横坐标..
   ObjectSet    ( Name_Graf_Text[1],OBJPROP_YDISTANCE, 2);  
// ..纵坐标..
   ObjectSetText(Name_Graf_Text[1],Graf_Text,10,"Courier New",
                 Color_GT);
//文字字体颜色
   Redraw=1;                                  // 重绘
//=================================================================
   return;
 }
//====================== 模块结束 ============================

没有必要详细研究此函数. 我们只要说说它的一些特点.

1. 所有消息通过图形方式显示.

2. 传给函数的前面的参数是消息编号.
3. 如果传入的参数值在1和999之间, 指标窗口中之前的文字会失去其颜色. 如果参数值超过1000, 消息会被显示, 即编号的分界点是1000. 在之后的文字行不会失去颜色.
4. 在最新消息出现15秒后, 所有文字行都失去颜色.
5. 为了保持把文字行颜色消除, 函数需要被一次次调用. 所以, 在Work_scr()函数末尾会调用它:

   Mess_graf_scr(0);

在叫做图形EA交易: AutoGraf的文章中, 展示了一个可以运行的组合程序, 它使用了类似的函数, 包含了超过250条以上的不同消息. 您可以以之为例, 在您的交易中使用全部或部分消息.

3.3. 指标

为了完成我们的演示, 让我们也探讨一下指标, 尽管其代码相对简单.

// Indicator.mq4
//===================================================; include ==========
#include <stdlib.mqh>
#include <stderror.mqh>
#include <WinUser32.mqh>
//=======================================================================
#include <Peremen_ind.mq4>       
// 指标变量描述
#include <Metsenat_ind.mq4>      
// 指标变量的预先定义. 
#include <Del_GV_ind.mq4>        
// 删除由指标创建的全部全局.
#include <Component_ind.mq4>     
// 检查组件可用性.
#include <Component_uni.mq4>     
// 指标中关于组件不可用的信息.
//=======================================================================
//
// 
//=======================================================================
#property indicator_separate_window
//=======================================================================
//
//
//=======================================================================
int init()  
 {
   Metsenat_ind();
   return;
 }
//=======================================================================
int start() 
 {
   if(Component_ind()==0) return; // 检查组件的可用性
   //...
   return;                                                           
 }
//=======================================================================
int deinit() 
 {
   Del_GV_ind();             // 删除指标创建的全局变量.
   return;
 }
//=======================================================================

指标中只有一个重要特点要在此处强调: 指标是显示在独立窗口中的:

#property indicator_separate_window

Metsenat_ind() 和 Del_GV_ind() 函数的内容和之前在EA交易和脚本程序中的对应函数非常类似.

Component_ind() 函数的内容也不复杂:

// Component_ind.mq4
//===================================================================
int Component_ind()
 {
//===================================================================
   if(GlobalVariableGet(GV_Exp_Yes)==1 && 
      GlobalVariableGet(GV_Scr_Yes)==1)
//如果全部组件都可用
    {                        // 关于指标可用的状态
      if(GlobalVariableGet(GV_Ind_Yes)!=1)
          GlobalVariableSet(GV_Ind_Yes,1);
      return(1);
    }
//--------------------------------------------------------------------
   if(GlobalVariableGet(GV_Scr_Yes)!=1 && 
      GlobalVariableGet(GV_Exp_Yes)!=1)
    {                           // 如果没有脚本程序和EA  
      Graf_Text = "EA和脚本程序组件没有安装.";            
// 消息文字
      Component_uni();        // 把消息写进指标window
    }
//=====================================================================
   return(0);
 }
//=====================; 模块结束 ================================

我们在代码中可以看到, Component_ind() 函数只有在两个其他组件 - 脚本程序和EA交易都没有加载的时候才会给出消息. 如果只有一个程序不可用, 不会进行任何操作. 这里假定, 如果他们在交易品种窗口可用, 这些程序会追踪此复合程序的组件并通知用户结果.

如果有必要, 指标的主要属性 - 绘图 - 也可以使用. 对于本复合程序而言, 此属性不是必须的, 但是它可以用在真实交易中, 例如把子窗口分成不同区域.


4. 实际使用

把应用程序组件附加到交易品种窗口的顺序并不重要. 但是, 推荐的是第一个附加指标, 因为这样我们就能够在附加之后立即读取注释内容了.

所以, 为了演示应用程序如何工作, 我们必须做到如下工作.

1. 在交易品种窗口中附加指标. 这样会立即在指标窗口中显示:

EA交易和脚本程序组件没有安装
应用程序无法工作.


2. 把 EA交易附加到交易品种窗口. Component_exp() 会被触发, 以下信息会显示在指标窗口:

脚本程序没有安装
应用程序无法工作.


3. 把脚本程序附加到交易品种窗口. 此事件将会被脚本程序中的 Component_scr() 函数处理并显示在指标窗口中:


所有所需的组件都安装好了.
EA交易被启用.


如果EA交易被禁用, 消息看起来会像:

所有所需组件都安装好了.
EA交易被禁用.


4. 您可以按下几次EA的按钮并确认这个事件序列被应用程序立即处理并显示在消息行中:

EA交易被禁用.
EA交易被启用.

EA交易被禁用.
EA交易被启用.
EA交易被禁用.


请注意, 因为复合程序中的脚本程序使用了循环代码, 程序对用户控制的回应是立即的, 而不是等到下一个订单时刻.

作为例子, 我们在Work_scr()函数中, 使用函数Alert()来按照订单时刻来显示EA交易设置中的变量.



让我们研究这个特性. Work_scr() 函数是脚本程序的一部分. 脚本程序的基本循环在订单时刻之间会运行几百次, 而Alert()函数发出的信息只出现在订单时刻之间.

5. 打开EA设置的工具条并把AAA的值替换为3. 脚本程序会跟踪到这个事件并且在指标窗口上显示一条信息:

EA交易已被允许使用.
EA交易已被禁用.
EA交易已被允许使用.

EA交易已被禁用.
EA设置已被更新.


Alert()函数的窗口将会每订单时刻显示AAA变量的新值:



6. 现在, 您可以用任何顺序载入或者卸载任何组件, 按下EA的按钮, 修改可调整变量的数值, 然后对这个复合程序的质量做出您自己的评价了.


结论

使用本文描述的技术, 我们的主要收获就是 脚本程序不论环境中是否有事件发生都不会停止工作. 脚本程序只有在它发现另外一个或两个组件(指标, EA交易)不可用的时候才会停止工作.

本文所述的创建复合程序的原则在真正工作的应用程序AutoGraf中有少许修改, 此应用程序在名为图形EA交易: AutoGraf的文章中有更多描述 .

SK. Dnepropetrovsk. 2006