English Русский Español Deutsch 日本語 Português
preview
MQL5 简介(第 8 部分):初学者构建 EA 交易系统指南(二)

MQL5 简介(第 8 部分):初学者构建 EA 交易系统指南(二)

MetaTrader 5交易 | 6 三月 2025, 10:19
953 0
Israel Pelumi Abioye
Israel Pelumi Abioye

概述

在您已经学习了 MQL5 的基础知识之后,您现在可以开始执行与算法交易相关的最重要的任务之一:创建一个可用的 EA 交易。正如我在上一篇文章中所指出的,我们将在本系列中使用基于项目的方法。这种方法既有助于理解抽象概念,也有助于识别它们在实际情况中的使用方式。完成本指南后,您将牢牢掌握如何根据烛形形态和预定条件自动做出交易决策。 

许多初学者经常在论坛上发布问题,尽管出色的社区成员给出了很好的建议,但一些自学成才的用户发现很难将这些答案纳入他们的程序中。虽然提供了特定问题的答案,但提供整个代码的详细解释通常是不切实际的。尽管代码片段和技巧可能很有帮助,但作为一名自学成才的新手,你可能仍然会发现很难把所有东西都放在一起。通过基于项目的方法,本文试图回答这些常见问题,并保证这些解决方案可以被任何 EA 应用。

在本文中,我们将重点开发一个 EA,该 EA 使用前一天的烛形分析来确定其交易方向。如果最近的每日烛形看跌,EA 将专注于当天的卖出,如果看涨,则专注于买入。EA 还将利用当天首个1小时烛形的收盘价来验证其交易信号。在任何时候都不会有超过一个的未平仓头寸,并且将强制执行每日最多两笔交易。它将在严格的交易限制下运作。此外,其操作将仅限于周一至周三的指定交易时间。

在整个项目中,我们将解决初学者经常遇到的几个常见问题,特别是那些经常在 MQL5 论坛上提出的问题。其中一些问题包括:

  • 如何在 MQL5 中买卖?
  • 如何获取烛形的开盘价和收盘价?
  • 如何避免在每个分时报价时进行交易?
  • 如何限制 EA 每次只能进行一笔交易?
  • 如何设置 EA 的交易期间?
  • 如何指定 EA 可以在星期几交易?
  • 如何设置交易的损益限额?

通过采用这种基于项目的方法,我的目标是为这些问题提供清晰、实用的答案,使您能够将解决方案直接实施到您的 EA 交易系统中。这种实践方法不仅有助于理解理论概念,还有助于看到它们在现实世界场景中的应用。让我们进入 MQL5 的世界,开始构建您的交易工具!


1.设置项目

1.1.伪代码

在开始编写代码之前,使用伪代码来描述我们的 EA 交易系统的逻辑是至关重要的。伪代码的重要性在前一篇文章中已经介绍过,强调了它如何促进对思路的清晰规划和组织,从而加快实际的编码过程。要学习 MQL5,请记住,基于项目的学习比一次学习所有内容更可取。你做的项目越多越好。这是我们的 EA 的基本伪代码: 

1.初始化 EA:

  • 设置用于识别交易的幻数。
  • 定义交易的开始和结束时间。
  • 初始化用于存储价格的变量。

2.每次分时报价时:

  • 通过检查当前时间,确保时间在交易时间内。
  • 获取前一天开盘价和收盘价。
  • 如果前一天的收盘价低于开盘价(看跌),则进行卖出交易。
  • 如果前一天的收盘价高于开盘价(看涨),则进行买入交易。
  • 确保一次只打开一笔交易。
  • 只在周一至周四交易。
  • 确保最多有一个未平仓头寸。
  • 每日限额为每日两笔交易。
  • 每日利润限额。
  • 在交易期结束时关闭交易。

1.2.导入所需程序库

库是 MQL5 中预先编写的类和函数集,使编码更容易。它们使您能够专注于 EA 交易系统的独特功能,而不必重写常见功能。在我们之前的一篇文章中,我们讨论了包含文件的想法。如果您不完全理解“包含文件”,请不要担心;你仍然可以阅读这篇文章。您不必一次了解所有内容才能使用 MQL5;通过基于项目的学习,你可以逐渐进步。如果您只是按照说明操作,您就可以在程序中有效地使用代码片段和解释。

类比

把您作为一名程序员所做的工作想象成一个巨大的、迷人的书架。这个书架上有很多书。每本书都包含独特的指导方针和叙述,帮助你实现各种目标。 与您书架上的这些稀有书籍类似的是 MQL5 库。这些书中预先写好的故事(或代码)可用于构建您的交易机器人。您可以拿起合适的书,用里面的故事来指导你,这样您就不用每次写作都从头开始了。 

因此,在开发您的 EA 交易时,您不需要亲自编写每一个细节。这些特定的书籍包含可以用来简化编码的说明。这将使您能够专注于交易机器人的特殊和有趣的方面,而不是陷入平凡的细节。

1.2.1.包含 Trade 库

您必须将 Trade 库集成到您的 EA 中才能管理交易操作。该库提供了一系列用于有效交易管理的类和函数。

类比

想象一下,您拥有一个装满书籍的宽敞书架。每本书都是一种工具包,在各个领域提供帮助。要构建一些独特的东西,比如一个很棒的交易机器人,您需要从书架上拿出合适的书。 就好像从书架上取下了一本特殊的书,将交易库纳入其中。您将在本书中找到管理交易操作所需的所有工具。这本书已经写了所有的说明,所以您不必自己做!

