
使用 MetaTrader 5 在 Python 中查找自定义货币对形态
外汇形态分析简介
初学者第一次看货币对图表时会看到什么?许多日内波动、波动性增加和减少、趋势变化等等。上升、下降、曲折 —— 如何弄清楚这一切?我也通过深入研究价格形态分析开始了解外汇。
乍一看,我们世界上的许多事情似乎都很混乱。但任何有经验的专家都会看到他或她个人领域的模式和可能性,这似乎让其他人感到困惑。货币对图表也是如此。如果我们试图将这种混乱系统化,我们可以发现可以暗示未来价格走势的隐藏形态。
但如何找到它们呢?如何区分真实模式和随机噪声呢?乐趣就从这里开始。我决定使用 Python 和 MetaTrader 5 创建自己的形态分析系统。一种数学和编程的共生关系,用于征服外汇。
这个想法是使用一种算法来研究大量的历史数据,该算法可以找到重复的模式并评估它们的性能。听起来很有趣吧?但实际上,实现起来并没有那么简单。
设置环境:安装必要的库并连接到 MetaTrader 5
因此,我们的首要任务是安装 Python。可以从官方网站 python.org 下载。确保选中 “将 Python 添加到 PATH” 框。
下一个重要步骤是库,我们需要其中的一些。主要的是 MetaTrader 5。此外,还有用于处理数据的 “pandas”。也许是 “numpy”。打开命令行并输入:
pip install MetaTrader5 pandas numpy matplotlib pytz
您需要做的第一件事就是安装 MetaTrader 5 本身。从您的经纪商的官方网站下载并安装,没什么复杂的。
现在我们需要找到终端的路径。通常,它类似于 “C:\Program Files\MetaTrader 5\terminal64.exe”。
现在打开 Python 并输入:
import MetaTrader5 as mt5 if not mt5.initialize(path="C:/Program Files/MetaTrader 5/terminal64.exe"): print("MetaTrader 5 initialization failed.") mt5.shutdown() else: print("MetaTrader 5 initialized successfully.")
启动它。如果您看到成功的终端初始化消息,则表示所有操作都已正确完成。
想确保一切正常吗?让我们尝试获取一些数据:
import MetaTrader5 as mt5 import pandas as pd from datetime import datetime if not mt5.initialize(): print("Oops! Something went wrong.") mt5.shutdown() eurusd_ticks = mt5.copy_ticks_from("EURUSD", datetime.now(), 10, mt5.COPY_TICKS_ALL) ticks_frame = pd.DataFrame(eurusd_ticks) print("Look at this beauty:") print(ticks_frame) mt5.shutdown()
如果您看到一个数据表,那么恭喜您!您刚刚迈出了使用 Python 进行算法外汇交易世界的第一步。它并不像看上去那么难。
代码结构:基本函数及其用途
那么,我们开始分析代码结构。这是分析外汇市场形态的完整系统。
我们将从系统中的主要内容开始 —— find_patterns 函数。该函数查看历史数据,识别给定长度的形态。找到第一个形态后,我们需要评估它们的效率。此函数还会记住最后的形态以供将来使用。
下一个函数是 calculate_winrate_and_frequency。该函数分析发现的形态 —— 这里是发生的频率和胜率,以及形态的排序。
process_currency_pair 函数也发挥着重要作用。这是一个相当重要的处理程序。它加载数据,查看数据,搜索不同长度的形态,并提供卖出和买入的前 300 个形态。至于代码的开头,这里是初始化、参数设置、图表间隔(TF,时间范围)和时间区间(在我的例子中,是从 1990 年到 2024 年)。
现在让我们进入主代码执行循环。形态搜索算法的特点包括不同的形态长度,因为短的形态很常见但不提供可靠性,而长的形态太罕见,尽管它们更有效。我们应该考虑各个方面。
从 MetaTrader 5 获取数据:copy_rates_range 函数
我们的第一个函数应该从终端接收数据。我们来看看代码:
import MetaTrader5 as mt5 import pandas as pd import time from datetime import datetime, timedelta import pytz # List of major currency pairs major_pairs = ['EURUSD'] # Setting up data request parameters timeframe = mt5.TIMEFRAME_H4 start_date = pd.Timestamp('1990-01-01') end_date = pd.Timestamp('2024-05-31') def process_currency_pair(symbol): max_retries = 5 retries = 0 while retries < max_retries: try: # Loading OHLC data rates = mt5.copy_rates_range(symbol, timeframe, start_date, end_date) if rates is None: raise ValueError("No data received") ohlc_data = pd.DataFrame(rates) ohlc_data['time'] = pd.to_datetime(ohlc_data['time'], unit='s') break except Exception as e: print(f"Error loading data for {symbol}: {e}") retries += 1 time.sleep(2) # Wait before retrying if retries == max_retries: print(f"Failed to load data for {symbol} after {max_retries} attempts") return # Further data processing...
这段代码到底发生了什么?首先,我们定义货币对。目前我们只有 EURUSD,但您可以添加其他货币对。然后我们设置时间间隔。H4 为 4 小时。这是最佳时间范围。
接下来是日期。从 1990 年到 2024 年。我们将需要大量的历史报价。我们拥有的数据越多,我们的分析就越准确。现在来谈谈主要内容 —— process_currency_pair 函数。它使用 copy_rates_range 加载数据。
我们得到了什么结果?带有历史数据的数据帧。时间、开盘价、收盘价、最高价、最低价 —— 工作所需的一切。
如果出现问题,我们会识别错误并将其显示在屏幕上,然后再次尝试。
时间序列处理:将 OHLC 数据转换为价格变动方向
让我们回到我们的主要任务。我们希望将外汇市场的混乱波动转化为更有序的趋势和逆转。我们该怎么做?我们将把价格变成方向。
这是我们的代码:
# Fill missing values with the mean ohlc_data.fillna(ohlc_data.mean(), inplace=True) # Convert price movements to directions ohlc_data['direction'] = np.where(ohlc_data['close'].diff() > 0, 'up', 'down')
这里发生了什么?首先,我们填补缺口。缺口可能会严重损害我们的最终结果。我们用平均值填充它们。
现在是最有趣的部分。我们创建一个名为 “direction” (方向)的新列。在那里,我们将价格数据转换为模拟趋势行为的数据。它以一种基本的方式工作:
- 如果当前收盘价高于之前的收盘价,我们就写 “up”(上涨)。
- 如果低于,我们就写 “down”(下跌)。
公式很简单,但很有效。现在我们不再使用复杂的数字,而是使用简单的 “up” 和 “down” 序列。这个序列更容易被人类感知。但我们为什么需要它呢?这些 “up” 和 “down” 是我们形态的基石。通过它们,我们可以全面了解市场正在发生的事情。
形态搜索算法:find_patterns 函数
因此,我们有一系列的 “up” 和 “down”。接下来,我们将在这个序列中寻找重复的形态。
这是 find_patterns 函数:
def find_patterns(data, pattern_length, direction): patterns = defaultdict(list) last_pattern = None last_winrate = None last_frequency = None for i in range(len(data) - pattern_length - 6): pattern = tuple(data['direction'][i:i+pattern_length]) if data['direction'][i+pattern_length+6] == direction: patterns[pattern].append(True) else: patterns[pattern].append(False) # Check last prices for pattern match last_pattern_tuple = tuple(data['direction'][-pattern_length:]) if last_pattern_tuple in patterns: last_winrate = np.mean(patterns[last_pattern_tuple]) * 100 last_frequency = len(patterns[last_pattern_tuple]) last_pattern = last_pattern_tuple return patterns, last_pattern, last_winrate, last_frequency
这一切是如何工作的呢?
- 我们创建了 “patterns” 词典。这将作为一种库,我们将在其中存储我们发现的所有形态。
- 然后我们开始迭代数据。我们取 pattern_length 的数据样本(可以是 3、4、5 等,最多为 25)并观察其后 6 个柱形发生的情况。
- 如果 6 条柱线之后价格朝期望方向移动(买入形态上涨或卖出形态下跌),我们就设置 True。如果不是 —— 则为 False。
- 我们对所有可能的数据样本都执行此操作。我们应该得到类似的形态:"up-up-down" (上-上-下)- True,"down-up-up"(下-上-上)- False 等等。
- 接下来,我们检查之前遇到的任何形态是否正在形成。如果是,我们就计算它的胜率(成功命中的百分比)和发生频率。
这就是我们如何将简单的“up”和“down”序列转变为非常强大的预测工具。但这并不是全部内容。接下来,我们将对这些形态进行排序,选出最有效的形态,并进行分析。
计算形态统计数据:胜率和发生频率
现在我们有了许多形态,我们需要选择最好的形态。
让我们看一下我们的代码:
def calculate_winrate_and_frequency(patterns): results = [] for pattern, outcomes in patterns.items(): winrate = np.mean(outcomes) * 100 frequency = len(outcomes) results.append((pattern, winrate, frequency)) results.sort(key=lambda x: x[1], reverse=True) return results
在这里,我们采用每种形态及其结果(我们之前将它们称为“True”和“False”),然后计算胜率 - 这就是我们的生产力百分比。如果某种形态 10 次中有 7 次有效,则其胜率为 70%。我们还计算频率 —— 即形态出现的次数。频率越高,我们的统计数据就越可靠。我们将所有这些都放入了 ‘results’ 列表中。最后,进行排序。我们将最好的形态放在列表的顶部。
排序结果:选择重要形态
现在我们有足够的数据。但我们并不需要所有这些。我们需要对它们进行整理。
filtered_buy_results = [result for result in all_buy_results if result[2] > 20] filtered_sell_results = [result for result in all_sell_results if result[2] > 20] filtered_buy_results.sort(key=lambda x: x[1], reverse=True) top_300_buy_patterns = filtered_buy_results[:300] filtered_sell_results.sort(key=lambda x: x[1], reverse=True) top_300_sell_patterns = filtered_sell_results[:300]
我们以类似的方式设置排序。首先,我们整理出出现次数少于 20 次的所有形态。统计数据显示,罕见形态的可靠性较低。
然后我们根据胜率对剩余的形态进行排序。最有效的设置在列表的开头。结果,我们选出了前 300 名。这是从数量超过一千的众多形态中剩下的全部内容。
使用不同的形态长度:从 3 到 25
现在我们需要选择在交易时能够统计并持续产生利润的形态变化。选项的长度有所不同。它们可以由 3 个或 25 个价格变动组成。让我们检查所有可能的情况:
pattern_lengths = range(3, 25) # Pattern lengths from 3 to 25 all_buy_patterns = {} all_sell_patterns = {} for pattern_length in pattern_lengths: buy_patterns, last_buy_pattern, last_buy_winrate, last_buy_frequency = find_patterns(ohlc_data, pattern_length, 'up') sell_patterns, last_sell_pattern, last_sell_winrate, last_sell_frequency = find_patterns(ohlc_data, pattern_length, 'down') all_buy_patterns[pattern_length] = buy_patterns all_sell_patterns[pattern_length] = sell_patterns
我们针对从 3 到 25 的每个长度启动形态搜索过滤器。我们为什么要使用这种实现方法?少于三次变动的形态太不可靠 —— 我们之前提到过这一点。长度超过 25 的形态太少见了。对于每种长度,我们都寻找买入和卖出的形态。
但为什么我们需要这么多不同的长度?短的形态可以捕捉快速的市场逆转,而长的形态可以显示长期趋势。我们事先不知道什么会更有效,所以我们测试了所有情况。
买卖形态分析
现在我们已经有了不同长度的形态选择,是时候确定哪些形态真正有效了。
以下是我们的实际代码:
all_buy_results = [] for pattern_length, patterns in all_buy_patterns.items(): results = calculate_winrate_and_frequency(patterns) all_buy_results.extend(results) all_sell_results = [] for pattern_length, patterns in all_sell_patterns.items(): results = calculate_winrate_and_frequency(patterns) all_sell_results.extend(results)
我们采用每一种形态(买入和卖出),并通过我们的胜率和频率计算器对其进行分类。
但我们并不只是计算统计数据,我们还要寻找买卖形态之间的差异。为什么这很重要?因为市场在上涨和下跌时的表现可能不同。有时买入形态会更有效,而有时卖出形态会更可靠。
接下来,我们将通过比较不同长度的形态进行下一步。事实证明,短期形态更适合确定进入市场的点,而长期形态更适合确定长期趋势。反过来也会发生同样的事情。这就是为什么我们分析一切,不提前丢弃任何东西。
在分析结束时,我们得出了第一个结果:哪些形态更适合买入,哪些形态更适用于卖出,在不同的市场条件下,形态的长度最有效。有了这些数据,我们就可以对外汇市场的价格进行分析。
但请记住,即使是最好的形态也不能保证成功。市场充满着意外。我们的任务是为了增加成功的机会,而这正是我们通过分析各个方面的形态来做的事情。
展望未来:根据近期形态进行预测
现在是时候做出一些预测了。让我们看一下预测器代码:
if last_buy_pattern: print(f"\nLast buy pattern for {symbol}: {last_buy_pattern}, Winrate: {last_buy_winrate:.2f}%, Frequency: {last_buy_frequency}") print(f"Forecast: Price will likely go up.") if last_sell_pattern: print(f"\nLast sell pattern for {symbol}: {last_sell_pattern}, Winrate: {last_sell_winrate:.2f}%, Frequency: {last_sell_frequency}") print(f"Forecast: Price will likely go down.")
我们观察最后形成的形态并尝试预测未来并进行交易分析。
请注意,我们正在考虑两种情况:买入形态和卖出形态。为什么呢?因为市场是多头与空头、买家与卖家永恒的对抗。我们应该为任何事态发展做好准备。
对于每个模式,我们输出三个关键参数:形态本身、其胜率和其发生频率。胜率尤其重要。如果买入形态的胜率为 70%,则意味着在该形态出现后 70% 的时间内,价格实际上会上涨。这些都是相当不错的结果。但请记住,即使 90% 也并不能保证什么。外汇世界总是充满着意外。
频率也起着重要作用。经常出现的形态比罕见的形态更可靠。
最有趣的部分是我们的预测,“价格可能会上涨”或“价格可能会下跌”。这些预测为所做的工作带来了一些满足感。但请记住,即使是最准确的预测也只是概率,而不是保证。外汇市场相当难以预测。新闻、经济事件,甚至有影响力的人的推文,都可以在几秒钟内改变价格走势的方向。
因此,我们的代码不是万能的,而是一个非常智能的 EA。它可以解释为:“看,根据历史数据,我们有理由相信价格会上涨(或下跌)”。是否进入市场由您决定。应用这些预测是一个深思熟虑的过程。你有关于可能走势的信息,但每一步都需要明智地采取,同时考虑到市场的整体情况。
描绘未来:可视化最佳形态和预测
让我们在代码中添加一些可视化魔法:
import matplotlib.pyplot as plt def visualize_patterns(patterns, title, filename): patterns = patterns[:20] # Take top 20 for clarity patterns.reverse() # Reverse the list to display it correctly on the chart fig, ax = plt.subplots(figsize=(12, 8)) winrates = [p[1] for p in patterns] frequencies = [p[2] for p in patterns] labels = [' '.join(p[0]) for p in patterns] ax.barh(range(len(patterns)), winrates, align='center', color='skyblue', zorder=10) ax.set_yticks(range(len(patterns))) ax.set_yticklabels(labels) ax.invert_yaxis() # Invert the Y axis to display the best patterns on top ax.set_xlabel('Winrate (%)') ax.set_title(title) # Add occurrence frequency for i, v in enumerate(winrates): ax.text(v + 1, i, f'Freq: {frequencies[i]}', va='center') plt.tight_layout() plt.savefig(filename) plt.close() # Visualize top buy and sell patterns visualize_patterns(top_300_buy_patterns, f'Top 20 Buy Patterns for {symbol}', 'top_buy_patterns.png') visualize_patterns(top_300_sell_patterns, f'Top 20 Sell Patterns for {symbol}', 'top_sell_patterns.png') # Visualize the latest pattern and forecast def visualize_forecast(pattern, winrate, frequency, direction, symbol, filename): fig, ax = plt.subplots(figsize=(8, 6)) ax.bar(['Winrate'], [winrate], color='green' if direction == 'up' else 'red') ax.set_ylim(0, 100) ax.set_ylabel('Winrate (%)') ax.set_title(f'Forecast for {symbol}: Price will likely go {direction}') ax.text(0, winrate + 5, f'Pattern: {" ".join(pattern)}', ha='center') ax.text(0, winrate - 5, f'Frequency: {frequency}', ha='center') plt.tight_layout() plt.savefig(filename) plt.close() if last_buy_pattern: visualize_forecast(last_buy_pattern, last_buy_winrate, last_buy_frequency, 'up', symbol, 'buy_forecast.png') if last_sell_pattern: visualize_forecast(last_sell_pattern, last_sell_winrate, last_sell_frequency, 'down', symbol, 'sell_forecast.png')
我们创建了两个函数:visualize_patterns 和 visualize_forecast。第一个绘制了一个信息丰富的水平柱形,其中包含前 20 种形态、它们的胜率和发生频率。第二个根据最新的形态创建我们预测的可视化表示。
对于形态,我们使用水平列,因为模式可以很长并且这使得它们更容易阅读。我们的颜色是人眼可以感受到的令人愉悦的颜色 —— 天蓝色。
我们将杰作保存为 PNG 文件。
测试和回测形态分析系统
我们已经创建了形态分析系统,但我们如何知道它是否真的有效?为此,我们需要根据历史数据进行测试。
以下是完成此任务所需的代码:
def simulate_trade(data, direction, entry_price, take_profit, stop_loss): for i, row in data.iterrows(): current_price = row['close'] if direction == "BUY": if current_price >= entry_price + take_profit: return {'profit': take_profit, 'duration': i} elif current_price <= entry_price - stop_loss: return {'profit': -stop_loss, 'duration': i} else: # SELL if current_price <= entry_price - take_profit: return {'profit': take_profit, 'duration': i} elif current_price >= entry_price + stop_loss: return {'profit': -stop_loss, 'duration': i} # If the loop ends without reaching TP or SL, close at the current price last_price = data['close'].iloc[-1] profit = (last_price - entry_price) if direction == "BUY" else (entry_price - last_price) return {'profit': profit, 'duration': len(data)} def backtest_pattern_system(data, buy_patterns, sell_patterns): equity_curve = [10000] # Initial capital $10,000 trades = [] for i in range(len(data) - max(len(p[0]) for p in buy_patterns + sell_patterns)): current_data = data.iloc[:i+1] last_pattern = tuple(current_data['direction'].iloc[-len(buy_patterns[0][0]):]) matching_buy = [p for p in buy_patterns if p[0] == last_pattern] matching_sell = [p for p in sell_patterns if p[0] == last_pattern] if matching_buy and not matching_sell: entry_price = current_data['close'].iloc[-1] take_profit = 0.001 # 10 pips stop_loss = 0.0005 # 5 pips trade_result = simulate_trade(data.iloc[i+1:], "BUY", entry_price, take_profit, stop_loss) trades.append(trade_result) equity_curve.append(equity_curve[-1] + trade_result['profit'] * 10000) # Multiply by 10000 to convert to USD elif matching_sell and not matching_buy: entry_price = current_data['close'].iloc[-1] take_profit = 0.001 # 10 pips stop_loss = 0.0005 # 5 pips trade_result = simulate_trade(data.iloc[i+1:], "SELL", entry_price, take_profit, stop_loss) trades.append(trade_result) equity_curve.append(equity_curve[-1] + trade_result['profit'] * 10000) # Multiply by 10000 to convert to USD else: equity_curve.append(equity_curve[-1]) return equity_curve, trades # Conduct a backtest equity_curve, trades = backtest_pattern_system(ohlc_data, top_300_buy_patterns, top_300_sell_patterns) # Visualizing backtest results plt.figure(figsize=(12, 6)) plt.plot(equity_curve) plt.title('Equity Curve') plt.xlabel('Trades') plt.ylabel('Equity ($)') plt.savefig('equity_curve.png') plt.close() # Calculating backtest statistics total_profit = equity_curve[-1] - equity_curve[0] win_rate = sum(1 for trade in trades if trade['profit'] > 0) / len(trades) if trades else 0 average_profit = sum(trade['profit'] for trade in trades) / len(trades) if trades else 0 print(f"\nBacktest Results:") print(f"Total Profit: ${total_profit:.2f}") print(f"Win Rate: {win_rate:.2%}") print(f"Average Profit per Trade: ${average_profit*10000:.2f}") print(f"Total Trades: {len(trades)}")
这里发生了什么?simulate_trade 函数是我们单笔交易的模拟器。它监控价格并在达到止盈或止损时关闭交易。
backtest_pattern_system 是一个比较重要的函数。它会逐日、逐步地检查历史数据,看看我们的某个形态是否已经形成。发现了买入形态?那我们就买。找到卖出的形态了吗?我们就卖。
我们使用 100 个点的固定止盈和 50 个点的止损。我们需要设定令人满意的利润的界限 —— 不要太多,以免我们承担超过限额的风险,但也不要太少,以便利润能够增长。
每次交易后,我们都会更新我们的净值曲线。在我们的工作结束时,我们得到了以下结果:我们总共赚了多少钱,有多少百分比的交易是盈利的,每笔交易的平均利润是多少。当然,我们也会将结果可视化。
让我们使用 MQL5 语言实现形态搜索。这是我们的代码:
//+------------------------------------------------------------------+ //| PatternProbabilityIndicator| //| Copyright 2024 | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, Your Name Here" #property link "https://www.mql5.com" #property version "1.06" #property indicator_chart_window #property indicator_buffers 2 #property indicator_plots 2 //--- plot BuyProbability #property indicator_label1 "BuyProbability" #property indicator_type1 DRAW_LINE #property indicator_color1 clrGreen #property indicator_style1 STYLE_SOLID #property indicator_width1 2 //--- plot SellProbability #property indicator_label2 "SellProbability" #property indicator_type2 DRAW_LINE #property indicator_color2 clrRed #property indicator_style2 STYLE_SOLID #property indicator_width2 2 //--- input parameters input int InpPatternLength = 5; // Pattern Length (3-10) input int InpLookback = 1000; // Lookback Period (100-5000) input int InpForecastHorizon = 6; // Forecast Horizon (1-20) //--- indicator buffers double BuyProbabilityBuffer[]; double SellProbabilityBuffer[]; //--- global variables int g_pattern_length; int g_lookback; int g_forecast_horizon; string g_patterns[]; int g_pattern_count; int g_pattern_occurrences[]; int g_pattern_successes[]; int g_total_bars; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- validate inputs if(InpPatternLength < 3 || InpPatternLength > 10) { Print("Invalid Pattern Length. Must be between 3 and 10."); return INIT_PARAMETERS_INCORRECT; } if(InpLookback < 100 || InpLookback > 5000) { Print("Invalid Lookback Period. Must be between 100 and 5000."); return INIT_PARAMETERS_INCORRECT; } if(InpForecastHorizon < 1 || InpForecastHorizon > 20) { Print("Invalid Forecast Horizon. Must be between 1 and 20."); return INIT_PARAMETERS_INCORRECT; } //--- indicator buffers mapping SetIndexBuffer(0, BuyProbabilityBuffer, INDICATOR_DATA); SetIndexBuffer(1, SellProbabilityBuffer, INDICATOR_DATA); //--- set accuracy IndicatorSetInteger(INDICATOR_DIGITS, 2); //--- set global variables g_pattern_length = InpPatternLength; g_lookback = InpLookback; g_forecast_horizon = InpForecastHorizon; //--- generate all possible patterns if(!GeneratePatterns()) { Print("Failed to generate patterns."); return INIT_FAILED; } g_total_bars = iBars(_Symbol, PERIOD_CURRENT); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- check for rates total if(rates_total <= g_lookback + g_pattern_length + g_forecast_horizon) { Print("Not enough data for calculation."); return 0; } int start = (prev_calculated > g_lookback + g_pattern_length + g_forecast_horizon) ? prev_calculated - 1 : g_lookback + g_pattern_length + g_forecast_horizon; if(ArraySize(g_pattern_occurrences) != g_pattern_count) { ArrayResize(g_pattern_occurrences, g_pattern_count); ArrayResize(g_pattern_successes, g_pattern_count); } ArrayInitialize(g_pattern_occurrences, 0); ArrayInitialize(g_pattern_successes, 0); // Pre-calculate patterns for efficiency string patterns[]; ArrayResize(patterns, rates_total); for(int i = g_pattern_length; i < rates_total; i++) { patterns[i] = ""; for(int j = 0; j < g_pattern_length; j++) { patterns[i] += (close[i-j] > close[i-j-1]) ? "U" : "D"; } } // Main calculation loop for(int i = start; i < rates_total; i++) { string current_pattern = patterns[i]; if(StringLen(current_pattern) != g_pattern_length) continue; double buy_probability = CalculateProbability(current_pattern, true, close, patterns, i); double sell_probability = CalculateProbability(current_pattern, false, close, patterns, i); BuyProbabilityBuffer[i] = buy_probability; SellProbabilityBuffer[i] = sell_probability; } // Update Comment with pattern statistics if total bars changed if(g_total_bars != iBars(_Symbol, PERIOD_CURRENT)) { g_total_bars = iBars(_Symbol, PERIOD_CURRENT); UpdatePatternStatistics(); } return(rates_total); } //+------------------------------------------------------------------+ //| Generate all possible patterns | //+------------------------------------------------------------------+ bool GeneratePatterns() { g_pattern_count = (int)MathPow(2, g_pattern_length); if(!ArrayResize(g_patterns, g_pattern_count)) { Print("Failed to resize g_patterns array."); return false; } for(int i = 0; i < g_pattern_count; i++) { string pattern = ""; for(int j = 0; j < g_pattern_length; j++) { pattern += ((i >> j) & 1) ? "U" : "D"; } g_patterns[i] = pattern; } return true; } //+------------------------------------------------------------------+ //| Calculate probability for a given pattern | //+------------------------------------------------------------------+ double CalculateProbability(const string &pattern, bool is_buy, const double &close[], const string &patterns[], int current_index) { if(StringLen(pattern) != g_pattern_length || current_index < g_lookback) { return 50.0; // Return neutral probability on error } int pattern_index = ArraySearch(g_patterns, pattern); if(pattern_index == -1) { return 50.0; } int total_occurrences = 0; int successful_predictions = 0; for(int i = g_lookback; i > g_pattern_length + g_forecast_horizon; i--) { int historical_index = current_index - i; if(historical_index < 0 || historical_index + g_pattern_length + g_forecast_horizon >= ArraySize(close)) { continue; } if(patterns[historical_index] == pattern) { total_occurrences++; g_pattern_occurrences[pattern_index]++; if(is_buy && close[historical_index + g_pattern_length + g_forecast_horizon] > close[historical_index + g_pattern_length]) { successful_predictions++; g_pattern_successes[pattern_index]++; } else if(!is_buy && close[historical_index + g_pattern_length + g_forecast_horizon] < close[historical_index + g_pattern_length]) { successful_predictions++; g_pattern_successes[pattern_index]++; } } } return (total_occurrences > 0) ? (double)successful_predictions / total_occurrences * 100 : 50; } //+------------------------------------------------------------------+ //| Update pattern statistics and display in Comment | //+------------------------------------------------------------------+ void UpdatePatternStatistics() { string comment = "Pattern Statistics:\n"; comment += "Pattern Length: " + IntegerToString(g_pattern_length) + "\n"; comment += "Lookback Period: " + IntegerToString(g_lookback) + "\n"; comment += "Forecast Horizon: " + IntegerToString(g_forecast_horizon) + "\n\n"; comment += "Top 5 Patterns:\n"; int sorted_indices[]; ArrayResize(sorted_indices, g_pattern_count); for(int i = 0; i < g_pattern_count; i++) sorted_indices[i] = i; // Use quick sort for better performance ArraySort(sorted_indices); for(int i = 0; i < 5 && i < g_pattern_count; i++) { int idx = sorted_indices[g_pattern_count - 1 - i]; // Reverse order for descending sort double win_rate = g_pattern_occurrences[idx] > 0 ? (double)g_pattern_successes[idx] / g_pattern_occurrences[idx] * 100 : 0; comment += g_patterns[idx] + ": " + "Occurrences: " + IntegerToString(g_pattern_occurrences[idx]) + ", " + "Win Rate: " + DoubleToString(win_rate, 2) + "%\n"; } Comment(comment); } //+------------------------------------------------------------------+ //| Custom function to search for a string in an array | //+------------------------------------------------------------------+ int ArraySearch(const string &arr[], string value) { for(int i = 0; i < ArraySize(arr); i++) { if(arr[i] == value) return i; } return -1; }
这是它在图表上的样子:
创建用于形态检测和交易的 EA
接下来,我检查了 MetaTrader 5 测试器中的开发情况,因为 Python 中的测试成功了。下面的代码也附加在文章中。该代码是外汇市场中形态分析概念的实际实现。它体现了这样的理念:历史价格形态可以提供有关未来市场走势的统计重要信息。
EA 的关键组件:
- 形态生成:EA 使用价格变动(向上或向下)的二进制表示,为给定的形态长度创建所有可能的组合。
- 统计分析:EA 进行回顾性分析,评估每种形态的发生频率及其预测效率。
- 动态适应:EA 不断更新形态统计数据以适应不断变化的市场条件。
- 做出交易决策:根据确定的最有效的买卖模式,EA 开仓、平仓或持仓。
- 参数化:EA 提供自定义关键参数的能力,例如形态长度、分析周期、预测范围以及要考虑的形态发生的最小次数。
总的来说,我制作了 4 个版本的 EA:第一个版本基于文章的概念,它根据形态开启交易,并在检测到相反方向的新的更好的模式时关闭交易。第二个是相同的,但是是多货币的:根据世界银行统计,它与 10 种流动性最强的外汇货币对一起使用。第三个是相同的,但是当价格超过预测范围的柱数时,它会关闭交易。最后一种是通过止盈止损平仓。
这是第一个 EA 的代码,其余的将在附加文件中:
//+------------------------------------------------------------------+ //| PatternProbabilityExpertAdvisor | //| Copyright 2024, Evgeniy Koshtenko | //| https://www.mql5.com/en/users/koshtenko | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, Evgeniy Koshtenko" #property link "https://www.mql5.com/en/users/koshtenko" #property version "1.00" #include <Trade\Trade.mqh> // Include the CTrade trading class //--- input parameters input int InpPatternLength = 5; // Pattern Length (3-10) input int InpLookback = 1000; // Lookback Period (100-5000) input int InpForecastHorizon = 6; // Forecast Horizon (1-20) input double InpLotSize = 0.1; // Lot Size input int InpMinOccurrences = 30; // Minimum Pattern Occurrences //--- global variables int g_pattern_length; int g_lookback; int g_forecast_horizon; string g_patterns[]; int g_pattern_count; int g_pattern_occurrences[]; int g_pattern_successes[]; int g_total_bars; string g_best_buy_pattern = ""; string g_best_sell_pattern = ""; CTrade trade; // Use the CTrade trading class //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- validate inputs if(InpPatternLength < 3 || InpPatternLength > 10) { Print("Invalid Pattern Length. Must be between 3 and 10."); return INIT_PARAMETERS_INCORRECT; } if(InpLookback < 100 || InpLookback > 5000) { Print("Invalid Lookback Period. Must be between 100 and 5000."); return INIT_PARAMETERS_INCORRECT; } if(InpForecastHorizon < 1 || InpForecastHorizon > 20) { Print("Invalid Forecast Horizon. Must be between 1 and 20."); return INIT_PARAMETERS_INCORRECT; } //--- set global variables g_pattern_length = InpPatternLength; g_lookback = InpLookback; g_forecast_horizon = InpForecastHorizon; //--- generate all possible patterns if(!GeneratePatterns()) { Print("Failed to generate patterns."); return INIT_FAILED; } g_total_bars = iBars(_Symbol, PERIOD_CURRENT); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if(!IsNewBar()) return; UpdatePatternStatistics(); string current_pattern = GetCurrentPattern(); if(current_pattern == g_best_buy_pattern) { if(PositionSelect(_Symbol) && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { trade.PositionClose(_Symbol); } if(!PositionSelect(_Symbol)) { trade.Buy(InpLotSize, _Symbol, 0, 0, 0, "Buy Pattern: " + current_pattern); } } else if(current_pattern == g_best_sell_pattern) { if(PositionSelect(_Symbol) && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { trade.PositionClose(_Symbol); } if(!PositionSelect(_Symbol)) { trade.Sell(InpLotSize, _Symbol, 0, 0, 0, "Sell Pattern: " + current_pattern); } } } //+------------------------------------------------------------------+ //| Generate all possible patterns | //+------------------------------------------------------------------+ bool GeneratePatterns() { g_pattern_count = (int)MathPow(2, g_pattern_length); if(!ArrayResize(g_patterns, g_pattern_count)) { Print("Failed to resize g_patterns array."); return false; } for(int i = 0; i < g_pattern_count; i++) { string pattern = ""; for(int j = 0; j < g_pattern_length; j++) { pattern += ((i >> j) & 1) ? "U" : "D"; } g_patterns[i] = pattern; } return true; } //+------------------------------------------------------------------+ //| Update pattern statistics and find best patterns | //+------------------------------------------------------------------+ void UpdatePatternStatistics() { if(ArraySize(g_pattern_occurrences) != g_pattern_count) { ArrayResize(g_pattern_occurrences, g_pattern_count); ArrayResize(g_pattern_successes, g_pattern_count); } ArrayInitialize(g_pattern_occurrences, 0); ArrayInitialize(g_pattern_successes, 0); int total_bars = iBars(_Symbol, PERIOD_CURRENT); int start = total_bars - g_lookback; if(start < g_pattern_length + g_forecast_horizon) start = g_pattern_length + g_forecast_horizon; double close[]; ArraySetAsSeries(close, true); CopyClose(_Symbol, PERIOD_CURRENT, 0, total_bars, close); string patterns[]; ArrayResize(patterns, total_bars); ArraySetAsSeries(patterns, true); for(int i = 0; i < total_bars - g_pattern_length; i++) { patterns[i] = ""; for(int j = 0; j < g_pattern_length; j++) { patterns[i] += (close[i+j] > close[i+j+1]) ? "U" : "D"; } } for(int i = start; i >= g_pattern_length + g_forecast_horizon; i--) { string current_pattern = patterns[i]; int pattern_index = ArraySearch(g_patterns, current_pattern); if(pattern_index != -1) { g_pattern_occurrences[pattern_index]++; if(close[i-g_forecast_horizon] > close[i]) { g_pattern_successes[pattern_index]++; } } } double best_buy_win_rate = 0; double best_sell_win_rate = 0; for(int i = 0; i < g_pattern_count; i++) { if(g_pattern_occurrences[i] >= InpMinOccurrences) { double win_rate = (double)g_pattern_successes[i] / g_pattern_occurrences[i]; if(win_rate > best_buy_win_rate) { best_buy_win_rate = win_rate; g_best_buy_pattern = g_patterns[i]; } if((1 - win_rate) > best_sell_win_rate) { best_sell_win_rate = 1 - win_rate; g_best_sell_pattern = g_patterns[i]; } } } Print("Best Buy Pattern: ", g_best_buy_pattern, " (Win Rate: ", DoubleToString(best_buy_win_rate * 100, 2), "%)"); Print("Best Sell Pattern: ", g_best_sell_pattern, " (Win Rate: ", DoubleToString(best_sell_win_rate * 100, 2), "%)"); } //+------------------------------------------------------------------+ //| Get current price pattern | //+------------------------------------------------------------------+ string GetCurrentPattern() { double close[]; ArraySetAsSeries(close, true); CopyClose(_Symbol, PERIOD_CURRENT, 0, g_pattern_length + 1, close); string pattern = ""; for(int i = 0; i < g_pattern_length; i++) { pattern += (close[i] > close[i+1]) ? "U" : "D"; } return pattern; } //+------------------------------------------------------------------+ //| Custom function to search for a string in an array | //+------------------------------------------------------------------+ int ArraySearch(const string &arr[], string value) { for(int i = 0; i < ArraySize(arr); i++) { if(arr[i] == value) return i; } return -1; } //+------------------------------------------------------------------+ //| Check if it's a new bar | //+------------------------------------------------------------------+ bool IsNewBar() { static datetime last_time = 0; datetime current_time = iTime(_Symbol, PERIOD_CURRENT, 0); if(current_time != last_time) { last_time = current_time; return true; } return false; }
至于测试结果,针对 EURUSD 如下:
详细来看:
还不错,而且图形很漂亮。其他 EA 版本要么徘徊在零点附近,要么进入长期亏损状态。其中的最佳选项也不太符合我的标准。我更喜欢盈利因子高于 2 且夏普比率高于 1 的 EA。我突然想到,在 Python 测试器中有必要同时考虑交易佣金以及点差和库存费。
潜在的改进:扩展时间范围并添加指标
让我们继续思考。该系统肯定显示了积极的结果,但如何改进这些结果,它是否现实?
现在我们来看看 4 小时的时间范围。让我们试着看得更远,我们应该添加每日、每周甚至每月的图表。通过这种方法,我们将能够看到更多的全局趋势和更大规模的形态。让我们扩展代码以涵盖所有这些时间尺度:
timeframes = [mt5.TIMEFRAME_H4, mt5.TIMEFRAME_D1, mt5.TIMEFRAME_W1, mt5.TIMEFRAME_MN1]
for tf in timeframes:
ohlc_data = get_ohlc_data(symbol, tf, start_date, end_date)
patterns = find_patterns(ohlc_data)
数据越多,噪音就越多。我们需要学会筛选这些噪音以获得更清晰的数据。
让我们扩大分析特征的范围。在交易世界中,这就是技术指标的补充。RSI、MACD 和布林带是最常用的工具。
def add_indicators(data): data['RSI'] = ta.RSI(data['close']) data['MACD'] = ta.MACD(data['close']).macd() data['BB_upper'], data['BB_middle'], data['BB_lower'] = ta.BBANDS(data['close']) return data ohlc_data = add_indicators(ohlc_data)
指标可以帮助我们确认我们的形态信号。或者,我们可以另外搜索指标上的形态。
结论
这样我们就完成了寻找和分析形态的工作。我们创建了一个在市场混乱中寻找形态的系统。我们学会了将结果可视化、进行回溯测试、规划未来的改进。但最重要的是,我们学会了像分析交易员一样思考。我们不会只是随波逐流,我们会寻找自己的道路、自己的形态、自己的可能性。
请记住,市场是活生生的人的行为的产物。它会成长、会变化。我们的任务就是随之改变。今天的形态明天可能就不再适用,但这并不是绝望的理由。这是一个学习、适应和成长的机会。使用该系统作为起点,实验、改进、创造你自己的系统。也许您会发现这种模式将打开成功交易的大门!
祝您在这次激动人心的旅程中好运!让你的模式始终可以获利,让损失只是你成功路上的教训。很快在外汇交易的世界与您相见!
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/15965



