English Русский Español Deutsch 日本語 Português
手动交易自动化的三个方面。 第 1 部分: 交易

手动交易自动化的三个方面。 第 1 部分: 交易

MetaTrader 4示例 | 11 四月 2016, 15:07
7 632 0
Sergey Kravchuk
Sergey Kravchuk

简介

多年来我一直致力于 MetaTrader 4 交易平台的开发工作,为了创建一个适用于交易者的自动化工作站,我尝试过很多模型和方法。 第一个也是最明显的解决方案是在交易脚本集 Mouse Only Trader 中实现的,这个解决方案总体上很成功。 再为其配上风险管理计算和资金管理功能后,我最终得出一个功能相当强大的工具,并将其命名为 Trading Mouse

MetaTrader 4 交易终端的开发人员主要将其作为一种创建全自动交易机器人的手段来进行宣传,对于想要一个舒适工作空间的用户来说,此交易终端及其人机工程学似乎还远不足以满足他们的需求。 因此我开始对图形接口进行实验,同时调查直接在交易图表上使用鼠标和键盘的可能性。 实验结果主要包括两个功能完备的产品: 交易控制台买入/卖出

遗憾的是,在强大的功能和用户友好性背后,还有一个很大的缺点 - Expert Advisor 对快速价格变动和交易命令执行几乎没有响应。 终端在绘制接口特征上花的时间超过了交易时间。 所有尝试使用这些看似用户友好的接口主动交易的操作都会遇到大量的麻烦(尤其是在基点和剥头皮交易的情况下)。

接口类型

图 1 接口类型。

因此,鉴于这些情况,我重新考虑了 Expert Advisor 的要求,然后创建了一个效率最高但朴实无华的剥头皮交易程序。 遗憾的是,它在鼠标命令处理上的老问题仍在,换句话说,还是必须要用平板电脑的触控面板。 结果,它成了一款主要用于平板电脑的純利基产品。 最终我设法解决了 Simple Trader 中几乎所有的疑难点,为我将要在本文中介绍的 Expert Advisor 提供了基础。

之所以介绍我产品的演变历程,主要是为了帮助你了解,本文所述的一切项目的实施并非是因为作者别无选择。 仅仅是因为在经历大量试验和错误之后,我已经选定了最简单也最可靠的解决方案,并乐意与大家共享。



一图胜千言

开始之前,让我们先看看下文中会进行进一步讨论的 Expert Advior 的操作。 这个视频会让你对交易期间终端中发生的流程有一个大致的概念。 看过之后,可以更轻松地理解本文(差不多就像: “喔,原来它是这样做的!”)

想要更好地理解视频,只需要了解有一个 Expert Advisor 在终端中操作,且它是通过将控制脚本放到终端窗口上进行控制的。 Expert Adviser 自身并不执行交易。 它仅可在收到交易者的相关命令之后停顿在某个价位(止损位和获利位)。 脚本名称明确对应其各自的操作,相关用途在工具提示中有述。


既然我们对本文主题有了个大致的概念,现在就让我们深入探讨一下吧。



手动交易自动化的基本原则

首先我想说,我没那个耐心在日线图上交易,更别提周线和月线图了。 只有在绝对稳定并且明显强劲的趋势下,我才会保持仓位一天以上不平仓。 我的工作周期是 5 到 15 分钟。 分钟图上已经有太多噪音,而小时图已足够摧毁我的耐心。 你们很多人会立刻想到两条移动平均线,即 Alligator 和 Elder 的画面等等 - 开玩笑!没有更高时间范围我们还能怎么做?! 难道我们不需要用它们来确定短期趋势和入场价位吗? 很明显,我不用它们。 我只要用锯齿形调整浪和我基于当前 5 到 15 分钟绘制的点数图的组合就好了。 不过现在这不是重点。 我们现在讨论的是盘中交易者。 他们所做的几乎就是经典的剥头皮。 他们需要非常仔细的监控当前价格,并对市场波动作出即时响应!

这种盘中交易意识让我能够极大地简化交易系统的要求。 每个交易品种只需要使用一个订单! 无延迟:一旦看到价格趋向与预期相反,立即平仓以最大程度减少亏损,或在反方向建仓,或在原方向上等待更好的入场点。 无锁定或网格。 如果不确定在哪个方向上建仓,那就别建! 这种说法可能有争议,但这就是我的交易方式,本文所述 EA 就是遵循这种方法。

每个交易品种只用一个订单的决定一下子解决了用于修改或平仓的工作订单的交互选择所带来的所有问题。 结果,Expert Advisor 的代码减少了数倍,代码中的错误也减少了数倍。 其自动化功能中包括快速打开/修改/关闭订单,以及以自动执行追踪止损位的方式实施经典交易原则“让利润奔跑起来”。 但是,与终端中实施的标准追踪止损位相反,此 Expert Advisor 中的追踪止损位完全受交易者控制,随时都可根据快速变化的市场状况轻松进行修改。

但它与标准追踪止损位的主要差别还在于能够设置触发价格。 一旦建仓,你就可以指定一个价格,在达到此价格时,EA 将开始追踪累积利润。 这个价格通常也是获利位的价格。 那么问题出来了:为什么要在那个价位上放弃利润?利润仍有可能继续增加! 由于我们无法确定之后的情况,我们从这一时刻起开始获利,同时使用内置自动跟踪止损位让利润自行奔跑。



著名的诀窍