您可以使用以下代码行将这本特殊的书包含到您的项目中:

#include <Trade/Trade.mqh> // Include the trade library for trading functions

您的交易机器人将从这句话中知道“去书架上拿一本名为 Trade.mqh 的书”。这本书包含了各种有用的工具,可以简化开仓、修改和平仓的过程。它确保一切顺利运行,为您节省大量时间。

示例:

#include <Trade/Trade.mqh> // Include the trade library for trading functions

// Create an instance of the CTrade class for trading operations
CTrade trade;
//magic number
int MagicNumber = 103432;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
// Set the magic number for the EA's trades
   trade.SetExpertMagicNumber(MagicNumber);

// Return initialization success
   return(INIT_SUCCEEDED);
  }

1.2.2.1.创建一个 CTrade 类的实例

CTrade 类是 Trade 库的一部分,它封装了执行交易操作的方法。创建此类的实例允许您使用其函数来管理您的交易。

类比

再次想象一下你的书架。您已经选择了包含所有交易说明的特殊书籍(Trade.mqh)。现在,将 CTrade 类视为本书中的一个特殊角色,就像一个了解交易一切的超级英雄。 要使用这个超级英雄,你需要将他带入你的故事中。您可以通过创建 CTrade 类的实例来实现这一点。这就像邀请超级英雄来帮助你完成你的项目。

以下是如何使用一行简单的代码来邀请超级英雄:

CTrade trade;

在这段代码中,CTrade 是超级英雄,而 trade 是您的邀请函。通过创建这个名为 trade 的实例,您是在说“超级英雄 CTrade,请加入我的项目并帮助我管理交易吧!” 

现在,无论何时您需要进行或管理交易,您都可以让您的超级英雄 trade 为您完成。这会使您的工作变得更加轻松并确保一切都正确完成。

1.2.2.2.设置幻数

幻数是分配给您的 EA 进行的每笔交易的唯一标识符。此标识符可帮助您跟踪和管理交易,确保您的 EA 可以区分自己的交易与手动或其他 EA 进行的交易。 把书架上的每一本书都当作一笔交易。有时,你可能有几本看起来很相似的书(交易),但每本书上都有一个特殊的标签,上面有一个唯一的数字。此标签可以帮助您快速找到和管理您的书籍而不会感到困惑。

您的 EA 交易将这个独特的标签称为幻数。这是一种可跟踪和可管理的方式来识别您的 EA 设置的每笔交易,确保它能够将其交易与手动或其他 EA 设置的交易区分开来。

示例:

int MagicNumber = 103432;
在 CTrade 类的上下文中,您可以按如下方式设置幻数:
trade.SetExpertMagicNumber(MagicNumber);

此行代码确保 trade 实例进行的每笔交易都具有指定的幻数,从而易于识别和管理。

要使用预先编写的代码来简化复杂任务,您必须将必要的库导入 MQL5。库类似于工具包,里面装满了节省时间和精力的物品。它们使您能够专注于 EA 交易系统的独特功能,而不必重写常见功能。


2.获取和分析蜡烛图数据

获取和分析蜡烛图的开盘价和收盘价至关重要。这些信息有助于我们了解市场趋势并决定是否买入或卖出。我们需要了解烛形的开盘价和收盘价才能清楚了解市场走势。使用 CopyOpen 和 CopyClose 等函数,我们可以有效地获取这些数据。这样,我们可以通过比较开盘价和收盘价来判断烛形是看涨还是看跌。

2.1.MQL5 中的 CopyClose 和 CopyOpen 函数

要获取给定交易品种和时间框架的烛形收盘价和开盘价,请使用 MQL5 的 CopyClose 和 CopyOpen 函数。这些函数有多种方式可以根据您的需要复制数据。

2.1.1.按第一个位置和所需元素的数量调用

类比

将书架视为价格数据来源,其中每本书都充当代表开盘价和收盘价的烛形。当你想从书架上的特定位置开始复印书籍时,你可以从左边开始数书,并选择起点。例如,如果你想从第三本书开始,你就可以从这本书开始。

接下来,如果您希望从该位置开始复印特定数量的书籍,请选择您想要复印的书籍数量。例如,如果你决定从第三本书开始拿五本书,你最终会得到总共五本书。类似于选择书架上的一本起始书并确定从该点取多少本书,此方法使用 CopyClose 函数允许您指定价格数据中的起始位置以及要复制的元素(烛形)数量。

语法:

CopyClose(symbol_name, timeframe,  start_pos, count, close_array);
CopyOpen(symbol_name, timeframe,  start_pos, count, open_array);

示例:

double close_prices[];
double open_prices[]; 
CopyClose(_Symbol, PERIOD_D1, 2, 5, close_prices); // Copy the close prices of 5 daily candlesticks starting from the 3rd candlestick
CopyOpen(_Symbol, PERIOD_D1, 2, 5, open_prices); // Copy the open prices of 5 daily candlesticks starting from the 3rd candlestick

在这个类比中,特定交易品种和时间框架的价格数据由书架表示,以交易品种名称和时间框架来表示。决定从起始位置(count)拿五本书相当于选择书架上的第三本书。起始位置(start_pos)与该决定类似。就像将选定的书籍握在手中一样,将复制的数据存储在目标数组(close_array)中。


