【MQL4リファレンス】MqlTick構造体の使い方!SymbolInfoTick()でBid/Askをリアルタイム取得する方法

構造体

MqlTick構造体とは

MqlTick構造体は、通貨ペアなどのシンボルにおける最新の価格情報(ティック情報)を格納するための構造体です。SymbolInfoTick()関数と組み合わせて使用することで、Bid価格・Ask価格・Last価格・出来高・タイムスタンプなどをリアルタイムに取得できます。

従来の定義済み変数(BidAsk)でも現在値は取得可能ですが、MqlTick構造体を使えば1回の関数呼び出しで複数の価格情報をまとめて取得できるため、効率的で安全なプログラミングが可能になります。

MqlTick構造体の定義

struct MqlTick
{
   datetime time;          // 最後の価格更新の時刻
   double   bid;           // 現在のBid価格
   double   ask;           // 現在のAsk価格
   double   last;          // 最後の約定価格(Last)
   ulong    volume;        // 現在のLast価格の出来高
   long     time_msc;      // 最後の価格更新の時刻(ミリ秒単位)
   uint     flags;         // ティックフラグ
};

各メンバの説明

メンバ名 説明
time datetime 最後の価格更新が行われた時刻(秒単位)
bid double 現在のBid(売り値)価格
ask double 現在のAsk(買い値)価格
last double 最後の約定価格。FXでは通常0が返されます
volume ulong 現在のLast価格に対応する出来高
time_msc long 最後の価格更新の時刻(ミリ秒精度、1970年1月1日からの経過ミリ秒)
flags uint ティックフラグ(どの値が変更されたかを示すビットフラグ)

SymbolInfoTick()関数の書式

bool SymbolInfoTick(
   string   symbol,     // シンボル名
   MqlTick& tick        // ティック情報を受け取る構造体への参照
);

引数

引数 説明
symbol string 価格情報を取得するシンボル名(例:”USDJPY”)
tick MqlTick& ティック情報を格納する構造体の参照

戻り値

bool型 — 取得に成功した場合はtrue、失敗した場合はfalseを返します。失敗した場合はGetLastError()でエラーコードを確認できます。

プログラム例1:基本的なBid/Ask価格の取得

最もシンプルな使い方として、現在のチャートシンボルのBid/Ask価格とスプレッドを取得・表示する例です。

//+------------------------------------------------------------------+
//| プログラム例1:基本的なBid/Ask価格の取得                          |
//+------------------------------------------------------------------+
void OnTick()
{
   // MqlTick構造体の変数を宣言
   MqlTick tick;

   // 現在のシンボルのティック情報を取得
   if(SymbolInfoTick(_Symbol, tick))
   {
      // Bid価格とAsk価格を出力
      Print("シンボル: ", _Symbol);
      Print("Bid: ", DoubleToString(tick.bid, _Digits));
      Print("Ask: ", DoubleToString(tick.ask, _Digits));

      // スプレッドを計算(ポイント単位)
      double spread = (tick.ask - tick.bid) / _Point;
      Print("スプレッド: ", DoubleToString(spread, 1), " points");

      // ティック受信時刻を出力
      Print("更新時刻: ", TimeToString(tick.time, TIME_DATE | TIME_SECONDS));
   }
   else
   {
      // 取得失敗時のエラー処理
      Print("ティック情報の取得に失敗しました。エラー: ", GetLastError());
   }
}

このコードでは_Symbolを使用して現在チャートに表示されている通貨ペアのティック情報を取得しています。_Digitsは現在のシンボルの小数桁数を自動的に返すため、正しい桁数で価格を表示できます。

プログラム例2:複数通貨ペアの価格モニタリング

SymbolInfoTick()は現在のチャート以外のシンボルの価格も取得できます。この例では複数の通貨ペアを同時に監視し、チャート上にダッシュボード風に表示します。

//+------------------------------------------------------------------+
//| プログラム例2:複数通貨ペアの価格モニタリング                      |
//+------------------------------------------------------------------+
// 監視する通貨ペアリスト
string g_symbols[] = {"USDJPY", "EURUSD", "GBPUSD", "AUDUSD"};

