
使用MQL5实现抛物线SAR趋势策略的自动化交易:打造高效的EA
概述
算法交易策略通过使交易者能够快速做出明智的决策,极大地创新了金融交易。这有助于消除人为错误和情绪偏见,因为自动化系统会在快节奏的市场环境中遵循预先确定的参数。因此,在自动化交易中,交易具有一致性,因为系统每次执行交易时都使用相同的逻辑。
使用MetaTrader 5构建和实施自动化技术是一种常见的做法。本文深入探讨了使用抛物线SAR指标自动执行交易的EA创建过程。
抛物线SAR策略概述
抛物线 SAR 指标:该指标由Welles Wilder于1978年发明。该指标通过在图表上跟随价格运动的上下方的一系列点来突出显示市场趋势中的潜在反转点。当点位于价格下方时,市场处于上升趋势;当点位于价格上方时,市场处于下降趋势。其特别适用于识别潜在反转点,即趋势可能即将结束的位置。
SAR 步长:有助于确定SAR点跟踪价格的紧密程度。较低的步长会使点落后于价格,降低敏感性。较高的步长会使点更接近价格,增加敏感性。
SAR点:作为加速因子的上限。在趋势市场中,抛物线SAR会增加步长以追赶价格。因此,交易者能够识别潜在趋势反转的入场和出场点。
该策略可以与其他指标结合使用,使其更加全面,也可以作为独立策略用于外汇和大宗商品等趋势市场。
在MQL5中的实现
在文件头部,我们包含了最基本的基础数据,例如版权信息和版本号。我们引入了Trade.mqh库,用于处理交易操作,即开仓和平仓。
#property copyright "Copyright 2024, MetaQuotes Ltd." "Duke" #property link "https://www.mql5.com" #property version "1.00" #include <Trade\Trade.mqh>
现在让我们来定义允许EA自定义的输入参数。
LotSize(手数) : 帮助EA确定交易的规模。这意味着较小的手数会因交易规模的减小而降低风险和收益。此外,如果手数较大,意味着风险和利润都会增加。
SAR_Step(SAR步长)和 SAR_Maximum(SAR最大值): 控制在价格运动检测抛物线SAR和抛物线SAR指标计算中的敏感度。
Slippage(滑点): 帮助EA在下单时设置允许的最大滑点。
//--- Input parameters input double LotSize = 0.2 ; // Lot size for trading input double SAR_Step = 0.02; // Parabolic SAR step input double SAR_Maximum = 0.2; // Parabolic SAR maximum input int Slippage = 3; // Slippage for orders
我们可以创建一个CTrade类的实例来管理交易操作。
//--- Trade object
CTrade trade;
下一步是OnInit函数,该函数在EA加载时执行。在这个简单的EA实例中,其只是在EA日志中打印一条成功初始化的确认消息。因此,我们将验证EA是否已成功加载并初始化,没有任何问题。该函数在调试中也很有用,可以确保EA已准备好开始处理数据。如果初始化因某种原因失败,该函数也可以返回INIT_FAILED,从而阻止EA运行。
int OnInit() { //--- Initialization code here Print("Parabolic SAR EA Initialized"); return(INIT_SUCCEEDED); }
我们转向OnDeinit函数。当EA重新编译时,它会从图表中移除。这一操作会激活OnDeinit函数,以执行清理任务并确保适当地释放资源。您可以通过查看日志来确定EA是被显式移除、重新编译,还是由于终端关闭导致的,这在开发和测试阶段提供了有用的信息。这个函数可以根据需要扩展到任何必要的清理程序。例如,您可能需要关闭任何打开的资源、释放文件句柄,或者保存当前状态。
这将有助于增强可靠性和稳定性,因为它可以确保早期运行中的性能问题不会干扰EA重新加载或重新启动的能力。
void OnDeinit(const int reason) { //--- Cleanup code here Print("Parabolic SAR EA Deinitialized"); }
在OnDeinit函数之后,我们来看一下OnTick函数。这个函数是EA的核心,会在每个市场行情变化时执行。它计算抛物线SAR的值,并将其与上一个收盘价进行比较。根据这一比较结果,EA决定是否开启买入或卖出。
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Get the Parabolic SAR value double sar = iSAR(_Symbol, _Period, SAR_Step, SAR_Maximum); // Calculate the Parabolic SAR value double lastPrice = iClose(_Symbol, _Period, 1); // Get the close price of the previous candle //--- Check if there is an existing position if(!IsPositionOpen(_Symbol)) { //--- Determine trade direction based on SAR if(lastPrice > sar) { OpenBuyOrder(); // If the last price is above the SAR, open a buy order } else if(lastPrice < sar) { OpenSellOrder(); // If the last price is below the SAR, open a sell order } } }
我们现在拆解OnTick函数,以便更好地理解它。
- 抛物线 SAR 的计算:代码的第一行计算抛物线SAR的值:
double sar = iSAR(_Symbol, _Period, SAR_Step, SAR_Maximum); // Calculate the Parabolic SAR value
-iSAR():这是MetaTrader平台上内置的一个函数,用于计算指定资产在特定时间段内的抛物线SAR指标值。
-_Symbol: 表示您正在分析的金融工具或交易资产,例如 EUR/USD、黄金或其他任何交易项目。
- _Period: 表示图表的时间框架(如1分钟、1小时、1天等),帮助函数确定使用哪些K线图来计算SAR。
- _SAR_Step和SAR Maximum:这些是抛物线SAR指标特有的参数,用于控制其敏感度。SAR_Step是随趋势发展而加速指标的增量步长值,而SAR_Maximum则限制了加速度,防止指标对价格变化的反应过快。
- 获取上一根K线的收盘价:下一行代码获取最后一根已完成K线的收盘价:
double lastPrice = iClose(_Symbol, _Period, 1); // Get the close price of the previous candle
-iClose(): 该函数返回图表中特定K线的收盘价。
- _Symbol和_Period:与 iSAR() 函数一样,指的是正在分析的特定金融工具和时间框架。
- 1:该参数指定了我们想要获取哪一根K线的收盘价。在这种情况下,1表示最后一根已完成的K线(即前一根),因为当前的K线仍在形成中。
该值存储在LastPrice中,代表最后一根K线收盘时资产交易的最终价格水平。
我们使用抛物线SAR代码来自动化设置止损和确定交易退出或反转的决策。例如,如果在多头仓位中收盘价跌破 SAR 值,这可能表明趋势反转,促使交易者平仓或考虑做空。EA使用这种比较来判断市场是继续当前趋势还是发生反转,这有助于消除情绪因素,依靠清晰的信号进行交易。
在构建交易算法时,通常会实现一个检查,看看是否已经为特定资产(交易品种)开仓。
//--- Check if there is an existing position if(!IsPositionOpen(_Symbol))
IsPositionOpen(_Symbol):该函数检查当前是否存在针对_Symbol的未平仓头寸。Symbol通常表示股票代码或资产代码。
!IsPositionOpen(_Symbol):这意味着只有在给定交易品种的没有持仓时,条件才为真。
if语句: 如果没有给定交易品种的持仓,那么将执行if块中的代码。这可能涉及一个新的开仓、执行一笔交易,或者触发其他交易逻辑。
因此,该函数将确保不会因针对同一交易品种多次开仓而造成过度交易。这将帮助系统更好地控制交易活动。
抛物线SAR跟踪趋势,用随价格变动而调整的跟踪止损来指示入场和出场点。交叉信号表明潜在的趋势反转。
//--- Determine trade direction based on SAR if(lastPrice > sar) { OpenBuyOrder(); // If the last price is above the SAR, open a buy order } else if(lastPrice < sar) { OpenSellOrder(); // If the last price is below the SAR, open a sell order } } }
LastPrice(最新价格):表示正在交易的资产的当前市场价格。
sar(抛物线SAR值):表示当前时间的抛物线SAR值。
通过比较两者,算法判断趋势是上升趋势还是下降趋势,并据此采取相应的措施。
买入和卖出逻辑
买入订单: 当最新价格超过SAR值时,表明市场处于上升趋势。算法将触发OpenBuyOrder()函数来执行交易,假设市场将继续随着价格高于SAR而上涨。
卖出订单:相反,如果最新价格低于SAR值,这可以解释为市场处于下降趋势的信号。算法通过调用OpenSellOrder()函数来启动卖出交易。在此,策略假设由于价格已经跌破SAR,市场可能会继续下跌。
代码首先优先评估买入订单。如果买入订单的条件(lastprice > sar)满足,系统会立即执行买入交易。如果该条件不满足,代码则检查卖出条件(lastPrice < sar)。如果条件为真,将执行卖出交易。
下一步是工具函数。
我们从检查OpenPositions函数开始。IsPositionOpen函数检查是否已经存在当前交易品种的持仓。这样可以防止EA同时在相同方向上多次开仓。
//+------------------------------------------------------------------+ //|Check if there is an open position for the given symbol | //+------------------------------------------------------------------+ bool IsPositionOpen(string symbol) { uint total=PositionsTotal(); //Get the total number of open positions for(uint i=0; i<total; i++) { string POSITION_SYMBOL=PositionGetSymbol(i); //Get the symbol of the position if(POSITION_SYMBOL == _Symbol) //Check if the symbol matches the current symbol { return true; } } return false; }
我们现在通过拆解函数代码来详细查看它是如何工作的:
- 输入参数:该函数接收一个字符串类型的参数symbol,它代表一个期望的交易品种,比如货币对或者股票代码。
- 获取总头寸数:函数通过调用PositionsTotal()来获取当前所有持仓的总数。这个函数返回执行时终端中开启的头寸数量。
- 遍历头寸:使用for循环,代码遍历所有持仓。变量total存储持仓的数量,循环从0运行到total - 1。
- 比较交易品种:检索到的交易品种POSITION_SYMBOL随后会与当前交易品种进行比较,使用条件if (POSITION_SYMBOL == _SYMBOL)。变量_Symbol是一个预定义的标识符,代表调用该函数图表的交易品种。如果交易品种匹配,这表明已经存在一个给定交易品种的持仓。
- 返回值:如果函数找到匹配项(即已经存在一个给定交易品种的持仓),它返回true。如果在循环检查完所有持仓后没有找到匹配的持仓,函数返回false,表示没有开启给定交易品种的持仓。
该函数特别有助于防止算法在已有持仓的情况下为同一交易品种开立新持仓。例如,如果您正在交易“EURUSD”,并且该函数返回true,那么策略可能会避免开启新的持仓,而是专注于管理现有的持仓。
我们的下一个工具函数是OpenBuyOrder();该函数负责执行买入交易。其尝试以指定的手数买入订单,同时包含错误处理,以确保过程正确完成,或者在出现问题时通知交易者。
//+------------------------------------------------------------------+ //| Open a Buy order | //+------------------------------------------------------------------+ void OpenBuyOrder() { if(trade.Buy(LotSize, NULL, 0, 0, 0, "Buy Order")) { Print("Buy order placed successfully"); } else { Print("Error placing buy order: ", GetLastError()); } }
该函数被声明为void OpenBuyOrder(),这意味着它不返回任何值,而是执行一项特定任务,在这种情况下是下单。
该函数使用trade.Buy()方法来执行下单。该方法需要以下参数:
- LotSize(手数):指定了订单的规模,在当前的情况下是0.2。
- Null:调用该函数的图表上的当前交易品种。在此情况下,NULL默认为当前图表上的交易品种。
- 0:设置了下单的价格。0表示以当前的市场价格执行订单。
- 0:该参数表示止损水平。0意味着在开启订单时没有指定止损。
- 0:该参数表示获利水平。其数值设置0意味着最初没有设置获利。
- "Buy Order"(下单):与订单关联的注释,用于识别目的。
如果成功下单(如果trade.Buy(....)的评估结果为true),将调用Print()函数记录消息:"Buy order placed successfully"(下单成功)。这为交易者提供了反馈结果,表明订单已成功执行。
如果订单失败(否则),函数记录错误消息:"Error placing buy order"(下单失败),之后由GetLastError()返回具体的错误代码。GetLastError()函数检索系统中最后发生的错误,为交易者和开发者提供有价值的信息,以便排查问题。
我们的最后一个工具函数是开启卖单:该函数旨在自动化算法交易系统中开启卖单的过程。该函数与OpenBuyOrder()函数的逻辑类似,但它是为市场中的空头交易或卖单量身定制的。
//+------------------------------------------------------------------+ //| Open a Sell order | //+------------------------------------------------------------------+ void OpenSellOrder() { if(trade.Sell(LotSize, NULL, 0, 0, 0, "Sell Order")) { Print("Sell order placed successfully"); } else { Print("Error placing sell order: ", GetLastError()); } }
void OpenSellOrder() 函数不返回任何值,而是专注于执行一项特定任务:卖出订单。
trade.Sell()方法用于启动卖单。传递给该方法的参数如下:
- Lotsize(手数):定义了卖单的手数,在当前的情况下是0.2。
- NULL:表示调用该函数的当前图表的交易品种。通过传递 NULL,函数默认当前图表上的交易品种。
- 0:设置订单的价格。使用0表示订单将以当前市场价格执行。
- 0:表示止损水平。数值为0意味着在开启订单时没有指定止损水平。
- 0:指定获利水平。数值为0意味着最初没有设置获利。
- Sell order(卖单):附加到订单上的注释,有助于识别和跟踪订单。
如果卖单成功,函数将使用 Print() 函数记录成功消息。消息 “Sell order placed successfully”(卖单成功)将出现在终端的日志中,用于确认交易已执行完成。
如果因某些原因卖单失败,函数将记录错误消息:“Error placing sell order:”(卖单失败),之后由GetLastError() 返回具体的错误代码。这样有助于诊断卖单失败的原因。
本文的完整代码如下:
//+------------------------------------------------------------------+ //| ParabolicSAR_EA.mq5 | //| Copyright 2024, MetaQuotes Ltd."Duke" | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." "Duke" #property link "https://www.mql5.com" #property version "1.00" #include <Trade\Trade.mqh> //--- Input parameters input double LotSize = 0.2 ; // Lot size for trading input double SAR_Step = 0.02; // Parabolic SAR step input double SAR_Maximum = 0.2; // Parabolic SAR maximum input int Slippage = 3; // Slippage for orders //--- Trade object CTrade trade; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Initialization code here Print("Parabolic SAR EA Initialized"); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Cleanup code here Print("Parabolic SAR EA Deinitialized"); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Get the Parabolic SAR value double sar = iSAR(_Symbol, _Period, SAR_Step, SAR_Maximum); // Calculate the Parabolic SAR value double lastPrice = iClose(_Symbol, _Period, 1); // Get the close price of the previous candle //--- Check if there is an existing position if(!IsPositionOpen(_Symbol)) { //--- Determine trade direction based on SAR if(lastPrice > sar) { OpenBuyOrder(); // If the last price is above the SAR, open a buy order } else if(lastPrice < sar) { OpenSellOrder(); // If the last price is below the SAR, open a sell order } } } //+------------------------------------------------------------------+ //|Check if there is an open position for the given symbol | //+------------------------------------------------------------------+ bool IsPositionOpen(string symbol) { uint total=PositionsTotal(); //Get the total number of open positions for(uint i=0; i<total; i++) { string POSITION_SYMBOL=PositionGetSymbol(i); //Get the symbol of the position if(POSITION_SYMBOL == _Symbol) //Check if the symbol matches the current symbol { return true; } } return false; } //+------------------------------------------------------------------+ //| Open a Buy order | //+------------------------------------------------------------------+ void OpenBuyOrder() { if(trade.Buy(LotSize, NULL, 0, 0, 0, "Buy Order")) { Print("Buy order placed successfully"); } else { Print("Error placing buy order: ", GetLastError()); } } //+------------------------------------------------------------------+ //| Open a Sell order | //+------------------------------------------------------------------+ void OpenSellOrder() { if(trade.Sell(LotSize, NULL, 0, 0, 0, "Sell Order")) { Print("Sell order placed successfully"); } else { Print("Error placing sell order: ", GetLastError()); } }
到目前为止,已经实现了我们的EA。
以下是回测结果:
测试是基于1分钟(M1)图表上的美元兑日元(USDJPY)进行的。测试期间是从2024年1月1日到2024年8月5日。类型为每个tick(每个价格变动)。
以下是测试过程中使用的输入参数:
在1分钟(M1)图表上进行了几次测试后,我意识到:
- 该策略适用于美元兑日元(USDJPY)和欧元兑美元(EURUSD)。
- 但是该策略在英镑兑美元(GBPUSD)上效果不佳。
结论
从结果来看,我们可以得出结论:如果对这个简单的EA进行进一步的细化和调整,以增强其管理能力并减少资产暴露于高风险的可能性,那么它就能为交易者带来一些不错的结果。这将涉及实施风险管理功能,例如添加跟踪止损,以帮助在市场有利波动时锁定利润,从而提升EA的性能。还需要进行参数优化、多次测试以及微调设置,比如调整SAR的步长(Step)和最大值(Maximum),以确保在不同市场条件下达到最佳性能。
免责声明:该代码仅旨在帮助交易者掌握实施抛物线SAR策略的基础知识:打造有效的EA,我们从回测中获得的演示结果并不能保证EA在未来交易中的表现。
在实际部署前,彻底的回测和前瞻性测试至关重要,持续监控也是必不可少的,以便防止意外损失并迅速做出调整。通过这种策略自动化交易,有助于减少人为错误和疲劳,通过定制、优化和测试,可以创建一个复杂且盈利的交易系统。因此,运用这种自动化类型可以更高效地把握机会,提升交易表现。到目前为止,本文已经展示了实施抛物线SAR策略所需的必要知识和技能。实施所需的其他资源可以在我们的MQL5平台上获取。
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/15589