2.1.2.按开始日期和所需元素数量调用

类比

将您的书架视为您的价格数据,其中一本书对应一天的烛形读数。要开始复制某个给定日期的收盘价,请找到书架上与该日期相对应的书。例如,如果您希望从“6 月 1 日”开始,那么就应该从该日期开始。

接下来,如果您想复制从该日期开始的特定数量的收盘价,请选择要取的书籍数量。例如,如果您要获取以“6 月 1 日”开头的五本书的收盘价,您将获得这五本书的收盘价。类似于选择书架上的一本起始书并确定从该点取多少本书,此方法使用 CopyClose 函数允许您指定价格数据中的起始位置以及要复制的元素(烛形)数量。

同样,您可以以相同的方式使用 CopyOpen 函数来获取开盘价。您选择与给定日期相对应的书籍,并选择您想要获取的书籍数量以获得开盘价格。

 语法:

CopyClose(symbol_name, timeframe,  timeframe, count, close_array[]);
CopyOpen(symbol_name, timeframe,  timeframe, count, open_array[]);

示例:

close_prices[];
double open_prices[];
datetime start_date = D'2023.06.01 00:00';  // Starting from June 1st, 2023
// Copy the close prices of 5 daily candlesticks starting from June 1st, 2023
CopyClose(_Symbol, PERIOD_D1, start_date, 5, close_prices);
// Copy the open prices of 5 daily candlesticks starting from June 1st, 2023 CopyOpen(_Symbol, PERIOD_D1, start_date, 5, open_prices);

在这个类比中,特定交易品种和时间框架的价格数据由书架表示,以交易品种名称和时间框架来表示。选择书架上与“6 月 1 日”对应的书籍类似于开始日期 (start_time),而从该开始日期开始选择五本书类似于元素的数量 (count)。就像将选定的书籍握在手中一样,将复制的数据存储在目标数组(close_array)中。

通过这个类比,您将能够看到 CopyClose 和 CopyOpen 函数如何根据开始日期从价格数据书架中有序、直观地获取特定数据。

2.1.3.按所需时间间隔的开始和结束日期调用

类比

想象一下,你书架上的每本书都是一根烛形,显示特定时间段内的开盘价和收盘价。如果您想要获取特定时间框架内的收盘价,则可以寻找涵盖全时间段的书籍。

举例来说,假设您希望复制 6 月 1 日至 6 月 5 日的收盘价。您将找到与 6 月 1 日的起点和 6 月 5 日的终点相对应的书。通过选择这两个日期之间出版的每一本书,可以获得该时间段的收盘价格。

类似于挑选一本开始书和一本结束书来覆盖书架上的某个范围,这种方法可以让您指定开始日期和结束日期。同样,您可以以相同的方式使用 CopyOpen 函数来获取开盘价。

语法:
CopyClose( symbol_name, timeframe, start_time, stop_time, close_array[]);
CopyOpen(symbol_name, timeframe, start_time, stop_time, open_array[]);
示例:
double close_prices[];
double open_prices[];
datetime start_date = D'2023.06.01 00:00'; // Starting from June 1st, 2023
datetime end_date = D'2023.06.05 00:00'; // Ending on June 5th, 2023
// Copy the close prices from June 1st to June 5th
CopyClose(_Symbol, PERIOD_D1, start_date, end_date, close_prices);
// Copy the open prices from June 1st to June 5th
CopyOpen(_Symbol, PERIOD_D1, start_date, end_date, open_prices);

在这个类比中,特定交易品种和时间框架的价格数据由书架表示,以交易品种名称和时间框架来表示。开始日期(start_time)就好像在书架上选择对应“6 月 1 日”的书,结束日期(stop_time)就好像找到对应“6 月 5 日”的书。就像将选定的书籍握在手中一样,将复制的数据存储在目标数组(close_array)中。

通过理解这个类比,您可以看到 CopyClose 和 CopyOpen 函数如何帮助您根据开始和结束日期从价格数据书架中获取特定数据,从而使数据获取过程变得直观且有条理。

将字符串转换为时间

当使用在一天中特定时间窗口内运行的 EA 交易时,使用按所需时间间隔的开始和结束日期进行调用的方法可能会很困难。主要问题是日期会改变,但时间不会改变。我们的 EA 交易是自动化的,因此每天手动输入日期是不可行的。

类比

想象一下一个图书馆,里面的书架是按照日期和时间排列的。如果您希望在一天中的特定时间阅读书籍(或获取数据),则必须知道日期和时间。为了从书架上取回合适的书籍,每天更新日期可能很费力。 将时间字符串视为您有兴趣阅读的特定时间的标签,以简化此过程(获取数据)。

示例:

// Declaring time strings
string start_time_str = "00:00";  // Start time
string end_time_str = "20:00";    // End time

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {

// Converting time strings to datetime values
   datetime start_time = StringToTime(start_time_str);
   datetime end_time = StringToTime(end_time_str);

   Print("start_time: ", start_time,"\nend_time: ",end_time);

  }

解释:

// Declaring time strings string start_time_str = "00:00"; // Start time string end_time_str = "20:00"; // End time

  • 将时间字符串视为您有兴趣阅读的特定时间的标签,以简化此过程(获取数据)。

