
在MQL5中开发马丁格尔(Martingale)区域恢复策略
概述
在本文中,我们将逐步在MetaQuotes Language 5(MQL5)中为MetaTrader 5(MT5)创建基于马丁格尔(Martingale)区域恢复的外汇交易策略的EA。马丁格尔区域恢复策略是一种常见策略,旨在通过开立与亏损头寸相反且交易量稍大的头寸来抵消亏损头寸。它本质上是一种趋势跟踪策略,不在乎市场的方向,而是希望市场在某个时刻会呈现上升趋势或下降趋势,并最终达到特定的目标。加入我们,不仅会讨论这一系统,还会在MQL5中将其自动化。
本次研究将涵盖以下主题:
- 区域恢复策略的定义
- 区域恢复策略的描述
- 在MQL5中的实现
- 回测结果
- 结论
区域恢复策略的定义
区域恢复交易策略是一种复杂的方法,主要用于外汇交易中控制和减少损失。该策略的核心思想是确定交易者预期市场价格波动的精确价格范围。当一笔交易走势不利并进入预先设定的损失区间时,交易者不是选择在亏损时平仓,而是启动一系列反向交易来构建一个恢复区间。理想情况下,这些交易应该这样设置:当市场重新进入恢复区间时,能够使整个仓位以盈亏平衡甚至盈利的状态平仓。
此技术主要侧重于对冲和均价下降策略。当市场走势与首笔交易相反时,交易者会建立一个等量的相反头寸来进行对冲。如果市场继续下行,交易者会在关键间隔处新开仓,以降低平均入场价格。这些交易的设计目的是,当市场回归平均水平时,恢复交易的累计收益能够抵消首笔交易的损失。这种方法需要有一个明确的风险管理策略,因为持仓量的增加可能会导致较高的保证金要求和面临市场波动的更大风险。
区域恢复策略的主要优势之一在于,它能够在不需要精确预测市场走势的情况下,将亏损的交易转变为盈利的交易。它使交易者能够从市场动荡和反转中获利,将不利趋势转化为反弹的机会。
区域恢复交易方法最适合那些精通风险管理策略和了解市场动态的老练交易者。它是交易者百宝箱中的一件有用的神器,尤其有助于应对价格频繁波动的动荡市场。尽管它有能力从失败的交易中恢复并实现盈利,但其复杂性和相关风险要求交易者进行周密的计划和谨慎的执行。
区域恢复策略的描述
基于市场分析,区域恢复交易方法始于开设初始头寸。假设一位交易者因认为市场会上涨而开设了一个买入头寸。从上涨趋势中获利是其首要目标。如果市场走势积极,价格上涨到预定的盈利目标,交易者会平仓并锁定利润。通过这种简单的策略,交易者就能从有利的市场走势中获利,而无需使用更复杂的策略。
另一方面,如果市场走势与初始的多头头寸相反,并触及预定的亏损点,区域恢复方法就会启动其减损机制。此时,交易者不是以亏损平仓,而是开立一个量更大的头寸,通常是前一个多头头寸规模的两倍。通过用新头寸的潜在收益来抵消第一笔交易的损失,这种反向交易旨在建立对冲。该策略利用市场固有的波动性,预期在某个范围内会出现反转或至少稳定下来。
随着市场继续发展,交易者会密切关注新的卖出头寸。如果市场继续下跌并达到另一个预定点,那么初始买入头寸和更大规模的卖出头寸共同作用,理想情况下会达到盈亏平衡或盈利状态。之后,交易者可以将两个都平仓,用较大后续交易的盈利来抵消第一笔交易的损失。为了确保整体能在恢复区内以盈利的方式平仓,该策略需要有精确的计算和时机的把握。
在MQL5中的实现
为了在MQL5中创建一个专注于可视化的区域恢复位置的EA(通常是四个),我们首先需要定义这些位置。我们需要尽早定义,因为它们在可视化交易系统中至关重要。通过使用关键字"#define"来实现,它是MQL5中的一个内置指令,可用于为常量分配有助于记住的名称。具体如下:
#define ZONE_H "ZH" // Define a constant for the high zone line name #define ZONE_L "ZL" // Define a constant for the low zone line name #define ZONE_T_H "ZTH" // Define a constant for the target high zone line name #define ZONE_T_L "ZTL" // Define a constant for the target low zone line name
其中,我们定义了区域边界的常量:ZONE_H 代表'ZH' (区域高点),ZONE_L 代表'ZL' (区域低点),ZONE_T_H 代表'ZTH' (目标区域高点),以及 ZONE_T_L 代表'ZTL' (目标区域低点)。这些常量在我们的系统中分别代表了相应的价格位置。
在定义完这些常量之后,我们需要开立交易仓位。开立仓位的最简单方法是包含一个交易实例,这通常是通过包含另一个专门用于开立仓位的文件来实现的。我们使用include指令来调用交易库,该库包含用于交易操作的函数。
#include <Trade/Trade.mqh>
CTrade obj_trade;
首先,我们使用尖括号来表示我们想要获取的文件位于include文件夹中,并提供Trade文件夹路径,后面跟一个普通的斜杠或反斜杠,然后是目标文件名,在这个例子中是 "Trade.MQH"。CTrade是一个处理交易操作的类,而obj_trade是这个类的一个实例,通常是从CTrade类创建的一个指针对象,用于访问该类的成员变量。
之后,我们需要一些控制逻辑来生成开仓信号。在我们的例子中,我们使用相对强弱指数(RSI)作为指标,但您也可以根据自己的需要选择任何指标。
int rsi_handle; // Handle for the RSI indicator double rsiData[]; // Array to store RSI data int totalBars = 0; // Variable to keep track of the total number of bars
rsi_handle存储了相对强弱指数(RSI)指标的标识,该标识在OnInit 函数中被初始化,从而使得EA能够获取RSI值。rsiData数组使用CopyBuffer函数获取这些 RSI 值,并根据RSI阈值来确定交易信号。The totalBars variable keeps track of the total number of bars on the chart, ensuring the trading logic executes only once per new bar, preventing multiple executions within a single bar. 这些变量共同作用,使得 EA 能够根据RSI值生成交易信号,同时确保正确的执行时机。
最后,在定义了指标值之后,我们定义了指标信号生成级别和区域级别。
double overBoughtLevel = 70.0; // Overbought level for RSI double overSoldLevel = 30.0; // Oversold level for RSI double zoneHigh = 0; // Variable to store the high zone price double zoneLow = 0; // Variable to store the low zone price double zoneTargetHigh = 0; // Variable to store the target high zone price double zoneTargetLow = 0; // Variable to store the target low zone price
再次,我们定义了两个双精度浮点数变量,overBoughtLevel和overSoldLevel,并将它们的值分别初始化为70和30。这两个变量作为我们生成信号的极限位置。此外,我们还定义了四个附加的双精度浮点数类型变量:zoneHigh、zoneLow、zoneTargetHigh 和 zoneTargetLow,并将它们的值初始化为0。在代码的后续部分,这些变量将保存我们的恢复设置位置。
到目前为止,我们已经定义了所有的全局变量,这些变量对系统至关重要。现在我们可以直接进入OnInit事件处理程序,该程序在EA每次初始化时都会被调用。在这个实例中,我们需要初始化指标句柄,稍后我们将从该句柄复制数据以进行进一步分析。为了初始化指标,我们使用内置函数通过提供正确的参数来返回其句柄。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Initialize the RSI indicator rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE); //--- Return initialization result return(INIT_SUCCEEDED); }
在EA的去初始化(又称卸载)事件处理程序中,我们需要释放指标数据并清空存储的数据。我们这样做的目的是通常为了从计算机内存中释放指标以节省资源,因为之后将不再使用它。如果将来再次需要使用,那么将在EA初始化时重新创建指标句柄和数据。
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Remove RSI indicator from memory IndicatorRelease(rsi_handle); ArrayFree(rsiData); // Free the RSI data array }
接下来,我们进入OnTick事件处理程序,这是一个在每次价格变动(即报价变动)时都会被调用的函数。它再次成为我们的核心功能或部分,因为包含了成功实现交易策略所需的所有关键代码段。由于我们将要开仓,我们需要定义我们的询价(asking)和出价(bidding)的价格,以便可以使用它们的最新值进行分析。
double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
通过双精度类型数据(double data type)来定义不同交易品类的询价和出价的价格,有助于我们获取最新的价格报价以供进一步分析。我们使用的函数是一个重载函数,包含两个变量,但我们使用的是只接受两个参数的第一个变量。第一个参数是交易品类;我们使用_Symbol来自动获取当前图表上的交易品类,并使用SYMBOL_ASK来获取双精度类型的枚举值。
在获取到每个交易点上技术生成的最新价格报价后,全部准备就绪。接下来,我们只需继续定义EA的区间范围和区间目标。我们将它们定义为双精度数据类型(double data type)的变量,并将它们与_Point变量相乘,该变量包含当前交易品类在报价货币中的点值大小。
double zoneRange = 200*_Point; double zoneTarget = 400*_Point;
最后,我们需要一些实用变量来确保我们能够正确地开仓和补仓。在这种情况下,我们定义了四个这样的变量。LastDirection将用于保存最后开立头寸的类型,以确保我们在买入和卖出订单之间交替进行。我们打算将卖出订单的值设为-1,买入订单的值设为+1,这是一个可以依据个人选择更改或实现的任意值。然后,我们使用recovery_lot变量并将其值初始化为0,其作用是保存和存储我们计算出的补仓点,以便在区域补仓系统仍然有效的情况下,能够跟踪下一笔的交易量。此外,我们还有两个布尔变量isBuyDone和isSellDone,它们通常用于存储标识,以帮助我们避免同时开立多个买入或卖出头寸。请注意,我们的所有变量都是静态的,以确保它们不会被误操作更新,除非我们自行对它们进行单独更新。这是因为使用static关键字声明的局部变量会在函数的整个生命周期内保留其值。因此,在每次调用OnTick函数时,我们的局部变量都将保留上一次调用时的值。
static int lastDirection = 0; //-1 = sell, 1 = buy static double recovery_lot = 0.0; static bool isBuyDone = false, isSellDone = false;
在定义了所有实用变量之后,我们就将开立头寸,随后我们将在此基础上实现区间恢复逻辑。我们打算通过使用相对强弱指数(RSI)指标来实现这一目标,该指标完全可以根据用户的偏好进行调整,因此你可以直接使用你的入场策略。现在,为了节省资源,我们希望在每根K线图上获取指标数据,而不是在每次价格变动时都获取。可以通过以下方式实现:
int bars = iBars(_Symbol,PERIOD_CURRENT); if (totalBars == bars) return; totalBars = bars;
这里,我们定义了一个整数型变量bars,并将其初始化为图表上当前柱形的数量,这通过使用内置函数iBars来实现,该函数接受两个参数:交易品类和周期。然后,我们检查之前定义的bars数量是否等于当前柱形的数量。如果相等,代表我们仍然在当前柱形上,因此执行返回,意味着我们中断操作并将控制权返回给调用程序。否则,如果这两个变量不匹配,意味着我们已经转入了一个新的K线图,可以继续。因此,我们将totalBars 的值更新为当前柱形的数量,以便在下一此价格变动时,我们能够获得totalBars 变量的更新值。
为了使区间恢复生效并且只开立且管理一个头寸,我们需要确保每个实例中只开立一个头寸。因此,如果头寸数量大于1,我们就不需要添加任何其他头寸,并且可以提前返回。这与下面的描述相同。
如果我们没有返回到这一点,这意味着我们还没有任何头寸,我们可以继续开一个。因此,我们从指标句柄中复制数据并将其存储在指标数据数组中以便进一步分析。这是通过复制缓冲区函数来实现的。
if (PositionsTotal() > 0) return;
if (!CopyBuffer(rsi_handle,0,1,2,rsiData)) return;
如下图所示,复制缓冲区函数是一个返回为整数的重载函数。出于安全角度考虑,我们使用if语句来检查它是否返回了请求的数据,如果没有,则意味着我们没有足够的数据可供返回,因此无法进行进一步地分析。让我们看看这个函数的具体作用。它包含五个参数。第一个参数是指标句柄,用于从中复制数据;第二个参数是指标的缓冲区编号,在此情况下为0,但完全可能因所使用的指标而异。第三个参数是起始位置,即要从哪个数据条开始复制数据。在这里,我们使用1来表示从图表上当前数据条之前的那个数据条开始。第四个参数是计数,即要存储的数据数量。对我们来说,两份数据就足够了,因为我们不做详细分析。最后,我们提供了用于存储检索到的数据存储的目标数组。
在从指标句柄中获取数据后,我们接下来会使用这些数据来进行交易,或者更确切地说,是用于生成交易信号。首先,我们寻找买入信号。我们通过使用if语句来实现,并据此开立一个买入仓位。
if (rsiData[1] < overSoldLevel && rsiData[0] > overSoldLevel){ obj_trade.Buy(0.01);
如果存储数组中索引为1的数据小于设定的超卖水平,并且索引为0的数据大于超卖水平,那么这意味着RSI线与超卖水平之间发生了交叉,表明我们收到了买入信号。然后,我们使用交易对象和点运算符来访问CTrade类中包含的买入方法。在这种情况下,我们开立一个买入仓位,交易量为0.01,并忽略其他我们不会使用的参数,如止损和止盈,它们会使我们的系统无法按预期工作,因为我们实施了x=区间恢复策略,该策略不需要在止损水平关闭仓位。
然而,为了设置区间恢复位置,我们需要仓位的编号,以便能够访问其属性。为了获取编号,我们使用之前开立仓位的订单结果。在获取编号后,我们要检查它是否大于0,这表示仓位已成功开立,然后我们通过编号选择该仓位。如果我们能够通过编号选中,那么我们就可以获取仓位的属性,但我们只关心开盘价。根据开盘价,我们按如下方式设置仓位:买入仓位的开盘价是我们的高位区间,而为了得到低位区间,我们只需从高位区间中减去区间范围。对于高位区间目标和低位区间目标,我们只需分别在高位区间的基础上加上区间目标值,并在低位区间的基础上减去区间目标值。最后,我们只需将值归一化到交易品类的小数位数以确保准确性,这样我们就完成了所有设置。
ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0){ if (PositionSelectByTicket(pos_ticket)){ double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneHigh = NormalizeDouble(openPrice,_Digits); zoneLow = NormalizeDouble(zoneHigh - zoneRange,_Digits); zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget,_Digits); zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget,_Digits);
到目前为止,我们可以设置级别,但它们在图表上是不可见的。为了实现这一点,我们新建了一个函数,用于在图表上可视化已定义的四个级别。
drawZoneLevel(ZONE_H,zoneHigh,clrGreen,2); drawZoneLevel(ZONE_L,zoneLow,clrRed,2); drawZoneLevel(ZONE_T_H,zoneTargetHigh,clrBlue,3); drawZoneLevel(ZONE_T_L,zoneTargetLow,clrBlue,3);
我们创建了一个简单的空函数,该函数接受四个输入参数或自变量,即levelName(级别名称)、price(价格)、CLR(颜色代码)和width(宽度)。我们使用内置的ObjectCreate函数来创建一条贯穿整个图表长度的水平线,并将其与提供的时间和价格相关联。最后,我们使用ObjectSetInteger函数来设置对象的颜色以实现其独特性,并设置宽度以便于调整可见范围。
void drawZoneLevel(string levelName, double price, color clr, int width) { ObjectCreate(0, levelName, OBJ_HLINE, 0, TimeCurrent(), price); // Create a horizontal line object ObjectSetInteger(0, levelName, OBJPROP_COLOR, clr); // Set the line color ObjectSetInteger(0, levelName, OBJPROP_WIDTH, width); // Set the line width }
最后,我们将最后一个方向的值设置为1,以表示我们确实开立了一个买入头寸;将下一个恢复量设置为初始量乘以一个常数倍数(此情况下为2,即交易量翻倍);最后,将isBuyDone标识设置为true,将isSellDone设置为false。
lastDirection = 1; recovery_lot = 0.01*2; isBuyDone = true; isSellDone = false;
开立头寸并设置区间恢复位置的完整代码如下所示:
//--- Check for oversold condition and open a buy position if (rsiData[1] < overSoldLevel && rsiData[0] > overSoldLevel) { obj_trade.Buy(0.01); // Open a buy trade with 0.01 lots ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0) { if (PositionSelectByTicket(pos_ticket)) { double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneHigh = NormalizeDouble(openPrice, _Digits); // Set the high zone price zoneLow = NormalizeDouble(zoneHigh - zoneRange, _Digits); // Set the low zone price zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line lastDirection = 1; // Set the last direction to buy recovery_lot = 0.01 * 2; // Set the initial recovery lot size isBuyDone = true; // Mark buy trade as done isSellDone = false; // Reset sell trade flag } } }
要开立卖出头寸并设置区间恢复位置,控制逻辑保持不变,但条件相反,如下所示:
else if (rsiData[1] > overBoughtLevel && rsiData[0] < overBoughtLevel) { obj_trade.Sell(0.01); // Open a sell trade with 0.01 lots ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0) { if (PositionSelectByTicket(pos_ticket)) { double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneLow = NormalizeDouble(openPrice, _Digits); // Set the low zone price zoneHigh = NormalizeDouble(zoneLow + zoneRange, _Digits); // Set the high zone price zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line lastDirection = -1; // Set the last direction to sell recovery_lot = 0.01 * 2; // Set the initial recovery lot size isBuyDone = false; // Reset buy trade flag isSellDone = true; // Mark sell trade as done } } }
这时,我们检查卖出信号条件是否满足,如果满足,则立即开立卖出头寸。然后,我们获取该头寸的订单编号(ticket),并使用该编号来检索头寸的开盘价,这个开盘价将用于设置区间恢复位置。由于是卖出头寸,其价格为区间低点,而为了获得区间高点,我们只需在区间低点的基础上加上区间范围。同样地,我们将区间目标值加在区间高点上,得到区间目标高点;从区间低点中减去区间目标值,得到区间目标低点。为了可视化,我们再次使用函数绘制这四个位置。最后,我们只需设置实用变量。
到目前为止,我们已经成功地根据给定的信号开立头寸,并建立了区间恢复系统。以下是实现这一功能的完整代码:
void OnTick() { int bars = iBars(_Symbol,PERIOD_CURRENT); if (totalBars == bars) return; totalBars = bars; if (PositionsTotal() > 0) return; if (!CopyBuffer(rsi_handle,0,1,2,rsiData)) return; //--- Check for oversold condition and open a buy position if (rsiData[1] < overSoldLevel && rsiData[0] > overSoldLevel) { obj_trade.Buy(0.01); // Open a buy trade with 0.01 lots ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0) { if (PositionSelectByTicket(pos_ticket)) { double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneHigh = NormalizeDouble(openPrice, _Digits); // Set the high zone price zoneLow = NormalizeDouble(zoneHigh - zoneRange, _Digits); // Set the low zone price zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line lastDirection = 1; // Set the last direction to buy recovery_lot = 0.01 * 2; // Set the initial recovery lot size isBuyDone = true; // Mark buy trade as done isSellDone = false; // Reset sell trade flag } } } //--- Check for overbought condition and open a sell position else if (rsiData[1] > overBoughtLevel && rsiData[0] < overBoughtLevel) { obj_trade.Sell(0.01); // Open a sell trade with 0.01 lots ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0) { if (PositionSelectByTicket(pos_ticket)) { double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneLow = NormalizeDouble(openPrice, _Digits); // Set the low zone price zoneHigh = NormalizeDouble(zoneLow + zoneRange, _Digits); // Set the high zone price zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line lastDirection = -1; // Set the last direction to sell recovery_lot = 0.01 * 2; // Set the initial recovery lot size isBuyDone = false; // Reset buy trade flag isSellDone = true; // Mark sell trade as done } } } }
我们可以建立区间恢复系统,一旦达到目标位置或达到预定位置后开立相应的头寸,就需要对其进行监控,确保能从中退出。我们必须在每次价格变动(tick)时都这样做,因此,这个逻辑必须在K线图循环限制之前实现。首先,我们来检查价格达到目标位置的条件,然后再检查价格达到区间位置的条件。让我们现在就开始吧。
if (zoneTargetHigh > 0 && zoneTargetLow > 0){ if (bid > zoneTargetHigh || bid < zoneTargetLow){ obj_trade.PositionClose(_Symbol); deleteZoneLevels(); ... } }
这里,我们通过设定目标位置高于0的逻辑来检查区间恢复系统是否已设置,这意味着我们的系统已经开始运行。如果情况确实如此,我们检查买入价是否高于目标高价或低于目标低价,这表明我们可以放心地终止区间恢复系统,因为其目标已经实现。据此,我们通过deleteZoneLevels函数删除区间水平线。我们使用的函数是void类型,因为我们不需要返回任何内容,并且实现了用内置的ObjectDelete函数删除水平线。该函数接受两个参数:图表索引和对象名称。
void deleteZoneLevels(){ ObjectDelete(0,ZONE_H); ObjectDelete(0,ZONE_L); ObjectDelete(0,ZONE_T_H); ObjectDelete(0,ZONE_T_L); }
因为此时可能有多个仓位,为了平仓,我们使用一个循环来遍历所有仓位,然后逐一删除它们。通过以下代码来实现:
for (int i = PositionsTotal()-1; i >= 0; i--){ ulong ticket = PositionGetTicket(i); if (ticket > 0){ if (PositionSelectByTicket(ticket)){ obj_trade.PositionClose(ticket); } } }
在关闭所有仓位并删除所有水平线之后,我们将系统重置为默认状态,即没有设置任何区间恢复系统。
//closed all, reset all zoneHigh=0;zoneLow=0;zoneTargetHigh=0;zoneTargetLow=0; lastDirection=0; recovery_lot = 0;
通过将区间水平线和目标设置为0来实现的,除最终的交易方向和恢复手数之外。这些是静态变量,因此我们必须手动重置它们。对于动态变量,则无需重置,因为它们通常会自动更新。
以下是在目标达成后用于销毁恢复系统的完整代码:
//--- Close all positions if the bid price is outside target zones if (zoneTargetHigh > 0 && zoneTargetLow > 0) { if (bid > zoneTargetHigh || bid < zoneTargetLow) { obj_trade.PositionClose(_Symbol); // Close the current position deleteZoneLevels(); // Delete all drawn zone levels for (int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if (ticket > 0) { if (PositionSelectByTicket(ticket)) { obj_trade.PositionClose(ticket); // Close positions by ticket } } } //--- Reset all zone and direction variables zoneHigh = 0; zoneLow = 0; zoneTargetHigh = 0; zoneTargetLow = 0; lastDirection = 0; recovery_lot = 0; } }
接下来,我们讨论如何开立恢复仓位。首先,我们需要检查系统是否仍在运行中,这可以通过检查区间水平线是否大于0来判断。如果系统仍在运行,我们将通过定义一个名为lots_rec的变量来设置系统,该变量仅用于存储我们的恢复手数。由于我们使用的是微手数账户,因此我们需要将其标准化为3位小数以确保精度。这个值可能会根据您使用的账户类型而有所不同。例如,如果您使用的是标准账户,其最小手数为1,因此您的值将为0,以去除小数位。大多数账户有2位小数,但如果您使用的是0.001手数的账户类型,那么您的值将为3,以便将手数四舍五入到最接近的3位小数。
if (zoneHigh > 0 && zoneLow > 0){ double lots_Rec = 0; lots_Rec = NormalizeDouble(recovery_lot,2); ... }
接下来,我们检查出价的价格是否高于区间高点,并且如果之前的isBuyDone标识为false或者最终的一个方向值小于0,我们就开立一个买入恢复头寸。在开立头寸后,我们将lastDirection设置为1,意味着之前开立的头寸是买入,然后计算恢复手数并将它们存储在recovery_lot变量中,以供下一次补仓时调用,接着将isBuyDone标志设置为true,将isSellDone设置为false,表明已经开立了一个买入头寸。
if (bid > zoneHigh) { if (isBuyDone == false || lastDirection < 0) { obj_trade.Buy(lots_Rec); // Open a buy trade lastDirection = 1; // Set the last direction to buy recovery_lot = recovery_lot * 2; // Double the recovery lot size isBuyDone = true; // Mark buy trade as done isSellDone = false; // Reset sell trade flag } }
否则,如果出价的价格低于区间低点,我们将相应地开立卖出恢复头寸,如下所示。
else if (bid < zoneLow) { if (isSellDone == false || lastDirection > 0) { obj_trade.Sell(lots_Rec); // Open a sell trade lastDirection = -1; // Set the last direction to sell recovery_lot = recovery_lot * 2; // Double the recovery lot size isBuyDone = false; // Reset buy trade flag isSellDone = true; // Mark sell trade as done } } }
以下是开立恢复头寸的完整代码:
//--- Check if price is within defined zones and take action if (zoneHigh > 0 && zoneLow > 0) { double lots_Rec = NormalizeDouble(recovery_lot, 2); // Normalize the recovery lot size to 2 decimal places if (bid > zoneHigh) { if (isBuyDone == false || lastDirection < 0) { obj_trade.Buy(lots_Rec); // Open a buy trade lastDirection = 1; // Set the last direction to buy recovery_lot = recovery_lot * 2; // Double the recovery lot size isBuyDone = true; // Mark buy trade as done isSellDone = false; // Reset sell trade flag } } else if (bid < zoneLow) { if (isSellDone == false || lastDirection > 0) { obj_trade.Sell(lots_Rec); // Open a sell trade lastDirection = -1; // Set the last direction to sell recovery_lot = recovery_lot * 2; // Double the recovery lot size isBuyDone = false; // Reset buy trade flag isSellDone = true; // Mark sell trade as done } } }
这是我们迄今为止我们取得的里程碑。
自动化区域恢复系统的完整代码如下:
//+------------------------------------------------------------------+ //| MARTINGALE EA.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- Define utility variables for later use #define ZONE_H "ZH" // Define a constant for the high zone line name #define ZONE_L "ZL" // Define a constant for the low zone line name #define ZONE_T_H "ZTH" // Define a constant for the target high zone line name #define ZONE_T_L "ZTL" // Define a constant for the target low zone line name //--- Include trade instance class #include <Trade/Trade.mqh> // Include the trade class for trading functions CTrade obj_trade; // Create an instance of the CTrade class for trading operations //--- Declare variables to hold indicator data int rsi_handle; // Handle for the RSI indicator double rsiData[]; // Array to store RSI data int totalBars = 0; // Variable to keep track of the total number of bars double overBoughtLevel = 70.0; // Overbought level for RSI double overSoldLevel = 30.0; // Oversold level for RSI double zoneHigh = 0; // Variable to store the high zone price double zoneLow = 0; // Variable to store the low zone price double zoneTargetHigh = 0; // Variable to store the target high zone price double zoneTargetLow = 0; // Variable to store the target low zone price //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Initialize the RSI indicator rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE); //--- Return initialization result return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Remove RSI indicator from memory IndicatorRelease(rsi_handle); ArrayFree(rsiData); // Free the RSI data array } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Retrieve the current Ask and Bid prices double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double zoneRange = 200 * _Point; // Define the range for the zones double zoneTarget = 400 * _Point; // Define the target range for the zones //--- Variables to track trading status static int lastDirection = 0; // -1 = sell, 1 = buy static double recovery_lot = 0.0; // Lot size for recovery trades static bool isBuyDone = false, isSellDone = false; // Flags to track trade completion //--- Close all positions if the bid price is outside target zones if (zoneTargetHigh > 0 && zoneTargetLow > 0) { if (bid > zoneTargetHigh || bid < zoneTargetLow) { obj_trade.PositionClose(_Symbol); // Close the current position deleteZoneLevels(); // Delete all drawn zone levels for (int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if (ticket > 0) { if (PositionSelectByTicket(ticket)) { obj_trade.PositionClose(ticket); // Close positions by ticket } } } //--- Reset all zone and direction variables zoneHigh = 0; zoneLow = 0; zoneTargetHigh = 0; zoneTargetLow = 0; lastDirection = 0; recovery_lot = 0; } } //--- Check if price is within defined zones and take action if (zoneHigh > 0 && zoneLow > 0) { double lots_Rec = NormalizeDouble(recovery_lot, 2); // Normalize the recovery lot size to 2 decimal places if (bid > zoneHigh) { if (isBuyDone == false || lastDirection < 0) { obj_trade.Buy(lots_Rec); // Open a buy trade lastDirection = 1; // Set the last direction to buy recovery_lot = recovery_lot * 2; // Double the recovery lot size isBuyDone = true; // Mark buy trade as done isSellDone = false; // Reset sell trade flag } } else if (bid < zoneLow) { if (isSellDone == false || lastDirection > 0) { obj_trade.Sell(lots_Rec); // Open a sell trade lastDirection = -1; // Set the last direction to sell recovery_lot = recovery_lot * 2; // Double the recovery lot size isBuyDone = false; // Reset buy trade flag isSellDone = true; // Mark sell trade as done } } } //--- Update bars and check for new bars int bars = iBars(_Symbol, PERIOD_CURRENT); if (totalBars == bars) return; // Exit if no new bars totalBars = bars; // Update the total number of bars //--- Exit if there are open positions if (PositionsTotal() > 0) return; //--- Copy RSI data and check for oversold/overbought conditions if (!CopyBuffer(rsi_handle, 0, 1, 2, rsiData)) return; //--- Check for oversold condition and open a buy position if (rsiData[1] < overSoldLevel && rsiData[0] > overSoldLevel) { obj_trade.Buy(0.01); // Open a buy trade with 0.01 lots ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0) { if (PositionSelectByTicket(pos_ticket)) { double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneHigh = NormalizeDouble(openPrice, _Digits); // Set the high zone price zoneLow = NormalizeDouble(zoneHigh - zoneRange, _Digits); // Set the low zone price zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line lastDirection = 1; // Set the last direction to buy recovery_lot = 0.01 * 2; // Set the initial recovery lot size isBuyDone = true; // Mark buy trade as done isSellDone = false; // Reset sell trade flag } } } //--- Check for overbought condition and open a sell position else if (rsiData[1] > overBoughtLevel && rsiData[0] < overBoughtLevel) { obj_trade.Sell(0.01); // Open a sell trade with 0.01 lots ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0) { if (PositionSelectByTicket(pos_ticket)) { double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneLow = NormalizeDouble(openPrice, _Digits); // Set the low zone price zoneHigh = NormalizeDouble(zoneLow + zoneRange, _Digits); // Set the high zone price zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line lastDirection = -1; // Set the last direction to sell recovery_lot = 0.01 * 2; // Set the initial recovery lot size isBuyDone = false; // Reset buy trade flag isSellDone = true; // Mark sell trade as done } } } } //+------------------------------------------------------------------+ //| FUNCTION TO DRAW HORIZONTAL ZONE LINES | //+------------------------------------------------------------------+ void drawZoneLevel(string levelName, double price, color clr, int width) { ObjectCreate(0, levelName, OBJ_HLINE, 0, TimeCurrent(), price); // Create a horizontal line object ObjectSetInteger(0, levelName, OBJPROP_COLOR, clr); // Set the line color ObjectSetInteger(0, levelName, OBJPROP_WIDTH, width); // Set the line width } //+------------------------------------------------------------------+ //| FUNCTION TO DELETE DRAWN ZONE LINES | //+------------------------------------------------------------------+ void deleteZoneLevels() { ObjectDelete(0, ZONE_H); // Delete the high zone line ObjectDelete(0, ZONE_L); // Delete the low zone line ObjectDelete(0, ZONE_T_H); // Delete the target high zone line ObjectDelete(0, ZONE_T_L); // Delete the target low zone line }
截至目前,我们已经成功地将外汇交易系统的区域恢复功能按预期自动化,接下来将进行测试,观察其表现,以确认其是否达到既定的目标。
回测结果
在策略测试器上进行测试后,结果如下:
图形
结果:
结论
在本文中,我们探讨了在使用MQL5实现著名的区间恢复马丁格尔策略自动化方面需要执行的基本步骤。我们为该策略提供基本定义和描述,并展示了如何在MQL5中实现。交易者可以利用所展示的知识开发更复杂的区间恢复系统,这些系统日后可以经过优化,以获得更好的结果。
免责声明:此代码仅旨在帮助您掌握创建区间恢复外汇交易系统的基础知识,所展示的结果并不能保证未来的表现。因此,请谨慎运用这些知识来创建和优化符合您交易风格的系统。
本文按照循序渐进的方式阐述了创建该系统所需的所有步骤。我们希望它对您有所帮助,能成为您创建更好、完美优化的区间恢复系统的一块垫脚石。我们附上了必要的文件,以提供用于演示这些示例的实例。您可以研究这些代码,并将其应用于您的特定策略,以实现最优结果。
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/15067



