プライスアクション分析ツールキットの開発(第38回):ティックバッファVWAPと短期不均衡エンジン
内容
はじめに
多くのトレーダーは、価格チャートだけでは市場の全容を把握できないことをすぐに学びます。本当に市場を動かしているのは、その表面のすぐ下、つまりオーダーブックに潜む情報です。買い気配と売り気配を見ることで、誰が買いを待っているのか、誰が売る準備をしているのか、そして流動性がどこに積み上がっているのかが明らかになります。プロのトレーダーはこうした情報を日常的に使い、需給ゾーンの特定、不均衡の検知、短期的な価格変動の精密な予測に役立てています。
しかし、小売トレーダーがこのような景色を目にする機会はほとんどありません。多くのプラットフォームではオーダーブックへの直接アクセスが提供されず、あっても遅延や欠落のあるデータに限られ、追加料金が必要な場合もあります。MetaTrader 5のような一般的な小売向けプラットフォームでは透明性が完全にブローカーに依存しており、結果として多くのトレーダーは、プロが当たり前のように参照するミクロ構造の洞察を持たないまま市場で取引することになります。
スリッページツールは、この隔たりを埋めるために作られました。完全なオーダーブックや生のテープを再現するものではありませんが、すべてのブローカーが提供するティックデータだけを使って、市場深度から通常得られる最も実用的な洞察を再構築します。価格を重み付けした VWAP(出来高加重平均価格)を計算して活動が集まりやすい価格帯を可視化し、短期的な注文不均衡から方向性の圧力を捉え、ティックボリュームの集計によって直近の参加状況をより明確に示します。これらの指標にスプレッドと ATR の文脈情報を組み合わせることで、トレーダーが自らの方向バイアスに有利な局面を見分けやすくします。
ツールはチャート上に直接表示され、アラートやトレードマーカーとともに、小売トレーダーにとって実用的な注文フローの代替指標として機能します。これにより、専門的なオーダーブックにアクセスできなくても、流動性の変化の観察、不均衡の識別、エントリータイミングの改善が可能になります。
下図はその一例です。EからFへの下落局面を見てみると、価格は一直線に落ちるのではなく、高値と安値をつけながら流動性を掃き取っていきます。CC で売りエントリーしたトレーダーは方向性は合っているかもしれませんが、リスク管理が甘くロットが大きすぎれば、Dへの戻しで容易にストップアウトされてしまいます。スリッページツールは、これを逆手に取るアプローチ、つまり、Cで買い、Dで利益確定するという対抗的なエントリーを示唆し、本来損失になり得る局面を管理された機会へと変換します。同様に、Aからの上昇局面では、Bを優れた売りゾーンとして示し、トレーダーが変動に振り回されるのではなく、スイングに沿って取引できるよう支援します。

取引には常にボラティリティ、反転、そして流動性トラップが付きものです。肝心なのは、それらの動きがトレーダーを市場から排除するのか、それともリスクと執行をより適切に管理するための機会になるのか、という点です。スリッページツールは不確実性を消すものではありませんが、小売トレーダーのチャートに構造的な透明性をもたらし、小売プラットフォームとプロ向けインフラとの間にあるギャップを縮めます。
続くセクションでは、ツールの動作メカニズム、主要指標が持つ意味、そしてこれらの機能をどのように活用して取引判断を改善できるかを詳しく説明します。より正確なエントリーを求める裁量トレーダーにとっても、新しいシグナル源を探しているアルゴリズム開発者にとっても、スリッページツールは、小売トレーダーが長年抱える最大の課題である「信頼性の高い市場深度データへのアクセスの制限」を補う、効率的で軽量なソリューションとなるはずです。
ツールの主要機能の詳細な概要
スリッページツールは、MetaTrader 5のチャート上で動作するシステムであり、ライブティックストリーム(VWAP、ティック/出来高不均衡、スプレッド、ATRコンテキスト)から注文フローの信号を再構築します。さらに、アラートやマーカーを通じて取引可能なエッジを可視化し、オプションで予想スリッページをリスクやロットサイズに反映させることができます。DOMが利用できない場合でも、オーダーブックの情報の実用的な代替手段として機能します。
以下に、このシステムの主要機能に関する数学的計算と詳細な説明を示します。
1. VWAP(出来高加重平均価格)
前回の議論では、VWAPについて詳細に分析しました。VWAPは、価格と取引量の両方を組み合わせ、指定された期間における加重平均価格を算出する高度なベンチマークです。単純な算術平均がすべての価格を同等に扱うのに対し、VWAPは取引量の多い価格レベルにより大きな重みを割り当てます。この手法により、VWAPは期間中に取引活動が集中した価格を正確に反映し、実際の市場コンセンサスを捉えることができます。
取引量を計算に組み込むことで、VWAPは異なる価格レベルでの取引の強度を考慮し、市場の「公正価値」をより正確に示す指標となります。トレーダーや機関投資家は、VWAPを使って市場状況を評価したり、エントリーとエグジットのタイミングを特定したり、このベンチマークに対する市場の方向性を判断したりします。VWAPは動的な指標であるため、日中の価格変動や流動性の状況を評価する信頼できる基準として機能し、実行戦略や市場分析において重要なツールです。

ここで
𝑃(i) = i番目のティックの価格𝑉(i) = i番目のティックの取引量
価格がVWAPを上回って取引される場合、市場は平均に対してプレミアムで取引されており、強気の傾向を示します。一方、価格がVWAPを下回る場合は割引を示し、弱気の傾向を反映します。トレーダーはVWAPを動的なサポートまたはレジスタンスとして利用し、このベンチマークに基づいて意思決定をおこないます。

すべてのブローカーが包括的なオーダーブックデータを提供しているわけではないため、このツールは受信したティックデータのみからVWAPラインを再構築します。この手法により、トレーダーはオーダーブックの「公正価値ゾーン」と同等の洞察を得ることができ、市場の均衡に関する貴重な代理情報として活用でき、より情報に基づいた取引判断が可能になります。
2. 不均衡
不均衡は、取引活動において積極的な買い手と売り手のどちらが優勢であるかを測定します。従来のオーダーブックでは、BidとAskの板数量を比較することで評価されます。しかし、直接のオーダーブックデータが利用できない場合、このツールはティックの動きを分析することで市場の不均衡を間接的に推測し、方向性の圧力や市場心理を把握する代替手段を提供します。

