共和分株式による統計的裁定取引(第6回):スコアリングシステム
はじめに
共和分株式を用いた完全自動化された統計的裁定取引パイプラインにおいて、最初のマイルストーンに到達しました。共和分検定用のPythonクラスを開発し、初期データベースを構築して進化させ、戦略をテストするためのエキスパートアドバイザー(EA)も準備できています。いくつかの補助コードを組み合わせれば、EAに戦略パラメータを提供するモデルを、データベースからデータを取得してリアルタイムで更新することも可能です。平均的な個人投資家向けの統計的裁定フレームワークを実装するまでには長い道のりでした。ナスダック上場企業全体から、半導体関連の数十銘柄までをステップバイステップでスクリーニングするプロセスも定義しています。しかし、スコアリングシステムはまだ存在しません。
このスクリーニングワークフローを踏まえ、今度はスコアリングシステムを定義し、バックテストをおこなう準備が整いました。前回の記事では、可能な基準の概要のみを示していました。
- 共和分の強さ
- 共和分ベクトルの数(ランク)
- ポートフォリオ比率の安定性
- 妥当なスプレッド
- 回帰までの時間
- 流動性
- 取引コスト
これらの基準に加え、前回の概要には含まれていなかった2つの基準も追加できます。
- 時間足(データ頻度)
- ルックバック期間
ここで問題となるのは、各基準の値をどのように決め、どのように重み付けするかです。最終的には各基準に加点方式でスコアを付与する形になるのは明らかですが、どの基準を他の基準より重視するのか、あるいは順位付けの際にどの順序で評価するのかを決める必要があります。どの基準を先に評価し、どの基準をランキングの最後に置くかを明確に定義することで、モデルの理論的説明とテスト可能で実用的な取引システムとの間のギャップを埋められます。
本記事では、排除基準と分類基準という2つの主要な基準タイプでスコアリングシステムを提案します。
排除基準はバスケットを失格にする基準です。他の基準で高得点であっても、この基準で設定した閾値を満たさなければ、バスケットはランキングの最後に回されます。言わば必須条件です。
分類基準はランキングシステムを構成する基準です。
さらに、これら2つの主要基準とは別に、取引目的によって排除基準にも分類基準にもなり得る2つの基準があります。これらを戦略的基準と呼びます。
スコアリングシステム
排除基準
排除基準を設けることで、実際に取引可能なバスケットのみを評価対象にすることができます。統計的な観点から見て完璧なバスケットであっても、効率的に取引できなければ価値はありません。そのため、これらの基準には大きな負の重みを与えることを検討すべきです。
では、なぜ前回の記事で見たスクリーニングフィルターの段階で、これらを完全に除外しないのでしょうか。それは、これらの基準を決定づける値が固定されたものではないからです。時間の経過とともに変化する可能性があり、実際に変化することがほとんどです。だからこそ、まず初期スクリーニングフィルターが存在します。この段階では、市場、セクター、業種など、戦略全体に適合する証券を選択します。その後、第二段階として、それらの証券をスコアリングフィルタに通します。スクリーニングステップが数千の候補の中から選別するのに対し、スコアリングステップは、比較的少数の候補の中でどれが最も優れているかを定義します。定義上、スコアリングは「今この瞬間に取引すべき理想的なペアまたはバスケットは何か」を示さなければなりません。
流動性:流動性は不可欠な基準です。なぜなら、流動性の低い株式を含むスプレッドは取引コストが高くなり得るからです。これは統計的裁定取引に限った話ではなく、一般的な取引においても同様です。一定期間における出来高は、証券の流動性を示す最も分かりやすい指標です。出来高が大きいほど流動性は高くなり、スプレッドとも直接的な関係があります。
Metatrader 5のドキュメントによると、「ボリューム」は、Forexのような分散型市場と、株式のような集中型市場とでは意味が異なります。
「Forex市場では、『ボリューム』とは、一定期間内に発生したティック(価格変動)の数を意味します。一方、株式では、、『ボリューム』は約定した取引量(契約数または金額)を意味します。」
証券の流動性を評価する際には、出来高とそれに関連するスプレッドだけでなく、板情報も考慮すべき場合があります。ただし、それは非常に大きな取引量を扱う場合の話であり、本フレームワークの対象である個人投資家の統計的裁定取引には当てはまりません。そのため、本稿ではDOMについては扱いません。
取引コスト:取引コストに影響を与える要因は、取引所、ブローカー、そしてバスケットに含まれる資産によって異なりますが、少なくとも以下の3つがあります。
- 手数料および/または各種費用
- スプレッド
- 想定されるスリッページ
Apple (AAPL)、Microsoft (MSFT)、Nvidia (NVDA)のような流動性の高いナスダック銘柄では、執筆時点でスプレッドは通常1株あたり0.01ドル程度です。しかし、流動性の低い銘柄では、より広くなる可能性があります。本スコアリングシステムでは、最も流動性の高い銘柄でこの値を仮定し、流動性が低くなるにつれて、最大で0.05ドルまで拡大すると想定できます。
私の理解が正しければ、多くのブローカーにおいて、最大許容スリッページ(直近価格からの乖離)を指定できるのはプロフェッショナル口座のみであり、個人投資家向けの本統計的裁定フレームワークの対象外です。この制約があるため、バックテストには想定スリッページを含める必要があります。ここでは、1注文あたり取引金額の0.05%を仮定します。
最後に、手数料および/または各種費用についてですが、多くのブローカーが手数料無料取引を提供しているとはいえ、規制費用や取引所手数料が発生する場合があります。バックテストでは、1株あたり0.005ドルという推定値を用いることができます。
バックテストを簡略化するため、往復取引(買い+売り)の総コストを取引金額の0.1%と近似します。この単純化により、スプレッド、想定スリッページ、手数料および費用をまとめて表現できます。ただし、これらはあくまで私自身の経験に基づく近似であり、実際の条件は大きく異なる可能性があります。そのため、この点については、常に各自で十分なデューデリジェンスをおこなう必要があることを明確にしておきます。
分類基準
分類基準は、バスケット候補を適切に順位付けするための基準です。
共和分の強さ:系列がどの程度強く共和分しているかを評価するために、ジョハンセン検定の統計量(トレース統計量および最大固有値統計量)を使用できます。検定統計量が強いほど、長期的な関係がより信頼できることを示します。
共和分ベクトルの数(ランク):共和分ベクトルの数を考慮することで、構築しようとしているシステムの望ましい複雑さを評価できます。
- ランク=1は、単一の安定したスプレッドを示します。監視が容易です。
- ランク>1は、複数のスプレッドが存在する可能性を示します。柔軟性は高まりますが、その分、システムはより複雑になります。
ポートフォリオ比率の安定性:異なるサンプルウィンドウ間でヘッジ比率が大きく変動する関係は避けるべきです。そのような挙動は、関係性が脆弱である可能性を示唆します。
回帰までの時間:スプレッドがどれだけ速く均衡に戻るかを評価するために、平均回帰の半減期を計算できます。
戦略的基準
データ頻度(時間足)とルックバック期間は、スコアリングの定量的な基準というよりは、戦略的な選択に近いものです。どちらが「より優れている」というよりも、私たちの取引戦略との整合性が重要です。 長期戦略では日次データを用いることがあり、短期的な機会を狙う戦略では分単位データを用います。そのため、この基準を他の基準と同じように「重み付け」するわけではありません。代わりに、事前選別のフィルターとして使用します。まず、戦略が長期か短期かを決定し、それに応じたデータ頻度でバスケットをスクリーニングします。データ頻度はシグナルのタイムリーさ、ノイズ、取引コストに影響を与えるため、他の基準のスコアリングにも影響を与える基礎的な決定です。
データ頻度(日次、日中データ):長期戦略では日次データが一般的です。日中データは短期間のチャンスを捉えることができます。高頻度データはシグナルのタイムリーさを改善しますが、ノイズや取引コストが増加します。
安定性のためのルックバック期間:相関や共和分検定に用いる過去データの期間の選択は重要です。短いルックバック期間は市場環境の変化に素早く適応できますが、一時的で不安定な関係を捉えてしまう可能性があります。長いルックバック期間はより安定した推定を提供しますが、経済状況が変わると古くなる可能性があります。
実務的なアプローチとしては、複数のルックバック期間(例:60日、120日、250日)でテストし、共和分結果の安定性を比較評価します。
また、ローリングウィンドウを用いてヘッジ比率を常に更新し、サンプル外でも関係性が維持されるかをテストすることも可能です。
ここで注意すべきは、上記のルックバックウィンドウの安定性評価と、戦略的にルックバック期間を選択することを混同しないことです。ここでは異なるルックバック期間間での安定性を評価するのではなく、EAによる平均、標準偏差、およびパラメータ更新頻度の基礎として使用するルックバック期間を選択します。特に、時間足とルックバック期間の間に本質的な結びつきはありません。日足で取引しつつ、分単位のルックバック期間を使用することは取引上全く問題ありません。実際、この組み合わせには、後ほど紹介するように予想外の機会が潜んでいます。
スコアリングワークフローの確立
流動性
これらの基準が整ったところで、スクリーニング済みバスケットにスコアを付ける準備が整いました。まず、戦略全体に適さない銘柄、すなわち流動性が低い銘柄を排除することから始めます。これは排除基準です。しかし、事前に「ある期間においてX量/契約未満で取引される銘柄は除外する」と明言できない限り、流動性は相対的な指標となります。高/中/低流動性の基準や、ある期間において許容可能かどうかの閾値を定義する必要があります。
流動性、および結果として妥当なスプレッドは、本連載でここまで例に挙げているナスダック銘柄の場合には問題にならないはずです。これらは比較的流動性の高い銘柄が多いためです。しかし、この比較的高流動性の銘柄群の中でも、より流動性の高い銘柄とそうでない銘柄が存在します。さらに、将来的にナスダック以外の銘柄をスクリーニング/スコアリングしたり、特定の理由で小型株を扱う場合もあります。その場合、流動性は必ず考慮すべき要素となります。
ここでは、すでにフィルタリング済みの半導体セクター銘柄を使って進めます。執筆時点では、データベースに63銘柄が保存されています。(もし本連載を追っていない場合は、スクリーニング後にどのようにここまで到達したかを理解するため、少なくとも前回の記事に目を通してください。)
![]()
図1:Metaeditorのデータベースインターフェースとsymbolテーブルの件数のスクリーンショット
先に述べた通り、ナスダック銘柄は比較的流動性の高い証券と見なされます。これは、平均的な個人トレーダー向けに開発した統計的裁定フレームワークの特徴を設計する際に選定した主な理由です。しかし、半導体セクターの銘柄の中で、どの銘柄が最も取引されているか、また最も取引量が少ない銘柄はどれかを知りたいところです。
![]()
図2:Metaeditorのデータベースインターフェースで最も流動性の高い半導体銘柄を表示したスクリーンショット
ここに特に驚きはありません。予想通り、Nvidiaは半導体セクター内で最も取引量が多く、最も流動性の高い銘柄であり、執筆時点で世界でも最も取引される銘柄のひとつです。しかし、この情報だけでは十分ではありません。各銘柄の平均値を取得し、相対的に評価する必要があります。
![]()
図3:Metaeditorのデータベースインターフェースで半導体銘柄の平均取引量を表示したスクリーンショット
なお、Forex市場の場合は、実際の出来高の代わりにティックボリュームを使用します。
ここで実用的なものが得られました。SQLに慣れていない場合でも、次のシンプルなクエリは覚えておくと便利です。今後、小さなバリエーションで何度も使用することになるでしょう。
SELECT
s.symbol_id,
s.ticker, -- symbol name
ROUND(AVG(md.real_volume)) AS avg_real_volume_int
FROM market_data AS md
JOIN symbol AS s
ON md.symbol_id = s.symbol_id
GROUP BY s.symbol_id, s.ticker
ORDER BY avg_real_volume_int DESC; AVGとROUNDはSQLiteの組み込み関数です。AVGは実取引量の平均を計算します。戻り値は浮動小数点値です。 ROUND関数はその結果を最も近い整数に丸めます。
JOIN句は各market_data行をsymbolメタデータに紐付けるために使用し、symbol IDから銘柄名を取得できるようにします。最後に、GROUP BY句により、結果が銘柄ごとに1行だけ表示されます。
共和分の強さ
半導体セクター銘柄を流動性順にランク付けしたところで、次は共和分の強さを確認する時期です。前回のテスト(相関、共和分、定常性)と同様に、この共和分強度のランキング結果も、後で再利用や比較ができるように別のテーブルに保存します。将来的に機械学習を実装する際に役立つ可能性のあるデータは、すべて保存しておくことを忘れないでください。
新しいcoint_rankテーブルは以下の形式になっています。