// Converting time strings to datetime values datetime start_time = StringToTime(start_time_str); datetime end_time = StringToTime(end_time_str);

  • 当您使用 StringToTime 函数时,就好像您拥有一个神奇的书签,在任何一天,它都确切知道指向哪层书架。无论日期如何,这都是事实。这样您就不必每天担心更改日期了。通过保证 start_time 和 end_time 始终引用书架上的当前时间,可以轻松获取适当的书籍(数据),而无需手动更新。

输出:

图 1. 代码输出


在 EA 交易中实现

示例:

#include <Trade/Trade.mqh>

// Create an instance of the CTrade class for trading operations
CTrade trade;

// Unique identifier for the EA's trades
int MagicNumber = 103432;

// Arrays to store the previous day's open and close prices
double daily_close[];
double daily_open[];

// Arrays to store the first H1 bar's open and close prices of the day
double first_h1_price_close[];

// Arrays to store H1 bars' open and close prices
double H1_price_close[];
double H1_price_open[];

// Strings to define the trading start and end times and the first trade time
string start = "00:00";
string end = "20:00";
string firsttrade  = "02:00";

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   trade.SetExpertMagicNumber(MagicNumber);
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
// Convert time strings to datetime format
   datetime start_time = StringToTime(start); // Convert start time string to datetime
   datetime end_time = StringToTime(end); // Convert end time string to datetime
   datetime current_time = TimeCurrent(); // Get the current time
   datetime first_tradetime = StringToTime(firsttrade); // Convert first trade time string to datetime

// Copy daily close and open prices
   CopyClose(_Symbol, PERIOD_D1, 1, 1, daily_close); // Copy the close price of the previous day
   CopyOpen(_Symbol, PERIOD_D1, 1, 1, daily_open); // Copy the open price of the previous day


// Set the arrays to be copied from right to left (latest to oldest)
   ArraySetAsSeries(daily_close, true); // Set daily_close array as series
   ArraySetAsSeries(daily_open, true); // Set daily_open array as series

// Copy close and open prices for the first H1 bar of the day
   CopyClose(_Symbol, PERIOD_H1, start_time, 1, first_h1_price_close); // Copy the close price of the first H1 bar

// Copy close prices for the latest 5 H1 bars
   CopyClose(_Symbol, PERIOD_H1, 0, 5, H1_price_close); // Copy the close prices of the latest 5 H1 bars
   CopyOpen(_Symbol, PERIOD_H1, 0, 5, H1_price_open); // Copy the open prices of the latest 5 H1 bars

// Set the arrays to be copied from right to left (latest to oldest)
   ArraySetAsSeries(H1_price_close, true); // Set H1_price_close array as series
   ArraySetAsSeries(H1_price_open, true); // Set H1_price_open array as series

// If the last daily bar is bearish
   if(daily_close[0] < daily_open[0])
     {
      // Check specific conditions for a sell trade
      if(H1_price_close[2] >= first_h1_price_close[0] && H1_price_close[1] < first_h1_price_close[0] && current_time >= first_tradetime)
        {

         Comment("Its a sell");

        }

     }

// If the last daily bar is bullish
   if(daily_close[0] > daily_open[0])
     {
      // Check specific conditions for a buy trade
      if(H1_price_close[2] <= first_h1_price_close[0] && H1_price_close[1] > first_h1_price_close[0] && current_time >= first_tradetime)
        {
         Comment("Its a buy");
        }
     }

  }

解释:

首先,我们使用 #include <Trade/Trade.mqh> 导入所需的 Trade 库,这样我们就可以使用与交易相关的函数。接下来,我们创建 CTrade 类的一个实例,CTrade trade;,这将用于我们的交易操作。我们还为 EA 的交易定义了一个唯一的标识符,int MagicNumber=103432;。

然后,我们声明数组来存储前一天开盘价和收盘价、当天第一个 H1 柱形的开盘价和收盘价以及 H1 柱形的开盘价和收盘价。这些数组将保存获取的价格数据,我们将对其进行分析以做出交易决策。 交易开始和结束时间以及首次交易时间被定义为字符串,我们稍后将其转换为日期时间格式。在 OnInit 函数中,我们设置 EA 的幻数来识别我们的交易。在 OnTick 函数中,我们将时间字符串转换为日期时间格式并获取当前时间。然后,我们使用 CopyClose 和 CopyOpen 函数将每日和 H1 烛形数据复制到相应的数组中。ArraySetAsSeries 函数将数组设置为从右到左复制,确保最新数据位于索引 0 处。

最后,我们分析获取的蜡烛图数据来确定市场情绪。如果最后的日线图看跌,我们会检查具体情况来决定是否进行卖出交易。类似地,如果最后的日线图看涨,我们会检查买入交易的条件。Comment 函数在图表上显示交易决策。 此示例代码建立了结构来获取和分析每日和 1 小时蜡烛图的开盘价和收盘价,然后使用此分析来确定交易方向。


3.实现交易的执行

3.1 如何在 MQL5 中买卖?

本节将介绍在 MQL5 中执行买卖订单所需的基本步骤,包括确定止损和止盈水平。我们将利用 Trade.mqh 库中的 CTrade 类,它提供了执行交易的基本方法。

设置买入和卖出订单:

我们使用 CTrade 类的 Buy 和 Sell 方法来下达买入和卖出订单。借助这些技术,我们可以用很少的代码来执行交易。将 CTrade 类视为库中以交易功能为中心的特殊书架。我们只需要从书架上拿起所需的书(方法),并在想要执行交易时运用它。

