English Deutsch
preview
SMC (Smart Money Concepts)で取引のレベルアップを実現する:OB、BOS、FVG

SMC (Smart Money Concepts)で取引のレベルアップを実現する:OB、BOS、FVG

MetaTrader 5 |
150 12
Hlomohang John Borotho
Hlomohang John Borotho

はじめに

トレーダーはしばしば戦略の一貫性に課題を抱え、明確な枠組みがないまま、インジケーターやセットアップ、手法を次々と切り替えてしまうことがあります。このような枠組みの欠如は、市場環境が変化する中で混乱や機会の損失、そしてフラストレーションの増大を招きやすく、従来のツールでは対応が難しくなることがあります。本トピックでは、複数のSMC(Smart Money Concepts、スマートマネーコンセプト)戦略を1つの強力なEAに統合するメリットについて説明します。

SMC(Smart Money Concepts、スマートマネーコンセプト)は、OB(Order Blocks、注文ブロック)、BOS(Break of Structure、ブレイクオブストラクチャ)、FVG(Fair Value Gaps、公正価格ギャップ)といった要素を通じて、市場の動きを理解するための体系的なアプローチを提供します。これらを1つのEAに統合することで、ワークフローを簡素化し、意思決定を自動化し、最も強力なプライスアクションシグナルに集中することができます。自動モードでのシームレスな実行はもちろん、個々のコンセプトを選択して使うことも可能で、迷いを減らし、より効率的で一貫性があり、市場の変化に適応した取引を実現します。



EAのロジック

OB

OB(Order Blocks、注文ブロック)とは、大きな価格変動が始まる直前に形成された最後の陽線または陰線を指します。これは、機関投資家が注文を仕込んだ可能性のある重要なゾーンを示すことが多いです。これらのゾーンに再び到達すると、強力なサポートまたはレジスタンスとして機能しやすく、高確率の取引機会を提供します。本EAでは、OBの検出精度をさらに高めるために、フィボナッチインジケーターによる確認を組み合わせています。これにより、エントリーが主要なリトレースメントやエクステンションの水準と一致するように設計されています。前回の記事でも説明したように、OBにフィボナッチによる確認を組み合わせることで、弱いセットアップを排除し、エントリーから取引管理に至るまで、より信頼性の高いフレームワークを提供することができます。

FVG

FVG(Fair Value Gaps、公正価格ギャップ)とは、価格の動きに不均衡が生じた際に発生するもので、通常は勢いのある大きな値動きによって形成され、前後のローソク足のヒゲに隙間が生じることがあります。このギャップは、市場における非効率性、すなわちすべての注文が適切にマッチされなかった領域を示しており、価格は多くの場合、その不均衡を「埋める」ために一度その領域へ戻ってから、元々の方向へと動きを再開します。FVGを検出し、その周囲で取引をおこなうことで、価格がこれらのゾーンへリトレース(戻り)する可能性を先読みし、リスクとリワードが明確な精度の高いエントリー機会を得ることができます。

BOS

BOS(Break of Structure、ブレイクオブストラクチャ)とは、価格が前回のスイングハイを上抜ける、あるいは前回のスイングローを下抜ける際に発生し、市場の方向性が変化する可能性を示すシグナルです。前回の記事では、スイングハイが破られた場合に売りエントリーをおこない、弱気(下落)モメンタムを活用する方法に焦点を当てていました。しかし本アプローチでは、BOSのロジックを市場のトレンドに沿う形で改良しています。具体的には、スイングハイが破られた場合は強気の力を確認し、買いの機会として捉えます。一方、スイングローが破られた場合は、売りの機会として認識します。この変更により、取引は支配的な市場の流れに沿っておこなわれ、逆張りによる無駄なリスクを避けることができます。

システムアーキテクチャ



導入手順

//+------------------------------------------------------------------+
//|                                                 SMC_ALL_IN_1.mq5 |
//|                        GIT under Copyright 2025, MetaQuotes Ltd. |
//|                     https://www.mql5.com/ja/users/johnhlomohang/ |
//+------------------------------------------------------------------+
#property copyright "GIT under Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/ja/users/johnhlomohang/"
#property version   "1.01"
#property description "Unified SMC: FVG + Order Blocks + BOS. Detect + Draw + Trade."

#include <Trade/Trade.mqh>
#include <Trade/PositionInfo.mqh>

CTrade         trade;
CPositionInfo  pos;

enum ENUM_STRATEGY
{
   STRAT_OB,         // Use Order Blocks Only
   STRAT_FVG,        // Use FVGs Only
   STRAT_BOS,        // Use Break of Structure Only
   STRAT_AUTO        // Auto (All SMC Concepts)
};
enum SWING_TYPE{
   SWING_OB,
   SWING_BOS,
};

本コードでは、MetaTraderの取引ライブラリ、特にCTradeとCPositionInfoを利用しており、EAはこれにより取引の発注や管理、さらに保有ポジションの情報取得が可能になります。コードの冒頭でこれらのクラスを準備することで、EAは注文の実行やポジション追跡に必要な基本的なツールを確実に利用できるようになっています。

次に、戦略の柔軟性とスイングポイントの分類を列挙型で定義しています。ENUM_STRATEGYは、EAがどの手法に従って動作するかをトレーダーが選択できるように設計されており、OBのみ、FVGのみ、BOSのみ、あるいは3つのコンセプトを動的に活用する完全自動モード(STRAT_AUTO)を選ぶことができます。同様に、SWING_TYPEの列挙型は、OB用に使用するスイングポイントとBOS解析用のスイングポイントを区別するために用意されています。この構造により、コードはモジュール化されて柔軟性が高く、トレーダーは異なるSMCアプローチを試したり、市場状況に応じてEAに自動判断させたりすることが可能です。