図4:coint_rankテーブルのフィールドとデータ型を示すスクリーンショット
テーブル定義は、これまでおこなってきたものと非常に似ています。主キーにはテストのタイムスタンプを使用し続けます。タイムスタンプはこの領域における自然キーの一種だからです。 STRICT SQLiteテーブルを使用して、誤ったデータ型によるトラブルを避けます。共和分メソッドや時間足のように、事前に許容値が分かっているTEXTフィールドでは、通常通りチェックを維持します。 下記の定義には、理解を助けるためのコメントが付けられています。
-- Cointegration ranking results (STRICT mode with lookback and timestamp uniqueness) CREATE TABLE coint_rank ( tstamp INTEGER PRIMARY KEY, -- Unix timestamp (UTC seconds since epoch) timeframe TEXT CHECK ( timeframe IN ( 'M1','M2','M3','M4','M5','M6','M10','M12','M15','M20','M30', 'H1','H2','H3','H4','H6','H8','H12','D1','W1','MN1' ) ) NOT NULL, lookback INTEGER NOT NULL, -- Lookback period (calendar days) assets TEXT NOT NULL, -- e.g., 'AAPL, MSFT' or 'AAPL, MSFT, GOOG' method TEXT CHECK ( method IN ('Engle-Granger', 'Johansen') ) NOT NULL, strength_stat REAL, -- Engle-Granger: ADF statistic; Johansen: trace statistic p_value REAL, -- Only for Engle-Granger eigen_strength REAL, -- Johansen eigenvalue indicator rank_score REAL, -- Combined ranking score -- Allow multiple test runs for the same combo but unique by timestamp CONSTRAINT coint_rank_unique_combo UNIQUE (timeframe, lookback, assets, tstamp) ) STRICT; CREATE INDEX idx_coint_rank_timeframe_lookback ON coint_rank (timeframe, lookback);
tstamp / timeframe / lookback / assetsの組み合わせに対してUNIQUE制約を設定しています。これにより、各タイムスタンプごとに、これらの組み合わせに対して一度だけ共和分ランク評価をおこなうことが保証されます。注意点として、tstamp自体はテーブルの主キーとしてすでに一意であるため、UNIQUE制約に含めなければ、timeframe‑lookback‑assetsの組み合わせに対して1回しか挿入できなくなり、UNIQUE制約に違反してしまいます。しかし、タイムスタンプを組み合わせに含めることで、同じ組み合わせを異なるタイムスタンプで保存でき、共和分の安定性を時間足上で確認することが可能になります。その後、結果を日付ごとにフィルタリングしたり集計したりできます。これは、各実行が自己完結しており、タイムスタンプ付きで追跡可能なバックテストログを作成する上で非常に有用です。最後に、主にtimeframeとlookbackでクエリをおこなうため、これら2列にインデックスを作成して処理を高速化しています。
エングル=グレンジャー検定の共和分の強さ
前回までで、エングル=グレンジャー検定やジョハンセン検定の仕組みについて詳しく説明していますので、ここでは繰り返しません。ポイントだけ押さえましょう。これらは、銘柄ペアまたはグループが長期的にどれだけ一緒に動くかを測定するものです。つまり、資産間の価格関係が時間とともにどれほど維持されるか、共和分の強さとして評価します。strength_statフィールドには、エングル=グレンジャー検定およびジョハンセン検定の数値結果を格納します。
エングル=グレンジャー検定では、このフィールドにスプレッドに対するADF (Augmented Dickey-Fuller)統計量が格納されます。前回の記事で、ADFによるスプレッドの定常性テストの詳細と解釈は説明済みですので、ここでは省略します。覚えておくべきことは、2つの資産間のスプレッドが平均値の周りで変動し、漂流しないかを確認することです。つまり、このフィールドはスプレッドの定常性、すなわち共和分の強さを示します。
前回までの論理に沿えば、ADF統計量が小さいほど共和分の強さは強く、エングル=グレンジャー検定のstrength_statがより負の値であるほどスプレッドは安定していると理解できます。
ジョハンセン検定の共和分の強さ
3銘柄以上のバスケットをテストする場合は、ジョハンセン検定を使用します。この検定は、時間とともに安定するポートフォリオ比率(線形結合)を構築するために使用します。これまでの記事でこの検定の仕組みと結果の解釈方法は説明済みですので、ここでは繰り返しません。ジョハンセン検定では、strength_statフィールドにトレース統計量が格納されます。トレース統計量が大きいほど、これらの資産の組み合わせが長期的な安定均衡を形成している証拠が強いことを示します。つまり、strength_statが大きいほど、このバスケットが均衡して一緒に動いている証拠が強いのです。
添付されたPythonスクリプトには、どのタイミングでどの検定を使用するか、またその結果を取得する方法に関するロジックが含まれています。
try: if len(basket) == 2: y0, y1 = sub_prices.iloc[:, 0], sub_prices.iloc[:, 1] score, p_value, _ = coint(y0, y1) results.append({ "assets": basket, "method": "Engle-Granger", "strength_stat": float(score), "p_value": float(p_value), "eigen_strength": None }) else: johansen_res = coint_johansen(sub_prices, det_order, k_ar_diff) max_eig = float(max(johansen_res.eig)) results.append({ "assets": basket, "method": "Johansen", "strength_stat": float(max(johansen_res.lr1)), "p_value": None, "eigen_strength": max_eig })
以下に、ロジックを視覚的に表現した図を示します。

