記事「オープニングレンジブレイクアウト日中取引戦略の解読」についてのディスカッション

 

新しい記事「オープニングレンジブレイクアウト日中取引戦略の解読」はパブリッシュされました:

オープニングレンジブレイクアウト(ORB)戦略は、市場が開いた直後に形成される初期の取引レンジが、買い手と売り手が価値に合意する重要な価格レベルを反映しているという考えに基づいて構築されています。特定のレンジを上抜けまたは下抜けするブレイクアウトを特定することで、市場の方向性が明確になるにつれて発生することが多いモメンタムを利用し、トレーダーは利益を狙うことができます。本記事では、Concretum Groupの論文から応用した3つのORB戦略を紹介します。

オープニングレンジブレイクアウト(ORB)戦略は、市場が開いた直後に形成される初期の取引レンジが、買い手と売り手が価値に合意する重要な価格レベルを反映しているという考えに基づいて構築されています。特定のレンジを上抜けまたは下抜けするブレイクアウトを特定することで、市場の方向性が明確になるにつれて発生することが多いモメンタムを利用し、トレーダーは利益を狙うことができます。 

本記事では、Concretum Groupの論文から応用された3つのORB戦略を紹介します。まず、研究の背景として、主要な概念および使用された方法論について解説します。その後、それぞれの戦略について、仕組みの解説、シグナルルールの一覧、統計的なパフォーマンス分析をおこないます。最後に、ポートフォリオの観点から戦略を検証し、分散投資というテーマにも焦点を当てます。  

本記事ではプログラミングの詳細な解説はおこなわず、主に研究課程に重点を置きます。具体的には、戦略の再現、分析、検証などの手順です。これは、取引の優位性を模索する読者や、これらの戦略の研究・再現過程に関心のある方に適しています。なお、エキスパートアドバイザー(EA)用のすべてのMQL5コードは公開しますので、読者ご自身でこのフレームワークを拡張していただくことも可能です。


作者: Zhuo Kai Chen

 
素晴らしい記事

追求するトレーダーにとって価値がある

私自身の経験から言うと
 
Timmy T #:
素晴らしい記事

を追求するすべてのトレーダーにとって価値がある。

私自身の経験から言えば

ありがとう、ティミー。

 

あなたがコードを書き、コミュニティと共有するために投資した時間と努力に感謝します。

私たちが適応している研究では、市場のオープンからクローズ(東部標準時の午前9時30分から午後4時)の間に取引する戦略に焦点を当てて います。私たちのブローカーはUTC+2/3を使用しているため、これはサーバー時間の 18:30-24:00になります

時間変換に関するあなたの考えを説明してもらえますか?

一般的なタイムコンバータを使用した場合、東部時間の9:30に変換すると、mt5がGMT+2の場合は16:30に なり、mt5がGMT+3の場合は17:30に なります。

ありがとうございました。

ファイル:
NY_GMT.png  12 kb
 
Sunjoo サーバー時間の 18:30-24:00に相当します

時間変換に関するあなたの考えを説明してもらえますか?

一般的なタイムコンバータを使用した場合、東部時間の9:30に変換すると、mt5がGMT+2の場合は16:30、GMT+3の場合は17:30に なります。

ありがとうございました。

おっしゃる通り、記事中のサーバー時間をニューヨーク株式市場のオープン時間に間違って変換していました。18:30ではなく、17:30にすべきでした。とはいえ、記事中の私のストラテジー・ルールはすべて、マーケット・オープンの1時間後に取引されるべきものと考えていただいて結構です。ご指摘ありがとうございました。

 

投稿ありがとう!

コードについての質問です:

MarketOpened関数の中で、あなたはこう使っています:

"if (currentHour >= startHour && currentMinute>=startMinute)return true;" <- これは、マーケットが開いている場合でも、毎時の始まりにいる場合はfalseを返すので、マーケットが開いている場合は、1時間の一部だけを取引するように見えます。 分が0にある場合のみ機能し、これはマーケットセッションの始まりではありません。

 
Digitus #:

投稿ありがとう!

コードについての質問です:

