<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>構造体 アーカイブ - 自動売買を作ろう！</title>
	<atom:link href="https://mql-programing.com/archives/tag/%e6%a7%8b%e9%80%a0%e4%bd%93/feed/" rel="self" type="application/rss+xml" />
	<link>https://mql-programing.com/archives/tag/構造体/</link>
	<description>MQLプログラミング学習サイト</description>
	<lastBuildDate>Tue, 31 Mar 2026 13:23:00 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://mql-programing.com/main29/wp-content/uploads/2021/02/cropped-ブログアイコン-32x32.jpg</url>
	<title>構造体 アーカイブ - 自動売買を作ろう！</title>
	<link>https://mql-programing.com/archives/tag/構造体/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>【MQL4リファレンス】MqlTick構造体の使い方！SymbolInfoTick()でBid/Askをリアルタイム取得する方法</title>
		<link>https://mql-programing.com/archives/12857/%e3%80%90mql4%e3%83%aa%e3%83%95%e3%82%a1%e3%83%ac%e3%83%b3%e3%82%b9%e3%80%91mqltick%e6%a7%8b%e9%80%a0%e4%bd%93%e3%81%ae%e4%bd%bf%e3%81%84%e6%96%b9%ef%bc%81symbolinfotick%e3%81%a7bid-ask%e3%82%92/</link>
		
		<dc:creator><![CDATA[朝日奈りさ]]></dc:creator>
		<pubDate>Tue, 31 Mar 2026 02:45:15 +0000</pubDate>
				<category><![CDATA[構造体]]></category>
		<category><![CDATA[【辞書】MQLリファレンス]]></category>
		<category><![CDATA[MQL4]]></category>
		<category><![CDATA[MqlTick]]></category>
		<category><![CDATA[SymbolInfoTick]]></category>
		<category><![CDATA[スプレッドフィルター]]></category>
		<guid isPermaLink="false">https://mql-programing.com/?p=12857</guid>

					<description><![CDATA[<p>MqlTick構造体とは MqlTick構造体は、通貨ペアなどのシンボルにおける最新の価格情報（ティック情報）を格納するための構造体です。SymbolInfoTick()関数と組み合わせて使用することで、Bid価格・As [&#8230;]</p>
<p>投稿 <a href="https://mql-programing.com/archives/12857/%e3%80%90mql4%e3%83%aa%e3%83%95%e3%82%a1%e3%83%ac%e3%83%b3%e3%82%b9%e3%80%91mqltick%e6%a7%8b%e9%80%a0%e4%bd%93%e3%81%ae%e4%bd%bf%e3%81%84%e6%96%b9%ef%bc%81symbolinfotick%e3%81%a7bid-ask%e3%82%92/">【MQL4リファレンス】MqlTick構造体の使い方！SymbolInfoTick()でBid/Askをリアルタイム取得する方法</a> は <a href="https://mql-programing.com">自動売買を作ろう！</a> に最初に表示されました。</p>
]]></description>
										<content:encoded><![CDATA[<h2><span id="toc1">MqlTick構造体とは</span></h2>
<p>MqlTick構造体は、通貨ペアなどのシンボルにおける最新の価格情報（ティック情報）を格納するための構造体です。<strong>SymbolInfoTick()関数</strong>と組み合わせて使用することで、Bid価格・Ask価格・Last価格・出来高・タイムスタンプなどをリアルタイムに取得できます。</p>
<p>従来の定義済み変数（<code>Bid</code>、<code>Ask</code>）でも現在値は取得可能ですが、MqlTick構造体を使えば<strong>1回の関数呼び出しで複数の価格情報をまとめて取得</strong>できるため、効率的で安全なプログラミングが可能になります。</p>
<h2><span id="toc2">MqlTick構造体の定義</span></h2>
<pre><code class="language-mql4">struct MqlTick
{
   datetime time;          // 最後の価格更新の時刻
   double   bid;           // 現在のBid価格
   double   ask;           // 現在のAsk価格
   double   last;          // 最後の約定価格（Last）
   ulong    volume;        // 現在のLast価格の出来高
   long     time_msc;      // 最後の価格更新の時刻（ミリ秒単位）
   uint     flags;         // ティックフラグ
};
</code></pre>
<h3><span id="toc3">各メンバの説明</span></h3>
<table>
<thead>
<tr>
<th>メンバ名</th>
<th>型</th>
<th>説明</th>
</tr>
</thead>
<tbody>
<tr>
<td>time</td>
<td>datetime</td>
<td>最後の価格更新が行われた時刻（秒単位）</td>
</tr>
<tr>
<td>bid</td>
<td>double</td>
<td>現在のBid（売り値）価格</td>
</tr>
<tr>
<td>ask</td>
<td>double</td>
<td>現在のAsk（買い値）価格</td>
</tr>
<tr>
<td>last</td>
<td>double</td>
<td>最後の約定価格。FXでは通常0が返されます</td>
</tr>
<tr>
<td>volume</td>
<td>ulong</td>
<td>現在のLast価格に対応する出来高</td>
</tr>
<tr>
<td>time_msc</td>
<td>long</td>
<td>最後の価格更新の時刻（ミリ秒精度、1970年1月1日からの経過ミリ秒）</td>
</tr>
<tr>
<td>flags</td>
<td>uint</td>
<td>ティックフラグ（どの値が変更されたかを示すビットフラグ）</td>
</tr>
</tbody>
</table>
<h2><span id="toc4">SymbolInfoTick()関数の書式</span></h2>
<pre><code class="language-mql4">bool SymbolInfoTick(
   string   symbol,     // シンボル名
   MqlTick& tick        // ティック情報を受け取る構造体への参照
);
</code></pre>
<h3><span id="toc5">引数</span></h3>
<table>
<thead>
<tr>
<th>引数</th>
<th>型</th>
<th>説明</th>
</tr>
</thead>
<tbody>
<tr>
<td>symbol</td>
<td>string</td>
<td>価格情報を取得するシンボル名（例：&#8221;USDJPY&#8221;）</td>
</tr>
<tr>
<td>tick</td>
<td>MqlTick&#038;</td>
<td>ティック情報を格納する構造体の参照</td>
</tr>
</tbody>
</table>
<h3><span id="toc6">戻り値</span></h3>
<p><strong>bool型</strong> — 取得に成功した場合は<code>true</code>、失敗した場合は<code>false</code>を返します。失敗した場合は<code>GetLastError()</code>でエラーコードを確認できます。</p>
<h2><span id="toc7">プログラム例1：基本的なBid/Ask価格の取得</span></h2>
<p>最もシンプルな使い方として、現在のチャートシンボルのBid/Ask価格とスプレッドを取得・表示する例です。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| プログラム例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());
   }
}
</code></pre>
<p>このコードでは<code>_Symbol</code>を使用して現在チャートに表示されている通貨ペアのティック情報を取得しています。<code>_Digits</code>は現在のシンボルの小数桁数を自動的に返すため、正しい桁数で価格を表示できます。</p>
<h2><span id="toc8">プログラム例2：複数通貨ペアの価格モニタリング</span></h2>
<p>SymbolInfoTick()は現在のチャート以外のシンボルの価格も取得できます。この例では複数の通貨ペアを同時に監視し、チャート上にダッシュボード風に表示します。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| プログラム例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]);
   }
}
</code></pre>
<p>他のシンボルの価格を取得する際は、対象シンボルが<strong>気配値表示（マーケットウォッチ）ウィンドウに追加されている</strong>必要があります。追加されていない場合、SymbolInfoTick()はfalseを返します。</p>
<h2><span id="toc9">プログラム例3：スプレッドの監視とエントリー条件の判定</span></h2>
<p>スプレッドが指定値以下に狭まったときだけエントリーを行う、実践的なスプレッドフィルター付きの注文ロジックの例です。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| プログラム例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 &#038;&#038; 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);
}
</code></pre>
<p>スプレッドフィルターは実運用において非常に重要です。経済指標発表時やマーケットオープン直後はスプレッドが大きく広がるため、不利な価格でのエントリーを防止できます。</p>
<h2><span id="toc10">プログラム例4：ティック情報のCSVログ記録</span></h2>
<p>MqlTick構造体のミリ秒精度のタイムスタンプ（time_msc）を活用し、ティックデータをCSVファイルに記録する例です。ティック分析やバックテスト用のデータ作成に役立ちます。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| プログラム例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);
   }
}
</code></pre>
<p>CSVファイルはMT4のデータフォルダ内の<code>MQL4\Files\</code>ディレクトリに保存されます。<code>time_msc</code>メンバを活用することで、通常の秒単位のタイムスタンプでは不可能な<strong>ミリ秒精度の時刻記録</strong>が可能になります。</p>
<h2><span id="toc11">使い方のポイント・注意事項</span></h2>
<h3><span id="toc12">1. Bid/Ask定義済み変数との違い</span></h3>
<p>MQL4では従来から<code>Bid</code>と<code>Ask</code>という定義済み変数が使えますが、これらは<strong>現在のチャートのシンボルの価格しか取得できません</strong>。SymbolInfoTick()を使えば任意のシンボルの価格を取得できるため、マルチ通貨ペアEAを開発する際に不可欠です。</p>
<pre><code class="language-mql4">// 従来の方法（現在のシンボルのみ）
double bid1 = Bid;
double ask1 = Ask;