図5:strength_statフィールドがどのように埋められ、簡略化して解釈されるかを示すフローチャート
固有値の強さ
エングル=グレンジャー検定は、strength_statフィールドに格納するADF統計量しか提供しませんが、ジョハンセン検定には、スコアリングに役立つもう1つの指標があります。それが、eigen_strengthフィールドに保持する固有値の強さです。3銘柄以上のバスケットの場合、ジョハンセン検定はこれらの資産の線形結合(ポートフォリオ比率となる)を見つけ、長期的に安定した関係を形成します。各線形結合は1つの共和分ベクトルに対応し、それぞれに固有値が付随します。固有値は、その特定の共和分関係がどれほど強いか、および資産がその均衡方向に沿ってどれだけ密接に動くかを示します。固有値が大きいほど、資産間の長期的な関係はより安定しており、均衡からの小さな逸脱もより早く修正されます。
添付スクリプトでは、検出されたすべての共和分ベクトルの中で最大の固有値を使用しています。
else: johansen_res = coint_johansen(sub_prices, det_order, k_ar_diff) max_eig = float(max(johansen_res.eig)) results.append({ "assets": basket, "method": "Johansen", "strength_stat": float(max(johansen_res.lr1)), "p_value": None, "eigen_strength": max_eig })
ジョハンセンのトレース統計量(strength_stat)は、グループ全体がどれだけ一体となって動くか、つまり関係性の安定度を示します。一方、最大固有値(eigen_strength)は、グループ内で最も安定した関係がどれほど安定しているかを示します。したがって、eigen_strengthが高い場合、グループは非常に密接に動き、eigen_strengthが低い場合は、たとえ共和分関係にあっても容易に分散しやすいことを意味します。
ランクスコア
フィールドは、私たちのスコアリングスクリプトが生成する最終的な要約指標であり、どのバスケットが最も強く、取引対象として注目に値するかを決定します。共和分テストを実行した後、エングル=グレンジャーペアから得られた結果であれ、ジョハンセンバスケットから得られた結果であれ、すべての結果を比較可能にする単一の数値が必要です。この数値がrank_scoreです。rank_scoreは、共和分の強さを比較可能な単一指標として考えることができ、すべての候補関係を強い順から弱い順にソートランク付けできます。
添付のスクリプトでは、以下の部分で計算されています。
df = pd.DataFrame(results) df["rank_score"] = df.apply(lambda row: -row["p_value"] if row["method"] == "Engle-Granger" else row["eigen_strength"], axis=1) df = df.sort_values("rank_score", ascending=False).reset_index(drop=True)
エングル=グレンジャー検定(ペア)の場合、テストはp値を返します。p値が小さいほど、共和分の強さを示す証拠が強いことを意味します。最終ランキングでより強いペアが上位に来るように、p値にはマイナスを付けます
rank_score=−p_value
例えば、p値が0.01の場合、rank_scoreは-0.01となり、-0.20のような弱いペアよりも上位にソートされます。p値が小さいほど、rank_scoreは高く(負の値が小さく)なり、そのペアはより共和分的であるとランク付けされます。
ジョハンセン検定(バスケット)の場合、テストは単一のp値ではなく、複数の固有値を返します。スクリプトでは、その中で最大の固有値をeigen_strengthに格納し、それをそのままrank_scoreとして使用します。
rank_score=eigen_strength
固有値が大きいほどrank_scoreは高くなり、より強いバスケットを意味します。
分析を簡潔かつ容易にするために、rank_scoreはすべてのペアとバスケットを同一の尺度で比較可能にします。どの検定(エングル=グレンジャー検定かジョハンセン検定か)から得られた結果かは考慮せず、関係性の強さのみを評価します。SQLを使えば、結果は自由にフィルタリングできます(データベースを使っている主な理由でもあります)。rank_scoreを降順でソートすれば、最も安定しており、統計的に有意な価格関係がリストの上位に表示されます。
![]()
図6:Metaeditorのデータベースインターフェースで、半導体セクター銘柄を含むcoint_rankテーブルを表示したスクリーンショット
ここでは、前述の図3で示した流動性の高い上位10銘柄を選択し、2銘柄、3銘柄、4銘柄のすべての組み合わせについて、添付の共和分ランキングスクリプトを実行しました。いずれもH4の時間足を使用しています。この時間足は、私たちの戦略的基準の一部です。上の図6は、ルックバック期間60日で実行した最初の結果を示しています。Metaeditor上で青くハイライトされている部分は、ランキングがジョハンセン検定の結果からエングル=グレンジャー検定の結果へと切り替わる位置を示しています。結果セットはすべて表示されており、まだフィルタは適用されていません。
この単一の実行だけで、375件の結果が得られています(実行前、このテーブルは空でした)。これは、10銘柄を2、3、4銘柄ずつにまとめた場合の組み合わせ数(順列ではない)に相当します。自身でテストをおこなう際には、この数を必ず意識してください。なぜなら、ここにはいわゆる「組み合わせ爆発」の問題があるからです。この理由から、デモ目的としての戦略的基準でもありますが、ここでは単一の時間足のみを選択しました。また、1バスケットあたりの銘柄数を4に制限した理由でもあります。今後は複数のルックバック期間でテストする必要があります。複数のルックバック期間を比較しなければ、このランキングは有用性が低く、少なくとも非常に脆弱だからです。さらに多くの銘柄や時間足を含めると、計算能力、時間、データ量のすべてにおいて、はるかに多くのリソースが必要になります。この点を念頭に置き、目的に合わせてリソース内で数を調整するようにしてください。その逆ではありません。
次に、同じテーブルの上位部分を見てみましょう。
![]()
図7:Metaeditorのデータベースインターフェースで、coint_rankテーブルの上位を表示したスクリーンショット
表の上位がジョハンセン検定の結果によって占められていることに注意してください。これは予想通りです。というのも、ジョハンセン検定はエングル=グレンジャー検定よりも強い関係性を見つける傾向があるからです。ただし、ここではサンプルが非常に限定的である点も考慮する必要があります。使用しているのは、時間足が1つ(H4)、ルックバック期間が1つ(60日)のみです。
しかし、このように限定されたサンプルであっても、図7で3本の線で強調されているようないくつかのパターンが現れ始めています。4銘柄からなる3つのバスケットに、合計6銘柄が関与していますが、そのうち1銘柄を入れ替えても結果にはほとんど影響がありません。これは、バスケット内で変わらない他の3銘柄(NVDA、MU、ASX)が、共和分の本当の源である可能性を示唆しています。ところが、その3銘柄だけで構成されたバスケットは、その5行下に現れており、他の銘柄を含めた場合よりも共和分の強さが弱くなっています。なぜでしょうか。本来であれば、これら3銘柄が共和分の強固な基盤になると予想してもおかしくありません。正直なところ、現時点ではその答えは分かりません。しかし、この10銘柄をさらに分析していく過程で、このようなパターンや、さらに予想外のパターンが数多く見つかることは間違いないでしょう。そしてその多くは、データ分析の助けがなければ見つけられなかった取引可能性のあるパターンになるはずです。これこそが統計的裁定取引の魅力であり、より広く言えばデータ駆動型トレーディングの美しさです。
これらのパターンを探す時間は、今後、手動分析でも、プロット結果の可視化によっても十分に取ることができます。ここではまず、当初の目的である、このワークフローを自動化し、EAにとっての「単一の真実の情報源」となる戦略テーブルにデータを供給することに集中しましょう。
H4の時間足において、最も流動性の高い10銘柄を対象とし、1か月から6か月までのルックバック期間でcoint_rankテーブルが埋まれば、rank_scoreを降順でソートすることで、最も共和分の強いバスケットが結果の最上位に表示されることになります。
ここで思い出してください。私たちの初期の取引仮説は、NVDAと共和分関係にある銘柄を探すことでした。では、それを確認してみましょう。

