チャネルブレイクアウトパターン
Dmitriy Gizlyk | 26 2月, 2018
はじめに
世界市場は古くから、売り手と買い手の間の闘いです。売り手はより高い価格で売ることによってより多くを稼ぎたいと思っていますが、買い手は自分の稼いだお金を使うつもりはなく、より安い価格を払うことを望んでいます。経済理論によれば、真の価格は需給の平等点で見いだされます。それは本当であるようです。しかし、供給と需要の量が絶えず変化しているため、問題は市場のトレンドにあります。
この闘争は価格の変動をもたらします。これらの変動はチャネルを形成し、市場のトレンドを見出すためにトレーダーによって分析されます。次に、これらの動きは高次の変動を形成します。トレンド変化の初期兆候の1つは、形成された価格チャネルのブレイクアウトです。
1. 戦略の理論的側面
トレンドラインに沿った価格チャネルは、主要なグラフ分析の形状を参照します。価格チャネルは、現在のトレンドとこのトレンド内の価格変動の振幅を示します。現在のトレンドに応じて、チャネルは上向き、下向き、または横向き(平ら)のいずれかになります。
MetaTrader 5ターミナルでは下記の4種類のチャネルがサポートされています。
- 等距離チャネル
- 標準偏差チャネル
- 回帰チャネル
- アンドリュースピッチフォーク
一例として、EURUSD M30チャートと価格変動を分析します。
上記のチャートは、トレンドに分けて3つの価格チャネルを印づけることができます。下のチャートには等距離チャネルが示されます。下向きチャネルは赤線で、上向きチャネルは青線で表示されます。下向きチャネルの描画は、上位チャネル境界から始まり、価格変動の高低に基づいてトレンドを決定します。下の境界は、上の境界と平行に低価格線で構築されます。下の境界は、最大偏差または平均偏差で描くことができます。上向きチャネルの構成は逆です。下の境界が最初に描画され、次に上の境界が描画されます。横向きのチャネルを描くときは前のトレンドに注意を払う必要があります。なぜなら、フラットな価格変動は前の動きの補正として機能することが多く、これはフラットな期間の後に続く可能性があるからです。
チャネル取引には、チャネル内取引(トレンド戦略)とチャネルブレイクアウト取引(反トレンド戦略)の2種類の戦略が使用されます。本稿では、トレンドの変化を示すチャネルブレイクアウト戦略について説明します。
トレンドが変化すると、価格は現在のトレンドとは反対の方向からチャネルを出ます。チャネルは、ローソク足が境界を超えて閉じると壊れたとみなされます。
チャネルブレイクアウトの後、価格はその境界に戻り、新しいトレンド方向にのみ移動することを考慮します。この動きによって、しばしば、価格が動くの前に逆指値が発動されます。これを避けるために、価格が壊れたチャネルの境界に戻ったときに市場にエントリします。
2. パターン検索の自動化
パターン検出アルゴリズムを作成するために、Dmitry Fedoseevが記事[1]で提案した方法を使用します。この記事で説明されている、指標から水平方向の形成の定義を使用しましょう。このコードをCChannelクラスに追加します。
そこで、ブレイクアウトの直後ではなく、チャネル境界に価格が戻った後にポジションを開くことに決めました。この場合、EAがすでに新しいチャネルを探している間に、価格がチャネルに戻るのを待っているという状況が発生する可能性があります。複数チャネルでの並列操作を可能にするために、作成されたクラスは1つのパターンのみを見つけて処理します。すべてのクラスを1つの配列にまとめましょう。パターンが処理され、適切な注文が開かれるとすぐに、クラスが削除されます。したがって、クラスでジグザグ指標を初期化することによって、各クラスの指標を呼び出す必要があります。これを避けるために、メインプログラムの指標を初期化し、指標のハンドルだけをクラスに渡します。
さらに、チャネルの重複を避けるために、初期化時に以前のチャネルブレイクアウト時間をクラスに渡します。これにより、次のクラスインスタンスが前のクラスインスタンスのブレイクアウト後にチャネルを検索することが保証されます。
下記にこのクラスを示します。
class CChannel : public CObject { private: string s_Symbol; // 銘柄 ENUM_TIMEFRAMES e_Timeframe; // 時間枠 int i_Handle; // 指標ハンドル datetime dt_LastCalc; // 最後に計算されたバー SPeackTrough PeackTrough[]; // ZigZagピークの配列 int CurCount; // ピーク数 int PreDir; // 1つ前のZigZagの足の方向 int CurDir; // 今のZigZagの足の方向 int RequiredCount; // チャネル内の最小ピーク double d_Diff; bool b_FoundChannel; bool b_Breaked; datetime dt_Breaked; double d_BreakedPrice; void RefreshLast(datetime time,double v); void AddNew(datetime time,double v,int d); bool CheckForm(double base); double GetRessistPrice(SPeackTrough &start_peack, datetime time); double GetSupportPrice(SPeackTrough &start_peack, datetime time); bool DrawChannel(MqlRates &break_bar); bool DrawChannel(void); bool UnDrawChannel(void); public: CChannel(int handle,datetime start_time,string symbol,ENUM_TIMEFRAMES timeframe); ~CChannel(); bool Calculate(ENUM_ORDER_TYPE &type,double &stop_loss,datetime &deal_time,bool &breaked,datetime &breaked_time); };
クラス初期化関数のパラメータには、指標ハンドル、チャネル検索開始時刻、銘柄名、作業時間枠などの情報が渡されます。関数本体では、渡されたデータは適切な変数に保存され、初期値は他の変数に割り当てられます。
CChannel::CChannel(int handle,datetime start_time,string symbol,ENUM_TIMEFRAMES timeframe) : RequiredCount(4), CurCount(0), CurDir(0), PreDir(0), d_Diff(0.1), b_Breaked(false), dt_Breaked(0), b_FoundChannel(false) { i_Handle=handle; dt_LastCalc=fmax(start_time-1,0); s_Symbol=symbol; e_Timeframe=timeframe; }
UnDrawChannel関数はクラス初期化解除関数で呼び出され、以前に追加されたグラフィカルオブジェクトをチャートから削除します。
主な操作はCalculate関数で実行されます。そのパラメータには、チャネルブレイクアウトに関する情報を書き込むための変数への参照とパターンによって開かれた取引が含まれます。パラメータの参照を使用すると、関数から複数の変数の値を返すことができます。
最後に保存されたピークから始まる銘柄の気配値は、関数の先頭で配列に読み込まれます。必要な気配値の読み込みが失敗した場合、関数はfalseを返します。
bool CChannel::Calculate(ENUM_ORDER_TYPE &type,double &stop_loss,datetime &deal_time, bool &breaked,datetime &breaked_time) { MqlRates rates[]; CurCount=ArraySize(PeackTrough); if(CurCount>0) { dt_LastCalc=PeackTrough[CurCount-1].Bar; CurDir=PeackTrough[CurCount-1].Dir; } int total=CopyRates(s_Symbol,e_Timeframe,fmax(dt_LastCalc-PeriodSeconds(e_Timeframe),0),TimeCurrent(),rates); if(total<=0) return false;
この後でリターン変数を初期化します。
stop_loss=-1; breaked=b_Breaked; breaked_time=dt_Breaked; deal_time=0;
その後、各バーのデータを処理するループが始まります。まず、新しいジグザグピークの出現が確認されます。新しいピークが現れるか、前のピークが再描画されると、データは、RefreshLastおよびAddNew関数を使用して配列に保存されます。
for(int i=0;i<total;i++) { if(rates[i].time>dt_LastCalc) { dt_LastCalc=rates[i].time; PreDir=CurDir; } else continue; // 新しい最高値 double lhb[2]; if(CopyBuffer(i_Handle,4,total-i-1,2,lhb)<=0) return false; if(lhb[0]!=lhb[1]) { if(CurDir==1) RefreshLast(rates[i].time,rates[i].high); else AddNew(rates[i].time,rates[i].high,1); } // 新しい最小値 double llb[2]; if(CopyBuffer(i_Handle,5,total-i-1,2,llb)<=0) return false; if(llb[0]!=llb[1]) { if(CurDir==-1) RefreshLast(rates[i].time,rates[i].low); else AddNew(rates[i].time,rates[i].low,-1); }
次のステップは、チャネルを識別するために必要なピークの最小量が形成されているかを確認することです。形成されている場合、現在の価格変動がチャネル形成に対応するかどうかを確認します。この確認はCheckForm関数で行われます。
対応する場合は、b_FoundChannel変数にtrueが割り当てられます。対応しない場合は、ピークのリストから最も古いピークが削除され、初期値が変数に割り当てられ、操作はループの先頭に戻ります。
double base=(CurCount>=2 ?MathAbs(PeackTrough[1].Val-PeackTrough[0].Val) : 0); if(CurCount>=RequiredCount && !b_FoundChannel) { if(CurDir!=PreDir) { if(CheckForm(base)) { b_FoundChannel=true; } else { UnDrawChannel(); dt_LastCalc=PeackTrough[0].Bar+PeriodSeconds(e_Timeframe); ArrayFree(PeackTrough); CurCount=0; CurDir=0; PreDir=0; b_Breaked=false; dt_Breaked=0; b_FoundChannel=false; deal_time=0; total=CopyRates(s_Symbol,e_Timeframe,fmax(dt_LastCalc,0),TimeCurrent(),rates); i=-1; continue; } } }
チャネルが見つかったら、ブレイクアウトが検索されます。チャネルが壊れたら、b_Breaked変数とbreaked変数にtrueが割り当てられます。ブレイクアウトローソク足の開いた時間は変数dt_Breakedとbreaked_timeに保存され、ローソク足の極値はd_BreakedPriceに保存されます。その後DrawChannel関数が呼ばれてチャート上にチャネルとブレイクアウトポイントが描画されます。この関数は、現在のトレンドと反対方向のブレイクアウトを検索することに注意してください。トレンドが強くなり、価格が現在のトレンド方向からチャネルを出る場合、クラスは新しいクラスインスタンスの作成を初期化してチャネルを検索します(下記のグローバルSearchNewChannel関数を参照)。
ブレイクアウトが見つかると、市場エントリパターンが検索されます。エントリシグナルは、価格がチャネルを出てからその境界に戻る場合に生成されます。追加の買い/売り取引のエントリシグナルは、ブレイクアウトローソク足の極値の上/下でローソク足が閉じられることです。このパターンは、価格が強い動きの中でチャネルを壊し、訂正せずにさらに移動した場合に市場にエントリするために使用されます。
シグナルが生成されると、必要な注文タイプが 'type'変数に書き出され、逆指値が計算されて適切な変数に保存されます。シグナルが出現したバーが開いた時刻はdeal_time変数に書き込まれます。
if(b_FoundChannel) { if(PeackTrough[0].Dir==1) { if(PeackTrough[0].Val>PeackTrough[2].Val) { if(!b_Breaked) { if((rates[i].close-GetRessistPrice(PeackTrough[0],rates[i].time))>=(d_Diff*base)) { b_Breaked=breaked=true; dt_Breaked=breaked_time=rates[i].time; d_BreakedPrice=rates[i].high; DrawChannel(rates[i]); continue; } if(CurCount>4 && PeackTrough[CurCount-1].Dir==1 && (GetRessistPrice(PeackTrough[1],rates[i].time)-PeackTrough[CurCount-1].Val)>0) { int channels=ArraySize(ar_Channels); if(ar_Channels[channels-1]==GetPointer(this)) { SearchNewChannel(PeackTrough[CurCount-3].Bar-PeriodSeconds(e_Timeframe)); } } } else { if(rates[i].time<=dt_Breaked) continue; //--- double res_price=GetRessistPrice(PeackTrough[0],rates[i].time); if(((rates[i].low-res_price)<=0 && (rates[i].close-res_price)>0 && (rates[i].close-res_price)<=(d_Diff*base)) || rates[i].close>d_BreakedPrice) { type=ORDER_TYPE_BUY; stop_loss=res_price-base*(1+d_Diff); deal_time=rates[i].time; return true; } } } else { UnDrawChannel(); dt_LastCalc=PeackTrough[0].Bar+PeriodSeconds(e_Timeframe); ArrayFree(PeackTrough); CurCount=0; CurDir=0; PreDir=0; b_Breaked=false; dt_Breaked=0; b_FoundChannel=false; deal_time=0; total=CopyRates(s_Symbol,e_Timeframe,fmax(dt_LastCalc,0),TimeCurrent(),rates); i=-1; continue; } } else { if(PeackTrough[0].Val<PeackTrough[2].Val) { if(!b_Breaked) { if((GetSupportPrice(PeackTrough[0],rates[i].time)-rates[i].close)>=(d_Diff*base)) { b_Breaked=breaked=true; dt_Breaked=breaked_time=rates[i].time; d_BreakedPrice=rates[i].low; DrawChannel(rates[i]); continue; } if(CurCount>4 && PeackTrough[CurCount-1].Dir==-1 && (PeackTrough[CurCount-1].Val-GetSupportPrice(PeackTrough[1],rates[i].time))>0) { int channels=ArraySize(ar_Channels); if(ar_Channels[channels-1]==GetPointer(this)) { SearchNewChannel(PeackTrough[CurCount-3].Bar-PeriodSeconds(e_Timeframe)); } } } else { if(rates[i].time<=dt_Breaked) continue; double sup_price=GetSupportPrice(PeackTrough[0],rates[i].time); if(((sup_price-rates[i].high)<=0 && (sup_price-rates[i].close)>0 && (sup_price-rates[i].close)<=(d_Diff*base)) || rates[i].close<d_BreakedPrice) { type=ORDER_TYPE_SELL; stop_loss=sup_price+base*(1+d_Diff); deal_time=rates[i].time; return true; } } } else { UnDrawChannel(); dt_LastCalc=PeackTrough[0].Bar+PeriodSeconds(e_Timeframe); ArrayFree(PeackTrough); CurCount=0; CurDir=0; PreDir=0; b_Breaked=false; dt_Breaked=0; b_FoundChannel=false; deal_time=0; total=CopyRates(s_Symbol,e_Timeframe,fmax(dt_LastCalc,0),TimeCurrent(),rates); i=-1; continue; } } } } return b_Breaked; }
CChannelクラスとその関数の完全なコードは下記に添付されています。
3. 戦略テスト用エキスパートアドバイザーの作成
#resource "\\Indicators\\ZigZags\\iUniZigZagSW.ex5" #include <\\Break_of_channel_DNG\\Channel.mqh> #include <Trade\\Trade.mqh>
エキスパートアドバイザーのパラメータは、指標パラメータと同じです。
input ESorce SrcSelect = Src_HighLow; input EDirection DirSelect = Dir_NBars; input int RSIPeriod = 14; input ENUM_APPLIED_PRICE RSIPrice = PRICE_CLOSE; input int MAPeriod = 14; input int MAShift = 0; input ENUM_MA_METHOD MAMethod = MODE_SMA; input ENUM_APPLIED_PRICE MAPrice = PRICE_CLOSE; input int CCIPeriod = 14; input ENUM_APPLIED_PRICE CCIPrice = PRICE_TYPICAL; input int ZZPeriod = 50;
EAには4つのグローバル変数があり、下記が書き入れられます。
- 指標ハンドル
- チャネルへのポインタの配列(CChannelクラスのオブジェクト)
- CTradeクラスへのポインタ(取引操作を実行するために使用)
- 最後のブレイクアウトが発生したバーが開いた時刻
int zz_handle; CChannel *ar_Channels[]; CTrade *Trade; datetime dt_last_break;
EAのOnInit関数では、指標を呼び出して必要なクラスを初期化します。エラーが発生した場合、この関数はINIT_FAILEDを返します。
int OnInit() { //--- zz_handle=iCustom(Symbol(),Period(),"::Indicators\\ZigZags\\iUniZigZagSW",SrcSelect, DirSelect, RSIPeriod, RSIPrice, MAPeriod, MAShift, MAMethod, MAPrice, CCIPeriod, CCIPrice, ZZPeriod); if(zz_handle==INVALID_HANDLE){ Alert("Error load indicator"); return(INIT_FAILED); } //--- Trade=new CTrade(); if(CheckPointer(Trade)==POINTER_INVALID) return INIT_FAILED; //--- dt_last_break=0; //--- return(INIT_SUCCEEDED); }
メモリをクリアするために、OnDeinit関数で使用されているすべてのクラスインスタンスを削除します。
void OnDeinit(const int reason) { //--- int total=ArraySize(ar_Channels); for(int i=0;i<total;i++) { if(CheckPointer(ar_Channels[i])!=POINTER_INVALID) delete ar_Channels[i]; } ArrayFree(ar_Channels); if(CheckPointer(Trade)!=POINTER_INVALID) delete Trade; }
主な操作はOnTick関数で実行されます。
チャネルは、ローソク足が境界を超えて閉じると壊れたとみなされるということはすでに決定されました。チャネルは完全に形成されたジグザグピークに基づいて描画されるので、EAはすべてのティックでアクションを実行する必要はありません。したがって、この関数で最初に行うことは、新しいバーの開きを確認することです。
void OnTick() { //--- static datetime last_bar=0; if(last_bar>=SeriesInfoInteger(_Symbol,PERIOD_CURRENT,SERIES_LASTBAR_DATE)) return; last_bar=(datetime)SeriesInfoInteger(_Symbol,PERIOD_CURRENT,SERIES_LASTBAR_DATE);
last_bar変数はグローバルに宣言されておらず、このコードブロックでのみ使用されることに注意してください。ご存知のように、すべてのローカル変数は対応する関数の開始後毎回初期化され、そこに保存されたデータは次のOnTick開始時に失われます。データの損失を避けるために、この変数はstatic修飾子で宣言されており、その値は後続の関数の開始時に保持されます。
次のステップでは、配列に格納されているチャネル数を決定します。チャネルがない場合は、最後に保存したブレイクアウトから検索を開始します。
int total=ArraySize(ar_Channels); if(total==0) if(SearchNewChannel(dt_last_break)) total++;
その後、保存された各チャネルをループで処理します。まず、クラスオブジェクトへのポインタが確認されます。ポインタが正しくない場合は、配列からポインタを削除し、次のポインタに移動します。
for(int i=0;i<total;i++) { if(CheckPointer(ar_Channels[i])==POINTER_INVALID) { DeleteChannel(i); i--; total--; continue; }
その後クラスのCalculate関数が呼ばれます。そのパラメータは、関数が実行された操作の結果に関する情報を返す変数への参照です。これらの変数は関数呼び出しの前に宣言します。さらに、この関数はbool値を返すため、'if'ステートメントの論理式として呼び出して、"true"が返された場合にのみ操作が実行されます。
ENUM_ORDER_TYPE type; double stop_loss=-1; bool breaked=false; datetime breaked_time=0; datetime deal_time=0; if(ar_Channels[i].Calculate(type,stop_loss,deal_time,breaked,breaked_time)) {
関数が正常に実行された後、最後のチャネルブレイクアウトが発生したバーの時間を再保存します。
dt_last_break=fmax(dt_last_break,breaked_time);
最後に保存されたチャネルが壊れた場合は、最後のブレイクアウトの後に形成された新しいチャネルの検索を初期化します。
if(breaked && i==(total-1)) if(SearchNewChannel(breaked_time)) { if(total>=5) i--; else total++; }
SearchNewChannel関数には直近の5つのチャネルが格納されます。したがって、 'total'変数の値は、配列内にあるのが5チャネル未満の場合にのみ増加され、それ以外の場合は、処理中のチャネルのインデックスを示す変数iを減らします。
次に、シグナルの出現を確認してポジションを開き、必要に応じて対応する注文を送信します。このエキスパートアドバイザーは、テスト目的でのみ設計されています。資産管理ブロックはないため、すべての取引が最小ロットで開かれます。注文を送信した後は、処理されたチャネルを削除します。
if(deal_time>=0 && stop_loss>=0) { int bars=Bars(_Symbol,PERIOD_CURRENT,deal_time,TimeCurrent()); double lot=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); switch(type) { case ORDER_TYPE_BUY: if(PositionSelect(_Symbol) && PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) Trade.PositionClose(_Symbol); if(bars<=2) Trade.Buy(lot,_Symbol,0,fmax(stop_loss,0)); break; case ORDER_TYPE_SELL: if(PositionSelect(_Symbol) && PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) Trade.PositionClose(_Symbol); if(bars<=2) Trade.Sell(lot,_Symbol,0,fmax(stop_loss,0)); break; } DeleteChannel(i); i--; total--; } } } }
このプログラムコードブロックの2つの重要な点に注意してください。
1. 注文は、シグナルが前のローソク足よりも早く現れなかった場合にのみ出されます。この制限が追加されたのは、エキスパートアドバイザーが履歴データを処理できるからです(たとえば、初期化中またはターミナルがサーバから切断された後など)。この場合、シグナルが遅れて現れることがあり、新たな取引が制御不能な損失につながる可能性があります。
2. エキスパートアドバイザーは逆指値注文を開きますが、指値は指定されていません。したがって、シグナルが出現すると、必要に応じて反対のポジションが閉じられます。
コードでは2つの補助関数SearchNewChannelとDeleteChannelが追加で使用されます。
SearchNewChannel関数はチャネルの配列内のCChannelクラスの新しいインスタンスを初期化します。関数の開始時には、指標ハンドルが確認されます。ハンドルが正しくない場合は、関数を終了て'false'を返します。
bool SearchNewChannel(datetime time) { if(zz_handle==INVALID_HANDLE) return false;
エキスパートアドバイザーを作成するときは、最後の5つのチャネルを使用することにしました。そのため、次のステップでは、入れるに格納されているチャネル数を確認し、必要に応じて最も古いチャネルを削除します。残りの4つのチャネルは配列の先頭に移動されます。
int total=ArraySize(ar_Channels); if(total>4) { for(int i=0;i<total-4;i++) { if(CheckPointer(ar_Channels[i])!=POINTER_INVALID) delete ar_Channels[i]; } for(int i=0;i<4;i++) ar_Channels[i]=ar_Channels[total-4+i]; if(total>5) { if(ArrayResize(ar_Channels,5)>0) total=5; else return false; } }
チャネルが5つ未満の場合、配列は増加されます。
else { if(ArrayResize(ar_Channels,total+1)>0) total++; else return false; }
関数の最後では、配列の最後のセルでCChannelクラスの新しいインスタンスを初期化します。
ar_Channels[total-1]=new CChannel(zz_handle,time,_Symbol,PERIOD_CURRENT); return (CheckPointer(ar_Channels[total-1])!=POINTER_INVALID); }
DeleteChannel関数は、指定されたインデックスを持つCChannelクラスインスタンスを配列から削除します。関数の始めに、インデックスが既存の配列内にあるかどうかを確認します。そうでない場合は、関数を終了して'false'を返します。
bool DeleteChannel(int pos) { int total=ArraySize(ar_Channels); if(pos<0 || pos>=total) return false;
指定されたオブジェクトは削除され、残りのオブジェクトは1つ下のセルに移動されます。
delete ar_Channels[pos]; for(int i=pos;i<total-1;i++) ar_Channels[i]=ar_Channels[i+1];
関数が開始する前に配列にオブジェクトが1つしかない場合、配列は解放され、それ以外の場合は、1要素によって減らされます。
if(total==1) { ArrayFree(ar_Channels); return true; } return (ArrayResize(ar_Channels,total-1)>0); }
エキスパートアドバイザーのテストの完全なコードは下記に添付されています。
4. エキスパートアドバイザーのテスト
4.1. H1時間枠
このような戦略は、より静的で偶発的な騒音の影響を受けにくい、より長い時間枠でうまく作動すると考えられています。したがって、最初のテストはH1時間枠で実行されました。テストは、パラメーターの予備的な最適化なしに2017年のEURUSDデータに対して行われました。
最初のテストでは、この戦略が利益を上げることができることが示されました。テスト期間中にEAは26取引しか行っておらず、10ポジションが開かれました。ポジションの80%は利益を持って決済されました。これによって残高は滑らかに成長しました。テスト結果に基づく利益率は4.06でした。これは良い結果です。
しかし、年間10ポジションでは不十分なので、取引回数を増やすために、パラメータを変更せずにEAをより短期間でテストすることに決めました。
4.2. M1時間枠
2番目のテストは、同じパラメータを使用してM15時間枠で実行されました。
取引数は増加し、EAはテスト期間中に63取引を開きました。 しかし、この増加は定性的な結果をもたらしませんでした。総利益は、H1での$133.46に比べて$130.60でした。勝ち取引の割合は、ほぼ2分の1の41.27%に減少しました。残高チャートはより悲惨で、利益率は前のテストの約3分の1の1.44です。
4.3. 他銘柄でのテスト
テスト結果は、戦略がH1の時間枠でより良い結果を示したことを示しています。他の時間枠での戦略の使用可能性を評価するために、さらに3つのテストを実施しました。H1時間枠、同じパラメータとテスト期間を使用しました。添付ファイルには完全なテスト結果がありますが、主な数値は以下の表に示されています。
銘柄 | 取引数 | 約定数 | 勝ち取引(%) | 利益率 | 回復率 | ポジション平均保有時間(時間) |
---|---|---|---|---|---|---|
EURUSD | 10 | 26 | 80 | 4.06 | 1.78 | 552 |
GBPUSD | 2 | 8 | 50 | 1.47 | 0.23 | 2072 |
EURGBP | 5 | 14 | 0 | 0.0 | -0.71 | 976 |
USDJPY | 6 | 17 | 83 | 0.72 | -0.19 | 875 |
最悪の結果はEURGBPのペアで得られました。5つの取引のいずれも利益をもたらしませんでした。しかし、価格チャートを分析すると、戦略に従ってエントリの利益潜在性が失われることがあることがわかります。下のスクリーンショットからわかるように、チャネルブレイクアウト戦略は良質のエントリシグナルを生成します。しかし、より安定した操作のためには適切なエグジット戦略が必要です。これはポジション保持時間によって確認されます。テストによると、平均ポジション保持時間は銘柄に応じて550〜2100時間です。このような長期間では、市場のトレンドは数回変化することがあります。
終わりに
本稿では、チャネルブレイクアウトパターンを扱うエキスパートアドバイザーの例を説明しました。テスト結果によって、この戦略が市場エントリシグナルの生成に使用できることが示されました。また、テストでは、この戦略がより長い時間枠でよりうまく機能することが確認されました。ただし、戦略を成功させるためには、ポジションエグジットシグナルを追加する必要があります。この戦略が生成する市場エントリシグナルは正確ではありますがまれであり、これらのシグナルはタイムリーな利益を確保するには十分ではありません。これはしばしば浮動利益や預金の損失につながります。
エキスパートアドバイザーには資金管理モジュールがなく、計算や取引操作で発生する可能性のあるエラーに対するチェックもありません。したがって、EAを実際の口座で使用することは推奨されていません。しかし、誰もそれに必要な機能を追加することができます。
参照文献
以下は本稿で使用されているプログラムです。
# |
名称 |
種類 |
説明 |
---|---|---|---|
1 | Break_of_channel_DNG.mq5 | エキスパートアドバイザー | 戦略テスト用エキスパートアドバイザー |
2 | Channel.mqh | クラスライブラリ | 価格チャネルとポジションを開くシグナルのを検索するクラス |
3 | Break_of_channel_DNG.mqproj | プロジェクト説明ファイル | |
4 | iUniZigZagSW.ex5 | 指標 | 汎用ジグザグ |
5 | Reports.zip | Zip | エキスパートアドバイザーテスト報告 |