ここで
BuyVolume = 価格が上昇したティックの取引量の合計
SellVolume = 価格が下落したティックの取引量の合計
正の不均衡は買い圧力を示し、より積極的な買い手が優勢であることを反映します。一方、負の不均衡は売り圧力を示し、より積極的な売り手が優勢であることを反映します。

ツールはティックごとに価格の動きを監視します。上向きの動き(アップティック)は買い活動としてカウントされ、下向きの動き(ダウンティック)は売り活動としてカウントされます。この継続的かつリアルタイムの計算により、どちらの側が現在市場により大きな影響力を及ぼしているかを常時測定でき、短期的な方向性の動向に関する貴重な洞察を提供します。
3. スプレッド
スプレッドは、市場のミクロ構造を測る最も基本的かつ重要な指標の一つです。これは即時性のコストを示しており、トレーダーが市場成行注文を即座に執行するために「支払う」必要のあるコストを表します。この指標は、迅速な取引に伴う手数料や取引コストを反映するとともに、流動性の状況や市場全体の効率性に関する重要な洞察を提供します。
![]()
スプレッドが狭い場合、通常は流動性が高く効率的な市場環境であり、よりスムーズでコスト効率の高い取引が可能です。逆に、スプレッドが広い場合は流動性の低下や取引コストの上昇を示し、時には市場のストレスや不確実性の兆候となることもあります。

ツールはスプレッドをリアルタイムで継続的に監視します。シグナルは事前に設定したスプレッド閾値でフィルタリング可能であり、トレーダーは取引コストが異常に高い期間中のエントリーを回避できます。この手法は、市場の厚みが薄いときに取引を控えるオーダーブックの観察者の行動を模倣しており、より有利な実行条件の確保に役立ちます。
4. フロー
フローは短期的なセンチメント指標として機能し、指定されたローリングウィンドウ内で最近のティックが主に強気か弱気かを把握します。これは、注文フローの圧力をリアルタイムで確認する指標として機能し、トレーダーに現在の市場の勢いに関する即時の洞察を提供します。

- ここで

Flow > 0の場合、取引量の大部分が価格の上昇に関連しており、買い圧力が優勢であることを示します。逆に、Flow < 0の場合、取引量の大部分が価格の下落に関連しており、売り圧力が優勢であることを示します。
ツールは、サイン付きの取引量をリアルタイムで集計することにより、短期的な注文フローの圧力を動的に描写します。この手法は、トレーダーがオーダーブックラダー内で変化する不均衡をどのように解釈するかを忠実に反映しており、即時の市場感情や方向性バイアスに関する貴重な洞察を提供します。