図8:Metaeditorのデータベースインターフェースでcoint_rankテーブルを表示したスクリーンショット
ここでは、SQLiteのLIKE演算子を使用して、NVDAを任意の位置に含むペアおよびバスケットのみをフィルタリングしています。
「LIKE演算子はパターンマッチングによる比較をおこないます。LIKE演算子の右側のオペランドにはパターンが指定され、左側のオペランドには、そのパターンと照合される文字列が指定されます。LIKEパターン内のパーセント記号(%)は、文字列中の0文字以上の任意の文字列にマッチします。」(詳細はSQLiteの公式ドキュメントを参照してください。)
バスケットではなくペアを取引したい場合は、共和分検定の手法でフィルタリングし、エングル=グレンジャー検定のみを指定します。(同様に、上のクエリに対してジョハンセン検定のみを指定してフィルタリングすることも可能でしたが、前述のとおり、ジョハンセン検定の結果はエングル=グレンジャー検定の結果よりも上位に浮上する傾向があるため、ここではその必要はありませんでした。)

図9:Metaeditorのデータベースインターフェースで、エングル=グレンジャー検定によりフィルタリングされたcoint_rankテーブルを表示したスクリーンショット
ここから分かるように、ルックバック期間60日では少なくとも1つの有力なバスケット候補があり、ペアトレードについても、ルックバック期間180日のものが1つ、60日のものが1つと、合計2つの良好な候補が存在しています。ここでは、これまで一貫して扱ってきたバスケットに集中することにし、ペアについては皆さん自身の実験に委ねることにします(本連載の第1回記事には、自由にテストできるペアトレード用のEAが含まれています)。
より強いバスケットをバックテストするためには、ポートフォリオ比率が必要です。そのために、前回の記事で紹介したスクリプトcoint_johansen_to_db.pyを使用します。このスクリプトにより、ポートフォリオ比率を取得すると同時に、検定結果をcoint_johansen_testテーブルに保存し、将来の分析に備えます。
if __name__ == '__main__': analyzer = SymbolJohansenMulti() # Our best-ranked NVDA cointegrated basket analyzer.run_johansen_analysis( asset_tickers=['NVDA', 'INTC', 'AVGO', 'ASX'], timeframe='H4', lookback=60 )
すべてが正常に動作していれば、ターミナルには次のような出力が表示されるはずです。
PS C:\...\StatArb\coint> python .\py\screening\coint_johansen_to_db.py
Successfully connected to the SQLite database.
Fetching data for symbol:NVDA...
Fetching data for symbol:INTC...
Fetching data for symbol:AVGO...
Fetching data for symbol:ASX...
Johansen test results for 4 assets:
Cointegrating rank:1
Trace Statistics: [56.636209656616835, 14.38576476690755, 5.106951610164332, 1.355015009275225]
Max-Eigenvalue Statistics: [42.250444889709286, 9.278813156743219, 3.7519366008891066, 1.355015009275225]
Cointegrating Vectors: [1.0, -1.9598590335874817, -0.36649674991957104, 21.608207065113874]
Successfully stored Johansen test results for 4 assets with test_id:21.
Database connection closed.
Analysis complete.
最後のステップは、これらの値をstrategyテーブルに挿入し、EAで読み取ってバックテストすることです。
バックテスト
MetaTrader 5に統合されているSQLiteデータベースは、実運用(取引中)では完全に利用可能ですが、ストラテジーテスターからはSQLiteデータベースを読み取ることができません。おそらくその理由は、ストラテジーテスターが別個のエージェントフォルダを使用しているため(サンドボックス化されているため)だと考えられます。テスト中は、すべてのファイル操作が通常のターミナルのMQL5\Filesフォルダではなく、ローカルのテストエージェントフォルダ内で実行されます。そのため、SQLiteデータベースに直接アクセスすることができません。1つの回避策としては、strategyテーブルをCSVファイルにエクスポートし、EAのOnInit()イベントハンドラでバックテスト開始時にそれを読み込む方法があります。これによりstrategyテーブルを読み取ることは可能になりますが、バックテスト中にモデルをリアルタイムで更新することは依然としてできません。しかし、まさにそれこそが、データベースをモデルのデータソースとして使用する主目的です。とはいえ、心配する必要はありません。リアルタイムでのモデル更新の挙動については、デモ口座で確認することができます。現時点では、EAにモデルパラメータをハードコードし、各戦略を個別にバックテストすることにしましょう。
なお、ここではMQL5 Algo Forge(Gitで管理される新しいMQL5 Storage)を使用しているため、これらのパラメータをEAに追加するためにおこなった変更内容は、容易に確認することができます。
// Input parameters input int InpUpdateFreq = 1; // Update frequency in minutes -input string InpDbFilename = "StatArb\\statarb-0.3.db"; // SQLite database filename -input string InpStrategyName = "CointNasdaq"; // Strategy name +input string InpDbFilename = "StatArb\\statarb-0.4.db"; // SQLite database filename +input string InpStrategyName = "CointNasdaq_H4_60"; // Strategy name input double InpEntryThreshold = 2.0; // Entry threshold (std dev) input double InpExitThreshold = 0.3; // Exit threshold (std dev) input double InpLotSize = 10.0; // Lot size per leg @@ -53,18 +53,40 @@ int OnInit() // Set a timer for spread, mean, stdev calculations // and strategy parameters update (check DB) EventSetTimer(InpUpdateFreq * 60); // min one minute -// Load strategy parameters from database - if(!LoadStrategyFromDB(InpDbFilename, - InpStrategyName, - symbols, - weights, - timeframe, - lookback_period)) +// check if we are backtesting + if(MQLInfoInteger(MQL_TESTER)) { - // Handle error - maybe use default values - printf("Error at " + __FUNCTION__ + " %s ", - getUninitReasonText(GetLastError())); - return INIT_FAILED; + Print("Running on tester"); + ArrayResize(symbols, 4); + ArrayResize(weights, 4); + //{"NVDA", "INTC", "AVGO", "ASX"}; + symbols[0] = "NVDA"; + symbols[1] = "INTC"; + symbols[2] = "AVGO"; + symbols[3] = "ASX"; + // {1.0, -1.9598590335874817, -0.36649674991957104, 21.608207065113874}; + weights[0] = 1.0; + weights[1] = -1.9598590335874817; + weights[2] = -0.36649674991957104; + weights[3] = 21.608207065113874; + timeframe = PERIOD_H4; + lookback_period = 60; + } + else + { + // Load strategy parameters from database + if(!LoadStrategyFromDB(InpDbFilename, + InpStrategyName, + symbols, + weights, + timeframe, + lookback_period))
データベースのスキーマ変更(coint_rankテーブルを追加)を反映するため、データベースのファイル名を更新しました。また、どの共和分検定によってパラメータが生成された戦略なのかが分かるように、戦略名もより具体的なものに変更しています。
さらに、ストラテジーテスター環境、すなわちバックテストを実行しているかどうかを判定するチェックを追加しました。バックテスト中である場合、パラメータはデータベースから取得せず、ハードコードされた戦略パラメータを使用します。
以下が、今回使用したバックテスト設定および入力パラメータです。バックテストの設定ファイル(.ini)と入力ファイル(.set)の両方は、本記事の末尾に添付されています。

