English
preview
知っておくべきMQL5ウィザードのテクニック(第70回): 指数カーネルネットワークにおけるSARとRVIのパターンの使用

知っておくべきMQL5ウィザードのテクニック(第70回): 指数カーネルネットワークにおけるSARとRVIのパターンの使用

MetaTrader 5統合 |
13 0
Stephen Njuki
Stephen Njuki

はじめに

前回の記事では、パラボリックSARインジケーター(SAR)と相対活力指数オシレーター(RVI: Relative Vigour Index)という補完的なペアを紹介しました。10種類のパターンをテストした結果、明確なフォワードウォークを達成できなかったパターンが3つありました。それが、インデックス1、2、6に該当するパターンです。パターンは0から9まで番号付けされており、これによりエキスパートアドバイザー(EA)で特定のパターンのみを利用するためのマップ値を簡単に計算できます。たとえば、パターンのインデックスが1であれば、パラメータ「PatternsUsed」を2^1=2に設定する必要があります。 

同様に、インデックスが2の場合は2^2=4と計算できます。このパラメータの最大値は1023で、10パターンすべてを利用する場合に対応します。0から1023の範囲で、2の累乗でない値を設定すると複数のパターンを組み合わせることを意味します。読者はEAに複数パターンを使用させる設定を試すことも可能です。しかし、過去の記事で提示した議論やテスト結果に基づき、本連載では現時点でこの方法は扱いません。 

過去の記事での約束通り、今回は教師あり学習を用いて、これまでクリーンなフォワードウォークを実現できなかったパターン1、2、6のシグナルを復活させることに挑戦します。これらのMQL5インジケーターシグナルに機械学習を適用するにあたり、ネットワークモデルのコーディングと学習を支援する目的でPythonを使用します。PythonはGPUを用いなくても効率的に処理をおこなえるためです。Pythonを使用する際には、MetaTraderのPythonモジュールを活用します。このモジュールにより、ログイン用のユーザー名とパスワードを提供することで、MetaTraderブローカーのサーバーに接続可能です。 

接続が確立されると、ブローカーの価格データにアクセスできるようになります。Pythonにはテクニカル指標用のライブラリも存在しますが、インストールが必要であったり、提供フォーマットがやや特殊な場合があります。幸い、SARやRVIのようなインジケーターを一から実装するのは比較的容易な作業です。したがって、まずはPython上でSARとRVIを実装することから始めます。


パラボリックSAR (SAR)関数

SARはトレンドフォロー型のインジケーターであり、価格の方向における反転フラクタルポイントを検出するために使用されます。ドットの配置によって優勢なトレンドが示され、ドットは潜在的なストップロス水準の位置を表します。したがって、上昇トレンドではドットが安値の下に表示され、下降トレンドではドットが高値の上に表示されます。SARを計算するための関数は、以下のように実装されます。

def SAR(df: pd.DataFrame, af: float = 0.02, af_max: float = 0.2) -> pd.DataFrame:
    """
    Calculate Parabolic SAR indicator and append it as 'SAR' column to the input DataFrame.
    
    Args:
        df (pd.DataFrame): DataFrame with 'high', 'low', 'close' columns
        af (float): Acceleration factor, default 0.02
        af_max (float): Maximum acceleration factor, default 0.2
        
    Returns:
        pd.DataFrame: Input DataFrame with new 'SAR' column
    """
    if not all(col in df.columns for col in ['high', 'low', 'close']):
        raise ValueError("DataFrame must contain 'high', 'low', 'close' columns")
    if af <= 0 or af_max <= 0 or af > af_max:
        raise ValueError("Invalid acceleration factors")
    if df.empty:
        raise ValueError("DataFrame is empty")

    result_df = df.copy()
    result_df['SAR'] = 0.0
    
    sar = df['close'].iloc[0]
    ep = df['high'].iloc[0]
    af_current = af
    trend = 1 if len(df) > 1 and df['close'].iloc[1] > df['close'].iloc[0] else -1
    
    for i in range(1, len(df)):
        prev_sar = sar
        high, low = df['high'].iloc[i], df['low'].iloc[i]
        
        if trend > 0:
            sar = prev_sar + af_current * (ep - prev_sar)
            if low < sar:
                trend = -1
                sar = ep
                ep = low
                af_current = af
            else:
                if high > ep:
                    ep = high
                    af_current = min(af_current + af, af_max)
        else:
            sar = prev_sar + af_current * (ep - prev_sar)
            if high > sar:
                trend = 1
                sar = ep
                ep = high
                af_current = af
            else:
                if low < ep:
                    ep = low
                    af_current = min(af_current + af, af_max)
        
        result_df.loc[i, 'SAR'] = sar
    
    return result_df