//----------------------------- Inputs ------------------------------//
input ENUM_STRATEGY TradeStrategy   = STRAT_AUTO;
input double        In_Lot          = 0.02;
input double        StopLoss        = 3500;   // points
input double        TakeProfit      = 7500;   // points
input long          MagicNumber     = 76543;

input int           SwingPeriod     = 5;      // bars each side to confirm swing
input int           SwingProbeBar   = 5;      // bar index we test for swings (>=SwingPeriod)
input double        Fib_Trade_lvls  = 61.8;   // OB retrace must reach this %
input bool          DrawBOSLines    = true;

input int           FVG_MinPoints   = 3;      // minimal gap in points
input int           FVG_ScanBars    = 20;     // how many bars to scan for FVGs
input bool          FVG_TradeAtEQ   = true;   // trade at 50% of the gap (EQ)
input bool          OneTradePerBar  = true;

//---------------------------- Colors -------------------------------//
#define BullOB   clrLime
#define BearOB   clrRed
#define BullFVG  clrPaleGreen
#define BearFVG  clrMistyRose
#define BOSBull  clrDodgerBlue
#define BOSBear  clrTomato

このコードのセクションでは、トレーダーが自身の取引スタイルやリスク管理の好みに合わせてカスタマイズできる入力パラメータが定義されています。TradeStrategy入力パラメータにより、EAがOBのみ、FVGのみ、BOSのみで取引するか、あるいは3つのコンセプトを自動的に組み合わせるかを選択することができます。リスクおよび資金管理はIn_Lot、StopLoss、TakeProfit、MagicNumberで制御され、適切なロットサイズ、保護用ストップ、利益確定ポイント、ユニークな取引IDが確保されます。さらに、スイング検出パラメータであるSwingPeriodやSwingProbeBarは、スイングハイやスイングローを特定する際に考慮するバーの本数を指定し、Fib_Trade_lvlsはOB取引がフィボナッチリトレースメントによる確認と整合するように設定されています。DrawBOSLinesオプションは、チャート上でBOSのレベルを直接表示できるようにすることで、視覚的な柔軟性を提供します。

次にFVG設定があり、ギャップの検出方法と取引方法をより細かく制御できます。FVG_MinPointsは、重要な不均衡のみを対象とすることを保証し、FVG_ScanBarsはEAが過去どの範囲まで有効なギャップを検索するかを決定します。FVG_TradeAtEQオプションを有効にすると、FVGの均衡点(ギャップの50%)で取引することが可能となり、SMC理論においてバランスの取れたエントリーポイントと見なされます。最後に、BullOB、BearOB、BullFVG、BearFVG、BOSBull、BOSBearなどの色設定により、チャート上のオブジェクトが視覚的に直感的に識別でき、強気と弱気のパターンを一目で判別することができます。

//---------------------------- Globals ------------------------------//
double   Bid, Ask;
datetime g_lastBarTime = 0;

// OB state
class COrderBlock : public CObject
{
public:
   int      direction;   // +1 bullish, -1 bearish
   datetime time;        // OB candle time
   double   high;        // OB candle high
   double   low;         // OB candle low

   string Key() const { return TimeToString(time, TIME_DATE|TIME_MINUTES); }

   void draw(datetime tmS, datetime tmE, color clr){
      string objOB = " OB REC" + TimeToString(time);
      ObjectCreate( 0, objOB, OBJ_RECTANGLE, 0, time, low, tmS, high);
      ObjectSetInteger( 0, objOB, OBJPROP_FILL, true);
      ObjectSetInteger( 0, objOB, OBJPROP_COLOR, clr);
      
      string objtrade = " OB trade" + TimeToString(time);
      ObjectCreate( 0, objtrade, OBJ_RECTANGLE, 0, tmS, high, tmE, low); // trnary operator
      ObjectSetInteger( 0, objtrade, OBJPROP_FILL, true);
      ObjectSetInteger( 0, objtrade, OBJPROP_COLOR, clr);
   }
};
COrderBlock* OB = NULL;

// OB fib state
// Track if an OB has already been traded
datetime lastTradedOBTime = 0;
bool tradedOB = false;
double fib_low, fib_high;
datetime fib_t1, fib_t2;
bool isBullishOB = false; 
bool isBearishOB = false;
datetime T1;
datetime T2;
color OBClr;
#define FIB_OB_BULL "FIB_OB_BULL"
#define FIB_OB_BEAR "FIB_OB_BEAR"
#define FIBO_OBJ "Fibo Retracement"

// BOS state 
datetime lastBOSTradeTime = 0;
bool Bull_BOS_traded, Bear_BOS_traded;
int lastBOSTradeDirection = 0; // 1 for buy, -1 for sell
double   swng_High = -1.0, swng_Low = -1.0;
datetime bos_tH = 0, bos_tL = 0;

このコードのグローバルセクションでは、市場の状態、取引機会、チャートオブジェクトを管理するための基本的な変数と構造が定義されています。まず、Bid、Ask、g_lastBarTimeといったシンプルなグローバル値が設定され、リアルタイムの価格追跡を可能にし、EAが新しいバーごとに一度だけロジックを実行することを保証します。そこから、COrderBlockという専用クラスが作成され、OBのプロパティをまとめて管理できるようになっています。このクラスは、方向(強気か弱気か)、時間、価格レベル(高値/安値)といった情報を保持し、さらにdraw()関数を備えているため、チャート上に自動で長方形を描画し、色分けすることが可能です。これにより、トレーダーは視覚的にアクティブなOBや取引ゾーンを容易に確認できます。関数をクラス内にまとめることで、コードは整理され再利用性も高くなります。

