
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を使ったアルゴリズムFX取引の世界に第一歩を踏み出したことになります。思っているほど難しくはありません。
コード構造:基本機能とその目的
それでは、コード構造の分析を始めましょう。これは外国為替市場のパターンを分析するための完全なシステムです。
まず、システムの中核となる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を使ってデータを読み込みます。
その結果、何が得られるでしょうか。時間、始値、終値、高値、安値など、取引に必要な情報を含んだ履歴データのDataFrameが返されます。
もし何か問題が起きた場合は、エラーが特定され画面に表示され、再度処理を試みます。
時系列処理: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のような結果が得られます。
- 次に、これまでに見つかったパターンが現在形成されているかをチェックし、もしそうなら、その勝率と発生頻度を計算します。
こうして、単純な「上昇」と「下降」のシーケンスから、非常に強力な予測ツールを作り上げているのです。しかし、これがすべてではありません。次にこれらのパターンを並べ替え、最も効率の良いものを選び出して分析していきます。
パターン統計の計算:勝率と発生頻度
いくつかのパターンが見つかったので、その中から最も優れたものを選び出す必要があります。
コードを見てみましょう。
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件を選び出します。数千以上あるパターンの中から残るのは、この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までの長さごとに、パターン検索フィルターを順に実行していきます。なぜこのような実装にしているのでしょうか。3ステップ未満のパターンは信頼性が低く(これは先ほども触れました)、一方で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.")
直近に形成された最後のパターンを観察し、それに基づいて将来の動きを予測し、取引分析をおこないます。
ここで重要なのは、「買い」と「売り」2つのシナリオを常に並行して検討している点です。なぜなら、相場とは常に強気派と弱気派、つまり買い手と売り手のせめぎ合いだからです。どちらに転ぶか分からない状況に、常に備えておく必要があります。
各パターンについて、パターン自体、その勝率、およびその発生頻度という3つの主要なパラメータを出力します。特に勝率は重要です。たとえば、ある買いパターンの勝率が70%であれば、そのパターンが出現したあとに実際に価格が上昇したケースが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')
2つの関数、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関数は、1回の取引をシミュレートするものです。価格の動きを監視し、設定したテイクプロフィットやストップロスに達した時点で取引を終了します。
一方、backtest_pattern_system関数はより重要です。過去のデータを1日ずつ順に確認し、事前に検出したパターンが現れているかをチェックします。買いパターンを見つけたら買い注文を出し、売りパターンを見つけたら売り注文を出します。
テイクプロフィットは100ポイント、ストップロスは50ポイントに固定しています。これは、利益が大きすぎてリスクが高くならないようにしつつ、利益が少なすぎて意味がなくならないちょうど良いバランスを設定するためです。
各取引の後にエクイティカーブを更新し、最後に総利益、勝率、1取引あたりの平均利益を算出します。そしてその結果を可視化ます。
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の作成
次に、Pythonでのテストが成功したため、MetaTrader 5のテスターで開発状況を確認しました。以下のコードも記事に添付されています。このコードは、外国為替市場におけるパターン分析の概念を実際的に実装したものです。過去の価格パターンが将来の市場動向に関する統計的に重要な情報を提供できるという考えを具体化しています。
EAの主要コンポーネント
- パターン生成:EAは価格変動(上昇または下降)のバイナリ表現を使用し、指定されたパターン長さに対して可能なすべての組み合わせを作成します。
- 統計分析:各パターンの発生頻度と予測効率を評価する遡及分析を実行します。
- 動的適応:変化する市場状況に適応できるよう、パターン統計を継続的に更新します。
- 取引の意思決定:最も効果的な買い・売りパターンに基づいて、ポジションのオープン、クローズ、保有をおこないます。
- パラメータ化:パターン長さ、分析期間、予測期間、考慮するパターン発生の最小数など、主要なパラメータをカスタマイズ可能にしています。
合計で4つのEAバージョンを作成しました。1つ目はこの記事のコンセプトに基づくもので、パターンに基づいて取引を開始し、反対方向により良いパターンが検出されると決済します。2つ目は同様ですが、世界銀行の統計に基づき最も流動性の高い10の外国為替ペアに対応する多通貨版です。3つ目は同じくパターンで取引しますが、予測期間よりも多くのバーを価格が通過した時点で決済します。最後の4つ目は、テイクプロフィットとストップロスによる決済をおこないます。
以下に最初のEAのコードを示します。残りのバージョンは添付ファイルをご覧ください。
//+------------------------------------------------------------------+ //| PatternProbabilityExpertAdvisor | //| Copyright 2024, Evgeniy Koshtenko | //| https://www.mql5.com/ja/users/koshtenko | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, Evgeniy Koshtenko" #property link "https://www.mql5.com/ja/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





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索