上記で実装した関数は、high(高値)、low(安値)、close(終値)の列を持つpandasデータフレームと、加速係数を指定する2つの浮動小数点パラメータを入力として受け取ります。最終的な出力では、計算された値を「SAR」という新しい列としてデータフレームに追加します。本関数の全体的なロジックは、極値と加速係数に基づいてトレンド方向を追跡し、SARを更新することです。

コードの詳細を見ると、まず元のデータを直接変更しないように、入力データフレームのコピーを作成しています。次に、高値と安値の列をNumPy配列に変換して効率的な計算をおこないます。その後、SARの値を格納する配列をゼロで初期化します。これはデータの整合性を保ち、SAR計算のための構造を準備するために重要です。また、「.copy()」を用いることで意図しない副作用を防止できます。反復計算においては、pandasのSeriesよりもNumPy配列を使う方が性能面で優れている点も利点です。

続いて、初期トレンドを上昇トレンド(trend = 1)として設定します。極値は初期の高値に割り当て、開始時点のSARは初期の安値を使用します。加速係数は入力値「af-start」から始めます。これはSAR計算の出発点を定義するために重要であり、初期状態を上昇トレンドと仮定する設計です。初期のSARとしてlow[0]の値を選ぶのは、SARが上昇トレンドにおけるストップロスの役割を担うためであり、af-startパラメータの重みで安値からの差し引きを調整し、市場のボラティリティに沿う形になっているからです。

次にSARの更新式に従って計算を進めます。現在のSARは、加速係数をスケーリング要素として極値に近づくように移動します。これはSARの核心的なロジックであり、トレンドの進行に伴う価格の放物線的な補正を反映しています。実装の際には、加速係数afを大きくしすぎないことが重要です。大きすぎるとトレンドが発生した際にステップが過剰に速くなり、またボラティリティの高い市場では数値的安定性の確保も必要となります。

続いてトレンド反転の判定ロジックを設定します。上昇トレンド時にSARが現在の安値を超えた場合、下降トレンドへの反転が発生します。この際、SARは直前の極値にリセットされ、新しい極値は現在の安値に設定されます。また、加速係数も初期値にリセットされます。 

これは重要です。なぜなら、トレンド反転の検出はSARにおける主要な機能であり、エントリーやエグジットのシグナルを提供するからです。この条件は、上昇トレンドにおいてSARが常に価格の下に位置し続けることを保証します。反転感度のテストは、af-incrementパラメータを調整することで実施できます。

上昇トレンドが継続する場合には、SARのバッファ値を更新し、さらに重要な点として加速係数をインクリメントします。SARは現在値または直近2本の安値の最小値に制約され、価格帯に食い込むことを防ぎます。また、高値が極値を上回った場合は極値を更新し、加速係数も上限(af-max)を超えない範囲で増加します。この維持ステップは重要です。なぜなら、SARが有効なストップロス水準として機能し続け、トレンドに沿ってのみ加速することを保証するからです。直近2本の安値を利用することで一定の堅牢性が得られますが、短期足では調整が必要となる場合もあります。

続いてSARの下降トレンド(弱気)ロジックを設定します。多くの点で、これは前述の上昇トレンド(強気)ロジックを反映するものです。要約すると、SARが現在の高値を下回った場合、上昇トレンドへ反転します。反転が発生しない場合、SARは現在値と直近高値の最大値に制約され、極値と加速係数は更新されます。このステップによってSARは、上昇トレンドと下降トレンドの両方を対称的に追跡できるようになります。トレンドロジックにおいて対称性を保つことは、バイアスを避けるために常に重要です。反転精度は過去データを用いた検証で確認可能です。