基本的なOBの識別に加え、フィボナッチによるOBの検証を扱うための追加変数も定義されています。たとえば、fib_low、fib_high、時間マーカーのfib_t1やfib_t2はフィボナッチリトレースメントレベルの境界を格納し、isBullishOB、isBearishOB、tradedOBといったフラグは、各OBが一度だけ検証や取引されることを保証します。これらの変数により、EAはフィボナッチレベルを動的に参照でき、入力で定義された61.8%などの有効なリトレースメントのみを取引のエントリー条件として考慮します。さらに、FIB_OB_BULL、FIB_OB_BEAR、FIBO_OBJといった定数を用いることで、チャートオブジェクトの名称が統一され、描画されたフィボナッチリトレースメントが視覚的に区別しやすくなっています。

最後に、BOSの状態管理も導入されています。lastBOSTradeTime、Bull_BOS_traded、Bear_BOS_tradedといった変数により、BOSシグナルが発生した際に重複した取引が実行されるのを防ぎます。lastBOSTradeDirectionは直近のBOS取引が強気(+1)か弱気(-1)かを記録し、swng_Highとswng_Lowは最新のスイングレベルを保持して市場構造の検出に使用されます。さらに、bos_tHやbos_tLといった時間マーカーを用いて、スイングが確定した正確なタイミングを特定し、市場構造のルールに沿うようにしています。このようにグローバルに状態を管理することで、EAはOB、BOS、FVGといった異なる戦略間で一貫性を保ち、重複取引を防ぐことができます。これにより、SMC取引の自動化におけるしっかりとした基盤が構築されます。

//--------------------------- Helpers -------------------------------//
double  getHigh(int i)   { return iHigh(_Symbol, _Period, i);  }
double  getLow(int i)    { return iLow(_Symbol, _Period, i);   }
double  getOpen(int i)   { return iOpen(_Symbol, _Period, i);  }
double  getClose(int i)  { return iClose(_Symbol, _Period, i); }
datetime getTimeBar(int i){ return iTime(_Symbol, _Period, i); }

bool IsNewBar()
{
   datetime lastbar_time = (datetime)SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE);
   if(g_lastBarTime == 0) { g_lastBarTime = lastbar_time; return false; }
   if(g_lastBarTime != lastbar_time) { g_lastBarTime = lastbar_time; return true; }
   return false;
}

void ExecuteTrade(ENUM_ORDER_TYPE type)
{
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   double price = (type==ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK)
                                         : SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double sl = (type==ORDER_TYPE_BUY) ? price - StopLoss*point
                                      : price + StopLoss*point;
   double tp = (type==ORDER_TYPE_BUY) ? price + TakeProfit*point
                                      : price - TakeProfit*point;
   sl = NormalizeDouble(sl, _Digits);
   tp = NormalizeDouble(tp, _Digits);

   trade.SetExpertMagicNumber(MagicNumber);
   trade.PositionOpen(_Symbol, type, In_Lot, price, sl, tp, "SMC");
}

ここで定義されているヘルパー関数は、チャートデータとやり取りする際に、コードを整理し再利用可能にする役割を持っています。コード全体で直接iHigh、iLow、iCloseといった組み込み関数を呼び出す代わりに、getHigh()、getLow()、getClose()などのラッパー関数を使用することで、バー情報にアクセスしやすくなると同時に、可読性も向上します。IsNewBar()関数は、EAが各ローソク足ごとに一度だけロジックを処理することを保証する重要な役割を果たします。これは、g_lastBarTimeに最後のバーの時刻を保存し、現在のバーのタイムスタンプと比較することで実現されます。この効率的な方法により、シグナルや取引は新しいバーが始まったときにのみ考慮され、確定済みのローソク足に基づく戦略において必須の機能となります。

ExecuteTrade()関数は、取引実行のロジック全体をまとめて扱います。この関数は、注文タイプ(買い/売り)に応じて適切なエントリー価格、ストップロス、テイクプロフィットを自動計算し、値が銘柄の小数桁精度に合わせて正規化されることを保証します。このロジックを集中させることで、EAは重複するコードを避け、エントリーする際のミスを減らすことができます。また、この関数ではEAの固有のMagicNumberを設定し、すべての取引に「SMC」というコメントを付与することで、トレーダーがこのシステムによって開かれたポジションを簡単に識別できるようになっています。

//----------------------- Unified Swing Detection -------------------//
// Detects if barIndex is a swing high and/or swing low using len bars on each side.
// If swing high found -> updates fib_high/fib_tH (and swng_High/bos_tH if for BOS).
// If swing low  found -> updates fib_low/fib_tL (and swng_Low/bos_tL if for BOS).
// return: true if at least one swing found.
void DetectSwingForBar(int barIndex, SWING_TYPE type)
{
   const int len = 5;
   bool isSwingH = true, isSwingL = true;   

   for(int i = 1; i <= len; i++){
      int right_bars = barIndex - i;
      int left_bars  = barIndex + i;
      
      if(right_bars < 0) {
         isSwingH = false;
         isSwingL = false;
         break;
      }
      
      if((getHigh(barIndex) <= getHigh(right_bars)) || (left_bars < Bars(_Symbol, _Period) && getHigh(barIndex) < getHigh(left_bars)))
         isSwingH = false;
      
      if((getLow(barIndex) >= getLow(right_bars)) || (left_bars < Bars(_Symbol, _Period) && getLow(barIndex) > getLow(left_bars)))
         isSwingL = false;
   }

   // Assign with ternary operator depending on swing type
   if(isSwingH){
      if(type == SWING_OB) {
         fib_high = getHigh(barIndex);
         fib_t1 = getTimeBar(barIndex);
      } else {
         swng_High = getHigh(barIndex);
         bos_tH = getTimeBar(barIndex);
      }
   }
   if(isSwingL){
      if(type == SWING_OB) {
         fib_low = getLow(barIndex);
         fib_t2 = getTimeBar(barIndex);
      } else {
         swng_Low = getLow(barIndex);
         bos_tL = getTimeBar(barIndex);
      }
   }
}