我说手动执行交易命令的唯一方法是运行特定脚本,并不表示我做出了新的突破。 把脚本放到图表上时,脚本的操作就开始执行了。 与脚本不同,Expert Advisor 必须等待下一次价格变动,才能触发操作开始执行。 但在下一次价格变动时,价格可能与你预期的相差很远。 对于长期交易,这可能并不那么重要。 但在步步紧逼的盘中交易中,这种延迟是不可接受的。

但是,一个窗口中一次只能运行一个脚本。 一旦开始运行新脚本,之前的脚本就将停止工作。 无可否认,脚本本身的功能比 Expert Advisor 的性能逊色不少。 所以我决定将 Expert Advisor 的操作分为两个阶段:交易命令的生成和执行。 结果,我们拥有了一组脚本,它们不执行任何特定操作,只是将它需要做的工作详情传达给 Expert Advisor。

为了统一此命令机制,我决定不在脚本中包括任何有意义的交易操作。 每个脚本都与拥有唯一数字索引的特定命令(用于打开订单、修改止损位/获利位和关闭订单)相关。 所以,将脚本放到图表上时,我们明白要执行什么命令。 可以使用落点坐标设置价格和命令执行时间(如有必要)。 我们只需要看如何将有关把脚本放到图表上的数据传达给 Expert Advisor。

在自动化交易程序前几个版本的开发过程中,我用操作系统级软件和 Win32API 试过了几乎所有符合要求的程序到程序通信方法:

  • 通过磁盘文件或通过内存中的 FileMapping 进行读/写,
  • 使用 http 协议交互,
  • 使用邮件槽等。

它们各自有很棒的特性(例如将交易映射到不同终端上的多个帐户,或远程控制 Expert Advisor 在 VPS 上的操作),但遗憾的是,这些方法的实现和支持都很费力,不值得那么麻烦地去做。 我根据经验设法处理掉不必要的内容,只留下一个正确的选择 - 使用终端全局变量。 脚本与 Expert Advisor 的交互最终归结为以下形式:

放到图表上时,脚本创建多个全局变量,并模拟终端中新的价格变动,以立即让 Expert Advisor 通过读取全局变量中的值来执行命令。


全局脚本命令机制的优势和功能

第一个明显的优势是使用标准平台机制,确保可靠的交互。 源代码大大减少,Expert Advisor 的代码去除了过多的 .ex4 和 .dll 库,使其更易修改和维护。

但此机制最显著的特征是能够在策略测试程序的可视化模式中进行正确操作。 它使你能够在加速的“市场复制”模式下轻松提高你的交易技能,并改进你的交易策略! 你可根据需要设置任何重复报价速度,并坚持练习...与结合市场进行操作的演示帐户不同,在实际的市场速度下,可视化程序始终可用(即使在周末以及没有网络连接可用的情况下)。 你可随时将它暂停(休息一下或对当前状况进行复杂的分析),并可使用复制速度控制在短短数分钟内遍历数个月的历史记录,以确定你交易方法的有效性。

同样重要的一点是,能够通过 Expert Advisor 使用几乎任何指标(本身不可进行交易)提供的信号来进行交易。 在图表上,此类指标通常仅显示特定的图形对象,即入市或关闭订单的信号。 但遗憾的是,它们所能做的就只有这些。一般不能在策略测试程序中进行交易或运行指标。 但如果你有指标的源代码,那么你只需要修改它,方法是添加一个块以创建所需的全局变量(只需添加几行源代码)并在指标设置其信号的价位上调用这个块。 你甚至无需担心每次价格变动都要重复此信号! 由于 Expert Advisor 对于每个交易品种都仅可打开一个订单,重复的建仓命令不会让 Expert Advisor 不停地打开新订单。 利用这样的命令指标,你将能够在策略测试程序中轻松测试指标的交易信号:只需启动 Expert Advisor 测试,将你的指标连接到相关图表,并基于指标信号观察 Expert Advisor 交易状况。

理论内容说得够多了。 现在是时候开始实践理论了。



控制命令的参数

每个 控制脚本都会创建多个全局变量 ,这些变量用于将以下参数传递给 Expert Advisor:

  • #Trader-Chart - 脚本所在窗口的句柄。 我们需要它可以在一个终端中实现多个不同 Expert Advisor 的操作(比如对于不同的交易品种或不同的时间范围)。 在落到特定窗口上时,脚本会获得其句柄。 此命令将由连接到同一窗口的 Expert Advisor 处理。 因此,不同的 Expert Advisor 将仅处理其对应的命令,而忽略其余命令。 可使用 WindowHandle() 函数获取句柄。

  • #Trader-Command - 命令代码。 遗憾的是,终端只允许我们在全局变量中存储数字,因此必须向它们指派数值代码而非有意义的字符串值。 为了确保 Expert Advisor 的值与脚本的值同步,我们一般在 #Trader-commands.mqh 文件中包括以下定义:

    #define   COMMAND_NOACTION  0
    #define   COMMAND_OPEN      1
    #define   COMMAND_MODIFY    2
    #define   COMMAND_CLOSE     3
    #define   COMMAND_TRALSTART 4
    #define   COMMAND_TRALSTOP  5
    #define   COMMAND_TRALSTART0LOSS 6
    #define   COMMAND_TRALSTART50PROFIT 7
    

    如果需要添加自己的新命令,只需添加其代码“COMMAND_MYNEWCOMMAND”并向其分配唯一的代码。 这几乎可让你无限扩展你自己的 Expert Advisor 的功能。

  • #Trader-Price - 脚本落点的价格。 通常这是一个新的建仓价格,止损位或获利位修改价格,或自动跟踪止损位触发价格。

  • #Trader-Time - 交易命令生成时间。 这确实是一个非常重要的参数。 它不允许 Expert Advisor 因任何理由处理终端中剩余的任何旧的挂起命令。 为此,Expert Advisor 中有一个特殊参数 - ExecDelaySec。 Expert Advisor 在执行命令之前会检查当前终端时间,如果脚本中存储的时间超过当前时间 ExecDelaySec 秒,则将不执行此命令并直接删除其详细信息。