SARのロジックを扱い終えた後は、出力の割り当てに進みます。具体的には、計算済みのSAR値をデータフレームの新しい「SAR」列に追加し、それを返します。これは重要です。なぜなら、インジケーターをデータフレームに統合し、さらなる分析や可視化を可能にするからです。実際の運用時には、SAR列が価格データと整合しているかを検証することが望ましく、これはMatplotlibなどの可視化ライブラリを用いてSARのドットをプロットすることで確認できます。

まとめると、SARはトレンド相場においてストップロスレベルの設定や反転ポイントの特定に有効な指標です。ただし、レンジ相場やノイズの多い市場では効果が限定されます。パラメータ調整は必須ではないものの、一部の銘柄では加速係数の開始値および増分値を標準的な0.02や0.2から変更する必要があります。ブローカーの過去データを用いたバックテストで検証することが有用です。SARの主な制約は、急速な相場変動に対して遅れが生じ、多数の誤シグナルを発生させる点にあります。


相対活力指数(RVI)関数

RVIの主な目的は、トレンドの強さ、すなわちモメンタムを測定することです。これは、終値が当該期間の値幅に対してどの位置にあるかを比較し、その結果を移動平均で平滑化することでおこなわれます。RVIはオシレーターであり、モメンタムを確認することでトレンドを裏付けたり、ダイバージェンスを発見することに役立ちます。Pythonでの実装は次のとおりです。

def RVI(df: pd.DataFrame, period: int, signal_period: int) -> pd.DataFrame:
    """
    Calculate Relative Vigor Index (RVI) with signal line and append as 'RVI' and 'RVI_Signal' columns.
    
    Args:
        df (pd.DataFrame): DataFrame with 'open', 'high', 'low', 'close' columns
        period (int): Lookback period for RVI calculation
        signal_period (int): Lookback period for signal line calculation
        
    Returns:
        pd.DataFrame: Input DataFrame with new 'RVI' and 'RVI_Signal' columns
    """
    # Input validation
    if not all(col in df.columns for col in ['open', 'high', 'low', 'close']):
        raise ValueError("DataFrame must contain 'open', 'high', 'low', 'close' columns")
    if period < 1 or signal_period < 1:
        raise ValueError("Period and signal period must be positive")
        
    # Create a copy to avoid modifying the input DataFrame
    result_df = df.copy()
    
    # Calculate price change and range
    close_open = df['close'] - df['open']
    high_low = df['high'] - df['low']
    
    # Calculate SMA for numerator and denominator
    num = close_open.rolling(window=period).mean()
    denom = high_low.rolling(window=period).mean()
    
    # Calculate RVI
    result_df['RVI'] = num / denom * 100
    
    # Calculate signal line (SMA of RVI)
    result_df['RVI_Signal'] = result_df['RVI'].rolling(window=signal_period).mean()
    
    return result_df

上記のコードでは、一般的に、データ列「high」、「low」、「close」を持つpandasデータフレームを入力としています。加えて、シグナルバッファのSMA移動平均に用いる「period」も入力に含まれます。出力では、平滑化されたRVIと、そのRVIシグナルをデータフレームに新しい列として追加します。基本的なロジックとしては、RVIを「(close – open) / (high – low)」で計算します。これを4期間のSMAで平滑化し、さらにユーザー定義のSMA期間を用いてシグナルラインを生成します。

ここから行ごとの詳細を見ていきます。まず最初におこなうのは、入力データフレームのコピーを作成することです。これは元のデータを保護する手段であり、入力データフレームに意図しない変更を加えるのを防ぎます。コピー関数を使うことで簡単にこの目的を達成でき、不要な副作用を避けることができます。

次にRVIを計算します。RVIの生値は、価格変化を当該期間の値幅で割る形で設定されます。これは重要です。なぜなら、当日の値幅に対する価格変動の活力度を捉えることができ、RVIの基礎となるからです。なお、この時点でhighとlowが等しい場合にはゼロ除算が発生する可能性があるため注意が必要です。ただし、この問題は次のステップで処理されます。現段階の式では、日中のモメンタムを前提にしています。

ゼロ除算によるエラーを回避するために、まず無限大値をNaNに置き換えます(これはhighとlowが等しい場合に発生します)。その後、NaNをゼロに置換します。これは重要です。なぜなら、数値的な安定性を確保し、その後の計算でエラーを防ぐためです。ただし、これはあくまで単純な対処であり、ゼロ値がRVI出力に過度な歪みを与える場合には代替的な処理方法を検討する余地があります。