これら4つの機能(VWAP、不均衡、スプレッド、フロー)を組み合わせることで、市場オーダーブックから得られる主要な洞察を包括的に把握できます。これらは生のティックデータのみから市場の主要動向を導き出すため、完全な注文書サポートがないブローカーでも貴重なミクロ構造情報を提供します。
MQL5への実装
このセクションでは、MQL5プログラミング言語を使用してスリッページツールを作成する手順をステップバイステップで解説します。
ファイルヘッダー、インクルード、および前方宣言
ファイルの先頭では、スクリプトはメタデータ(#property)を設定し、Trade.mqhをインクルードします。さらに、VWAPとFlowの関数シグネチャを宣言しておくことで、定義前でもこれらの関数を使用できるようにしています。ヘッダーには3つの実用的な目的があります。著者情報やバージョンを記録し、将来のメンテナンスに備えること、コンパイラの厳密モードを有効にして、安全でモダンなMQL5の文法を強制すること、依存関係を宣言して、後でEAに自動取引機能を追加する場合にトレード関連のルーチンを呼び出せるようにすることです。前方宣言はスタイル上の便利な工夫です。これにより、可読性の高い上位レベルの関数をファイル内のどこに置いても、テキスト上の順序を気にせず使用できます。メンテナンスの観点から、このファイル冒頭のブロックは読者にとってのロードマップとして機能します。ここで、利用可能な外部機能(インジケーターハンドルや取引ヘルパー関数など)を示し、後で主要な指標がどのように実装されているかを理解する準備を整えます。
// File metadata and includes #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/ja/users/lynnchris" #property version "1.0" #property strict #include <Trade\Trade.mqh> // CTrade utility for possible auto-trade usage
列挙型と入力パラメータ(構成サーフェス)
列挙型の定義と長い入力変数リストは、ツールのパブリックAPIを構成します。これらは、トレーダーがコードを変更せずにツールの動作を調整するための「操作ノブ」です。列挙型は、UIオプション(パネルのコーナー位置、アラートモードなど)に分かりやすい名前を付与します。入力パラメータは、重要なシグナル設定を公開します。具体的には、分単位のVWAPウィンドウ、秒単位の不均衡ウィンドウ、「安価な」スプレッド検出のためのスプレッド対ATR比率、アラート用フローの閾値とヒステリシス、ストップ用のリスク割合とATR乗数、さらに多数のUI/レイアウトオプションなどです。これらを入力値として公開する設計は重要です。EAを様々な通貨ペアや取引スタイルに柔軟に対応させることができるからです。
実稼働記事では、各入力パラメータの推奨範囲とトレードオフを説明します。たとえば、VWAPウィンドウを長く設定すると安定性は高まりますが、短期的な応答性は低下します。また、InpImbWindowSecを大きくするとフローは滑らかになりますが、微細構造シグナルは鈍化します。さらに、適切なデフォルト値は提供されていますが、ユーザーは各銘柄のティックレートや市場構造に応じて調整するようガイドされるべきです。
// Enums (UI choices) and user-exposed inputs (tuning knobs) enum eCorner { COR_LT=0, COR_LB=1, COR_RT=2, COR_RB=3 }; enum eAlertMode { AM_SINGLE=0, AM_ROLLING_CLEAR=1, AM_POOL_REUSE=2 }; input int InpVWAPminutes = 1440; // VWAP window in minutes (rolling) input int InpImbWindowSec = 30; // Imbalance/flow window in seconds input double InpCheapSpreadFrac = 0.50; // Spread < ATR * this fraction => "cheap" input double InpFlowTh = 0.30; // flow threshold for alerts input double InpFlowHystFactor = 0.80; // hysteresis factor for flow reset input double InpRiskPct = 1.0; // risk per trade as % of balance input double InpStopATRmult = 1.2; // stop = ATR * this multiplier input uint InpRingSize = 20000; // tick ring buffer size input uint InpTimerSec = 2; // UI refresh / aggregation cadence
グローバル状態と命名規則
グローバル変数は、銘柄設定やマーケットパラメータ(g_point、g_tickVal)、ATRインジケーターのハンドル、CTradeオブジェクト、そして循環ティックバッファを実装する配列群など、ツールの動作状態を初期化します。コードでは、チャートオブジェクト名にg_ プレフィックスを使用しています。この命名はオブジェクトの安全なクリーンアップに重要であり、EAが自身のラベルのみを操作し、他のインジケーターの描画を誤って削除することを防ぎます。
リングバッファ配列(g_time、g_bid、g_ask、g_last、g_vol)およびインデックス(g_head、g_used、g_size)は、すべての集計計算の基盤です。ティックをローカルに保存することで、プラットフォームAPIへの繰り返し呼び出しを避け、計算の決定論を向上させ、時間ウィンドウに基づく集計が可能になります。さらに、グローバル変数には変更検出用の過去値も保持しており、UIのフリッカーを抑制します。記事では、MQL5ではターミナルが単一スレッドで動作し、インジケーターハンドルや孤立したオブジェクトによるメモリリークがよく起こるため、グローバル状態を慎重に管理する必要があることを強調しています。
// Globals: symbol settings, ring-buffer arrays and indices string g_sym=""; double g_point=0.0, g_tickVal=0.0, g_tickSize=0.0; double g_volMin=0, g_volMax=0, g_volStep=0; int g_atrHandle = INVALID_HANDLE; MqlTick g_latestTick; datetime g_time[]; // ring buffer timestamps double g_bid[], g_ask[], g_last[], g_vol[]; // ring buffer data int g_head = -1; // index of most recent slot int g_used = 0; // number of filled slots int g_size = 0; // capacity (InpRingSize)
小さなヘルパー関数(SafeDiv、ARGB、RR)
いくつかの小さなヘルパー関数により、コードの正確性と可読性が向上します。SafeDivはゼロ除算エラーを回避する防御用ラッパーです。ライブデータにはゼロや未初期化の値が含まれる可能性があるため、重要なパターンとなります。ARGBはパネルの背景用にアルファブレンドされたカラー整数を生成します。アルファ処理を一箇所に集約することで、カラー計算を簡素化し、UIコードをコンパクトに保つことができます。RRはゼロ分母を回避しつつ、リスクリワード比を単一の式で計算します。これらのヘルパー関数は短いものの、非常に重要です。繰り返しの補助処理ではなく、メインロジックを取引のセマンティクスに集中させることができ、ライブ取引でデバッグが難しい実行時例外からも保護します。// Defensive helpers used across the codebase double SafeDiv(double a, double b) { return (b == 0.0 ? 0.0 : a/b); } uint ARGB(color c, int a) // produce ARGB color integer { if(a<0) a=0; if(a>255) a=255; return ((uint)a<<24) | (c & 0x00FFFFFF); } double RR(double entry,double stop,double tp) // simple R/R guard { return (entry - stop != 0.0 ? (tp - entry) / (entry - stop) : 0.0); }
リングバッファの初期化と追加(BufInit / BufAdd)
リングバッファの設計は意図的におこなわれています。BufInitは最小容量を保証し、配列のサイズを調整します。BufAddは最新のティックを次のスロットに書き込み、モジュラス演算を用いて循環させます。この設計により、挿入操作の時間計算量はO(1)となり、メモリ使用量も固定でコンパクトに保たれます。実務上、このパターンは高頻度の銘柄でメモリ変動を避けるために採用されており、ティックごとにArrayResizeをおこなうような無制限のプッシュよりも効率的です。さらに、BufAddは出来高を正規化します。volume_realが存在する場合はそれを使用し、存在しない場合はvolumeあるいは1.0にフォールバックします。これは、多くの小売ブローカーのティックデータが実際の取引量を省略するか、異なる方法で表現しているためです。この設計に伴うトレードオフも説明する必要があるでしょう。InpRingSizeを大きくすると、長いVWAPウィンドウ向けにより多くの履歴を保持できますが、メモリ消費が増加します。一方でバッファが小さすぎると、正確なVWAPやフロー計算に必要な直近のコンテキストが失われます。// Initialize ring buffer with minimum capacity void BufInit(int n) { if(n < 128) n = 128; g_size = n; ArrayResize(g_time, n); ArrayResize(g_bid, n); ArrayResize(g_ask, n); ArrayResize(g_last, n); ArrayResize(g_vol, n); g_head = -1; g_used = 0; } // Append a tick into the circular buffer (O(1) insertion) void BufAdd(const MqlTick &q) { if(g_size <= 0) return; g_head = (g_head + 1) % g_size; g_time[g_head] = q.time; g_bid[g_head] = q.bid; g_ask[g_head] = q.ask; g_last[g_head] = q.last; double vol = (q.volume_real > 0 ? q.volume_real : q.volume); g_vol[g_head] = (vol > 0 ? vol : 1.0); // safe fallback if(g_used < g_size) g_used++; }
価格アクセサー(Mid関数)
Mid関数は、保存されたティックの中で利用可能な最良の価格を返します。最後の価格(直近の取引)が存在する場合はそれを優先し、存在しない場合はBidとAskの中間値を使用します。この階層的アプローチは実用的です。「トレードプリント(直近約定価格)」は実際に成立した価格の最も直接的な証拠ですが、ティック更新の中には見積もり価格(クオート)のみが含まれることもあるため、中間値を使うことで公正価値を近似できます。使用できないスロットに対しては0を返すことで、呼び出し元にそのエントリをスキップするよう通知します。実務上、ラスト(直近取引)とミッド(中間値)のどちらを優先するかによって感度が変わることを読者に理解させることが重要です。ラスト優先のVWAPやフローは、実際に取引が発生した場合にのみ反応します。一方、ミッドポイントを使用するとデータ密度は増しますが、クオートノイズが混入する可能性があります。// Prefer last trade price; fallback to midpoint if last absent double Mid(int idx) { if(idx < 0 || idx >= g_size) return 0.0; if(g_last[idx] > 0.0) return g_last[idx]; if(g_bid[idx] > 0.0 && g_ask[idx] > 0.0) return 0.5 * (g_bid[idx] + g_ask[idx]); return 0.0; }
スプレッドとATRアクセサー
SpreadPipsとATRは、即時の取引コストと市場ボラティリティという2つの独立した尺度をカプセル化しています。SpreadPipsはSymbolInfoTickを呼び出し、「ask - bid」をg_pointで割ってスプレッドをピップス(またはポイント)で表現します。これにより、ATRとの比較が容易になります。ATR ()はiATRハンドルのCopyBufferを使用して最新のATR値を取得します。一方、ATR_Avgは短期のローリング平均を計算します。この分離は重要です。ATRは、ストップサイズや「安価なスプレッド」テスト(スプレッド < ATR × 割合)に使用されるボラティリティの基準として扱われます。このハウツー記事では、読者に次の点を認識させるべきです。1つ目は、CopyBufferはI/O操作を伴うため、使用は最小限に抑えること、2つ目は、ATRはInpTFによって決定される時間枠で計算されるため、InpTFの設定によってATRの値が変わり、ストップ設定や「安価スプレッド」の判定にも影響することです。// Spread in pips (points normalized by g_point) double SpreadPips() { MqlTick t; if(!SymbolInfoTick(g_sym, t) || g_point == 0.0) return 0.0; return (t.ask - t.bid) / g_point; } // Latest ATR (indicator handle must be created beforehand) double ATR() { if(g_atrHandle == INVALID_HANDLE) return 0.0; double buf[]; if(CopyBuffer(g_atrHandle, 0, 0, 1, buf) == 1) return buf[0]; return 0.0; } // Average ATR across 'bars' (caps at 200 to limit work) double ATR_Avg(int bars) { if(g_atrHandle == INVALID_HANDLE || bars <= 0) return 0.0; int cap = MathMin(bars, 200); double buf[]; int copied = CopyBuffer(g_atrHandle, 0, 0, cap, buf); if(copied <= 0) return 0.0; double s = 0.0; for(int i=0; i<copied; i++) s += buf[i]; return s / copied; }
アグリゲータ(Acc):コアデータコレクター
Accは、VWAPおよびFlow関数が呼び出す、単一の中央集約ルーチンです。sinceタイムスタンプが指定されると、最新のティックからリングバッファを逆方向にたどり、価格 × 出来高(VWAP分子)、合計出来高(VWAP分母)、およびオプションでflowNeededがtrueの場合は上昇ティックと下降ティックの数を累積します。ループは、sinceよりも古いティックに遭遇すると停止し、関数が時間ウィンドウを認識するようになっています。実装の微妙な点は重要です。prevは最初の比較をスキップするためにDBL_MAXに初期化され、無効な価格のティックはスキップされます。インデックスは開始に達するとラップアラウンドします。
この関数は、符号付き出来高を合計するのではなく、発生回数(ティックカウント)として意図的にupとdnをカウントします。これは、信頼できる出来高がないフィードでもシンプルかつ堅牢に動作させるための設計上の選択です。ここで明確に伝える必要があります。Accは出来高加重VWAPとティックベースのフローの両方を有効にしますが、ここで選択されたフロー指標は出来高ではなくイベントをカウントしており、この違いはシグナルの挙動に実務上の影響を与えます。
// Accumulate price*volume and volume; optionally compute up/dn tick counts void Acc(datetime since, double &pxVol, double &vol, int &up, int &dn, bool flowNeeded) { pxVol = 0.0; vol = 0.0; up = 0; dn = 0; if(g_used == 0) return; int idx = g_head; double prev = DBL_MAX; for(int i = 0; i < g_used; ++i) { if(g_time[idx] < since) break; double p = Mid(idx); if(p <= 0.0) { idx--; if(idx < 0) idx = g_size - 1; continue; } double w = (g_vol[idx] > 0.0 ? g_vol[idx] : 1.0); pxVol += p * w; // VWAP numerator vol += w; // VWAP denominator if(flowNeeded && prev != DBL_MAX) { if(p > prev) up++; else if(p < prev) dn++; } prev = p; idx--; if(idx < 0) idx = g_size - 1; } }
VWAPの実装(VWAP関数)
VWAP関数は、汎用のAccを使い慣れた式に変換します。TimeCurrent ()から設定された分数を引いてsinceを計算し、それをAccに渡してpxとvを取得し、「px/ v」を返します。出来高が存在しない場合は関数はゼロを返し、NaNsを防ぎます。概念的な観点から見ると、このツールのVWAPはローリング型の時間ウィンドウVWAP(デフォルトではセッションVWAPではありません)であり、これにより読者はInpVWAPminutesを介して応答性を制御できます。このツールでは、公正価値アンカーとして使う長期ウィンドウVWAPと、日中のマイクロストラクチャ分析に用いる短期ウィンドウVWAPの2つの一般的な使い方が可能です。また、必要に応じてsinceを変更することでセッションベースのVWAPを計算することもできます// Rolling VWAP over last 'minutes' minutes double VWAP(int minutes) { if(minutes <= 0) return 0.0; datetime since = TimeCurrent() - (datetime)minutes * 60; double px = 0.0, v = 0.0; int u, d; Acc(since, px, v, u, d, false); return (v > 0.0 ? px / v : 0.0); }
フローの実装(Flow関数)
Flowは、flowNeeded=trueを指定したAccを使用してアップティックカウントとダウンティックカウントを取得し、[-1,1]に正規化されたティックカウント不均衡である「(up - dn) / (up + dn)」を返します。この単純な比率により、方向性のある活動を即座に把握できます。値が+1に近い場合は、ウィンドウ内のほぼすべてのティックが上昇しており、積極的な買い圧力を示唆します。一方、-1に近い場合は売り優勢を意味します。
出来高加重の符号付き出来高ではなくティックカウントを明示的に選択している点は、強く強調する価値があります。ティックを単純にカウントすることで、すべての価格変動に等しい重みが与えられ、異常な単一の大規模取引にも影響されにくくなります。これに対し、出来高加重フローは変動の背後にある取引量の大きさを測定するもので、実際の取引サイズが信頼できる場合にはより有益です。このツールでは、ティックカウントベースのフローが用いられており、すべての価格変動に等しい重みを与え、異常な単一の大規模取引にも堅牢です。必要に応じて、Accを調整してsignedVolアキュムレータに加算/減算することで、出来高加重フローに変更することも可能です。
// Tick-count based flow (implemented in base tool) double Flow(int sec) { if(sec <= 0) return 0.0; datetime since = TimeCurrent() - sec; double px = 0.0, v = 0.0; int up = 0, dn = 0; Acc(since, px, v, up, dn, true); int tot = up + dn; return (tot ? double(up - dn) / tot : 0.0); // normalized [-1,1] } // Alternative: volume-weighted flow (replace Acc's tick-count mode) // *Requires modifying Acc to accumulate signedVol and totalVol* double VolumeWeightedFlow(int sec) { if(sec <= 0) return 0.0; datetime since = TimeCurrent() - sec; int idx = g_head; double signedVol = 0.0, totalVol = 0.0; double prev = DBL_MAX; for(int i = 0; i < g_used; ++i) { if(g_time[idx] < since) break; double p = Mid(idx); if(p <= 0.0) { idx--; if(idx < 0) idx = g_size - 1; continue; } double w = (g_vol[idx] > 0.0 ? g_vol[idx] : 1.0); if(prev != DBL_MAX) { if(p > prev) signedVol += w; else if(p < prev) signedVol -= w; } totalVol += w; prev = p; idx--; if(idx < 0) idx = g_size - 1; } return (totalVol ? signedVol / totalVol : 0.0); // normalized to [-1,1] }
オブジェクト、パネル、ラベルヘルパー関数(EnsureObj、SetLabelIfChanged、SetRectIfChanged)
UIヘルパーは、繰り返しおこなわれるオブジェクト作成やプロパティ更新を抽象化し、値を変更する前に現在の値を比較することで不要なObjectSet*呼び出しを回避します。これにより、ターミナルAPIを使ったオブジェクト作成やプロパティ設定によるCPU負荷やちらつきを軽減できます。EnsureObjはオブジェクト作成のセマンティクスを集中管理し、SetLabelIfChangedとSetRectIfChangedはラベルや四角形背景のプロパティ更新を標準化します。ソフトウェアエンジニアリングの観点からも有効な方法で、プラットフォーム特有の癖を分離し、主要ロジックを明確に保ちます。ドキュメントでは、これらのヘルパーが常にオブジェクトを選択不可に設定することで、誤ってチャートを操作してしまうことを防ぎ、パネルをインタラクティブなチャート要素ではなく、ダッシュボードとして動作させることを指摘します。// Create object if missing void EnsureObj(string name, ENUM_OBJECT type) { if(ObjectFind(0, name) == -1) ObjectCreate(0, name, type, 0, 0, 0); } // Update label text & position only if changed (reduces redraws) void SetLabelIfChanged(string name, int corner, int xdist, int ydist, string text, int fontsize, color col, string font) { EnsureObj(name, OBJ_LABEL); if(ObjectGetInteger(0,name,OBJPROP_CORNER) != corner) ObjectSetInteger(0,name,OBJPROP_CORNER,corner); if(ObjectGetInteger(0,name,OBJPROP_XDISTANCE) != xdist) ObjectSetInteger(0,name,OBJPROP_XDISTANCE,xdist); if(ObjectGetInteger(0,name,OBJPROP_YDISTANCE) != ydist) ObjectSetInteger(0,name,OBJPROP_YDISTANCE,ydist); if(ObjectGetString(0,name,OBJPROP_TEXT) != text) ObjectSetString(0,name,OBJPROP_TEXT,text); if(ObjectGetInteger(0,name,OBJPROP_FONTSIZE) != fontsize) ObjectSetInteger(0,name,OBJPROP_FONTSIZE,fontsize); if(ObjectGetInteger(0,name,OBJPROP_COLOR) != (int)col) ObjectSetInteger(0,name,OBJPROP_COLOR,col); if(ObjectGetString(0,name,OBJPROP_FONT) != font) ObjectSetString(0,name,OBJPROP_FONT,font); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); } // Update rectangle only when a property changed void SetRectIfChanged(string name,int corner,int xdist,int ydist,int xsize,int ysize,uint bgARGB) { EnsureObj(name, OBJ_RECTANGLE_LABEL); if(ObjectGetInteger(0,name,OBJPROP_CORNER) != corner) ObjectSetInteger(0,name,OBJPROP_CORNER,corner); if(ObjectGetInteger(0,name,OBJPROP_XDISTANCE) != xdist) ObjectSetInteger(0,name,OBJPROP_XDISTANCE,xdist); if(ObjectGetInteger(0,name,OBJPROP_YDISTANCE) != ydist) ObjectSetInteger(0,name,OBJPROP_YDISTANCE,ydist); if(ObjectGetInteger(0,name,OBJPROP_XSIZE) != xsize) ObjectSetInteger(0,name,OBJPROP_XSIZE,xsize); if(ObjectGetInteger(0,name,OBJPROP_YSIZE) != ysize) ObjectSetInteger(0,name,OBJPROP_YSIZE,ysize); if((uint)ObjectGetInteger(0,name,OBJPROP_BGCOLOR) != bgARGB) ObjectSetInteger(0,name,OBJPROP_BGCOLOR,bgARGB); if((uint)ObjectGetInteger(0,name,OBJPROP_COLOR) != bgARGB) ObjectSetInteger(0,name,OBJPROP_COLOR,bgARGB); ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false); }
パネルの作成とレイアウト(CreatePanelObjectsおよびUpdatePanelObjects)
CreatePanelObjectsは、情報パネルを構成するラベルと四角形をインスタンス化します。UpdatePanelObjectsは、テキスト幅、バーのサイズ、位置を計算し、プロパティを更新して最新の指標を視覚的に反映するレイアウトエンジンです。文字幅定数を使ってラベル幅を推定し、バーに使用可能なスペースを算出した上で、スプレッド、ATR、フローの比例した前景バーを描画します。
コードでは、ヘッダー、概要行、3本の水平バー、フッターのタイムスタンプというコンパクトな視覚的構成を採用しており、トレーダーは重要なシグナルを一目で把握できます。記事内では、各UI要素を基になる変数(spPips → スプレッドバー、flow → フローバー)と対応しており、設計上のトレードオフも示されています。事前に計算したおおよそのテキスト幅により複雑なフォント指標の呼び出しを回避できますが、稀にフォントによっては若干の位置ずれが生じる可能性があります。選択されたアルファブレンディングにより、チャートを隠さずにUIを読みやすくすることができます。
// Minimal set of dashboard objects used by UpdatePanelObjects void CreatePanelObjects() { EnsureObj("ESGP_bg", OBJ_RECTANGLE_LABEL); EnsureObj("ESGP_hdr", OBJ_LABEL); EnsureObj("ESGP_lbl", OBJ_LABEL); EnsureObj("ESGP_vwap", OBJ_LABEL); EnsureObj("ESGP_spbar", OBJ_RECTANGLE_LABEL); EnsureObj("ESGP_spbar_fg", OBJ_RECTANGLE_LABEL); EnsureObj("ESGP_flow_lbl", OBJ_LABEL); // Make them non-selectable by default string objs[] = {"ESGP_bg","ESGP_hdr","ESGP_lbl","ESGP_vwap","ESGP_spbar","ESGP_spbar_fg","ESGP_flow_lbl"}; for(int i=0;i<ArraySize(objs);i++) ObjectSetInteger(0,objs[i],OBJPROP_SELECTABLE,false); }
アラート管理と視覚マーカー
アラートサブシステムは、単一置換、ローリングクリア(タイムスタンプに基づく一意の名前)、およびプールされた円形マーカーセットの3つのモードをサポートします。アラートは、安価なスプレッド条件が満たされ、フローが設定された閾値を超えた場合にのみ生成され、フラッピングを防ぐためにヒステリシスが組み込まれています。コードは各マーカーのタイムスタンプをOBJPROP_TOOLTIPに保存し、AutoClearOldAlertsはInpAlertMaxAgeSecより古いマーカーを削除します。この設計は、チャート上でのジャーナリングを低依存で実現しており、外部ストレージを必要とせず、シグナルイベントの視覚的な監査証跡を提供します。ここでは、取引における使いやすさを強調するべきです。永続的なマーカーにより、視覚的にレビューやバックテストをおこないやすくなり、ヒステリシスによってノイズや誤った再トリガーも抑制されます。// Draw a simple buy/sell alert label in the panel corner void DrawAlertMarker(bool isBuy) { string nm = (isBuy ? "ESGP_alert_buy" : "ESGP_alert_sell"); if(ObjectFind(0, nm) != -1) ObjectDelete(0, nm); ObjectCreate(0, nm, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, nm, OBJPROP_CORNER, COR_LT); ObjectSetInteger(0, nm, OBJPROP_XDISTANCE, 8); ObjectSetInteger(0, nm, OBJPROP_YDISTANCE, isBuy ? 60 : 80); ObjectSetString(0, nm, OBJPROP_TEXT, isBuy ? "▲ BUY" : "▼ SELL"); ObjectSetInteger(0, nm, OBJPROP_FONTSIZE, 12); ObjectSetInteger(0, nm, OBJPROP_COLOR, isBuy ? clrLime : clrRed); ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false); ObjectSetString(0, nm, OBJPROP_TOOLTIP, IntegerToString((int)TimeCurrent())); // store timestamp }
OnInitおよびOnDeinitによるライフサイクル管理
OnInitは、必要なすべての起動タスクを実行します。具体的には、シンボルの選択、必要に応じたチャート時間足の変更、銘柄パラメータ(SYMBOL_POINT、SYMBOL_VOLUME_STEPなど)の取得、iATRを用いたATRインジケーターの作成、リングバッファの初期化、パネルオブジェクトの作成、そしてEventSetTimerによるタイマー設定が含まれます。各ステップではエラー(例:無効なATRハンドル)がチェックされ、重要な初期化障害の場合はINIT_FAILEDが返されます。これは、安全性を確保するための重要なパターンです。int OnInit() { g_sym = (_Symbol); if(!SymbolSelect(g_sym, true)) return INIT_FAILED; g_point = SymbolInfoDouble(g_sym, SYMBOL_POINT); g_tickVal = SymbolInfoDouble(g_sym, SYMBOL_TRADE_TICK_VALUE); g_tickSize= SymbolInfoDouble(g_sym, SYMBOL_TRADE_TICK_SIZE); g_volMin = SymbolInfoDouble(g_sym, SYMBOL_VOLUME_MIN); g_volMax = SymbolInfoDouble(g_sym, SYMBOL_VOLUME_MAX); g_volStep = SymbolInfoDouble(g_sym, SYMBOL_VOLUME_STEP); // create ATR handle for InpTF and InpATRperiod (example uses default TF & period) g_atrHandle = iATR(g_sym, PERIOD_M1, 14); if(g_atrHandle == INVALID_HANDLE) return INIT_FAILED; BufInit((int)InpRingSize); CreatePanelObjects(); EventSetTimer((int)InpTimerSec); return INIT_SUCCEEDED; }
OnDeinitは、タイマーの停止、パネルおよびアラートオブジェクトの削除、インジケーターハンドルの解放などのクリーンアップをおこないます。インジケーターハンドルを解放せず、タイマーを残したままにすると、ターミナル内でメモリやハンドルのリークが発生したり、EAを再度アタッチした際に予期せぬ動作を引き起こす可能性があるため注意が必要です。
void OnDeinit(const int reason) { EventKillTimer(); // remove panel objects (prefix "ESGP_") int total = ObjectsTotal(0); for(int i=total-1; i>=0; --i) { string nm = ObjectName(0, i); if(StringFind(nm, "ESGP_", 0) == 0) ObjectDelete(0, nm); } if(g_atrHandle != INVALID_HANDLE) { IndicatorRelease(g_atrHandle); g_atrHandle = INVALID_HANDLE; } }
OnTickの取り込み戦略
OnTickは意図的に軽量に設計されています。SymbolInfoTickで最新ティックを取得し、BufAddを通じてリングバッファに追加するだけです。負荷の高い処理はOnTimerに委ねられます。この設計は重要なアーキテクチャ上の選択です。一部の金融商品では、1秒間に数百〜数千のティックが生成されるため、OnTick内で高負荷のUI更新やインジケーター読み取りをおこなうと、メインスレッドが飽和してターミナルの動作が遅くなる可能性があります。取り込み処理と計算処理を分離することで、EAは応答性と決定論を維持できます。また、このパターンによりテストが簡単になります。OnTimerが制御された周期で実行されるため、挙動の再現性も向上します。void OnTick() { MqlTick q; if(SymbolInfoTick(g_sym, q)) { g_latestTick = q; // store latest tick BufAdd(q); // append into ring buffer (fast) } }
OnTimerメインループ:計算、意思決定、UI、アラート
OnTimerはオーケストレーターとして機能します。リングバッファが最新であることを確認し、更新リズムを維持し、スプレッドとATRをピップ単位で計算して「安いスプレッド」テストを適用し、VWAPとFlowを呼び出し、ポジションサイズとリスクパラメータを算出し、パネルを更新するかどうか(変更閾値に基づく)を判断し、アラートロジックを評価します。リスク計算では、ATRとInpStopATRmultに基づくストップを算出し、ストップ距離をロットあたりのティック数や金額に換算し、InpRiskPctに従って口座リスクの上限を設定するロットサイズを決定します。算出されたロットサイズはブローカーのg_volStepに丸められ、最小および最大の許容出来高の範囲内に制限されます。アラートは、安価なスプレッドかつフローが設定閾値を超えた場合に発生し、InpFlowHystFactorによるヒステリシスで急速な切り替えを防ぎます。この段落では、OnTimerが「意思決定エンジン」として機能することを示しています。コストフィルタリング(スプレッドvs ATR)、方向性の確認(フロー)、資金管理(リスク/ロット計算)が組み合わさることで、実用的な取引シグナルが形成されます。
Flowは現在の実装ではティックカウントベースであり、アップティックとダウンティックの頻度を測定します。これは、これらの価格変動の背後にある総出来高を測定するものではありません。これにより、信頼できるティック単位の取引量がないフィードでも堅牢に機能しますが、多くの小さなティックと単一の大きな取引で結果が異なる場合があります。VWAPは分単位のローリング時間ウィンドウとして計算され、セッションVWAPとは異なります。ローリングVWAPは即時的な反応を示し、セッションVWAPは日中のベンチマークを提供します。
スプレッドはg_pointを使用してピップに正規化され、ATRはポイントで取得した後ピップに変換され、比較が一貫しておこなえるようになっています。また、Flowを出来高加重型に変更することも可能で、Accを調整して符号付き出来高を累積し、総出来高で割ることで実装できます。
void OnTimer() { // 1) ensure buffer has latest tick (sometimes needed) MqlTick t; if(SymbolInfoTick(g_sym, t)) { if(g_used == 0 || g_time[g_head] != t.time) BufAdd(t); } // 2) compute metrics double spPips = SpreadPips(); double atrPts = ATR(); // ATR in price units (points) double atrPips = (atrPts > 0 && g_point > 0) ? atrPts / g_point : 0.0; bool cheap = (atrPts > 0 && spPips < atrPips * InpCheapSpreadFrac); double vwap = VWAP(InpVWAPminutes); double flow = Flow(InpImbWindowSec); // tick-count flow // 3) Example risk sizing (conservative) double bal = AccountInfoDouble(ACCOUNT_BALANCE); double bid = SymbolInfoDouble(g_sym, SYMBOL_BID); double stop = bid - InpStopATRmult * atrPts; double riskPx = bid - stop; double ticks = (g_tickSize > 0 ? riskPx / g_tickSize : 0.0); double cashPerLot = ticks * g_tickVal; double maxLoss = bal * InpRiskPct / 100.0; double rawLot = (cashPerLot > 0.0 ? maxLoss / cashPerLot : 0.0); double lot = (g_volStep > 0.0 ? MathFloor(rawLot / g_volStep) * g_volStep : 0.0); // 4) Alerting with hysteresis double resetThresh = InpFlowTh * InpFlowHystFactor; static bool buyF=false, sellF=false; if(cheap) { if(flow >= InpFlowTh && !buyF) { Alert("BUY edge: cheap spread + buy flow"); buyF = true; sellF = false; DrawAlertMarker(true); } else if(flow < resetThresh) buyF = false; if(flow <= -InpFlowTh && !sellF) { Alert("SELL edge: cheap spread + sell flow"); sellF = true; buyF = false; DrawAlertMarker(false); } else if(flow > -resetThresh) sellF = false; } }
最後に、ストレステストに関する考慮事項です。ストラテジーテスターのティック粒度は実際のライブフィードの挙動を正確に反映しない場合があるため、ティックリプレイや履歴ティックデータを使用してティックベースのロジックを検証する必要があります。ログ出力は慎重におこない、アクティブな銘柄でPrintを多用して端末を圧迫しないようにします。また、InpTimerSecが小さい場合はCPU使用率のプロファイリングが推奨されます。パラメータの目安としては、日中トレード向けのInpVWAPminutesは60〜240分、マイクロストラクチャ検出用のInpImbWindowSecは10〜60秒、InpCheapSpreadFracは銘柄に応じて0.3〜0.7程度が参考になります。チューニングは段階的におこなうのが望ましく、まず保守的な閾値で開始し、視覚的マーカーでシグナルを確認してから、自動売買を有効にするのが安全です。改善案としては、セッションベースのVWAPのオプションを提供する、フローを出来高加重にする、フローにEMA平滑化を加えてノイズを減らす、ティック履歴を記録し、オフラインで再現可能な分析をおこなう、といった方法が考えられます。
結果
EAを開発した後、次のステップはテストのための展開です。テストはデモ口座上でおこなうことも、バックテストで実施することも可能です。以下では、MetaTrader 5ターミナルでのテスト過程で取得した一連の画像を用いて、システムの動作と挙動を示します。
下の図では、パネルに以前発行されたBUYアラート(緑のヘッダー / BUYマーカー)が表示されています。しかし、現在の短期ウィンドウのフローはややマイナスで、Imbalanceは約-10.3%、Flowは-0.10です。
スプレッドは71.0ピップで、現在のボラティリティと比べると比較的小さく、システムでは「安価」と見なされています(スプレッド安価条件が満たされています)。ATRは469.4ピップとかなり大きいため、ATRに基づいて計算された保護ストップは広めになり、ストップは約563.3ピップ、TP2Rは1,126.6ピップとなり、リスクリワード比は2.00です。
全体として、EAのコストフィルター(安価なスプレッドを示す)は満たされています。しかし、フローによる方向性確認はこの時点でやや弱気であるため、以前のBUYマーカーが残っているにもかかわらず、新しい買いトリガーは生成されません。
初回の買いシグナル後、市場は時間の経過とともにトレード方向に動きました。下の図はこのプラスの動きを示しています。現在、新しい売りシグナルが示されており、市場状況の変化を反映しています。
その後、市場は下落に反転し、シグナルの妥当性が確認されました。下の図で、この動きを視覚的に確認できます。
すべてのシグナルにはアラート通知が伴い、MetaTrader 5の[エキスパート]タブに記録されるため、包括的な追跡と記録管理が可能です。
結論
スリッページツールは、小売トレーダーが直面する典型的な課題、すなわち「信頼性の高いオーダーブックデータへのアクセスが限られている一方で、高勝率のエントリーポイントを見つけるためには注文フローのシグナルが必要である」という問題に対処するために開発されました。本ツールは、包括的な深度ラダーを代替しようとするものではなく、利用可能なティックストリームから、実際の取引判断に使えるミクロ構造の洞察を抽出する仕組みです。ローリングVWAPを公正価値のアンカーとして用い、短期ウィンドウの不均衡指標とフロー指標で方向性の圧力を示し、さらにスプレッドとATRの文脈情報を組み合わせることで、シグナルが「取引可能」であり「リスクを把握したもの」になるよう設計されています。チャート上の視覚化やアラート、自動ポジションサイジングロジックと統合することで、生のティックデータをリアルタイムに意思決定へ転換します。
スリッページツールの特徴は、堅牢性とユーザーフレンドリーさを実用面で重視している点にあります。リングバッファと軽量なOnTick取り込みにより、高ティック銘柄でもターミナルのパフォーマンスを維持し、ディフェンシブなヘルパー関数や変更検出ロジックによって不要な再描画を防ぎます。アラートにはヒステリシスや設定可能なマーカーモードが組み込まれており、ノイズや誤シグナルの発生を抑制します。また、統合されたリスクサイジングロジックは、シグナル生成を実際の資金管理と結び付け、ATRベースのストップおよび口座リスクに照らして各トレード機会を評価することで、無謀なポジション取得ではなく規律あるサイズ管理を促します。
一方で、本ツールの限界を理解することも重要です。本ツールはオーダーブック情報の代理であり、直接的なDOMフィードではありません。フローはティックカウント(または任意で出来高加重)から推定し、VWAPはトレードプリントとミッドポイントから再構築しています。そのため、得られたシグナルは、チャート、複数時間足、ティックデータの再生といった文脈の中で検証する必要があります。VWAPウィンドウ、不均衡ウィンドウ、スプレッド比率、フロー閾値などのパラメータは、各銘柄のティックレートやボラティリティに合わせて調整する必要があり、万能な設定は存在しません。
実務で本システムを安全に導入するには、段階的なアプローチが推奨されます。(1) 再生モードまたはデモモードでパネルやマーカーを用いてシグナルを視覚的に確認する、(2) 閾値を保守的に調整しながらフォワードテストでパフォーマンスを検証する、(3) そのうえで、堅牢なストップおよびタイムアウトルールと包括的なログ記録を併せて、エントリーの自動化を検討する、という流れです。今後の拡張案として、選択可能なセッションVWAPモード、出来高加重フロー計算のオプション、急激な変動を抑えるEMA平滑化、そして再現性のある分析を支援するイベントログのエクスポート機能などが考えられます。
他の記事もご覧ください。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/19290
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
機械学習の限界を克服する(第3回):既約誤差に関する新たな視点
MQL5での取引戦略の自動化(第29回):プライスアクションに基づくガートレーハーモニックパターンシステムの作成
平均足を使ったプロフェッショナルな取引システムの構築(第1回):カスタムインジケーターの開発
MetaTraderとGoogleシートがPythonAnywhereで融合:安全なデータフローのガイド
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
なぜパネルがないのですか?
サー
なぜパネルがないのですか?