
知っておくべきMQL5ウィザードのテクニック(第61回):教師あり学習でADXとCCIのパターンを活用する
はじめに
市場のさまざまな側面を追跡するインジケーターの組み合わせを、機械学習と組み合わせて取引システムを構築する方法を引き続き見ていきます。今回の記事では、平均方向性指数(ADX)オシレーターと商品チャネル指数(CCI)の組み合わせを取り上げます。ADXは主にトレンドの確認インジケーターであり、CCIはモメンタムインジケーターです。これら2つの特性については、過去の記事で個別インジケーターのパターンを見た際にも触れました。要点を振り返ると、トレンド確認は与えられた価格トレンドの強さを測定し、その強さがエントリーの適性を示します。一方で、モメンタムインジケーターは価格変化の速度を測定します。価格がある方向に急速に変化しているほど、不利な逆行を被る可能性は低くなります。
商品チャネル指数(CCI: Commodity Channel Index)
インジケーターのシグナルを入力として受け取るネットワークはPythonでテストしています。これは、Pythonが現時点でMQL5と比較して大幅なパフォーマンス上の優位性を持っているためです。しかし、最近の記事でも述べたように、MQL5もOpenCLを利用することで、この性能に匹敵するか、近いレベルに到達することが可能です。ただし、OpenCLの速度向上を享受するにはGPUが必要となります。とはいえ、現状では両方の言語を同等のCPU上で使用する場合、Pythonが明らかに優位に立っており、Pythonでモデルをテストし、開発しているのはこのためです。
MetaTraderにはPythonモジュールが用意されており、これを利用することでブローカーの価格データをPythonに読み込むだけでなく、Pythonから直接取引をおこなうことも可能です。詳細については、こちらとこちらで確認できます。そのため、私たちはこれらのモジュールの一部を使い、ブローカーにログインして価格データをPythonへ取り込み、教師あり学習用のMLPに利用します。価格データを取得したら、次に必要なのはインジケーター関数の定義です。CCIから始めると、実装は次のようになります。
def CCI(df, period=14): """ Calculate Commodity Channel Index (CCI) :param df: pandas DataFrame with columns ['high', 'low', 'close'] :param period: lookback period (default 20) :return: Series with CCI values """ # Calculate Typical Price typical_price = (df['high'] + df['low'] + df['close']) / 3 # Calculate Simple Moving Average of Typical Price sma = typical_price.rolling(window=period).mean() # Calculate Mean Deviation mean_deviation = typical_price.rolling(window=period).apply( lambda x: np.mean(np.abs(x - np.mean(x))), raw=True) # Calculate CCI df['cci'] = (typical_price - sma) / (0.015 * mean_deviation) return df[['cci']]
CCIは、資産価格が統計的平均からどれほど乖離しているかを数値化するオシレーターです。表面的には、これはトレーダーが買われすぎまたは売られすぎの状態を特定するのに役立ちます。先ほどの実装関数は、高値、安値、終値の価格列を持つpandasデータフレームと、過去一定期間(デフォルト14)を受け取り、CCI値を計算します。「代表価格(Typical Price)」の計算は、高値、安値、終値の平均を取り、一定期間の価格変動をほぼ網羅的に表す値とします。これは各時間枠における3つの主要価格の算術平均であり、価格データを単一の代表値に簡略化することで、日中の細かな変動によるノイズを軽減するのに役立ちます。この工程は基礎的であり、CCIはこの代表価格からの乖離に基づくため、すべての価格要素(高値、安値、終値)を使用することが値動きのバランスの取れた見方につながります。そのため、MetaTrader 5モジュールを介して取得するブローカーの価格データ用のpandasデータフレームに、これらすべての列が存在する必要があります。欠損値があるとエラーにつながるからです。
単純移動平均(SMA)は、指定した期間にわたって代表価格を平滑化し、基準線を確立します。これは、入力期間(デフォルト14)でSMAを計算することで実現され、基準値のように機能します。短期的な価格変動が平滑化されるため、代表的な「正常」価格水準を提供する点が重要です。rolling関数は有効なSMAを計算するために十分なデータポイント(少なくとも入力期間の2乗程度)が必要です。データセットが短すぎる場合、最初にNaN値が発生する可能性があり、その際にはdropna()などの関数で特別な処理が必要になります。
平均偏差は、これらの代表価格とそのSMAの絶対的な乖離の平均を測定することで、価格のボラティリティを捉えます。各ウィンドウごとに、各代表価格とその平均値との差の絶対値を計算し、それらを平均化します。これは、価格の変動幅を測定しCCIのスケーリングに不可欠であり、資産の典型的な価格変動を反映する上で重要です。また異なる資産間の比較を可能にします。ラムダ関数の適用は大規模データセットでは計算負荷が高いため、ベクトル化された代替手法やta-libのようなライブラリを使用することが望ましいでしょう。その際にはNumPyのインポートも必要です。
CCIの公式は、これらすべての要素を組み合わせて最終値を計算し、それを正規化のために0.015という定数でスケーリングします。この定数は標準的なスケーリング係数で、CCIの値を±100の範囲に収めることを目的としています(必ずしも完全には収まらない場合もありますが、それが目標です)。これはCCI公式の中核で、生の価格乖離を標準化されたオシレーターに変換します。+100を超える値は買われすぎを示し、-100を下回る値は売られすぎを示します。この計算では、平均偏差がゼロの場合にゼロ除算エラーが発生する可能性があるため注意が必要です。価格が完全にフラットであれば起こり得るため、その場合は分母に小さなε値(例:1e-10)を加えて対応するのが有効です。
return文は、cci列のみを含むデータフレームを返します。デバッグや追加分析のために他の列(例:typical_price)も必要な場合、このreturn文を修正してそれらを含めることも可能です。
平均方向性指数(ADX: Average Directional Movement Index)
ADXは前述のとおり、トレンドの方向に関わらず、その強さを測定します。これは、Directional Movement Index(方向性指数、DMI)を利用することでおこなわれ、DMIは主に2つのバッファ(+DIと-DI)から構成されます。この関数も、先ほどのCCIと同様に、高値、安値、終値の列を持つpandasデータフレームと、過去一定期間(デフォルト14)を受け取り、ADX、+DI、-DIの値を計算します。Pythonでの実装は次のとおりです。
def ADX(df, period=14): """ Calculate Average Directional Index (ADX) :param df: pandas DataFrame with columns ['high', 'low', 'close'] :param period: lookback period (default 14) :return: DataFrame with ADX, +DI, -DI columns """ # Calculate +DM, -DM, and True Range df['up_move'] = df['high'] - df['high'].shift(1) df['down_move'] = df['low'].shift(1) - df['low'] df['+dm'] = np.where( (df['up_move'] > df['down_move']) & (df['up_move'] > 0), df['up_move'], 0.0 ) df['-dm'] = np.where( (df['down_move'] > df['up_move']) & (df['down_move'] > 0), df['down_move'], 0.0 ) # Calculate True Range df['tr'] = np.maximum( df['high'] - df['low'], np.maximum( abs(df['high'] - df['close'].shift(1)), abs(df['low'] - df['close'].shift(1)) )) # Smooth the values using Wilder's smoothing method (EMA with alpha=1/period) df['+dm_smoothed'] = df['+dm'].ewm(alpha=1/period, adjust=False).mean() df['-dm_smoothed'] = df['-dm'].ewm(alpha=1/period, adjust=False).mean() df['tr_smoothed'] = df['tr'].ewm(alpha=1/period, adjust=False).mean() # Calculate +DI and -DI df['+di'] = 100 * (df['+dm_smoothed'] / df['tr_smoothed']) df['-di'] = 100 * (df['-dm_smoothed'] / df['tr_smoothed']) # Calculate DX df['dx'] = 100 * (abs(df['+di'] - df['-di']) / (df['+di'] + df['-di'])) # Calculate ADX df['adx'] = df['dx'].ewm(alpha=1/period, adjust=False).mean() # Return the relevant columns return df[['adx', '+di', '-di']]
方向性の動きを計算する2つのバッファ(+DM、-DM)は、上昇および下降の価格変動の大きさを数値化し、方向性の強さを評価します。これらは、連続する高値の差(up_move)と、連続する安値の差(down_move)から導かれます。これは、+DMおよび-DMの基盤となり、それが方向性モメンタムを決定するため重要です。shift値を用いることで、異なる期間の比較が可能になります。up_moveがdown_moveを上回り、かつ正の値である場合、その値が+DMに割り当てられ、それ以外は+DMは0となります。同様に、down_moveがup_moveを上回り正である場合、その値が-DMに割り当てられます。これにより、支配的でない動きや負の動きが除外されます。
NumPyのwhere関数はベクトル化演算に効率的であるため、NumPyをインポートしておく必要があります。up_moveおよびdown_moveの計算が正しいかを確認することも重要で、誤分類を避けるためです。shift(1)の使用により最初の行にNaNが発生するため、その処理は後続の計算や結果を返す際に対応が必要です。また、「high」列と「low」列が数値型であることを必ず確認してください。
トゥルーレンジ(TR)は価格のボラティリティを測定し、ギャップや日中の変動幅を考慮します。計算は、高値-安値、現在の高値-前日の終値の絶対値、現在の安値-前日の終値の絶対値の3つの値のうち最大値を取ることでおこないます。これにより価格ギャップやボラティリティが反映されます。この測定は、+DIおよび-DIを正規化する分母として機能するため重要です。つまり、ADXは価格変動に対するトレンド強度を表すことになります。NumPyのmaximum関数を用いることで、最も大きな値が確実に選ばれます。なお、shift(1)によるNaN処理も注意が必要です。
ワイルダーの平滑化は、+DM、-DM、TRに対して特定のα値を持つ指数移動平均を適用します。αは1/期間であり、adjust=Falseを指定することで、直近データにより重みを与え、ワイルダーの元の手法を模倣します。平滑化はノイズを低減し、トレンド分析における信頼性を高めます。ewm関数は効率的ですが、αの値に敏感です。
方向性インジケーター(+DI、-DI)は、平滑化された動きをTRで正規化することで、強気と弱気の強さを比較可能にします。100倍のスケーリングによってパーセンテージとして表現されます。これにより、+DIと-DIはボラティリティに対する強気と弱気の度合いを数値化します。+DIと-DIのクロスは、トレンドの転換を示すシグナルとなります。ゼロ除算を防ぐためには、小さなε値を加えることが有効です。スケーリング係数の100は標準的で、解釈を容易にします。
DXバッファは、+DIと-DIの差の絶対値をその和で割り、スケーリングした値です。これは方向性の相対的な強さを測定するために不可欠です。DXはADXに至る重要な中間ステップであり、トレンドの「強さ」(強気弱気を問わず)を測定します。+DIと-DIの和がゼロの場合は除算エラーを避ける必要があり、その場合はゼロを返すか計算をスキップします。
最後に、ADXは方向性指数を平均化し、全体的なトレンド強度を評価します。DX値をワイルダーのEMAで平滑化することで、長期的なトレンド強度を反映させます。これが最終的なインジケーターの出力であり、25以上の値は強いトレンドを示し、20未満の値は弱いまたはレンジ相場を示すとされます。すべての平滑化工程において、一貫したα値を使用することが重要です。
return文は、adx、+di、-diの列を持つデータフレームを返します。これがADXインジケーターの完全なバッファセットであり、トレーダーにトレンド分析のための関連指標を提供します。デバッグやカスタマイズのために、中間列(dxやtr)を追加することも可能です。
特徴量
これら2つの関数(ADXとCCI)から得られた読み取り値を組み合わせ、トレンドの強さを示すADXとモメンタムを示すCCIを統合した多次元シグナル配列を作成します。これにより、トレンドの開始、反転、買われすぎ/売られすぎといった特定の市場状態を識別できるようになります。これら2つのインジケーターを組み合わせて生成されるシグナルを、より広い枠組みでは「特徴量」と呼びます。前回までの4つの記事で、移動平均(MA)とストキャスティクスの組み合わせを検討した際、特徴量、状態、行動、報酬、符号化の5つの主要なデータセットを扱いました。ここでの特徴量は、そのときの「特徴量」と同等のものです。
これら2つのインジケーターの組み合わせからは、最大で10種類の特徴量パターンを生成できると仮定します。実際にはさらに多くなる可能性がありますが、ここではこの数で十分です。各パターンには関数を割り当て、それぞれの関数はNumPy配列を返します。この配列では各列が特定の条件(次元)を表し、条件が満たされていれば1、そうでなければ0とします。関数の入力としてはpandasのデータフレームを使用し、adx_df(列:adx、+di、-di)、cci_df(列:cci)、必要に応じてprice_df(列:high、low、close)が与えられます。
Pythonでこれらの関数を実装し、テストプロセスを加速させていますが、最終的なEAを展開し利用するためにはMQL5での同等実装も必要になります。MQL5にはCCIとADXのポイントを扱う組み込み処理があるため、EA使用時に問題は発生しません。復習として、Pythonにおいては、adx_dfとcci_dfに必要な列が存在するか検証し、比較時のエラーを避けるためにNaN値をdropまたはfillで処理する必要があります。shift(1)のようなshift演算は本質的に最初の行にNaNを導入するため、最初の行を0に設定するのが標準的な対応方法です。大規模データセットでは、NumPyのwhere()関数やastype(int)では十分でない場合があるため、ベクトル化された処理を導入するのが望ましいです。
すべての特徴量は、EURUSDの日足データを対象に、2020年1月1日から2024年1月1日までの期間でテスト/訓練されました。フォワードウォーク期間は2024年1月1日から2025年1月1日までです。フォワードウォークに成功したのはFeature_2、3、4のみであり、それぞれの説明とともに結果を共有します。
Feature_0
これはADX>25およびCCIが±100でクロスオーバーすることに基づくパターンです。この組み合わせは、ブレイクアウト時のモメンタム確認やトレンドの始まりを示します。ADXでは25レベルが重要で、CCIの100レベルと同時にクロスした場合、新しいトレンドが発生する可能性が高くなります。これら2つのインジケーターを使用することで、市場ノイズをフィルタリングできます。トレンドの開始を見極める際には、ADXが25レベルをテストするのを待つことが常に重要です。20未満の値は避けるべきです。これは、株価指数などのトレンドが発生しやすいアセットにおいて高い確率で有効なセットアップです。
これらのシグナルから生成される特徴量ベクトルは6次元ベクトルです。PythonおよびMQL5による実装は以下の通りです。
def feature_0(adx_df, cci_df): """ Creates a 3D signal array with the following dimensions: 1. ADX > 25 crossover (1 when crosses above 25, else 0) 2. CCI > +100 crossover (1 when crosses above +100, else 0) 3. CCI < -100 crossover (1 when crosses below -100, else 0) """ # Initialize empty array with 3 dimensions and same length as input feature = np.zeros((len(adx_df), 6)) # Dimension 1: ADX > 25 crossover feature[:, 0] = (adx_df['adx'] > 25).astype(int) feature[:, 1] = (adx_df['adx'].shift(1) <= 25).astype(int) # Dimension 2: CCI > +100 crossover feature[:, 2] = (cci_df['cci'] > 100).astype(int) feature[:, 3] = (cci_df['cci'].shift(1) <= 100).astype(int) # Dimension 3: CCI < -100 crossover feature[:, 4] = (cci_df['cci'] < -100).astype(int) feature[:, 5] = (cci_df['cci'].shift(1) >= -100).astype(int) # Set first row to 0 (no previous values to compare) feature[0, :] = 0 return feature
if(Index == 0) { if(CopyBuffer(A.Handle(), 0, T, 2, _adx) >= 2 && CopyBuffer(C.Handle(), 0, T, 1, _cci) >= 2) { _features[0] = (_adx[0] > 25 ? 1.0f : 0.0f); _features[1] = (_adx[1] <= 25 ? 1.0f : 0.0f); _features[2] = (_cci[0] > 100 ? 1.0f : 0.0f); _features[3] = (_cci[1] <= 100 ? 1.0f : 0.0f); _features[4] = (_cci[0] < -100 ? 1.0f : 0.0f); _features[5] = (_cci[1] >= -100 ? 1.0f : 0.0f); } }
このパターンのMLP入力ベクトルを、主要な構成シグナルに分解しました。具体的には、ADXが以前は25未満であったこと、その後ADXが25を超えたこと、CCIが以前は+100未満であったこと、現在CCIが+100を超えていること、CCIが以前は-100より大きかったこと、現在CCIが-100未満になっていることです。このセットアップでは、当然ながらすべての状況が同時に成り立つわけではありません。しかしこれにより、典型的なパターンロジックに単純化してまとめるのではなく、すべての価格データポイントをカスタマイズして扱うことが可能になります。
従来の強気セットアップは、ADXが25を下回っていた状態から上抜けし、その後CCIも+100を下から上に抜けて終値をつけるパターンです。同様に、弱気パターンはADXが25を下から上に抜けて終値をつけた後、CCIが-100を上から下に抜けて終値をつける場合です。もしこれらの「純粋な」強気または弱気セットアップのみをテスト対象とする厳密なベクトルを生成するならば、入力ベクトルの次元数は3となり、大規模なテストデータにおいて変動性が少なくなってしまいます。私たちが採用した6次元入力データの方法は、これらの従来型の基準を捉えるだけでなく、本来なら「離散的」あるいは「伝統的」なセットアップでは無視されてしまう「連続的」なデータも取り込むことができます。
Feature_1
このパターンはADX>25とCCIが±50レベルを反対側からクロスする動きに基づいています。これは確立されたトレンドにおけるモメンタム再エントリーの形です。ADXがトレンドの健全性を確認し、CCIが短期的な逆行後の回復を検出するため、プルバック(調整局面)継続の取引に理想的です。これは、押し目(リトレースメント)後にトレンドへ参入しようとするトレンドフォロワーに適したパターンです。CCIがゼロ境界をクロスするシグナルも重要な手掛かりであり、軽視すべきではありません。すでにトレンドに乗っているトレーダーにとっては、利益を保護するためにこのシグナルに基づいてトレーリングストップを設定することも可能です。PythonおよびMQL5による実装は以下の通りです。
def feature_1(adx_df, cci_df): """ Creates a modified 3D signal array with: 1. ADX > 25 (1 when above 25, else 0) 2. CCI crosses from below 0 to above +50 (1 when condition met, else 0) 3. CCI crosses from above 0 to below -50 (1 when condition met, else 0) """ # Initialize empty array with 3 dimensions feature = np.zeros((len(adx_df), 5)) # Dimension 1: ADX above 25 (continuous, not just crossover) feature[:, 0] = (adx_df['adx'] > 25).astype(int) # Dimension 2: CCI crosses from <0 to >+50 feature[:, 1] = (cci_df['cci'] > 50).astype(int) feature[:, 2] = (cci_df['cci'].shift(1) < 0).astype(int) # Dimension 3: CCI crosses from >0 to <-50 feature[:, 3] = (cci_df['cci'] < -50).astype(int) feature[:, 4] = (cci_df['cci'].shift(1) > 0).astype(int) # Set first row to 0 (no previous values to compare) feature[0, :] = 0 return feature
else if(Index == 1) { if(CopyBuffer(A.Handle(), 0, T, 2, _adx) >= 2 && CopyBuffer(C.Handle(), 0, T, 1, _cci) >= 2) { _features[0] = (_adx[0] > 25 ? 1.0f : 0.0f); _features[1] = (_cci[0] > 50 ? 1.0f : 0.0f); _features[2] = (_cci[1] < 0 ? 1.0f : 0.0f); _features[3] = (_cci[0] < -50 ? 1.0f : 0.0f); _features[4] = (_cci[1] > 0 ? 1.0f : 0.0f); } }
この関数は5次元の出力ベクトルを生成し、その要素はバイナリ値で、ADXが25レベルを上回っているかどうか、直前のCCIが0を上回っていたかどうか、現在のCCIが-50以下であるかどうか、直前のCCIが0未満であったかどうか、現在のCCIが50以上であるかどうかです。従来の強気セットアップは、ADXが25を上回っており、CCIが直前では0未満だったが現在は50を超えている場合です。弱気セットアップは、ADXが25を上回っており、CCIが直前では0を上回っていたが現在は-50以下になっている場合です。上記で述べた「離散的」なマッピングよりも「連続的/回帰的」なマッピングを重視する点は、ここにも当てはまります。
Feature_2
このパターンはADX>25を条件とし、価格とCCIによるダイバージェンスを利用するものです。これはダイバージェンスセットアップ、またはトレンド内でのリバーサルの一形態です。古典的なモメンタムダイバージェンスのパターンにADXをトレンドフィルターとして組み合わせています。これにより、トレンドが依然として継続している中でも潜在的な反転を示唆します。このパターンは、より確度の高い確認を得るためにプライスアクションやサポート/レジスタンスと組み合わせて使う状況に適しています。また、長く続いた値動きの後にダイバージェンスが形成される場合にも理想的です。ただし注意が必要です。ダイバージェンスは多くの場合、初期シグナルにすぎず、強いトレンドから派生した場合には失敗するケースも少なくありません。PythonおよびMQL5による実装は以下の通りです。
def feature_2(adx_df, cci_df, price_df): """ Creates a 5D signal array with: 1. ADX > 25 (1 when above 25, else 0) 2. Lower low (1 when current low < previous low, else 0) 3. Higher CCI (1 when current CCI > previous CCI, else 0) 4. Higher high (1 when current high > previous high, else 0) 5. Lower CCI (1 when current CCI < previous CCI, else 0) """ # Initialize empty array with 5 dimensions feature = np.zeros((len(price_df), 5)) # Dimension 1: ADX above 25 feature[:, 0] = (adx_df['adx'] > 25).astype(int) # Dimension 2: Lower low feature[:, 1] = (price_df['low'] < price_df['low'].shift(1)).astype(int) # Dimension 3: Higher CCI feature[:, 2] = (cci_df['cci'] > cci_df['cci'].shift(1)).astype(int) # Dimension 4: Higher high feature[:, 3] = (price_df['high'] > price_df['high'].shift(1)).astype(int) # Dimension 5: Lower CCI feature[:, 4] = (cci_df['cci'] < cci_df['cci'].shift(1)).astype(int) # Set first row to 0 (no previous values to compare) feature[0, :] = 0 return feature
else if(Index == 2) { if(CopyBuffer(A.Handle(), 0, T, 2, _adx) >= 2 && CopyBuffer(C.Handle(), 0, T, 1, _cci) >= 2 && CopyRates(Symbol(),Period(),T, 2, _r) >= 2) { _features[0] = (_adx[0] > 25 ? 1.0f : 0.0f); _features[1] = (_r[0].low <= _r[1].low ? 1.0f : 0.0f); _features[2] = (_cci[0] > _cci[1] ? 1.0f : 0.0f); _features[3] = (_r[0].high > _r[1].high ? 1.0f : 0.0f); _features[4] = (_cci[0] < _cci[1] ? 1.0f : 0.0f); } }
この関数の出力は6次元ベクトルで構成され、その要素はADXが25を上回っているかどうか、安値が切り下がっているかどうか、CCIが切り上がっているかどうか、高値が切り上がっているかどうか、CCIが切り下がっているかどうかです。通常の強気セットアップは、ADXが25を上回っており、価格が安値を更新しながらもCCIによって示されるモメンタムが上昇している場合です。同様に弱気セットアップは、ADXが25を上回っているものの、価格が高値を更新する一方でCCIが下降を示している場合となります。
Feature_3
このパターンは上昇するADXとニュートラルゾーンにあるCCIを組み合わせたものです。CCIがニュートラルゾーンにある状態でのトレンド確認として機能します。これは上昇または下降トレンドにおける持続的なモメンタムに焦点を当てています。CCIがニュートラルゾーン(0から±100)にあるとき、価格は極端ではないが安定した動きをしていることを意味する場合が多いです。これは買われすぎ/売られすぎシグナルよりも安全で、誤ったエントリーのリスクを減らすことができます。ボラティリティの低い市場では「トレンドフォロー」の戦略として捉えることもできます。移動平均の整合性や価格構造と組み合わせることで、さらに安全性を高めることが可能です。PythonおよびMQL5による実装は以下の通りです。
def feature_3(adx_df, cci_df): """ Creates a 3D signal array with: 1. ADX rising (1 when current ADX > previous ADX, else 0) 2. CCI between 0 and +100 (1 when in range, else 0) 3. CCI between 0 and -100 (1 when in range, else 0) """ # Initialize empty array with 3 dimensions feature = np.zeros((len(adx_df), 5)) # Dimension 1: ADX rising feature[:, 0] = (adx_df['adx'] > adx_df['adx'].shift(1)).astype(int) # Dimension 2: CCI between 0 and +100 feature[:, 1] = (cci_df['cci'] > 0).astype(int) feature[:, 2] = (cci_df['cci'] < 100).astype(int) # Dimension 3: CCI between 0 and -100 feature[:, 3] = (cci_df['cci'] < 0).astype(int) feature[:, 4] = (cci_df['cci'] > -100).astype(int) # Set first row to 0 (no previous ADX value to compare) feature[0, :] = 0 return feature
else if(Index == 3) { if(CopyBuffer(A.Handle(), 0, T, 2, _adx) >= 2 && CopyBuffer(C.Handle(), 0, T, 1, _cci) >= 2) { _features[0] = (_adx[0] > _adx[1] ? 1.0f : 0.0f); _features[1] = (_cci[0] > 0 ? 1.0f : 0.0f); _features[2] = (_cci[1] < 100 ? 1.0f : 0.0f); _features[3] = (_cci[0] < 0 ? 1.0f : 0.0f); _features[4] = (_cci[1] > -100 ? 1.0f : 0.0f); } }
この関数は5次元ベクトルを生成し、その要素はADXが上昇しているかどうか、CCIが0を上回っているかどうか、CCIが+100未満であるかどうか、CCIが0未満であるかどうか、CCIが-100を上回っているかどうかです。従来の強気セットアップは、ADXが上昇しており、CCIが0を上回りつつ+100未満である場合です。反対の弱気パターンは、ADXが上昇しているものの、CCIが0未満かつ-100以上の場合となります。このパターンはADXが単に25を超えるだけでなく、上昇していることを強調しています。また、CCIのニュートラルゾーンは、Feature_0の極端なクロスオーバーとは異なり、トレンド初期の段階を狙いやすい傾向があります。
Feature_4
このパターンはADX>25の条件下で、CCIが極端値から回復するセットアップを利用します。これはフェイルスイング(失敗スイング)のセットアップです。CCIが極端なレベルを突破するものの継続できずに失敗するモメンタムトラップを捉えます。ADXを組み合わせることで、レンジ内の鞭打ち(ホイップソー)状況でないことを確認できます。このパターンは、反転や急激なスナップバックの前によく見られます。ボラティリティが高い取引セッションや、非農業部門雇用者数の発表などニュース駆動型イベントでの使用が最適です。ここで重要なのは、失敗スイングの日にピンバーを確認することで、より強い確認が得られる点です。PythonおよびMQL5による実装は以下の通りです。
def feature_4(adx_df, cci_df): """ Creates a 3D signal array with: 1. ADX > 25 (1 when above 25, else 0) 2. CCI dips below -100 then closes above it (1 when condition met, else 0) 3. CCI breaks above +100 then closes below it (1 when condition met, else 0) """ feature = np.zeros((len(cci_df), 5)) # Dimension 1: ADX above 25 feature[:, 0] = (adx_df['adx'] > 25).astype(int) # Dimension 2: CCI dips below -100 then closes above it feature[:, 1] = (cci_df['cci'].shift(1) < -100).astype(int) feature[:, 2] = (cci_df['cci'] > -100).astype(int) # Dimension 3: CCI breaks above +100 then closes below it feature[:, 3] = (cci_df['cci'].shift(1) > 100).astype(int) feature[:, 4] = (cci_df['cci'] < 100).astype(int) return feature
else if(Index == 4) { if(CopyBuffer(A.Handle(), 0, T, 2, _adx) >= 2 && CopyBuffer(C.Handle(), 0, T, 1, _cci) >= 2) { _features[0] = (_adx[0] > 25 ? 1.0f : 0.0f); _features[1] = (_cci[0] < -100 ? 1.0f : 0.0f); _features[2] = (_cci[1] > -100 ? 1.0f : 0.0f); _features[3] = (_cci[0] > 100 ? 1.0f : 0.0f); _features[4] = (_cci[1] < 100 ? 1.0f : 0.0f); } }
Feature_4関数は5次元ベクトルを生成し、その出力はバイナリ値(1または0)で構成されます。要素はADXが20未満であるかどうか、CCIが-100未満であったかどうか、CCIが現在-100を上回っているかどうか、CCIが+100を上回っていたかどうか、そしてCCIが現在+100未満であるかどうかです。典型的な強気シグナル(モメンタムシフトを狙う場合)は、ADXが20未満で、CCIが-100未満から-100を上回る動きを示す場合です。逆に弱気パターンは、ADXが20未満でトレンドが弱いことを示し、CCIが+100を上回っていたものの+100未満に下降することで、ポジティブモメンタムの低下を示す場合となります。
Feature_5
このパターンは単純にADX<20と極端なCCIスパイクを利用します。これは低ボラティリティ期におけるモメンタムスパイクの前兆として機能します。ADXが低い期間中にCCIの急上昇を観察することで、初期段階のブレイクアウトを捉えるのに役立ちます。また、トレンドが始まる前にポジションを構築することを目的としています。このパターンを実装する際は、スパイクの多くがフェイクになる可能性があるため、タイトなストップを設定することが推奨されます。他のインジケーター(Bollinger-Band-Squeezeやボリュームブレイクアウトなど)と組み合わせることで、さらに有利に活用できます。ただし、M15からH1などの短めの時間足に適しており、素早いモメンタムトレードに向いています。PythonおよびMQL5による実装は以下の通りです。
def feature_5(adx_df, cci_df): """ Creates a 3D signal array with: 1. ADX < 20 (1 when below 20, else 0) - indicates weak trend 2. CCI spikes above 150 (1 when condition met, else 0) - extreme overbought 3. CCI drops below -150 (1 when condition met, else 0) - extreme oversold """ # Initialize array feature = np.zeros((len(cci_df), 5)) # Dimension 1: ADX below 20 (weak trend) feature[:, 0] = (adx_df['adx'] < 20).astype(int) # Dimension 2: CCI spikes above 150 (sudden extreme overbought) # Using 2-bar momentum to detect "sudden" spikes feature[:, 1] = (cci_df['cci'] > 150).astype(int) feature[:, 2] = (cci_df['cci'].shift(1) < 130).astype(int) # Dimension 3: CCI drops below -150 (sudden extreme oversold) # Using 2-bar momentum to detect "sudden" drops feature[:, 3] = (cci_df['cci'] < -150).astype(int) feature[:, 4] = (cci_df['cci'].shift(1) > -130).astype(int) return feature
else if(Index == 5) { if(CopyBuffer(A.Handle(), 0, T, 2, _adx) >= 2 && CopyBuffer(C.Handle(), 0, T, 1, _cci) >= 2) { _features[0] = (_adx[0] < 20? 1.0f : 0.0f); _features[1] = (_cci[0] > 150 ? 1.0f : 0.0f); _features[2] = (_cci[1] < 130 ? 1.0f : 0.0f); _features[3] = (_cci[0] < -150 ? 1.0f : 0.0f); _features[4] = (_cci[1] > -130 ? 1.0f : 0.0f); } }
Feature_5関数は、MLPへの入力用に5次元ベクトルを生成します。捕捉されるシグナルはADXが20未満であるかどうか、CCIが+150を上回っているかどうか、CCIが以前+130未満であったかどうか、CCIが-150未満であるかどうか、そしてCCIが以前-130を上回っていたかどうかです。ADXが25を上回っているかどうかを指標として使用することで、強いトレンドが発生していることを確認します。また、CCIが極端値から回復する動きを捉え、一般的に反転やモメンタムの変化に焦点を当てています。
典型的な強気セットアップは、ADXが25を上回っており、CCIが以前+130未満だったものが現在+150に到達している場合です。同様に、弱気セットアップはADXが25を上回っており、CCIが以前-130をテストした後に現在-150になっている場合となります。
Feature_6
この特徴量は、ADXが40を下回る際のCCIクロスオーバーに関するものです。トレンドの勢いが弱まったことを示す「トレンドの消耗」シグナルとしての出口シグナルです。ADXが下降している場合、それはトレンドが弱まっていることを示します。さらにCCIが中立または逆方向にクロスすると、勢いが衰えていることを示唆します。このシグナルは、リスク低減のための目安として、トレーリングストップの調整や利益確定の目安として利用できます。また、ローソク足パターンと組み合わせることで、よりクリーンな出口ポイントを見つけることも可能です。しかし、このセットアップが発生している状況で新規取引を開始することは、一般的には推奨されません。PythonおよびMQL5による実装は以下の通りです。
def feature_6(adx_df, cci_df): """ Creates a 3D signal array with: 1. ADX crosses below 40 (1 when crosses down, else 0) 2. CCI crosses below +100 (1 when crosses down, else 0) 3. CCI crosses above -100 (1 when crosses up, else 0) """ # Initialize array with zeros feature = np.zeros((len(cci_df), 6)) # Dimension 1: ADX crosses below 40 feature[:, 0] = (adx_df['adx'] < 40).astype(int) feature[:, 1] = (adx_df['adx'].shift(1) >= 40).astype(int) # Dimension 2: CCI crosses below +100 feature[:, 2] = (cci_df['cci'] < 100).astype(int) feature[:, 3] = (cci_df['cci'].shift(1) >= 100).astype(int) # Dimension 3: CCI crosses above -100 feature[:, 4] = (cci_df['cci'] > -100).astype(int) feature[:, 5] = (cci_df['cci'].shift(1) <= -100).astype(int) return feature
else if(Index == 6) { if(CopyBuffer(A.Handle(), 0, T, 2, _adx) >= 2 && CopyBuffer(C.Handle(), 0, T, 1, _cci) >= 2) { _features[0] = (_adx[0] < 40? 1.0f : 0.0f); _features[1] = (_adx[1] < 40? 1.0f : 0.0f); _features[2] = (_cci[0] < 100 ? 1.0f : 0.0f); _features[3] = (_cci[1] >= 100 ? 1.0f : 0.0f); _features[4] = (_cci[0] > -100 ? 1.0f : 0.0f); _features[5] = (_cci[1] <= -100 ? 1.0f : 0.0f); } }
Feature_6関数は、6次元ベクトルを出力します。このベクトルは、ADXが現在40未満かどうか、ADXが以前40を超えていたかどうか、CCIが100未満かどうか、CCIが以前100を超えていたかどうか、CCIが-100を上回っているかどうか、そしてCCIが以前-100未満であったかどうか、という情報で構成されています。弱気の終了パターン(強気の終了パターンに相当するもの)は、ADXが40から40未満に下落し、かつCCIが以前-100未満であったが現在は-100を上回っている場合に発生します。逆に、強気の終了パターン(弱気の終了パターンに相当)は、ADXが40から下落し、さらにCCIが以前100超から現在100未満に低下した場合に該当します。これらは主にポジションの決済や決済警告パターンとして最適ですが、ここではテスト目的でのみエントリーシグナルとして含めています。
Feature_7
これは、ADXが25を超えている場合のCCIゼロラインクロスオーバーに関するものです。CCIがゼロラインをクロスした時点でのトレンド捕捉用の特徴量です。これは、CCIのゼロラインクロスが勢いの転換点として機能するためです。ADXも同時にトレンドの強さを確認しているため、このセットアップはトレンド中盤でのクリーンなエントリーを提供します。このパターンは、価格が高値を更新している場合や安値を更新している場合に特に信頼性が高くなる傾向があります。このパターンをシグナルとして複数のポイントでエントリーすることも検討に値します。時間軸の整合性やセッションごとのボラティリティの影響を確認するためにバックテストをおこなうことが推奨されます。PythonおよびMQL5による実装は以下の通りです。
def feature_7(adx_df, cci_df): """ Creates Feature-7 3D signal array with: 1. ADX > 25 (1 when above 25, else 0) - trend strength 2. CCI crosses above 0 (1 when bullish crossover, else 0) 3. CCI crosses below 0 (1 when bearish crossover, else 0) """ # Initialize array with zeros feature = np.zeros((len(cci_df), 5)) # Dimension 1: ADX above 25 (continuous signal) feature[:, 0] = (adx_df['adx'] > 25).astype(int) # Dimension 2: CCI crosses above 0 (bullish) feature[:, 1] = (cci_df['cci'] > 0).astype(int) feature[:, 2] = (cci_df['cci'].shift(1) <= 0).astype(int) # Dimension 3: CCI crosses below 0 (bearish) feature[:, 3] = (cci_df['cci'] < 0).astype(int) feature[:, 4] = (cci_df['cci'].shift(1) >= 0).astype(int) return feature
else if(Index == 7) { if(CopyBuffer(A.Handle(), 0, T, 2, _adx) >= 2 && CopyBuffer(C.Handle(), 0, T, 1, _cci) >= 2) { _features[0] = (_adx[0] > 25? 1.0f : 0.0f); _features[1] = (_cci[0] > 0? 1.0f : 0.0f); _features[2] = (_cci[1] <= 0 ? 1.0f : 0.0f); _features[3] = (_cci[0] < 0 ? 1.0f : 0.0f); _features[4] = (_cci[1] >= 0 ? 1.0f : 0.0f); } }
Feature_7関数も5次元の出力ベクトルを返します。このベクトルは、ADXが25を上回っているかどうか、CCIが0を上回っているかどうか、CCIが以前0以下であったかどうか、CCIが0未満であるかどうか、そしてCCIが以前0以上であったかどうか、という情報を記録します。これに基づく典型的なパターンでは、ADXが25を上回っており、CCIが以前0未満であったが現在は0を上回っている場合に強気シグナルが示されます。同様に、ADXが再び25を上回っており、CCIが以前0以上であったが現在0未満にある場合が弱気パターンに該当します。
Feature_8
番目の特徴量シグナルは、ADXが20未満でCCIの極端なプルバックを捉えるものです。これは、ADXによる強さフィルターと、CCIの買われすぎ/売られすぎの反転指標を組み合わせたものです。ADXによってフィルターされた典型的なレンジ反転パターンです。トレンドが弱い市場や方向感が不安定な市場では、CCI反転はより良く機能する傾向があります。理想的には、ADXが真に低い(20未満)場合のみ使用すべきで、トレンド相場での使用は避けるべきです。ボリンジャーバンドやRSIと組み合わせることで、マルチインジケーターによる確認が可能です。この反転パターンは、商品や通貨ペアなどの平均回帰型資産に最適である可能性があります。PythonおよびMQL5による実装は以下の通りです。
def feature_8(adx_df, cci_df): """ Creates a 3D signal array with: 1. ADX < 20 (1 when below 20, else 0) - weak trend 2. CCI rises from -200 to -100 (1 when condition met, else 0) - extreme oversold bounce 3. CCI falls from +200 to +100 (1 when condition met, else 0) - extreme overbought pullback """ # Initialize array with zeros feature = np.zeros((len(cci_df), 5)) # Dimension 1: ADX below 20 (weak trend) feature[:, 0] = (adx_df['adx'] < 20).astype(int) # Dimension 2: CCI rises from -200 to -100 # Using 2-bar lookback to detect the move feature[:, 1] = (cci_df['cci'] > -100).astype(int) feature[:, 2] = (cci_df['cci'].shift(1) <= -200).astype(int) # Dimension 3: CCI falls from +200 to +100 # Using 2-bar lookback to detect the move feature[:, 3] = (cci_df['cci'] < 100).astype(int) feature[:, 4] = (cci_df['cci'].shift(1) >= 200).astype(int) return feature
else if(Index == 8) { if(CopyBuffer(A.Handle(), 0, T, 2, _adx) >= 2 && CopyBuffer(C.Handle(), 0, T, 1, _cci) >= 2) { _features[0] = (_adx[0] < 20? 1.0f : 0.0f); _features[1] = (_cci[0] > -100? 1.0f : 0.0f); _features[2] = (_cci[1] <= -200 ? 1.0f : 0.0f); _features[3] = (_cci[0] < 100 ? 1.0f : 0.0f); _features[4] = (_cci[1] >= 200 ? 1.0f : 0.0f); } }
Feature_8の関数出力も、バイナリ値の5次元ベクトルです。これらは、ADXが20未満かどうか、CCIが-100を上回っているかどうか、CCIが以前-200未満であったかどうか、CCIが100未満であるかどうか、そしてCCIが以前200を上回っていたかどうか、という情報を記録します。これに基づく強気シグナルは、ADXが20未満で、CCIが以前-200をテストしていた後に-100を上回る場合に示されます。逆に、弱気パターンは、ADXが再び20未満で、CCIが以前200を上回っていた後に100未満に低下する場合に該当します。
Feature_9
最後の特徴量は、再びADXが25を上回る場合のCCI遅延クロスを用いたものです。このパターンは、反対方向のシグナル拒否やトラップフィルターを表しています。優勢トレンドに対する偽のブレイクやフェイクアウトを見抜くのに適しています。このセットアップでは、価格は反転を示唆することが多いものの、CCIがこの提案を拒否し、優勢トレンドを再確認します。トレンドトラップ防止に適しており、ローソク足での確認やフェイクアウト後の出来高減少と組み合わせることも可能です。過去に偽シグナルで損失を被ったトレーダーにとって、自信を持って取引できるフィルターとして有効です。PythonおよびMQL5による実装は以下の通りです。
def feature_9(adx_df, cci_df): feature = np.zeros((len(cci_df), 7)) cci = cci_df['cci'].values # Dimension 1 feature[:, 0] = (adx_df['adx'] > 25).astype(int) # Dimension 2: Below 0 then above +50 within 20 periods feature[:, 1] = (cci < 0).astype(int) feature[:, 2] = (np.roll(cci, 1) >= 0).astype(int) below_zero = (feature[:, 1]==1) & (feature[:, 2]==1) feature[:, 3] = 0 for i in np.where(below_zero)[0]: if i+20 < len(cci) and np.max(cci[i+1:i+21]) > 50: feature[:, 3] = 1 break # Dimension 3: Above 0 then below -50 within 20 periods feature[:, 4] = (cci > 0).astype(int) feature[:, 5] = (np.roll(cci, 1) <= 0).astype(int) feature[:, 6] = 0 above_zero = (feature[:, 4]==1) & (feature[:, 5]==1) for i in np.where(above_zero)[0]: if i+20 < len(cci) and np.min(cci[i+1:i+21]) < -50: feature[:, 6] = 1 break return feature
else if(Index == 9) { if(CopyBuffer(A.Handle(), 0, T, 2, _adx) >= 2 && CopyBuffer(C.Handle(), 0, T, 1, _cci) >= 21) { _features[0] = (_adx[0] > 25? 1.0f : 0.0f); _features[1] = (_cci[0] < 0? 1.0f : 0.0f); _features[2] = (_cci[1] >= 0 ? 1.0f : 0.0f); _features[3] = (_cci[ArrayMaximum(_cci,1,20)] > 50 ? 1.0f : 0.0f); _features[4] = (_cci[0] > 0? 1.0f : 0.0f); _features[5] = (_cci[1] <= 0 ? 1.0f : 0.0f); _features[6] = (_cci[ArrayMinimum(_cci,1,20)] < -50 ? 1.0f : 0.0f); } }
このパターンは、7次元ベクトルを生成します。このベクトルは、ADXが25を上回っているかどうか、CCIが0未満であるかどうか、以前のCCIが0を上回っていたかどうか、直前の20本のバーの期間内でCCIが50を上回ったことがあるかどうか、CCIが0を上回っているかどうか、以前のCCIが0未満であったかどうか、そして直前の20本のバーの期間内でCCIが-50を下回ったことがあるかどうか、という情報をマッピングします。
これらのシグナルが生成される典型的なパターンは次の通りです。強気シグナルの場合、ADXは25を上回っており、CCIは以前0未満に下落した後、20本のバー内で最大50までの範囲で0を上回る水準を再テストしている必要があります。同様に、弱気パターンは、CCIが直前に0を上回っていた後に0を下回り、さらに直前20本のバー内で少なくとも-50の低値を記録している場合に該当します。このパターンのテスト結果はフォワードウォーク検証に失敗したため共有していませんが、独自に検証できるようソースコード一式を末尾に添付しています。
結論
AADXとCCIの組み合わせパターンを入力とした教師あり学習型のMLPを検討しましたが、フォワードウォークにおける結果は混合的で、概ね芳しくありませんでした。これは、入力ベクトルをより連続的で離散的でないものにしようとした試みであり、第57回で取り上げた移動平均とストキャスティクスオシレーターのケースに似ています。この点が原因である可能性もありますが、今後の研究でもこのアプローチを継続して用いる予定です。また、他の機械学習手法がこれらの指標とどのように組み合わせて使用できるかも検討していきます。
名前 | 説明 |
---|---|
61_0.onnx | パターン0のMLP |
61_1.onnx | パターン1のMLP |
61_2.onnx | パターン2のMLP |
61_3.onnx | パターン3のMLP |
61_4.onnx | パターン4のMLP |
61_5.onnx | パターン5のMLP |
61_6.onnx | パターン6のMLP |
61_7.0nnx | パターン7のMLP |
61_8.onnx | パターン8のMLP |
61_9.onnx | パターン9のMLP |
61_x.mqh | パターン処理ファイル |
SignalWZ_61.mqh | シグナルクラスファイル |
wz_61.mq5 | インクルードファイルを示す、ウィザードで組み立てられたEA |
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/17910
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。





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