統合スイング検出関数は、指定したバーを中心に左右の一定本数のバーと比較することで、チャート上の潜在的なスイングハイおよびスイングローを特定することを目的としています。左右に同じ本数(len)のバーを設定し、現在のバーが局所的な極値を形成しているかどうかを判定します。すなわち、現在のバーの高値が左右のバーよりも高ければスイングハイ、安値が左右よりも低ければスイングローとみなします。左右いずれかのバーがこの条件を満たさない場合、そのスイングは無効とされます。この方法により、微細な価格変動による誤検出を避け、市場構造における重要なピボットポイントに焦点を当てることができます。

スイングが確定すると、スイングの種類に応じて主要な変数が更新されます。OB (SWING_OB)の検出では、スイングハイおよびスイングローがフィボナッチのアンカーポイント(fib_high、fib_low)およびタイムスタンプに割り当てられ、後にリトレースメントの検証に使用されます。一方、BOSのロジックでは、同じスイングレベルがswng_Highまたはswng_Lowに割り当てられ、それぞれ対応するタイムスタンプ(bos_tHまたはbos_tL)と組み合わされます。OBとBOSの両方を1つの関数で扱うことで、EAはスイング検出を効率的におこない、冗長なロジックを避けながら、市場構造の検証とフィボナッチリトレースメントセットアップの両方で一貫したスイング識別プロセスを共有することができます。

void DetectAndDrawOrderBlocks()
{
   static datetime lastDetect = 0;
   datetime lastBar = (datetime)SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE);
   
   // Reset OB detection on new bar
   if(lastDetect != lastBar)
   {
      if(OB != NULL) 
      { 
         delete OB; 
         OB = NULL; 
      }
      lastDetect = lastBar;
   }
   
   // Only detect new OB if we don't have one already
   if(OB == NULL)
   {
      for(int i = 1; i < 100; i++)
      {
         // Bullish OB candidate
         if(getOpen(i) < getClose(i) && 
            getOpen(i+2) < getClose(i+2) &&
            getOpen(i+3) > getClose(i+3) && 
            getOpen(i+3) < getClose(i+2))
         {
            OB = new COrderBlock();
            OB.direction = 1;
            OB.time = getTimeBar(i+3);
            OB.high = getHigh(i+3);
            OB.low = getLow(i+3);
            OBClr = BullOB;
            T1 = OB.time;
            Print("Bullish Order Block detected at: ", TimeToString(OB.time));
            break;
         }
         
         // Bearish OB candidate
         if(getOpen(i) > getClose(i) && 
            getOpen(i+2) > getClose(i+2) &&
            getOpen(i+3) < getClose(i+3) && 
            getOpen(i+3) > getClose(i+2)) // Fixed condition
         {
            OB = new COrderBlock();
            OB.direction = -1;
            OB.time = getTimeBar(i+3);
            OB.high = getHigh(i+3);
            OB.low = getLow(i+3);
            OBClr = BearOB;
            T1 = OB.time;
            Print("Bearish Order Block detected at: ", TimeToString(OB.time));
            break;
         }
      }
   }

   if(OB == NULL) return;
   
   // Check if we already traded this OB
   if(lastTradedOBTime == OB.time) return;

   // If price retraces inside OB zone
   Bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   Ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

   bool inBullZone = (OB.direction > 0 && Ask <= OB.high && Ask >= OB.low);
   bool inBearZone = (OB.direction < 0 && Bid >= OB.low && Bid <= OB.high);

   if(!inBullZone && !inBearZone) return;

   // Use your DetectSwing function to find swings
   // We need to call it multiple times to find the most recent swings
   double mostRecentSwingHigh = 0;
   double mostRecentSwingLow = EMPTY_VALUE;
   datetime mostRecentSwingHighTime = 0;
   datetime mostRecentSwingLowTime = 0;
   
   // Scan recent bars to find the most recent swings
   for(int i = 0; i < 20; i++) // Check the last 20 bars
   {
      // Reset swing variables
      fib_high = 0;
      fib_low = 0;
      fib_t1 = 0;
      fib_t2 = 0;
      
      DetectSwingForBar(i, SWING_OB);
      
      if(fib_high > 0 && (mostRecentSwingHighTime == 0 || fib_t1 > mostRecentSwingHighTime))
      {
         mostRecentSwingHigh = fib_high;
         mostRecentSwingHighTime = fib_t1;
      }
      
      if(fib_low < EMPTY_VALUE && (mostRecentSwingLowTime == 0 || fib_t2 > mostRecentSwingLowTime))
      {
         mostRecentSwingLow = fib_low;
         mostRecentSwingLowTime = fib_t2;
      }
   }
   
   // Ensure we found both swing points
   if(mostRecentSwingHighTime == 0 || mostRecentSwingLowTime == 0) return;
   
   // Draw Fibonacci before trading to validate
   if(OB.direction > 0 && inBullZone)
   {
      // Draw Fibonacci from recent swing low to recent swing high
      ObjectDelete(0, "FIB_OB_BULL");
      if(ObjectCreate(0, "FIB_OB_BULL", OBJ_FIBO, 0, mostRecentSwingLowTime, mostRecentSwingLow, 
                     mostRecentSwingHighTime, mostRecentSwingHigh))
      {
         // Format Fibonacci
         ObjectSetInteger(0, "FIB_OB_BULL", OBJPROP_COLOR, clrBlack);
         for(int i = 0; i < ObjectGetInteger(0, "FIB_OB_BULL", OBJPROP_LEVELS); i++)
         {
            ObjectSetInteger(0, "FIB_OB_BULL", OBJPROP_LEVELCOLOR, i, clrBlack);
         }
         
         double entLvlBull = mostRecentSwingHigh - (mostRecentSwingHigh - mostRecentSwingLow) * (Fib_Trade_lvls / 100.0);
         
         if(Ask <= entLvlBull)
         {
            T2 = getTimeBar(0);
            OB.draw(T1, T2, BullOB);
            ExecuteTrade(ORDER_TYPE_BUY);
            lastTradedOBTime = OB.time; // Mark this OB as traded
            delete OB;
            OB = NULL;
         }
      }
   }
   else if(OB.direction < 0 && inBearZone)
   {
      // Draw Fibonacci from recent swing high to recent swing low
      ObjectDelete(0, "FIB_OB_BEAR");
      if(ObjectCreate(0, "FIB_OB_BEAR", OBJ_FIBO, 0, mostRecentSwingHighTime, mostRecentSwingHigh, 
                     mostRecentSwingLowTime, mostRecentSwingLow))
      {
         // Format Fibonacci
         ObjectSetInteger(0, "FIB_OB_BEAR", OBJPROP_COLOR, clrBlack);
         for(int i = 0; i < ObjectGetInteger(0, "FIB_OB_BEAR", OBJPROP_LEVELS); i++)
         {
            ObjectSetInteger(0, "FIB_OB_BEAR", OBJPROP_LEVELCOLOR, i, clrBlack);
         }
         
         double entLvlBear = mostRecentSwingLow + (mostRecentSwingHigh - mostRecentSwingLow) * (Fib_Trade_lvls / 100.0);
         
         if(Bid >= entLvlBear)
         {
            T2 = getTimeBar(0);
            OB.draw(T1, T2, BearOB);
            ExecuteTrade(ORDER_TYPE_SELL);
            lastTradedOBTime = OB.time; // Mark this OB as traded
            delete OB;
            OB = NULL;
         }
      }
   }
}