控制脚本的源代码

鉴于上述内容,所有脚本的代码似乎都大致相同,唯有命令代码有所不同。 因此,整个常见部分作为一个单独的 .mqh 文件,包括在所有控制脚本之中。

它仅提供与交易可用性相关的最低控制功能。 其他所有操作都应在终端中执行。

#property link      "http://forextools.com.ua"
#include <stderror.mqh>
#include <WinUser32.mqh>
 
#import "user32.dll"
 int RegisterWindowMessageA(string lpstring);
 int PostMessageA(int hWnd,int Msg,int wParam,int lParam);
#import
 
void start()
{
  if ( !IsExpertEnabled() )   
  { MessageBox("Experts disabled. Action canceled.","SimpleTrader ERROR", MB_ICONERROR); return; }
  if ( !IsTradeAllowed() )    
  { MessageBox("Trade disabled. Action canceled.","SimpleTrader ERROR", MB_ICONERROR); return; }
  if ( IsTradeContextBusy() ) 
  { MessageBox("Trade context is busy. Action canceled.","SimpleTrader ERROR", MB_ICONERROR); return; }
 
  GlobalVariableDel ( "#Trader-Chart" );
  GlobalVariableDel ( "#Trader-Command" );
  GlobalVariableDel ( "#Trader-Price" );
  GlobalVariableDel ( "#Trader-Time" );
 
  // to allow several EAs handle scripts dropped onto THE RELEVANT windows only
  GlobalVariableSet ( "#Trader-Chart", WindowHandle( Symbol(), Period()) ); 
  GlobalVariableSet ( "#Trader-Command", COMMAND_ID );
  GlobalVariableSet ( "#Trader-Price", NormalizeDouble ( WindowPriceOnDropped(), Digits ) );
  GlobalVariableSet ( "#Trader-Time", TimeCurrent() );
 
  // emulation of the incoming tick to launch the Expert Advisor
  int hwnd = WindowHandle(Symbol(), Period());
  int MT4InternalMsg = RegisterWindowMessageA("MetaTrader4_Internal_Message");
  PostMessageA(hwnd, MT4InternalMsg, 2, 1);  
}

脚本代码自身包含以下几行:

#property copyright "Open buy or sell according to the dropped price of the Stop Loss level"
#include  "#Trader-commands.mqh"
int COMMAND_ID = COMMAND_OPEN;
#include  "#Trader-common.mqh"  

#property copyright 用于在终端中生成工具提示文本。 对于简单的命令来说,这个文本可能很浅显易懂,但对于你自己的命令(如果你决定将它们添加到 Expert Advisor 中),你需要使用一个非常合适的有意义的名称,外加一段恰当的描述。

此行后跟一般文件中的命令代码。 为给定脚本设置命令代码后,上述的几段一般可执行代码紧随其后。

此时我们停止使用控制脚本,继续执行 Expert Advisor 的操作。



Expert Advisor 对于手动交易自动化的逻辑

起初我想要以流程图的形式表现 Expert Advisor 的操作算法。 但最终我发现用纯文本就非常简单和直接。

获得命令 –> 检查和计算参数 –> 执行命令 –> 显示结果

Expert Advisor 的输入参数和基础逻辑一样简约。 它们控制的内容会在后文我们继续说明 Expert Advisor 源代码的对应块时进行介绍。

#property copyright "Copyright © 2006-2013, Sergey Kravchuk. http://forextools.com.ua"
#property link      "http://forextools.com.ua"

#include <stdlib.mqh>
#include "scripts\#Trader-commands.mqh"

extern int    MaxAllowedRisk = 2// maximum permissible percentage of losses 
extern int    OpenSlippage   = 2// distance from the current price for placing an order to open a position
extern int    CloseSlippage  = 10; // maximum permissible slippage when closing an order
extern int    ExecDelaySec   = 10; // maximum permissible delay as of the START of command execution

extern int    ShowClosedCnt  = 5// number of closed market orders for the history display

extern int    TralStep       = 5// price change step in trailing
extern color  TralStartColor = BlueViolet;
extern color  Tral0LossStartColor = LimeGreen;
extern int    Tral0LossGap   = 10; // offset from the opening price to the breakeven point in trailing

// arrow colors for order opening and closing
extern color  MarkBuyColor    = Blue;
extern color  MarkSellColor   = Red;
extern color  MarkModifyColor = Magenta;
extern color  MarkCloseColor  = Gray;

extern int    MessageShowSec  = 30; // time of displaying the last message on the screen in seconds

extern bool   ShowComment     = true;
extern bool   WriteGadgetFile = true;

Expert Advisor 首先获取终端的参数,以计算当前价格(取决于它在可视化程序还是在真实帐户中操作)。 如果工作图表交易品种上有未结订单,它将立即被 OrderSelect() 运算符选中。