MarketOpened関数の中で、あなたは次のように使っています:

"if (currentHour >= startHour && currentMinute>=startMinute)return true;" <- これは、マーケットが開いている場合でも、毎時の始まりにいる場合はfalseを返すので、マーケットが開いている場合は、1時間の一部だけを取引するように見えます。 分が0である場合のみ機能し、これはマーケットセッションの始まりではありません。

OMG、これを見逃すなんて信じられない。そうあるべきだ:

    if ((currentHour >= startHour &&currentMinute>=startMinute)||currentHour>startHour)return true;

うっかりミスで本当に申し訳ない。注意深く読んで指摘してくれてありがとう。

 

Ps.ORB3では、マーケットのオープン時間を9:30にハードコードしました。ORB3では、マーケットオープン時間を9:30にハードコードしました。

//+------------------------------------------------------------------+
//| コンクレタム・バンド上限値の取得|
//+------------------------------------------------------------------+
double getUpperBand(int target_hour = 17, int target_min = 30) {
    // 現在のバーの時刻を取得
    datetime current_time = iTime(_Symbol, PERIOD_CURRENT, 0);
    MqlDateTime current_dt;
    TimeToStruct(current_time, current_dt);
    int current_hour = current_dt.hour;
    int current_min = current_dt.min;
    
    // ターゲット時間(例:サーバー時間17:30)における本日の始値を検索する。
    datetime today_start = iTime(_Symbol, PERIOD_D1, 0);
    int bar_at_target_today = getBarShiftForTime(today_start, target_hour, target_min);
    if (bar_at_target_today < 0) return 0; // ターゲットバーが存在しない場合は0を返す
    double open_target_today = iOpen(_Symbol, PERIOD_M1, bar_at_target_today);
    if (open_target_today == 0) return 0; // 有効な価格なし
    
    // 過去14日間に基づくシグマの計算
    double sum_moves = 0;
    int valid_days = 0;
    for (int i = 1; i <= 14; i++) {
        datetime day_start = iTime(_Symbol, PERIOD_D1, i);
        int bar_at_target = getBarShiftForTime(day_start, target_hour, target_min);
        int bar_at_HHMM = getBarShiftForTime(day_start, current_hour, current_min);
        if (bar_at_target < 0 || bar_at_HHMM < 0) continue; // バーが存在しない場合はスキップする
        double open_target = iOpen(_Symbol, PERIOD_M1, bar_at_target);
        double close_HHMM = iClose(_Symbol, PERIOD_M1, bar_at_HHMM);
        if (open_target == 0) continue; // 有効な初値がない場合はスキップする
        double move = MathAbs(close_HHMM / open_target - 1);
        sum_moves += move;
        valid_days++;
    }
    if (valid_days == 0) return 0; // 有効なデータがない場合は0を返す
    double sigma = sum_moves / valid_days;
    
    // 上限バンドを計算する
    double upper_band = open_target_today * (1 + sigma);
    
    // 上のバンド・レベルに青い点をプロットする。
    string obj_name = "UpperBand_" + TimeToString(current_time, TIME_DATE|TIME_MINUTES|TIME_SECONDS);
    ObjectCreate(0, obj_name, OBJ_ARROW, 0, current_time, upper_band);
    ObjectSetInteger(0, obj_name, OBJPROP_ARROWCODE, 159); // ドット記号
    ObjectSetInteger(0, obj_name, OBJPROP_COLOR, clrBlue);
    ObjectSetInteger(0, obj_name, OBJPROP_WIDTH, 2);
    
    return upper_band;
}