DetectAndDrawOrderBlocks()関数は、OBを識別、検証し、さらに取引を実行する役割を担っており、フィボナッチの一致(コンフルエンス)を意思決定プロセスに統合しています。関数は新しいバーごとに検出をリセットするところから始まり、直近のローソク足をスキャンして強気または弱気のOBパターンを探します。有効なOBが見つかると、現在の価格がそのゾーンにリトレースしているかを確認し、潜在的な取引機会を示します。取引を実行する前に、関数はスイング検出を呼び出して直近のスイングハイとスイングローを特定し、その間にフィボナッチリトレースメントを描画できる状態にします。

次に、フィボナッチツールを用いてエントリーを検証し、価格が事前に設定されたリトレースメントレベルに一致することを確認してからエントリーを確定します。これにより、早まったエントリーを避け、OBゾーンとフィボナッチリトレースメントの両方が整合した場合にのみ取引が執行されるようにすることで、取引の検証精度と一貫性を高めています。

//============================== FVG ================================//
// Definition (ICT-style):
// Let C=i, B=i+1, A=i+2.
// Bullish FVG if Low(A) > High(C) -> gap [High(C), Low(A)]
// Bearish FVG if High(A) < Low(C) -> gap [High(A), Low(C)]
struct SFVG
{
   int      dir;    // +1 bull, -1 bear
   datetime tLeft;  // left time anchor
   double   top;    // zone top price
   double   bot;    // zone bottom price

   string Name() const
   {
      string k = TimeToString(tLeft, TIME_DATE|TIME_MINUTES);
      return (dir>0 ? "FVG_B_" : "FVG_S_") + k + "_" + IntegerToString((int)(top*1000.0));
   }
};

bool FVGExistsAt(const string &name){ return ObjectFind(0, name) != -1; }

void DetectAndDrawFVGs()
{
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   int counted = 0;

   for(int i=2; i<MathMin(FVG_ScanBars, Bars(_Symbol, _Period))-2; i++)
   {
      // Build A,B,C
      double lowA  = getLow(i+2);
      double highA = getHigh(i+2);
      double highC = getHigh(i);
      double lowC  = getLow(i);

      // Bullish FVG: Low of A > High of C
      if(lowA > highC && (lowA - highC >= FVG_MinPoints * point))
      {
         SFVG z;
         z.dir   = +1;
         z.tLeft = getTimeBar(i+2);  // Changed from getTimeBar to getTime
         z.top   = lowA;
         z.bot   = highC;
         DrawFVG(z);
         counted++;
      }
      // Bearish FVG: High of A < Low of C
      else if(highA < lowC && (lowC - highA >= FVG_MinPoints * point))
      {
         SFVG z;
         z.dir   = -1;
         z.tLeft = getTimeBar(i+2);  // Changed from getTimeBar to getTime
         z.top   = lowC;          // Fixed: should be lowC for bearish FVG top
         z.bot   = highA;         // Fixed: should be highA for bearish FVG bottom
         DrawFVG(z);
         counted++;
      }
      
      if(counted > 15) break; // avoid clutter
   }

   // --- Simplified trading for FVGs ---
   Bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   Ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

   // scan drawn objects and trade on first valid touch of EQ (50%)
   int total = ObjectsTotal(0, 0, -1);
   static datetime lastTradeBar = 0;
   
   if(OneTradePerBar)
   {
      datetime barNow = (datetime)SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE);
      if(lastTradeBar == barNow) return; // already traded this bar
   }

   for(int idx=0; idx<total; idx++)
   {
      string name = ObjectName(0, idx);
      if(StringFind(name, "FVG_", 0) != 0) continue; // only our FVGs

      // Get object coordinates
      datetime t1 = (datetime)ObjectGetInteger(0, name, OBJPROP_TIME, 0);
      double y1 = ObjectGetDouble(0, name, OBJPROP_PRICE, 0);
      datetime t2 = (datetime)ObjectGetInteger(0, name, OBJPROP_TIME, 1);
      double y2 = ObjectGetDouble(0, name, OBJPROP_PRICE, 1);

      double top = MathMax(y1, y2);
      double bot = MathMin(y1, y2);
      bool isBull = (StringFind(name, "FVG_B_", 0) == 0);
      double mid  = (top + bot) * 0.5;

      if(isBull)
      {
         // trade when Ask is inside the gap and at/under EQ
         if(Ask <= top && Ask >= bot && (!FVG_TradeAtEQ || Ask <= mid))
         {
            ExecuteTrade(ORDER_TYPE_BUY);
            lastTradeBar = (datetime)SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE);
            break;
         }
      }
      else
      {
         // trade when Bid is inside the gap and at/over EQ
         if(Bid <= top && Bid >= bot && (!FVG_TradeAtEQ || Bid >= mid))
         {
            ExecuteTrade(ORDER_TYPE_SELL);
            lastTradeBar = (datetime)SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE);
            break;
         }
      }
   }
}

