使用Python和MQL5进行交易策略的自动参数优化
为什么自优化至关重要
想象一下,您花费大量精力开发了一个EA。您迫不及待地想看到它投入运行,但一开始没有进行适当的优化。初步的正向结果可能会让您误以为一切都很好,但很快就会出现变化和亏损。
一个未经优化的EA缺乏一致性,可能会对不相关数据做出反应,导致利润和亏损难以预测。它可能会基于错误信号做出决策,无法适应市场变化,并承担未预见的风险,从而造成重大亏损。优化可以确保更好的性能和可靠性。
读者需要理解自动优化的重要性,了解所使用的不同算法,并看到Python和EA脚本中的实际示例。要学习如何设置自动优化,比较结果,并正确配置优化参数,从而提升其交易策略的效率。
交易策略的自优化算法包括参数优化、进化算法、启发式方法、基于梯度的方法、机器学习和基于模拟的优化。每种方法都有其独特的优点和缺点,适用于不同的交易需求和市场条件。
参数优化技术
- 暴力优化(Brute Force Optimization):测试所有参数组合以获得精确结果,但资源消耗大。
- 网格搜索(Grid Search):在网格中评估组合,以平衡彻底性和效率。
- 随机搜索(Random Search):随机测试组合以快速获得结果,但牺牲了一定的精确度。
每种技术都有其优势,因此选择取决于可用资源、时间和所需的精度。
我们为什么要使用Python,何时使用?
Python程序是尝试新思路、快速创建图形以及用历史交易数据验证理论陈述的绝佳工具。Python允许用户灵活开发和调整模型,从而便于尝试不同的策略和参数。其生成详细图表和可视化的能力有助于用户更直观地理解结果。此外,Python能够整合历史数据,允许验证策略在过去的表现,为提出的理论提供实际验证。这种速度、灵活性和分析能力的结合,使Python成为任何寻求优化交易策略并更好地理解金融市场的交易者不可或缺的工具。
我们将对本文及其指标采用何种策略
移动平均线交叉策略(MAs Crossing)是一种基于两条移动平均线相交来生成买入和卖出信号的交易技术。它使用两个不同周期的移动平均线,一个短期和一个长期,以识别价格趋势的变化。当短期移动平均线上穿长期移动平均线时,会产生买入信号,表明可能出现上涨趋势。相反,当短期移动平均线下穿长期移动平均线时,会产生卖出信号,这表明可能出现下跌趋势。这种策略因其简单性和在趋势明显的市场中的有效性而广受欢迎。
简单移动平均线(SMA指标)是一种计算资产在特定时期内平均价格的工具。要计算SMA,需要将选定期间内资产的收盘价相加,然后除以期间的数量。SMA能够平滑价格波动,并有助于识别趋势的总体方向。该指标有助于消除市场噪音,提供更清晰的潜在趋势视图。在Python中,可以使用诸如pandas等库轻松计算SMA,这些库提供了高效且精确计算移动平均线的函数。
使用Python进行参数优化。案例研究
我们为每个技术方法附上一个Python脚本。据此对比每种方法的不同之处。
三个脚本中的策略是相同的。如果您想使用另一种策略,这就是您需要更改的内容。
data = data.copy() # Create a copy of the original DataFrame data['Short_MA'] = data['Close'].rolling(window=short_window).mean() data['Long_MA'] = data['Close'].rolling(window=long_window).mean() data['Signal'] = 0 data.loc[data.index[short_window:], 'Signal'] = np.where( data['Short_MA'][short_window:] > data['Long_MA'][short_window:], 1, 0) data['Position'] = data['Signal'].diff()
Python非常直观,所以我不会解释代码。
这三段脚本已随文章附上(分别是b_forec.py、grid_search_v2.py和random_search_v2.py)。
在使用这些脚本之前,您必须安装所需的库。您可以使用pip来安装这些库:
pip install numpy pandas matplotlib itertools MetaTrader5 random
对于每个脚本,使用以下输入时,结果将类似于如下内容:
symbol = "EURUSD" timeframe = mt5.TIMEFRAME_D1 start = pd.Timestamp("2020-01-01") end = pd.Timestamp("2021-01-01")
暴力
最佳参数:Short = 14.0, Long = 43.0 最佳表现:10014.176, Risk: 3.7431030827241524e-05
网格搜索
最佳参数:Short = 14.0, Long = 43.0 最佳表现:10014.176, Risk: 3.7431030827241524e-05
随机搜索
最佳参数:Short = 14.0, Long = 44.0 最佳表现:10013.697, Risk: 3.725494046576829e-05
这些脚本不会自动优化,因此您必须选择每个时间段进行研究。
在所有这些结果中,表现都相当好,但我们必须考虑到这个策略很简单,而其他策略可能有更多的参数和更大的范围。
应该多久优化一次策略呢?
优化交易策略对于保持其长期有效性至关重要。优化频率和回溯期取决于多个因素,尤其是市场波动性。
想象一下,您正在开发一个以1天为时间周期的交易策略,即每个信号都是基于每日市场数据产生的。在这里,波动性起着重要作用:当市场更具波动性时,价格波动会更大且更快,如果策略不适应这种变化,可能会影响其有效性。
为了确定何时进行优化以及使用何种回溯期,您需要监控市场波动性。一个很好的做法是观察您所交易资产的每日价格区间(最高价-最低价)或真实平均波动范围。以下是一个基于波动性的大致指南。
低波动性:当市场平稳且每日价格波动区间较小时,策略往往不需要频繁调整。您可以考虑每1-3个月进行一次优化,并使用较长的回溯期来捕捉更稳定的趋势。50-100天的回溯期可能足够。
中等波动性:在正常市场条件下,价格波动更为规律但并非极端巨大幅度时,考虑每1-2个月进行一次优化。20-50天的回溯期可能足以捕捉趋势中的显著变化。
高波动性:在高波动性时期,如危机时期或重要经济事件期间,价格波动大且迅速。在这种情况下,更频繁地进行优化至关重要,可能每2-4周一次,并使用较短的回溯期,如10-20天,以便快速适应市场条件的变化。
MQL5的自优化示例
以下MQL5代码是一个EA,它实现了穿越移动平均线(MA)的交易策略。该EA在MetaTrader 5平台上运行,旨在金融市场中自动执行交易操作。EA使用两个可调周期的简单移动平均线(SMA)来生成买卖信号,当它们交叉时触发信号。
该EA的主要目标是自动优化MA周期,以根据用户选择的配置最大化净收益、最小化回撤或最大化夏普比率。这是通过优化过程实现的,该过程在特定历史数据期间内测试不同组合的MA周期。
该EA由以下几个关键部分组成:
1. 初始化和配置:定义初始参数,如MA周期、手数大小、用于订单识别的特殊编号等。
2. 优化:使用穷举搜索算法在指定范围内测试所有可能的MA周期组合。基于所选标准进行优化:净收益或最小回撤。
3. 交易执行:持续监控市场,并根据定义的策略在MA交叉时开立买入或卖出仓位。另外,还管理已开仓位,根据平均真实范围(ATR)应用止损和止盈水平。
4. 自动重新优化:在设定的时间段(可配置)内,EA重新优化MA的参数,以适应不断变化的市场条件。
5. 追踪止损:实施不同类型的追踪止损(简单追踪止损和预期盈利追踪止损)以确保盈利并防止亏损。
6. 交易时间表:可以配置为在每周的某些天或特定时间不进行交易。
7. 完成和清算:EA停止时,会执行适当的清算和关闭所有已开交易的操作。
总体来说,该EA是一个复杂的实现,它将技术分析(均线交叉)与风险管理策略(止损和跟踪止损)以及自动化参数优化相结合。它基于自主运行设计,并经过优化,以在自动化交易环境中实现风险调整后的性能最大化。
文章中和代码中使用的跟踪止损策略来源于Aleksej Poljakov的Trailing stop in trading一文。
代码
要使用另一种优化技术,您应该更改代码的这一部分:
for(int fastPeriod = FastMAPeriodStart; fastPeriod <= FastMAPeriodStop; fastPeriod += FastMAPeriodStep) { for(int slowPeriod = SlowMAPeriodStart; slowPeriod <= SlowMAPeriodStop; slowPeriod += SlowMAPeriodStep) { double criterionValue = PerformBacktest(fastPeriod, slowPeriod, startBar); if(IsNewOptimal(criterionValue, bestCriterionValue, OptimizationCriterion)) { bestCriterionValue = criterionValue; bestFastPeriod = fastPeriod; bestSlowPeriod = slowPeriod; } } }
要使用另一种策略,您应该更改代码的这一部分(及其输入):
double fastMA_curr[]; double slowMA_curr[]; double fastMA_prev[]; double slowMA_prev[]; ArraySetAsSeries(fastMA_curr, true); ArraySetAsSeries(slowMA_curr, true); ArraySetAsSeries(fastMA_prev, true); ArraySetAsSeries(slowMA_prev, true); int fastMA_current = iMA(_Symbol, 0, FastMAPeriod, 0, MODE_SMA, PRICE_CLOSE); int fastMA_previous = iMA(_Symbol, 0, FastMAPeriod, 1, MODE_SMA, PRICE_CLOSE); int slowMA_current = iMA(_Symbol, 0, SlowMAPeriod, 0, MODE_SMA, PRICE_CLOSE); int slowMA_previous = iMA(_Symbol, 0, SlowMAPeriod, 1, MODE_SMA, PRICE_CLOSE); CopyBuffer(fastMA_current, 0, 0, 2, fastMA_curr); CopyBuffer(slowMA_current, 0, 0, 2, slowMA_curr); CopyBuffer(fastMA_previous, 0, 0, 2, fastMA_prev); CopyBuffer(slowMA_previous, 0, 0, 2, slowMA_prev); double fastMA_previousFF = fastMA_prev[0]; double slowMA_previousSS = slowMA_prev[0]; double fastMA_currentFF = fastMA_curr[0]; double slowMA_currentSS = slowMA_curr[0]; // Check for buy signal (fast MA crosses above slow MA) if(fastMA_previousFF < slowMA_previousSS && fastMA_currentFF > slowMA_currentSS) { // Close any existing sell positions if(PositionsTotal() > 0) { for(int i = PositionsTotal() - 1; i >= 0; i--) { if(PositionSelectByTicket(i)) { if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { Print("Closing sell position: Ticket ", PositionGetInteger(POSITION_TICKET)); ClosePosition(PositionGetInteger(POSITION_TICKET)); } } } } // Open a buy position double openPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double atrMultiplier = ATRmultiplier; OpenPosition(ORDER_TYPE_BUY, openPrice, atrMultiplier); } // Check for sell signal (fast MA crosses below slow MA) else if(fastMA_previousFF > slowMA_previousSS && fastMA_currentFF < slowMA_currentSS) { // Close any existing buy positions if(PositionsTotal() > 0) { for(int i = PositionsTotal() - 1; i >= 0; i--) { if(PositionSelectByTicket(i)) { if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { Print("Closing buy position: Ticket ", PositionGetInteger(POSITION_TICKET)); ClosePosition(PositionGetInteger(POSITION_TICKET)); } } } } // Open a sell position double openPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID); double atrMultiplier = ATRmultiplier; OpenPosition(ORDER_TYPE_SELL, openPrice, atrMultiplier); } IndicatorRelease(fastMA_current); IndicatorRelease(slowMA_current); IndicatorRelease(fastMA_previous); IndicatorRelease(slowMA_previous); }
重要的一点要指出,如果您不释放指标,图形窗口将被它们填满。
启用与未启用自优化的区别
对于EURUSD,我们将使用相同的时段(从2024年4月20日到2024年5月20日),并且时间间隔设置为1天。
启用优化
未启用优化
显然,自优化是一个更好的解决方案。在没有启用自优化的情况下,没有达成任何交易。第一个(交易策略或模型)在一开始就进行了自优化,然后在它有40天的时间进行了重新优化(在所研究的时间段之外)。
结论
自优化在确保EA在瞬息万变的金融市场中持续可靠运行方面至关重要。通过本文,交易者已经全面了解到为什么自优化至关重要,并且了解到可用于优化交易策略和参数的各种算法类型,以及不同参数优化技术的优点和缺点。本文强调了Python作为快速高效进行策略回测工具的重要性,并通过研究一个使用移动平均线(MA)交叉策略的参数优化案例进行了说明。
文章进一步探讨了基于市场波动进行定期优化的必要性,并且强调了频繁调整以保持有效性的重要性。通过使用MQL5进行自优化的示例,呈现了这些概念的实际应用,证明了通过自优化可以实现显著的性能提升。自优化EA与未优化EA的对比凸显了前者的明显优势,优化后的EA展现出更强的适应性和效率。
总而言之,自优化不仅提升了EA的性能,还为交易者增强了信心和带来了安心,因为他们知道他们的EA能够有效地应对金融市场的复杂性。这种针对EA开发和维护的策略方法对于任何希望在交易中保持一贯且持续成功的人来说都是不可或缺的。
参考文献及资料
- Marcos López de Prado的《金融机器学习进阶》。
- Barry Johnson的《算法交易与直接市场准入(DMA)》。
- Wes McKinney的《利用Python进行数据分析》。
- Marcos López de Prado的《资产管理者的机器学习》。
- Ernest P的《量化交易:如何建立自己的算法交易业务》。
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/15116