この処理を経て、RVIを平滑化し、シグナルラインを定義します。生のRVIには4期間のSMAを適用し、ノイズや前述のゼロ値による影響を軽減してインジケータRVIラインを作成します。次に、さらにユーザー定義のSMA期間を適用し、すでに平滑化されたRVIを追加的に平滑化してシグナルラインを生成します。このステップは極めて重要です。なぜなら、平滑化によってRVIの解釈性が高まり、シグナルラインによってクロスオーバーをより明確に捉えられるようになるからです。一般的には生RVIの平滑化に固定の4期間SMAが使われますが、シグナルラインに用いるSMAの期間は調整可能です。調整の必要がある場合には6~14期間の範囲が考えられます。

その後、出力を割り当てます。平滑化されたRVIとシグナルラインのそれぞれが、新しい列として出力データフレームに追加されます。これにより、RVIとそのシグナルバッファがさらなる分析や可視化に利用可能となります。たとえば両者をプロットすることでクロスオーバーを視覚的に確認できます。 

まとめると、RVIはモメンタムの代理指標としてトレンドの強さを確認するのに活用されます。また、価格が新高値を更新しているのにRVIがそうでないといったダイバージェンスを早期に特定することも可能です。さらに、シグナルラインとのクロスオーバーは強気・弱気の見通しを補強します。RVIのパラメータ調整は主にシグナルライン生成時のSMA期間に関わります。短い期間(6程度)はより速い頻度でシグナルを出し、長い期間(14程度)はより滑らかなシグナルを提供します。過去データに基づいてRVIクロスやダイバージェンスを検証することは、シグナルの信頼性を確認する上で重要です。RVIの主な制約は、二重の平滑化をおこなうために遅行性がある点です。また、ボラティリティの低い市場では有効なシグナルを生成しにくい傾向があります。 


SARとRVIの補完性

このインジケーターの組み合わせは、本連載で検討してきた多くのインジケーターと同様に、互いに補完し合う特性を持つことから選ばれています。SARは価格チャート上にプロットされるトレンドフォロー型のインジケーターです。一方でRVIは、価格チャートとは別のウィンドウにプロットされるオシレーターであり、主にモメンタムを測定するために用いられます。SARはストップロスの設定に適しており、RVIはトレンドの確認やダイバージェンスの検出に適しています。

両者ともPythonで実装されており、データ処理にはpandasが利用されます。計算速度の向上にはNumPyが用いられます。SARは反復計算を伴うためロジックが複雑で、特にトレンド反転処理のために計算量が増加します。一方、RVIはベクトル化された計算で比較的シンプルですが、ゼロ除算の可能性に注意する必要があります。そのため、この2つのインジケーターの性能は、SARにおける反復計算にNumPy配列を用いることで向上し、RVIにおけるベクトル化計算にはpandasデータフレームが有効に機能します。

この組み合わせに対して読者が追加実装できる施策としては、Pythonでのバックテスト(ziplineなどのライブラリを用いたもの)、特に裁量トレーダー向けの可視化としてSARドットプロットやRVIとそのシグナルラインの描画、さらにエラー処理として、pandasデータフレームに必要な列が存在するかやインジケーター期間が妥当であるかなどの入力の検証をコードに組み込むことが挙げられます。加えて、各インジケーターに対する拡張機能の追加も可能であり、たとえばRVIにダイバージェンス検出機能を組み込んだり、別の平滑化手法に変更したりすることも考えられます。


選択されたシグナルパターン

Feature_1

前回の記事で紹介したほとんどのパターンは、ある程度のフォワードウォーク(将来の価格変動の予測力)を提供できましたが、Pattern_1、Pattern_2、Pattern_6はこれに該当しませんでした。これまで私たちは、フォワードウォークを示すパターンに着目し、機械学習によってその優位性をさらに高められるかを検討してきました。しかし本記事では、そして今後も同様の記事においては、機械学習を用いて、予備的なテストの結果フォワードウォークできなかったパターンの成績を改善することを試みます。