// get dynamic parameters
Spread      = MarketInfo ( Symbol(), MODE_SPREAD );
StopLevel   = MarketInfo ( Symbol(), MODE_STOPLEVEL );

// update prices (instead of Ask and Bid, the Strategy Tester uses Close[0])
if ( IsTesting() ) { CurBid = Close[0]; CurAsk = Close[0]+Spread*Point; } 
else { CurAsk = Ask; CurBid = Bid; }

// Select ANY open order (for possible modifications), including Limit orders 
// e.g. to be able to set Take Profit
SelectedOrder = 0; for ( i = 0; i < OrdersTotal() && SelectedOrder <= 0; i++ )
{
  OrderSelect ( i, SELECT_BY_POS, MODE_TRADES );
  if ( OrderSymbol() == Symbol() && OrderMagicNumber() == MAGIC ) SelectedOrder = OrderTicket();
}

如果当前交易品种有一个未结订单,那么我们会使用跟踪止损位。 此操作的适当性和性能由获取唯一参数 TralStep 的程序来定义。 如果没有未结订单,跟踪参数将被重置。 此操作是必要的,因为可能会在触发止损位时平仓,并且与手动平仓相反,Expert Advisor 并不知道这些参数需要重置。

//trailing trigger price exists as long as there is an open order!!!
if ( SelectedOrder <= 0 ) { ZeroLossTral = false; HalfProfitTral = false; PrevPrice = 0; TralStart = 0; } 
// trailing (checking and performing - within the TrailingStop)
else TrailingStop ( TralStep );

我们进一步检查控制命令是否针对我们的窗口。 如果这是另一个脚本的命令,Expert Advisor 将仅显示有关帐户和未结订单的已更新信息。 如果有相关命令,Expert Advisor 将读取其参数并立即删除命令全局变量(例如为了不意外打开多个订单)。

// if the script is intended for another window, only update the information.
if ( GlobalVariableGet ( "#Trader-Chart" ) != WindowHandle( Symbol(), Period()) ) { ShowInfo(); return; }
// if it is intended for the right window, execute the commands
// get the code and price of the command from the global variables and immediately delete them
if ( GlobalVariableCheck( "#Trader-Command" ) == false ) { CmdID = 0; CmdPrice = 0; ShowInfo(); return; } 
// there is a command, so we execute it
CmdTime  = GlobalVariableGet ( "#Trader-Time" );
CmdID    = GlobalVariableGet ( "#Trader-Command" );
CmdPrice = NormalizeDouble ( GlobalVariableGet ( "#Trader-Price" ), PriceDigits );
GlobalVariableDel ( "#Trader-Command" );
GlobalVariableDel ( "#Trader-Price" );
GlobalVariableDel ( "#Trader-Time" );
// if the command was generated earlier than the permissible execution delay, do not do anything!
if ( CmdTime+ExecDelaySec*60 < TimeCurrent() )
{
  SetError("Igore command from " + TimeToStr(CmdTime,TIME_DATE+TIME_SECONDS) 
         + " delayed > " + ExecDelaySec + "sec");
  ShowInfo();
  return;
}

之后是执行命令和显示执行结果。 必要时,Expert Advisor 的源代码与注释一同提供,这样你在阅读和理解时不会有任何困难。 但是,有几点需要进一步澄清。



请注意:

  1. 我们不能在没有止损位的情况下交易。

    开立订单时始终需要使用预设止损位。 选择手数时需要考虑止损位,使亏损(如果触发)不会超过可用于交易的资金的预设百分比。 修改止损位时,也要遵守此规则。 因此你将无法以较低的风险建仓然后以会损失剩余保证金的方式设置止损位。 此规则间接导致另一个规则。

  2. 我们仅开立限价订单。

    一些交易中心不允许在打开市价订单时当场预设止损位和获利位,这样,建仓时会有滑移,(理论上)可能会出现类似于你在买入时,止损位变得比建仓价还要高的现象。 在市场不稳定,止损位又较低的情况下,的确很可能发生上述情形。 为了避免此限制,我们打开了一个与当前价格略有偏差的限价订单(即上述滑移)。 它允许我们在确切的必要价位下订单,且止损位不超出指定风险百分比。 如果市场朝着预期方向运动,几次价格变动后订单就会打开。 如果市场与预期方向相反,我们有机会无亏损关闭不使用的限价订单,或将其移至更好的价位。



建仓

分析完当前状况并选择了交易方向后,决定所建仓的止损价位,并将 #OPEN 脚本放到止损点上。 其价位也用于确定交易方向。 买入订单的止损价位应始终低于开盘价,并且如果你通过放置脚本来指定的止损价位低于当前价格,则表示你将要买入。 类似的,超过当前价位的止损位表示要打开一个卖出订单。 此规则的唯一例外情况是止损位处于价差中。 但通常来说,交易中心的交易政策是不允许这种情况的,你无法借以设置一个比 StopLevel 参数中指定的价位更接近的止损位。

尽管有大量文本描述了建仓规则,但相应的代码仅有三行长:

// get dynamic parameters
Spread      = MarketInfo ( Symbol(), MODE_SPREAD );
StopLevel   = MarketInfo ( Symbol(), MODE_STOPLEVEL );
// update prices (instead of Ask and Bid, the Strategy Tester uses Close[0])
if ( IsTesting() ) { CurBid = Close[0]; CurAsk = Close[0]+Spread*Point; } 
else { CurAsk = Ask; CurBid = Bid; }
…
// determine the trading direction
if ( CurBid <= CmdPrice && CmdPrice <= CurAsk ) 
{ SetError("Stop inside spread. Undefined trade direction."); ShowInfo(); return; }
if ( CmdPrice < CurBid ) 
{ 
  Operation = OP_BUYSTOP;  
  PriceForOrder = CurAsk+(StopLevel+OpenSlippage)*Point; 
  MarkOpenColor = MarkBuyColor; 
}
if ( CmdPrice > CurAsk ) 
{ 
  Operation = OP_SELLSTOP; 
  PriceForOrder = CurBid-(StopLevel+OpenSlippage)*Point; 
  MarkOpenColor = MarkSellColor; 
}

建仓时存在另一种允许你加快交易进程的机制 - 即能够“自动”反转仓位。 如果你有一个未结的买入订单,并看到市场变得越来越悲观,你将需要关闭买入订单并打开卖出订单。 只需放下建仓脚本就能搞定一切了。 Expert Advisor 将分析情况,在出现仓位反转的情况下,它将首先关闭未结订单(如果它处于未结状态),然后打开反方向的订单。

// if this is the opening in the opposite direction, we first close the current order (if it exists)
if ( SelectedOrder > 0 )
{
  if ( ( Operation == OP_BUYSTOP  && ( OrderType() == OP_BUY  || OrderType() == OP_BUYSTOP  ) ) ||
       ( Operation == OP_SELLSTOP && ( OrderType() == OP_SELL || OrderType() == OP_SELLSTOP ) ) )
  {
    SetError("Only one order per symbol is allowed"); 
    ShowInfo();
    return;
  }
  // if orders are differently directed, close the previous one
  {
    if ( OrderType() == OP_BUY || OrderType() == OP_SELL )
    {
      // update with the current prices. in the Strategy Tester, Close[0] is used instead of Ask and Bid
      if ( IsTesting() ) { CurBid = Close[0]; CurAsk = Close[0]+Spread*Point; }   else { CurAsk = Ask; CurBid = Bid; }
      if ( OrderType() == OP_BUY ) PriceForOrder = CurBid; else PriceForOrder = CurAsk;
      OK = OrderClose ( OrderTicket(), OrderLots(), PriceForOrder, CloseSlippage, MarkCloseColor );
    }
    else 
    {
      OK = OrderDelete ( OrderTicket(), MarkCloseColor );      
      // clear lines and arrows of deleted Limit orders (not to overload the screen with information)
      for(i = ObjectsTotal()-1; i >= 0 ; i--)   if ( StringFind(ObjectName(i),"#"+SelectedOrder) == 0 ) ObjectDelete(ObjectName(i)); 
    }
    if ( OK == false) { SetLastError("Close on reverse"); return; } // failed to delete - do not do anything else 
  } 
}


资金管理和允许风险百分比

确定交易方向后,我们需要确定交易量。 要打开的订单的手数由 Expert Advisor 计算,以便不超出允许的亏损百分比。 计算算法非常简单:它确定标准手数的点值,此点值将进一步用于计算脚本指定的止损位处发生的亏损。 此外,根据成比例规则,手数减少的百分比与标准手数亏损超出与可用资金相关的最大允许风险百分比的值一样大。

// calculate the required lot based on the specified losses at the given Stop level
Loss = AccountBalance() * MaxAllowedRisk / 100.0; // amount of permissible losses expressed as a percentage of the balance
PriceInPoint = MathAbs ( PriceForOrder - CmdPrice ) / Point;
SL1lot = PriceInPoint * TickValue;   // Stop level size expressed in monetary value for a single lot transaction.
Lot = Loss / SL1lot;         // permissible risk / SL1lot
Lot = NormalizeDouble ( MathFloor ( Lot / LotStep ) * LotStep, LotDigits );

如果止损位设置的过高,而风险百分比很低,则必要手数看起来会小于最小允许值。 注意到无法建仓以及可能的最大止损位值时,就能轻松发现这一情况。

if ( Lot < MinLot )
{
  OnePipCost = TickValue * MinLot; // recalculate the point value for the minimum possible lot
  SetError("Stoploss "+DoubleToStr(PriceInPoint,0)
          +" > max available "+DoubleToStr(MathAbs (Loss / OnePipCost),0));
  ShowInfo();
  return;
}

如果手数超出最大允许手数(如果设置的止损位非常接近,并已指定了相当高的风险百分比),你将收到相关提示信息:

if ( MaxLot < Lot )
{
  OnePipCost = TickValue * MaxLot; // recalculate the point value for the maximum possible lot
  SetError("Stoploss "+DoubleToStr(PriceInPoint,0)
          +" < min available "+DoubleToStr(MathAbs (Loss / OnePipCost),0));
  ShowInfo();
  return;
}  


订单修改

如果你需要设置获利位或将止损位移至另一个价位,将 #MODIFY 脚本放到图表中的相应价格点上。 Expert Advisor 将计算出要修改的确切内容(止损位或获利位)并相应地修改订单。 这里,我们使用建仓时所用的技巧:在买入时,如果修改价格低于当前价格,则表示修改止损位。 如果修改价格高于当前价格,则表示修改获利位。

// determine what needs to be modified
if ( ( (OrderType() == OP_BUY  || OrderType() == OP_BUYSTOP)  && CmdPrice >= CurAsk ) ||
     ( (OrderType() == OP_SELL || OrderType() == OP_SELLSTOP) && CmdPrice <= CurBid ) ) TP = CmdPrice;