この関数は、ICTロジックに基づきFVGを検出、描画、取引する役割を担っています。まず直近のバーをスキャンし、強気のギャップではローソク足Aの安値がローソク足Cの高値を上回っている場合、弱気のギャップではローソク足Aの高値がローソク足Cの安値を下回っている場合を特定し、ギャップが指定された最小ポイント数以上であることを条件とします。FVGが検出されると、その情報は構造体に保存され、チャート上に視覚的に描画され、取引対象として追跡されます。その後、システムはアクティブなFVGゾーンを監視し、価格がギャップ内に入ったタイミングや、オプションが有効であれば均衡点(ゾーンの50%)との整合を確認します。条件が整った場合、関数は取引を執行し、強気FVGでは買い、弱気FVGでは売りをおこないます。なお、1バーにつき1取引に制限することで、過剰取引を防いでいます。このように、検出、視覚化、取引を組み合わせることで、FVGツールは分析的であると同時に、直接取引に活用できる機能となっています。

//=============================== BOS ===============================//
// Use unified swings (no RSI). Trading logic mirrors your earlier code:
// - Sell when price breaks above last swing high (liquidity run idea)
// - Buy  when price breaks below last swing low
void DetectAndDrawBOS()
{
   // Use DetectSwingForBar to find the most recent swing points
   double mostRecentSwingHigh = 0;
   double mostRecentSwingLow = EMPTY_VALUE;
   datetime mostRecentSwingHighTime = 0;
   datetime mostRecentSwingLowTime = 0;
   
   // Scan recent bars to find the most recent swings for BOS
   for(int i = 0; i < 20; i++) // Check the last 20 bars
   {
      // Reset swing variables
      swng_High = 0;
      swng_Low = 0;
      bos_tH = 0;
      bos_tL = 0;
      
      // Detect swing at this bar for BOS
      DetectSwingForBar(i, SWING_BOS);
      
      if(swng_High > 0 && (mostRecentSwingHighTime == 0 || bos_tH > mostRecentSwingHighTime))
      {
         mostRecentSwingHigh = swng_High;
         mostRecentSwingHighTime = bos_tH;
      }
      
      if(swng_Low < EMPTY_VALUE && (mostRecentSwingLowTime == 0 || bos_tL > mostRecentSwingLowTime))
      {
         mostRecentSwingLow = swng_Low;
         mostRecentSwingLowTime = bos_tL;
      }
   }
   
   // Update the global BOS variables with the most recent swings
   if(mostRecentSwingHighTime > 0)
   {
      if(mostRecentSwingHighTime != bos_tH)
         Bull_BOS_traded = false;
      swng_High = mostRecentSwingHigh;
      bos_tH = mostRecentSwingHighTime;
   }
   
   if(mostRecentSwingLowTime > 0)
   {
      if(mostRecentSwingLowTime != bos_tL)
         Bear_BOS_traded = false;
      swng_Low = mostRecentSwingLow;
      bos_tL = mostRecentSwingLowTime;
   }
   
   // Now check for break of structure
   Bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   Ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   
   // Get current bar time to prevent multiple trades on same bar
   datetime currentBarTime = iTime(_Symbol, _Period, 0);
   
   // SELL on break above swing high
   if(swng_High > 0 && Ask > swng_High && Bull_BOS_traded == false)
   {
      // Check if we haven't already traded this breakout
      if(lastBOSTradeTime != currentBarTime || lastBOSTradeDirection != -1)
      {
         if(DrawBOSLines)
            DrawBOS("BOS_H_" + TimeToString(bos_tH), bos_tH, swng_High,
                    TimeCurrent(), swng_High, BOSBear, -1);
         
         ExecuteTrade(ORDER_TYPE_BUY);
         
         // Update trade tracking
         lastBOSTradeTime = currentBarTime;
         lastBOSTradeDirection = -1;
         Bull_BOS_traded = true;
         
         // Reset the swing high to prevent immediate re-trading
         swng_High = -1.0;
      }
   }
   
   // BUY on break below swing low
   if(swng_Low > 0 && Bid < swng_Low && Bear_BOS_traded == false)
   {
      // Check if we haven't already traded this breakout
      if(lastBOSTradeTime != currentBarTime || lastBOSTradeDirection != 1)
      {
         if(DrawBOSLines)
            DrawBOS("BOS_L_" + TimeToString(bos_tL), bos_tL, swng_Low,
                    TimeCurrent(), swng_Low, BOSBull, +1);
         
         ExecuteTrade(ORDER_TYPE_SELL);
         
         // Update trade tracking
         Bear_BOS_traded = true;
         lastBOSTradeTime = currentBarTime;
         lastBOSTradeDirection = 1;
         
         // Reset the swing low to prevent immediate re-trading
         swng_Low = -1.0;
      }
   }   
}
このBOS関数は、統合スイングロジックを用いてBOSイベントを検出し、取引を実行する役割を担っています。直近20本のバーをスキャンして最新のスイングハイおよびスイングローを特定し、グローバルなBOS変数を更新しながら、同じバーでの重複取引を防ぎます。価格が直近のスイングハイを上抜けた場合は買い(高値上の流動性確保)、直近のスイングローを下抜けた場合は売りを実行します。必要に応じてBOSラインをチャート上に描画して視覚化することも可能で、内部フラグによって同じブレイクアウトでの即時再エントリーを防止しています。
//---------------------------- BOS UI -------------------------------//
void DrawBOS(const string name, datetime t1, double p1, datetime t2, double p2, color col, int dir)
{
   if(ObjectFind(0, name) == -1)
   {
      ObjectCreate(0, name, OBJ_TREND, 0, t1, p1, t2, p2);
      ObjectSetInteger(0, name, OBJPROP_COLOR, col);
      ObjectSetInteger(0, name, OBJPROP_WIDTH, 2);

      string lbl = name + "_lbl";
      ObjectCreate(0, lbl, OBJ_TEXT, 0, t2, p2);
      ObjectSetInteger(0, lbl, OBJPROP_COLOR, col);
      ObjectSetInteger(0, lbl, OBJPROP_FONTSIZE, 10);
      ObjectSetString(0,  lbl, OBJPROP_TEXT, "Break");
      ObjectSetInteger(0, lbl, OBJPROP_ANCHOR, (dir>0)?ANCHOR_RIGHT_UPPER:ANCHOR_RIGHT_LOWER);
   }
}