PythonにおいてもMQL5と同様に、各パターンには、そのパターンが出現したか否かを示すシグナルを出力する関数を割り当てる必要があります。パターンが存在する場合はtrue、存在しない場合はfalseを返します。これらのtrue/falseの出力は、0と1のバイナリ信号として扱われます。これは、2つのインジケーターと価格データを組み合わせた取引戦略の特徴量を表現するためです。各関数は2列からなる特徴量配列を生成します。第1列(column-0)には強気シグナルが記録され、第2列(column-1)には弱気シグナルが記録されます。各列では、シグナルが存在しない場合は0、存在する場合は1をログとして記録します。

前回の記事ですでに紹介したように、Feature_1は、価格がSARの上/下に位置し、かつRVIが上昇/下降している場合にトレンドを検出します。この特徴量は、SARと価格の持続的な相補関係およびRVIのモメンタムに着目しています。Pythonでの実装は以下の通りです。

def feature_1(sar_df, rvi_df, price_df):
    """
    
    """
    feature = np.zeros((len(sar_df), 2))
    
    feature[:, 0] = ((price_df['low'].shift(1) > sar_df['SAR'].shift(1)) &
                     (price_df['low'] > sar_df['SAR']) &
                     (rvi_df['RVI'] > rvi_df['RVI'].shift(1))).astype(int)
    
    feature[:, 1] = ((price_df['high'].shift(1) < sar_df['SAR'].shift(1)) &
                     (price_df['high'] < sar_df['SAR']) &
                     (rvi_df['RVI'] < rvi_df['RVI'].shift(1))).astype(int)
    
    
    feature[0, :] = 0
    feature[1, :] = 0
    
    return feature

まず、コード内で最初におこなうのは、入力データフレームの長さに合わせて[データ件数 × 2]の形状を持つゼロで初期化された配列を作成することです。この2つの「列」は、最終的にそれぞれ強気シグナルと弱気シグナルを格納することを目的としています。このステップは重要です。なぜなら、バイナリ信号を保持するためのクリーンな状態を確保し、未初期化値による予期せぬ計算結果を防ぐことができるからです。np.zerosを使用することで効率的に初期化が行え、数値計算時の不具合も防止できます。NaNで埋める方法もありますが、np.zerosの方が安全です。

初期化が終わったら、次に強気シグナルの値を設定します。列0では、前回の安値が現在のSARより上でかつ持続的な強気トレンドにあり、さらに現在の安値もSARを上回り、かつ現在のRVIが前回より高く上昇モメンタムを示している場合に1を割り当てます。これらすべての条件を用いることが重要です。なぜなら、SARによるトレンド確認とRVIによるモメンタム確認の両方によって、強い強気トレンドを捉えられるからです。この際、shift(1)によってデータを正しく整列させることも重要です。データの欠損があると大きなエラーにつながる可能性があります。また、astype(int)を用いることで、ブール値を0/1のバイナリ形式に変換できます。

強気の列を設定した後は、弱気シグナルの設定に移ります。弱気シグナルの条件は強気と対称的であるため、強気と弱気の両方に同時に1が入るベクトルが生成される可能性は低くなります。また、特徴量ベクトルのサイズを2に制限しているのは、「完全な強気」または「完全な弱気」の状態のみを捉えるためです。以前に、より長いベクトルで各インジケーターのシグナルを個別に扱うテストもおこないましたが、フォワードテストでの性能は限定的なテストウィンドウでのみ良好であり、この理由から2サイズ(強気/弱気)の設定に留めています。

したがって、2列目では、前回の高値が前回のSARより下で、現在の高値も現在のSARより下にある場合に弱気シグナルを設定します。さらに、現在のRVIが前回より低下している場合に、モメンタムが低下していることを示します。これらの条件が統合されることで、SARとRVIの補完性により、強い弱気トレンドを定義できます。強気のロジックと同様に、データ整列の確認やshiftによるNaN値の処理も必要です。

強気および弱気シグナルが定義された後は、出力配列に対してshiftによる比較を考慮した調整をおこないます。具体的には、最初の2行には0を割り当て、無効なシグナルが発生するのを防ぎます。これは重要です。なぜなら、ラグデータの初期部分で不完全なデータから誤ったシグナルが出るのを防ぐためです。ラグデータを扱う際には、初期行を無効化することが堅牢性を保つ上で常に有効なルールです。この設定に基づき、学習およびフォワードウォークを含むテスト結果は以下の通りです。

r1