//+------------------------------------------------------------------+
//| 初期化処理                                                        |
//+------------------------------------------------------------------+
int OnInit()
{
   // チャートの左上にラベルを準備
   int totalSymbols = ArraySize(g_symbols);
   for(int i = 0; i < totalSymbols; i++)
   {
      // 各シンボルのラベルオブジェクトを作成
      string objName = "Label_" + g_symbols[i];
      ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0);
      ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
      ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, 10);
      ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, 20 + i * 22);
      ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, 11);
      ObjectSetString(0, objName, OBJPROP_FONT, "Consolas");
   }

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| ティック受信時の処理                                               |
//+------------------------------------------------------------------+
void OnTick()
{
   MqlTick tick;
   int totalSymbols = ArraySize(g_symbols);

   for(int i = 0; i < totalSymbols; i++)
   {
      string objName = "Label_" + g_symbols[i];

      // 各シンボルのティック情報を取得
      if(SymbolInfoTick(g_symbols[i], tick))
      {
         // 小数桁数をシンボルごとに取得
         int digits = (int)SymbolInfoInteger(g_symbols[i], SYMBOL_DIGITS);

         // スプレッドをポイント単位で計算
         double point = SymbolInfoDouble(g_symbols[i], SYMBOL_POINT);
         double spread = (tick.ask - tick.bid) / point;

         // 表示テキストを構築
         string text = StringFormat("%s  Bid: %s  Ask: %s  Spread: %.1f",
                                    g_symbols[i],
                                    DoubleToString(tick.bid, digits),
                                    DoubleToString(tick.ask, digits),
                                    spread);

         ObjectSetString(0, objName, OBJPROP_TEXT, text);
         ObjectSetInteger(0, objName, OBJPROP_COLOR, clrWhite);
      }
      else
      {
         // 取得失敗時はエラー表示
         ObjectSetString(0, objName, OBJPROP_TEXT, g_symbols[i] + "  データ取得失敗");
         ObjectSetInteger(0, objName, OBJPROP_COLOR, clrRed);
      }
   }
   // チャートを再描画
   ChartRedraw();
}

//+------------------------------------------------------------------+
//| 終了処理                                                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // 作成したオブジェクトを削除
   int totalSymbols = ArraySize(g_symbols);
   for(int i = 0; i < totalSymbols; i++)
   {
      ObjectDelete(0, "Label_" + g_symbols[i]);
   }
}

他のシンボルの価格を取得する際は、対象シンボルが気配値表示(マーケットウォッチ)ウィンドウに追加されている必要があります。追加されていない場合、SymbolInfoTick()はfalseを返します。

プログラム例3:スプレッドの監視とエントリー条件の判定

スプレッドが指定値以下に狭まったときだけエントリーを行う、実践的なスプレッドフィルター付きの注文ロジックの例です。

//+------------------------------------------------------------------+
//| プログラム例3:スプレッドフィルター付きエントリー                  |
//+------------------------------------------------------------------+
input double MaxSpreadPoints = 20.0;  // 許容最大スプレッド(ポイント単位)
input double LotSize         = 0.01;  // 取引ロットサイズ
input int    MagicNumber     = 12345; // マジックナンバー

//+------------------------------------------------------------------+
//| ティック受信時の処理                                               |
//+------------------------------------------------------------------+
void OnTick()
{
   MqlTick tick;

   // ティック情報を取得
   if(!SymbolInfoTick(_Symbol, tick))
   {
      Print("ティック情報取得失敗: ", GetLastError());
      return;
   }

   // 現在のスプレッドをポイント単位で計算
   double currentSpread = (tick.ask - tick.bid) / _Point;

   // スプレッドが許容範囲内かチェック
   if(currentSpread > MaxSpreadPoints)
   {
      // スプレッドが広すぎるのでエントリーしない
      Comment(StringFormat("スプレッド: %.1f pts(上限: %.1f pts)- エントリー待機中",
              currentSpread, MaxSpreadPoints));
      return;
   }

   // --- ここにエントリー条件を記述 ---
   // (例:移動平均線のゴールデンクロス判定など)
   bool buySignal  = CheckBuyCondition();
   bool sellSignal = CheckSellCondition();

   // 既存ポジションがなければエントリー
   if(OrdersTotal() == 0)
   {
      if(buySignal)
      {
         // MqlTickから取得したAsk価格で買い注文
         int ticket = OrderSend(_Symbol, OP_BUY, LotSize, tick.ask, 3,
                                0, 0, "MqlTick Buy", MagicNumber, 0, clrBlue);
         if(ticket > 0)
            Print("買い注文成功 Ask=", DoubleToString(tick.ask, _Digits),
                  " Spread=", DoubleToString(currentSpread, 1));
         else
            Print("買い注文失敗: ", GetLastError());
      }
      else if(sellSignal)
      {
         // MqlTickから取得したBid価格で売り注文
         int ticket = OrderSend(_Symbol, OP_SELL, LotSize, tick.bid, 3,
                                0, 0, "MqlTick Sell", MagicNumber, 0, clrRed);
         if(ticket > 0)
            Print("売り注文成功 Bid=", DoubleToString(tick.bid, _Digits),
                  " Spread=", DoubleToString(currentSpread, 1));
         else
            Print("売り注文失敗: ", GetLastError());
      }
   }

   Comment(StringFormat("Bid: %s | Ask: %s | Spread: %.1f pts | 状態: 監視中",
           DoubleToString(tick.bid, _Digits),
           DoubleToString(tick.ask, _Digits),
           currentSpread));
}