设置止损和止盈:

TP 和 SL (止损和止盈)水平对于风险管理至关重要。TP 指定的是期望的利润,而 SL 指定的是您愿意接受的最大损失。两者都指定了与当前市场价格相关的价格点。将 SL 和 TP 视为书中的书签,指示如果出现问题您将在哪里停止(SL)以及如果一切顺利您将在哪里获得奖励(TP)。

示例:
#include <Trade/Trade.mqh> // Include the trade library for trading functions

// Create an instance of the CTrade class for trading operations
CTrade trade;

// Unique identifier for the EA's trades
int MagicNumber = 103432;

// Arrays to store the previous day's open and close prices
double daily_close[];
double daily_open[];

// Arrays to store the first H1 bar's open and close prices of the day
double first_h1_price_close[];

// Arrays to store H1 bars' open and close prices
double H1_price_close[];
double H1_price_open[];

// Strings to define the trading start and end times and the first trade time
string start = "00:00";
string end = "20:00";
string firsttrade  = "02:00";

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   trade.SetExpertMagicNumber(MagicNumber);
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
// Convert time strings to datetime format
   datetime start_time = StringToTime(start); // Convert start time string to datetime
   datetime end_time = StringToTime(end); // Convert end time string to datetime
   datetime current_time = TimeCurrent(); // Get the current time
   datetime first_tradetime = StringToTime(firsttrade); // Convert first trade time string to datetime

// Copy daily close and open prices
   CopyClose(_Symbol, PERIOD_D1, 1, 1, daily_close); // Copy the close price of the previous day
   CopyOpen(_Symbol, PERIOD_D1, 1, 1, daily_open); // Copy the open price of the previous day

// Set the arrays to be copied from right to left (latest to oldest)
   ArraySetAsSeries(daily_close, true); // Set daily_close array as series
   ArraySetAsSeries(daily_open, true); // Set daily_open array as series

// Copy close and open prices for the first H1 bar of the day
   CopyClose(_Symbol, PERIOD_H1, start_time, 1, first_h1_price_close); // Copy the close price of the first H1 bar

// Copy close prices for the latest 5 H1 bars
   CopyClose(_Symbol, PERIOD_H1, 0, 5, H1_price_close); // Copy the close prices of the latest 5 H1 bars
   CopyOpen(_Symbol, PERIOD_H1, 0, 5, H1_price_open); // Copy the open prices of the latest 5 H1 bars

// Set the arrays to be copied from right to left (latest to oldest)
   ArraySetAsSeries(H1_price_close, true); // Set H1_price_close array as series
   ArraySetAsSeries(H1_price_open, true); // Set H1_price_open array as series

// Get the symbol point size
   double symbol_point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);

// Get the current Bid price
   double Bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

// Calculate the stop loss and take profit prices for sell
   double tp_sell = Bid - 400 * symbol_point;
   double sl_sell = Bid + 100 * symbol_point;

// Get the current Ask price
   double Ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

// Calculate the stop loss and take profit prices for buy
   double tp_buy = Ask + 400 * symbol_point;
   double sl_buy = Ask - 100 * symbol_point;

// If the last daily bar is bearish
   if(daily_close[0] < daily_open[0])
     {
      // Check specific conditions for a sell trade
      if(H1_price_close[2] >= first_h1_price_close[0] && H1_price_close[1] < first_h1_price_close[0] && current_time >= first_tradetime)
        {
         // Execute the sell trade
         trade.Sell(1.0, _Symbol, Bid, sl_sell, tp_sell); // Replace with your lot size
         Comment("It's a sell");
        }
     }

// If the last daily bar is bullish
   if(daily_close[0] > daily_open[0])
     {
      // Check specific conditions for a buy trade
      if(H1_price_close[2] <= first_h1_price_close[0] && H1_price_close[1] > first_h1_price_close[0] && current_time >= first_tradetime)
        {
         // Execute the buy trade
         trade.Buy(1.0, _Symbol, Ask, sl_buy, tp_buy); // Replace with your lot size
         Comment("It's a buy");
        }
     }
  }

解释:

  • 使用#include,我们包含了交易库。
  • 我们构建了一个 CTrade 类的交易实例。
  • 根据当前的买入价和卖出价以及交易品种的点大小,我们确定 SL 和 TP 水平。
  • 我们使用 Buy 和 Sell 方法下达具有指定止损和获利水平的订单。

trade.Buy 和 trade.Sell 的参数:

  • lotsize:交易的交易量。
  • _Symbol:交易执行的交易品种 (例如 EURUSD)。
  • Ask/Bid:当前买入交易的卖价或卖出交易的买价。
  • sl:止损价格水平。
  • tp: 止盈价格水平。

在本文中,我们讨论了获取某些烛形数据,例如开盘价和收盘价,以及如何下达买卖订单。这一进展对初学者来说至关重要,因为它解释了将交易纳入程序的基本原理。然而,由于 OnTick 事件处理程序的存在,订单可能会随着每次报价持续发送。

为了控制风险和防止过度交易,将 EA 交易限制为一次仅进行一笔交易至关重要。为了做到这一点,必须实现逻辑来确定在进行新的交易之前是否存在任何未平仓头寸。此策略可确保仅在必要时执行交易,从而有助于防止过度交易。