c1

教師あり学習を導入することで、パフォーマンスが向上していることが確認されています。以下は、教師あり学習を用いなかった場合のPattern_1のグラフです。

c1_old

Feature_2

テストした3つ目のパターンも、フォワードウォークでは有効性を示しませんでした。この特徴量パターンは、前回の記事で述べたように、価格がSARを横断し、かつRVIに一定のモメンタムが見られる場合、潜在的な反転を検出します。さらに、追加の方向性情報として終値を用いることで、価格の動きの方向も取り入れています。Pythonでの実装は次のとおりです。

def feature_2(sar_df, rvi_df, price_df):
    """

    """
    feature = np.zeros((len(sar_df), 2))
    
    feature[:, 0] = ((price_df['high'].shift(1) <= sar_df['SAR'].shift(1)) &
                     (price_df['low'] >= sar_df['SAR']) &
                     (rvi_df['RVI'] > rvi_df['RVI'].shift(1)) &
                     (price_df['close'].shift(1) > price_df['close'])).astype(int)
    
    feature[:, 1] = ((price_df['low'].shift(1) >= sar_df['SAR'].shift(1)) &
                     (price_df['high'] <= sar_df['SAR']) &
                     (rvi_df['RVI'].shift(1) > rvi_df['RVI']) &
                     (price_df['close'] > price_df['close'].shift(1))).astype(int)
    
    
    feature[0, :] = 0
    feature[1, :] = 0
    
    return feature

ここでもまず、強気および弱気の両方のシグナルを受け取る形状のゼロで初期化された配列を作成します。前述の通り、これは初期値でNaNを含まないことを保証し、関数から予測可能な出力を得るために重要です。一定の初期化は、その後の処理において不可欠です。 

次に、強気シグナルの条件を定義します。これらすべての条件を満たした場合に1を記録し、そうでなければ0を割り当てます。そのため、ゼロで初期化された配列を用いることは、誤ったシグナルを防ぐ安全な方法となります。具体的には、前回の高値が前回のSAR以下であり、かつ現在の安値が現在のSAR以上で反転が発生している場合、さらにRVIが上昇しており、最後に終値が下落してダイバージェンスを確認できる場合に1を設定します。これにより、価格がSARを上抜けしつつRVIのモメンタムが伴う強気の反転を捉えることが可能です。このパターンには複数の条件があり、すべてが確認された場合にのみtrueまたは1が割り当てられます。 

弱気シグナルについては、特徴量配列の第2インデックスに設定します。弱気条件は、前回の安値が前回のSAR以下、現在の高値が現在のSAR以下、RVIが下降、かつ、終値が上昇してダイバージェンスの設定を確認できるということです。Pattern_2は弱気のダイバージェンスを捉えるため、データフレーム全体でデータの整合性を保つことが重要です。また、shiftによる比較を考慮して、特徴量配列の初期行には0を割り当て、ラグデータの欠損による無効なシグナルを防ぎます。これはラグを含む時系列特徴量で一般的におこなわれる標準的な処理です。

理論的には、このFeature_2は先に示したパターン1と組み合わせて使用することも可能です。両者はやや補完的な関係にあるからです。Feature_1は純粋なトレンドフォロー型システムであるのに対し、Feature_2はダイバージェンスポイントでの反転を捉えるため、スイングトレードを取り入れることでより堅牢なシステムを構築できます。しかし、過去の記事でも議論した通り、異なるパターンを組み合わせるとシグナルが早期に相殺される可能性があるため、こうしたシステムのテストには十分な過去データを用い、フォワードウォークも明確である必要があります。このパターンを学習させテストした結果の、学習期間とテスト期間の両方にわたるレポートは以下の通りです。再び、機械学習を用いることでより良好な結果が得られていることが確認されています。

r2

c2

以下は、教師あり学習を用いなかった場合のパターン2のグラフです。

c2_old

Feature_6

前回の記事で紹介した10パターンのうち、最後にテストする特徴量はPattern_6に基づいています。この特徴量では、確認の指標として単なるRVIのモメンタムではなく、RVIとそのシグナルラインのクロスオーバーを利用します。SARとRVIのクロスオーバーイベントを伴う強いトレンドシグナルに着目しており、Pythonでの実装は次のとおりです。