DrawBOS関数は、チャート上でBOSを視覚的に表示する役割を担っています。指定された2点(t1, p1)と(t2, p2)の間にトレンドラインを作成し、色や線の太さを設定します。指定された名前のオブジェクトがまだ存在しない場合は、まずトレンドラインを作成し、色と太さを設定した後、終点に「Break」と表示するテキストラベルを追加します。ラベルの位置は方向(dir)に応じて固定され、強気の場合は右上、弱気の場合は右下に配置されます。これにより、チャート上で市場構造の変化を直感的に確認できる明確な視覚的指標が提供されます。

void DrawFVG(const SFVG &z)
{
   string name = z.Name();
   datetime tNow = (datetime)SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE);
   
   // Delete existing object if it exists
   if(ObjectFind(0, name) != -1) 
      ObjectDelete(0, name);
   
   // Create rectangle object for FVG
   if(!ObjectCreate(0, name, OBJ_RECTANGLE, 0, z.tLeft, z.bot, tNow, z.top))
   {
      Print("Error creating FVG object: ", GetLastError());
      return;
   }
   
   // Set object properties
   ObjectSetInteger(0, name, OBJPROP_COLOR, z.dir>0 ? BullFVG : BearFVG);
   ObjectSetInteger(0, name, OBJPROP_FILL, true);
   ObjectSetInteger(0, name, OBJPROP_BACK, true);
   ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
   ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
   
   // Set Z-order to make sure it's visible
   ObjectSetInteger(0, name, OBJPROP_ZORDER, 0);
}

DrawFVG関数は、チャート上でFVGを視覚化する役割を担っています。まず、同じ名前のオブジェクトが既に存在するかを確認し、重複を避けるために削除します。その後、ギャップの左端時刻tLeftから現在のバーまで、またギャップの下限botから上限topまでの範囲に長方形を作成します。この長方形はギャップの方向に応じた色で塗りつぶされ、チャートの背面に配置され、境界線は実線で描かれます。Zオーダーを0に設定することで、FVGは他のチャート要素の背後に表示され、トレーダーが価格の不均衡をリアルタイムで直感的に確認できる視覚的参照として機能します。

void OnTick()
{
   if(!IsNewBar()) return;

   // Strategy switch
   if(TradeStrategy == STRAT_FVG || TradeStrategy == STRAT_AUTO)
      DetectAndDrawFVGs();

   if(TradeStrategy == STRAT_OB  || TradeStrategy == STRAT_AUTO)
      DetectAndDrawOrderBlocks();

   if(TradeStrategy == STRAT_BOS || TradeStrategy == STRAT_AUTO)
      DetectAndDrawBOS();
}

OnTick関数はティックごとに実行されますが、新しいバーが形成された場合にのみ処理を進めるようになっており、計算はローソク足ごとに一度だけおこなわれます。その後、選択された取引戦略を確認し、FVGまたは自動モードが有効な場合はDetectAndDrawFVGsを呼び出し、OBまたは自動モードが有効な場合はDetectAndDrawOrderBlocksを呼び出します。さらに、BOSまたは自動モードが有効な場合はDetectAndDrawBOSを呼び出します。この構造により、EAやインジケーターは選択した戦略に基づいて、リアルタイムで異なる市場構造を動的に検出、視覚化することが可能となります。


バックテスト結果

バックテストは、1時間足(1H)を対象に、約2か月間(2025年7月1日から2025年9月1日まで)の期間で評価されました。設定はデフォルトのままで、戦略にはBOSが使用されています。



結論