//+------------------------------------------------------------------+
//| 買い条件チェック(例:簡易的なMA判定)                             |
//+------------------------------------------------------------------+
bool CheckBuyCondition()
{
   double maFast = iMA(_Symbol, 0, 10, 0, MODE_EMA, PRICE_CLOSE, 1);
   double maSlow = iMA(_Symbol, 0, 25, 0, MODE_EMA, PRICE_CLOSE, 1);
   double maFastPrev = iMA(_Symbol, 0, 10, 0, MODE_EMA, PRICE_CLOSE, 2);
   double maSlowPrev = iMA(_Symbol, 0, 25, 0, MODE_EMA, PRICE_CLOSE, 2);

   // ゴールデンクロス判定
   return (maFastPrev < maSlowPrev && maFast > maSlow);
}

//+------------------------------------------------------------------+
//| 売り条件チェック(例:簡易的なMA判定)                             |
//+------------------------------------------------------------------+
bool CheckSellCondition()
{
   double maFast = iMA(_Symbol, 0, 10, 0, MODE_EMA, PRICE_CLOSE, 1);
   double maSlow = iMA(_Symbol, 0, 25, 0, MODE_EMA, PRICE_CLOSE, 1);
   double maFastPrev = iMA(_Symbol, 0, 10, 0, MODE_EMA, PRICE_CLOSE, 2);
   double maSlowPrev = iMA(_Symbol, 0, 25, 0, MODE_EMA, PRICE_CLOSE, 2);

   // デッドクロス判定
   return (maFastPrev > maSlowPrev && maFast < maSlow);
}

スプレッドフィルターは実運用において非常に重要です。経済指標発表時やマーケットオープン直後はスプレッドが大きく広がるため、不利な価格でのエントリーを防止できます。

プログラム例4:ティック情報のCSVログ記録

MqlTick構造体のミリ秒精度のタイムスタンプ(time_msc)を活用し、ティックデータをCSVファイルに記録する例です。ティック分析やバックテスト用のデータ作成に役立ちます。

//+------------------------------------------------------------------+
//| プログラム例4:ティックデータのCSVログ記録                         |
//+------------------------------------------------------------------+
input int MaxRecords = 10000;  // 最大記録件数(0で無制限)

int    g_fileHandle = INVALID_HANDLE;  // ファイルハンドル
int    g_recordCount = 0;              // 記録件数カウンター
double g_lastBid = 0;                  // 前回のBid価格
double g_lastAsk = 0;                  // 前回のAsk価格

//+------------------------------------------------------------------+
//| 初期化処理                                                        |
//+------------------------------------------------------------------+
int OnInit()
{
   // ファイル名に日付を含めて一意にする
   string fileName = _Symbol + "_ticks_" +
                     TimeToString(TimeCurrent(), TIME_DATE) + ".csv";

   // 日付部分のドットをハイフンに変換
   StringReplace(fileName, ".", "-");

   // CSVファイルを書き込みモードで開く
   g_fileHandle = FileOpen(fileName, FILE_WRITE | FILE_CSV, ',');

   if(g_fileHandle == INVALID_HANDLE)
   {
      Print("ファイルのオープンに失敗しました: ", GetLastError());
      return(INIT_FAILED);
   }

   // ヘッダー行を書き込み
   FileWrite(g_fileHandle,
             "Time",
             "TimeMsc",
             "Bid",
             "Ask",
             "Spread(pts)",
             "Last",
             "Volume");

   Print("ティックログ記録開始: ", fileName);
   g_recordCount = 0;

   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| ティック受信時の処理                                               |
//+------------------------------------------------------------------+
void OnTick()
{
   // 最大記録件数チェック
   if(MaxRecords > 0 && g_recordCount >= MaxRecords)
      return;

   // ファイルハンドルが有効か確認
   if(g_fileHandle == INVALID_HANDLE)
      return;

   MqlTick tick;

   // ティック情報を取得
   if(!SymbolInfoTick(_Symbol, tick))
      return;

   // 価格が変化した場合のみ記録(不要なデータを減らす)
   if(tick.bid == g_lastBid && tick.ask == g_lastAsk)
      return;

   // スプレッドを計算
   double spread = (tick.ask - tick.bid) / _Point;

   // time_mscからミリ秒部分を抽出
   // time_mscは1970年1月1日からのミリ秒数
   int milliseconds = (int)(tick.time_msc % 1000);

   // 時刻文字列を構築(秒 + ミリ秒)
   string timeStr = TimeToString(tick.time, TIME_DATE | TIME_SECONDS)
                    + "." + IntegerToString(milliseconds, 3, '0');

   // CSVにデータを書き込み
   FileWrite(g_fileHandle,
             timeStr,
             IntegerToString(tick.time_msc),
             DoubleToString(tick.bid, _Digits),
             DoubleToString(tick.ask, _Digits),
             DoubleToString(spread, 1),
             DoubleToString(tick.last, _Digits),
             IntegerToString(tick.volume));

   // 前回の価格を更新
   g_lastBid = tick.bid;
   g_lastAsk = tick.ask;

   // カウンターを更新
   g_recordCount++;

   // 1000件ごとにフラッシュして安全性を確保
   if(g_recordCount % 1000 == 0)
   {
      FileFlush(g_fileHandle);
      Print("記録件数: ", g_recordCount);
   }

   // チャートに状況を表示
   Comment(StringFormat("ティックログ記録中\n件数: %d / %s\nBid: %s  Ask: %s",
           g_recordCount,
           (MaxRecords > 0 ? IntegerToString(MaxRecords) : "無制限"),
           DoubleToString(tick.bid, _Digits),
           DoubleToString(tick.ask, _Digits)));
}

//+------------------------------------------------------------------+
//| 終了処理                                                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // ファイルを閉じる
   if(g_fileHandle != INVALID_HANDLE)
   {
      FileClose(g_fileHandle);
      Print("ティックログ記録終了。総記録件数: ", g_recordCount);
   }
}