def feature_6(sar_df, rvi_df, price_df):
    """

    """
    feature = np.zeros((len(sar_df), 2))
    
    feature[:, 0] = ((price_df['low'].shift(1) > sar_df['SAR'].shift(1)) &
                     (price_df['low'] > sar_df['SAR']) &
                     (rvi_df['RVI'] > rvi_df['RVI_Signal']) &
                     (rvi_df['RVI'].shift(1) < rvi_df['RVI_Signal'].shift(1))).astype(int)
    
    feature[:, 1] = ((price_df['high'].shift(1) < sar_df['SAR'].shift(1)) &
                     (price_df['high'] < sar_df['SAR']) &
                     (rvi_df['RVI'] < rvi_df['RVI_Signal']) &
                     (rvi_df['RVI'].shift(1) > rvi_df['RVI_Signal'].shift(1))).astype(int)
    
    feature[0, :] = 0
    feature[1, :] = 0
    
    return feature

まず、これまでと同様に、出力を格納するためのゼロで初期化されたNumPy配列を作成します。この初期化は重要であり、他の関数と同様にNaNを含まないクリーンな出力を保証します。また、後続の処理で安定した配列構造を維持することにも役立ちます。

初期化が完了したら、強気シグナルの条件を定義します。強気シグナルは配列の最初のインデックス(0)に記録されます。具体的には、前回の安値がSARを上回っており、かつ現在の安値も現在のSARを上回っている場合、さらに現在のRVIがそのシグナルラインを上回っており、かつ前回のRVIがシグナルラインを下回っていた場合に1を記録します。

強気パターンを定義した後は、弱気シグナルを設定します。弱気シグナルは、前回の高値が前回のSARを下回り、かつ現在の高値も現在のSARを下回っている場合に成立します。さらに現在のRVIがシグナルラインを下回り、前回のRVIがゼロバウンドを上回っていた場合に1を記録します。このパターンを他のパターン同様に学習およびテストした結果、以下のテストレポートが得られました。再び、フォワードウォークが良好であることが確認されています。

r6

c6

以下は、教師あり学習を用いなかった場合のPattern_6のグラフです。

c6_old


ネットワーク

テストにおいて、上記で選択した3つのパターンには、1次元畳み込みニューラルネットワークを適用しました。本ネットワークは3つの畳み込み層を持ち、それぞれの層でフィルター数とカーネルサイズを指数的に増加させています。続いて、最大値プーリングと平滑化操作をおこないます。最後に、全結合層を通じて最終出力を導きます。これをPythonで次のように実装します。

class ExpConv1DNetwork(nn.Module):
    def __init__(self, input_length, input_channels=1, base_filters=16, base_kernel_size=3, exp_base=2):
        super(ExpConv1DNetwork, self).__init__()
        self.conv_layers = nn.ModuleList()
        self.pool_layers = nn.ModuleList()
        
        for i in range(3):
            filters = int(base_filters * (exp_base ** i))
            kernel_size = int(base_kernel_size * (exp_base ** i)) | 1
            self.conv_layers.append(
                nn.Conv1d(
                    in_channels=input_channels if i == 0 else int(base_filters * (exp_base ** (i-1))),
                    out_channels=filters,
                    kernel_size=kernel_size,
                    padding='same'
                )
            )
            # Use smaller kernel size for pooling to prevent size reduction to 0
            self.pool_layers.append(nn.MaxPool1d(kernel_size=2, ceil_mode=True))
        
        self.flatten_size = self._get_flatten_size(input_length, input_channels, base_filters, exp_base)
        self.flatten = nn.Flatten()
        self.dense1 = nn.Linear(self.flatten_size, 128)
        self.relu = nn.ReLU()
        self.dropout = nn.Dropout(0.5)
        self.dense2 = nn.Linear(128, 1)
    
    def _get_flatten_size(self, input_length, input_channels, base_filters, exp_base):
        current_length = input_length
        current_channels = input_channels
        
        for i in range(3):
            current_channels = int(base_filters * (exp_base ** i))
            # Update length after pooling
            current_length = (current_length + 1) // 2  # Ceiling division for ceil_mode=True
        
        return current_channels * current_length
    
    def forward(self, x):
        for conv, pool in zip(self.conv_layers, self.pool_layers):
            x = self.relu(conv(x))
            x = pool(x)
        x = self.flatten(x)
        x = self.relu(self.dense1(x))
        x = self.dropout(x)
        x = self.dense2(x)
        return x