else // modify the Stop Loss
{
  SL = CmdPrice;

由于亏损严格受控,你将无法将止损位设置得高于建仓时所用价格,从而避免不合理的风险。

  // Stop Loss and risk percentage control! 
  Loss = AccountBalance() * MaxAllowedRisk / 100.0; // losses set as a percentage of the balance
  OnePipCost = TickValue * OrderLots(); // recalculate the point value for the order lot
  if ( OrderType() == OP_BUY  ) NewLoss = ( OrderOpenPrice() - SL ) / Point * OnePipCost;
  if ( OrderType() == OP_SELL ) NewLoss = ( SL - OrderOpenPrice() ) / Point * OnePipCost;
  if ( NewLoss > Loss ) 
  { 
    SetError("Stoploss "+DoubleToStr(NewLoss/OnePipCost,0)
            +" > max available "+DoubleToStr(Loss/OnePipCost,0)); 
    ShowInfo(); return; 
  }
}


平仓

当你决定关闭当前订单时,将 #CLOSE 脚本放到图表中的任意价位点上,因为关闭时这个价位点并无任何意义。 完成此操作后,订单就将被关闭。



跟踪止损位

就像标准跟踪止损位机制那样,Expert Advisor 中内置的跟踪止损位算法也跟踪你指定距离处的价格(如果该价格处于“正确的”方向上)。 但与标准机制不同的是,它拥有更灵活的操作控制。

首先,它允许你设置触发跟踪的价格。 可在建仓后就执行此操作,方法是将 #TRAL-START 脚本放到需要激活跟踪的价位上。 Expert Advisor 将“记住”该值,一旦价格突破对应的价位,跟踪止损位机制就会激活,而 Expert Advisor 会控制跟踪止损位跟在不断变动的价格之后。 为了避免频繁修改订单,Expert Advisor 有一个跟踪止损位步长离散性参数 - TralStep。 仅在价格在“正确”方向上移动了至少为 TralStep 个点的距离时,才会设置新的跟踪止损位。

与标准跟踪止损位的第二个不同之处是 你可在设置跟踪止损位初始点的同时设置其大小。 # TRAL-START0LOSS 脚本将指示初始跟踪点,一旦触发,它将自动将止损位移至与建仓价相距 Tral0LossGap 个点的盈亏平衡点处。 同一脚本的另一个修改 TRAL-START50PROFIT 将在跟踪流程开始时把止损位移至跟踪触发价与建仓价之间的中线处,这将自动保留跟踪开始时至少 50% 的累积利润。

跟踪止损位设置命令将未来的跟踪止损位参数传递给 Expert Advisor。

//——— TRALSTART

// set the trailing trigger price. 
// before the triggering TralStart < 0 as a sign that it is still inactive
if ( CmdID == COMMAND_TRALSTART && CmdPrice > 0) 
{ 
  ZeroLossTral = false; HalfProfitTral = false; TralStart = CmdPrice; PrevPrice = 0; ShowInfo();   return; 
}
//——— TRALSTART0LOSS
// set the trailing trigger price. and simultaneously move the Stop level to the breakeven point
if ( CmdID == COMMAND_TRALSTART0LOSS && CmdPrice > 0) 
{ 
  ZeroLossTral = true; HalfProfitTral = false; TralStart = CmdPrice; PrevPrice = 0; ShowInfo();   return; 
}
//——— TRALSTART50PROFIT
// set the trailing trigger price. and simultaneously move the Stop level to 50% of the earned profit
if ( CmdID == COMMAND_TRALSTART50PROFIT && CmdPrice > 0) 
{ 
  ZeroLossTral = false; HalfProfitTral = true; TralStart = CmdPrice; PrevPrice = 0; ShowInfo();   return; 
}
//——— TRALSTOP
// zero out, which means that trailing stops
if ( CmdID == COMMAND_TRALSTOP ) 
{ 
  ZeroLossTral = false; HalfProfitTral = false; PrevPrice = 0; TralStart = 0; ShowInfo();   return; 
}

此机制帮助你减少盘中交易压力,并可保证在价格达到跟踪触发点时至少能够做到无亏损交易。 由于上述操作都由 Expert Advisor 自动完成,你无需时时监控当前价格,等待跟踪止损位激活。

EA 在跟踪模式下操作时,可通过 #MODIFY 脚本用标准修改命令随时修改跟踪止损位。 将止损位移至另一个价位后,激活的跟踪止损位机制将继续保持该距离。 由于所有操作都在图表中完成,并可直观地得到解决,因此此机制比需要点值的标准跟踪机制更简单也更方便。 就像修改标准止损位那样,跟踪止损位也提供对允许亏损的控制,使亏损不超过指定范围:

void TrailingStop(int Step)
{
  double OnePipCost, NewLoss, SL=0;
  
  if ( OrderSelect ( SelectedOrder, SELECT_BY_TICKET ) == false ) return; 
  if ( OrderCloseTime() > 0 ) return; // the order has already been closed - there is nothing to trail
  
  // check if the data is valid
  if ( TralStart <= 0 || Step < 1 ) return(-1); 

  // Get the data for the Stop level size and risk percentage control!  
  Loss = AccountBalance() * MaxAllowedRisk / 100.0; // losses are set as a percentage of the balance
  OnePipCost = TickValue * OrderLots(); // recalculate the point value for the order lot

  if ( OrderType() == OP_BUY && CurBid >= TralStart ) 
  {
    if ( PrevPrice <= 0 ) 
    { 
      if ( ZeroLossTral   ) SL = NormalizeDouble(OrderOpenPrice() + Tral0LossGap*Point, Digits); 
      if ( HalfProfitTral ) SL = NormalizeDouble(OrderOpenPrice() + (CurBid - OrderOpenPrice())/2.0, Digits); 
      else                  SL = NormalizeDouble(OrderStopLoss(), Digits);
    }
    else SL = NormalizeDouble(OrderStopLoss() + (CurBid - PrevPrice), Digits);
    if ( SL < OrderStopLoss() ) return;
    NewLoss = ( OrderOpenPrice() - SL ) / Point * OnePipCost;
  }
  if ( OrderType() == OP_SELL && CurAsk <= TralStart )  
  {
    if ( PrevPrice <= 0 ) 
    { 
      if ( ZeroLossTral   ) SL = NormalizeDouble(OrderOpenPrice() - Tral0LossGap*Point, Digits); 
      if ( HalfProfitTral ) SL = NormalizeDouble(OrderOpenPrice() - (OrderOpenPrice() - CurAsk)/2.0, Digits); 
      else                  SL = NormalizeDouble(OrderStopLoss(), Digits);
    }
    else SL = NormalizeDouble(OrderStopLoss() - (PrevPrice - CurAsk), Digits);
    if ( SL > OrderStopLoss() ) return;
    NewLoss = ( SL - OrderOpenPrice() ) / Point * OnePipCost;
  }
  if ( SL <= 0 ) return; // the price has not yet crossed the trailing trigger level
  
  if ( NewLoss > Loss ) 
  { 
    SetError("Trailing Stoploss "+DoubleToStr(NewLoss/OnePipCost,0)
            +" > max available "+DoubleToStr(Loss/OnePipCost,0)); 
    ShowInfo(); 

return; 
  }

  if ( ( OrderType() == OP_BUY && SL - OrderStopLoss() >= Step*Point ) || 
      ( OrderType() == OP_SELL && OrderStopLoss() - SL >= Step*Point ) )
  {
    TXT = "• Tralingstop order. Please wait..."; 
    bool OK = OrderModify(OrderTicket(),OrderOpenPrice(),SL,OrderTakeProfit(),OrderExpiration(),Blue);
    if ( OK ) SetWarning("Traling stop moved to " + DoubleToStr(SL, Digits) 
                        +" at " + TimeToStr(TimeLocal(),TIME_SECONDS)); 
    else SetLastError("Tralingstop");
  }
  if ( PrevPrice <= 0 || OK )
  {  
    // only if we have moved the Trailing Stop, store the prices for the Trailing Stop on the next tick
    if ( OrderType() == OP_BUY  ) PrevPrice = CurBid;
    if ( OrderType() == OP_SELL ) PrevPrice = CurAsk;
  }
}


显示有关当前情况的信息

在本文讨论范围内,我们仅考虑了与交易本身相关的问题。 在 Expert Advisor 的操作过程中,本文中提供的代码仅显示一段简短的注释,其中包含订单当前状态和交易帐户的数据。 这些注释无疑可以更精细、更具信息量。 事实上,我也是这么做的。 我所用系统的工作版本输出其对 Windows 7 操作系统标准构件窗口的操作的详细可视化信息。 这是我第二篇文章要重点讲的问题,因为我想讨论一些与交易不直接相关的东西。 但这些东西可以使交易监控流程更方便。 假设你建了一个仓,计算了跟踪止损位,现在只需要等待它触发。 等待时,你无需一直打开着你的终端窗口。 始终位于顶部的小构件窗口将稳定地监控当前状况,而你可以去做其他事情,不时来检查一下就行。

但即使没有构件窗口,Expert Advisor 也有完善的功能,可随时执行实际工作。 可轻松将缺少的内容添加到代码中,以显示当前信息。

谈及 Expert Advisor 的这个块时,唯一值得注意的是错误消息显示和 Expert Advisor 正常操作的作用机制。 所有例外情况和错误消息都存储在特殊字符串变量 LastMsgTxt 中。 此文本在屏幕上显示的时间可达指标参数中设置的 MessageShowSec 秒。



基于外部信号的交易

Expert Advisor 基于外部命令进行交易。 这些命令的源对 Expert Advisor 来说完全无关紧要。 上述所有情况都适用于人为控制 Expert Advisor 的情况。 但全局变量并不是只能手动在终端中设置。 它可以由连接到同一窗口的指标来设置。 以标准 RSI 指标为例,我们来看一看如何使用指标实施此操作。

在计算了所有指标缓冲区数据之后,我们将修改指标计算的源代码,方法是在代码末尾添加一个用于控制 Expert Advisor 的“分析块”。 此示例实施了以下建仓规则:如果 RSI 向下穿越价位 52 或向上穿越价位 48,则将打开新的订单。

它表明,我们无需担心因为内置阻挡机制不允许建多个仓和在仓位反转时自动平仓而不得不强制平仓。 针对 50% 利润的自动跟踪机制在价位 45 和 55 的交叉点激活。 此系统当然不能说盈利。 它在此处仅作示范用途,表明需要在指标代码中做什么、怎么做,才能使其能够控制 Expert Advisor 的操作。