// MqlTickを使う方法（任意のシンボルに対応）
MqlTick tick;
SymbolInfoTick("EURUSD", tick);
double bid2 = tick.bid;
double ask2 = tick.ask;
</code></pre>
<h3><span id="toc13">2. lastとvolumeについて</span></h3>
<p>FX（外国為替）取引では<code>last</code>（最終約定価格）と<code>volume</code>（出来高）は通常<strong>0</strong>が返されます。これらのメンバは主に株式や先物など、取引所取引型の金融商品で使用されます。</p>
<h3><span id="toc14">3. time_mscの活用</span></h3>
<p><code>time_msc</code>メンバはミリ秒単位のタイムスタンプを返します。高頻度トレーディング（HFT）やティック分析において、秒単位の<code>time</code>では精度が不足する場面で威力を発揮します。</p>
<h3><span id="toc15">4. エラーハンドリングを忘れずに</span></h3>
<p>SymbolInfoTick()は失敗する可能性があります（シンボルが無効、マーケットが閉じている場合など）。必ず戻り値をチェックし、<code>false</code>の場合は<code>GetLastError()</code>でエラーを確認しましょう。</p>
<h3><span id="toc16">5. マーケットウォッチへの追加</span></h3>
<p>他のシンボルの価格を取得する場合、そのシンボルが<strong>マーケットウォッチウィンドウ</strong>に表示されている必要があります。コードから追加する場合は以下のようにします。</p>
<pre><code class="language-mql4">// シンボルをマーケットウォッチに追加
SymbolSelect("GBPJPY", true);