図10:バックテスト設定を示すスクリーンショット
バックテスト期間は、戦略のルックバック期間が60日であるにもかかわらず、2023年初頭から約2年半に設定しています。また、「Delays」フィールドは、理想的な約定(レイテンシーゼロ)のままにしています。これは、統計的裁定戦略においては重要な要素になり得ると分かっていながら、あえてそうしています。
これら2つの選択はいずれも、今回の最初のバックテストの主目的が、収益性ではなく、戦略の長期的な安定性を評価することにあるためです。では、なぜ最初から共和分検定のルックバック期間を2年半にしなかったのでしょうか。それは、現在(今この瞬間)において最も共和分の強い関係をスコアリングし、取引対象として評価しているからです。長期的な安定性はスコアリングにおける重要な要素の1つではありますが、バスケットをランキングする段階では、「今日、最も共和分が強いもの」に関心があります。
レイテンシーゼロについても、ここではほとんど影響はないはずです。以前、統計的裁定取引の大規模プレイヤーとの不公平な競争を避けるために上位時間足を選んだことを思い出してください。当時は1分足での話でしたが、今回はH4の時間足、より落ち着いたスイングトレード環境です。

図11:バックテスト入力パラメータを示すスクリーンショット
図11から分かるように、エントリーおよびエグジットの閾値について最適化をおこなっています。いずれも平均値からの標準偏差として計算されており、バックテストではそれぞれ6.9と0.1を使用しました。
以下がバックテスト結果です。

