
流動性狩り取引戦略
流動性狩り取引戦略は、市場における機関投資家の行動を特定し、それを活用することを目指すSmart Money Concepts(SMC)の重要な要素です。これには、サポートゾーンやレジスタンスゾーンなどの流動性の高い領域をターゲットにすることが含まれます。市場がトレンドを再開する前に、大量の注文によって一時的な価格変動が引き起こされます。この記事では、流動性狩りの概念を詳しく説明し、MQL5による流動性狩り取引戦略エキスパートアドバイザー(EA)の開発プロセスの概要を紹介します。
戦略の概要:主要な概念と戦術
流動性狩り(Liquidity Grab)戦略は、サポートやレジスタンスといった流動性の高いレベルを活用します。これらのポイントでは、機関投資家が価格を操作し、ストップロスを誘発してボラティリティを増幅させることが頻繁に見られます。この戦略は、市場のこうした動きを予測して活用することを目的としています。この戦略は、一般的な市場動向を活用して、これらの動きを予測し、それを活用します。
コアコンセプト
- ストップロスハンティング:価格を操作してストップロス注文を発動させ、連鎖的な買い・売りを引き起こす。
- レイヤリングとスプーフィング:偽の注文を使い、市場参加者に誤った方向感を与える。
- アイスバーグ注文:大口注文を小口に分割し、目立たないように隠す。
- モメンタムイグニッション:人工的に勢いを作り、他のトレーダーを誘い込んだのち反転する。
- サポートとレジスタンスの操作:主要価格帯を利用し、予測可能な価格反応を引き出す。
- 心理的価格ポイント:キリの良い数字や端数を利用し、トレーダーの行動に影響を与える。
市場操作とは、意図的に価格や取引量を操作し、市場参加者に誤解を与える状況を作り出す行為です。ほとんどの機関投資家は法令や倫理基準を遵守していますが、特定の戦略目標を達成するために操作的手法を用いる場合もあります。以下はその動機と手法の概要です。
動機:
- 短期的な価格変動と裁定取引を通じて利益を最大化する
- 競合他社から取引の意図を隠す
- 市場への影響を最小限に抑えながら大規模な取引を実行する
戦術:
- ストップロスをトリガーして流動性を創出し、価格を動かす
- アイスバーグ注文やスプーフィングで注文フローをコントロールする
- サポート・レジスタンスや心理的価格レベルをターゲットにする
これらの市場のダイナミクスを理解することで、トレーダーは機関投資家の動きを予測し、その知見を自動化ツールに活かしてより効果的な戦略を構築できます。
ストップロス注文の蓄積や大口市場参加者の行動により発生する一時的な価格変動を活用し、流動性の高いエリアを特定することで、トレーダーは価格が反転しトレンドが継続する直前の最適なタイミングでエントリーし、有利なリスクリワードの取引機会を得ることを目指します。
以下に、概略図を示します。
戦略開発
EAをコーディングする際には、以下の順序で進めることを提案します。
- この戦略に必要な関数を洗い出し、それらをどのように複数のコンポーネントに分けてカプセル化するか検討します。
- 各関数を1つずつ実装しながら、関連するグローバル変数の宣言や必要なハンドルの初期化も同時におこないます。
- 関数を実装した後、それぞれを見直して、パラメータの受け渡しや関数呼び出しの仕組みでどう連携させるかを考えます。
- 最後にOnTick()関数に移り、前段階で作成した関数を組み合わせてロジックを完成させます。
まずはルールの定量化に取り組みます。SMCは定量化が難しい微妙なニュアンスを多く含むため、主に裁量トレーダー向けの手法です。操作的な特徴を正確に定義することは客観的に困難です。注文フローの出来高変化を分析する方法もありますが、ブローカーが提供する出来高データは信頼性に欠けることが多いです。特にFX市場は取引が分散されており、先物などの集中型取引所でも多くのブローカーは流動性プロバイダーからのデータを使っているため、中央集権的なデータではありません。 よりシンプルかつ最適化しやすい方法として、テクニカル分析を用いるアプローチをこの記事では採用します。ルールの定量化として、以下のように戦略をシンプルに分割します。
- ルックバック期間内の最高値または最安値(=キーレベル)で、拒否ローソク足パターンが形成されたかどうかを検出する。
- その拒否ローソク足の後、価格が反転し、より短いルックバック期間で反対側のキーレベルを突破する。
- 最後に、価格の移動平均に対する位置関係で示されるように、全体の動きが広いトレンドと一致している場合に、固定の損切り・利益確定を設定してエントリーする。
市場操作の特徴をより忠実に再現するために追加ルールを設けることも可能ですが、過剰適合を防ぐため、戦略はできるだけシンプルに保つことを推奨します。
次に、利益確定と損切りを計算し、注文を執行し、注文チケットを管理するために必要な関数をコーディングしていきます 。
//+------------------------------------------------------------------+ //| Expert trade transaction handling function | //+------------------------------------------------------------------+ void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result) { if (trans.type == TRADE_TRANSACTION_ORDER_ADD) { COrderInfo order; if (order.Select(trans.order)) { if (order.Magic() == Magic) { if (order.OrderType() == ORDER_TYPE_BUY) { buypos = order.Ticket(); } else if (order.OrderType() == ORDER_TYPE_SELL) { sellpos = order.Ticket(); } } } } } //+------------------------------------------------------------------+ //| Execute sell trade function | //+------------------------------------------------------------------+ void executeSell() { double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); bid = NormalizeDouble(bid, _Digits); double tp = NormalizeDouble(bid - tpp * _Point, _Digits); double sl = NormalizeDouble(bid + slp * _Point, _Digits); trade.Sell(lott, _Symbol, bid, sl, tp); sellpos = trade.ResultOrder(); } //+------------------------------------------------------------------+ //| Execute buy trade function | //+------------------------------------------------------------------+ void executeBuy() { double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); ask = NormalizeDouble(ask, _Digits); double tp = NormalizeDouble(ask + tpp * _Point, _Digits); double sl = NormalizeDouble(ask - slp * _Point, _Digits); trade.Buy(lott, _Symbol, ask, sl, tp); buypos = trade.ResultOrder(); }
こちらの2つの関数は、指定された範囲内で最高点または最安点を特定して返します。その際、そのポイントがキーレベルとして適格であることを確認するために、当該レベルから反転を引き起こすサポートまたはレジスタンスの存在を検証します。
//+------------------------------------------------------------------+ //| find the key level high given a look-back period | //+------------------------------------------------------------------+ double findhigh(int Range = 0) { double highesthigh = 0; for (int i = BarsN; i < Range; i++) { double high = iHigh(_Symbol, PERIOD_CURRENT, i); if (i > BarsN && iHighest(_Symbol, PERIOD_CURRENT, MODE_HIGH, BarsN * 2 + 1, i - BarsN) == i) //used to make sure there's rejection for this high { if (high > highesthigh) { return high; } } highesthigh = MathMax(highesthigh, high); } return 99999; } //+------------------------------------------------------------------+ //| find the key level low given a look-back period | //+------------------------------------------------------------------+ double findlow(int Range = 0) { double lowestlow = DBL_MAX; for (int i = BarsN; i < Range; i++) { double low = iLow(_Symbol, PERIOD_CURRENT, i); if (i > BarsN && iLowest(_Symbol, PERIOD_CURRENT, MODE_LOW, BarsN * 2 + 1, i - BarsN) == i) { if (lowestlow > low) { return low; } } lowestlow = MathMin(lowestlow, low); } return -1; }
findhigh()関数は、指定された範囲内での最高値が現在の足(i)で発生しており、かつより大きな範囲(ルックバック期間の2倍)内での最高点とも一致しているかを確認することで、その高値ポイントでの拒否があったかどうかを判定します。これは、価格がそのレベルに達した後にそれ以上上昇できなかったことを示し、拒否の兆候と解釈されます。この条件が満たされれば、その高値を潜在的なキーレベルとして返します。findlow()関数はその逆のロジックで動作します。
これら2つの関数は、直近の確定ローソク足がキーレベル上での拒否ローソク足であるかどうかを検出します。これは、いわゆる「流動性刈り(LiquidityGrab)」の挙動として捉えることができます。
//+------------------------------------------------------------------+ //| Check if the market rejected in the upward direction | //+------------------------------------------------------------------+ bool IsRejectionUp(int shift=1) { // Get the values of the last candle (shift = 1) double open = iOpen(_Symbol,PERIOD_CURRENT, shift); double close = iClose(_Symbol,PERIOD_CURRENT, shift); double high = iHigh(_Symbol,PERIOD_CURRENT, shift); double low = iLow(_Symbol,PERIOD_CURRENT,shift); // Calculate the body size double bodySize = MathAbs(close - open); // Calculate the lower wick size double lowerWickSize = open < close ? open - low : close - low; // Check if the lower wick is significantly larger than the body if (lowerWickSize >= wickToBodyRatio * bodySize&&low<findlow(DistanceRange)&&high>findlow(DistanceRange)) { return true; } return false; } //+------------------------------------------------------------------+ //| Check if the market rejected in the downward direction | //+------------------------------------------------------------------+ bool IsRejectionDown(int shift = 1) { // Get the values of the last candle (shift = 1) double open = iOpen(_Symbol,PERIOD_CURRENT, shift); double close = iClose(_Symbol,PERIOD_CURRENT, shift); double high = iHigh(_Symbol,PERIOD_CURRENT, shift); double low = iLow(_Symbol,PERIOD_CURRENT,shift); // Calculate the body size double bodySize = MathAbs(close - open); // Calculate the upper wick size double upperWickSize = open > close ? high - open : high - close; // Check if the upper wick is significantly larger than the body if (upperWickSize >= wickToBodyRatio * bodySize&&high>findhigh(DistanceRange)&&low<findhigh(DistanceRange)) { return true; } return false; }
ローソク足が拒否パターンを示すのは、髭が実体よりも著しく大きく、かつローソク足の方向が直前のローソク足の方向と逆転している場合です。
前述の2つの関数(findhigh()およびfindlow())を活用し、指定されたルックバック期間内をループして、流動性刈りの挙動を検出する処理が作成されています。この検出結果は、その後の価格反転およびブレイクアウトのシグナルを観察する前段階として、そうした挙動が直前に発生していたかどうかを確認するために使用されます。
//+------------------------------------------------------------------+ //| check if there were rejection up for the short look-back period | //+------------------------------------------------------------------+ bool WasRejectionUp(){ for(int i=1; i<CandlesBeforeBreakout;i++){ if(IsRejectionUp(i)) return true; } return false; } //+------------------------------------------------------------------+ //| check if there were rejection down for the short look-back period| //+------------------------------------------------------------------+ bool WasRejectionDown(){ for(int i=1; i<CandlesBeforeBreakout;i++){ if(IsRejectionDown(i)) return true; } return false; }
現在の移動平均値のデータを取得するには、まずOnInit()関数でハンドルを初期化します。
int handleMa; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { trade.SetExpertMagicNumber(Magic); handleMa = iMA(_Symbol, PERIOD_CURRENT, MaPeriods, 0, MODE_SMA,PRICE_CLOSE); if (handleMa == INVALID_HANDLE) { Print("Failed to get indicator handles. Error: ", GetLastError()); return INIT_FAILED; } return INIT_SUCCEEDED; }
次のようにバッファ配列を作成し、ハンドル値をバッファ配列にコピーすることで、移動平均値に簡単にアクセスできます。
double ma[]; if (CopyBuffer(handleMa, 0, 1, 1, ma) <= 0) { Print("Failed to copy MA data. Error: ", GetLastError()); return; }
最後に、OnTick()関数内でこれまで定義した関数を活用し、取引ロジックを実装します。 これにより、新しいバーが形成されたタイミングでのみシグナルを計算することが保証されます。具体的には、現在のバーが最後に保存された確定バーと異なるかどうかを確認することで判定します。このアプローチにより、計算資源の無駄な消費を抑え、取引処理をよりスムーズにおこなうことができます。
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { int bars = iBars(_Symbol, PERIOD_CURRENT); if (barsTotal != bars) { barsTotal = bars;
次に、以下のようにシグナル条件を適用します。
if(WasRejectionDown()&&bid<ma[0]&&bid<findlow(CandlesBeforeBreakout)) executeSell(); else if(WasRejectionUp()&&ask>ma[0]&&ask>findhigh(CandlesBeforeBreakout)) executeBuy();
このステップの後、プログラムをコンパイルし、バックテストビジュアライザーに移動してEAが動作するかどうかを確認します。
バックテストビジュアライザーでは、一般的なエントリは次のようになります。
提案
戦略の基本的なアイデアは完成していますが、このEAをライブマーケットで実装するにあたり、以下のような提案があります。
1. 市場操作は非常に迅速におこなわれるため、この戦略では5分足や15分足といった短期の時間足を使ったデイトレードが最適です。あまりに短い時間足ではダマシのシグナルが多くなりやすく、逆に長すぎる時間足では市場操作への反応が遅れてしまう可能性があります。
2. 市場操作は、通常、ニューヨーク/ロンドンの為替市場セッションや株式市場の寄り付き・引けといったボラティリティの高い時間帯に発生しやすいです。そのため、以下のように取引時間を特定の時間帯に制限する機能を実装することを推奨します。
//+------------------------------------------------------------------+ //| Check if the current time is within the specified trading hours | //+------------------------------------------------------------------+ bool IsWithinTradingHours() { datetime currentTime = TimeTradeServer(); MqlDateTime timeStruct; TimeToStruct(currentTime, timeStruct); int currentHour = timeStruct.hour; if (( currentHour >= startHour1 && currentHour < endHour1) || ( currentHour >= startHour2 && currentHour < endHour2)) { return true; } return false; }
3. 価格が重要なレベル付近でもみ合い状態になると、売買の両方向に連続して複数の取引が発生する可能性があります。これを防ぎ、常に1つのポジションのみが実行されるようにするため、追加の条件として「両方のポジションチケットが0であること(このEAによる未決済ポジションがないこと)」を確認します。これを実現するために、OnTick()関数内で以下のようにポジションチケットを0にリセットする処理を記述します。
if(buypos>0&&(!PositionSelectByTicket(buypos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){ buypos = 0; } if(sellpos>0&&(!PositionSelectByTicket(sellpos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){ sellpos = 0; }
先ほどおこなった変更を組み込むために、元のコードを更新します。
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { int bars = iBars(_Symbol, PERIOD_CURRENT); if (barsTotal != bars) { barsTotal = bars; double ma[]; double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); if (CopyBuffer(handleMa, 0, 1, 1, ma) <= 0) { Print("Failed to copy MA data. Error: ", GetLastError()); return; } if(WasRejectionDown()&&IsWithinTradingHours()&&sellpos==buypos&&bid<ma[0]&&bid<findlow(CandlesBeforeBreakout)) executeSell(); else if(WasRejectionUp()&&IsWithinTradingHours()&&sellpos==buypos&&ask>ma[0]&&ask>findhigh(CandlesBeforeBreakout)) executeBuy(); if(buypos>0&&(!PositionSelectByTicket(buypos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){ buypos = 0; } if(sellpos>0&&(!PositionSelectByTicket(sellpos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){ sellpos = 0; } } }
バックテスト
この記事では、5分の時間枠でGBPUSDにこのEAを使用します。
このEAに使用することにしたパラメータ設定は次のとおりです。
重要な注意事項
- 利益確定と損切りについては、日中のボラティリティに基づいて適切なポイント額を選択します。この戦略は基本的にトレンドに沿ってポジションを取るため、リスクリワード比は1以上であることが推奨されます。
- DistanceRangeは、流動性刈り(Liquidity Grab)シグナルのための過去の重要レベルを探すルックバック期間を指定します。
- 同様に、CandlesBeforeBreakoutは、ブレイクアウトシグナルを検出するための直近の重要レベルを探すルックバック期間です。
- 髭と実体の比率は、拒否パターンを明確に示すとトレーダーが判断できる値に調整可能です。
- 取引時間はブローカーのサーバー時間に基づきます。たとえば、私のブローカー(GMT+0)の場合、為替市場でボラティリティが高いニューヨーク時間は13:00〜19:00です。
それでは、2020.11.1~2024.11.1のバックテストを実行してみましょう。
この戦略は過去4年間にわたって良好な結果を示しました。
結論
本記事ではまず、流動性狩り(Liquidity Grab)の概念と、その背景にある市場参加者の動機について紹介しました。続いて、この戦略に基づいたEAをゼロから構築するためのステップバイステップガイドを提供しました。その後、EAのパフォーマンスを最適化するための追加の推奨事項も紹介しています。最後に、200件以上の取引を含む4年間のバックテストによって、本戦略の潜在的な収益性を実証しました。
この戦略が皆さんにとって有益であり、同様の手法の構築や設定の最適化など、さらなる発展のきっかけとなれば幸いです。対応するEAファイルを以下に添付したので、ぜひダウンロードしてご自由にお試しください。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/16518





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
コードをありがとう 、非常にうまく書かれた記事とうまくまとめた、コードは非常に便利ですありがとうございました 。あなたがソーシャルメディアでSMCトレーダーを見れば興味深いリターンは非常に異なっている 。トランザクションを確認し、トレーリングストップとトレーリングTPまたは外部レンジ上のいくつかのフィボナッチを試してみます。
コメントありがとうございます!はい、ソーシャルメディアではSMCトレーダーを見かけます。一般的に、彼らは流動性をつかむという点で、戦略についてあまり同意していないと思います。ある人は1つのフェイクアウトではなく2つのフェイクアウトを探し、ある人は取引量を見ます。全体として、彼らの行動には、戦略の妥当性を評価するのを難しくするような、彼ら自身の裁量が含まれている。とはいえ、トレーリング・スリッページやフィボナッチ・レンジを使った実験結果を楽しみにしている。