// 追加後にティック情報を取得
MqlTick tick;
if(SymbolInfoTick("GBPJPY", tick))
{
   Print("GBPJPY Bid=", tick.bid, " Ask=", tick.ask);
}
</code></pre>
<h3><span id="toc17">6. OnTick()の外での使用</span></h3>
<p>SymbolInfoTick()は<code>OnTick()</code>以外の関数（<code>OnTimer()</code>、<code>OnChartEvent()</code>など）からも呼び出すことができます。タイマーを使った定期的な価格チェックなどに活用できます。</p>
<pre><code class="language-mql4">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();
}
</code></pre>
<h2><span id="toc18">まとめ</span></h2>
<p>MqlTick構造体とSymbolInfoTick()関数は、MQL4プログラミングにおける<strong>価格情報取得の基本ツール</strong>です。主なメリットをまとめると以下の通りです。</p>
<ul>
<li><strong>一括取得</strong>：1回の呼び出しでBid/Ask/Last/Volume/タイムスタンプをまとめて取得</li>
<li><strong>マルチシンボル対応</strong>：任意のシンボルの価格を取得可能</li>
<li><strong>高精度タイムスタンプ</strong>：ミリ秒単位の時刻情報を取得可能</li>
<li><strong>安全性</strong>：エラーハンドリングにより信頼性の高いコードが書ける</li>
</ul>
<p>特にマルチ通貨ペアEAやスプレッド監視ツール、ティックデータの記録・分析など、本格的なトレーディングシステムの開発では欠かせない要素です。</p>
<p>投稿 <a href="https://mql-programing.com/archives/12857/%e3%80%90mql4%e3%83%aa%e3%83%95%e3%82%a1%e3%83%ac%e3%83%b3%e3%82%b9%e3%80%91mqltick%e6%a7%8b%e9%80%a0%e4%bd%93%e3%81%ae%e4%bd%bf%e3%81%84%e6%96%b9%ef%bc%81symbolinfotick%e3%81%a7bid-ask%e3%82%92/">【MQL4リファレンス】MqlTick構造体の使い方！SymbolInfoTick()でBid/Askをリアルタイム取得する方法</a> は <a href="https://mql-programing.com">自動売買を作ろう！</a> に最初に表示されました。</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>【MQL4リファレンス】MqlRates構造体の使い方！CopyRates()でローソク足データを配列取得する方法</title>
		<link>https://mql-programing.com/archives/12855/%e3%80%90mql4%e3%83%aa%e3%83%95%e3%82%a1%e3%83%ac%e3%83%b3%e3%82%b9%e3%80%91mqlrates%e6%a7%8b%e9%80%a0%e4%bd%93%e3%81%ae%e4%bd%bf%e3%81%84%e6%96%b9%ef%bc%81copyrates%e3%81%a7%e3%83%ad%e3%83%bc/</link>
		
		<dc:creator><![CDATA[朝日奈りさ]]></dc:creator>
		<pubDate>Tue, 31 Mar 2026 02:45:13 +0000</pubDate>
				<category><![CDATA[構造体]]></category>
		<category><![CDATA[【辞書】MQLリファレンス]]></category>
		<category><![CDATA[MqlRates]]></category>
		<category><![CDATA[CopyRates]]></category>
		<category><![CDATA[ローソク足]]></category>
		<category><![CDATA[MQL4]]></category>
		<guid isPermaLink="false">https://mql-programing.com/?p=12855</guid>

					<description><![CDATA[<p>MqlRates構造体とは MqlRates構造体は、MQL4でローソク足（バー）の価格データを格納するための構造体です。1本のローソク足に関する情報（始値・高値・安値・終値・出来高・時刻・スプレッド）をひとまとめに扱う [&#8230;]</p>
<p>投稿 <a href="https://mql-programing.com/archives/12855/%e3%80%90mql4%e3%83%aa%e3%83%95%e3%82%a1%e3%83%ac%e3%83%b3%e3%82%b9%e3%80%91mqlrates%e6%a7%8b%e9%80%a0%e4%bd%93%e3%81%ae%e4%bd%bf%e3%81%84%e6%96%b9%ef%bc%81copyrates%e3%81%a7%e3%83%ad%e3%83%bc/">【MQL4リファレンス】MqlRates構造体の使い方！CopyRates()でローソク足データを配列取得する方法</a> は <a href="https://mql-programing.com">自動売買を作ろう！</a> に最初に表示されました。</p>
]]></description>
										<content:encoded><![CDATA[<h2><span id="toc1">MqlRates構造体とは</span></h2>
<p>MqlRates構造体は、MQL4で<strong>ローソク足（バー）の価格データを格納するための構造体</strong>です。1本のローソク足に関する情報（始値・高値・安値・終値・出来高・時刻・スプレッド）をひとまとめに扱うことができます。</p>
<p>この構造体は、主に<strong>CopyRates()関数</strong>と組み合わせて使用し、過去のローソク足データを配列として一括取得する際に活用されます。</p>
<h2><span id="toc2">MqlRates構造体のメンバー</span></h2>
<p>MqlRates構造体は以下のメンバーで構成されています。</p>
<table>
<thead>
<tr>
<th>メンバー名</th>
<th>型</th>
<th>説明</th>
</tr>
</thead>
<tbody>
<tr>
<td>time</td>
<td>datetime</td>
<td>バーの開始時刻</td>
</tr>
<tr>
<td>open</td>
<td>double</td>
<td>始値</td>
</tr>
<tr>
<td>high</td>
<td>double</td>
<td>高値</td>
</tr>
<tr>
<td>low</td>
<td>double</td>
<td>安値</td>
</tr>
<tr>
<td>close</td>
<td>double</td>
<td>終値</td>
</tr>
<tr>
<td>tick_volume</td>
<td>long</td>
<td>ティックボリューム</td>
</tr>
<tr>
<td>spread</td>
<td>int</td>
<td>スプレッド</td>
</tr>
<tr>
<td>real_volume</td>
<td>long</td>
<td>出来高（取引所データが利用可能な場合）</td>
</tr>
</tbody>
</table>
<h3><span id="toc3">構造体の定義</span></h3>
<pre><code class="language-mql4">struct MqlRates
  {
   datetime time;         // バーの開始時刻
   double   open;         // 始値
   double   high;         // 高値
   double   low;          // 安値
   double   close;        // 終値
   long     tick_volume;  // ティックボリューム
   int      spread;       // スプレッド
   long     real_volume;  // 出来高
  };
</code></pre>
<h2><span id="toc4">CopyRates()関数の書式</span></h2>
<p>MqlRates構造体の配列にデータを格納するには、<strong>CopyRates()関数</strong>を使用します。CopyRates()には3つの呼び出し方法があります。</p>
<h3><span id="toc5">書式1：開始位置と本数を指定</span></h3>
<pre><code class="language-mql4">int CopyRates(
   string           symbol_name,   // 通貨ペア名
   ENUM_TIMEFRAMES  timeframe,     // 時間足
   int              start_pos,     // 開始位置（0が最新バー）
   int              count,         // 取得するバーの本数
   MqlRates         rates_array[]  // 格納先の配列
);
</code></pre>
<h3><span id="toc6">書式2：開始日時と本数を指定</span></h3>
<pre><code class="language-mql4">int CopyRates(
   string           symbol_name,   // 通貨ペア名
   ENUM_TIMEFRAMES  timeframe,     // 時間足
   datetime         start_time,    // 開始日時
   int              count,         // 取得するバーの本数
   MqlRates         rates_array[]  // 格納先の配列
);
</code></pre>
<h3><span id="toc7">書式3：開始日時と終了日時を指定</span></h3>
<pre><code class="language-mql4">int CopyRates(
   string           symbol_name,   // 通貨ペア名
   ENUM_TIMEFRAMES  timeframe,     // 時間足
   datetime         start_time,    // 開始日時
   datetime         stop_time,     // 終了日時
   MqlRates         rates_array[]  // 格納先の配列
);
</code></pre>
<h3><span id="toc8">引数の説明</span></h3>
<table>
<thead>
<tr>
<th>引数</th>
<th>説明</th>
</tr>
</thead>
<tbody>
<tr>
<td>symbol_name</td>
<td>通貨ペア名。NULL または Symbol() で現在のチャートの通貨ペアを指定</td>
</tr>
<tr>
<td>timeframe</td>
<td>時間足。PERIOD_CURRENT で現在の時間足を指定</td>
</tr>
<tr>
<td>start_pos</td>
<td>取得開始位置。0が最新のバー</td>
</tr>
<tr>
<td>count</td>
<td>取得するバーの本数</td>
</tr>
<tr>
<td>start_time</td>
<td>取得開始日時</td>
</tr>
<tr>
<td>stop_time</td>
<td>取得終了日時</td>
</tr>
<tr>
<td>rates_array[]</td>
<td>MqlRates型の配列（データ格納先）</td>
</tr>
</tbody>
</table>
<h3><span id="toc9">戻り値</span></h3>
<p>CopyRates()関数は、<strong>コピーに成功した要素数</strong>を返します。失敗した場合は<strong>-1</strong>を返します。エラー内容はGetLastError()で取得できます。</p>
<h2><span id="toc10">プログラム例1：直近のローソク足データを取得して表示する（基本）</span></h2>
<p>まずは最もシンプルな使い方から見ていきましょう。直近10本のローソク足データを取得し、エキスパートログに出力する例です。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| スクリプト：直近10本のローソク足データを表示                      |
//+------------------------------------------------------------------+
void OnStart()
  {
   // MqlRates型の配列を宣言
   MqlRates rates[];

   // 配列を時系列順に設定（最新のバーが[0]になる）
   ArraySetAsSeries(rates, true);

   // 現在の通貨ペア・時間足で直近10本のバーデータを取得
   int copied = CopyRates(Symbol(), PERIOD_CURRENT, 0, 10, rates);

   // 取得に失敗した場合のエラーチェック
   if(copied <= 0)
     {
      Print("CopyRates()でエラーが発生しました。エラーコード: ", GetLastError());
      return;
     }

   // 取得成功：バーデータを1本ずつ表示
   Print("===== 直近 ", copied, " 本のローソク足データ =====");

   for(int i = 0; i < copied; i++)
     {
      Print("バー[", i, "] ",
            "時刻=", TimeToString(rates[i].time, TIME_DATE | TIME_MINUTES), " | ",
            "始値=", DoubleToString(rates[i].open, Digits), " | ",
            "高値=", DoubleToString(rates[i].high, Digits), " | ",
            "安値=", DoubleToString(rates[i].low, Digits), " | ",
            "終値=", DoubleToString(rates[i].close, Digits), " | ",
            "出来高=", rates[i].tick_volume, " | ",
            "スプレッド=", rates[i].spread);
     }
  }
</code></pre>
<p>この例のポイントは、<strong>ArraySetAsSeries(rates, true)</strong>を呼び出している点です。これにより配列のインデックス[0]が最新のバーを指すようになり、通常のバー番号と同じ感覚でアクセスできます。</p>
<h2><span id="toc11">プログラム例2：ローソク足の実体とヒゲの長さを分析する</span></h2>
<p>MqlRates構造体の各メンバーを活用して、ローソク足の形状を分析するスクリプトです。陽線・陰線の判定や、実体・ヒゲの長さを計算します。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| スクリプト：ローソク足の形状分析                                  |
//+------------------------------------------------------------------+
void OnStart()
  {
   MqlRates rates[];
   ArraySetAsSeries(rates, true);

   // 直近20本のバーデータを取得
   int copied = CopyRates(Symbol(), PERIOD_CURRENT, 0, 20, rates);
   if(copied <= 0)
     {
      Print("データ取得に失敗しました。");
      return;
     }

   // 各バーのローソク足形状を分析
   for(int i = 0; i < copied; i++)
     {
      // --- ローソク足の各部分の長さを計算 ---

      // 実体の長さ（始値と終値の差の絶対値）
      double body = MathAbs(rates[i].close - rates[i].open);

      // バー全体の長さ（高値と安値の差）
      double total_range = rates[i].high - rates[i].low;

      // 上ヒゲの長さ
      double upper_shadow = rates[i].high - MathMax(rates[i].open, rates[i].close);

      // 下ヒゲの長さ
      double lower_shadow = MathMin(rates[i].open, rates[i].close) - rates[i].low;

      // 陽線か陰線かを判定
      string candle_type;
      if(rates[i].close > rates[i].open)
         candle_type = "陽線";
      else if(rates[i].close < rates[i].open)
         candle_type = "陰線";
      else
         candle_type = "同値";

      // ピンバー（上ヒゲまたは下ヒゲが実体の2倍以上）の判定
      string pattern = "";
      if(total_range > 0)
        {
         if(upper_shadow > body * 2.0 && upper_shadow > lower_shadow * 2.0)
            pattern = " [上ヒゲピンバー]";
         else if(lower_shadow > body * 2.0 && lower_shadow > upper_shadow * 2.0)
            pattern = " [下ヒゲピンバー]";
        }

      // 結果を出力
      Print("バー[", i, "] ",
            TimeToString(rates[i].time, TIME_DATE | TIME_MINUTES), " | ",
            candle_type, " | ",
            "実体=", DoubleToString(body / Point, 1), "pts | ",
            "上ヒゲ=", DoubleToString(upper_shadow / Point, 1), "pts | ",
            "下ヒゲ=", DoubleToString(lower_shadow / Point, 1), "pts",
            pattern);
     }
  }
</code></pre>
<p>ローソク足の分析では、<strong>open, high, low, close</strong>の4つの値を使って実体やヒゲの長さを算出します。ピンバーなどのパターン検出は、裁量トレードの判断支援やEAのエントリー条件に応用できます。</p>
<h2><span id="toc12">プログラム例3：異なる時間足のデータを比較するEA</span></h2>
<p>CopyRates()の強みは、<strong>現在のチャートとは異なる時間足のデータも取得できる</strong>点です。この例では、マルチタイムフレーム分析として日足と1時間足のデータを同時に取得し、トレンド判定に活用するEAを作成します。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| Expert：マルチタイムフレーム分析によるトレンド判定EA              |
//+------------------------------------------------------------------+
#property strict

//+------------------------------------------------------------------+
//| OnTick関数：ティックごとに実行される                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   // --- 日足データの取得（上位時間足のトレンド判定用） ---
   MqlRates daily_rates[];
   ArraySetAsSeries(daily_rates, true);

   int daily_copied = CopyRates(Symbol(), PERIOD_D1, 0, 5, daily_rates);
   if(daily_copied < 5)
     {
      Print("日足データの取得に失敗しました。");
      return;
     }

   // --- 1時間足データの取得（エントリータイミング用） ---
   MqlRates h1_rates[];
   ArraySetAsSeries(h1_rates, true);

   int h1_copied = CopyRates(Symbol(), PERIOD_H1, 0, 10, h1_rates);
   if(h1_copied < 10)
     {
      Print("1時間足データの取得に失敗しました。");
      return;
     }

   // --- 日足のトレンド判定 ---
   // 直近3本の日足の終値が連続して上昇しているか確認
   bool daily_uptrend = (daily_rates[1].close > daily_rates[2].close) &&
                        (daily_rates[2].close > daily_rates[3].close);

   // 直近3本の日足の終値が連続して下降しているか確認
   bool daily_downtrend = (daily_rates[1].close < daily_rates[2].close) &#038;&#038;
                          (daily_rates[2].close < daily_rates[3].close);

   // --- 1時間足のシグナル判定 ---
   // 直前の1時間足が陽線かどうか
   bool h1_bullish = (h1_rates[1].close > h1_rates[1].open);

   // 直前の1時間足が陰線かどうか
   bool h1_bearish = (h1_rates[1].close < h1_rates[1].open);

   // --- マルチタイムフレームの総合判定 ---
   string signal = "待機";

   if(daily_uptrend &#038;&#038; h1_bullish)
      signal = "買いシグナル（日足上昇トレンド＋1時間足陽線）";
   else if(daily_downtrend &#038;&#038; h1_bearish)
      signal = "売りシグナル（日足下降トレンド＋1時間足陰線）";

   // チャート左上にシグナルを表示
   Comment("=== マルチタイムフレーム分析 ===\n",
           "日足トレンド: ", daily_uptrend ? "上昇" : (daily_downtrend ? "下降" : "レンジ"), "\n",
           "日足[1] 始値=", DoubleToString(daily_rates[1].open, Digits),
           " 終値=", DoubleToString(daily_rates[1].close, Digits), "\n",
           "1時間足[1] 始値=", DoubleToString(h1_rates[1].open, Digits),
           " 終値=", DoubleToString(h1_rates[1].close, Digits), "\n",
           "判定: ", signal);
  }
</code></pre>
<p>CopyRates()の第2引数で時間足を指定することで、現在のチャートの時間足に関係なく任意の時間足のデータを取得できます。<strong>PERIOD_D1（日足）</strong>で全体のトレンドを確認し、<strong>PERIOD_H1（1時間足）</strong>でエントリータイミングを計るという実践的なマルチタイムフレーム分析を実装しています。</p>
<h2><span id="toc13">プログラム例4：日時指定でデータ取得し、期間内の最高値・最安値を求める</span></h2>
<p>CopyRates()の書式3（開始日時と終了日時を指定）を使って、特定期間のローソク足データを取得し、その期間内の最高値・最安値やボラティリティを計算する実用的なスクリプトです。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| スクリプト：特定期間の価格レンジとボラティリティ分析              |
//+------------------------------------------------------------------+
void OnStart()
  {
   // --- 分析する期間を設定 ---
   datetime start_time = D'2024.01.01 00:00'; // 開始日時
   datetime end_time   = D'2024.01.31 23:59'; // 終了日時

   // MqlRates配列を宣言（時系列設定はしない＝古い順）
   MqlRates rates[];

   // 指定期間のH1（1時間足）データを取得
   int copied = CopyRates(Symbol(), PERIOD_H1, start_time, end_time, rates);

   if(copied <= 0)
     {
      Print("指定期間のデータ取得に失敗しました。エラーコード: ", GetLastError());
      return;
     }

   Print("取得したバー数: ", copied);

   // --- 期間内の最高値・最安値を検索 ---
   double highest_price = rates[0].high;   // 最高値の初期値
   double lowest_price  = rates[0].low;    // 最安値の初期値
   datetime highest_time = rates[0].time;  // 最高値の時刻
   datetime lowest_time  = rates[0].time;  // 最安値の時刻
   long total_volume = 0;                  // 合計出来高
   double total_range = 0;                 // 各バーのレンジ合計

   for(int i = 0; i < copied; i++)
     {
      // 最高値の更新チェック
      if(rates[i].high > highest_price)
        {
         highest_price = rates[i].high;
         highest_time  = rates[i].time;
        }

      // 最安値の更新チェック
      if(rates[i].low < lowest_price)
        {
         lowest_price = rates[i].low;
         lowest_time  = rates[i].time;
        }

      // 出来高の合計
      total_volume += rates[i].tick_volume;

      // 各バーのレンジ（高値−安値）を合計
      total_range += (rates[i].high - rates[i].low);
     }

   // --- 平均ボラティリティの計算 ---
   double avg_range = total_range / copied;           // 平均レンジ（price）
   double avg_range_pts = avg_range / Point;           // 平均レンジ（ポイント）
   double avg_volume = (double)total_volume / copied;  // 平均出来高

   // --- 分析結果を出力 ---
   Print("===========================================");
   Print("  期間分析レポート: ", Symbol(), " H1");
   Print("===========================================");
   Print("分析期間: ", TimeToString(start_time, TIME_DATE), " ～ ",
         TimeToString(end_time, TIME_DATE));
   Print("バー数: ", copied);
   Print("-------------------------------------------");
   Print("期間最高値: ", DoubleToString(highest_price, Digits),
         " (", TimeToString(highest_time, TIME_DATE | TIME_MINUTES), ")");
   Print("期間最安値: ", DoubleToString(lowest_price, Digits),
         " (", TimeToString(lowest_time, TIME_DATE | TIME_MINUTES), ")");
   Print("期間レンジ: ", DoubleToString((highest_price - lowest_price) / Point, 1), " pts");
   Print("-------------------------------------------");
   Print("平均レンジ（1バーあたり）: ", DoubleToString(avg_range_pts, 1), " pts");
   Print("平均出来高（1バーあたり）: ", DoubleToString(avg_volume, 0));
   Print("合計出来高: ", total_volume);
   Print("===========================================");

   // --- 最初の5本と最後の5本のデータを確認表示 ---
   Print("--- 期間先頭のバー ---");
   int show_count = MathMin(5, copied);
   for(int i = 0; i < show_count; i++)
     {
      Print("  ", TimeToString(rates[i].time, TIME_DATE | TIME_MINUTES),
            " O=", DoubleToString(rates[i].open, Digits),
            " H=", DoubleToString(rates[i].high, Digits),
            " L=", DoubleToString(rates[i].low, Digits),
            " C=", DoubleToString(rates[i].close, Digits));
     }

   Print("--- 期間末尾のバー ---");
   for(int i = MathMax(0, copied - 5); i < copied; i++)
     {
      Print("  ", TimeToString(rates[i].time, TIME_DATE | TIME_MINUTES),
            " O=", DoubleToString(rates[i].open, Digits),
            " H=", DoubleToString(rates[i].high, Digits),
            " L=", DoubleToString(rates[i].low, Digits),
            " C=", DoubleToString(rates[i].close, Digits));
     }
  }
</code></pre>
<p>書式3では開始日時と終了日時を直接指定するため、<strong>特定の期間に限定した分析</strong>に非常に便利です。この例では期間内の最高値・最安値の検出に加え、平均ボラティリティや出来高の統計情報も算出しています。なお、日時指定で取得した場合は<strong>ArraySetAsSeries()を使わなければ古い順（時系列ではない順）</strong>で格納されます。</p>
<h2><span id="toc14">よくある使い方のポイント・注意事項</span></h2>
<h3><span id="toc15">1. ArraySetAsSeries()の設定を忘れない</span></h3>
<p>CopyRates()で取得した配列は、デフォルトでは<strong>古いデータから順番に格納</strong>されます。最新のバーを[0]としてアクセスしたい場合は、必ず<strong>CopyRates()の前に</strong>ArraySetAsSeries(rates, true)を呼び出してください。</p>
<pre><code class="language-mql4">// 正しい使い方
MqlRates rates[];
ArraySetAsSeries(rates, true);  // ← CopyRatesの前に設定
CopyRates(Symbol(), PERIOD_CURRENT, 0, 10, rates);
// rates[0]が最新バー、rates[9]が10本前のバー
</code></pre>
<h3><span id="toc16">2. 戻り値のエラーチェックは必須</span></h3>
<p>CopyRates()は、データが利用できない場合やサーバーとの接続が切れている場合に<strong>-1を返す</strong>ことがあります。戻り値を必ずチェックしてからデータにアクセスしてください。</p>
<pre><code class="language-mql4">int copied = CopyRates(Symbol(), PERIOD_CURRENT, 0, 100, rates);
if(copied <= 0)
  {
   Print("データ取得失敗: ", GetLastError());
   return; // データがない状態で処理を続行しない
  }
// copiedの値を上限として配列にアクセスする
for(int i = 0; i < copied; i++)
  {
   // rates[i] を安全に使用できる
  }
</code></pre>
<h3><span id="toc17">3. 他の通貨ペアのデータ取得時の注意</span></h3>
<p>現在のチャートとは異なる通貨ペアのデータを取得する場合、<strong>その通貨ペアのデータがダウンロード済みでないとエラーになる</strong>ことがあります。気配値表示ウィンドウに該当通貨ペアを追加し、過去のデータがダウンロードされていることを確認してください。</p>
<h3><span id="toc18">4. 配列サイズは自動調整される</span></h3>
<p>CopyRates()に渡す配列は<strong>動的配列</strong>である必要があります。関数が自動的に必要なサイズにリサイズしてくれるため、事前にArrayResize()でサイズを指定する必要はありません。ただし、<strong>静的配列（固定サイズの配列）を渡すとエラーになる</strong>場合があるので注意してください。</p>
<pre><code class="language-mql4">// OK：動的配列
MqlRates rates[];
CopyRates(Symbol(), PERIOD_CURRENT, 0, 100, rates);

// NG：静的配列は避ける
// MqlRates rates[100]; ← CopyRatesには動的配列を使うこと
</code></pre>
<h3><span id="toc19">5. iOpen()等の従来関数との使い分け</span></h3>
<p>MQL4にはiOpen()、iClose()、iHigh()、iLow()などの関数もありますが、<strong>複数のバーの情報をまとめて取得したい場合はCopyRates()の方が効率的</strong>です。1本のバー情報だけが必要な場合は従来の関数でも問題ありません。</p>
<pre><code class="language-mql4">// 1本だけ必要な場合 → 従来の関数が簡潔
double close_1 = iClose(Symbol(), PERIOD_CURRENT, 1);

// 複数本まとめて処理する場合 → CopyRates()が効率的
MqlRates rates[];
ArraySetAsSeries(rates, true);
CopyRates(Symbol(), PERIOD_CURRENT, 0, 100, rates);
// rates[0]～rates[99]まで一括でアクセス可能
</code></pre>
<h3><span id="toc20">6. インジケーターでの使用時はバッファとの混同に注意</span></h3>
<p>カスタムインジケーター内でCopyRates()を使う場合、インジケーターバッファの配列とMqlRates配列のインデックス方向が一致しているか注意してください。ArraySetAsSeries()の設定がバッファと同じ方向になっていないと、バー番号がずれてしまいます。</p>
<h2><span id="toc21">まとめ</span></h2>
<p>MqlRates構造体とCopyRates()関数は、MQL4でローソク足データを効率的に扱うための重要な機能です。以下のポイントを押さえておきましょう。</p>
<ul>
<li><strong>MqlRates構造体</strong>は1本のバーのOHLC、出来高、時刻、スプレッドを格納する</li>
<li><strong>CopyRates()</strong>は3つの書式があり、位置指定・日時指定・期間指定で柔軟にデータ取得できる</li>
<li><strong>ArraySetAsSeries()</strong>でインデックスの方向を制御できる</li>
<li>戻り値のエラーチェックを必ず行い、安全にデータにアクセスする</li>
<li>複数バーのデータをまとめて処理する場合に特に威力を発揮する</li>
</ul>
<p>投稿 <a href="https://mql-programing.com/archives/12855/%e3%80%90mql4%e3%83%aa%e3%83%95%e3%82%a1%e3%83%ac%e3%83%b3%e3%82%b9%e3%80%91mqlrates%e6%a7%8b%e9%80%a0%e4%bd%93%e3%81%ae%e4%bd%bf%e3%81%84%e6%96%b9%ef%bc%81copyrates%e3%81%a7%e3%83%ad%e3%83%bc/">【MQL4リファレンス】MqlRates構造体の使い方！CopyRates()でローソク足データを配列取得する方法</a> は <a href="https://mql-programing.com">自動売買を作ろう！</a> に最初に表示されました。</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>【MQL4リファレンス】MqlDateTime構造体の使い方！時間帯フィルターEAを作る方法</title>
		<link>https://mql-programing.com/archives/12853/%e3%80%90mql4%e3%83%aa%e3%83%95%e3%82%a1%e3%83%ac%e3%83%b3%e3%82%b9%e3%80%91mqldatetime%e6%a7%8b%e9%80%a0%e4%bd%93%e3%81%ae%e4%bd%bf%e3%81%84%e6%96%b9%ef%bc%81%e6%99%82%e9%96%93%e5%b8%af%e3%83%95/</link>
		
		<dc:creator><![CDATA[朝日奈りさ]]></dc:creator>
		<pubDate>Tue, 31 Mar 2026 02:45:10 +0000</pubDate>
				<category><![CDATA[構造体]]></category>
		<category><![CDATA[【辞書】MQLリファレンス]]></category>
		<category><![CDATA[自動売買]]></category>
		<category><![CDATA[MqlDateTime]]></category>
		<category><![CDATA[MQL4]]></category>
		<category><![CDATA[時間帯フィルター]]></category>
		<guid isPermaLink="false">https://mql-programing.com/?p=12853</guid>

					<description><![CDATA[<p>MqlDateTime構造体とは MqlDateTime構造体は、日時情報を年・月・日・時・分・秒・曜日・年内通算日といった個別の要素に分解して格納するための構造体です。MQL4では、時間は通常datetime型（197 [&#8230;]</p>
<p>投稿 <a href="https://mql-programing.com/archives/12853/%e3%80%90mql4%e3%83%aa%e3%83%95%e3%82%a1%e3%83%ac%e3%83%b3%e3%82%b9%e3%80%91mqldatetime%e6%a7%8b%e9%80%a0%e4%bd%93%e3%81%ae%e4%bd%bf%e3%81%84%e6%96%b9%ef%bc%81%e6%99%82%e9%96%93%e5%b8%af%e3%83%95/">【MQL4リファレンス】MqlDateTime構造体の使い方！時間帯フィルターEAを作る方法</a> は <a href="https://mql-programing.com">自動売買を作ろう！</a> に最初に表示されました。</p>
]]></description>
										<content:encoded><![CDATA[<h2><span id="toc1">MqlDateTime構造体とは</span></h2>
<p>MqlDateTime構造体は、日時情報を年・月・日・時・分・秒・曜日・年内通算日といった個別の要素に分解して格納するための構造体です。MQL4では、時間は通常<code>datetime</code>型（1970年1月1日からの経過秒数）で扱われますが、「何時台か？」「何曜日か？」といった判定を行うには、MqlDateTime構造体に変換するのが便利です。</p>
<p>特にEA（エキスパートアドバイザー）開発では、<strong>特定の時間帯だけエントリーする</strong>、<strong>金曜日は取引しない</strong>、<strong>月末にポジションを閉じる</strong>など、時間条件のフィルタリングに頻繁に使用されます。</p>
<h2><span id="toc2">MqlDateTime構造体の定義</span></h2>
<p>MqlDateTime構造体は以下のメンバーで構成されています。</p>
<pre><code class="language-mql4">struct MqlDateTime
{
   int year;         // 年（例: 2024）
   int mon;          // 月（1〜12）
   int day;          // 日（1〜31）
   int hour;         // 時（0〜23）
   int min;          // 分（0〜59）
   int sec;          // 秒（0〜59）
   int day_of_week;  // 曜日（0=日曜, 1=月曜, ..., 6=土曜）
   int day_of_year;  // 年内通算日（1月1日=0）
};
</code></pre>
<h3><span id="toc3">各メンバーの詳細</span></h3>
<table>
<thead>
<tr>
<th>メンバー名</th>
<th>型</th>
<th>説明</th>
<th>値の範囲</th>
</tr>
</thead>
<tbody>
<tr>
<td>year</td>
<td>int</td>
<td>西暦年</td>
<td>1970〜</td>
</tr>
<tr>
<td>mon</td>
<td>int</td>
<td>月</td>
<td>1〜12</td>
</tr>
<tr>
<td>day</td>
<td>int</td>
<td>日</td>
<td>1〜31</td>
</tr>
<tr>
<td>hour</td>
<td>int</td>
<td>時</td>
<td>0〜23</td>
</tr>
<tr>
<td>min</td>
<td>int</td>
<td>分</td>
<td>0〜59</td>
</tr>
<tr>
<td>sec</td>
<td>int</td>
<td>秒</td>
<td>0〜59</td>
</tr>
<tr>
<td>day_of_week</td>
<td>int</td>
<td>曜日（0=日, 1=月, 2=火, 3=水, 4=木, 5=金, 6=土）</td>
<td>0〜6</td>
</tr>
<tr>
<td>day_of_year</td>
<td>int</td>
<td>年の初日からの通算日数（1月1日=0）</td>
<td>0〜365</td>
</tr>
</tbody>
</table>
<h2><span id="toc4">datetime型との変換方法</span></h2>
<p><code>datetime</code>型とMqlDateTime構造体の相互変換には、以下の2つの関数を使用します。</p>
<h3><span id="toc5">TimeToStruct() ─ datetime → MqlDateTime</span></h3>
<pre><code class="language-mql4">// datetime型をMqlDateTime構造体に変換
bool TimeToStruct(
   datetime      dt,       // 変換元のdatetime値
   MqlDateTime&  dt_struct  // 変換先の構造体（参照渡し）
);
</code></pre>
<p>戻り値は<code>bool</code>型で、変換に成功すれば<code>true</code>、失敗すれば<code>false</code>を返します。</p>
<h3><span id="toc6">StructToTime() ─ MqlDateTime → datetime</span></h3>
<pre><code class="language-mql4">// MqlDateTime構造体をdatetime型に変換
datetime StructToTime(
   MqlDateTime&  dt_struct  // 変換元の構造体（参照渡し）
);
</code></pre>
<p>戻り値は変換された<code>datetime</code>型の値です。</p>
<h2><span id="toc7">プログラム例1：基本的な使い方 ─ 現在時刻の各要素を表示する</span></h2>
<p>まずは最もシンプルな使い方です。現在のサーバー時刻をMqlDateTime構造体に変換し、各メンバーの値をチャートに表示します。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| プログラム例1：現在時刻の分解表示                                    |
//+------------------------------------------------------------------+
#property indicator_chart_window

//+------------------------------------------------------------------+
//| 曜日番号を日本語文字列に変換する関数                                  |
//+------------------------------------------------------------------+
string DayOfWeekToString(int dow)
{
   string days[] = {"日曜日","月曜日","火曜日","水曜日","木曜日","金曜日","土曜日"};
   if(dow >= 0 && dow <= 6)
      return days[dow];
   return "不明";
}

//+------------------------------------------------------------------+
//| カスタムインジケーターの初期化関数                                    |
//+------------------------------------------------------------------+
int OnInit()
{
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| メインの計算関数（ティック毎に呼ばれる）                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &#038;time[],
                const double &#038;open[],
                const double &#038;high[],
                const double &#038;low[],
                const double &#038;close[],
                const long &#038;tick_volume[],
                const long &#038;volume[],
                const int &#038;spread[])
{
   // MqlDateTime構造体の変数を宣言
   MqlDateTime dt;
   
   // 現在のサーバー時刻を取得して構造体に変換
   datetime currentTime = TimeCurrent();
   TimeToStruct(currentTime, dt);
   
   // 各メンバーの値を文字列にまとめる
   string info = "";
   info += "━━━ 現在のサーバー時刻 ━━━\n";
   info += "datetime値: " + TimeToString(currentTime, TIME_DATE|TIME_SECONDS) + "\n";
   info += "年: " + IntegerToString(dt.year) + "\n";
   info += "月: " + IntegerToString(dt.mon) + "\n";
   info += "日: " + IntegerToString(dt.day) + "\n";
   info += "時: " + IntegerToString(dt.hour) + "\n";
   info += "分: " + IntegerToString(dt.min) + "\n";
   info += "秒: " + IntegerToString(dt.sec) + "\n";
   info += "曜日: " + DayOfWeekToString(dt.day_of_week) + " (" + IntegerToString(dt.day_of_week) + ")\n";
   info += "年内通算日: " + IntegerToString(dt.day_of_year) + "\n";
   
   // チャート左上に表示
   Comment(info);
   
   return(rates_total);
}

//+------------------------------------------------------------------+
//| インジケーター削除時にコメントをクリア                                |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   Comment("");
}
</code></pre>
<p>このプログラムをインジケーターとしてチャートに適用すると、サーバー時刻の各要素がリアルタイムで表示されます。<code>TimeToStruct()</code>に<code>datetime</code>値と構造体変数を渡すだけで、自動的に年・月・日・時・分・秒・曜日が分解されることが分かります。</p>
<h2><span id="toc8">プログラム例2：時間帯フィルターの実装 ─ 東京・ロンドン・NY時間を判定する</span></h2>
<p>実際のEA開発で最も多い用途が「特定の時間帯だけ取引する」というフィルター処理です。以下の例では、東京時間・ロンドン時間・ニューヨーク時間を判定する関数を作成します。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| プログラム例2：主要市場の時間帯判定                                   |
//| ※サーバー時刻がGMT+2（冬）/ GMT+3（夏）前提の例                     |
//+------------------------------------------------------------------+

// 取引を許可する時間帯の設定（サーバー時刻基準）
input int TokyoStartHour    = 2;   // 東京時間 開始（サーバー時刻）
input int TokyoEndHour      = 9;   // 東京時間 終了（サーバー時刻）
input int LondonStartHour   = 10;  // ロンドン時間 開始
input int LondonEndHour     = 18;  // ロンドン時間 終了
input int NewYorkStartHour  = 15;  // ニューヨーク時間 開始
input int NewYorkEndHour    = 23;  // ニューヨーク時間 終了

//+------------------------------------------------------------------+
//| 指定した時間帯内かどうかを判定する汎用関数                            |
//| startHour〜endHourの範囲内であればtrueを返す                        |
//+------------------------------------------------------------------+
bool IsWithinTimeRange(int startHour, int endHour)
{
   MqlDateTime dt;
   TimeToStruct(TimeCurrent(), dt);
   
   // 日付をまたがない通常のケース（例: 10時〜18時）
   if(startHour < endHour)
   {
      return (dt.hour >= startHour && dt.hour < endHour);
   }
   // 日付をまたぐケース（例: 22時〜6時）
   else if(startHour > endHour)
   {
      return (dt.hour >= startHour || dt.hour < endHour);
   }
   
   // startHour == endHour の場合は常にfalse
   return false;
}

//+------------------------------------------------------------------+
//| 現在が東京時間かどうか                                              |
//+------------------------------------------------------------------+
bool IsTokyoSession()
{
   return IsWithinTimeRange(TokyoStartHour, TokyoEndHour);
}

//+------------------------------------------------------------------+
//| 現在がロンドン時間かどうか                                           |
//+------------------------------------------------------------------+
bool IsLondonSession()
{
   return IsWithinTimeRange(LondonStartHour, LondonEndHour);
}

//+------------------------------------------------------------------+
//| 現在がニューヨーク時間かどうか                                       |
//+------------------------------------------------------------------+
bool IsNewYorkSession()
{
   return IsWithinTimeRange(NewYorkStartHour, NewYorkEndHour);
}

//+------------------------------------------------------------------+
//| EAのメインティック処理                                              |
//+------------------------------------------------------------------+
void OnTick()
{
   // 各セッションの状態を取得
   bool tokyo   = IsTokyoSession();
   bool london  = IsLondonSession();
   bool newyork = IsNewYorkSession();
   
   // セッション情報を表示
   string sessionInfo = "【セッション状態】\n";
   sessionInfo += "東京:      " + (tokyo   ? "●アクティブ" : "○非アクティブ") + "\n";
   sessionInfo += "ロンドン:   " + (london  ? "●アクティブ" : "○非アクティブ") + "\n";
   sessionInfo += "ニューヨーク: " + (newyork ? "●アクティブ" : "○非アクティブ") + "\n";
   Comment(sessionInfo);
   
   // 例：ロンドン時間のみエントリーを許可
   if(!london)
   {
      // ロンドン時間外なので何もしない
      return;
   }
   
   // ここにロンドン時間中の取引ロジックを記述
   // ...
}
</code></pre>
<p><code>IsWithinTimeRange()</code>関数は、日付をまたぐ時間帯（例：22時〜翌6時）にも対応しています。<code>MqlDateTime</code>の<code>hour</code>メンバーを利用して、サーバー時刻が指定範囲内かどうかを効率的に判定しています。</p>
<h2><span id="toc9">プログラム例3：曜日フィルターと月末決済の実装</span></h2>
<p>次に、<code>day_of_week</code>と<code>day</code>メンバーを活用した、曜日フィルターと月末自動決済のサンプルです。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| プログラム例3：曜日フィルターと月末自動決済                            |
//+------------------------------------------------------------------+

input bool TradeOnMonday    = true;   // 月曜日に取引するか
input bool TradeOnTuesday   = true;   // 火曜日に取引するか
input bool TradeOnWednesday = true;   // 水曜日に取引するか
input bool TradeOnThursday  = true;   // 木曜日に取引するか
input bool TradeOnFriday    = false;  // 金曜日に取引するか（デフォルトOFF）
input int  FridayCloseHour  = 20;    // 金曜日の強制決済時刻

//+------------------------------------------------------------------+
//| 今日が取引許可曜日かどうかを判定                                      |
//+------------------------------------------------------------------+
bool IsTradingDay()
{
   MqlDateTime dt;
   TimeToStruct(TimeCurrent(), dt);
   
   // day_of_week: 0=日, 1=月, 2=火, 3=水, 4=木, 5=金, 6=土
   switch(dt.day_of_week)
   {
      case 1: return TradeOnMonday;
      case 2: return TradeOnTuesday;
      case 3: return TradeOnWednesday;
      case 4: return TradeOnThursday;
      case 5: return TradeOnFriday;
      default: return false;  // 土日は常に取引しない
   }
}

//+------------------------------------------------------------------+
//| 金曜日の指定時刻以降かどうかを判定（週末リスク回避用）                  |
//+------------------------------------------------------------------+
bool IsFridayCloseTime()
{
   MqlDateTime dt;
   TimeToStruct(TimeCurrent(), dt);
   
   // 金曜日（day_of_week == 5）かつ指定時刻以降
   if(dt.day_of_week == 5 && dt.hour >= FridayCloseHour)
      return true;
   
   return false;
}

//+------------------------------------------------------------------+
//| 今月の最終営業日かどうかを判定                                       |
//+------------------------------------------------------------------+
bool IsLastBusinessDayOfMonth()
{
   MqlDateTime dt;
   TimeToStruct(TimeCurrent(), dt);
   
   // 各月の最終日を定義（配列インデックス0は未使用）
   int daysInMonth[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
   
   // うるう年の判定（2月の日数を修正）
   if(dt.mon == 2)
   {
      if((dt.year % 4 == 0 && dt.year % 100 != 0) || (dt.year % 400 == 0))
         daysInMonth[2] = 29;
   }
   
   int lastDay = daysInMonth[dt.mon];
   
   // 月末の最終営業日を逆算（土日を除く）
   // lastDayの曜日を計算するため、一時的にdatetimeを生成
   MqlDateTime tempDt;
   tempDt.year = dt.year;
   tempDt.mon  = dt.mon;
   tempDt.day  = lastDay;
   tempDt.hour = 12;
   tempDt.min  = 0;
   tempDt.sec  = 0;
   
   datetime lastDayTime = StructToTime(tempDt);
   TimeToStruct(lastDayTime, tempDt);  // 曜日を再計算
   
   // 最終日が土曜なら金曜（-1日）、日曜なら金曜（-2日）
   int lastBusinessDay = lastDay;
   if(tempDt.day_of_week == 6)       // 土曜
      lastBusinessDay = lastDay - 1;
   else if(tempDt.day_of_week == 0)  // 日曜
      lastBusinessDay = lastDay - 2;
   
   return (dt.day == lastBusinessDay);
}

//+------------------------------------------------------------------+
//| 全ポジションを決済する関数                                           |
//+------------------------------------------------------------------+
void CloseAllPositions()
{
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         if(OrderSymbol() == Symbol() && OrderMagicNumber() == 12345)
         {
            if(OrderType() == OP_BUY)
               OrderClose(OrderTicket(), OrderLots(), Bid, 3, clrRed);
            else if(OrderType() == OP_SELL)
               OrderClose(OrderTicket(), OrderLots(), Ask, 3, clrBlue);
         }
      }
   }
}

//+------------------------------------------------------------------+
//| EAのメインティック処理                                              |
//+------------------------------------------------------------------+
void OnTick()
{
   // 金曜日の指定時刻を超えたら全ポジション決済
   if(IsFridayCloseTime())
   {
      CloseAllPositions();
      Comment("金曜日 " + IntegerToString(FridayCloseHour) + "時以降のため全決済済み");
      return;
   }
   
   // 月末最終営業日も全ポジション決済
   if(IsLastBusinessDayOfMonth())
   {
      CloseAllPositions();
      Comment("月末最終営業日のため全決済済み");
      return;
   }
   
   // 取引許可曜日でなければ新規エントリーしない
   if(!IsTradingDay())
   {
      Comment("本日は取引対象外の曜日です");
      return;
   }
   
   // ここに通常の取引ロジックを記述
   Comment("取引許可中");
   // ...
}
</code></pre>
<p>この例では3つの重要なテクニックを示しています。<code>day_of_week</code>を<code>switch</code>文で分岐させる曜日フィルター、<code>hour</code>と<code>day_of_week</code>を組み合わせた金曜決済、そして<code>StructToTime()</code>を使って任意の日付のdatetime値を生成し曜日を逆算する月末判定です。</p>
<h2><span id="toc10">プログラム例4：複合時間フィルターEA ─ 時間帯・曜日・重要イベント前を統合管理</span></h2>
<p>最後に、これまでの要素を統合した実践的な複合フィルターEAのテンプレートです。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| プログラム例4：複合時間フィルターEA                                   |
//| 時間帯・曜日・特定日を統合的にフィルタリングする                       |
//+------------------------------------------------------------------+
#property strict

// === 時間帯フィルター設定 ===
input string   _TimeFilter_     = "=== 時間帯フィルター ===";   // セパレータ
input int      TradeStartHour   = 10;    // エントリー開始時刻（時）
input int      TradeStartMin    = 30;    // エントリー開始時刻（分）
input int      TradeEndHour     = 20;    // エントリー終了時刻（時）
input int      TradeEndMin      = 0;     // エントリー終了時刻（分）

// === 曜日フィルター設定 ===
input string   _DayFilter_      = "=== 曜日フィルター ===";     // セパレータ
input bool     AllowMonday      = true;  // 月曜の取引を許可
input bool     AllowTuesday     = true;  // 火曜の取引を許可
input bool     AllowWednesday   = true;  // 水曜の取引を許可
input bool     AllowThursday    = true;  // 木曜の取引を許可
input bool     AllowFriday      = true;  // 金曜の取引を許可

// === 特殊ルール設定 ===
input string   _SpecialRules_   = "=== 特殊ルール ===";         // セパレータ
input bool     AvoidMonthEnd    = true;  // 月末（最終3日間）は取引しない
input bool     AvoidYearEnd     = true;  // 年末年始（12/25〜1/3）は取引しない
input int      MagicNumber      = 20240101; // マジックナンバー

//+------------------------------------------------------------------+
//| フィルター結果を格納する構造体                                       |
//+------------------------------------------------------------------+
struct FilterResult
{
   bool  isAllowed;     // 取引許可されているか
   string reason;       // 不許可の理由（許可の場合は空文字列）
};

//+------------------------------------------------------------------+
//| 時刻を「時×60＋分」の数値に変換する補助関数                           |
//+------------------------------------------------------------------+
int TimeToMinutes(int hour, int minute)
{
   return hour * 60 + minute;
}

//+------------------------------------------------------------------+
//| 時間帯フィルターのチェック                                           |
//+------------------------------------------------------------------+
bool CheckTimeFilter(MqlDateTime &dt, string &reason)
{
   int currentMinutes = TimeToMinutes(dt.hour, dt.min);
   int startMinutes   = TimeToMinutes(TradeStartHour, TradeStartMin);
   int endMinutes     = TimeToMinutes(TradeEndHour, TradeEndMin);
   
   // 日付をまたがないケース
   if(startMinutes < endMinutes)
   {
      if(currentMinutes < startMinutes || currentMinutes >= endMinutes)
      {
         reason = StringFormat("時間外（許可: %02d:%02d〜%02d:%02d）",
                               TradeStartHour, TradeStartMin,
                               TradeEndHour, TradeEndMin);
         return false;
      }
   }
   // 日付をまたぐケース
   else if(startMinutes > endMinutes)
   {
      if(currentMinutes < startMinutes &#038;&#038; currentMinutes >= endMinutes)
      {
         reason = StringFormat("時間外（許可: %02d:%02d〜%02d:%02d）",
                               TradeStartHour, TradeStartMin,
                               TradeEndHour, TradeEndMin);
         return false;
      }
   }
   
   return true;
}

//+------------------------------------------------------------------+
//| 曜日フィルターのチェック                                             |
//+------------------------------------------------------------------+
bool CheckDayFilter(MqlDateTime &dt, string &reason)
{
   // 曜日ごとの許可フラグを配列にまとめる
   bool dayAllowed[7];
   dayAllowed[0] = false;          // 日曜（常に不許可）
   dayAllowed[1] = AllowMonday;
   dayAllowed[2] = AllowTuesday;
   dayAllowed[3] = AllowWednesday;
   dayAllowed[4] = AllowThursday;
   dayAllowed[5] = AllowFriday;
   dayAllowed[6] = false;          // 土曜（常に不許可）
   
   string dayNames[] = {"日","月","火","水","木","金","土"};
   
   if(!dayAllowed[dt.day_of_week])
   {
      reason = dayNames[dt.day_of_week] + "曜日は取引不許可";
      return false;
   }
   
   return true;
}

//+------------------------------------------------------------------+
//| 特殊日フィルターのチェック（月末・年末年始）                           |
//+------------------------------------------------------------------+
bool CheckSpecialDateFilter(MqlDateTime &dt, string &reason)
{
   // 月末チェック（最終3日間）
   if(AvoidMonthEnd)
   {
      int daysInMonth[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
      
      // うるう年判定
      if(dt.mon == 2 && ((dt.year % 4 == 0 && dt.year % 100 != 0) || (dt.year % 400 == 0)))
         daysInMonth[2] = 29;
      
      if(dt.day > daysInMonth[dt.mon] - 3)
      {
         reason = "月末期間（最終3日間）のため取引停止";
         return false;
      }
   }
   
   // 年末年始チェック（12/25〜1/3）
   if(AvoidYearEnd)
   {
      if((dt.mon == 12 && dt.day >= 25) || (dt.mon == 1 && dt.day <= 3))
      {
         reason = "年末年始期間のため取引停止";
         return false;
      }
   }
   
   return true;
}

//+------------------------------------------------------------------+
//| 全フィルターを統合チェック                                           |
//+------------------------------------------------------------------+
FilterResult CheckAllFilters()
{
   FilterResult result;
   result.isAllowed = true;
   result.reason    = "";
   
   MqlDateTime dt;
   TimeToStruct(TimeCurrent(), dt);
   
   string reason = "";
   
   // 曜日チェック（最優先）
   if(!CheckDayFilter(dt, reason))
   {
      result.isAllowed = false;
      result.reason    = reason;
      return result;
   }
   
   // 特殊日チェック
   if(!CheckSpecialDateFilter(dt, reason))
   {
      result.isAllowed = false;
      result.reason    = reason;
      return result;
   }
   
   // 時間帯チェック
   if(!CheckTimeFilter(dt, reason))
   {
      result.isAllowed = false;
      result.reason    = reason;
      return result;
   }
   
   return result;
}

//+------------------------------------------------------------------+
//| EAの初期化関数                                                     |
//+------------------------------------------------------------------+
int OnInit()
{
   Print("複合時間フィルターEA 初期化完了");
   Print("取引時間帯: ", StringFormat("%02d:%02d〜%02d:%02d",
         TradeStartHour, TradeStartMin, TradeEndHour, TradeEndMin));
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| EAのメインティック処理                                              |
//+------------------------------------------------------------------+
void OnTick()
{
   // 全フィルターの統合チェック
   FilterResult filter = CheckAllFilters();
   
   // 現在時刻情報を取得して表示用に整形
   MqlDateTime dt;
   TimeToStruct(TimeCurrent(), dt);
   string dayNames[] = {"日","月","火","水","木","金","土"};
   
   string display = "";
   display += "━━━ 複合時間フィルターEA ━━━\n";
   display += StringFormat("サーバー時刻: %04d/%02d/%02d (%s) %02d:%02d:%02d\n",
                           dt.year, dt.mon, dt.day,
                           dayNames[dt.day_of_week],
                           dt.hour, dt.min, dt.sec);
   
   if(filter.isAllowed)
   {
      display += "状態: &#x2714; 取引許可中\n";
      
      // ここに取引ロジックを記述
      // 例: シグナル判定、エントリー処理など
   }
   else
   {
      display += "状態: &#x2716; 取引停止中\n";
      display += "理由: " + filter.reason + "\n";
   }
   
   Comment(display);
}

//+------------------------------------------------------------------+
//| EA削除時の処理                                                     |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   Comment("");
   Print("複合時間フィルターEA 削除完了");
}
</code></pre>
<p>このプログラム例4では、フィルター結果を専用の<code>FilterResult</code>構造体に格納することで、取引の可否だけでなく不許可の理由も管理できるようにしています。各フィルター関数は独立しているため、必要に応じて個別に有効・無効を切り替えたり、新しいフィルター条件を追加したりすることが容易です。</p>
<h2><span id="toc11">MqlDateTime構造体を使う際の注意点</span></h2>
<h3><span id="toc12">サーバー時刻とローカル時刻の違い</span></h3>
<p><code>TimeCurrent()</code>はブローカーのサーバー時刻を返します。日本時間とは異なる場合がほとんどです。時間帯フィルターを設定する際は、必ずサーバー時刻のタイムゾーンを確認してください。ローカル時刻が必要な場合は<code>TimeLocal()</code>を使用します。</p>
<h3><span id="toc13">day_of_yearの開始値</span></h3>
<p><code>day_of_year</code>は1月1日が<strong>0</strong>から始まります。1月2日は1、12月31日は364（うるう年は365）となります。直感的に「1月1日=1日目」と考えがちなので注意が必要です。</p>
<h3><span id="toc14">StructToTime()使用時の曜日自動計算</span></h3>
<p><code>StructToTime()</code>で<code>datetime</code>に変換する際、<code>day_of_week</code>と<code>day_of_year</code>の値は無視されます。逆に、<code>TimeToStruct()</code>で変換した場合は自動的に正しい値が設定されます。プログラム例3の月末判定のように、<code>StructToTime()</code>→<code>TimeToStruct()</code>と変換することで、任意の日付の曜日を取得できます。</p>
<h2><span id="toc15">まとめ</span></h2>
<p>MqlDateTime構造体は、<code>datetime</code>型の時刻データを人間が理解しやすい個別の要素に分解するための構造体です。<code>TimeToStruct()</code>と<code>StructToTime()</code>の2つの変換関数を使うことで、時間帯フィルター・曜日フィルター・月末判定など、EA開発で頻出する時間条件のロジックを効率的に実装できます。</p>
<p>投稿 <a href="https://mql-programing.com/archives/12853/%e3%80%90mql4%e3%83%aa%e3%83%95%e3%82%a1%e3%83%ac%e3%83%b3%e3%82%b9%e3%80%91mqldatetime%e6%a7%8b%e9%80%a0%e4%bd%93%e3%81%ae%e4%bd%bf%e3%81%84%e6%96%b9%ef%bc%81%e6%99%82%e9%96%93%e5%b8%af%e3%83%95/">【MQL4リファレンス】MqlDateTime構造体の使い方！時間帯フィルターEAを作る方法</a> は <a href="https://mql-programing.com">自動売買を作ろう！</a> に最初に表示されました。</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