CSVファイルはMT4のデータフォルダ内のMQL4\Files\ディレクトリに保存されます。time_mscメンバを活用することで、通常の秒単位のタイムスタンプでは不可能なミリ秒精度の時刻記録が可能になります。

使い方のポイント・注意事項

1. Bid/Ask定義済み変数との違い

MQL4では従来からBidAskという定義済み変数が使えますが、これらは現在のチャートのシンボルの価格しか取得できません。SymbolInfoTick()を使えば任意のシンボルの価格を取得できるため、マルチ通貨ペアEAを開発する際に不可欠です。

// 従来の方法(現在のシンボルのみ)
double bid1 = Bid;
double ask1 = Ask;

// MqlTickを使う方法(任意のシンボルに対応)
MqlTick tick;
SymbolInfoTick("EURUSD", tick);
double bid2 = tick.bid;
double ask2 = tick.ask;

2. lastとvolumeについて

FX(外国為替)取引ではlast(最終約定価格)とvolume(出来高)は通常0が返されます。これらのメンバは主に株式や先物など、取引所取引型の金融商品で使用されます。

3. time_mscの活用

time_mscメンバはミリ秒単位のタイムスタンプを返します。高頻度トレーディング(HFT)やティック分析において、秒単位のtimeでは精度が不足する場面で威力を発揮します。

4. エラーハンドリングを忘れずに

SymbolInfoTick()は失敗する可能性があります(シンボルが無効、マーケットが閉じている場合など)。必ず戻り値をチェックし、falseの場合はGetLastError()でエラーを確認しましょう。

5. マーケットウォッチへの追加

他のシンボルの価格を取得する場合、そのシンボルがマーケットウォッチウィンドウに表示されている必要があります。コードから追加する場合は以下のようにします。

// シンボルをマーケットウォッチに追加
SymbolSelect("GBPJPY", true);

// 追加後にティック情報を取得
MqlTick tick;
if(SymbolInfoTick("GBPJPY", tick))
{
   Print("GBPJPY Bid=", tick.bid, " Ask=", tick.ask);
}

6. OnTick()の外での使用

SymbolInfoTick()はOnTick()以外の関数(OnTimer()OnChartEvent()など)からも呼び出すことができます。タイマーを使った定期的な価格チェックなどに活用できます。

int OnInit()
{
   // 1秒ごとにタイマーイベントを発生させる
   EventSetTimer(1);
   return(INIT_SUCCEEDED);
}

void OnTimer()
{
   MqlTick tick;
   if(SymbolInfoTick(_Symbol, tick))
   {
      // タイマーからでも最新価格を取得可能
      Comment("Timer更新 Bid=", tick.bid, " Ask=", tick.ask);
   }
}

void OnDeinit(const int reason)
{
   EventKillTimer();
}

まとめ

MqlTick構造体とSymbolInfoTick()関数は、MQL4プログラミングにおける価格情報取得の基本ツールです。主なメリットをまとめると以下の通りです。

  • 一括取得:1回の呼び出しでBid/Ask/Last/Volume/タイムスタンプをまとめて取得
  • マルチシンボル対応:任意のシンボルの価格を取得可能
  • 高精度タイムスタンプ:ミリ秒単位の時刻情報を取得可能
  • 安全性:エラーハンドリングにより信頼性の高いコードが書ける

特にマルチ通貨ペアEAやスプレッド監視ツール、ティックデータの記録・分析など、本格的なトレーディングシステムの開発では欠かせない要素です。