
基于MQL5的自动化交易策略(第一部分):Profitunity系统(比尔·威廉姆斯的《交易混沌》)
引言
在本文中,我们探讨了Profitunity系统,这是一种由Bill Williams开发的交易策略,旨在通过利用一组关键指标从市场的“混沌”中获利,并展示如何在MetaQuotes Language 5(MQL5)中实现自动化。我们首先对策略及其核心原则进行概述。然后,我们逐步介绍在MQL5中的实现过程,重点关注编码关键指标以及自动化入场和出场信号。接下来,我们对系统进行测试和优化,以确保其在各种市场条件下都能表现良好。最后,我们讨论Profitunity系统在自动化交易中的潜力和有效性。本文涵盖的部分包括:
- Profitunity系统的概述
- 在MQL5中实现策略
- 策略的测试与优化
- 结论
到本文结束时,您将清楚地了解如何使用MQL5创建Profitunity系统,从实现其关键指标到优化其性能。这将为您提供增强交易策略的工具,并利用市场的“混沌”来提升潜在的交易表现。让我们开始深入探讨。
Profitunity系统的概述
Profitunity系统由Bill Williams精心打造,利用一组专业指标,使我们能够理解和应对市场中的混沌波动。该策略结合了趋势跟踪和动量指标的能力,创建了一种动态且高度响应的交易方法。该系统识别趋势反转和市场加速,帮助我们找到高概率的交易设置。 该策略中使用的关键指标包括:
- 分形(Fractals)
- 鳄鱼 (Alligator)
- 动量振荡器(AO)
- 加速振荡器 (AC)
这些指标协同工作,提供对市场行情变化的关键监测,并提供入场和出场信号。让我们更深入地了解适用于该策略的各个指标设置。
指标设置
分形指标它识别市场中的反转点。当出现五个连续的K线,中间的K线是最高的或最低的时,就会形成分形。它们发出新趋势开始或价格反转的潜在信号,有助于标记局部高点或低点,提前显示可能的趋势变化。至于设置,分形的默认周期为2或5。这意味着它会检查一个K线被两侧各有两个K线包围的模式,这些K线更低(对于向下分形)或更高(对于向上分形)。以下是它在图表上的样子。
鳄鱼线指标。该指标由三条平滑移动平均线组成,分别称为颚(Jaw)、牙齿(Teeth)和嘴唇(Lips),它们协同工作以确定市场的趋势。这些线之间的相互作用帮助我们识别市场是否处于趋势中或正在盘整。当这些线开始分离时,它发出趋势信号;当它们收敛时,它表明市场处于盘整阶段。
设置:
- 颚(蓝色线):13周期,平滑8根K线
- 牙齿(红色线):8周期,平滑5根K线
- 嘴唇(绿色线):5周期,平滑3根K线
该指标将帮助我们识别趋势方向和时机,以及市场的入场和出场点。以下是其在图表上的设置。
动量振荡器(AO)该指标是一种动量指标,以中位价计算周期为34和周期为5的简单移动平均线之间的差异。它通过绘制这两个移动平均线之间的差异作为直方图,帮助我们衡量趋势的强度和方向。该指标的设置为默认值。
AO通常用于识别市场中的看涨或看跌动量,并发现趋势转变。直方图在零线上方表示向上动量,而在零线下方则表示向下动量。
加速震荡器 (AC) 该指标源自AO指标,用于衡量市场动量的加速度。它提供了市场动量是加速还是减速的指示,这对于在趋势完全发展之前检测趋势变化至关重要。AC围绕零线振荡,移动到绿色(正)或红色(负)区域。其设置也为默认值。
AC指标与AO指标结合使用,以确认市场强度和动量变化,确保市场在进入交易之前强烈地朝一个方向移动。现在让我们看看系统使用的入场和出场条件。AO和AC指标将如下图所示。
入场和出场条件
该系统使用一组特定条件来进入和退出交易,这些条件基于分形(Fractal)、鳄鱼线(Alligator)、加速振荡器(AC)和精彩振荡器(AO)指标的顺序信号。这些信号协同工作,确保只有在市场强烈确认方向时才启动交易,从而降低虚假信号的风险。
买单入场条件:
- 分形信号:当价格走势形成一系列较低的高点时,出现分形向下信号,暗示价格可能向上反转。
- 鳄鱼线突破:鳄鱼的蓝色线(颚)从下向上被突破,表明上升趋势的开始。
- 加速振荡器(AC)确认:AC处于绿色区域,表明看涨动量并支持趋势的强度。
- 动量振荡器(AO)确认:AO直方图从下向上穿过零线,进一步确认上升动量。
买入条件:
在AO指标直方图从下方穿过零线后,触发买入入场,确认看涨动量的上升。这表明一个强烈的上升趋势正在形成,这是我们开立买入头寸的时机。以下是MetaTrader 5图表中的买入信号示例。
卖单入场条件:
- 分形信号:当价格走势形成一系列较高的低点时,出现分形向上信号,暗示价格可能向下反转。
- 鳄鱼线突破:鳄鱼的蓝色线(颚)从上向下被突破,表明下降趋势的开始。
- 加速振荡器(AC)确认:AC处于红色区域,确认强烈的看跌动量,并表明继续下跌的可能性很高。
- 动量振荡器(AO)确认:AO直方图从上向下穿过零线,表明看跌趋势。
卖出条件:
在AO直方图从上方穿过零线后,触发卖出入场,确认下降动量。这表明市场可能会继续向下移动,这是我们开立卖出头寸的时机。
出场或反转条件:
- 鳄鱼线反转:鳄鱼的绿色线(嘴唇)发生反转,表明当前趋势的结束。嘴唇反转方向表明价格可能正在反转或盘整。
- 加速振荡器(AC)反转:AC从绿色区域穿过到红色区域(反之亦然),表明动量可能发生变化。这是市场动量正在转变的早期指标,当前趋势可能即将结束。
- 动量振荡器(AO)反转:AO直方图在相反方向穿过零线,进一步确认趋势反转的可能性。
离场条件:
可以使用上述任何一个或所有出场条件,但在我们的情况下,将在AO指标直方图穿过到相反区域时选择退出头寸,表明市场动量的转变。
通过使用上述提到的指标组合,Profitunity系统为识别市场反转和强劲趋势机会提供了一种强大的方法。在下一节中,我们将讨论如何在MQL5中实现这些入场和出场条件,从而实现该策略的完全自动化。
在MQL5中实现策略
在学习了有关比尔·威廉姆斯(Bill Williams)Profitunity交易策略的所有理论之后,接下来让我们将理论付诸实践,使用 MetaTrader 5编写一个EA。
要在MetaTrader 5终端中创建EA,请点击“工具”选项卡并选择“MetaQuotes语言编辑器”,或者简单地在键盘上按F4键。另外,您还可以点击工具栏上的IDE(集成开发环境)图标。这将打开MetaQuotes语言编辑器环境,允许您编写EA、技术指标、脚本和函数库。
一旦MetaEditor被打开,在工具栏上,导航到“文件”选项卡并选择“新建文件”,或者简单地按CTRL + N,来创建一个新文档。另外,您也可以点击工具栏上的“新建”图标。这将弹出一个MQL向导(MQL Wizard)窗口。
在弹出的向导中,选择“Expert Advisor (template) ”并点击“下一步”。
在EA的一般属性中,在名称部分,输入您的EA文件的名称。请注意,如果要指定或创建一个不存在的文件夹,您需要在EA名称前使用反斜杠。例如,这里我们默认有“Experts\”。这意味着我们的EA将被创建在Experts文件夹中,我们可以在那里找到它。其他部分都很容易理解,您也可以按照向导底部的链接去详细了解这一过程。
在提供了您想要的EA文件名后,点击“下一步”,再点击“下一步”,然后点击“完成”。 完成这些步骤后,我们现在就可以开始编写和规划我们的策略了。
首先我们从定义EA的基础数据开始。这包括EA的名称、版本信息和MetaQuotes的网站链接。我们还将指定EA的版本号,设置为“1.00”。
//+------------------------------------------------------------------+ //| 1. PROFITUNITY (TRADING CHAOS BY BILL WILLIAMS).mq5 | //| Copyright 2024, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader. | //| https://forexalgo-trader.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader" #property link "https://forexalgo-trader.com" #property description "1. PROFITUNITY (TRADING CHAOS BY BILL WILLIAMS)" #property version "1.00"
当加载程序时,这会将程序基本信息显示出来。然后我们给程序添加一些后面将会用到的全局变量。首先,我们在源代码的开头使用#include包含一个交易实例。这使我们能够访问CTrade类,我们将使用该类来创建一个交易对象。这非常关键,因为我们需要它来执行交易。
#include <Trade/Trade.mqh>
CTrade obj_Trade;
预处理器会用文件Trade.mqh的内容替换#include <Trade/Trade.mqh>这一行。尖括号表示Trade.mqh文件将从标准目录(通常是terminal_installation_directory\MQL5\Include)中获取。当前目录不会包含在搜索路径中。这行代码可以放在程序的任何位置,但通常,为了代码结构更好和引用更方便,所有的包含指令都放在源代码的开头。声明CTrade类的obj_Trade对象将使我们能够轻松访问该类中包含的方法,这得益于MQL5开发者的设计。
接着,声明后续将会用到的几个重要指标的句柄。
int handle_Fractals = INVALID_HANDLE; //--- Initialize fractals indicator handle with an invalid handle value int handle_Alligator = INVALID_HANDLE; //--- Initialize alligator indicator handle with an invalid handle value int handle_AO = INVALID_HANDLE; //--- Initialize Awesome Oscillator (AO) handle with an invalid handle value int handle_AC = INVALID_HANDLE; //--- Initialize Accelerator Oscillator (AC) handle with an invalid handle value
在这里,我们设置初始变量来保存程序中每个技术指标的句柄。具体来说,我们初始化四个整型变量——“handle_Fractals”、“handle_Alligator”、“handle_AO”和“handle_AC”——并赋予它们INVALID_HANDLE值。
这些句柄中的每一个都将作为引用,以便在代码中访问各自的技术指标。通过赋予初始的“INVALID_HANDLE”值,我们确保每个句柄变量在代码中明确显示无效状态,直到稍后进行适当的初始化。这种设置可以防止使用未初始化的句柄导致错误,并有助于检测在初始化过程中是否有任何指标加载失败。
总结一下,这些指标各自的作用如下:
- “handle_Fractals”将存储分形(Fractals)指标的句柄。
- “handle_Alligator”将存储鳄鱼线(Alligator)指标的句柄。
- “handle_AO”将存储动量振荡器(Awesome Oscillator)的句柄。
- “handle_AC”将存储加速振荡器(Accelerator Oscillator)的句柄。
接下来,我们需要定义并初始化用于存储和处理来自这些指标的数据的数组和常量,这些数据我们从初始化的指标句柄中专门获取。我们将按顺序进行,以保持一切清晰和简单,便于引用。
double fractals_up[]; //--- Array to store values for upward fractals double fractals_down[]; //--- Array to store values for downward fractals double alligator_jaws[]; //--- Array to store values for Alligator's Jaw line double alligator_teeth[]; //--- Array to store values for Alligator's Teeth line double alligator_lips[]; //--- Array to store values for Alligator's Lips line double ao_values[]; //--- Array to store values of the Awesome Oscillator (AO) double ac_color[]; //--- Array to store color status of the Accelerator Oscillator (AC) #define AC_COLOR_UP 0 //--- Define constant for upward AC color state #define AC_COLOR_DOWN 1 //--- Define constant for downward AC color state
我们首先创建两个数组,“fractals_up”和“fractals_down”,它们将分别存储向上和向下的分形值。这些数组将帮助我们跟踪特定的分形点,从而识别重要的价格反转或模式。
接下来,我们设置三个数组——“alligator_jaws”、“alligator_teeth”和“alligator_lips”——以存储鳄鱼线(Alligator)指标的不同线条的值。通过将这些值分别存储在不同的数组中,我们可以高效地跟踪每条鳄鱼线的状态,并在交叉引用中使用它们以获取交易信号。
然后,我们定义“ao_values”数组以存储精彩振荡器(Awesome Oscillator,AO)的值。AO将帮助我们识别市场动量和趋势,存储这些值将使我们能够分析随时间的变化,并将其应用于我们的交易条件。
最后,我们定义“ac_color”数组以捕获加速振荡器(Accelerator Oscillator,AC)的颜色状态。这个数组将保存有关AC向上或向下运动的信息,我们将这些信息存储为颜色状态。为了便于实现,我们定义了两个常量:“AC_COLOR_UP”(设置为0)和“AC_COLOR_DOWN”(设置为1)。这些常量将代表AC的颜色状态,其中绿色(向上)表示动量增长,红色(向下)表示趋势放缓。这种设置将简化我们在后续检查AC状态以获取交易信号时的逻辑。
你可能已经注意到,除了分形之外,我们可以轻松地直接存储其他指标的值。这是因为它们的值在每个K线上都是现成可用的。然而,对于分形,它们在特定的摆动点形成,这些点至少距离当前有3根K线的间隔。因此,我们不能简单地检索任何分形K线,因为它们是条件性形成的。因此,我们需要逻辑来跟踪前一个分形值及其方向。以下是我们的逻辑代码。
double lastFractal_value = 0.0; //--- Variable to store the value of the last detected fractal enum fractal_direction {FRACTAL_UP, FRACTAL_DOWN, FRACTAL_NEUTRAL}; //--- Enum for fractal direction states fractal_direction lastFractal_direction = FRACTAL_NEUTRAL; //--- Variable to store the direction of the last fractal
在这里,我们定义变量和一个枚举变量 ,以存储和管理我们在交易分析中检测到的最近分形的数据。我们首先声明“lastFractal_value”变量并将其初始化为“0.0”。这个变量将存储我们在图表上检测到的最后一个分形的数值。通过跟踪这个值,我们可以将其与当前价格进行比较,并分析分形形态以获取潜在的交易信号。
接下来,我们定义一个名为“fractal_direction”的枚举,包含三种可能的状态:“FRACTAL_UP”、“FRACTAL_DOWN”和“FRACTAL_NEUTRAL”。这些状态代表最后一个分形的方向:
- “FRACTAL_UP”表示向上分形,暗示潜在的看跌条件。
- “FRACTAL_DOWN”表示向下分形,暗示潜在的看涨条件。
- “FRACTAL_NEUTRAL”表示尚未确认特定分形方向的状态。
最后,我们声明一个类型为“fractal_direction”的变量“lastFractal_direction”,并将其初始化为“FRACTAL_NEUTRAL”。这个变量将保存最近检测到的分形方向,使我们能够在交易逻辑中根据最新的分形数据进行方向性评估。
从这里开始,我们可以进入实际的代码处理程序。我们需要初始化指标,因此我们将直接进入OnInit事件处理程序,它在程序每次初始化时被调用并执行。//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ //--- //--- return(INIT_SUCCEEDED); //--- Return successful initialization status }
这仅仅是默认的初始化事件处理程序,我们将使用它来初始化我们程序的控制逻辑。接下来,我们需要初始化指标,以便它们能够附加到图表上,并且我们可以使用它们进行数据检索和交易决策。我们将首先如下初始化分形指标。
handle_Fractals = iFractals(_Symbol,_Period); //--- Initialize the fractals indicator handle if (handle_Fractals == INVALID_HANDLE){ //--- Check if the fractals indicator failed to initialize Print("ERROR: UNABLE TO INITIALIZE THE FRACTALS INDICATOR. REVERTING NOW!"); //--- Print error if fractals initialization failed return (INIT_FAILED); //--- Exit initialization with failed status }
在这里,我们通过调用 iFractals函数来初始化“handle_Fractals”变量。这个函数为应用于指定符号(由_Symbol表示)和当前图表周期(_Period)的分形指标创建一个句柄。通过将“handle_Fractals”设置为iFractals的返回值,我们能够访问指标数据,这些数据我们可以在后续策略中用来分析分形形态。
在尝试初始化分形指标后,我们通过检查“handle_Fractals”是否等于INVALID_HANDLE来验证是否初始化成功。“INVALID_HANDLE”值表明指标无法被初始化,这可能由多种原因导致,例如系统资源不足或参数错误。
如果初始化失败,我们使用Print函数输出错误消息:“ERROR: UNABLE TO INITIALIZE THE FRACTALS INDICATOR. REVERTING NOW!”,将其记录到日志中。这条消息将作为问题的明确通知,使故障排除更加容易。然后我们返回INIT_FAILED以退出OnInit函数,表明初始化过程未能成功完成。这个检查有助于确保EA不会在设置不完整的情况下继续执行,这可能会导致执行期间出现错误。我们对鳄鱼线(Alligator)指标的初始化也做同样的处理。
handle_Alligator = iAlligator(_Symbol,_Period,13,8,8,5,5,3,MODE_SMMA,PRICE_MEDIAN); //--- Initialize the alligator indicator with specific settings if (handle_Alligator == INVALID_HANDLE){ //--- Check if the alligator indicator failed to initialize Print("ERROR: UNABLE TO INITIALIZE THE ALLIGATOR INDICATOR. REVERTING NOW!"); //--- Print error if alligator initialization failed return (INIT_FAILED); //--- Exit initialization with failed status }
我们通过调用iAlligator函数来初始化“handle_Alligator”变量。鳄鱼线指标需要几个参数来定义其三条线(颚、牙齿和嘴唇),每条线都对市场趋势做出响应。我们指定这些设置如下:“13”为颚的周期,“8”为牙齿的周期,“5”为嘴唇的周期。此外,我们分别为每条线定义了“8”、“5”和“3”的偏移值,将计算方法设置MODE_SMMA(平滑移动平均线),并使用作为价格类型。
在尝试初始化鳄鱼线指标后,我们检查“handle_Alligator”是否具有有效的句柄。如果它等于INVALID_HANDLE,则表明初始化过程失败。这可能是由于资源不足或参数错误导致的,从而导致鳄鱼线指标无法正常工作。
如果初始化失败,我们调用Print函数显示错误消息:“ERROR: UNABLE TO INITIALIZE THE ALLIGATOR INDICATOR. REVERTING NOW!”。这条消息有助于提醒我们问题所在,从而更容易诊断和解决。打印消息后,我们返回INIT_FAILED,这将退出OnInit函数,并表明初始化未成功完成。
我们采用类似的方法来初始化AO和AC指标,如下所示。
handle_AO = iAO(_Symbol,_Period); //--- Initialize the Awesome Oscillator (AO) indicator handle if (handle_AO == INVALID_HANDLE){ //--- Check if AO indicator failed to initialize Print("ERROR: UNABLE TO INITIALIZE THE AO INDICATOR. REVERTING NOW!"); //--- Print error if AO initialization failed return (INIT_FAILED); //--- Exit initialization with failed status } handle_AC = iAC(_Symbol,_Period); //--- Initialize the Accelerator Oscillator (AC) indicator handle if (handle_AC == INVALID_HANDLE){ //--- Check if AC indicator failed to initialize Print("ERROR: UNABLE TO INITIALIZE THE AC INDICATOR. REVERTING NOW!"); //--- Print error if AC initialization failed return (INIT_FAILED); //--- Exit initialization with failed status }
所有指标成功初始化后,可以在程序加载时自动将它们添加到图表中。我们使用以下代码来实现这一点。
if (!ChartIndicatorAdd(0,0,handle_Fractals)){ //--- Add the fractals indicator to the main chart window and check for success Print("ERROR: UNABLE TO ADD THE FRACTALS INDICATOR TO CHART. REVERTING NOW!"); //--- Print error if fractals addition failed return (INIT_FAILED); //--- Exit initialization with failed status }
在这里,我们尝试使用ChartIndicatorAdd函数将分形指标添加到主图表窗口。 我们传递“0”作为图表ID(表示当前图表),并指定“0”作为窗口ID,目标是主图表窗口。我们之前初始化的用于存储分形指标句柄的“handle_Fractals”变量被传递进来,以添加这个特定的指标。
调用ChartIndicatorAdd函数后,我们检查函数调用是否成功。如果它返回“false”,用“!”表示,这表明分形指标无法被添加到图表中。这里的失败可能是由于图表限制或资源不足导致的。在这种情况下,我们使用Print显示错误消息以提醒我们:“ERROR: UNABLE TO ADD THE FRACTALS INDICATOR TO CHART. REVERTING NOW!”。这条消息将使我们能够在调试过程中快速识别问题的来源。
如果添加失败,我们返回INIT_FAILED以失败状态退出OnInit函数,确保如果分形指标缺失,专家顾问将不会运行,通过确认指标的视觉可用性,帮助防止后续执行中出现错误。我们使用类似的逻辑来添加鳄鱼线(Alligator)指标,因为它也在主窗口中,如下所示。
if (!ChartIndicatorAdd(0,0,handle_Alligator)){ //--- Add the alligator indicator to the main chart window and check for success Print("ERROR: UNABLE TO ADD THE ALLIGATOR INDICATOR TO CHART. REVERTING NOW!"); //--- Print error if alligator addition failed return (INIT_FAILED); //--- Exit initialization with failed status }
为了将其他指标添加到图表中,我们采用类似的方法,只是现在子窗口会改变,因为我们分别为每个指标创建一个新的子窗口,如下所示。
if (!ChartIndicatorAdd(0,1,handle_AO)){ //--- Add the AO indicator to a separate subwindow and check for success Print("ERROR: UNABLE TO ADD THE AO INDICATOR TO CHART. REVERTING NOW!"); //--- Print error if AO addition failed return (INIT_FAILED); //--- Exit initialization with failed status } if (!ChartIndicatorAdd(0,2,handle_AC)){ //--- Add the AC indicator to a separate subwindow and check for success Print("ERROR: UNABLE TO ADD THE AC INDICATOR TO CHART. REVERTING NOW!"); //--- Print error if AC addition failed return (INIT_FAILED); //--- Exit initialization with failed status }
我们将动量振荡器(AO)和加速振荡器(AC)指标分别添加到图表的不同子窗口中,确保它们各自都有独立的视图。为了实现这一点,我们为每个指标使用ChartIndicatorAdd函数。我们指定“0”作为图表ID(表示当前图表),并使用不同的窗口ID:“1”用于AO指标,“2”用于AC指标,将每个指标分别放置在独特的子窗口中。这里的编号至关重要,因为每个指标都必须在其自己的窗口中,因此你需要正确跟踪子窗口的索引。
然后我们分别检查每个添加操作是否成功。如果ChartIndicatorAdd对于AO或AC指标返回“false”,这表明添加过程失败。如果失败,我们使用“Print”输出错误消息,以明确指出哪个特定指标加载失败。例如,如果AO指标无法添加,我们打印:“ERROR: UNABLE TO ADD THE AO INDICATOR TO CHART. REVERTING NOW!”。同样地,如果AC指标添加失败,我们也会输出错误消息。
如果任何一个指标的添加失败,我们立即返回INIT_FAILED,退出OnInit函数并防止进一步执行。为了确保一切正常,我们可以将指标句柄打印到日志中。
Print("HANDLE ID FRACTALS = ",handle_Fractals); //--- Print the handle ID for fractals Print("HANDLE ID ALLIGATOR = ",handle_Alligator); //--- Print the handle ID for alligator Print("HANDLE ID AO = ",handle_AO); //--- Print the handle ID for AO Print("HANDLE ID AC = ",handle_AC); //--- Print the handle ID for AC
运行程序后,我们得到如下初始化数据。
从图中可以看出,句柄ID从10开始,依次到13。在MQL5中,句柄ID至关重要,因为它们允许我们在EA的整个生命周期中引用每个指标。当像CopyBuffer这样的函数从指标中检索值时,它依赖这些句柄来访问正确的数据。 在这里,整数代表每个已初始化指标的具体标识符。每个ID在MQL5环境中作为一个独特的“指针”发挥作用,将每个句柄与其指定的指标链接起来。这有助于EA知道从内存中提取哪个指标的数据,支持高效执行并清晰组织基于指标的任务。
从这一点出发,我们现在需要做的就是将数据容器设置为时间序列。
ArraySetAsSeries(fractals_up,true); //--- Set the fractals_up array as a time series ArraySetAsSeries(fractals_down,true); //--- Set the fractals_down array as a time series ArraySetAsSeries(alligator_jaws,true); //--- Set the alligator_jaws array as a time series ArraySetAsSeries(alligator_teeth,true); //--- Set the alligator_teeth array as a time series ArraySetAsSeries(alligator_lips,true); //--- Set the alligator_lips array as a time series ArraySetAsSeries(ao_values,true); //--- Set the ao_values array as a time series ArraySetAsSeries(ac_color,true); //--- Set the ac_color array as a time series
在这里,我们将每个数组设置为时间序列,这意味着每个数组内的数据将从最近值到最早值进行排序。我们通过将ArraySetAsSeries函数应用于每个数组,并将“true”作为第二个参数传递来实现这一点。 这种设置确保最新数据点始终出现在索引0处,这在交易应用中特别有用,因为访问最新值对于实时决策至关重要。
我们首先将“fractals_up”和“fractals_down”数组设置为时间序列,使我们能够高效地跟踪最新的向上和向下分形值。同样,我们将这种组织方式应用于“alligator_jaws”、“alligator_teeth”和“alligator_lips”数组,这些数组代表鳄鱼线指标的三条线。这使我们能够实时访问每条线的最新值,从而更容易检测市场趋势的变化。
我们还以同样的方式配置了存储动量振荡器指标数据的“ao_values”数组。通过将其设置为时间序列,我们确保最新的振荡器值始终可用于我们的计算。最后,我们将这种结构应用于跟踪加速振荡器颜色状态的“ac_color”数组,以便立即访问最新的颜色状态。负责确保平稳且清晰初始化的完整初始化代码片段如下。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ //--- handle_Fractals = iFractals(_Symbol,_Period); //--- Initialize the fractals indicator handle if (handle_Fractals == INVALID_HANDLE){ //--- Check if the fractals indicator failed to initialize Print("ERROR: UNABLE TO INITIALIZE THE FRACTALS INDICATOR. REVERTING NOW!"); //--- Print error if fractals initialization failed return (INIT_FAILED); //--- Exit initialization with failed status } handle_Alligator = iAlligator(_Symbol,_Period,13,8,8,5,5,3,MODE_SMMA,PRICE_MEDIAN); //--- Initialize the alligator indicator with specific settings if (handle_Alligator == INVALID_HANDLE){ //--- Check if the alligator indicator failed to initialize Print("ERROR: UNABLE TO INITIALIZE THE ALLIGATOR INDICATOR. REVERTING NOW!"); //--- Print error if alligator initialization failed return (INIT_FAILED); //--- Exit initialization with failed status } handle_AO = iAO(_Symbol,_Period); //--- Initialize the Awesome Oscillator (AO) indicator handle if (handle_AO == INVALID_HANDLE){ //--- Check if AO indicator failed to initialize Print("ERROR: UNABLE TO INITIALIZE THE AO INDICATOR. REVERTING NOW!"); //--- Print error if AO initialization failed return (INIT_FAILED); //--- Exit initialization with failed status } handle_AC = iAC(_Symbol,_Period); //--- Initialize the Accelerator Oscillator (AC) indicator handle if (handle_AC == INVALID_HANDLE){ //--- Check if AC indicator failed to initialize Print("ERROR: UNABLE TO INITIALIZE THE AC INDICATOR. REVERTING NOW!"); //--- Print error if AC initialization failed return (INIT_FAILED); //--- Exit initialization with failed status } if (!ChartIndicatorAdd(0,0,handle_Fractals)){ //--- Add the fractals indicator to the main chart window and check for success Print("ERROR: UNABLE TO ADD THE FRACTALS INDICATOR TO CHART. REVERTING NOW!"); //--- Print error if fractals addition failed return (INIT_FAILED); //--- Exit initialization with failed status } if (!ChartIndicatorAdd(0,0,handle_Alligator)){ //--- Add the alligator indicator to the main chart window and check for success Print("ERROR: UNABLE TO ADD THE ALLIGATOR INDICATOR TO CHART. REVERTING NOW!"); //--- Print error if alligator addition failed return (INIT_FAILED); //--- Exit initialization with failed status } if (!ChartIndicatorAdd(0,1,handle_AO)){ //--- Add the AO indicator to a separate subwindow and check for success Print("ERROR: UNABLE TO ADD THE AO INDICATOR TO CHART. REVERTING NOW!"); //--- Print error if AO addition failed return (INIT_FAILED); //--- Exit initialization with failed status } if (!ChartIndicatorAdd(0,2,handle_AC)){ //--- Add the AC indicator to a separate subwindow and check for success Print("ERROR: UNABLE TO ADD THE AC INDICATOR TO CHART. REVERTING NOW!"); //--- Print error if AC addition failed return (INIT_FAILED); //--- Exit initialization with failed status } Print("HANDLE ID FRACTALS = ",handle_Fractals); //--- Print the handle ID for fractals Print("HANDLE ID ALLIGATOR = ",handle_Alligator); //--- Print the handle ID for alligator Print("HANDLE ID AO = ",handle_AO); //--- Print the handle ID for AO Print("HANDLE ID AC = ",handle_AC); //--- Print the handle ID for AC ArraySetAsSeries(fractals_up,true); //--- Set the fractals_up array as a time series ArraySetAsSeries(fractals_down,true); //--- Set the fractals_down array as a time series ArraySetAsSeries(alligator_jaws,true); //--- Set the alligator_jaws array as a time series ArraySetAsSeries(alligator_teeth,true); //--- Set the alligator_teeth array as a time series ArraySetAsSeries(alligator_lips,true); //--- Set the alligator_lips array as a time series ArraySetAsSeries(ao_values,true); //--- Set the ao_values array as a time series ArraySetAsSeries(ac_color,true); //--- Set the ac_color array as a time series //--- return(INIT_SUCCEEDED); //--- Return successful initialization status }
接下来,我们可以转向OnTick事件处理程序,在这里我们将实现控制逻辑。
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ //--- }这只是一个默认的价格变动事件处理程序,我们将用它来构建我们的控制逻辑。接着,我们从指标句柄中获取将来用于分析的数据。
if (CopyBuffer(handle_Fractals,0,2,3,fractals_up) < 3){ //--- Copy upward fractals data; check if copying is successful Print("ERROR: UNABLE TO COPY THE FRACTALS UP DATA. REVERTING!"); //--- Print error message if failed return; } if (CopyBuffer(handle_Fractals,1,2,3,fractals_down) < 3){ //--- Copy downward fractals data; check if copying is successful Print("ERROR: UNABLE TO COPY THE FRACTALS DOWN DATA. REVERTING!"); //--- Print error message if failed return; }
在这里,我们将“handle_Fractals”指标的“fractals_up”和“fractals_down”缓冲区中的数据复制到各自的数组中。我们使用CopyBuffer函数从指标句柄中检索数据。具体来说,我们尝试从第三个最近的K线(索引2)开始,为向上和向下的分形分别复制3个数据点。
首先,我们检查该函数返回的值是否小于3,这将表明“fractals_up”数组中成功复制的值少于3个如果发生这种情况,我们打印一条错误消息(“ERROR: UNABLE TO COPY THE FRACTALS UP DATA. REVERTING!”),并退出函数,以防止使用不完整的数据进行进一步处理。
同样,我们使用相同的CopyBuffer函数尝试将向下的分形数据复制到“fractals_down”数组中。同样,如果复制失败(返回的值少于3个),我们打印相应的错误消息(“ERROR: UNABLE TO COPY THE FRACTALS DOWN DATA. REVERTING!”),并退出函数,以避免进一步的问题。这种方法确保我们的程序不会使用无效或不完整的数据继续执行,从而保持我们交易逻辑的完整性。通过验证已正确复制的值的数量,我们可以防止在分析分形时出现潜在错误,这对于检测市场反转点至关重要。
然而,您可能已经注意到,指标缓冲区编号不同,为0和1。这些是您需要仔细注意的关键索引,因为它们代表指标值的实际映射缓冲区。以下是一个示例,帮助我们理解为什么使用特定的索引。
从图中可以看出,分形向上是第一个,因此索引为0,而分形向下是第二个,索引为1。通过相同的逻辑,我们映射鳄鱼线。
if (CopyBuffer(handle_Alligator,0,0,3,alligator_jaws) < 3){ //--- Copy Alligator's Jaw data Print("ERROR: UNABLE TO COPY THE ALLIGATOR JAWS DATA. REVERTING!"); return; } if (CopyBuffer(handle_Alligator,1,0,3,alligator_teeth) < 3){ //--- Copy Alligator's Teeth data Print("ERROR: UNABLE TO COPY THE ALLIGATOR TEETH DATA. REVERTING!"); return; } if (CopyBuffer(handle_Alligator,2,0,3,alligator_lips) < 3){ //--- Copy Alligator's Lips data Print("ERROR: UNABLE TO COPY THE ALLIGATOR LIPS DATA. REVERTING!"); return; }
在这里,由于缓冲区分布在三条线上,你可以看到缓冲区索引从0开始,依次到1,再到2。在只有一条缓冲区的情况下,例如对于AO指标,我们只会有一个价格缓冲区索引0,如下所示。
if (CopyBuffer(handle_AO,0,0,3,ao_values) < 3){ //--- Copy AO data Print("ERROR: UNABLE TO COPY THE AO DATA. REVERTING!"); return; }
如果我们也对AC指标的数值感兴趣,情况也会相同。然而,我们只需要知道形成的直方图的颜色。这些可以通过使用相同的缓冲区编号逻辑来获取,但在这种情况下,颜色缓冲区通常映射在数据窗口中下一个不可用的缓冲区索引处。因此,在我们的情况下,它是0+1=1,如下所示。
if (CopyBuffer(handle_AC,1,0,3,ac_color) < 3){ //--- Copy AC color data Print("ERROR: UNABLE TO COPY THE AC COLOR DATA. REVERTING!"); return; }
实际上,要获取颜色缓冲区,你只需打开指标属性窗口,颜色索引就会出现在参数选项卡上。
在检索和存储数据之后,我们可以使用它来进行交易决策。为了节省资源,我们将在每个K线上运行检查,而不是在每个生成的tick上。因此,我们需要逻辑来检测新K线的形成。
if (isNewBar()){ //--- Check if a new bar has formed //--- }
在这里,我们使用一个自定义函数,其代码片段如下。
//+------------------------------------------------------------------+ //| IS NEW BAR FUNCTION | //+------------------------------------------------------------------+ bool isNewBar(){ static int prevBars = 0; //--- Store previous bar count int currBars = iBars(_Symbol,_Period); //--- Get current bar count for the symbol and period if (prevBars == currBars) return (false); //--- If bars haven't changed, return false prevBars = currBars; //--- Update previous bar count return (true); //--- Return true if new bar is detected }
在这里,我们定义了一个名为“isNewBar”的boolean函数,用于检查图表上是否出现了新的K线,这将帮助我们检测新K线的形成,这对于更新或重新计算交易条件至关重要。我们首先声明一个静态的integer变量“prevBars”,并将其初始化为0。关键字"static”确保“prevBars”的值在函数的多次调用之间保持不变,而不是在每次调用函数时都被重置。这使我们能够存储上一次函数调用时的K线数量。
接下来,我们定义一个局部整数变量“currBars”,并使用内置函数iBars 检索选定符号(_Symbol)和周期(_Period)的当前柱状图数量。这个函数统计给定时间框架下可用的K线总数,并将其存储在“currBars”中。
然后,我们将“prevBars”与“currBars”进行比较。如果这两个值相等,这意味着自上次调用函数以来没有形成新的K线,因此我们返回“false”以表示未检测到新的K线。如果K线数量发生了变化(即形成了新的K线),条件失败,我们将“prevBars”更新为当前K线数量(“currBars”),以跟踪新的值。最后,我们返回“true”以表示检测到了新的K线。
现在在这个函数内,我们可以通过检查“fractals_up”和“fractals_down”数组中存储的数据,来检测并存储分形数据,并根据这些数据更新最后检测到的分形值和方向。
const int index_fractal = 0; if (fractals_up[index_fractal] != EMPTY_VALUE){ //--- Detect upward fractal presence lastFractal_value = fractals_up[index_fractal]; //--- Store fractal value lastFractal_direction = FRACTAL_UP; //--- Set last fractal direction as up } if (fractals_down[index_fractal] != EMPTY_VALUE){ //--- Detect downward fractal presence lastFractal_value = fractals_down[index_fractal]; lastFractal_direction = FRACTAL_DOWN; }
我们首先定义一个常量整数“index_fractal”,并将其设置为0。这个常量将代表我们想要检查的当前分形数据的索引。在这种情况下,我们检查数组中的第一个分形。
接下来,我们检查“fractals_up[index_fractal]”的值是否不等于EMPTY_VALUE,这表明存在有效的向上分形。如果这个条件为真,我们将向上分形的值存储在“lastFractal_value”变量中。我们还将“lastFractal_direction”更新为“FRACTAL_UP”,以表明最后检测到的分形是向上的。
同样,我们检查“fractals_down[index_fractal]”的值是否不等于EMPTY_VALUE,这表明存在向下的分形。如果这个条件为真,我们将向下的分形值存储在“lastFractal_value”变量中,并将“lastFractal_direction”设置为“FRACTAL_DOWN”,以反映最后检测到的分形是向下的。然后,我们可以记录获取的数据并检查其有效性。
if (lastFractal_value != 0.0 && lastFractal_direction != FRACTAL_NEUTRAL){ //--- Ensure fractal is valid Print("FRACTAL VALUE = ",lastFractal_value); Print("FRACTAL DIRECTION = ",getLastFractalDirection()); }
这将记录分形数据。我们使用了一个自定义函数来获取分形方向,其代码片段如下。
//+------------------------------------------------------------------+ //| FUNCTION TO GET FRACTAL DIRECTION | //+------------------------------------------------------------------+ string getLastFractalDirection(){ string direction_fractal = "NEUTRAL"; //--- Default direction set to NEUTRAL if (lastFractal_direction == FRACTAL_UP) return ("UP"); //--- Return UP if last fractal was up else if (lastFractal_direction == FRACTAL_DOWN) return ("DOWN"); //--- Return DOWN if last fractal was down return (direction_fractal); //--- Return NEUTRAL if no specific direction }
在这里,我们定义了函数“getLastFractalDirection”,用于确定并返回最后检测到的分形的方向。该函数通过检查“lastFractal_direction”变量的值来工作,该变量跟踪最近的分形方向(向上或向下)。我们首先初始化一个string变量“direction_fractal”,并将其默认设置为“NEUTRAL”。这意味着,如果没有找到有效的方向,或者分形方向尚未更新,函数将返回“NEUTRAL”作为结果。
接下来,我们检查“lastFractal_direction”变量的值。如果它等于“FRACTAL_UP”(表明最后检测到的分形是向上的),函数返回字符串“UP”。如果“lastFractal_direction”等于“FRACTAL_DOWN”(表明最后检测到的分形是向下的),函数返回字符串“DOWN”。如果这两个条件都不满足(意味着没有检测到向上或向下的分形,或者方向仍然是中性的),函数返回默认的“NEUTRAL”值,表明目前没有特定的方向。
我们也可以按照以下方式记录其他指标的数据。
Print("ALLIGATOR JAWS = ",NormalizeDouble(alligator_jaws[1],_Digits)); Print("ALLIGATOR TEETH = ",NormalizeDouble(alligator_teeth[1],_Digits)); Print("ALLIGATOR LIPS = ",NormalizeDouble(alligator_lips[1],_Digits)); Print("AO VALUE = ",NormalizeDouble(ao_values[1],_Digits+1)); if (ac_color[1] == AC_COLOR_UP){ Print("AC COLOR UP GREEN = ",AC_COLOR_UP); } else if (ac_color[1] == AC_COLOR_DOWN){ Print("AC COLOR DOWN RED = ",AC_COLOR_DOWN); }
运行后,我们得到如下输出:
分形确认:
其他指标确认:
从可视化结果来看,我们获取的数据与数据窗口中显示的实际数据一致,这表明我们成功地获取了数据。我们可以继续使用这些数据来进行交易决策。首先,我们定义一些必要的函数,用于进行分析,如下所示。
//+------------------------------------------------------------------+ //| FUNCTION TO GET CLOSE PRICES | //+------------------------------------------------------------------+ double getClosePrice(int bar_index){ return (iClose(_Symbol, _Period, bar_index)); //--- Retrieve the close price of the specified bar } //+------------------------------------------------------------------+ //| FUNCTION TO GET ASK PRICES | //+------------------------------------------------------------------+ double getAsk(){ return (NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits)); //--- Get and normalize the Ask price } //+------------------------------------------------------------------+ //| FUNCTION TO GET BID PRICES | //+------------------------------------------------------------------+ double getBid(){ return (NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits)); //--- Get and normalize the Bid price }
在这里,我们定义了3个函数,分别用于获取收盘价、买价(Ask)和卖价(Bid)。接下来,我们需要定义布尔变量,以便根据“鳄鱼的颚”线来检查潜在的交易信号,如下所示。
bool isBreakdown_jaws_buy = alligator_jaws[1] < getClosePrice(1) //--- Check if breakdown for buy && alligator_jaws[2] > getClosePrice(2); bool isBreakdown_jaws_sell = alligator_jaws[1] > getClosePrice(1) //--- Check if breakdown for sell && alligator_jaws[2] < getClosePrice(2);
首先,我们定义“isBreakdown_jaws_buy”来检测潜在买入信号的突破条件。条件是“alligator_jaws”数组在索引1处(代表前一柱)的值应小于前一柱的收盘价,该值通过调用“getClosePrice(1)”函数获取。此外,“alligator_jaws”数组在索引2处(代表再前一柱)的值应大于再前一柱的收盘价,该值通过调用“getClosePrice(2)”函数获取。这种组合表明鳄鱼的颚线已跌破前一K线的收盘价,但高于再前一K线的收盘价,这可以被解释为潜在买入交易的设置。
接下来,我们定义“isBreakdown_jaws_sell”来检测潜在卖出信号的突破条件。在这种情况下,“alligator_jaws”在索引1处的值应大于前一K线的收盘价,而“alligator_jaws”在索引2处的值应小于再前一K线的收盘价。这种情况表明鳄鱼的颚线已升穿前一K线的收盘价,但低于再前一K线的收盘价,暗示了一个潜在的卖出交易设置。从这里开始,我们可以定义开仓的其余条件。
if (lastFractal_direction == FRACTAL_DOWN //--- Conditions for Buy signal && isBreakdown_jaws_buy && ac_color[1] == AC_COLOR_UP && (ao_values[1] > 0 && ao_values[2] < 0)){ Print("BUY SIGNAL GENERATED"); obj_Trade.Buy(0.01,_Symbol,getAsk()); //--- Execute Buy order } else if (lastFractal_direction == FRACTAL_UP //--- Conditions for Sell signal && isBreakdown_jaws_sell && ac_color[1] == AC_COLOR_DOWN && (ao_values[1] < 0 && ao_values[2] > 0)){ Print("SELL SIGNAL GENERATED"); obj_Trade.Sell(0.01,_Symbol,getBid()); //--- Execute Sell order }
在这里,我们根据指标的组合来执行买入和卖出信号的逻辑,具体包括分形方向、鳄鱼颚线突破、加速振荡器(AC)的颜色状态以及动量振荡器(AO)的值。
首先,我们检查是否满足买入信号的条件。我们验证“lastFractal_direction”是否设置为“FRACTAL_DOWN”,这意味着最后检测到的分形是向下的分形。然后,我们检查“isBreakdown_jaws_buy”条件是否为真,这表明鳄鱼的颚线已跌破价格,现在为潜在的买入做好了准备。
此外,我们确保“ac_color[1]”等于“AC_COLOR_UP”,这意味着加速振荡器处于向上颜色状态,表明市场情绪看涨。最后,我们检查动量振荡器的值:“ao_values[1]”应大于零(表明动量为正),“ao_values[2]”应小于零(表明之前的动量为负)。这种组合表明动量发生了反转,市场从负动量转向正动量。如果满足所有这些条件,将生成买入信号,并以指定的手数(0.01)在买价执行买入订单。
另一方面,我们检查是否满足卖出信号的条件。我们验证“lastFractal_direction”是否设置为“FRACTAL_UP”,这意味着最后检测到的分形是向上的分形。然后,我们检查“isBreakdown_jaws_sell”条件是否为真,这表明鳄鱼的颚线已升穿价格,现在为潜在的卖出订单做好了准备。
此外,我们确保“ac_color[1]”等于“AC_COLOR_DOWN”,这意味着加速振荡器处于向下颜色状态,表明市场情绪看跌。最后,我们检查动量振荡器的值:“ao_values[1]”应小于零(表明动量为负),“ao_values[2]”应大于零(表明之前的动量为正)。这种组合表明市场从正动量转向负动量。如果满足所有这些条件,将生成卖出信号,并以指定的手数(0.01)在卖价执行卖出订单。
这将建立头寸。然而,你可以看到没有设置平仓逻辑,因此头寸将一直在那。因此,我们需要根据AO指标的反转来实现平仓的逻辑。
if (ao_values[1] < 0 && ao_values[2] > 0){ //--- Condition to close all Buy positions if (PositionsTotal() > 0){ Print("CLOSE ALL BUY POSITIONS"); for (int i=0; i<PositionsTotal(); i++){ ulong pos_ticket = PositionGetTicket(i); //--- Get position ticket if (pos_ticket > 0 && PositionSelectByTicket(pos_ticket)){ //--- Check if ticket is valid ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); if (pos_type == POSITION_TYPE_BUY){ //--- Close Buy positions obj_Trade.PositionClose(pos_ticket); } } } } } else if (ao_values[1] > 0 && ao_values[2] < 0){ //--- Condition to close all Sell positions if (PositionsTotal() > 0){ Print("CLOSE ALL SELL POSITIONS"); for (int i=0; i<PositionsTotal(); i++){ ulong pos_ticket = PositionGetTicket(i); //--- Get position ticket if (pos_ticket > 0 && PositionSelectByTicket(pos_ticket)){ //--- Check if ticket is valid ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); if (pos_type == POSITION_TYPE_SELL){ //--- Close Sell positions obj_Trade.PositionClose(pos_ticket); } } } } }
在这里,我们根据动量振荡器(AO)的值来实现平仓所有活跃头寸(买入或卖出)的逻辑。 首先,我们检查平仓所有买入头寸的条件。如果“ao_values[1]”小于0且“ao_values[2]”大于0,这表明可能存在从负动量到正动量的转变。这就是平仓所有买入头寸的条件。为此,我们首先使用PositionsTotal函数检查是否存在任何未平仓头寸。
如果有头寸,我们通过循环遍历每个头寸,使用PositionGetTicket函数获取每个头寸的编号。对于每个头寸,我们使用PositionSelectByTicket函数验证头寸编号,确保它是一个有效的头寸。然后,我们使用PositionGetInteger检索头寸类型,并将其转换为ENUM_POSITION_TYPE枚举类型。如果头寸类型是POSITION_TYPE_BUY,这意味着它是一个买入头寸,我们使用“obj_Trade.PositionClose(pos_ticket)”将其平仓,并打印“CLOSE ALL BUY POSITIONS”以确认。
接下来,我们检查平仓所有卖出头寸的条件。如果“ao_values[1]”大于0且“ao_values[2]”小于0,这表明可能存在从正动量到负动量的转变,表明需要平仓所有卖出头寸。同样,我们首先检查是否存在任何未平仓头寸。如果有,我们遍历它们,检索头寸编号,验证编号,并检查头寸类型。如果头寸类型是POSITION_TYPE_SELL,这意味着它是一个卖出头寸,我们使用“obj_Trade.PositionClose(pos_ticket)”将其平仓,并打印“CLOSE ALL SELL POSITIONS”以确认。
运行程序我们将得到下面的输出。
成功了。我们可以看到,当所有入场条件都满足时,我们确认并建立了买入头寸。这就是策略实现的全部内容。我们现在需要在策略测试器中测试程序,并在必要时对其进行优化,使其适应当前的市场条件。这将在后续部分中完成。
策略的测试与优化
在完成核心实现之后,下一步是使用MetaTrader 5策略测试器测试我们的EA,以准确评估其在各种市场情境下的表现。这个测试阶段旨在验证策略的行为是否符合我们的预期,并识别出需要进行优化以改善结果的任何调整。在这里,我们已经完成了初步优化,特别关注对我们策略至关重要的参数。
我们特别关注分形和鳄鱼线的阈值,以评估EA在不同交易时段和条件下的响应能力。这种完整的测试使我们能够验证程序是否能够高效地处理交易,同时遵循预期的买入和卖出信号,从而增强可靠性和性能,同时将潜在错误降至最低。以下是我们从测试中获得的结果。
回测结果:
回测结果图形:
结论
在本文中,我们探讨了如何使用MQL5创建一个基于Profitunity交易策略的EA,整合分形、鳄鱼线指标以及动量振荡器和加速振荡器等振荡器,以识别具有战略意义的买入和卖出信号。 从核心指标和基于阈值的条件开始,我们自动化交易信号,利用市场动量和价格突破。每一步都涉及精心构建代码、配置指标句柄,并根据策略的标准实现触发买入和卖出交易的逻辑。完成实现后,我们使用MetaTrader 5的策略测试器对EA进行了严格的测试,以验证其在各种市场条件下的响应能力和可靠性,通过优化参数强调交易执行的准确性。
本文提供了一种使用MQL5和Profitunity策略自动化交易信号的结构化方法。我们希望它能激励您进一步探索MQL5开发,激发您创建更复杂、更具盈利能力的交易系统。祝编程愉快,交易成功!
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/16365
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.



关于这部分内容,我有一个问题要问文章的作者:
В частности, мы инициализируем четыре переменные типа integer: "handle_Fractals", "handle_Alligator", "handle_AO" и "handle_AC» со значением INVALID_HANDLE.
作为 MQL5编程的初学者,我不太清楚为什么必须一次性用INVALID_HANDLE 值初始化所有指标的句柄?如果我们不进行初始化就声明指标句柄,会发生什么情况?是 Expert Advisor 无法运行还是什么?
请问,弗拉基米尔。
谢谢。解释得非常清楚(感谢程序代码中的许多注释),即使是 Mql5 的新手也很容易理解。非常适合 MQL5 的初学者。我还将阅读您系列中的其他文章,希望之后也能实现我自己的 EA。
感谢您的善意反馈。当然,欢迎。
请问,弗拉基米尔。
感谢您的反馈。并非必须初始化句柄,但这样做是一种良好的编程做法,因为您可以在定义句柄后检查它们是否被初始化,以避免潜在错误。这只是为了进行安全检查。例如,你可以这样做
现在明白了吗?谢谢。