アルゴリズム取引におけるニューロシンボリックシステム:シンボリックルールとニューラルネットワークを組み合わせる
ニューラルシンボリックシステム入門:ルールとニューラルネットワークを組み合わせる原理
コンピュータに株式市場での取引方法を説明しようとしているところを想像してください。一方には、三尊やダブルボトムなど、すべてのトレーダーに馴染みのある古典的なルールやパターンがあります。多くの人が、これらのパターンをMQL5でEAとして実装しようとした経験があると思います。しかし、市場は生きた有機体のようなものであり、常に変化し続けるため、厳密なルールはしばしば機能しなくなります。
もう一方にはニューラルネットワークがあります。これは強力で人気がありますが、その意思決定は時に完全に不透明です。LSTMネットワークに過去のデータを入力すれば、かなりの精度で予測をおこなうことができますが、その背後にある理由は謎のままです。取引の世界では、誤った判断が実際のお金の損失につながります。
数年前、私自身も自作の取引アルゴリズムでこのジレンマに直面しました。古典的なパターンは誤検出が多く、ニューラルネットワークは時に論理性のない驚くべき予測を出しました。そして、ある瞬間に気づいたのです。「両方のアプローチを組み合わせたらどうだろう?」と。明確なルールをシステムの枠組みとして使い、ニューラルネットワークを市場の現在の状態を考慮する適応的な仕組みとして使うのです。
こうして、アルゴリズムトレーディングのためのニューラルシンボリックシステムというアイデアが生まれました。これは、すべての古典的パターンとルールを知り尽くした熟練トレーダーが、市場の微妙な変化や関係性を考慮して適応するシステムです。このようなシステムは、明確なルールという「骨格」と、柔軟性と適応性を与えるニューラルネットワークという「筋肉」を持っています。
本記事では、私のチームがどのようにPythonでそのようなシステムを開発したのかを説明し、古典的なパターン分析を現代の機械学習手法とどのように組み合わせるかを示します。アーキテクチャを基本構成から複雑な意思決定メカニズムまで順に解説し、実際のコードとテスト結果も共有します。
古典的な取引ルールとニューラルネットワークが出会う世界に飛び込む準備はできましたか。それでは始めましょう。
取引における象徴的なルール:パターンとその統計
まず単純なことから始めましょう。マーケットパターンとは何でしょうか。古典的なテクニカル分析では、チャート上に現れる特定の形、たとえば「ダブルボトム」や「フラグ」を指します。しかし、取引システムをプログラムとして考えると、より抽象的に考える必要があります。コード上では、パターンとは価格変動のシーケンスであり、1は上昇、0は下落としてバイナリ形式で表現します。
単純に見えるかもしれませんが、まったくそんなことはありません。この表現は、私たちに非常に強力な分析ツールを与えてくれます。シーケンス [1, 1, 0, 1, 0] を例にとってみましょう。これは単なる数字の集合ではなく、符号化されたミニトレンドです。Pythonでは、次のようなシンプルで効果的なコードを使ってこのようなパターンを検索できます。
pattern = tuple(np.where(data['close'].diff() > 0, 1, 0))
しかし、本当の魔法は統計分析を始めたときに起こります。各パターンについて、次の3つの主要なパラメータを計算することができます。
- 頻度:パターンが過去に出現した回数
- 勝率:パターン出現後に価格が予測した方向へ動いた割合
- 信頼度:頻度と勝率の両方を考慮する複合的な指標
実際の例として、私の経験では、EURUSDのH4チャートにおけるパターン [1, 1, 1, 0, 0] は、年間200回以上の出現頻度で68%の勝率を示しました。魅力的に聞こえますよね。しかし、ここで注意すべきは過剰最適化の罠です。
そのため、私たちは次のような動的信頼度フィルターを追加しました。
reliability = frequency * winrate * (1 - abs(0.5 - winrate))
この式は驚くほどシンプルですが、非常に優れています。頻度と勝率の両方を考慮するだけでなく、異常に高い効率を示すパターン(統計的に偶然である可能性が高いもの)を自動的に減点する仕組みになっています。
パターンの長さも別の重要な要素です。短いパターン(3~4バー)は出現頻度が高い一方でノイズが多く長いパターン(20~25バー)はより信頼性が高いものの希少です。最もバランスが良いのは5~8バー程度の長さです。ただし、特定の銘柄では12バーのパターンで非常に良い結果を得たこともあります。
もう1つの重要なパラメータは予測期間です。私たちのシステムではforecast_horizonというパラメータを使い、何本先のバーまでの値動きを予測するかを定義します。経験的に、6に設定すると、予測精度と取引機会のバランスが最も良いことが分かりました。
しかし、本当に興味深いのは、異なる市場環境において同じパターンがまったく異なる動きを見せるという点です。ボラティリティが異なったり、取引時間帯が違ったりすると、同一パターンの振る舞いが劇的に変わります。だからこそ、単純な統計分析はあくまで出発点にすぎません。ここでニューラルネットワークの出番となります。この続きについては、次のセクションで説明します。
市場データ分析のためのニューラルネットワークアーキテクチャ
それでは、システムの「頭脳」にあたるニューラルネットワークを見ていきましょう。私たちは多くの実験を経て、時系列データ処理にLSTM層を、そしてパターンの統計的特徴を処理するために全結合層を組み合わせたハイブリッド型のアーキテクチャに落ち着きました。
なぜLSTMなのでしょうか。その理由は、市場データが単なる数値の集まりではなく、各値が前後の値と密接に関連した連続的なシーケンスであるという点にあります。LSTMネットワークは、このような長期的な依存関係を捉えるのに非常に優れています。私たちのネットワークの基本構造は次のようになっています。
model = tf.keras.Sequential([ tf.keras.layers.LSTM(256, input_shape=input_shape, return_sequences=True), tf.keras.layers.Dropout(0.4), tf.keras.layers.LSTM(128), tf.keras.layers.Dropout(0.3), tf.keras.layers.Dense(64, activation='relu'), tf.keras.layers.Dense(1, activation='sigmoid') ])
Dropout層に注目してください。これは過学習に対する私たちの防御手段です。システムの初期バージョンではDropoutを使用していませんでしたが、その結果、ネットワークは過去データ上では完璧に動作する一方で、実際の市場ではまったく機能しませんでした。Dropoutは学習中にランダムに一部のニューロンを無効化することで、ネットワークがより堅牢なパターンを探索するよう促します。
重要なポイントのひとつは入力データの次元構造です。input_shapeパラメータは、次の3つの要素によって決定されます。
- 分析ウィンドウのサイズ(この例では10タイムステップ)
- 基本特徴量の数(価格、出来高、テクニカル指標など)
- パターンから抽出された特徴量の数
その結果、入力データは(batch_size, 10, features)という形状のテンソルになります。ここでfeaturesはすべての特徴量を合計したものです。このフォーマットこそ、最初のLSTM層が期待する入力形式です。
また、最初のLSTM層のreturn_sequences=Trueパラメータにも注目してください。これは、各タイムステップごとに出力のシーケンス全体を返すことを意味します。これにより、2層目のLSTMが時間的なダイナミクスについてより詳細な情報を取得できるようになります。ただし、2層目のLSTMは最終状態のみを出力し、その結果を全結合層に渡します。
全結合層(Dense層)は「インタープリター」として機能します。LSTMによって検出された複雑なパターンを、具体的な意思決定へと変換します。最初のDense層ではReLU活性化関数を用いて非線形な依存関係を処理し、最終層ではsigmoid活性化関数を用いて価格が上昇する確率を出力します。
モデルのコンパイルプロセスにも特筆すべき点があります。
model.compile( optimizer='adam', loss='binary_crossentropy', metrics=['accuracy', tf.keras.metrics.Precision(), tf.keras.metrics.Recall()] )
私たちは、Adamオプティマイザを使用しています。これは市場価格のような非定常データに対して高い効果を発揮することが実証されています。損失関数には二値交差エントロピーを採用しており、価格変動の方向を予測するという私たちの二値分類タスクに最適です。また、複数のメトリクスを併用することで、単なる精度だけでなく、偽陽性や偽陰性の観点からも予測品質を評価できるようにしています。
開発中には、さまざまなネットワーク構成を試しました。局所的なパターンを識別するために畳み込み層(CNN)を追加したり、アテンションメカニズムを導入したりといった実験もおこないました。しかし最終的には、アーキテクチャのシンプルさと透明性こそが最も重要であるという結論に至りました。ネットワークが複雑になればなるほど、その意思決定の解釈は困難になります。そして取引の分野では、システムの判断ロジックを理解できることが極めて重要なのです。
ニューラルネットワークへのパターン統合:入力データの拡張
ここからが最も興味深い部分です。つまり、古典的なパターン分析とニューラルネットワークをどのように「融合」させるかという点です。これは単に特徴量を連結するだけの処理ではなく、データの事前処理と分析を体系的に組み合わせた仕組みそのものです。
まず、基本となる入力データセットから始めましょう。各時点ごとに、次の要素を含む多次元の特徴量ベクトルを構成します。
base_features = [ 'close', # Close price 'volume', # Volume 'rsi', # Relative Strength Index 'macd', # MACD 'bb_upper', 'bb_lower' # Bollinger Bands borders ]
しかし、これはまだ始まりにすぎません。私たちのシステムにおける主要な革新点は、ここにパターン統計情報を追加することです。各パターンについて、次の3つの主要な指標を計算します。
pattern_stats = {
'winrate': np.mean(outcomes), # Percentage of successful triggers
'frequency': len(outcomes), # Occurrence frequency
'reliability': len(outcomes) * np.mean(outcomes) * (1 - abs(0.5 - np.mean(outcomes))) # Reliability
}
特に最後の指標である信頼度には注意を払う必要があります。これは私たち独自の設計であり、頻度や勝率だけでなく、統計的な「不自然さ」も考慮に入れています。もし勝率が100%に近すぎたり、値が過度に変動していたりする場合には、この信頼度指標は低下します。
このデータをニューラルネットワークに統合する際には、特別な注意が必要です。
def prepare_data(df): # We normalize the basic features using MinMaxScaler X_base = self.scaler.fit_transform(df[base_features].values) # For pattern statistics we use special normalization pattern_features = self.pattern_analyzer.extract_pattern_features( df, lookback=len(df) ) return np.column_stack((X_base, pattern_features))
異なるパターンサイズの問題を解決します。
def extract_pattern_features(self, data, lookback=100): features_per_length = 5 # fixed number of features per pattern total_features = len(self.pattern_lengths) * features_per_length features = np.zeros((len(data) - lookback, total_features)) # ... filling the feature array
各パターンは、その長さに関係なく固定次元のベクトルに変換されます。これにより、アクティブなパターン数が変動するという問題を解決し、ニューラルネットワークが一定の入力次元で動作できるようになります。
一方で、市場コンテキスト(状況)の考慮は別の重要なテーマです。私たちは、市場の現在の状態を表すために、以下のような特別な特徴量を追加します。
market_features = {
'volatility': calculate_atr(data), # Volatility via ATR
'trend_strength': calculate_adx(data), # Trend strength via ADX
'market_phase': identify_market_phase(data) # Market phase
}
これにより、システムは異なる市場状況に適応できるようになります。たとえば、高ボラティリティの期間には、パターンの信頼度に対する要求を自動的に引き上げます。
もう1つの重要なポイントは、欠損データの処理です。実際の取引では、特に複数の時間軸を扱う場合に、欠損データはよく発生する問題です。私たちは、以下のような方法を組み合わせてこれを解決しています。
# Fill in the blanks, taking into account the specifics of each feature df['close'] = df['close'].fillna(method='ffill') # for prices df['volume'] = df['volume'].fillna(df['volume'].rolling(24).mean()) # for volumes pattern_features = np.nan_to_num(pattern_features, nan=-1) # for pattern features
その結果、ニューラルネットワークは完全かつ一貫性のあるデータセットを受け取ることができます。ここでは、古典的なテクニカルパターンが基本的な市場指標を自然に補完しています。これにより、システムは長年検証されてきたパターンと、学習過程で発見された複雑な関係性の両方に基づいて判断できる、独自の優位性を持つことになります。
意思決定システム:分析からシグナルまで
次に、システムが実際にどのように意思決定をおこなうかについて説明します。一旦ニューラルネットワークやパターンの話は置いておきましょう。最終的には、エントリーするかどうかという明確な判断が必要です。そして、エントリーする場合は取引量も決定しなければなりません。
基本的なロジックはシンプルです。2つのデータストリームを使用します。1つはニューラルネットワークによる予測、もう1つはパターン統計です。ニューラルネットワークは価格上昇/下降の確率を提供し、パターン統計がその予測を補強するか、反証するかを判断します。しかし、細部にこそ複雑さがあります。
ここで、システム内部で何が起こっているのかを見てみましょう。
def get_trading_decision(self, market_data): # Get a forecast from the neural network prediction = self.model.predict(market_data) # Extract active patterns patterns = self.pattern_analyzer.get_active_patterns(market_data) # Basic check of market conditions if not self._market_conditions_ok(): return None # Do not trade if something is wrong # Check the consistency of signals if not self._signals_aligned(prediction, patterns): return None # No consensus - no deal # Calculate the signal confidence confidence = self._calculate_confidence(prediction, patterns) # Determine the position size size = self._get_position_size(confidence) return TradingSignal( direction='BUY' if prediction > 0.5 else 'SELL', size=size, confidence=confidence, patterns=patterns )
まず最初に確認するのは、基本的な市場状況です。特に難しい話ではなく、常識的な判断に基づきます。
def _market_conditions_ok(self): # Check the time if not self.is_trading_session(): return False # Look at the spread if self.current_spread > self.MAX_ALLOWED_SPREAD: return False # Check volatility if self.current_atr > self.volatility_threshold: return False return True
次におこなうのは、シグナルの整合性の確認です。ここで重要なのは、すべてのシグナルが完全に一致する必要はないという点です。主要な指標同士が矛盾しなければ十分です。
def _signals_aligned(self, ml_prediction, pattern_signals): # Define the basic direction ml_direction = ml_prediction > 0.5 # Count how many patterns confirm it confirming_patterns = sum(1 for p in pattern_signals if p.predicted_direction == ml_direction) # At least 60% of patterns need to be confirmed return confirming_patterns / len(pattern_signals) >= 0.6
最も難しいのは、シグナルの信頼度の算出です。数多くの実験とさまざまなアプローチの分析を経て、私たちはニューラルネットワーク予測の統計的信頼度と検出されたパターンの過去のパフォーマンスの両方を考慮する複合指標を用いる方法に行き着きました。
def _calculate_confidence(self, prediction, patterns): # Baseline confidence from ML model base_confidence = abs(prediction - 0.5) * 2 # Consider confirming patterns pattern_confidence = self._get_pattern_confidence(patterns) # Weighted average with empirically selected ratios return (base_confidence * 0.7 + pattern_confidence * 0.3)
この意思決定アーキテクチャは、古典的なテクニカル分析と機械学習の能力が有機的に補完し合うハイブリッドアプローチの有効性を示しています。システムの各コンポーネントが最終的な意思決定に寄与し、多層のチェック機構によって、さまざまな市場状況に対する必要な信頼性と柔軟性が確保されています。
結論
古典的パターンとニューラルネットワーク分析を組み合わせることで、質的に新しい成果を得ることができます。ニューラルネットワークは市場の微妙な関係性を捉え、長年検証されてきたパターンは取引判断の基本構造を提供します。私たちのテストでは、このアプローチは純粋なテクニカル分析や単独の機械学習よりも一貫して優れた結果を示しました。
重要な発見のひとつは、シンプルさと解釈可能性の重要性です。私たちは意図的に複雑なアーキテクチャを避け、透明で理解しやすいシステムを選びました。これにより、取引判断の制御が容易になるだけでなく、市場状況の変化に応じて迅速に調整することも可能になります。多くの人が複雑さを追い求める世界において、シンプルさこそが私たちの競争優位性であることが証明されました。
私たちの経験が、古典的取引と人工知能の交差点で可能性を探求する方々に役立つことを願っています。結局のところ、最も興味深く実用的な解決策は、こうした学際的な領域から生まれることが多いのです。実験を続けてください。ただし、取引には万能の解決策は存在しないことを忘れないでください。存在するのは、ツールを絶えず改善し続ける道だけです。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/16894
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
初級から中級まで:テンプレートとtypename(III)
学習中にニューロンを活性化する関数:高速収束の鍵は?
循環単為生殖アルゴリズム(CPA)
量子コンピューティングと取引:価格予測への新たなアプローチ
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
論文「アルゴリズム取引におけるニューロシンボリック・システム:シンボリック・ルールとニューラルネットワークの組み合わせ」発表:
著者:Yevgeniy Koshtenko