このネットワークは、バッチサイズ、入力チャンネル数、入力長さの形状を持つ1次元入力を想定しています。また、base-filters、base-kernel-size、exp-baseといったパラメータによってさらにカスタマイズ可能です。出力は単一のスカラーで、0から1の範囲に学習されます。0は弱気を予測し、1は強気を予測します。このクラスコードの行ごとの詳細な解析は、本クラスが改良されたシグナルパターンの性能に直接影響するため非常に重要ですが、記事の長さの関係上ここでは割愛します。しかし、今後同様の記事では注意して取り扱う予定です。


結論

今回、GBP/CHFペアのわずか2年間という限定データウィンドウでフォワードテストで有効性を示さなかったシグナルパターンに対し、教師あり学習を導入することによる潜在的なメリットを検討しました。非常に制約のある条件下でのテスト結果では、前回の記事と比較して明らかに改善が見られることが確認されました。これは励みになる結果です。しかし、常に言えることですが、ここで提示したアイデアを実際に適用する前には、読者自身による独立した、より徹底的な検証が必要です。 


名前 説明
WZ-70.mq5 ヘッダにウィザードアセンブリで使用されるファイルが示されたファイル
SignalWZ_70.mqh MQL5ウィザードで使用されるカスタムシグナルクラスファイル
70_1.onnx Pattern_1のエクスポート済みネットワークモデル
70_2.onnx Pattern_2のエクスポート済みネットワークモデル
70_6.onnx Pattern_6のエクスポート済みネットワークモデル

なお、始めて読む読者のために、この添付コードはMQL5ウィザードを使用してEAに組み込むことを目的としています。これをおこなう方法に関するチュートリアルはこちらにあります。

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/18433

添付されたファイル |
WZ-70.mq5 (6.92 KB)
SignalWZ_70.mqh (14.34 KB)
70_1.onnx (153.86 KB)
70_2.onnx (153.86 KB)
70_6.onnx (153.86 KB)
初心者からエキスパートへ:MQL5を使ったアニメーションニュース見出し(I) 初心者からエキスパートへ:MQL5を使ったアニメーションニュース見出し(I)
MetaTrader 5ターミナルでの取引において、ニュースのアクセス性は非常に重要な要素です。数多くのニュースAPIが存在するものの、多くのトレーダーはそれらを効果的に取引環境に統合することに課題を抱えています。本記事では、ニュースを最も必要とする場所であるチャート上に直接表示する、効率的なソリューションの構築を目指します。その実現のために、APIソースからのリアルタイムニュースを監視し、表示するNews Headline EA(エキスパートアドバイザー)を作成します。
MQL5で自己最適化エキスパートアドバイザーを構築する(第8回):複数戦略分析 MQL5で自己最適化エキスパートアドバイザーを構築する(第8回):複数戦略分析
複数の戦略をどのように組み合わせれば、最も効果的に強力なアンサンブル戦略を構築できるでしょうか。本記事では、3種類の戦略を1つの取引アプリケーションに統合する方法について検討します。トレーダーは通常、ポジションのエントリーとクローズに特化した戦略を用いますが、私たちは機械がこのタスクをより優れた形で遂行できるかどうかを探ります。最初の議論として、ストラテジーテスターの機能と、本タスクで必要となるオブジェクト指向プログラミング(OOP)の原則に慣れていきます。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
MQL5で取引管理者パネルを作成する(第12回):FX取引計算ツールの統合 MQL5で取引管理者パネルを作成する(第12回):FX取引計算ツールの統合
取引において重要な数値を正確に計算することは、すべてのトレーダーにとって欠かせません。本記事では、強力なユーティリティであるFX取引計算ツールを取引管理パネルに組み込み、マルチパネル型の取引管理者システムの機能をさらに拡張する方法について解説します。リスク、ポジションサイズ、潜在的な利益を効率的に算出することは、取引の精度を高めるうえで非常に重要です。この新機能は、パネル内でこれらの計算をよりスムーズかつ直感的におこなえるよう設計されています。本記事では、MQL5を用いた高度な取引パネル構築の実践的な応用例を紹介します。