// addition to #Trader ======================================================================
double DefaultStop = 150 * Point; // shifting the Stop level when opening an order

// the Buy condition
if( RSIBuffer[3] <= 50 && RSIBuffer[2] <= 52 && RSIBuffer[1] > 52 )
{
   GlobalVariableSet ( "#Trader-Chart", WindowHandle( Symbol(), Period()) ); 
   GlobalVariableSet ( "#Trader-Command", 1 );
   GlobalVariableSet ( "#Trader-Price", NormalizeDouble ( Close[0] - DefaultStop, Digits ) );
   GlobalVariableSet ( "#Trader-Time", TimeCurrent() );
}

// the Sell condition
if( RSIBuffer[3] >= 50 && RSIBuffer[2] >= 48 && RSIBuffer[1] < 48 ) 
{
   GlobalVariableSet ( "#Trader-Chart", WindowHandle( Symbol(), Period()) ); 
   GlobalVariableSet ( "#Trader-Command", 1 );
   GlobalVariableSet ( "#Trader-Price", NormalizeDouble ( Close[0] + DefaultStop, Digits ) );
   GlobalVariableSet ( "#Trader-Time", TimeCurrent() );
}

// 50% trailing start
if( ( RSIBuffer[2] >= 45 && RSIBuffer[1] < 45 ) || ( RSIBuffer[2] <= 55 && RSIBuffer[1] > 55 ) ) 
{
   GlobalVariableSet ( "#Trader-Chart", WindowHandle( Symbol(), Period()) );
   GlobalVariableSet ( "#Trader-Command", 7 );
   GlobalVariableSet ( "#Trader-Price", NormalizeDouble ( Close[0], Digits ) );
   GlobalVariableSet ( "#Trader-Time", TimeCurrent() );
}
// addition to #Trader ======================================================================

重要提示:此类指标仅可在可视化模式下进行测试,且不可优化。 以下是 RSI 指标测试的一个片段,其中的建仓和平仓都对应于此指标的命令!


基于外部信号进行交易时出现的另一个有趣的可能性是能够在多个终端上对不同的交易帐户实施镜像交易。 跨程序交互的问题不在本系列文章讨论范围之内。 然而,此类实施背后的理念相当明显:在处理命令或在脚本中生成命令时,交易中当前使用的终端之一应将获取的参数传递给另一个终端,再由另一个 Expert Advisor 或指标接收这些参数。 然后在另一个终端中创建同样的控制全局变量,且第二个终端中的对应 Expert Advisor 将执行这些变量,执行方式与第一个终端中的 Expert Advisor 相同。


总结

现在让我们总结一下文中提出的手动交易自动化方法的实施要点:

  • 交易系统的命令和执行组件可分为两个部分:控制脚本给出命令并设置其参数,Expert Advisor 根据这些参数进行操作。 它允许通过添加新特征轻松扩展功能。 为此,我们需要添加一个新的操作代码,即一个将把此代码传递给 Expert Advisor 并在 Expert Advisor 中安排适当处理的脚本。
  • 同一个命令用于设置订单处理参数和确定要执行的操作类型。建仓时的止损位价格定义了交易方向,并在需要修改时确定是否应将此价格应用于止损位或获利位。
  • 此机制负责打开和修改订单,并且 Expert Advisor 中内置的资金管理程序可避免亏损超出风险的指定初始值(用可用余额的百分比表示)。
  • 不同于削减利润的标准获利机制,Expert Advisor 采用“让利润奔跑起来”的算法,不会在获利位关闭获利订单,而是继续跟踪不断增长的利润。
  • 使用标准终端全局变量进行脚本与 Expert Advisor 之间的数据交换时,你可以在处理真实帐户以及在策略测试程序的可视化模式下练习时使用同一个代码。
  • 交易命令不仅可通过命令脚本传递到 Expert Advisor,也可通过修改任何指标的源代码来传递,方法是添加一个块以在生成指标信号的点上创建命令全局变量。 基于此类指标信号的交易甚至可以在终端的标准测试程序中进行测试。 这非常适用于确定准可盈利重绘指标。

随附存档包含 #Trader Expert Advisor 的源代码以及所有控制脚本和一个 RSI 指标示例,此示例采用一个负责将控制命令传递给 Expert Advisor 的内置机制。

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

附加的文件 |
experts.zip (40.86 KB)
Chuvashov 的三角形机械交易系统 Chuvashov 的三角形机械交易系统
我将对基于 Stanislav Chuvashov 理念的机械交易系统进行概述并提供程序代码。 三角形建基于上分形和下分形产生的两条趋势线的交叉。
MetaTrader 4 Expert Advisor 与外部世界交换信息 MetaTrader 4 Expert Advisor 与外部世界交换信息
一个供 МetaТrader 4 Expert Advisor 与外部世界进行信息交换的简单、通用而可靠的解决方案。 信息的提供商和用户可能使用不同的计算机,连接是通过全局 IP 地址实现的。
LibMatrix:矩阵代数库(第一部分) LibMatrix:矩阵代数库(第一部分)
作者让读者熟悉一个简单的矩阵代数库,并提供主要函数的说明和独特特性。
随机沙盒 随机沙盒
本文包括用作为 Excel 文件的交互式“沙盒”,用于模拟随机的 Expert Advisor 回溯测试数据。 读者可以使用它,有助于探索和深入了解 MetaTrader 默认提供的 EA 性能指标。 本文旨在引导读者获得这种体验。