防止在每个分时报价时进行交易:

OnTick 的事件处理可能导致每个分时报价都出现新的头寸。我们通过检查新柱和未平仓头寸来确保只在必要时进行交易。将每一个新柱形都视为我们收藏中的一本书。我们确保此刻没有其他书籍(交易)正在被阅读,并且只有当添加新书籍(柱形)时才选择“阅读”(交易)。

示例:

// Flag to indicate a new bar has formed
bool newBar;

// Variable to store the time of the last bar
datetime lastBarTime;

// Array to store bar data (OHLC)
MqlRates bar[];

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {

// Check for a new bar
   CopyRates(_Symbol, PERIOD_H1, 0, 3, bar); // Copy the latest 3 H1 bars
   if(bar[0].time > lastBarTime)  // Check if the latest bar time is greater than the last recorded bar time
     {
      newBar = true; // Set the newBar flag to true
      lastBarTime = bar[0].time; // Update the last bar time
     }
   else
     {
      newBar = false; // Set the newBar flag to false
     }

// If a new bar has formed
   if(newBar == true)
     {
      // If the last daily bar is bearish
      if(daily_close[0] < daily_open[0])
        {
         // Check specific conditions for a sell trade
         if(H1_price_close[2] >= first_h1_price_close[0] && H1_price_close[1] < first_h1_price_close[0] && current_time >= first_tradetime)
           {
            // Execute the sell trade
            trade.Sell(1.0, _Symbol, Bid, sl_sell, tp_sell); // Replace with your lot size
            Comment("It's a sell");
           }
        }

      // If the last daily bar is bullish
      if(daily_close[0] > daily_open[0])
        {
         // Check specific conditions for a buy trade
         if(H1_price_close[2] <= first_h1_price_close[0] && H1_price_close[1] > first_h1_price_close[0] && current_time >= first_tradetime)
           {
            // Execute the buy trade
            trade.Buy(1.0, _Symbol, Ask, sl_buy, tp_buy); // Replace with your lot size
            Comment("It's a buy");
           }
        }

     }

  }

解释:

bool newBar;

  • 为了指示是否形成了新的柱形(蜡烛图),此行声明了一个名为 newBar 的布尔变量。

datetime lastBarTime;

  • 为了存储最近处理的柱形的时间,此行声明了一个名为 lastBarTime 的 datetime 类型的变量。

MqlRates bar[];

  • 此行声明了一个 MqlRates 类型的数组 bar。该数组将用于存储柱形数据,例如开盘价、最高价、最低价和收盘价 (OHLC)。

CopyRates(_Symbol, PERIOD_H1, 0, 3, bar); // Copy the latest 3 H1 bars

  • 使用此行中的 CopyRates 函数将当前交易品种 (_Symbol) 的最近三个小时 (H1) 柱形复制到 bar 数组中。

if(bar[0].time > lastBarTime)

  • 此行确定最近的柱的时间(bar[0].time)是否超过前一个柱的时间。假设它是正确的,那么一个新的柱形就出现了。

newBar = true;

  • 如果形成了新的柱形,则 newBar 标志设置为 true。

lastBarTime = bar[0].time;

  • 最新柱形的时间在 lastBarTime 变量中更新。

else {

newBar = false;

        }

  • 如果最新柱形的时间不大于 lastBarTime,则 newBar 标志设置为 false。

if(newBar == true)

{

 // do this

}

  • 如果形成了新的柱形,就这样做。

此代码确保 if 语句中的代码仅在形成新的柱形时运行,而不是每次分时报价时运行。它通过确定最近的柱形的时间是否长于记录的最后一个柱形的时间来实现这一点。基于此,我们可以确定何时形成新的柱形并适当地设置 newBar 标志。这避免了 'if(newBar == true)' 块内的代码在每个报价上重复运行,从而提高了 EA 交易的性能并消除了不必要的操作。

3.2 限制 EA 每次只开仓一个

由于现在仅在新柱形成后才启动交易执行,我们可以通过限制未平仓头寸的数量来进一步改进我们的 EA。这样,只有在没有其他交易待处理的情况下,EA 才会发起新的交易。

逻辑如下:

  • 检查未平仓的头寸:在进行新的交易之前,我们会查看是否有任何未平仓头寸。如果没有未平仓头寸,EA 将进行交易。
  • 停止在每次分时报价时交易:我们通过检查新柱和未平仓头寸来确保只在必要时进行交易。

将每一个新柱形都视为我们收藏中的一本书。只有在确保当前没有其他书籍(交易)正在被阅读后,我们才会在添加新书籍(柱形)时决定“阅读”(交易)。

示例:

// Initialize the total number of positions being held
int totalPositions = 0;
for(int i = 0; i < PositionsTotal(); i++)
  {
// Get the ticket number for the position
   ulong ticket = PositionGetTicket(i);

// Check if the position's magic number matches
   if(PositionGetInteger(POSITION_MAGIC) == MagicNumber)
     {
      // Increment the total positions count
      totalPositions++;
     }
  }

解释:

int totalPositions = 0;

  • 在此行中,声明整数变量 totalPositions 并将其设置为零。EA 交易当前持有的未平仓头寸数量将使用此变量进行计算。