図12:バックテストレポート(統計結果)
EAは合計160回の取引をおこない、34か月間で平均月4.7回、つまり平均すると週1回程度の取引です。これはスイングトレードとしては適切な保有期間だと言えるでしょう。また、平均利益取引(1,479.02)は、平均損失取引(-1,369.62)を上回っており、最大残高ドローダウンも1.27%と非常に小さい値です。収益性は今回のバックテストの主目的ではありませんでしたが、全体として見ると、かなり有望な結果だと言えます。

図13:残高/純資産曲線を示すバックテスト結果
残高/純資産曲線は、正直なところ予想よりも良好です。2024年11月頃から明確に利益が出始めており、しかも共和分検定のルックバック期間はわずか60日でした。

図14:取引エントリーの曜日および時間を示すバックテストレポート
『ストラウスのデータを精査する中で、Lauferは曜日ごとに繰り返される特定の取引シーケンスを発見しました。たとえば、月曜日の値動きは金曜日の動きを引き継ぐことが多く、一方で火曜日にはそれ以前のトレンドへの回帰が見られることが多かったのです。さらにLauferは、前日の取引が翌日の動きを予測し得ることも突き止め、これを「24時間効果」と名付けました。メダリオンモデルは、たとえば明確な上昇トレンドが存在する場合、金曜日の引け際に買い、月曜日の早い時間に売ることで、彼らが「週末効果」と呼んでいた現象を利用していました。』(グレゴリー・ザッカーマン著『The Man Who Solved the Market: How Jim Simons Launched the Quant Revolution』2019年、ニューヨーク:Portfolio / Penguin)
上記の時間別エントリーに関する結果から、あるパターンに気づかれたでしょうか。160回すべてのエントリーが同じ時刻におこなわれています。正確には16:30です。
ここでは、ストラテジーテスターにおいてUTC時間が使用されていると仮定しています。
「テスト中は、ローカル時間TimeLocal()は常にサーバー時間TimeTradeServer()と等しくなります。また、サーバー時間は常にGMT時間TimeGMT()に対応する時間と等しくなります。このため、テスト中はこれらすべての関数が同じ時刻を表示します。」(『メタトレーダー5における検証の原則』より)
正直なところ、すべてのエントリー時刻が同じだというのは、やや不思議に感じられます。これは実際の市場特性を反映している可能性もありますし、EAの想定外の挙動によるものかもしれません。いずれにしても、この時刻はロンドン証券取引所の引け時刻にあたります。そして、このようなパターンこそが、先ほどの引用が示しているように、統計的裁定におけるデータ分析によって発見される典型的な現象なのです。