//+------------------------------------------------------------------+
//| コンクレタム・バンドの下限値を得る|
//+------------------------------------------------------------------+
double getLowerBand(int target_hour = 17, int target_min = 30) {
    // 現在のバーの時刻を取得
    datetime current_time = iTime(_Symbol, PERIOD_CURRENT, 0);
    MqlDateTime current_dt;
    TimeToStruct(current_time, current_dt);
    int current_hour = current_dt.hour;
    int current_min = current_dt.min;
    
    // ターゲット時間(例:サーバー時間17:30)における本日の始値を検索する。
    datetime today_start = iTime(_Symbol, PERIOD_D1, 0);
    int bar_at_target_today = getBarShiftForTime(today_start, target_hour, target_min);
    if (bar_at_target_today < 0) return 0; // ターゲットバーが存在しない場合は0を返す
    double open_target_today = iOpen(_Symbol, PERIOD_M1, bar_at_target_today);
    if (open_target_today == 0) return 0; // 有効な価格なし
    
    // 過去14日間に基づくシグマの計算
    double sum_moves = 0;
    int valid_days = 0;
    for (int i = 1; i <= 14; i++) {
        datetime day_start = iTime(_Symbol, PERIOD_D1, i);
        int bar_at_target = getBarShiftForTime(day_start, target_hour, target_min);
        int bar_at_HHMM = getBarShiftForTime(day_start, current_hour, current_min);
        if (bar_at_target < 0 || bar_at_HHMM < 0) continue; // バーが存在しない場合はスキップする
        double open_target = iOpen(_Symbol, PERIOD_M1, bar_at_target);
        double close_HHMM = iClose(_Symbol, PERIOD_M1, bar_at_HHMM);
        if (open_target == 0) continue; // 有効な初値がない場合はスキップする
        double move = MathAbs(close_HHMM / open_target - 1);
        sum_moves += move;
        valid_days++;
    }
    if (valid_days == 0) return 0; // 有効なデータがない場合は0を返す
    double sigma = sum_moves / valid_days;
    
    // 下限バンドを計算する
    double lower_band = open_target_today * (1 - sigma);
    
    // 下のバンド・レベルに赤い点をプロットする。
    string obj_name = "LowerBand_" + TimeToString(current_time, TIME_DATE|TIME_MINUTES|TIME_SECONDS);
    ObjectCreate(0, obj_name, OBJ_ARROW, 0, current_time, lower_band);
    ObjectSetInteger(0, obj_name, OBJPROP_ARROWCODE, 159); // ドット記号
    ObjectSetInteger(0, obj_name, OBJPROP_COLOR, clrRed);
    ObjectSetInteger(0, obj_name, OBJPROP_WIDTH, 2);
    
    return lower_band;
}

計算時間を変更することで、ストラテジーをさらに最適化できるかもしれません。)

 
Zhuo Kai Chen #:

OMG、これを見逃すなんて信じられない。そうあるべきだ:

うっかりミスで本当に申し訳ない。注意深く読んで、指摘してくれてありがとう。

オープンマーケットとクローズドマーケットを1つの関数に統合しましたので、ご心配なく。

bool MarketState()
{
   MqlDateTime structTime;
   TimeCurrent(structTime);
   structTime.sec = 0;
   structTime.hour = startHour;
   structTime.min = startMinute; 
   datetime timeStart = StructToTime(structTime);
   structTime.hour = endHour;
   structTime.min = endMinute;   
   datetime timeEnd = StructToTime(structTime);
   if(TimeCurrent() >= timeStart && TimeCurrent() < timeEnd)return true;
   else return false;
}


もう一つ、バックテストにブローカーのOHLCデータを使用していますが、遅延はありません。これらのバックテストは、スリッページとリクオートのためのランダムな遅延がある実際のティックデータで行われたバックテストと比較すると、少し楽観的なようです。

ご苦労様でした!

 
Digitus #:

大丈夫、オープンマーケットとクローズドマーケットはすでに一つの機能に統合してある。


もう一つ、バックテストにはブローカーのOHLCデータを使用し、遅延はありません。これらのバックテストは、スリッページとリクオートのためのランダムな遅延を持つ実際のティックデータで行われたバックテストと比較して、少し楽観的なようです。

ご苦労様でした!

ご改造ありがとうございます!私のGithubですべてのコードを更新しました。

ご心配いただいている点についてですが、取引ロジックは新しいバーごとに発生し、ティックの動きには関与しません。その上、平均保有時間は数時間のようなもので、スリッページが大きな問題になることはないと思います。5年以上リアルティックのデータを提供しているブローカーはほとんどありませんし、OHLCは1分足で十分です。

 
素晴らしい記事