for(int i = 0; i < PositionsTotal(); i++)

  • 每个未平仓头寸都会在这个 for 循环中被迭代。交易终端中未平仓头寸的总数由 PositionsTotal() 函数返回。
  • i:一个迭代循环计数器,从 0 开始,每次增加 1。只要 i 小于 PositionsTotal(),循环就会继续。

  ulong ticket = PositionGetTicket(i);

  • 在此行上检索索引 i 处位置的仓位编号。PositionGetTicket(i) 函数返回给定索引处的仓位的编号。
  • ticket:ulong(无符号长整型)类型变量,用于存储当前正在检查的仓位的编号。

if(PositionGetInteger(POSITION_MAGIC) == MagicNumber)

  • 此 if 语句确定分配给 EA 的幻数 (MagicNumber) 和当前仓位的幻数是否匹配。
  • PositionGetInteger(POSITION_MAGIC):检索当前仓位的指定属性(在本例中为幻数)的整数值的函数。
  • 幻数:一个预先确定的常数,作为此 EA 的唯一交易标识符。它保证只计算本 EA 已开立的仓位。

totalPositions++;

  • 如果当前仓位的幻数与 MagicNumber 匹配,此行将 totalPositions 计数器增加 1。本质上,这是该特定 EA 已开启的仓位数量的计数。

  此代码块用于计算 EA 当前持有的仓位数量。它是这样完成的:

  • 将计数器 (totalPositions) 初始化为零。
  • 使用 for 循环遍历所有未平仓仓位。
  • 对于每个仓位,获取其编号。
  • 检查仓位的幻数是否与 EA 的幻数匹配。
  • 如果匹配,则增加 totalPositions 计数器。

3.3 将 EA 限制为每天最多两次交易

现在我们已经让 EA 根据每个新柱的特定条件执行交易,因此我们将确保 EA 每天最多只开启两笔交易,以防止过度交易。

实现细节

这部分代码使用 HistorySelect 函数过滤当天的交易历史记录。接下来,我们将使用 EA 的独特幻数来循环遍历所选的交易历史记录并计算算法进行的交易次数。我们仅通过验证每笔交易的性质来计算入场交易。如果总共有两笔入场交易,则 EA 将不会在当天剩余时间内开启新的交易。

将其视为类似于监督书架,其中每本书都是一项交易。每天只能容纳两本新书。在把这两本书放到书架上之后,直到第二天你才会再放其他书到这个书架上。这可确保您保持整洁且易于管理的收藏,防止混乱(过度交易)并维持秩序(风险管理)。

示例:

// Select the trading history within the specified time range
bool success = HistorySelect(start_time, end_time); // Select the trading history

// Initialize the total number of trades for the day
int totalDeal = 0;
if(success)
  {
   for(int i = 0; i < HistoryDealsTotal(); i++)
     {
      // Get the ticket number for the deal
      ulong ticket = HistoryDealGetTicket(i);

      // Check if the deal's magic number matches
      if(HistoryDealGetInteger(ticket, DEAL_MAGIC) == MagicNumber)
        {
         // Check if the deal was an entry
         if(HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_IN)
           {
            // Increment the total deal count
            totalDeal++;
           }
        }
     }
  }

解释:

  • HistorySelect:过滤当天的交易历史记录。
  • totalDeal:用于记录交易量的计数器。
  • 在历史中循环:要计算交易次数,请遍历交易历史记录。
  • 验证幻数: 验证我们的 EA 拥有该交易。
  • 入场交易是通过增加计数器来计算的。

该代码确保 EA 每天最多仅交易两次,保持纪律的交易方式并降低过度交易的风险。

3.4 限制当日盈利或亏损

在本节中,我们将确保 EA 交易的每日总利润或损失不超过限制。

// Initialize the total profit
double totalProfit = 0;
long dealsMagic = 0;
double profit = 0;
if(success)
  {
   for(int i = 0; i < HistoryDealsTotal(); i++)
     {
      // Get the ticket number for the deal
      ulong ticket = HistoryDealGetTicket(i);

      // Check if the deal was an entry
      if(HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_IN)
        {
         // Get the magic number of the deal
         dealsMagic = HistoryDealGetInteger(ticket, DEAL_MAGIC);
        }

      // Check if the deal was an exit
      if(HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_OUT)
        {
         // Get the profit of the deal
         profit = HistoryDealGetDouble(ticket, DEAL_PROFIT);

         // Check if the magic number matches
         if(MagicNumber == dealsMagic)
           {
            // Add the profit to the total profit
            totalProfit += profit;
           }
        }
     }
  }

解释:

  • HistorySelect:过滤当天的交易历史记录。
  • totalProfit:用于追踪当天的总利润或损失的计数器。
  • 循环遍历交易历史来计算收益和损失的总和。
  • 验证入场交易:确定每笔交易的幻数。
  • 通过计算每笔交易的利润来验证退出交易。
  • 添加到总利润:累计与幻数匹配的交易的利润或损失。

通过在当天达到预定的损益阈值后停止额外的交易,这种策略降低了风险。它保证了受控交易,并有助于避免过度交易,因为会增加风险。

3.5 在指定时间关闭所有未平仓头寸

本节将介绍如何使用 MQL5 框架来实现这样的功能。由于夜间或非高峰交易时段没有交易未结,出现意外市场波动的可能性较小。 将此视为图书馆开放时间的结束。我们确保所有头寸在指定的结束时间关闭,就像图书管理员确保所有书籍(交易)在一天结束时归还并入账一样。