図15:MFEとMAEを示すバックテストレポート
MFE(最大有利変動)とMAE(最大不利変動)の両方を見ると、このシステムでは、保有時間に基づいてポジションをクローズするための閾値を追加することを検討すべきであることが示唆されています。この点は、以下に示す最大および平均のポジション保有時間によっても裏付けられています。MFEとMAEについての詳細は、取引における数学を解説した非常に優れた記事を参照するとよいでしょう。す。

図16:ポジション保有時間を示すバックテストレポート
少なくとも1つのポジションは、約15週間も保持されたままだったようです。また、平均ポジション保有時間が約1か月というのも、この種の戦略としてはやや長すぎるように感じられます。
しかし、これらの課題については今後あらためて検討していきます。現時点では、スコアリングシステムの背後にある原則を理解し、それを自分のスタイルや取引目的に合わせて調整できるようになることが重要です。ジョン・フォン・ノイマンの言葉を借りるなら、「真理とは……近似以外を許さないほど、あまりにも複雑なものである」のです。
取引をお楽しみください。
結論
本記事では、共和分した株式を用いた統計的裁定のためのスコアリングシステムを提案しました。このシステムは、まず時間足やルックバック期間といった戦略的基準から始まり、その後、流動性や取引コストといった排除基準を適用して、取引コストに見合わない、あるいは取引可能性のない銘柄を除外します。
最終的に、ランキングスコアは、共和分の強さ、共和分ベクトルの数(ランク)、ポートフォリオ比率の安定性、および平均回帰までの時間に基づいて適切に構築されます。本記事では、これらのうち最初の2つの分類基準について、サンプル実装を提示しました。一方、ポートフォリオ比率の安定性と平均回帰の半減期については、次回の記事で扱う予定です。
また、2つの分類基準に基づくバックテスト結果を提示し、それらについて解説しました。さらに、共和分検定の再現およびバックテストの再現に必要なファイル一式も提供しています。これにより、読者の皆さんは、提案した統計的裁定戦略とスコアリングシステムをすぐに実践することができます。
| ファイル | 説明 |
|---|---|
| backtests\CointNasdaq.EURUSD.H4.20230101_20251019.000.ini | バックテスト設定ファイル |
| backtests\CointNasdaq.set | バックテスト入力(SETファイル) |
| Experts\StatArb\CointNasdaq.mq5 | EAのMQL5ソースファイル |
| Files\StatArb\schema-0.4.sql | データベーススキーマ(SQLファイル) |
| Include\StatArb\CointNasdaq.mqh | EAのMQHヘッダファイル |
| coint_ranker_auto.py | 共和分結果をランキングするためのPythonスクリプト |
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/20026
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
プライスアクション分析ツールキットの開発(第47回):MetaTrader 5で外国為替セッションとブレイクアウトを追跡する
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
迅速な取引判断を極める:実行麻痺を克服する
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索