English Русский Español Deutsch 日本語 Português
preview
使用 MetaTrader 5 在 Python 中查找自定义货币对形态

使用 MetaTrader 5 在 Python 中查找自定义货币对形态

MetaTrader 5交易 |
60 1
Yevgeniy Koshtenko
Yevgeniy Koshtenko

外汇形态分析简介

初学者第一次看货币对图表时会看到什么?许多日内波动、波动性增加和减少、趋势变化等等。上升、下降、曲折 —— 如何弄清楚这一切?我也通过深入研究价格形态分析开始了解外汇。

乍一看,我们世界上的许多事情似乎都很混乱。但任何有经验的专家都会看到他或她个人领域的模式和可能性,这似乎让其他人感到困惑。货币对图表也是如此。如果我们试图将这种混乱系统化,我们可以发现可以暗示未来价格走势的隐藏形态。

但如何找到它们呢?如何区分真实模式和随机噪声呢?乐趣就从这里开始。我决定使用 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

附加的文件 |
PredictPattern.py (9.23 KB)
AutoPattern.mq5 (18.98 KB)
PatternEA.mq5 (16.12 KB)
PatternEAMult.mq5 (9.59 KB)
最近评论 | 前往讨论 (1)
linfo2
linfo2 | 9 5月 2025 在 02:09
谢谢你,Yevgniy,一个伟大的模板,用 python 评估了一个想法。
使用 Python 分析天气对农业国家货币的影响 使用 Python 分析天气对农业国家货币的影响
天气与外汇之间有什么关系?传统经济理论长期忽视天气对市场行为的影响。但一切都已改变。让我们尝试找出天气条件与农业货币在市场上的走势之间的联系。
交易中的神经网络:节点-自适应图形表征(NAFS) 交易中的神经网络:节点-自适应图形表征(NAFS)
我们邀请您领略 NAFS(节点-自适应特征平滑)方法,这是一种创建节点表征的非参数方法,不需要参数训练。NAFS 提取每个给定节点的邻域特征,然后把这些特征自适应组合,从而形成最终表征。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
MQL5中的逐步特征选择 MQL5中的逐步特征选择
在本文中,我们介绍一个在MQL5中实现的逐步特征选择算法的改进版本。这种方法基于Timothy Masters在其著作《C++和CUDA C中的现代数据挖掘算法》中概述的技术。