// Close trades at the specified end time
for(int i = 0; i < PositionsTotal(); i++)
  {
// Get the ticket number for the position
   ulong ticket = PositionGetTicket(i);

// Check if the position's magic number matches and if it's the end time
   if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && current_time == end_time)
     {
      // Close the position
      trade.PositionClose(ticket);
     }
  }

解释:

此部分通过迭代所有未平仓头寸来获取每个未平仓头寸的不同编号。接下来,它会验证仓位的幻数是否与 EA 的幻数相对应,以及当前时间是否在指定的结束时间内。如果满足这些要求,则使用 CTrade 类的 PositionClose 函数来平仓。

3.6 指定 EA 可以在星期几交易

必须获取一周中的当前星期几,并将其与我们的交易规则进行比较,以确保我们的 EA 交易仅在一周中指定的星期几进行交易。

示例:
//getting the day of week and month
MqlDateTime day; //Declare an MqlDateTime structure to hold the current time and date
TimeCurrent(day); // Get the current time and fill the MqlDateTime structure
int week_day = day.day_of_week; //Extract the day of the week (0 = Sunday, 1 = Monday, ..., 6 = Saturday)

//getting the current month
MqlDateTime month; //Declare a structure to hold current month information
TimeCurrent(month); //Get the current date and time
int year_month = month.mon; //Extract the month component (1 for January, 2 for February, ..., 12 for December)

if(week_day == 5)
  {
   Comment("No trades on fridays", "\nday of week: ",week_day);
  }
else
   if(week_day == 4)
     {
      Comment("No trades on Thursdays", "\nday of week: ",week_day);
     }
   else
     {
      Comment(week_day);
     }

解释:

我们必须确定当前星期几,并将其与预先确定的准则进行比较,以便规范 EA 交易可以交易的日子。为了存储当前日期和时间,我们首先声明一个 MqlDateTime 结构。我们首先使用 TimeCurrent() 函数用当前月份和星期几填充该结构。

接下来,我们使用条件语句检查星期几。该代码显示一条消息,指示如果当前日期是星期五,则星期五不允许进行交易(由值 5 表示)。同样,星期四(值 4)表示星期四不允许交易。该代码仅通过显示当前星期几来表示一周中其他所有日子都允许交易。

类比

把一周中的每一天都看作图书馆中不同类型的书籍。

  • 星期五 (5):该规则禁止在周五进行交易,就像不允许在周五借出侦探小说一样。
  • 星期四 (4):该代码在星期四停止交易,就像科幻小说在星期四不能借阅一样。
  • 其他日子:该代码允许交易所有其他类型(星期几),就像交易书籍类型一样。


结论

本文中我们使用了基于项目的学习方法,使任何人都能轻松理解 MQL5 算法交易的基本原理,即使是完全的初学者。通过学习,您已经了解了如何在 MQL5 中执行基本任务,如买和卖,获取烛形的开盘价和收盘价,以及制定策略以防止每次分时报价时交易。您还学习了如何控制自动交易的重要方面,比如限制 EA 每次只进行一笔交易、定义交易日和交易期间以及设立盈亏限额。

在您的旅程中,请随时就本文所涵盖的主题提问。我在这里提供帮助,无论是理解特定的代码实现,还是澄清这些概念与各种交易策略的关系。与我们联系,我们可以一起讨论如何提高您对 MQL5 算法交易的理解和利用率。 回想一下,掌握任何编程语言,包括 MQL5,都是迈向算法交易的旅程。当然,一个人不可能一下子理解一切。通过采用基于项目的学习,你将逐渐增加你的知识和能力,在这种学习中,你将概念应用于与我们研究过的类似的现实世界项目。每个项目都是逐步建立你的熟练程度和信心的垫脚石。


本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/15299

附加的文件 |
MQL5Project2.mq5 (10.02 KB)
掌握 MQL5:从入门到精通(第三部分)复杂数据类型和包含文件 掌握 MQL5:从入门到精通(第三部分)复杂数据类型和包含文件
这是描述 MQL5 编程主要方面的系列文章中的第三篇。本文涵盖了上一篇文章中未讨论的复杂数据类型。这些包括结构、联合、类和“函数”数据类型。它还解释了如何使用 #include 预处理器指令为程序添加模块化。
人工蜂巢算法(ABHA):测试与结果 人工蜂巢算法(ABHA):测试与结果
在本文中,我们将继续深入探索人工蜂巢算法(ABHA),通过深入研究代码并探讨其余的方法。正如您可能还记得的那样,模型中的每只蜜蜂都被表示为一个独立的智能体,其行为取决于内部和外部信息以及动机状态。我们将在各种函数上测试该算法,并通过在评分表中呈现结果来总结测试效果。
神经网络变得简单(第 96 部分):多尺度特征提取(MSFformer) 神经网络变得简单(第 96 部分):多尺度特征提取(MSFformer)
高效提取与集成长期依赖关系和短期特征,仍然是时间序列分析中的一项重要任务。它们的正确理解及整合,对于创建准确可靠的预测模型是必要的。
您应当知道的 MQL5 向导技术(第 24 部分):移动平均 您应当知道的 MQL5 向导技术(第 24 部分):移动平均
移动平均是大多数交易者使用和理解的最常见指标。我们探讨一些在 MQL5 向导组装智能系统时可能不那么常见的可能用例。