![シンプルな平均回帰取引戦略](https://c.mql5.com/2/55/Mean_reversion_600x314.jpg)
シンプルな平均回帰取引戦略
はじめに
平均回帰とは、トレーダーが価格が何らかの形の均衡に戻ることを期待する逆張り取引の一種で、通常は平均値または別の中心的傾向の統計によって測定されます。この記事では、実にシンプルな平均回帰トレード戦略について説明します。
平均回帰入門
市場は一般的に不規則なサイクルで動きます。つまり、チャートを見ると、上昇、下降、比較的平坦な局面が見られる傾向があります。取引や投資で重要なのは、市場レジームとも呼ばれるこうした局面の変化を見極められるかどうかです。
平均回帰は移動平均線のような形でも可能で、市場が移動平均線から離れ過ぎると、その領域に戻る可能性が高くなります。
平均回帰とは
平均回帰は金融用語で、資産価格が時間の経過とともに平均価格に収束する傾向があるという仮定を意味します。
タイミング戦略としての平均回帰分析には、ある証券の取引レンジの特定と、定量的手法による平均価格の計算の両方が含まれます。平均回帰は、価格データ、収益データ、簿価データなど、多くの金融時系列データに見られる現象です。
現在の市場価格が過去の平均価格を下回っている場合、その証券は価格は上昇すると考えられ、魅力的な購入対象になります。現在の市場価格が過去の平均価格を上回っている場合、市場価格は下落すると予想されます。言い換えれば、平均価格からの乖離は平均価格に戻ると予想されます。この知識は、複数の取引戦略の基礎となります。
株式レポートサービスでは、一般的に50日や100日といった期間の移動平均を提供しています。レポートサービスは平均値を提供しますが、調査期間の最高値と最安値を特定することは依然として必要です。
平均回帰分析は過去のデータから正確な数値を導き出して売買の値を特定するため、チャートを使って値動きを解釈しようとするチャート分析よりも科学的な株価の売買ポイントの選択方法のように見えますが(チャート分析はテクニカル分析としても知られています)、RSI指標やATR(Average True Range)は、このような体系的なパターンを捉えようとする初期の試みです。
多くの資産クラスは、為替レートでさえも、平均回帰することが観察されていますが、このプロセスは何年も続く可能性があるため、短期投資家にとっては価値がありません。
株価は過去の平均を下回るのとほぼ同じ頻度で上回る可能性があるため、平均回帰は一種の対称性を示すはずです。
過去の平均回帰モデルは、証券価格の実際の動きを完全には反映しません。例えば、原株の長期的評価に恒久的な影響を与えるような新しい情報が入手可能になることがあります。倒産した場合は、取引は完全に停止し、かつての歴史的平均まで回復することはないかもしれません。
金融における「平均回帰」という言葉は、統計学における「平均への回帰」とは少し意味が異なります。 ジェレミー・シーゲルは 「平均への回帰」という言葉を使って、一般的な原則、つまり「リターンは短期的には非常に不安定だが、長期的には非常に安定している」という金融時系列を表現しています。 定量的には、年間平均リターンの標準偏差が保有期間の逆数よりも速く減少することであり、このプロセスはランダムウォークではなく、例えば季節的なビジネスでは、リターンが低い期間が高い期間を補うことを示唆しています。
下図はその例です。
しかし、「遠すぎる」とはどのように測ればいいのでしょうか。移動平均線に対する価格の相対的な位置だけに基づく、非常に単純な方法を試してみます。
戦略の設計
これで、相場と200期間移動平均の間の50期間の正規化された距離がわかったので、次の売買シグナルをコード化します。
- 正規化指数が100から下落し、現在の終値が5期間前の終値を下回り、200期間移動平均線を下回る場合、ロング(買い)シグナルが発生します。
- 現在の終値が5期間前の終値を上回り、200期間移動平均線を上回っている間に、正規化指数が100に等しくなった後、100から下落するたびに、ショート(売り)シグナルが発生します。
この条件が揃えば、これは最もシンプルな戦略ではないかもしれませんが、それでも非常に直感的でわかりやすいものです。シグナル関数は以下の通りです。
前のセクションから、戦略設計を始めるための明確な目標ができました。
- ロング(買い)シグナルは、相場が移動平均線を大きく下回り、平均線に回帰する可能性が高いときに発生します。
- ショート(売り)シグナルは、相場が移動平均線を大きく上回り、下降平均線に回帰する可能性高いときに発生します。
戦略がうまく機能しない場合(エクイティが下がる場合)に、より良い結果が得られるように戦略を修正するつもりです。次のようにです。
- 直近の終値を見る代わりに、高値と安値を調べて、より良い注文を選別する。
注文が開く頻度もより少ないです。
これが修正したコードです。
if(previousValue==100) { if(Normalizado<100 && array_ma[0]>tick.bid && rates[5].high < rates[1].low ) { Print("Open Order Buy"); Alert(" Buying"); Orden="Buy"; sl=NormalizeDouble(tick.ask - ptsl*_Point,_Digits); tp=NormalizeDouble(tick.bid + pttp*_Point,_Digits); //trade.Buy(get_lot(tick.bid),_Symbol,tick.bid,sl,tp); trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,get_lot(tick.bid),tick.bid,sl,tp,"Buy"); } if(Normalizado<100 && array_ma[0]<tick.ask && rates[5].low > rates[1].high ) { Print("Open Order Sell"); Alert(" Selling"); Orden="Sell"; sl=NormalizeDouble(tick.bid + ptsl*_Point,_Digits); tp=NormalizeDouble(tick.ask - pttp*_Point,_Digits); //trade.Sell(get_lot_s(tick.ask),_Symbol,tick.ask,sl,tp); trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,get_lot(tick.ask),tick.ask,sl,tp,"Sell"); } }
これらの変更によって勝率は下がりましたが、より安定しているように見えます(SL=4000ポイント)。
より良い結果を得るためには、このEAを最適化する必要があります。コードを変えることもできます。これは、30分間に300のSLをつけた場合の例です。使用されている銘柄と時間枠から、最適なものを検索してください。
グラフはSLが300ポイントの方が安定しているように見えますが、SLが4000ポイントの方が収益性が高くなります。30分間でバランスを取るために、SLを最適化するべきですが、この仕事は読者にお任せします。
コード
入力は以下です。
input ENUM_LOT_TYPE inp_lot_type = LOT_TYPE_FIX; // type of lot input double inp_lot_fix = 0.01; // fix lot input double inp_lot_risk = 0.01; input bool InpPrintLog = false; // Print log ulong Expert_MagicNumber =66777; // bool Expert_EveryTick =false; // input ENUM_TIMEFRAMES my_timeframe=PERIOD_CURRENT; // Timeframe int handle_iMA; input int Inp_MA_ma_period = 200; // MA: averaging period input int Inp_MA_ma_shift = 5; // MA: horizontal shift input ENUM_MA_METHOD Inp_MA_ma_method = MODE_SMA; // MA: smoothing type input ENUM_APPLIED_PRICE Inp_MA_applied_price = PRICE_CLOSE; // MA: type of price int shift = 49; // loockback normalization input int ptsl = 350; // points for stoploss input int pttp = 5000; // points for takeprofit
最初の行では、使用するロットのタイプを設定します。次の行では、固定ロットのサイズを設定し、続いてリスクロットのサイズを設定します。次の行では、ログを出力するかどうかを決定するブーリアン値を設定します。次の行では、エキスパートアドバイザー(EA)のマジックナンバーを設定します。次の行では、EAを毎ティック実行するかどうかを決定するブーリアン値を設定します。次の行では、EAの時間枠を設定します。次の数行は、平均期間、水平シフト、平滑化タイプ、価格のタイプなど、移動平均指標のパラメータを設定します。次の行では、振り返りの正規化のためのシフトを設定します。次の2行では、ストップロスとテイクプロフィットのポイントを設定します。
OnInit()内:
int OnInit() { //--- handle_iMA=iMA(_Symbol,my_timeframe,Inp_MA_ma_period,Inp_MA_ma_shift, Inp_MA_ma_method,Inp_MA_applied_price); // Initialize the variable here if needed previousValue = 0.0; //--- return(INIT_SUCCEEDED); }
このコードはMQL5言語で書かれており、変数を初期化するために使用されます。iMA関数は、指定された銘柄の指定された時間枠での移動平均を計算するために使用されます。iMA関数のパラメータは、Inp_MA_ma_period、Inp_MA_ma_shift、Inp_MA_ma_method、Inp_MA_applied_price入力変数値に設定されます。2行目のコードでは、previousValue変数を0.0に初期化します。コードの最後の行ではINIT_SUCCEEDEDという値を返し、初期化が成功したことを示します。
OnTick()内:
MqlTick tick; double last_price = tick.ask; SymbolInfoTick(_Symbol,tick);
そして
if(SymbolInfoTick(_Symbol,tick)) last=tick.last; double Last = NormalizeDouble(last,_Digits);
このコードはMQL5言語で書かれており、銘柄の現在の売呼値と直近の売呼値を比較するために使用されます。最初の行では、MqlTick型のtick変数を作成します。2行目では、最後の売呼値を変last_price変数に格納します。3行目では、_Symbol変数で指定された銘柄のティック情報を取得し、tick変数に格納します。4行目では、現在の売呼値がlast_price変数に格納されている最後の売呼値を上回るかどうかを確認します。上回っている場合は、何かをしなければなりません。
このコードは、指定された銘柄のスプレッド率を計算するために使用されます。まず、SymbolInfoTick()関数を使って銘柄の最終価格を取得します。この価格は、_Digitsパラメータで指定された桁数に正規化されます。正規化された最終価格が0より大きい場合、銘柄の売呼値と買呼値が検索され、正規化されます。スプレッドは、正規化された売呼値から正規化された買呼値を差し引いて計算されます。そして、そのスプレッドを銘柄のポイント値(Pow()関数を使って計算)で割って、ポイント単位のスプレッドを得ます。最後に、ポイント単位のスプレッドを正規化された最終価格で割り、100を掛けてスプレッドの割合を求めます。スプレッドの割合がMax_Spreadパラメータ以下の場合、何らかのアクションが取られます。
MAには次を使用します。
handle_iMA=iMA(_Symbol,my_timeframe,Inp_MA_ma_period,Inp_MA_ma_shift, Inp_MA_ma_method,Inp_MA_applied_price);
//--- double array_ma[]; ArraySetAsSeries(array_ma,true); int start_pos=0,count=3; if(!iGetArray(handle_iMA,0,start_pos,count,array_ma)) return;
string text=""; for(int i=0; i<count; i++) text=text+IntegerToString(i)+": "+DoubleToString(array_ma[i],Digits()+1)+"\n"; //--- Comment(text);
bool iGetArray(const int handle,const int buffer,const int start_pos, const int count,double &arr_buffer[]) { bool result=true; if(!ArrayIsDynamic(arr_buffer)) { //if(InpPrintLog) PrintFormat("ERROR! EA: %s, FUNCTION: %s, this a no dynamic array!",__FILE__,__FUNCTION__); return(false); } ArrayFree(arr_buffer); //--- reset error code ResetLastError(); //--- fill a part of the iBands array with values from the indicator buffer int copied=CopyBuffer(handle,buffer,start_pos,count,arr_buffer); if(copied!=count) { //--- if the copying fails, tell the error code //if(InpPrintLog) PrintFormat("ERROR! EA: %s, FUNCTION: %s, amount to copy: %d, copied: %d, error code %d", __FILE__,__FUNCTION__,count,copied,GetLastError()); //--- quit with zero result - it means that the indicator is considered as not calculated return(false); } return(result); }このコードは、指定された銘柄の移動平均(MA)を指定された時間枠で計算し、表示するために使用されます。MAを計算するにはiMA()関数を使用し、指標バッファからMA値を取得するにはiGetArray()関数を使用します。ArraySetAsSeries()関数は配列を系列として設定するために使用し、IntegerToString()関数とDoubleToString()関数は配列の値を文字列に変換するために使用します。最後に、Comment()関数を用いてMA値をチャートに表示します。
注文を開始する際に出来高エラーが発生しないようにするために、次を使用します。
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< double get_lot(double price) { if(inp_lot_type==LOT_TYPE_FIX) return(normalize_lot(inp_lot_fix)); double one_lot_margin; if(!OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,1.0,price,one_lot_margin)) return(inp_lot_fix); return(normalize_lot((AccountInfoDouble(ACCOUNT_BALANCE)*(inp_lot_risk/100))/ one_lot_margin)); } //+------------------------------------------------------------------+ //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< double normalize_lot(double lt) { double lot_step = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); lt = MathFloor(lt / lot_step) * lot_step; double lot_minimum = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); lt = MathMax(lt, lot_minimum); return(lt); }
このコードは、買い注文のロットサイズを計算するために使用されます。最初の関数get_lot()は、引数として価格を受け取り、ロットサイズを返します。ロットサイズはロットタイプによって決定され、固定またはリスクの割合に基づいて決定されます。ロットタイプが固定の場合、この関数は正規化されたロットサイズを返します。ロットタイプがリスクの割合に基づく場合、この関数は1ロットの証拠金を計算し、残高とリスクの割合に基づいてロットサイズを計算し、正規化されたロットサイズを返します。2番目の関数normalize_lot()は、引数としてロットサイズを受け取り、正規化されたロットサイズを返します。正規化されたロットサイズは、ロットサイズをボリュームステップで割り、それにボリュームステップを乗じることで計算されます。そして、正規化されたロットサイズが最小ロットサイズと比較され、両者の最大値が返されます。
注文を出すには次のようにします。
trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,get_lot(tick.bid),tick.bid,sl,tp,"Buy");
このコードはMQL5で書かれており、市場で新規ポジションを建てるために使用されます。最初のパラメータは、取引される資産の銘柄です。2番目のパラメータは注文の種類で、この場合は買い注文です。3つ目のパラメータはロットサイズで、get_lot()関数と現在の買呼値を使って計算されます。第4パラメータは現在の買呼値です。5番目と6番目のパラメータは、それぞれストップロスとテイクプロフィットのレベルです。最後のパラメータは、注文に追加されるコメントです。
最初の戦略に従って、高値/安値の決済条件だけを変えて(より確実な結果を得るために)スタートします。
if(0<=Normalizado<=100 ) { //------------------------------------------------------------------------------ if(previousValue==100) { if(Normalizado<100 && array_ma[0]>tick.bid && rates[5].high < rates[1].low ) { Print("Open Order Buy"); Alert(" Buying"); Orden="Buy"; sl=NormalizeDouble(tick.ask - ptsl*_Point,_Digits); tp=NormalizeDouble(tick.bid + pttp*_Point,_Digits); //trade.Buy(get_lot(tick.bid),_Symbol,tick.bid,sl,tp); trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,get_lot(tick.bid),tick.bid,sl,tp,"Buy"); } if(Normalizado<100 && array_ma[0]<tick.ask && rates[5].low > rates[1].high ) { Print("Open Order Sell"); Alert(" Selling"); Orden="Sell"; sl=NormalizeDouble(tick.bid + ptsl*_Point,_Digits); tp=NormalizeDouble(tick.ask - pttp*_Point,_Digits); //trade.Sell(get_lot_s(tick.ask),_Symbol,tick.ask,sl,tp); trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,get_lot(tick.ask),tick.ask,sl,tp,"Sell"); } } } previousValue = Normalizado; if(Orden=="Sell" && rates[0].low <array_ma[0]) { trade.PositionClose(_Symbol,5); Print("cerro sell"); return; } if(Orden=="Buy" && rates[0].high >array_ma[0]) { trade.PositionClose(_Symbol,5); Print("cerro buy"); return; } }
このコードはMQL5言語で書かれており、外国為替市場で注文を出したりクローズしたりするために使用されます。このコードではまず、Normalizado変数の値が0から100の間かどうかを確認します。もしそうなら、Normalizadoの前の値が100かどうかを確認します。もしそうなら、Normalizadoの値が100より小さいかどうか、array_ma[0]の値が現在の買呼値より大きいかどうか、直近5レートの高値が最初のレートの安値より小さいかどうかを確認します。これらの条件がすべて満たされた場合、コードは「Open Order Buy」と表示し、買いであることをユーザーに警告し、Orden変数をBuyに設定し、損切りと利食いを設定し、指定されたパラメータで買い注文を出します。
Normalizadoの値が100より小さく、array_ma[0]の値が現在の売値より小さく、直近5レートの安値が最初のレートの高値より大きい場合、コードは「Open Order Sell」と表示し、売りであることをユーザーに警告し、Orden変数をSellに設定し、損切りと利食いのレベルを設定し、指定されたパラメータで売り注文を出します。この後、コードはNormalizadoの前の値を現在の値に設定します。最後に、コードはOrden変数がSellに設定されているかどうか、そして現在のレートの安値がarray_ma[0]の値より小さいかどうかを確認します。これらの条件が満たされれば、コードは売り注文をクローズし、「cerro sell」と表示します。同様に、Orden変数がBuyに設定され、現在のレートの高値がarray_ma[0]の値より大きい場合、コードは買い注文をクローズし、「cerro buy」と表示します。
結論
この戦略を市場に適用するためには最適化しなければなりませんが、市場分析に関する平均回帰的な考え方を提示することが目的でした。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/12830
![MQL5オブジェクト指向プログラミング(OOP)について](https://c.mql5.com/2/56/object-oriented-programming-avatar.png)
![MetaTrader 5用のMQTTクライアントの開発:TDDアプローチ](https://c.mql5.com/2/56/mqtt-avatar.png)
![MQL5のインタラクティブGUIで取引チャートを改善する(前編):移動可能なGUI (I)](https://c.mql5.com/2/55/Revolutionize_Your_Trading_Charts_Part_I_avatar.png)
![MQL5 - MetaTrader 5クライアントターミナルにビルトインされたトレードストラテジーの言語](https://c.mql5.com/i/registerlandings/logo-2.png)
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索