3つの主要要素であるOB、BOS、FVGを統合した統一型SMC取引フレームワークを開発しました。それぞれのコンセプトには、検出、描画、取引のロジックが組み込まれています。OBはフィボナッチリトレースメントで検証される機関投資家の足跡として識別され、BOSは価格がスイングハイやスイングローを抜けた際の流動性を捉え、FVGは価格形成の非効率性を示し、反応の強いゾーンとして機能します。これらの要素を1つのシステムに統合することで、チャート上で高確率のセットアップを動的に検出でき、視覚的補助や自動実行機能も備えたEAが実現しました。

この統合アプローチにより、トレーダーはSMCの原則を体系的に適用でき、従来の手動チャート分析に伴う推測を排除できます。EAは市場構造の変化をマーク、追跡するだけでなく、価格が事前に定義されたSMC条件に整合するとリアルタイムで取引を実行します。これにより、トレーダーは規律あるルールベースの手法を利用でき、感情的バイアスを減らし、一貫性を高め、さまざまな市場環境において機関投資家スタイルの取引機会を捉える専門的な優位性を得ることが可能となります。

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/16340

添付されたファイル |
SMC_ALL_IN_1.mq5 (21.41 KB)
最後のコメント | ディスカッションに移動 (12)
Hlomohang John Borotho
Hlomohang John Borotho | 16 9月 2025 において 15:59
Stanislav Korotky #:

フェア・バリュー・ギャップの説明と描写が間違っています

FVGゾーンの内側にすでに価格/キャンドルスティックが戻っている状態でFVGを描きました。
Hlomohang John Borotho
Hlomohang John Borotho | 16 9月 2025 において 16:00
Yata Tema Gea #:

あなたの記事は素晴らしい。ただ、記事の中にインドネシア人がいることは知っています。僕らのコンセプトは同じだけど、違うのは僕がSMC+GPTにPythonを使っていることだね。


なるほどね。)
Stanislav Korotky
Stanislav Korotky | 16 9月 2025 において 17:37
Hlomohang John Borotho #:
FVGゾーンの内側にすでに価格/キャンドルスティックが戻っている状態でFVGを描きました。

あなたは間違っているか、図形の準備が不十分です。左から 1番目と3番目のローソク足が赤で示されているように、これらは弱気であり、2番目のローソク足(おそらくFVGが発生した場所)の前後に 2つの大きなギャップを形成しています。

インターネットでFVGを検索すると、このフォーメーションは価格の大きな一方向のジャンプ/ギャップであり、ジグザグのジャンプではないことがわかります。
Bao Thuan Thai
Bao Thuan Thai | 28 9月 2025 において 11:06
でも、教えてくれてありがとう。
ritik pathak
ritik pathak | 4 10月 2025 において 11:01
ストップロスがFVGより下か上か、またはストップロスが 定点でなくオーダーブロックであれば、もっと良くなります。
MQL5での取引戦略の自動化(第30回):視覚的フィードバックによるプライスアクションAB-CDハーモニックパターンの作成 MQL5での取引戦略の自動化(第30回):視覚的フィードバックによるプライスアクションAB-CDハーモニックパターンの作成
本記事では、MQL5で弱気、強気双方のAB=CDハーモニックパターンを、ピボットポイントとフィボナッチ比率に基づいて識別し、正確なエントリー、ストップロス、テイクプロフィットレベルを用いて取引を自動化するAB=CDパターンエキスパートアドバイザー(EA)を開発します。さらに、チャートオブジェクトによる視覚的フィードバックによって、トレーダーの洞察を強化します。
初心者からエキスパートへ:MQL5を使ったアニメーションニュース見出し(X) - ニュース取引のための多銘柄チャート表示 初心者からエキスパートへ:MQL5を使ったアニメーションニュース見出し(X) - ニュース取引のための多銘柄チャート表示
本日は、チャートオブジェクトを用いたマルチチャート表示システムを開発します。本システムの目的は、MQL5アルゴリズムを活用して、重要なニュース発表時などの高ボラティリティ期間におけるトレーダーの反応時間を短縮し、ニュース取引を支援することです。複数の主要通貨ペアを、統合的に監視できる、オールインワンのニュース取引環境を提供します。News Headline EAの開発は継続的に進化しており、完全自動システムを使用するトレーダーはもちろん、アルゴリズム補助による手動取引をおこなうトレーダーにとっても実用的な機能が追加されています。さらに知識や洞察、実践的なアイデアを深めたい方は、ぜひ本ディスカッションに参加して詳細をご覧ください。
カスタム市場センチメント指標の開発 カスタム市場センチメント指標の開発
本記事では、複数の時間足を用いて市場センチメントを判定し、強気、弱気、リスクオン、リスクオフ、中立のいずれかに分類するMarket Sentimentカスタムインジケーターの開発について解説します。多時間足分析を組み合わせることで、トレーダーは市場全体の偏りと短期的な動向をより明確に把握できるようになります。
プライスアクション分析ツールキットの開発(第39回):MQL5でBOSとChoCHの検出を自動化する プライスアクション分析ツールキットの開発(第39回):MQL5でBOSとChoCHの検出を自動化する
本記事では、フラクタルピボットを実用的な市場構造シグナルへ変換する、コンパクトなMQL5システム「Fractal Reaction System」を紹介します。リペイントを回避するために確定バーのロジックを用い、EAはChoCH (Change-of-Character)警告を検出し、BOS (Break-of-Structure)を確定させ、永続的なチャートオブジェクトを描画し、すべての確定イベントをログ出力してアラート(デスクトップ、モバイル、サウンド)します。アルゴリズム設計、実装上の注意点、テスト結果、そしてEAコード全文を順に解説し、読者ご自身でコンパイル、テスト、展開できるようにします。