株価指数両建て裁定取引のやり方と検証
目次/もくじ
日経225やダウ、SP500のチャートには強い相関関係があります。かつては「日経はニューヨークの後追いをする」と言われた時代もありましたが、現在ではこの二つはリアルタイムで連動する銘柄です。
しかも、日足チャートのみならず、1時間足チャートや1分足チャートにおいても同様の強い相関関係が見られます。今回はアメリカと日本の枠組みから視点を少し広げ、経済インデックスの相関関係を利用した裁定取引(アービトラージ)の可能性について考察します。
日経225とSP500の記事はこちら
株価指数両建てアービトラージとは
日経225 と SP500 の1時間チャート
株価指数(インデックス)の両建てアービトラージ(裁定取引)とは、二つの相関がある銘柄間で乖離が生じたタイミングで両建て取引を行うトレード手法です。
図の場合、上のチャートと下のチャートには類似性が見られますが、直近(右端)の局面ではSP500が日経225よりも大きく下げています。これは言い換えれば、SP500が値を下げたほど、日経は下がらなかったと言うこともできます。
このとき、日経で売り、SP500で買いを行えば、二つのチャートの相関が戻ったときに利益が得られるという仕組みです。
同じようなアービトラージに、ビットコインアービトラージやFXアービトラージや先物アービトラージなどがありますが、この場合のアービトラージの大きな違いは全く異なる2つの銘柄チャートである、という点です。
つまり、2つの価格が”同じ値になるべきである”という保証はまったくなく、”なぜかは分からないけど似たような動きをしている”という程度の比較的弱い相関です。
また、日経225などの場合には、ミニやラージ、CFDなどの複数種のチャートがあり、それぞれの利ザヤをとる裁定取引もありますが、今回は対象そのものが異なる2つの指数を扱います。
各国株価指数(インデックス)の相関関係は
そもそも、当初は日経225とSP500に焦点を当てていた訳ですが、「他の国の経済インデックスとの相関はどうなっているんだろう」と視点を広くした方が合理的です。実際問題、日経225は他の国の経済インデックスと比較して何倍もスプレッドが高い傾向があるので、もし他の経済インデックスにも同様の強い相関があり、同じような裁定取引ができるのであれば、スプレッドが小さい国の株価インデックスの方が有利です。
TradeViewで採用されている経済インデックスには、
- DOW30, ニューヨークダウ
- NDX, US100, ナスダック100指数
- SPX, SP500,
- UK100, FTSE100種総合株価指数(イギリス)
- EU50, (ヨーロッパ)
- AUS200, オーストラリア200
- J225, 日経225
- GDAXI(ドイツ株価指数30)
- FCHI, CAC 40(フランス)
があります。残念ながら上海などはありません。
アメリカの経済インデックスを軸にして、各国の経済インデックスの相関が大きな時間枠、小さな時間枠で存在するのかをまずは目視で確認してみます。
SP500 vs ダウ M1 強い相関にある二つの株価指数(主観的)
SP500 vs EU50 H1 相関なし
※ただし、相場がオープンしている時間の差により過去になるほど時間軸がズレる点に注意
統計ソフト((RやSPSSなど)を使って相関係数などを調べれば卒論レベル程度のレポートはこれで書けるかもしれません。
M1 | H1 | D1 | ||
SP500 | ダウ | ◎ | ◎ | ◎ |
SP500 | US100 | ◎ | ◎ | ◎ |
SP500 | 日経 | 〇 | 〇 | ◎ |
SP500 | EU50 | × | × | △ |
SP500 | オーストラリア200 | × | 〇 | 〇 |
SP500 | ドイツ30 | 〇 | 〇 | 〇 |
SP500 | イギリス100 | 〇 | 〇 | 〇 |
SP500 | フランス40 | 〇 | 〇 | 〇 |
US100 | ダウ | ◎ | △ | ◎ |
US100 | 日経 | △ | 〇 | 〇 |
EU50 | ドイツ30 | ◎ | ◎ | ◎ |
EU50 | イギリス100 | ◎ | 〇 | 〇 |
EU50 | フランス40 | ◎ | ◎ | ◎ |
ドイツ30 | フランス40 | ◎ | ◎ | ◎ |
◎:強い相関, 〇:相関あり, △:相関があるようなないような…, ×:相関なし(あくまで主観)
アメリカの3つの指数はそれぞれ強い相関があり、ドイツとフランスも強い相関、アメリカと日本も強い相関があることがわかります。また、EU50とそれぞれのヨーロッパの国の株価指数にも相関があります。
ヨーロッパの中ではイギリスだけ相関が弱く、独自の動きをする場面が見られました。これはブレグジットでEUを離脱した背景などがあるかもしれません。
ただし、これらはあくまで主観的なものであり、また時系列によって今後相関が失われる可能性なども十分に考えられるので、”仮説の域をでない”と判断した方が賢明です。
TradeViewでの株価指数のスプレッド
スプレッドを比較すると、やはり日経225のスプレッドが大きく、裁定取引をするにあたって不利であることが分かります。
縦軸の数値が異なる2つのグラフを比較するには
二つの相関がある株価指数はチャートにして比較すると一目瞭然ですが、数値処理を施すにはいくつかのステップが必要です。
ドイツとフランスの株価指数の比較 1分足
例えば、ドイツとフランスの株価指数の場合、ドイツでは10671前後ですがフランスは4417前後であり、値が全然異なります。
縦軸のスケールを統一する方法1:価格の変化率にする
一定期間中にレートがどれくらい変化したかを表すレート変化のインジケータを利用します。
”レートの変化倍率の差”とは言いかえると、ある一定期間に何%上下に変位したかを表します。つまり、ROC(rate of change)のことです。ROCはレートの変化倍率を表したテクニカル指標であり、ROCの比較をすることにより縦軸の値を統一することができます。
ROC = ((CLOSE (i) – CLOSE (i – n)) / CLOSE (i – n)) * 100
ただし:
CLOSE (i) – 現在足の終値;
CLOSE (i – n) – n本前の終値;
ROC – ROCの値
ドイツとフランスの株価指数 M1 ROC=12
“変化率”を使うと、いつから比較するかという起点の問題が生じます。一般的特性として、期間が短い場合(12など)は図のように縦軸の値を一致させることができますが、元のグラフの原型が失われます。
ドイツとフランスの株価指数 M1 ROC=500
一方、比較対象の期間が遠い場合(500など)、元のグラフに似た状態を維持しつつ比較することができますが、縦軸の値のズレが大きくなります。
ROCは単体のエントリーシグナルとして利用する場合は短い期間が一般的ですが、今回の意味合いとしては後者の方が元のチャートの形を維持したまま比較できるので望ましいです。
変化率の弱点を補強する
変化率の期間を大きくした場合、縦軸の値の一致度が不十分なので、この変化率のグラフの変化率を取ります。
この考え方が妥当かどうかを判断するために、変化率の変化率のインジケーターを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
//+------------------------------------------------------------------+ //| ROC.mq5 | //| Copyright 2009, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2009, MetaQuotes Software Corp." #property link "http://www.mql5.com" #property description "Rate of Change" //--- indicator settings #property indicator_separate_window #property indicator_buffers 1 #property indicator_plots 1 #property indicator_type1 DRAW_LINE #property indicator_color1 clrWhite input int iCustomRocPeriod=500; //--- input parameters input int ROCROCPeriod=12; //--- indicator buffers double ExtRocBuffer[]; //--- global variable int ExtRocPeriod; //+------------------------------------------------------------------+ //| Rate of Change initialization function | //+------------------------------------------------------------------+ int ROChandle; double ROC_Buffer[]; void OnInit() { //--- check for input if(ROCROCPeriod<1) { ExtRocPeriod=12; Print("Incorrect value for input variable ROCROCPeriod =",ROCROCPeriod, "Indicator will use value =",ExtRocPeriod,"for calculations."); } else ExtRocPeriod=ROCROCPeriod; //--- indicator buffers mapping SetIndexBuffer(0,ExtRocBuffer,INDICATOR_DATA); //--- set accuracy IndicatorSetInteger(INDICATOR_DIGITS,2); //--- name for DataWindow and indicator subwindow label IndicatorSetString(INDICATOR_SHORTNAME,_Symbol+" ROC ROC("+string(ROCROCPeriod)+")"+" iCustomROC("+string(iCustomRocPeriod)+")"); //--- sets first bar from what index will be drawn PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,ExtRocPeriod); //--- initialization done ROChandle = iCustom(Symbol(),Period(),"roc.ex5",iCustomRocPeriod); } //+------------------------------------------------------------------+ //| Rate of Change | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total,const int prev_calculated,const int begin,const double &price[]) { //--- check for rates count if(rates_total<ExtRocPeriod) return(0); //--- preliminary calculations int pos=prev_calculated-1; // set calc position if(pos<ExtRocPeriod) pos=ExtRocPeriod; //--- the main loop of calculations for(int i=pos;i<rates_total && !IsStopped();i++) { if(price[i]==0.0) ExtRocBuffer[i]=0.0; else //ExtRocBuffer[i]=(price[i]-price[i-ExtRocPeriod])/price[i-ExtRocPeriod]*100; if ((CopyBuffer(ROChandle,0,0,2048,ROC_Buffer))<0) if(!ArraySetAsSeries(ROC_Buffer,true)) return(0); //ExtRocBuffer[i]=ROC_Buffer[i]; if( ROC_Buffer[i-ExtRocPeriod] != 0 )ExtRocBuffer[i]=(ROC_Buffer[i]-ROC_Buffer[i-ExtRocPeriod])/ROC_Buffer[i-ExtRocPeriod]*100; } //--- OnCalculate done. Return new prev_calculated. return(rates_total); } //+------------------------------------------------------------------+ |
ROCのインジケーターのバッファ部分をiCustomで呼び出したROCに置き換えただけです。
グラフでこのインジケーターが妥当かどうか判断すると、
フランスとドイツの株価指数 1分足 変化率の変化率
元のグラフからは多少変化していますが、縦軸の一致度は向上しました。
これで元のグラフの形状を比較的維持したまま、縦軸の数値を統一させることができました。
縦軸のスケールを統一する方法2:100分率に変換する
もう一つ、縦軸のスケールを統一する方法に100分率に変換するという方法があります。これは単純にチャート上に表示されている最大値を100、最小値を0になるように縦軸を変換するだけです。
レート最高値:レート最低値 = 100:0
の相対比になるので、
( 現在価格 - 最低値 )/ ( 最高値 - 最低値 )
を各レートで計算すれば良いことになります。
1 2 3 |
double High = iHigh(Symbol(),Period(),iHighest(Symbol(),Period(),MODE_HIGH,ChartGetInteger(ChartID(),CHART_VISIBLE_BARS),0)); double Low = iLow(Symbol(),Period(),iLowest(Symbol(),Period(),MODE_LOW,ChartGetInteger(ChartID(),CHART_VISIBLE_BARS),0)); if( price[i] != 0 ) Buffer[i]= 100*(price[i]-Low)/(High-Low); |
フランスとドイツの株価指数 1分足の比較 100分率
100分率の場合でも同じように縦軸を統一することができます。変化率の比較の場合は最低値と最高値の幅が分からないので、エントリーポイントを決める閾値の決定が数値最適化に依存しやすいというデメリットがありますが、100分率の場合は最低は0、最高は100と決まっているので、感覚的にわかりやすいというメリットがあります。
デメリットとしては、直近の最高値と最低値を基準にした比較になるので、最高値・最低値から遠くなればなるほどズレが大きくなるという点が挙げられます。
Rate to 100 per scale インジケーターダウンロード
また、一つのインジケーターウィンドウに二つの銘柄の100%スケールのグラフを重ねて表示させるインジケーターも開発しました。
US100のチャートにSP500の100分率グラフとUS100の100分率のグラフを重ねたインジケーター
こうして比較すると、やはり複数の株価指数には相関があるが、その比率変位はリアルタイムで変化していることが分かります。
Rate to 100 per scale with another symbol インジケーターダウンロード
このインジケーターはチャート上のロウソク足の本数に依存する仕様になっています。つまり、チャート上のロウソク足が多ければ多いほど遠くの最大値と最小値を起点にする可能性があります。その結果、チャートを遠巻きに見て、かつ、極値が現在から遠い場合、誤差が大きくなる傾向があります。
チャートを拡大して表示した場合 ロウソク足の数:38
チャートを縮小して表示した場合 ロウソク足の数:1217 (悪い例)
各株価指数を包括的にテストできるようにする
TradeViewで採用されている経済インデックスには、
- DOW30, ニューヨークダウ
- NDX, US100, ナスダック100指数
- SPX, SP500,
- UK100, FTSE100種総合株価指数(イギリス)
- EU50, (ヨーロッパ)
- AUS200, オーストラリア200
- J225, 日経225
- GDAXI(ドイツ株価指数30)
- FCHI, CAC 40(フランス)
がありますが、これら9つから2つ選んで繰り返しテストするのは非常に時間がかかり大変です。全部で9C2=36通りあります。
あらかじめこれらの株価指数の名称をEAにパラメータで渡しておいて、最適化の際に一括で検証できるようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
extern string Index1SymbolName = "SPXm"; extern string Index2SymbolName = "NDX"; input int IndexCombinationPattern = 1; input string IndexSymbolName_Candidate1 = "GDAXI"; input string IndexSymbolName_Candidate2 = "FCHI"; input string IndexSymbolName_Candidate3 = "J225"; input string IndexSymbolName_Candidate4 = "WS30"; input string IndexSymbolName_Candidate5 = "AUS200"; input string IndexSymbolName_Candidate6 = "UK100"; input string IndexSymbolName_Candidate7 = "STOXX50E"; input string IndexSymbolName_Candidate8 = "SPXm"; input string IndexSymbolName_Candidate9 = "NDX"; static string IndexSymbolName_Candidate[10]; |
MT5のFX会社が変わっても利用できるように株価指数名はパラメータにしておきます。
MQLには配列変数をinputで入力させる方法がないので、OnInit内で一度配列に変換して、ループ文で回せるようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
IndexSymbolName_Candidate[1] = IndexSymbolName_Candidate1; IndexSymbolName_Candidate[2] = IndexSymbolName_Candidate2; IndexSymbolName_Candidate[3] = IndexSymbolName_Candidate3; IndexSymbolName_Candidate[4] = IndexSymbolName_Candidate4; IndexSymbolName_Candidate[5] = IndexSymbolName_Candidate5; IndexSymbolName_Candidate[6] = IndexSymbolName_Candidate6; IndexSymbolName_Candidate[7] = IndexSymbolName_Candidate7; IndexSymbolName_Candidate[8] = IndexSymbolName_Candidate8; IndexSymbolName_Candidate[9] = IndexSymbolName_Candidate9; int PatternNum = 0; for(int s=1;s<=9;s++) { for(int t=1;t<=9;t++) { if( s == t ) continue; if( s < t ) continue; PatternNum++; Index1SymbolName = IndexSymbolName_Candidate[s]; Index2SymbolName = IndexSymbolName_Candidate[t]; if( IndexCombinationPattern == PatternNum ) break; } if( IndexCombinationPattern == PatternNum ) break; } |
このようにしておけば、IndexCombinationPatternが1のときはドイツとフランスの組み合わせになり、36のときはダウとSP500の組み合わせまですべてのパターンを選び取ることが可能です。
あとは最適化の際に1~36でステップ1で検証すれば、すべての組み合わせを自動で検証してくれます。MT4ではバックテストの際に選択した銘柄以外のレートを取得したり取引することができないので、MT5でのみ可能です。
EAのダウンロード・使い方
StockIndexArbitrage100percentage1.1_with_passcode.ex5ダウンロード
任意の1チャートに1つのEAをセットします。マルチ通貨EAですが、一つのチャートで複数の銘柄を取引します。
時間枠は任意です。
パラメータ
- Lot_forIndex1:インデックス1用のロット
- Lot_forIndex2:インデックス2用のロット(※稀にインデックス毎に取引枚数が異なる業者があります。)
- IndexCombinationPattern: インデックスのコンビネーションパターン。最適化時以外は1にしておいてください。
- IndexSymbolName_Candidate1: その業者のインデックス1の銘柄名をセット
- IndexSymbolName_Candidate2: その業者のインデックス2の銘柄名をセット
- IndexSymbolName_Candidate3~9: 最適化する場合に各株価指数の銘柄名をセット(最適化時のみ有効)
- TradeIndex1: インデックス1をトレードするかどうか(基本はtrue)
- TradeIndex2: インデックス2をトレードするかどうか(基本はtrue)
- EntryGap: この数値以上のパーセンテージ差が発生した場合に両建てエントリー
- ExitGap: この数値以内にパーセンテージの差が縮小したら決済
- CalcBarCount: 100分率に変換する対象の足の数
- MinTradeInterval_Sec: 最小取引時間間隔(秒)
- Magic: マジックナンバー
- FillingMode: フィリングモード
検証
100分率のロジックをベースとして、エントリーする差、決済する差、株価指数の組み合わせを最適化します。
2020.4.15~2020.5.14 M5 最適化結果
まずは0~30まで1刻みで最適化を行うと、3つの山があることが分かります。
ちなみに、この段階での最適化上位の組み合わせは、
7番と23番が強いことが分かります。7番はSP500とドイツ株価指数で、23番はWS30(ダウ)とUK100(イギリス)の組み合わせです。
つまり、この時点で、SP500とドイツ株価指数が最も株価指数裁定取引を行うにあたって相関が強い、あるいは、スプレッドなどを加味して最有力候補に成りえる、ということが分かります。(確定ではありません。)
相関だけを見るとダウとSP500などの方が強いと考えらるので、”価格の動きのズレ”と”その補正に働く力”がちょうどよい、という可能性も考えられます。
ちなみにことのときの資産グラフは
2020.4.15~2020.5.14 M5 SP500とEU50 裁定取引 3%差でエントリー、1%に縮まったら決済
このような具合です。
次に、1時間足で検証してみます。
2020.4.15~2020.5.14 H1 最適化結果
H1の場合も、だいたい0~30の範囲でエントリー、決済を決定した方が良いという結果が得られました。この”だいたい”というのが最適化においては重要で、ピンポイントで成績が良い集合ではないので、過剰最適化の危険性を避けることができます。
また、最適化の上位パターンでは株価指数の組み合わせにおいて2番が総なめしています。2番はSP500と日経225の組み合わせです。(ビジュアルモードでどのインデックスの組み合わせかを確認することができます。)
5分足チャートでは、SP500とEU50、SP500とドイツ株価指数の組み合わせに高い収益性が見られましたが、1時間足チャートの場合は、スプレッドを考慮してもSP500と日経225が良いということが分かります。
2020.4.15~2020.5.14 H1 SP500 vs 日経225 裁定取引結果
上位の組み合わせ群ではだいたい同じようなグラフになります。
レイテンシーアービトラージの場合は、アジア時間よりもヨーロッパ時間やニューヨーク時間で取引が頻繁になりますが、1時間足の日経225・SP500の裁定取引では、特に時間帯の偏りは見られません。
日足チャートも少しだけ見てみましょう。
2020.4.15~2020.5.14 D1 最適化結果
日足の場合は同じ期間で最適化をすると取引回数が少ないので、あまり信頼性は高くありません。信頼度を上げるにはテスト期間を増やす必要がありますが、1か月程度のテストでも数時間要したので、時間とリソースがある方はトライしてみてください。
日足では、2番と17番の組み合わせが収益性が高いパターンという結果が出ています。2番はSP500と日経225の組み合わせで、17番は日経225とオーストラリア株価指数です。
結論
異なる種類の株価指数で裁定取引をするにあたって、強い相関があるということが必ずしも裁定取引の機会と利益にはつながらないということが(非定量的に)分かりました。
本来は米国内の3つの株価指数間や日経225などの相関が強く出るので取引機会も多そうですが、実際には程よく”相関から外れるタイミング”が来ることがアービトラージのチャンスに繋がっていると考えられます。
また、裁定取引を行うチャート時間枠に関しても、小さい時間枠と大きな時間枠とどちらが適切か、という分析・検証も行えていないので、どの時間枠が最適かというのは地道に最適化していくしかありません。
また、検証した期間ではコロナショック後の影響で、一時的に買い建てしかできない相場があったことから(フランス株価指数など)その影響も強く入っていたと考えられます。可能であれば、相場の建玉の方向に制限がなくなった期間で再び検証をするべきでしょう。
関連記事
-
EURGBP, USDCAD用EA(MT5)[無料EA配布]
今回はなぜかEURGBP, USDCADだけ資産グラフが右肩上がりになるEAです。他の通貨ペアでは全
-
ボリンジャーバンドEA (MT5)[無料EA配布]
ボリンジャーバンドとマーチンゲールを使ったMT5用のEAです。 EURUS
-
[MT5EA]仮想通貨対応EAダウンロード(固定SLTP自動売買)
世界では利益確定を20pips,損切を200pipsで行う戦略がかつて有名なものとしてあったそうです
-
MACDのEA(MT5)FX 仮想通貨対応[無料]
MACD, ストキャスティクス, モメンタムのEAです。 バックテスト結果
-
勝てればデカいEA(20倍)[MT5]
バックテスト結果 GBPUSD H1 2019/1/1~2019/12/31
コメント
EU50とドイツインデックス(34番)でやっていましたが、初回の同時決済(指定のexit gapに到達)の直後に、EU50だけを買っては数秒で売り、というのを10秒ごと(自身が設定したminimal intervalです)に繰り返す暴走をしていて爆損こいてました。ちなみに、その際のgapは設定したentry gapを満たしていました。つまりEU50を買うというentryは正常です。しかしドイツの方の売りが入っていないのが謎ですし、exit gapに達していないのにEU50を数秒で売ってしまっています。entryで片方だけ入らないために決済も二次的にバグっているのかもしれませんが・・・
ログを見ないと正確なことは言えませんが、状況を察するに、利用している業者のドイツインデックスでそもそも何らかの外的要因によりエントリーできていない確率が高いです。株価指数は国の情勢やなんかで取引自体が停止したり、決済のみOKになったりすることがあります。
特に今のコロナ後相場では、コロナショックの急落時にドイツ株式指数がエントリー禁止になり、それが現在も解除されていない可能性があります。
また、最小ロットも0.01でないことがあるので、それらも含めてまずは裁量で事前にちゃんとオーダーが通るかを確認した方が良いと思います。
決済を繰り返すのは、両建ての設定になっていて、もう片方が今回のように何らかの理由によりエントリーできずに片側だけのポジションになった場合、直ちに離脱するEAの設定(パラメータ:ExitIfTheOtherCompanyFailedToEntry)がtrueになっている為です。