<?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/%E3%83%AA%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9/feed/" rel="self" type="application/rss+xml" />
	<link>https://mql-programing.com/archives/tag/リファレンス/</link>
	<description>MQLプログラミング学習サイト</description>
	<lastBuildDate>Wed, 08 Apr 2026 11:29:52 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</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関数】iAlligator関数の使い方！アリゲーターでトレンド判定EAを作ろう</title>
		<link>https://mql-programing.com/archives/13104/%e3%80%90mql4%e9%96%a2%e6%95%b0%e3%80%91ialligator%e9%96%a2%e6%95%b0%e3%81%ae%e4%bd%bf%e3%81%84%e6%96%b9%ef%bc%81%e3%82%a2%e3%83%aa%e3%82%b2%e3%83%bc%e3%82%bf%e3%83%bc%e3%81%a7%e3%83%88%e3%83%ac/</link>
		
		<dc:creator><![CDATA[朝日奈りさ]]></dc:creator>
		<pubDate>Sat, 02 May 2026 01:00:00 +0000</pubDate>
				<category><![CDATA[関数]]></category>
		<category><![CDATA[【辞書】MQLリファレンス]]></category>
		<category><![CDATA[MQL4]]></category>
		<category><![CDATA[インジケーター]]></category>
		<category><![CDATA[アリゲーター]]></category>
		<category><![CDATA[リファレンス]]></category>
		<category><![CDATA[iAlligator]]></category>
		<guid isPermaLink="false">https://mql-programing.com/?p=13104</guid>

					<description><![CDATA[<p>iAlligator関数を使えば、アリゲーターインジケーターの3本のライン（顎・歯・唇）の値をMQL4プログラムから取得できます。アリゲーターはトレンドの有無と方向を視覚的に判断できるインジケーターで、EAのエントリーフ [&#8230;]</p>
<p>投稿 <a href="https://mql-programing.com/archives/13104/%e3%80%90mql4%e9%96%a2%e6%95%b0%e3%80%91ialligator%e9%96%a2%e6%95%b0%e3%81%ae%e4%bd%bf%e3%81%84%e6%96%b9%ef%bc%81%e3%82%a2%e3%83%aa%e3%82%b2%e3%83%bc%e3%82%bf%e3%83%bc%e3%81%a7%e3%83%88%e3%83%ac/">【MQL4関数】iAlligator関数の使い方！アリゲーターでトレンド判定EAを作ろう</a> は <a href="https://mql-programing.com">自動売買を作ろう！</a> に最初に表示されました。</p>
]]></description>
										<content:encoded><![CDATA[<p><strong>iAlligator関数を使えば、アリゲーターインジケーターの3本のライン（顎・歯・唇）の値をMQL4プログラムから取得できます。</strong>アリゲーターはトレンドの有無と方向を視覚的に判断できるインジケーターで、EAのエントリーフィルターとして非常に人気があります。この記事では iAlligator の構文から実践的なEAサンプルまでPREP法で解説します。</p>
<h2><span id="toc1">iAlligator関数とは？なぜトレンド判定に使われるのか</span></h2>
<p>アリゲーター（Alligator）はビル・ウィリアムズが考案したインジケーターです。移動平均線を時間軸でずらした3本のラインで構成されており、ラインが開くとトレンド発生、収束するとレンジ相場という状態を示します。</p>
<p>MQL4では <code>iAlligator()</code> 関数でこの3本のライン値をリアルタイムに取得できるため、「トレンドが発生しているときだけエントリーする」というフィルターロジックをEAに組み込めます。</p>
<figure class="wp-block-table">
<table>
<thead>
<tr>
<th>ライン名</th>
<th>別名</th>
<th>設定（デフォルト）</th>
</tr>
</thead>
<tbody>
<tr>
<td>Jaw（顎）</td>
<td>青ライン</td>
<td>13期間SMMA・8バーシフト</td>
</tr>
<tr>
<td>Teeth（歯）</td>
<td>赤ライン</td>
<td>8期間SMMA・5バーシフト</td>
</tr>
<tr>
<td>Lips（唇）</td>
<td>緑ライン</td>
<td>5期間SMMA・3バーシフト</td>
</tr>
</tbody>
</table>
</figure>
<h2><span id="toc2">iAlligator() の構文と引数</span></h2>
<pre><code class="language-mql4">double iAlligator(
    string symbol,          // 通貨ペア（NULLで現在のチャート）
    int    timeframe,       // 時間足（0で現在の時間足）
    int    jaw_period,      // 顎の期間（デフォルト13）
    int    jaw_shift,       // 顎のシフト（デフォルト8）
    int    teeth_period,    // 歯の期間（デフォルト8）
    int    teeth_shift,     // 歯のシフト（デフォルト5）
    int    lips_period,     // 唇の期間（デフォルト5）
    int    lips_shift,      // 唇のシフト（デフォルト3）
    int    ma_method,       // 移動平均の種類（MODE_SMMA推奨）
    int    applied_price,   // 適用価格（PRICE_MEDIAN推奨）
    int    mode,            // 取得するライン
    int    shift            // 何本前のバーか（0=現在）
);</code></pre>
<h3><span id="toc3">mode の値</span></h3>
<figure class="wp-block-table">
<table>
<thead>
<tr>
<th>定数</th>
<th>値</th>
<th>説明</th>
</tr>
</thead>
<tbody>
<tr>
<td>MODE_GATORJAW</td>
<td>1</td>
<td>顎（Jaw）ラインの値</td>
</tr>
<tr>
<td>MODE_GATORTEETH</td>
<td>2</td>
<td>歯（Teeth）ラインの値</td>
</tr>
<tr>
<td>MODE_GATORLIPS</td>
<td>3</td>
<td>唇（Lips）ラインの値</td>
</tr>
</tbody>
</table>
</figure>
<h2><span id="toc4">基本的な使い方：3本のライン値を取得する</span></h2>
<p>iAlligator関数でアリゲーターの各ラインを取得する最もシンプルなコードです。</p>
<pre><code class="language-mql4">// アリゲーターのデフォルト設定で3本のライン値を取得
double jaw   = iAlligator(NULL, 0, 13, 8, 8, 5, 5, 3, MODE_SMMA, PRICE_MEDIAN, MODE_GATORJAW,   1);
double teeth = iAlligator(NULL, 0, 13, 8, 8, 5, 5, 3, MODE_SMMA, PRICE_MEDIAN, MODE_GATORTEETH, 1);
double lips  = iAlligator(NULL, 0, 13, 8, 8, 5, 5, 3, MODE_SMMA, PRICE_MEDIAN, MODE_GATORLIPS,  1);

Print("顎(Jaw): ",   jaw);
Print("歯(Teeth): ", teeth);
Print("唇(Lips): ",  lips);</code></pre>
<h2><span id="toc5">実践例1：アリゲーターでトレンドフィルターを作る</span></h2>
<p>アリゲーターの3本のラインの並び順でトレンドを判定します。唇 &gt; 歯 &gt; 顎なら上昇トレンド、顎 &gt; 歯 &gt; 唇なら下降トレンドと判断できます。</p>
<pre><code class="language-mql4">// トレンド判定関数
// 戻り値: 1=上昇トレンド / -1=下降トレンド / 0=レンジ（睡眠中）
int GetAlligatorTrend()
{
    double jaw   = iAlligator(NULL, 0, 13, 8, 8, 5, 5, 3, MODE_SMMA, PRICE_MEDIAN, MODE_GATORJAW,   1);
    double teeth = iAlligator(NULL, 0, 13, 8, 8, 5, 5, 3, MODE_SMMA, PRICE_MEDIAN, MODE_GATORTEETH, 1);
    double lips  = iAlligator(NULL, 0, 13, 8, 8, 5, 5, 3, MODE_SMMA, PRICE_MEDIAN, MODE_GATORLIPS,  1);

    // 唇 > 歯 > 顎 → 上昇トレンド
    if(lips > teeth && teeth > jaw)
        return 1;

    // 顎 > 歯 > 唇 → 下降トレンド
    if(jaw > teeth && teeth > lips)
        return -1;

    // それ以外 → レンジ（アリゲーターが眠っている状態）
    return 0;
}

void OnTick()
{
    int trend = GetAlligatorTrend();
    if(trend == 1)
        Comment("アリゲーター: 上昇トレンド");
    else if(trend == -1)
        Comment("アリゲーター: 下降トレンド");
    else
        Comment("アリゲーター: レンジ（様子見）");
}</code></pre>
<h2><span id="toc6">実践例2：アリゲーターをフィルターにしたEA</span></h2>
<p>iAlligator関数のトレンド判定と iMA（移動平均）のゴールデンクロスを組み合わせた実践的なEAです。アリゲーターがトレンドを示しているときだけエントリーするため、ダマシを減らせます。</p>
<pre><code class="language-mql4">extern int    MagicNumber = 20001;
extern double Lots        = 0.1;
extern int    FastMA      = 5;
extern int    SlowMA      = 20;

void OnTick()
{
    // ラインが3本とも開いているか（絶対値で判定）
    double jaw   = iAlligator(NULL, 0, 13, 8, 8, 5, 5, 3, MODE_SMMA, PRICE_MEDIAN, MODE_GATORJAW,   1);
    double teeth = iAlligator(NULL, 0, 13, 8, 8, 5, 5, 3, MODE_SMMA, PRICE_MEDIAN, MODE_GATORTEETH, 1);
    double lips  = iAlligator(NULL, 0, 13, 8, 8, 5, 5, 3, MODE_SMMA, PRICE_MEDIAN, MODE_GATORLIPS,  1);

    bool upTrend   = (lips > teeth && teeth > jaw);
    bool downTrend = (jaw  > teeth && teeth > lips);

    // MAクロス判定
    double fastMA_cur  = iMA(NULL, 0, FastMA, 0, MODE_EMA, PRICE_CLOSE, 1);
    double fastMA_prev = iMA(NULL, 0, FastMA, 0, MODE_EMA, PRICE_CLOSE, 2);
    double slowMA_cur  = iMA(NULL, 0, SlowMA, 0, MODE_EMA, PRICE_CLOSE, 1);
    double slowMA_prev = iMA(NULL, 0, SlowMA, 0, MODE_EMA, PRICE_CLOSE, 2);

    bool goldenCross = (fastMA_prev < slowMA_prev &#038;&#038; fastMA_cur > slowMA_cur);
    bool deadCross   = (fastMA_prev > slowMA_prev && fastMA_cur < slowMA_cur);

    if(OrdersTotal() > 0) return;

    // アリゲーターが上昇トレンド ＋ ゴールデンクロス → 買い
    if(upTrend && goldenCross)
    {
        OrderSend(Symbol(), OP_BUY, Lots, Ask, 20, 0, 0,
                  "Alligator+MA", MagicNumber, 0, clrBlue);
    }

    // アリゲーターが下降トレンド ＋ デッドクロス → 売り
    if(downTrend && deadCross)
    {
        OrderSend(Symbol(), OP_SELL, Lots, Bid, 20, 0, 0,
                  "Alligator+MA", MagicNumber, 0, clrRed);
    }
}</code></pre>
<h2><span id="toc7">iAlligator使用時の注意点</span></h2>
<ul>
<li><strong>シフトに注意：</strong>アリゲーターは各ラインが未来方向にシフトされるため、チャート上の表示位置とバーインデックスがずれます。shift=1（1本前）を使って確定済みのバーを参照するのが基本です。</li>
<li><strong>レンジ相場では使わない：</strong>3本のラインが絡み合っている（アリゲーターが眠っている）状態でのエントリーはダマシが増えます。必ず上記のトレンド判定と組み合わせてください。</li>
<li><strong>applied_price はPRICE_MEDIAN：</strong>ビル・ウィリアムズのオリジナル設定は中値（(High+Low)/2）です。PRICE_CLOSEで計算するとチャートの表示と一致しないことがあります。</li>
</ul>
<h2><span id="toc8">まとめ</span></h2>
<p><strong>iAlligator関数は、アリゲーターインジケーターの顎・歯・唇の3本のライン値をMQL4から直接取得できる関数です。</strong>ラインの並び順を見るだけでトレンドの有無・方向を判定できるため、EA開発のフィルターとして非常に使いやすいのが特徴です。</p>
<p>レンジ相場（アリゲーターが眠っている状態）ではエントリーを避け、トレンドが明確に出たときだけ他のシグナルと組み合わせるのがiAlligator活用のポイントです。</p>
<p>投稿 <a href="https://mql-programing.com/archives/13104/%e3%80%90mql4%e9%96%a2%e6%95%b0%e3%80%91ialligator%e9%96%a2%e6%95%b0%e3%81%ae%e4%bd%bf%e3%81%84%e6%96%b9%ef%bc%81%e3%82%a2%e3%83%aa%e3%82%b2%e3%83%bc%e3%82%bf%e3%83%bc%e3%81%a7%e3%83%88%e3%83%ac/">【MQL4関数】iAlligator関数の使い方！アリゲーターでトレンド判定EAを作ろう</a> は <a href="https://mql-programing.com">自動売買を作ろう！</a> に最初に表示されました。</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>【MQL4関数】iEnvelopes関数の使い方！エンベロープでトレンドフィルターEAを作ろう</title>
		<link>https://mql-programing.com/archives/13096/%e3%80%90mql4%e9%96%a2%e6%95%b0%e3%80%91ienvelopes%e9%96%a2%e6%95%b0%e3%81%ae%e4%bd%bf%e3%81%84%e6%96%b9%ef%bc%81%e3%82%a8%e3%83%b3%e3%83%99%e3%83%ad%e3%83%bc%e3%83%97%e3%81%a7%e3%83%88%e3%83%ac/</link>
		
		<dc:creator><![CDATA[朝日奈りさ]]></dc:creator>
		<pubDate>Fri, 01 May 2026 01:00:00 +0000</pubDate>
				<category><![CDATA[関数]]></category>
		<category><![CDATA[【辞書】MQLリファレンス]]></category>
		<category><![CDATA[iEnvelopes]]></category>
		<category><![CDATA[MQL4]]></category>
		<category><![CDATA[インジケーター]]></category>
		<category><![CDATA[エンベロープ]]></category>
		<category><![CDATA[リファレンス]]></category>
		<guid isPermaLink="false">https://mql-programing.com/?p=13096</guid>

					<description><![CDATA[<p>エンベロープ（Envelopes）は移動平均線の上下に一定割合のバンドを描くインジケーターです。価格がバンドを超えたときの反転狙い、またはバンドを突破したときのトレンドフォローなど、さまざまな手法で活用されます。 MQL [&#8230;]</p>
<p>投稿 <a href="https://mql-programing.com/archives/13096/%e3%80%90mql4%e9%96%a2%e6%95%b0%e3%80%91ienvelopes%e9%96%a2%e6%95%b0%e3%81%ae%e4%bd%bf%e3%81%84%e6%96%b9%ef%bc%81%e3%82%a8%e3%83%b3%e3%83%99%e3%83%ad%e3%83%bc%e3%83%97%e3%81%a7%e3%83%88%e3%83%ac/">【MQL4関数】iEnvelopes関数の使い方！エンベロープでトレンドフィルターEAを作ろう</a> は <a href="https://mql-programing.com">自動売買を作ろう！</a> に最初に表示されました。</p>
]]></description>
										<content:encoded><![CDATA[<p>エンベロープ（Envelopes）は移動平均線の上下に一定割合のバンドを描くインジケーターです。価格がバンドを超えたときの反転狙い、またはバンドを突破したときのトレンドフォローなど、さまざまな手法で活用されます。</p>
<p>MQL4では <code>iEnvelopes()</code> 関数を使ってエンベロープの値を取得できます。</p>
<h2><span id="toc1">iEnvelopes() の構文</span></h2>
<pre><code class="language-mql4">double iEnvelopes(
    string  symbol,       // 通貨ペア（NULLで現在のチャート）
    int     timeframe,    // 時間足（0で現在の時間足）
    int     ma_period,    // 移動平均の期間
    int     ma_method,    // 移動平均の種類
    int     ma_shift,     // 移動平均のシフト
    int     applied_price,// 適用価格
    double  deviation,    // 偏差（%）
    int     mode,         // バンドの種類（上・下）
    int     shift         // 何本前のバーか（0=現在）
);</code></pre>
<h2><span id="toc2">引数の詳細</span></h2>
<figure class="wp-block-table">
<table>
<thead>
<tr>
<th>引数</th>
<th>説明</th>
<th>よく使う値</th>
</tr>
</thead>
<tbody>
<tr>
<td>symbol</td>
<td>通貨ペア</td>
<td>NULL（現在のチャート）</td>
</tr>
<tr>
<td>timeframe</td>
<td>時間足</td>
<td>0（現在の時間足）</td>
</tr>
<tr>
<td>ma_period</td>
<td>移動平均の期間</td>
<td>20〜50</td>
</tr>
<tr>
<td>ma_method</td>
<td>移動平均の種類</td>
<td>MODE_SMA / MODE_EMA</td>
</tr>
<tr>
<td>ma_shift</td>
<td>MAのシフト</td>
<td>0（通常）</td>
</tr>
<tr>
<td>applied_price</td>
<td>適用価格</td>
<td>PRICE_CLOSE</td>
</tr>
<tr>
<td>deviation</td>
<td>バンドの偏差（%）</td>
<td>0.1〜0.5</td>
</tr>
<tr>
<td>mode</td>
<td>上下バンドの指定</td>
<td>MODE_UPPER / MODE_LOWER</td>
</tr>
<tr>
<td>shift</td>
<td>何本前のバーか</td>
<td>0（現在）/ 1（1本前）</td>
</tr>
</tbody>
</table>
</figure>
<h3><span id="toc3">mode の値</span></h3>
<figure class="wp-block-table">
<table>
<thead>
<tr>
<th>定数</th>
<th>値</th>
<th>説明</th>
</tr>
</thead>
<tbody>
<tr>
<td>MODE_UPPER</td>
<td>1</td>
<td>上バンド（移動平均 + 偏差%）</td>
</tr>
<tr>
<td>MODE_LOWER</td>
<td>2</td>
<td>下バンド（移動平均 &#8211; 偏差%）</td>
</tr>
</tbody>
</table>
</figure>
<h2><span id="toc4">基本的な使い方</span></h2>
<pre><code class="language-mql4">// 20期間SMA、偏差0.2%のエンベロープを取得する
double upperBand = iEnvelopes(NULL, 0, 20, MODE_SMA, 0, PRICE_CLOSE, 0.2, MODE_UPPER, 0);
double lowerBand = iEnvelopes(NULL, 0, 20, MODE_SMA, 0, PRICE_CLOSE, 0.2, MODE_LOWER, 0);

Print("上バンド: ", upperBand);
Print("下バンド: ", lowerBand);</code></pre>
<h2><span id="toc5">実践例1：エンベロープのブレイクアウトでエントリー</span></h2>
<p>価格が上バンドを上抜けたら買い、下バンドを下抜けたら売りのシグナルとして使う例です。</p>
<pre><code class="language-mql4">extern int    MagicNumber = 11001;
extern double Lots        = 0.1;
extern int    MA_Period   = 20;
extern double Deviation   = 0.2;  // バンド幅（%）

//+------------------------------------------------------------------+
//| エンベロープ ブレイクアウトEA                                       |
//+------------------------------------------------------------------+
void OnTick()
{
    // すでにポジションがある場合はスキップ
    if(OrdersTotal() > 0) return;

    double upperBand = iEnvelopes(NULL, 0, MA_Period, MODE_SMA, 0, PRICE_CLOSE, Deviation, MODE_UPPER, 1);
    double lowerBand = iEnvelopes(NULL, 0, MA_Period, MODE_SMA, 0, PRICE_CLOSE, Deviation, MODE_LOWER, 1);
    double prevClose = iClose(NULL, 0, 2);  // 2本前の終値
    double lastClose = iClose(NULL, 0, 1);  // 1本前の終値

    // 上バンドを上抜け → 買いエントリー
    if(prevClose < upperBand &#038;&#038; lastClose > upperBand)
    {
        OrderSend(Symbol(), OP_BUY, Lots, Ask, 20, 0, 0,
                  "Envelope Breakout", MagicNumber, 0, clrBlue);
    }

    // 下バンドを下抜け → 売りエントリー
    if(prevClose > lowerBand && lastClose < lowerBand)
    {
        OrderSend(Symbol(), OP_SELL, Lots, Bid, 20, 0, 0,
                  "Envelope Breakout", MagicNumber, 0, clrRed);
    }
}</code></pre>
<h2><span id="toc6">実践例2：エンベロープをトレンドフィルターとして使う</span></h2>
<p>価格が上バンドより上にある＝上昇トレンドと判断し、他のシグナルと組み合わせるフィルターとして活用する例です。</p>
<pre><code class="language-mql4">// トレンドフィルター関数
// 戻り値: 1=上昇トレンド / -1=下降トレンド / 0=レンジ
int GetTrend()
{
    double upperBand = iEnvelopes(NULL, 0, 20, MODE_SMA, 0, PRICE_CLOSE, 0.2, MODE_UPPER, 1);
    double lowerBand = iEnvelopes(NULL, 0, 20, MODE_SMA, 0, PRICE_CLOSE, 0.2, MODE_LOWER, 1);
    double lastClose = iClose(NULL, 0, 1);

    if(lastClose > upperBand) return  1;  // 上昇トレンド
    if(lastClose < lowerBand) return -1;  // 下降トレンド
    return 0;                             // レンジ（バンド内）
}

void OnTick()
{
    int trend = GetTrend();

    if(trend == 1)
        Comment("トレンド: 上昇（買いシグナルのみ有効）");
    else if(trend == -1)
        Comment("トレンド: 下降（売りシグナルのみ有効）");
    else
        Comment("トレンド: レンジ（様子見）");
}</code></pre>
<h2><span id="toc7">実践例3：反転狙い（逆張り）</span></h2>
<p>価格がバンドに触れたら反転を狙う逆張り戦略です。</p>
<pre><code class="language-mql4">extern int    MagicNumber = 11002;
extern double Lots        = 0.1;
extern int    MA_Period   = 30;
extern double Deviation   = 0.3;

void OnTick()
{
    if(OrdersTotal() > 0) return;

    double upperBand = iEnvelopes(NULL, 0, MA_Period, MODE_EMA, 0, PRICE_CLOSE, Deviation, MODE_UPPER, 1);
    double lowerBand = iEnvelopes(NULL, 0, MA_Period, MODE_EMA, 0, PRICE_CLOSE, Deviation, MODE_LOWER, 1);
    double high1     = iHigh(NULL, 0, 1);
    double low1      = iLow(NULL, 0, 1);

    // 上バンドに高値が触れた → 売り（反転狙い）
    if(high1 >= upperBand)
    {
        OrderSend(Symbol(), OP_SELL, Lots, Bid, 20, 0, 0,
                  "Envelope Mean Reversion", MagicNumber, 0, clrRed);
    }

    // 下バンドに安値が触れた → 買い（反転狙い）
    if(low1 <= lowerBand)
    {
        OrderSend(Symbol(), OP_BUY, Lots, Ask, 20, 0, 0,
                  "Envelope Mean Reversion", MagicNumber, 0, clrBlue);
    }
}</code></pre>
<h2><span id="toc8">偏差（deviation）の目安</span></h2>
<figure class="wp-block-table">
<table>
<thead>
<tr>
<th>通貨ペア</th>
<th>推奨偏差</th>
<th>補足</th>
</tr>
</thead>
<tbody>
<tr>
<td>USDJPY</td>
<td>0.1〜0.3%</td>
<td>ボラティリティが低め</td>
</tr>
<tr>
<td>EURUSD</td>
<td>0.1〜0.2%</td>
<td>標準的な設定</td>
</tr>
<tr>
<td>GBPJPY</td>
<td>0.3〜0.5%</td>
<td>ボラティリティが高め</td>
</tr>
</tbody>
</table>
</figure>
<p>偏差が小さすぎるとノイズが多くなり、大きすぎるとシグナルが少なくなります。バックテストで最適な値を見つけましょう。</p>
<h2><span id="toc9">まとめ</span></h2>
<ul>
<li><code>iEnvelopes()</code> は移動平均の上下にバンドを描くインジケーター関数</li>
<li><strong>MODE_UPPER</strong> で上バンド、<strong>MODE_LOWER</strong> で下バンドの値を取得</li>
<li>ブレイクアウト（トレンドフォロー）と反転狙い（逆張り）の両方に応用できる</li>
<li><code>deviation</code> パラメーターはバックテストで通貨ペアに合わせて調整する</li>
</ul>
<p>投稿 <a href="https://mql-programing.com/archives/13096/%e3%80%90mql4%e9%96%a2%e6%95%b0%e3%80%91ienvelopes%e9%96%a2%e6%95%b0%e3%81%ae%e4%bd%bf%e3%81%84%e6%96%b9%ef%bc%81%e3%82%a8%e3%83%b3%e3%83%99%e3%83%ad%e3%83%bc%e3%83%97%e3%81%a7%e3%83%88%e3%83%ac/">【MQL4関数】iEnvelopes関数の使い方！エンベロープでトレンドフィルターEAを作ろう</a> は <a href="https://mql-programing.com">自動売買を作ろう！</a> に最初に表示されました。</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>【MQL4関数】OnCalculate関数とは？ロウソク足が動くたびに実行されるイベント関数</title>
		<link>https://mql-programing.com/archives/3471/oncalculate/</link>
		
		<dc:creator><![CDATA[朝日奈りさ]]></dc:creator>
		<pubDate>Mon, 23 May 2022 12:19:49 +0000</pubDate>
				<category><![CDATA[関数]]></category>
		<category><![CDATA[【中級編】MQLプログラムの読み方・書き方]]></category>
		<category><![CDATA[その他]]></category>
		<category><![CDATA[【辞書】MQLリファレンス]]></category>
		<category><![CDATA[リファレンス]]></category>
		<category><![CDATA[自動売買]]></category>
		<category><![CDATA[イベント関数]]></category>
		<category><![CDATA[OnCalculate]]></category>
		<category><![CDATA[インジケータ]]></category>
		<category><![CDATA[MQL]]></category>
		<guid isPermaLink="false">https://mql-programing.com/?p=3471</guid>

					<description><![CDATA[<p>OnCalculate関数とは？ OnCalculate()は、カスタムインジケータ専用のイベント関数で、チャートに新しい価格データ（ティック）が到着するたびに自動的に呼び出されます。ロウソク足の値動きが発生するたびに実 [&#8230;]</p>
<p>投稿 <a href="https://mql-programing.com/archives/3471/oncalculate/">【MQL4関数】OnCalculate関数とは？ロウソク足が動くたびに実行されるイベント関数</a> は <a href="https://mql-programing.com">自動売買を作ろう！</a> に最初に表示されました。</p>
]]></description>
										<content:encoded><![CDATA[<h2><span id="toc1">OnCalculate関数とは？</span></h2>
<p>OnCalculate()は、<strong>カスタムインジケータ専用のイベント関数</strong>で、チャートに新しい価格データ（ティック）が到着するたびに自動的に呼び出されます。ロウソク足の値動きが発生するたびに実行されるため、移動平均線やRSIなどのテクニカル指標の計算処理をこの関数の中に記述します。</p>
<p>EA（エキスパートアドバイザー）における <code>OnTick()</code> に相当する役割を持ちますが、OnCalculate()はインジケータバッファへの描画計算に特化した設計になっています。</p>
<h2><span id="toc2">OnCalculate関数の2つの書式</span></h2>
<p>OnCalculate()には<strong>2つの書式</strong>があります。1つのインジケータで使えるのはどちらか一方のみです。</p>
<h3><span id="toc3">書式1：短縮形（シンプル版）</span></h3>
<p>チャートの価格データ（始値・高値・安値・終値など）を直接受け取る形式です。最も一般的に使われます。</p>
<pre><code class="language-mql4">int OnCalculate(const int rates_total,      // 利用可能なバーの総数
                const int prev_calculated,   // 前回呼び出し時の計算済みバー数
                const datetime &time[],      // 各バーの時刻
                const double &open[],        // 始値
                const double &high[],        // 高値
                const double &low[],         // 安値
                const double &close[],       // 終値
                const long &tick_volume[],   // ティックボリューム
                const long &volume[],        // 実ボリューム
                const int &spread[])         // スプレッド
</code></pre>
<h3><span id="toc4">書式2：OnInit()でのSetIndexBuffer連携版</span></h3>
<p>他のインジケータの値を入力として使う場合に利用します。<code>#property indicator_separate_window</code>を使うインジケータなどで使われます。</p>
<pre><code class="language-mql4">int OnCalculate(const int rates_total,      // 利用可能なデータ数
                const int prev_calculated,   // 前回呼び出し時の計算済みデータ数
                const int begin,             // 意味のあるデータの開始位置
                const double &price[])       // 計算対象の価格配列
</code></pre>
<h2><span id="toc5">引数の詳細解説</span></h2>
<table>
<thead>
<tr>
<th>引数</th>
<th>型</th>
<th>説明</th>
</tr>
</thead>
<tbody>
<tr>
<td>rates_total</td>
<td>int</td>
<td>チャート上の全バー数。<code>Bars</code>と同等の値</td>
</tr>
<tr>
<td>prev_calculated</td>
<td>int</td>
<td>前回のOnCalculate()呼び出しで返した戻り値。初回は<strong>0</strong></td>
</tr>
<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>volume[]</td>
<td>long</td>
<td>各バーの実ボリューム（対応サーバーのみ）</td>
</tr>
<tr>
<td>spread[]</td>
<td>int</td>
<td>各バーのスプレッド（ポイント単位）</td>
</tr>
<tr>
<td>begin</td>
<td>int</td>
<td>（書式2のみ）有効データの開始インデックス</td>
</tr>
<tr>
<td>price[]</td>
<td>double</td>
<td>（書式2のみ）計算に使う価格データ配列</td>
</tr>
</tbody>
</table>
<h2><span id="toc6">戻り値</span></h2>
<p>OnCalculate()は<strong>int型</strong>の値を返します。この戻り値は、<strong>次回呼び出し時に <code>prev_calculated</code> 引数として渡されます</strong>。</p>
<ul>
<li>通常は <code>rates_total</code> を返します（すべてのバーを計算済みという意味）</li>
<li><strong>0を返す</strong>と、次回の呼び出しで全バーを再計算します</li>
<li>初回呼び出し時は <code>prev_calculated</code> が0になるため、全バーの計算が行われます</li>
</ul>
<h2><span id="toc7">prev_calculatedの仕組み（重要）</span></h2>
<p><code>prev_calculated</code> は、OnCalculate()の<strong>パフォーマンス最適化の鍵</strong>となる引数です。毎回すべてのバーを計算し直すのではなく、新しいバーだけを計算することで処理を高速化します。</p>
<pre><code class="language-mql4">// prev_calculatedの動作イメージ
// 
// 1回目の呼び出し: prev_calculated = 0  → 全バーを計算
//   戻り値として rates_total（例：1000）を返す
//
// 2回目の呼び出し: prev_calculated = 1000
//   rates_total = 1000（新しいバーなし）→ 最後のバーだけ更新
//   rates_total = 1001（新しいバーあり）→ 新しいバーだけ計算
</code></pre>
<h2><span id="toc8">プログラム例1：最もシンプルな終値ラインインジケータ</span></h2>
<p>まずは最も基本的な例として、終値をそのままラインとして描画するインジケータを作成します。OnCalculate()の基本構造と<code>prev_calculated</code>の使い方を理解するのに最適です。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| 終値ラインインジケータ（OnCalculateの基本構造を学ぶ）             |
//+------------------------------------------------------------------+
#property indicator_chart_window        // メインチャートに表示
#property indicator_buffers 1           // バッファ数：1本
#property indicator_color1 clrDodgerBlue // ラインの色
#property indicator_width1 2            // ラインの太さ

// インジケータバッファ
double LineBuffer[];

//+------------------------------------------------------------------+
//| 初期化関数                                                        |
//+------------------------------------------------------------------+
int OnInit()
{
   // バッファをインジケータラインとして割り当て
   SetIndexBuffer(0, LineBuffer);
   SetIndexStyle(0, DRAW_LINE);
   SetIndexLabel(0, "終値ライン");
   
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| OnCalculate - ティックごとに呼ばれるメイン関数                    |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
   // 計算開始位置を決定
   // prev_calculated == 0 なら全バーを計算（初回 or 再計算）
   // それ以外なら、最後のバーから計算（効率化）
   int start;
   if(prev_calculated == 0)
      start = 0;                        // 初回：最初のバーから
   else
      start = prev_calculated - 1;      // 2回目以降：最後のバーから
   
   // 計算ループ
   for(int i = start; i < rates_total; i++)
   {
      LineBuffer[i] = close[i];         // 終値をそのままバッファに格納
   }
   
   // rates_totalを返すことで、次回のprev_calculatedに反映される
   return(rates_total);
}
</code></pre>
<p>このコードのポイントは、<code>prev_calculated</code>を活用した計算開始位置の決定です。初回はすべてのバーを計算し、2回目以降は最後のバーだけを更新するため、処理が非常に軽くなります。</p>
<h2><span id="toc9">プログラム例2：単純移動平均（SMA）インジケータ</span></h2>
<p>次に、実用的な単純移動平均線（SMA）を自作します。外部パラメータで期間を変更でき、<code>prev_calculated</code>による最適化も実装しています。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| 自作SMA（単純移動平均線）インジケータ                              |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_color1 clrRed
#property indicator_width1 2

// 外部パラメータ（ユーザーが変更可能）
input int    MaPeriod = 20;             // 移動平均の期間
input int    MaShift  = 0;              // 表示をシフトするバー数

// インジケータバッファ
double MaBuffer[];

//+------------------------------------------------------------------+
//| 初期化関数                                                        |
//+------------------------------------------------------------------+
int OnInit()
{
   // パラメータチェック
   if(MaPeriod < 1)
   {
      Print("エラー: 期間は1以上を指定してください");
      return(INIT_PARAMETERS_INCORRECT);
   }
   
   SetIndexBuffer(0, MaBuffer);
   SetIndexStyle(0, DRAW_LINE);
   SetIndexShift(0, MaShift);           // ラインの水平シフト
   SetIndexLabel(0, "SMA(" + IntegerToString(MaPeriod) + ")");
   
   // 最初のMaPeriod-1本は描画しない（データ不足のため）
   SetIndexDrawBegin(0, MaPeriod);
   
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| OnCalculate - SMAの計算処理                                       |
//+------------------------------------------------------------------+
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[])
{
   // データが移動平均の期間に満たない場合は計算しない
   if(rates_total < MaPeriod)
      return(0);
   
   // 計算開始位置の決定
   int start;
   if(prev_calculated == 0)
      start = MaPeriod - 1;             // 初回：期間分のデータが揃った位置から
   else
      start = prev_calculated - 1;      // 2回目以降：最後のバーから
   
   // SMAの計算ループ
   for(int i = start; i < rates_total; i++)
   {
      double sum = 0.0;
      
      // 現在のバーからMaPeriod本分の終値を合計
      for(int j = 0; j < MaPeriod; j++)
      {
         sum += close[i - j];
      }
      
      // 平均値をバッファに格納
      MaBuffer[i] = sum / MaPeriod;
   }
   
   return(rates_total);
}
</code></pre>
<p><code>SetIndexDrawBegin()</code> を使って、データが不足する最初の数本のバーでは描画を行わないようにしている点に注目してください。</p>
<h2><span id="toc10">プログラム例3：高値・安値バンドインジケータ（複数バッファ）</span></h2>
<p>複数のインジケータバッファを使い、指定期間の高値ラインと安値ラインを同時に描画する例です。ドンチャンチャネルに似たバンド型インジケータを作成します。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| 高値・安値バンドインジケータ（ドンチャンチャネル風）               |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_buffers 3           // バッファ3本（高値・安値・中間線）
#property indicator_color1 clrDeepSkyBlue   // 高値バンド
#property indicator_color2 clrOrangeRed     // 安値バンド
#property indicator_color3 clrGray          // 中間線
#property indicator_width1 2
#property indicator_width2 2
#property indicator_style3 STYLE_DOT    // 中間線は点線

// 外部パラメータ
input int BandPeriod = 20;              // バンドの期間

// インジケータバッファ
double HighBand[];                      // 高値バンド
double LowBand[];                       // 安値バンド
double MidLine[];                       // 中間線

//+------------------------------------------------------------------+
//| 初期化関数                                                        |
//+------------------------------------------------------------------+
int OnInit()
{
   if(BandPeriod < 2)
   {
      Print("エラー: 期間は2以上を指定してください");
      return(INIT_PARAMETERS_INCORRECT);
   }
   
   // バッファの割り当て
   SetIndexBuffer(0, HighBand);
   SetIndexBuffer(1, LowBand);
   SetIndexBuffer(2, MidLine);
   
   // 描画スタイルの設定
   SetIndexStyle(0, DRAW_LINE);
   SetIndexStyle(1, DRAW_LINE);
   SetIndexStyle(2, DRAW_LINE);
   
   // ラベル設定
   SetIndexLabel(0, "高値バンド(" + IntegerToString(BandPeriod) + ")");
   SetIndexLabel(1, "安値バンド(" + IntegerToString(BandPeriod) + ")");
   SetIndexLabel(2, "中間線");
   
   // 描画開始位置
   SetIndexDrawBegin(0, BandPeriod);
   SetIndexDrawBegin(1, BandPeriod);
   SetIndexDrawBegin(2, BandPeriod);
   
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| OnCalculate - 高値・安値バンドの計算                               |
//+------------------------------------------------------------------+
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[])
{
   // データ不足チェック
   if(rates_total < BandPeriod)
      return(0);
   
   // 計算開始位置
   int start;
   if(prev_calculated == 0)
      start = BandPeriod - 1;
   else
      start = prev_calculated - 1;
   
   // バンドの計算
   for(int i = start; i < rates_total; i++)
   {
      double highestHigh = high[i];     // 最高値の初期値
      double lowestLow   = low[i];      // 最安値の初期値
      
      // 過去BandPeriod本のバーから最高値・最安値を探す
      for(int j = 0; j < BandPeriod; j++)
      {
         if(high[i - j] > highestHigh)
            highestHigh = high[i - j];
         if(low[i - j] < lowestLow)
            lowestLow = low[i - j];
      }
      
      // バッファに格納
      HighBand[i] = highestHigh;        // 期間内の最高値
      LowBand[i]  = lowestLow;          // 期間内の最安値
      MidLine[i]   = (highestHigh + lowestLow) / 2.0;  // 中間値
   }
   
   return(rates_total);
}
</code></pre>
<p>3本のバッファを同時に使うことで、チャート上にバンド（帯域）を視覚的に表示できます。<code>SetIndexStyle()</code>で各ラインの線種を変えることで、見分けやすくなります。</p>
<h2><span id="toc11">プログラム例4：書式2を使った価格モメンタムインジケータ（サブウインドウ）</span></h2>
<p>書式2（price[]配列を使う形式）の例です。サブウインドウに表示するモメンタム系インジケータを作成します。ユーザーがチャート上で「適用価格」を選択でき、その値がprice[]として渡されます。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| モメンタムインジケータ（OnCalculate書式2を使用）                   |
//+------------------------------------------------------------------+
#property indicator_separate_window      // サブウインドウに表示
#property indicator_buffers 2
#property indicator_color1 clrLimeGreen  // モメンタムライン
#property indicator_color2 clrRed        // ゼロライン用（水平線）
#property indicator_width1 2
#property indicator_level1 0.0           // ゼロラインを自動描画
#property indicator_levelcolor clrSilver

// 外部パラメータ
input int MomentumPeriod = 14;          // モメンタム期間

// インジケータバッファ
double MomentumBuffer[];
double SignalBuffer[];                   // シグナル用（SMA平滑化）

//+------------------------------------------------------------------+
//| 初期化関数                                                        |
//+------------------------------------------------------------------+
int OnInit()
{
   if(MomentumPeriod < 1)
   {
      Print("エラー: 期間は1以上を指定してください");
      return(INIT_PARAMETERS_INCORRECT);
   }
   
   SetIndexBuffer(0, MomentumBuffer);
   SetIndexBuffer(1, SignalBuffer);
   
   SetIndexStyle(0, DRAW_LINE);
   SetIndexStyle(1, DRAW_LINE);
   
   SetIndexLabel(0, "Momentum(" + IntegerToString(MomentumPeriod) + ")");
   SetIndexLabel(1, "Signal(9)");
   
   SetIndexDrawBegin(0, MomentumPeriod);
   SetIndexDrawBegin(1, MomentumPeriod + 9);
   
   // インジケータの短縮名（サブウインドウのタイトル）
   IndicatorShortName("My Momentum(" + IntegerToString(MomentumPeriod) + ")");
   
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| OnCalculate - 書式2：price[]配列を使う形式                        |
//| ※ユーザーがインジケータの「適用価格」で選択した値がprice[]に入る   |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,             // 有効データの開始位置
                const double &#038;price[])       // 適用価格の配列
{
   // データ不足チェック
   if(rates_total < MomentumPeriod + 1)
      return(0);
   
   // ===== モメンタムの計算 =====
   int start;
   if(prev_calculated == 0)
      start = MomentumPeriod + begin;   // beginを考慮して開始位置を決定
   else
      start = prev_calculated - 1;
   
   // モメンタム = 現在価格 - N期間前の価格
   for(int i = start; i < rates_total; i++)
   {
      MomentumBuffer[i] = price[i] - price[i - MomentumPeriod];
   }
   
   // ===== シグナルライン（モメンタムの9期間SMA）=====
   int signalPeriod = 9;
   int signalStart;
   
   if(prev_calculated == 0)
      signalStart = MomentumPeriod + begin + signalPeriod - 1;
   else
      signalStart = prev_calculated - 1;
   
   // signalStartがデータ範囲内か確認
   if(signalStart < MomentumPeriod + begin + signalPeriod - 1)
      signalStart = MomentumPeriod + begin + signalPeriod - 1;
   
   for(int i = signalStart; i < rates_total; i++)
   {
      double sum = 0.0;
      for(int j = 0; j < signalPeriod; j++)
      {
         sum += MomentumBuffer[i - j];
      }
      SignalBuffer[i] = sum / signalPeriod;
   }
   
   return(rates_total);
}
</code></pre>
<p>書式2では、ユーザーがインジケータのプロパティで「適用価格」（終値、始値、高値、安値、中間値など）を選択でき、その値が<code>price[]</code>として自動的に渡されます。<code>begin</code>パラメータは、他のインジケータの出力を入力にチェーンする場合に、有効なデータの開始位置を示します。</p>
<h2><span id="toc12">よくある使い方のポイント・注意事項</span></h2>
<h3><span id="toc13">1. prev_calculatedの正しい活用</span></h3>
<p>OnCalculate()で最も重要なのは<code>prev_calculated</code>の活用です。毎回全バーを再計算すると、バー数が多い場合に処理が重くなります。</p>
<pre><code class="language-mql4">// ◎ 正しい使い方：差分計算
int start;
if(prev_calculated == 0)
   start = 0;              // 初回のみ全バー計算
else
   start = prev_calculated - 1;  // -1 は最後のバー（形成中）を再計算するため

// ✕ 悪い使い方：毎回全バー計算（非効率）
for(int i = 0; i < rates_total; i++)  // 毎回全部計算してしまう
</code></pre>
<h3><span id="toc14">2. 配列のインデックス方向に注意</span></h3>
<p>OnCalculate()の引数で渡される配列（close[], high[]など）は、<strong>デフォルトで時系列ではない</strong>点に注意が必要です。</p>
<pre><code class="language-mql4">// OnCalculate内の配列のインデックス
// close[0]                → 最も古いバーの終値
// close[rates_total - 1]  → 最新（現在形成中の）バーの終値
//
// ※通常のClose[0]が最新バーを指すのとは逆！
//
// もし時系列順にしたい場合は以下を使う（ただし通常は不要）
// ArraySetAsSeries(close, true);
// ※ただし const 配列なので変更できない場合がある
</code></pre>
<h3><span id="toc15">3. 戻り値は必ずrates_totalを返す</span></h3>
<pre><code class="language-mql4">// 正常な計算が完了した場合
return(rates_total);     // 次回のprev_calculatedにこの値が入る

// エラーや計算不能の場合
return(0);               // 次回は全バーを再計算する
</code></pre>
<h3><span id="toc16">4. OnCalculate()はEAやスクリプトでは使えない</span></h3>
<p>OnCalculate()は<strong>カスタムインジケータ（.mq4ファイルの#property indicator_～ がある場合）でのみ</strong>使用できます。EAではOnTick()、スクリプトではOnStart()を使用してください。</p>
<h3><span id="toc17">5. rates_totalの変化を検知して新しいバーを判定</span></h3>
<pre><code class="language-mql4">int OnCalculate(const int rates_total,
                const int prev_calculated,
                /* 省略 */)
{
   // 新しいバーが形成されたかどうかの判定
   if(rates_total > prev_calculated)
   {
      // 新しいバーが追加された（または初回呼び出し）
      Print("新しいバーが追加されました。バー総数: ", rates_total);
   }
   else
   {
      // 既存のバー（最新バー）のティック更新
      // 最新バーの終値だけが変わっている
   }
   
   return(rates_total);
}
</code></pre>
<h3><span id="toc18">6. EMPTY_VALUEで描画をスキップ</span></h3>
<p>特定の条件でラインを描画しない場合は、バッファに<code>EMPTY_VALUE</code>を設定します。</p>
<pre><code class="language-mql4">// 条件を満たさない場合は描画しない
if(close[i] < open[i])
   LineBuffer[i] = EMPTY_VALUE;  // この位置はラインが途切れる
else
   LineBuffer[i] = close[i];
</code></pre>
<h2><span id="toc19">まとめ</span></h2>
<ul>
<li>OnCalculate()は<strong>カスタムインジケータ専用</strong>のイベント関数で、ティックごとに自動実行される</li>
<li><strong>2つの書式</strong>があり、用途に応じて使い分ける（書式1がメイン、書式2は他インジケータの値を入力にする場合）</li>
<li><code>prev_calculated</code>を活用して<strong>差分計算</strong>を行い、パフォーマンスを最適化するのが基本</li>
<li>引数の配列は<strong>時系列ではない</strong>（インデックス0が最古、rates_total-1が最新）</li>
<li>戻り値として<code>rates_total</code>を返すことで、次回の<code>prev_calculated</code>に計算済みバー数を伝える</li>
<li>描画をスキップしたい場合は<code>EMPTY_VALUE</code>を使用する</li>
</ul>
<p>投稿 <a href="https://mql-programing.com/archives/3471/oncalculate/">【MQL4関数】OnCalculate関数とは？ロウソク足が動くたびに実行されるイベント関数</a> は <a href="https://mql-programing.com">自動売買を作ろう！</a> に最初に表示されました。</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>【MQL4関数】OnChartEvent関数とは？チャート操作を行なったときに実行されるイベント関数</title>
		<link>https://mql-programing.com/archives/3451/onchartevent/</link>
		
		<dc:creator><![CDATA[朝日奈りさ]]></dc:creator>
		<pubDate>Sun, 22 May 2022 08:32:00 +0000</pubDate>
				<category><![CDATA[関数]]></category>
		<category><![CDATA[【中級編】MQLプログラムの読み方・書き方]]></category>
		<category><![CDATA[【辞書】MQLリファレンス]]></category>
		<category><![CDATA[その他]]></category>
		<category><![CDATA[MQL]]></category>
		<category><![CDATA[リファレンス]]></category>
		<category><![CDATA[自動売買]]></category>
		<category><![CDATA[OnChartEvent]]></category>
		<category><![CDATA[イベント関数]]></category>
		<guid isPermaLink="false">https://mql-programing.com/?p=3451</guid>

					<description><![CDATA[<p>OnChartEvent関数とは？ OnChartEvent関数は、チャート上で発生するさまざまなイベント（マウスクリック、キーボード入力、オブジェクトの操作など）を検知して処理を実行するためのイベントハンドラ関数です。 [&#8230;]</p>
<p>投稿 <a href="https://mql-programing.com/archives/3451/onchartevent/">【MQL4関数】OnChartEvent関数とは？チャート操作を行なったときに実行されるイベント関数</a> は <a href="https://mql-programing.com">自動売買を作ろう！</a> に最初に表示されました。</p>
]]></description>
										<content:encoded><![CDATA[<h2><span id="toc1">OnChartEvent関数とは？</span></h2>
<p>OnChartEvent関数は、チャート上で発生するさまざまなイベント（マウスクリック、キーボード入力、オブジェクトの操作など）を検知して処理を実行するための<strong>イベントハンドラ関数</strong>です。エキスパートアドバイザー（EA）やインジケーターで使用でき、ユーザーのチャート操作に応じたインタラクティブな処理を実装できます。</p>
<p>この関数は、MQL4が自動的に呼び出す<strong>定義済みイベント関数</strong>の一つで、開発者が直接呼び出すものではありません。チャート上でイベントが発生するたびに、MT4のランタイムがこの関数を自動的に実行します。</p>
<h2><span id="toc2">基本的な書式</span></h2>
<pre><code class="language-mql4">void OnChartEvent(const int id,         // イベントID
                  const long &lparam,    // long型イベントパラメータ
                  const double &dparam,  // double型イベントパラメータ
                  const string &sparam)  // string型イベントパラメータ
</code></pre>
<h3><span id="toc3">引数の説明</span></h3>
<table>
<thead>
<tr>
<th>引数名</th>
<th>型</th>
<th>説明</th>
</tr>
</thead>
<tbody>
<tr>
<td>id</td>
<td>const int</td>
<td>イベントの種類を示すID。CHARTEVENT列挙値が入る</td>
</tr>
<tr>
<td>lparam</td>
<td>const long&amp;</td>
<td>イベントに応じたlong型パラメータ（キーコード、X座標など）</td>
</tr>
<tr>
<td>dparam</td>
<td>const double&amp;</td>
<td>イベントに応じたdouble型パラメータ（Y座標、価格など）</td>
</tr>
<tr>
<td>sparam</td>
<td>const string&amp;</td>
<td>イベントに応じたstring型パラメータ（オブジェクト名など）</td>
</tr>
</tbody>
</table>
<h3><span id="toc4">戻り値</span></h3>
<p>OnChartEvent関数の戻り値は<strong>void</strong>（なし）です。値を返す必要はありません。</p>
<h2><span id="toc5">主要なイベントID一覧</span></h2>
<p>id引数に渡されるイベントIDは、CHARTEVENT列挙型で定義されています。代表的なものを以下に示します。</p>
<table>
<thead>
<tr>
<th>イベントID</th>
<th>説明</th>
<th>lparam</th>
<th>dparam</th>
<th>sparam</th>
</tr>
</thead>
<tbody>
<tr>
<td>CHARTEVENT_KEYDOWN</td>
<td>キーが押された</td>
<td>キーコード</td>
<td>繰り返し回数</td>
<td>&#8211;</td>
</tr>
<tr>
<td>CHARTEVENT_MOUSE_MOVE</td>
<td>マウスが移動した</td>
<td>X座標</td>
<td>Y座標</td>
<td>マウスボタンフラグ</td>
</tr>
<tr>
<td>CHARTEVENT_OBJECT_CREATE</td>
<td>オブジェクトが作成された</td>
<td>&#8211;</td>
<td>&#8211;</td>
<td>オブジェクト名</td>
</tr>
<tr>
<td>CHARTEVENT_OBJECT_CHANGE</td>
<td>オブジェクトが変更された</td>
<td>&#8211;</td>
<td>&#8211;</td>
<td>オブジェクト名</td>
</tr>
<tr>
<td>CHARTEVENT_OBJECT_DELETE</td>
<td>オブジェクトが削除された</td>
<td>&#8211;</td>
<td>&#8211;</td>
<td>オブジェクト名</td>
</tr>
<tr>
<td>CHARTEVENT_CLICK</td>
<td>チャートがクリックされた</td>
<td>X座標</td>
<td>Y座標</td>
<td>&#8211;</td>
</tr>
<tr>
<td>CHARTEVENT_OBJECT_CLICK</td>
<td>オブジェクトがクリックされた</td>
<td>X座標</td>
<td>Y座標</td>
<td>オブジェクト名</td>
</tr>
<tr>
<td>CHARTEVENT_OBJECT_DRAG</td>
<td>オブジェクトがドラッグされた</td>
<td>&#8211;</td>
<td>&#8211;</td>
<td>オブジェクト名</td>
</tr>
<tr>
<td>CHARTEVENT_OBJECT_ENDEDIT</td>
<td>テキスト編集が完了した</td>
<td>&#8211;</td>
<td>&#8211;</td>
<td>オブジェクト名</td>
</tr>
<tr>
<td>CHARTEVENT_CHART_CHANGE</td>
<td>チャートのサイズ等が変更された</td>
<td>&#8211;</td>
<td>&#8211;</td>
<td>&#8211;</td>
</tr>
<tr>
<td>CHARTEVENT_CUSTOM</td>
<td>カスタムイベントの最初のID</td>
<td>カスタム値</td>
<td>カスタム値</td>
<td>カスタム値</td>
</tr>
<tr>
<td>CHARTEVENT_CUSTOM_LAST</td>
<td>カスタムイベントの最後のID</td>
<td>カスタム値</td>
<td>カスタム値</td>
<td>カスタム値</td>
</tr>
</tbody>
</table>
<h2><span id="toc6">プログラム例1：全イベントをログ出力する基本例</span></h2>
<p>まずは最も基本的な使い方として、チャート上で発生するすべてのイベントをエキスパートログに出力するサンプルです。どのようなイベントが発生しているかを確認する際に便利です。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| OnChartEvent基本サンプル：全イベントのログ出力                      |
//+------------------------------------------------------------------+
#property strict

//+------------------------------------------------------------------+
//| 初期化関数                                                        |
//+------------------------------------------------------------------+
int OnInit()
{
   // オブジェクト作成・削除イベントを有効にする
   ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, true);
   ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true);
   
   Print("=== OnChartEventログ出力サンプル開始 ===");
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| ティック関数（必須だが今回は処理なし）                               |
//+------------------------------------------------------------------+
void OnTick()
{
   // 特に処理なし
}

//+------------------------------------------------------------------+
//| チャートイベント処理関数                                           |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
   // イベントIDに応じてイベント名を判定
   string eventName = "";
   
   switch(id)
   {
      case CHARTEVENT_KEYDOWN:
         eventName = "キーダウン";
         // lparamにキーコードが入る
         Print(eventName, " | キーコード=", lparam, 
               " | 文字='", ShortToString((ushort)lparam), "'");
         break;
         
      case CHARTEVENT_CLICK:
         eventName = "チャートクリック";
         // lparamにX座標、dparamにY座標が入る
         Print(eventName, " | X=", lparam, " | Y=", dparam);
         break;
         
      case CHARTEVENT_OBJECT_CLICK:
         eventName = "オブジェクトクリック";
         // sparamにオブジェクト名が入る
         Print(eventName, " | オブジェクト名=", sparam,
               " | X=", lparam, " | Y=", dparam);
         break;
         
      case CHARTEVENT_OBJECT_CREATE:
         eventName = "オブジェクト作成";
         Print(eventName, " | オブジェクト名=", sparam);
         break;
         
      case CHARTEVENT_OBJECT_DELETE:
         eventName = "オブジェクト削除";
         Print(eventName, " | オブジェクト名=", sparam);
         break;
         
      case CHARTEVENT_OBJECT_CHANGE:
         eventName = "オブジェクト変更";
         Print(eventName, " | オブジェクト名=", sparam);
         break;
         
      case CHARTEVENT_OBJECT_DRAG:
         eventName = "オブジェクトドラッグ";
         Print(eventName, " | オブジェクト名=", sparam);
         break;
         
      case CHARTEVENT_CHART_CHANGE:
         eventName = "チャート変更";
         Print(eventName, "（サイズ変更、スクロール等）");
         break;
         
      default:
         // カスタムイベントまたは未分類のイベント
         if(id >= CHARTEVENT_CUSTOM && id <= CHARTEVENT_CUSTOM_LAST)
         {
            Print("カスタムイベント | ID=", id - CHARTEVENT_CUSTOM,
                  " | lparam=", lparam,
                  " | dparam=", dparam,
                  " | sparam=", sparam);
         }
         else
         {
            Print("不明なイベント | ID=", id);
         }
         break;
   }
}
</code></pre>
<p>このサンプルを動かすと、チャート上で操作を行うたびにエキスパートタブにイベント情報が出力されます。<code>ChartSetInteger</code>でオブジェクトの作成・削除イベントを有効にしている点がポイントです。これを設定しないと、CHARTEVENT_OBJECT_CREATEとCHARTEVENT_OBJECT_DELETEイベントは発生しません。</p>
<h2><span id="toc7">プログラム例2：ボタンクリックで売買を行うパネルEA</span></h2>
<p>チャート上にBuy/Sellボタンを配置し、クリックで注文を出す実用的なサンプルです。GUIベースのトレードパネルの基本構造として応用できます。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| ボタンクリックで売買を行うパネルEA                                  |
//+------------------------------------------------------------------+
#property strict

// 入力パラメータ
input double LotSize    = 0.01;   // ロットサイズ
input int    Slippage   = 3;      // スリッページ
input int    StopLoss   = 50;     // ストップロス（ポイント）
input int    TakeProfit = 100;    // テイクプロフィット（ポイント）

// ボタン名の定数
#define BTN_BUY    "BtnBuy"
#define BTN_SELL   "BtnSell"
#define BTN_CLOSE  "BtnCloseAll"
#define LBL_INFO   "LblInfo"

//+------------------------------------------------------------------+
//| ボタンを作成する関数                                               |
//+------------------------------------------------------------------+
bool CreateButton(string name, string text, int x, int y, 
                  int width, int height, color bgColor, color textColor)
{
   // ボタンオブジェクトを作成
   if(!ObjectCreate(0, name, OBJ_BUTTON, 0, 0, 0))
   {
      Print("ボタン作成失敗: ", name);
      return false;
   }
   
   // ボタンのプロパティを設定
   ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
   ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
   ObjectSetInteger(0, name, OBJPROP_XSIZE, width);
   ObjectSetInteger(0, name, OBJPROP_YSIZE, height);
   ObjectSetString(0, name, OBJPROP_TEXT, text);
   ObjectSetInteger(0, name, OBJPROP_COLOR, textColor);
   ObjectSetInteger(0, name, OBJPROP_BGCOLOR, bgColor);
   ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 10);
   ObjectSetInteger(0, name, OBJPROP_BORDER_COLOR, clrBlack);
   
   return true;
}

//+------------------------------------------------------------------+
//| 情報ラベルを作成する関数                                           |
//+------------------------------------------------------------------+
void CreateInfoLabel()
{
   ObjectCreate(0, LBL_INFO, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, LBL_INFO, OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetInteger(0, LBL_INFO, OBJPROP_XDISTANCE, 20);
   ObjectSetInteger(0, LBL_INFO, OBJPROP_YDISTANCE, 150);
   ObjectSetInteger(0, LBL_INFO, OBJPROP_COLOR, clrWhite);
   ObjectSetInteger(0, LBL_INFO, OBJPROP_FONTSIZE, 9);
   ObjectSetString(0, LBL_INFO, OBJPROP_TEXT, "ボタンをクリックしてください");
}

//+------------------------------------------------------------------+
//| 情報ラベルを更新する関数                                           |
//+------------------------------------------------------------------+
void UpdateInfoLabel(string message)
{
   ObjectSetString(0, LBL_INFO, OBJPROP_TEXT, message);
   ChartRedraw(0);
}

//+------------------------------------------------------------------+
//| 初期化関数                                                        |
//+------------------------------------------------------------------+
int OnInit()
{
   // パネルのボタンを作成
   CreateButton(BTN_BUY,   "BUY",       20, 30, 120, 40, clrDodgerBlue,  clrWhite);
   CreateButton(BTN_SELL,  "SELL",       20, 75, 120, 40, clrCrimson,     clrWhite);
   CreateButton(BTN_CLOSE, "CLOSE ALL",  20, 120, 120, 25, clrDimGray,    clrWhite);
   
   // 情報表示ラベルを作成
   CreateInfoLabel();
   
   ChartRedraw(0);
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| 終了処理関数                                                      |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // 作成したオブジェクトを全て削除
   ObjectDelete(0, BTN_BUY);
   ObjectDelete(0, BTN_SELL);
   ObjectDelete(0, BTN_CLOSE);
   ObjectDelete(0, LBL_INFO);
}

//+------------------------------------------------------------------+
//| ティック関数                                                      |
//+------------------------------------------------------------------+
void OnTick()
{
   // 特に処理なし
}

//+------------------------------------------------------------------+
//| 買い注文を送信する関数                                             |
//+------------------------------------------------------------------+
void ExecuteBuy()
{
   double sl = 0, tp = 0;
   double price = Ask;
   
   // ストップロス・テイクプロフィットを計算
   if(StopLoss > 0)
      sl = price - StopLoss * _Point;
   if(TakeProfit > 0)
      tp = price + TakeProfit * _Point;
   
   int ticket = OrderSend(Symbol(), OP_BUY, LotSize, price, Slippage, sl, tp,
                           "Panel Buy", 0, 0, clrBlue);
   
   if(ticket > 0)
      UpdateInfoLabel("BUY注文成功 Ticket#" + IntegerToString(ticket));
   else
      UpdateInfoLabel("BUY注文失敗 Error=" + IntegerToString(GetLastError()));
}

//+------------------------------------------------------------------+
//| 売り注文を送信する関数                                             |
//+------------------------------------------------------------------+
void ExecuteSell()
{
   double sl = 0, tp = 0;
   double price = Bid;
   
   if(StopLoss > 0)
      sl = price + StopLoss * _Point;
   if(TakeProfit > 0)
      tp = price - TakeProfit * _Point;
   
   int ticket = OrderSend(Symbol(), OP_SELL, LotSize, price, Slippage, sl, tp,
                           "Panel Sell", 0, 0, clrRed);
   
   if(ticket > 0)
      UpdateInfoLabel("SELL注文成功 Ticket#" + IntegerToString(ticket));
   else
      UpdateInfoLabel("SELL注文失敗 Error=" + IntegerToString(GetLastError()));
}

//+------------------------------------------------------------------+
//| 全ポジションを決済する関数                                         |
//+------------------------------------------------------------------+
void CloseAllPositions()
{
   int closedCount = 0;
   
   // 全注文を逆順にループして決済
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
         continue;
      
      // 現在のシンボルのポジションのみ対象
      if(OrderSymbol() != Symbol())
         continue;
      
      bool result = false;
      
      if(OrderType() == OP_BUY)
         result = OrderClose(OrderTicket(), OrderLots(), Bid, Slippage, clrBlue);
      else if(OrderType() == OP_SELL)
         result = OrderClose(OrderTicket(), OrderLots(), Ask, Slippage, clrRed);
      
      if(result)
         closedCount++;
   }
   
   UpdateInfoLabel(IntegerToString(closedCount) + "件のポジションを決済しました");
}

//+------------------------------------------------------------------+
//| チャートイベント処理関数                                           |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
   // オブジェクトクリックイベントのみ処理
   if(id == CHARTEVENT_OBJECT_CLICK)
   {
      // クリックされたオブジェクト名で分岐
      if(sparam == BTN_BUY)
      {
         ExecuteBuy();
         // ボタンの押下状態を解除（元に戻す）
         ObjectSetInteger(0, BTN_BUY, OBJPROP_STATE, false);
      }
      else if(sparam == BTN_SELL)
      {
         ExecuteSell();
         ObjectSetInteger(0, BTN_SELL, OBJPROP_STATE, false);
      }
      else if(sparam == BTN_CLOSE)
      {
         CloseAllPositions();
         ObjectSetInteger(0, BTN_CLOSE, OBJPROP_STATE, false);
      }
      
      ChartRedraw(0);
   }
}
</code></pre>
<p>このサンプルのポイントは、ボタンクリック後に<code>ObjectSetInteger</code>で<code>OBJPROP_STATE</code>をfalseに戻している点です。これを行わないと、ボタンが押されたままの状態で表示され続けます。</p>
<h2><span id="toc8">プログラム例3：キーボードショートカットで機能を切り替えるインジケーター</span></h2>
<p>キーボード入力（CHARTEVENT_KEYDOWN）を使って、インジケーターの表示モードをキーボードショートカットで切り替えるサンプルです。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| キーボードショートカットで表示を切り替えるインジケーター              |
//+------------------------------------------------------------------+
#property strict
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_color1 clrDodgerBlue
#property indicator_color2 clrOrangeRed
#property indicator_width1 2
#property indicator_width2 2

// インジケーターバッファ
double FastMABuffer[];   // 短期移動平均
double SlowMABuffer[];   // 長期移動平均

// 表示モード管理
int    DisplayMode = 0;     // 0=両方表示, 1=短期のみ, 2=長期のみ
string ModeName[]  = {"両方表示", "短期MAのみ", "長期MAのみ"};

// 移動平均のパラメータ
input int FastPeriod = 20;   // 短期MA期間
input int SlowPeriod = 50;   // 長期MA期間

// ステータス表示用ラベル名
#define STATUS_LABEL "MA_StatusLabel"

//+------------------------------------------------------------------+
//| ステータスラベルの作成・更新                                        |
//+------------------------------------------------------------------+
void UpdateStatusLabel()
{
   // ラベルがなければ作成
   if(ObjectFind(0, STATUS_LABEL) < 0)
   {
      ObjectCreate(0, STATUS_LABEL, OBJ_LABEL, 0, 0, 0);
      ObjectSetInteger(0, STATUS_LABEL, OBJPROP_CORNER, CORNER_RIGHT_UPPER);
      ObjectSetInteger(0, STATUS_LABEL, OBJPROP_XDISTANCE, 15);
      ObjectSetInteger(0, STATUS_LABEL, OBJPROP_YDISTANCE, 30);
      ObjectSetInteger(0, STATUS_LABEL, OBJPROP_COLOR, clrYellow);
      ObjectSetInteger(0, STATUS_LABEL, OBJPROP_FONTSIZE, 10);
      ObjectSetString(0, STATUS_LABEL, OBJPROP_FONT, "Arial Bold");
      ObjectSetInteger(0, STATUS_LABEL, OBJPROP_ANCHOR, ANCHOR_RIGHT_UPPER);
   }
   
   // 現在のモード名を表示
   string text = "MA表示: " + ModeName[DisplayMode] + " [M:切替 / H:ヘルプ]";
   ObjectSetString(0, STATUS_LABEL, OBJPROP_TEXT, text);
   ChartRedraw(0);
}

//+------------------------------------------------------------------+
//| 表示モードに応じてバッファの表示/非表示を切り替える                   |
//+------------------------------------------------------------------+
void ApplyDisplayMode()
{
   switch(DisplayMode)
   {
      case 0: // 両方表示
         SetIndexStyle(0, DRAW_LINE, STYLE_SOLID, 2, clrDodgerBlue);
         SetIndexStyle(1, DRAW_LINE, STYLE_SOLID, 2, clrOrangeRed);
         break;
         
      case 1: // 短期MAのみ
         SetIndexStyle(0, DRAW_LINE, STYLE_SOLID, 2, clrDodgerBlue);
         SetIndexStyle(1, DRAW_NONE);  // 長期MAを非表示
         break;
         
      case 2: // 長期MAのみ
         SetIndexStyle(0, DRAW_NONE);  // 短期MAを非表示
         SetIndexStyle(1, DRAW_LINE, STYLE_SOLID, 2, clrOrangeRed);
         break;
   }
   
   UpdateStatusLabel();
}

//+------------------------------------------------------------------+
//| 初期化関数                                                        |
//+------------------------------------------------------------------+
int OnInit()
{
   // バッファの設定
   SetIndexBuffer(0, FastMABuffer);
   SetIndexBuffer(1, SlowMABuffer);
   SetIndexLabel(0, "Fast MA(" + IntegerToString(FastPeriod) + ")");
   SetIndexLabel(1, "Slow MA(" + IntegerToString(SlowPeriod) + ")");
   
   // 初期表示モードを適用
   ApplyDisplayMode();
   
   Print("=== MA表示切替インジケーター ===");
   Print("Mキー: 表示モード切替");
   Print("Hキー: ヘルプ表示");
   
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| 終了処理関数                                                      |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   ObjectDelete(0, STATUS_LABEL);
}

//+------------------------------------------------------------------+
//| メイン計算関数                                                    |
//+------------------------------------------------------------------+
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[])
{
   // 計算開始位置を決定
   int limit = rates_total - prev_calculated;
   if(prev_calculated == 0)
      limit = rates_total - SlowPeriod - 1;
   
   // 移動平均を計算
   for(int i = limit; i >= 0; i--)
   {
      FastMABuffer[i] = iMA(NULL, 0, FastPeriod, 0, MODE_EMA, PRICE_CLOSE, i);
      SlowMABuffer[i] = iMA(NULL, 0, SlowPeriod, 0, MODE_EMA, PRICE_CLOSE, i);
   }
   
   return(rates_total);
}

//+------------------------------------------------------------------+
//| チャートイベント処理関数                                           |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
   // キーダウンイベントのみ処理
   if(id != CHARTEVENT_KEYDOWN)
      return;
   
   // lparamにキーコードが入る
   switch((int)lparam)
   {
      case 'M':  // Mキー：表示モードを切り替え
         DisplayMode = (DisplayMode + 1) % 3;  // 0→1→2→0とループ
         ApplyDisplayMode();
         Print("表示モードを変更: ", ModeName[DisplayMode]);
         break;
         
      case 'H':  // Hキー：ヘルプを表示
         Print("=== ヘルプ ===");
         Print("M: 表示モード切替（両方→短期のみ→長期のみ）");
         Print("H: このヘルプを表示");
         Print("現在のモード: ", ModeName[DisplayMode]);
         break;
   }
}
</code></pre>
<p>CHARTEVENT_KEYDOWNイベントでは、<code>lparam</code>に押されたキーの仮想キーコードが格納されます。英字キーの場合は大文字のASCIIコード（'A'〜'Z'）で判定できます。このサンプルではMキーで表示モードを順番に切り替え、Hキーでヘルプ情報を出力しています。</p>
<h2><span id="toc9">プログラム例4：マウス位置のクロスヘア情報を表示するインジケーター</span></h2>
<p>CHARTEVENT_MOUSE_MOVEを使って、マウスカーソルの位置に対応する価格と時刻をリアルタイムに表示するインジケーターです。マウスイベントの活用方法を理解できます。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| マウス位置のクロスヘア情報表示インジケーター                         |
//+------------------------------------------------------------------+
#property strict
#property indicator_chart_window
#property indicator_buffers 0

// オブジェクト名定数
#define LABEL_PRICE  "CrossHair_Price"
#define LABEL_TIME   "CrossHair_Time"
#define LABEL_BAR    "CrossHair_Bar"
#define LINE_H       "CrossHair_HLine"
#define LINE_V       "CrossHair_VLine"

// 表示ON/OFF
bool ShowCrossHair = true;

//+------------------------------------------------------------------+
//| ラベルを作成する関数                                               |
//+------------------------------------------------------------------+
void CreateLabel(string name, int x, int y, color clr)
{
   if(ObjectFind(0, name) < 0)
   {
      ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
      ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_LOWER);
      ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
      ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
      ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
      ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 10);
      ObjectSetString(0, name, OBJPROP_FONT, "Consolas");
      ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER);
   }
}

//+------------------------------------------------------------------+
//| 全オブジェクトを削除する関数                                       |
//+------------------------------------------------------------------+
void DeleteAllObjects()
{
   ObjectDelete(0, LABEL_PRICE);
   ObjectDelete(0, LABEL_TIME);
   ObjectDelete(0, LABEL_BAR);
   ObjectDelete(0, LINE_H);
   ObjectDelete(0, LINE_V);
}

//+------------------------------------------------------------------+
//| 初期化関数                                                        |
//+------------------------------------------------------------------+
int OnInit()
{
   // マウス移動イベントを有効にする
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
   
   // 情報ラベルを作成
   CreateLabel(LABEL_PRICE, 20, 60, clrLime);
   CreateLabel(LABEL_TIME,  20, 40, clrAqua);
   CreateLabel(LABEL_BAR,   20, 20, clrYellow);
   
   Print("=== クロスヘア情報インジケーター ===");
   Print("Sキーで表示ON/OFF切替");
   
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| 終了処理関数                                                      |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // マウス移動イベントを無効に戻す
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
   DeleteAllObjects();
}

//+------------------------------------------------------------------+
//| メイン計算関数                                                    |
//+------------------------------------------------------------------+
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[])
{
   return(rates_total);
}

//+------------------------------------------------------------------+
//| チャートイベント処理関数                                           |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &#038;lparam,
                  const double &#038;dparam,
                  const string &#038;sparam)
{
   // キーダウンイベント：Sキーで表示切替
   if(id == CHARTEVENT_KEYDOWN)
   {
      if((int)lparam == 'S')
      {
         ShowCrossHair = !ShowCrossHair;
         if(!ShowCrossHair)
         {
            // 非表示時はラベルをクリア
            ObjectSetString(0, LABEL_PRICE, OBJPROP_TEXT, "");
            ObjectSetString(0, LABEL_TIME,  OBJPROP_TEXT, "");
            ObjectSetString(0, LABEL_BAR,   OBJPROP_TEXT, "");
            ObjectDelete(0, LINE_H);
            ObjectDelete(0, LINE_V);
            ChartRedraw(0);
         }
         Print("クロスヘア表示: ", ShowCrossHair ? "ON" : "OFF");
      }
      return;
   }
   
   // マウス移動イベント
   if(id == CHARTEVENT_MOUSE_MOVE &#038;&#038; ShowCrossHair)
   {
      // ピクセル座標を取得
      int mouseX = (int)lparam;
      int mouseY = (int)dparam;
      
      // ピクセル座標をチャートの時間・価格に変換
      int      subWindow = 0;
      datetime mouseTime = 0;
      double   mousePrice = 0;
      
      // ChartXYToTimePrice関数でピクセル座標を価格・時刻に変換
      if(ChartXYToTimePrice(0, mouseX, mouseY, subWindow, mouseTime, mousePrice))
      {
         // メインウィンドウ上のみ処理
         if(subWindow == 0)
         {
            // バーインデックスを取得
            int barIndex = iBarShift(NULL, 0, mouseTime);
            
            // ラベルに情報を表示
            ObjectSetString(0, LABEL_PRICE, OBJPROP_TEXT,
                          "価格: " + DoubleToString(mousePrice, _Digits));
            ObjectSetString(0, LABEL_TIME, OBJPROP_TEXT,
                          "時刻: " + TimeToString(mouseTime, TIME_DATE | TIME_MINUTES));
            ObjectSetString(0, LABEL_BAR, OBJPROP_TEXT,
                          "バー: " + IntegerToString(barIndex) +
                          " | O:" + DoubleToString(Open[barIndex], _Digits) +
                          " H:" + DoubleToString(High[barIndex], _Digits) +
                          " L:" + DoubleToString(Low[barIndex], _Digits) +
                          " C:" + DoubleToString(Close[barIndex], _Digits));
            
            // 水平線を描画
            if(ObjectFind(0, LINE_H) < 0)
            {
               ObjectCreate(0, LINE_H, OBJ_HLINE, 0, 0, mousePrice);
               ObjectSetInteger(0, LINE_H, OBJPROP_COLOR, clrGray);
               ObjectSetInteger(0, LINE_H, OBJPROP_STYLE, STYLE_DOT);
               ObjectSetInteger(0, LINE_H, OBJPROP_SELECTABLE, false);
            }
            else
            {
               ObjectSetDouble(0, LINE_H, OBJPROP_PRICE, mousePrice);
            }
            
            // 垂直線を描画
            if(ObjectFind(0, LINE_V) < 0)
            {
               ObjectCreate(0, LINE_V, OBJ_VLINE, 0, mouseTime, 0);
               ObjectSetInteger(0, LINE_V, OBJPROP_COLOR, clrGray);
               ObjectSetInteger(0, LINE_V, OBJPROP_STYLE, STYLE_DOT);
               ObjectSetInteger(0, LINE_V, OBJPROP_SELECTABLE, false);
            }
            else
            {
               ObjectSetInteger(0, LINE_V, OBJPROP_TIME, mouseTime);
            }
            
            ChartRedraw(0);
         }
      }
   }
}
</code></pre>
<p>マウス移動イベントを使用するには、<code>ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true)</code>で明示的に有効化する必要があります。<code>ChartXYToTimePrice</code>関数を使うことで、ピクセル座標をチャート上の時刻と価格に変換できます。このサンプルでは変換した情報をラベルに表示し、クロスヘア線も描画しています。</p>
<h2><span id="toc10">OnChartEvent関数を使う際の注意点</span></h2>
<h3><span id="toc11">イベントの有効化が必要な場合がある</span></h3>
<p>一部のイベントはデフォルトでは無効になっており、<code>ChartSetInteger</code>で明示的に有効化する必要があります。</p>
<table>
<thead>
<tr>
<th>イベント</th>
<th>有効化に必要な設定</th>
</tr>
</thead>
<tbody>
<tr>
<td>CHARTEVENT_MOUSE_MOVE</td>
<td><code>ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true)</code></td>
</tr>
<tr>
<td>CHARTEVENT_OBJECT_CREATE</td>
<td><code>ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, true)</code></td>
</tr>
<tr>
<td>CHARTEVENT_OBJECT_DELETE</td>
<td><code>ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true)</code></td>
</tr>
</tbody>
</table>
<h3><span id="toc12">スクリプトでは使用できない</span></h3>
<p>OnChartEvent関数はEA（エキスパートアドバイザー）とインジケーターでのみ使用できます。スクリプトではイベントハンドラが呼び出されません。</p>
<h3><span id="toc13">重い処理を避ける</span></h3>
<p>特にCHARTEVENT_MOUSE_MOVEはマウスを動かすたびに高頻度で発生するため、イベントハンドラ内に重い処理を記述するとチャートの動作が遅くなります。必要最小限の処理にとどめるか、一定間隔でのみ処理を実行するようにしましょう。</p>
<h3><span id="toc14">ボタンの状態リセット</span></h3>
<p>OBJ_BUTTONオブジェクトをクリックした際は、処理後に<code>OBJPROP_STATE</code>をfalseに設定してボタンの押下状態をリセットする必要があります。これを忘れると、次回クリック時にイベントが正しく発生しない場合があります。</p>
<h3><span id="toc15">同一チャートでの制約</span></h3>
<p>一つのチャートに複数のEAやインジケーターがアタッチされている場合、それぞれのOnChartEvent関数が同じイベントを受け取ります。オブジェクト名の命名規則に注意し、自分が作成したオブジェクトのみを処理するようにフィルタリングすることが重要です。</p>
<p>投稿 <a href="https://mql-programing.com/archives/3451/onchartevent/">【MQL4関数】OnChartEvent関数とは？チャート操作を行なったときに実行されるイベント関数</a> は <a href="https://mql-programing.com">自動売買を作ろう！</a> に最初に表示されました。</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>【MQL4関数】OnTester関数とは？バックテスト終了時に実行されるイベント関数</title>
		<link>https://mql-programing.com/archives/3050/ontester/</link>
		
		<dc:creator><![CDATA[朝日奈りさ]]></dc:creator>
		<pubDate>Tue, 17 May 2022 08:02:17 +0000</pubDate>
				<category><![CDATA[関数]]></category>
		<category><![CDATA[【中級編】MQLプログラムの読み方・書き方]]></category>
		<category><![CDATA[【辞書】MQLリファレンス]]></category>
		<category><![CDATA[その他]]></category>
		<category><![CDATA[リファレンス]]></category>
		<category><![CDATA[自動売買]]></category>
		<category><![CDATA[イベント関数]]></category>
		<category><![CDATA[OnTester]]></category>
		<category><![CDATA[バックテスト]]></category>
		<category><![CDATA[シャープレシオ]]></category>
		<category><![CDATA[MQL]]></category>
		<guid isPermaLink="false">https://mql-programing.com/?p=3050</guid>

					<description><![CDATA[<p>OnTester関数の基本 OnTester()は、ストラテジーテスター（バックテスト）の終了時に自動的に呼び出されるイベント関数です。バックテスト完了後に独自の評価指標（カスタム最適化基準）を計算し、その結果を最適化プ [&#8230;]</p>
<p>投稿 <a href="https://mql-programing.com/archives/3050/ontester/">【MQL4関数】OnTester関数とは？バックテスト終了時に実行されるイベント関数</a> は <a href="https://mql-programing.com">自動売買を作ろう！</a> に最初に表示されました。</p>
]]></description>
										<content:encoded><![CDATA[<h2><span id="toc1">OnTester関数の基本</span></h2>
<p>OnTester()は、<strong>ストラテジーテスター（バックテスト）の終了時に自動的に呼び出されるイベント関数</strong>です。バックテスト完了後に独自の評価指標（カスタム最適化基準）を計算し、その結果を最適化プロセスに反映させることができます。</p>
<p>通常のバックテストでは、MetaTraderが用意するプロフィットファクターやシャープレシオなどの標準指標しか最適化基準に使えませんが、OnTester()を実装することで<strong>自分だけのオリジナル評価指標</strong>を最適化基準として使用できるようになります。</p>
<h2><span id="toc2">書式・引数・戻り値</span></h2>
<pre><code class="language-mql4">double OnTester(void);
</code></pre>
<h3><span id="toc3">引数</span></h3>
<p>引数はありません（void）。</p>
<h3><span id="toc4">戻り値</span></h3>
<table>
<thead>
<tr>
<th>型</th>
<th>説明</th>
</tr>
</thead>
<tbody>
<tr>
<td>double</td>
<td>カスタム最適化基準として使用される値。ストラテジーテスターの最適化時に「Custom max」を選択すると、この戻り値が最大化される方向で最適化が行われます。</td>
</tr>
</tbody>
</table>
<h3><span id="toc5">基本的な使い方</span></h3>
<p>OnTester()はEA（エキスパートアドバイザー）内に記述します。ストラテジーテスターでバックテストが完了した直後、OnDeinit()が呼ばれる前に実行されます。</p>
<pre><code class="language-mql4">// OnTester()の基本構造
double OnTester()
{
   // バックテスト結果を評価する処理
   double customCriterion = 0.0;
   
   // 何らかの計算を行う
   customCriterion = 計算結果;
   
   // 戻り値が「Custom max」最適化基準として使われる
   return(customCriterion);
}
</code></pre>
<h2><span id="toc6">呼び出しタイミング</span></h2>
<p>OnTester()が呼び出されるタイミングは以下の通りです。</p>
<ul>
<li><strong>バックテスト終了後</strong>、最後のティック処理が完了した直後</li>
<li><strong>OnDeinit()の前</strong>に呼ばれる</li>
<li><strong>ストラテジーテスター内でのみ</strong>動作する（ライブ取引では呼ばれない）</li>
<li>最適化実行時は<strong>各パス（パラメータの組み合わせ）ごと</strong>に呼ばれる</li>
</ul>
<p>呼び出し順序をまとめると以下のようになります。</p>
<pre><code class="language-mql4">// イベント関数の呼び出し順序（ストラテジーテスター）
// 1. OnInit()          ← テスト開始時
// 2. OnTick()          ← 各ティックごと（繰り返し）
// 3. OnTester()        ← テスト終了後
// 4. OnDeinit()        ← EA解放時
</code></pre>
<h2><span id="toc7">プログラム例1：基本的な損益ベースの評価指標</span></h2>
<p>最もシンプルな例として、<strong>総利益と総損失の比率から独自のスコアを算出</strong>するOnTester()を実装します。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| プログラム例1：基本的な損益評価指標                                  |
//| 総利益÷総損失（プロフィットファクター的な指標）を返す                    |
//+------------------------------------------------------------------+
#property strict

// EA本体のパラメータ（例）
input int    MAPeriod   = 20;     // 移動平均期間
input double LotSize    = 0.1;    // ロットサイズ
input int    TakeProfit = 100;    // 利確（ポイント）
input int    StopLoss   = 50;     // 損切り（ポイント）

//+------------------------------------------------------------------+
//| 初期化関数                                                         |
//+------------------------------------------------------------------+
int OnInit()
{
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| ティック処理（簡易的なMA戦略の例）                                   |
//+------------------------------------------------------------------+
void OnTick()
{
   // 既存ポジションがあれば何もしない
   if(OrdersTotal() > 0) return;
   
   double ma = iMA(NULL, 0, MAPeriod, 0, MODE_SMA, PRICE_CLOSE, 1);
   double prevClose = iClose(NULL, 0, 1);
   double prevClose2 = iClose(NULL, 0, 2);
   
   // 価格がMAを上抜けたら買い
   if(prevClose2 < ma &#038;&#038; prevClose > ma)
   {
      OrderSend(Symbol(), OP_BUY, LotSize, Ask, 3,
                Ask - StopLoss * Point, Ask + TakeProfit * Point,
                "MA Cross Buy", 0, 0, clrBlue);
   }
   // 価格がMAを下抜けたら売り
   else if(prevClose2 > ma && prevClose < ma)
   {
      OrderSend(Symbol(), OP_SELL, LotSize, Bid, 3,
                Bid + StopLoss * Point, Bid - TakeProfit * Point,
                "MA Cross Sell", 0, 0, clrRed);
   }
}

//+------------------------------------------------------------------+
//| バックテスト終了時に呼ばれるイベント関数                              |
//+------------------------------------------------------------------+
double OnTester()
{
   // TesterStatistics()でバックテスト結果の統計を取得
   double totalProfit = TesterStatistics(STAT_GROSS_PROFIT);  // 総利益
   double totalLoss   = TesterStatistics(STAT_GROSS_LOSS);    // 総損失（負の値）
   double totalTrades = TesterStatistics(STAT_TRADES);        // 総取引回数
   
   // 取引が一定回数以上ない場合は評価しない
   if(totalTrades < 10)
   {
      Print("取引回数が少なすぎます: ", totalTrades, " 回");
      return(0.0);
   }
   
   // 総損失が0の場合（全勝の場合）の処理
   if(totalLoss == 0.0)
   {
      Print("全勝！ 総利益: ", totalProfit);
      return(totalProfit);
   }
   
   // プロフィットファクター（総利益÷総損失の絶対値）を計算
   double profitFactor = totalProfit / MathAbs(totalLoss);
   
   Print("カスタム評価値（PF）: ", DoubleToStr(profitFactor, 2),
         " | 取引回数: ", totalTrades,
         " | 総利益: ", DoubleToStr(totalProfit, 2),
         " | 総損失: ", DoubleToStr(totalLoss, 2));
   
   // この値が「Custom max」最適化基準として使用される
   return(profitFactor);
}

//+------------------------------------------------------------------+
//| 終了処理                                                           |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   Print("EA終了 - 理由コード: ", reason);
}
</code></pre>
<p>この例では<code>TesterStatistics()</code>関数を使ってバックテスト結果の統計データを取得しています。取引回数が少なすぎる場合は0を返すことで、信頼性の低い結果を排除しています。</p>
<h2><span id="toc8">プログラム例2：リカバリーファクターによる評価</span></h2>
<p><strong>リカバリーファクター（純利益÷最大ドローダウン）</strong>は、リスクに対してどれだけ効率的に利益を上げているかを示す重要な指標です。この値をカスタム最適化基準として使います。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| プログラム例2：リカバリーファクターで最適化                            |
//| 純利益÷最大ドローダウンを評価指標として返す                           |
//+------------------------------------------------------------------+
#property strict

input int    FastMA = 10;   // 短期MA期間
input int    SlowMA = 30;   // 長期MA期間
input double Lots   = 0.1;  // ロットサイズ

//+------------------------------------------------------------------+
//| 初期化関数                                                         |
//+------------------------------------------------------------------+
int OnInit()
{
   // 短期MAが長期MAより大きくならないようにバリデーション
   if(FastMA >= SlowMA)
   {
      Print("FastMAはSlowMAより小さくしてください");
      return(INIT_PARAMETERS_INCORRECT);
   }
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| ティック処理（ゴールデンクロス・デッドクロス戦略）                     |
//+------------------------------------------------------------------+
void OnTick()
{
   if(OrdersTotal() > 0) return;
   
   double fastCurr = iMA(NULL, 0, FastMA, 0, MODE_EMA, PRICE_CLOSE, 1);
   double fastPrev = iMA(NULL, 0, FastMA, 0, MODE_EMA, PRICE_CLOSE, 2);
   double slowCurr = iMA(NULL, 0, SlowMA, 0, MODE_EMA, PRICE_CLOSE, 1);
   double slowPrev = iMA(NULL, 0, SlowMA, 0, MODE_EMA, PRICE_CLOSE, 2);
   
   // ゴールデンクロス→買い
   if(fastPrev <= slowPrev &#038;&#038; fastCurr > slowCurr)
   {
      OrderSend(Symbol(), OP_BUY, Lots, Ask, 3, 0, 0, "GC Buy", 12345, 0, clrBlue);
   }
   // デッドクロス→売り
   else if(fastPrev >= slowPrev && fastCurr < slowCurr)
   {
      OrderSend(Symbol(), OP_SELL, Lots, Bid, 3, 0, 0, "DC Sell", 12345, 0, clrRed);
   }
}

//+------------------------------------------------------------------+
//| バックテスト終了時：リカバリーファクターを計算して返す                  |
//+------------------------------------------------------------------+
double OnTester()
{
   // 各種統計値を取得
   double netProfit      = TesterStatistics(STAT_PROFIT);              // 純利益
   double maxDrawdown    = TesterStatistics(STAT_EQUITY_DD);           // 最大ドローダウン（金額）
   double totalTrades    = TesterStatistics(STAT_TRADES);              // 総取引回数
   double profitTrades   = TesterStatistics(STAT_PROFIT_TRADES);       // 勝ちトレード数
   double expectedPayoff = TesterStatistics(STAT_EXPECTED_PAYOFF);     // 期待利得
   
   // ログに詳細を出力
   Print("=== バックテスト結果サマリー ===");
   Print("純利益: ", DoubleToStr(netProfit, 2));
   Print("最大DD: ", DoubleToStr(maxDrawdown, 2));
   Print("取引数: ", (int)totalTrades);
   Print("勝率:   ", DoubleToStr(profitTrades / MathMax(totalTrades, 1) * 100, 1), "%");
   Print("期待利得: ", DoubleToStr(expectedPayoff, 2));
   
   // 最低取引回数のフィルター
   if(totalTrades < 30)
   {
      Print("取引回数不足（30回未満）→ 評価値0");
      return(0.0);
   }
   
   // 最大ドローダウンが0または利益がマイナスの場合
   if(maxDrawdown <= 0.0 || netProfit <= 0.0)
   {
      Print("利益なしまたはDD計算不可 → 評価値0");
      return(0.0);
   }
   
   // リカバリーファクター = 純利益 ÷ 最大ドローダウン
   double recoveryFactor = netProfit / maxDrawdown;
   
   Print("リカバリーファクター: ", DoubleToStr(recoveryFactor, 3));
   
   return(recoveryFactor);
}

//+------------------------------------------------------------------+
//| 終了処理                                                           |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
}
</code></pre>
<p>リカバリーファクターが高いほど、少ないリスク（ドローダウン）で大きな利益を上げていることを意味します。最適化時に「Custom max」を選択すれば、この値が最大化されるパラメータの組み合わせが探索されます。</p>
<h2><span id="toc9">プログラム例3：複合スコアによる多角的評価</span></h2>
<p>実際のトレーディングでは、単一の指標だけでなく<strong>複数の指標を組み合わせた総合スコア</strong>で評価するのが効果的です。この例では、プロフィットファクター・勝率・取引回数・ドローダウン率を組み合わせた複合スコアを計算します。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| プログラム例3：複合スコアによる多角的評価                              |
//| 複数の評価指標を重み付けして総合スコアを算出                           |
//+------------------------------------------------------------------+
#property strict

input int    RSIPeriod  = 14;    // RSI期間
input int    RSIBuyLv   = 30;    // RSI買いレベル
input int    RSISellLv  = 70;    // RSI売りレベル
input double Lots       = 0.1;   // ロットサイズ
input int    SL_Points  = 200;   // 損切り幅（ポイント）
input int    TP_Points  = 300;   // 利確幅（ポイント）

//+------------------------------------------------------------------+
//| 初期化                                                             |
//+------------------------------------------------------------------+
int OnInit()
{
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| ティック処理（RSI戦略の例）                                         |
//+------------------------------------------------------------------+
void OnTick()
{
   // 既にポジションがあれば何もしない
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(OrderSelect(i, SELECT_BY_POS) && OrderSymbol() == Symbol())
         return;
   }
   
   double rsiCurr = iRSI(NULL, 0, RSIPeriod, PRICE_CLOSE, 1);
   double rsiPrev = iRSI(NULL, 0, RSIPeriod, PRICE_CLOSE, 2);
   
   // RSIが売られすぎ水準から回復→買い
   if(rsiPrev < RSIBuyLv &#038;&#038; rsiCurr >= RSIBuyLv)
   {
      double sl = Ask - SL_Points * Point;
      double tp = Ask + TP_Points * Point;
      OrderSend(Symbol(), OP_BUY, Lots, Ask, 3, sl, tp, "RSI Buy", 0, 0, clrBlue);
   }
   // RSIが買われすぎ水準から下落→売り
   else if(rsiPrev > RSISellLv && rsiCurr <= RSISellLv)
   {
      double sl = Bid + SL_Points * Point;
      double tp = Bid - TP_Points * Point;
      OrderSend(Symbol(), OP_SELL, Lots, Bid, 3, sl, tp, "RSI Sell", 0, 0, clrRed);
   }
}

//+------------------------------------------------------------------+
//| 複合スコアを計算する関数                                            |
//+------------------------------------------------------------------+
double CalculateCompositeScore()
{
   // ---- 統計データの取得 ----
   double netProfit     = TesterStatistics(STAT_PROFIT);
   double grossProfit   = TesterStatistics(STAT_GROSS_PROFIT);
   double grossLoss     = TesterStatistics(STAT_GROSS_LOSS);     // 負の値
   double totalTrades   = TesterStatistics(STAT_TRADES);
   double profitTrades  = TesterStatistics(STAT_PROFIT_TRADES);
   double maxDDPercent  = TesterStatistics(STAT_EQUITY_DDREL_PERCENT); // 最大DD（%）
   double maxDD         = TesterStatistics(STAT_EQUITY_DD);
   double sharpRatio    = TesterStatistics(STAT_SHARPE_RATIO);
   
   // ---- フィルター条件 ----
   // 取引回数が少なすぎる結果を除外
   if(totalTrades < 20)
   {
      Print("フィルター: 取引回数不足 (", (int)totalTrades, " < 20)");
      return(-1000.0);  // 大きな負の値で排除
   }
   
   // 純利益がマイナスの結果を除外
   if(netProfit <= 0)
   {
      Print("フィルター: 純利益がマイナス (", DoubleToStr(netProfit, 2), ")");
      return(-500.0);
   }
   
   // 最大ドローダウンが大きすぎる結果を除外（30%超）
   if(maxDDPercent > 30.0)
   {
      Print("フィルター: DD過大 (", DoubleToStr(maxDDPercent, 1), "% > 30%)");
      return(-100.0);
   }
   
   // ---- 各指標のスコア計算 ----
   
   // 1. プロフィットファクター（0〜10にスケーリング）
   double pf = 0.0;
   if(MathAbs(grossLoss) > 0)
      pf = grossProfit / MathAbs(grossLoss);
   double pfScore = MathMin(pf, 5.0) * 2.0;  // PF=5以上は全て10点
   
   // 2. 勝率スコア（0〜10にスケーリング）
   double winRate = profitTrades / totalTrades * 100.0;
   double winRateScore = MathMin(winRate, 100.0) / 10.0;  // 勝率100%で10点
   
   // 3. 取引回数スコア（多いほど信頼性が高い）
   // 100回以上で満点（10点）
   double tradeCountScore = MathMin(totalTrades / 100.0, 1.0) * 10.0;
   
   // 4. ドローダウン抑制スコア（DDが小さいほど高評価）
   // DD 0%→10点、DD 30%→0点
   double ddScore = MathMax(0.0, (30.0 - maxDDPercent) / 30.0 * 10.0);
   
   // ---- 重み付けによる総合スコア ----
   double weightPF       = 0.35;  // プロフィットファクター重視
   double weightWinRate  = 0.20;  // 勝率
   double weightTrades   = 0.15;  // 取引回数（信頼性）
   double weightDD       = 0.30;  // ドローダウン抑制
   
   double compositeScore = pfScore       * weightPF
                         + winRateScore  * weightWinRate
                         + tradeCountScore * weightTrades
                         + ddScore       * weightDD;
   
   // ---- 結果のログ出力 ----
   Print("=== 複合スコア詳細 ===");
   Print("  PFスコア:     ", DoubleToStr(pfScore, 2), " (PF=", DoubleToStr(pf, 2), ")");
   Print("  勝率スコア:   ", DoubleToStr(winRateScore, 2), " (勝率=", DoubleToStr(winRate, 1), "%)");
   Print("  取引数スコア: ", DoubleToStr(tradeCountScore, 2), " (取引数=", (int)totalTrades, ")");
   Print("  DDスコア:     ", DoubleToStr(ddScore, 2), " (DD=", DoubleToStr(maxDDPercent, 1), "%)");
   Print("  ★総合スコア:  ", DoubleToStr(compositeScore, 3));
   
   return(compositeScore);
}

//+------------------------------------------------------------------+
//| バックテスト終了時のイベント                                         |
//+------------------------------------------------------------------+
double OnTester()
{
   return(CalculateCompositeScore());
}

//+------------------------------------------------------------------+
//| 終了処理                                                           |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
}
</code></pre>
<p>この例のポイントは以下の通りです。</p>
<ul>
<li><strong>フィルター機能</strong>：取引回数が少ない、利益がマイナス、ドローダウンが大きすぎる結果を大きな負の値で排除</li>
<li><strong>スケーリング</strong>：各指標を0〜10のスコアに正規化してから合算</li>
<li><strong>重み付け</strong>：プロフィットファクターとドローダウンを重視した設計</li>
</ul>
<h2><span id="toc10">プログラム例4：取引履歴を直接分析してカスタム指標を算出</span></h2>
<p>TesterStatistics()だけでなく、<strong>決済済み注文の履歴を直接走査</strong>して、連続損失回数や最大連敗額など、より細かな分析を行う例です。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| プログラム例4：取引履歴を直接分析する高度な評価                        |
//| 連続損失・利益の安定性を評価指標に組み込む                             |
//+------------------------------------------------------------------+
#property strict

input int    Period1    = 12;
input int    Period2    = 26;
input double LotSize   = 0.1;

//+------------------------------------------------------------------+
//| 初期化                                                             |
//+------------------------------------------------------------------+
int OnInit()
{
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| ティック処理（省略：任意の売買ロジックを実装）                         |
//+------------------------------------------------------------------+
void OnTick()
{
   // ここには任意のトレードロジックを実装
   // （本例ではOnTester()の解説に焦点を当てるため省略）
}

//+------------------------------------------------------------------+
//| バックテスト終了時：取引履歴を直接分析                                |
//+------------------------------------------------------------------+
double OnTester()
{
   // 決済済み注文の履歴を分析
   int totalOrders = OrdersHistoryTotal();
   
   if(totalOrders == 0)
   {
      Print("取引履歴なし");
      return(0.0);
   }
   
   // ---- 分析用変数の初期化 ----
   double profits[];          // 各取引の損益を格納する配列
   int    tradeCount = 0;     // 有効な取引数
   int    maxConsecLoss = 0;  // 最大連敗数
   int    currentConsecLoss = 0;  // 現在の連敗カウント
   double maxConsecLossAmount = 0.0;  // 最大連敗時の損失額
   double currentConsecLossAmount = 0.0;
   double totalProfit = 0.0;  // 総利益
   
   // 配列サイズを事前確保
   ArrayResize(profits, totalOrders);
   
   // ---- 取引履歴をスキャンして損益データを収集 ----
   for(int i = 0; i < totalOrders; i++)
   {
      if(!OrderSelect(i, SELECT_BY_POS, MODE_HISTORY))
         continue;
      
      // 対象シンボルの決済注文のみ処理
      if(OrderSymbol() != Symbol())
         continue;
      
      // OP_BUYまたはOP_SELLのみ（入金・出金等を除外）
      int type = OrderType();
      if(type != OP_BUY &#038;&#038; type != OP_SELL)
         continue;
      
      // 損益（手数料・スワップ込み）
      double orderProfit = OrderProfit() + OrderCommission() + OrderSwap();
      profits[tradeCount] = orderProfit;
      totalProfit += orderProfit;
      tradeCount++;
      
      // 連敗トラッキング
      if(orderProfit < 0)
      {
         currentConsecLoss++;
         currentConsecLossAmount += MathAbs(orderProfit);
         
         if(currentConsecLoss > maxConsecLoss)
         {
            maxConsecLoss = currentConsecLoss;
            maxConsecLossAmount = currentConsecLossAmount;
         }
      }
      else
      {
         // 勝ちトレードで連敗リセット
         currentConsecLoss = 0;
         currentConsecLossAmount = 0.0;
      }
   }
   
   // 有効な取引がない場合
   if(tradeCount < 10)
   {
      Print("有効取引数不足: ", tradeCount);
      return(0.0);
   }
   
   // 配列サイズを実際の取引数にリサイズ
   ArrayResize(profits, tradeCount);
   
   // ---- 利益の安定性（標準偏差）を計算 ----
   double meanProfit = totalProfit / tradeCount;
   double sumSqDiff = 0.0;
   
   for(int j = 0; j < tradeCount; j++)
   {
      double diff = profits[j] - meanProfit;
      sumSqDiff += diff * diff;
   }
   
   double stdDev = MathSqrt(sumSqDiff / tradeCount);
   
   // ---- カスタムシャープレシオ的な指標 ----
   // 平均利益 ÷ 標準偏差（大きいほど安定して利益を出している）
   double customSharpe = 0.0;
   if(stdDev > 0)
      customSharpe = meanProfit / stdDev;
   
   // ---- 連敗ペナルティを適用 ----
   // 最大連敗が多いほどペナルティを与える
   double consecLossPenalty = 1.0;
   if(maxConsecLoss > 5)
      consecLossPenalty = 5.0 / maxConsecLoss;  // 5連敗超でペナルティ
   
   // ---- 最終スコアの計算 ----
   // カスタムシャープ × 連敗ペナルティ × √取引回数（信頼性補正）
   double finalScore = customSharpe * consecLossPenalty * MathSqrt(tradeCount);
   
   // ---- 詳細ログ出力 ----
   Print("=== 取引履歴分析結果 ===");
   Print("有効取引数: ", tradeCount);
   Print("総損益: ", DoubleToStr(totalProfit, 2));
   Print("平均損益: ", DoubleToStr(meanProfit, 2));
   Print("損益の標準偏差: ", DoubleToStr(stdDev, 2));
   Print("カスタムシャープ: ", DoubleToStr(customSharpe, 4));
   Print("最大連敗数: ", maxConsecLoss, " (損失額: ", DoubleToStr(maxConsecLossAmount, 2), ")");
   Print("連敗ペナルティ: ", DoubleToStr(consecLossPenalty, 2));
   Print("★最終スコア: ", DoubleToStr(finalScore, 4));
   
   return(finalScore);
}

//+------------------------------------------------------------------+
//| 終了処理                                                           |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
}
</code></pre>
<p>この例では、<code>OrdersHistoryTotal()</code>と<code>OrderSelect()</code>を使って決済済み注文を1件ずつ走査しています。TesterStatistics()では取得できない連敗数や損益の標準偏差といった情報を、取引履歴から直接計算している点がポイントです。</p>
<h2><span id="toc11">TesterStatistics()で取得できる主な統計定数</span></h2>
<p>OnTester()内で使用する<code>TesterStatistics()</code>関数で取得できる代表的な統計定数を一覧にまとめます。</p>
<table>
<thead>
<tr>
<th>定数名</th>
<th>説明</th>
</tr>
</thead>
<tbody>
<tr>
<td>STAT_PROFIT</td>
<td>純利益（総利益 - 総損失）</td>
</tr>
<tr>
<td>STAT_GROSS_PROFIT</td>
<td>総利益</td>
</tr>
<tr>
<td>STAT_GROSS_LOSS</td>
<td>総損失（負の値）</td>
</tr>
<tr>
<td>STAT_TRADES</td>
<td>総取引回数</td>
</tr>
<tr>
<td>STAT_PROFIT_TRADES</td>
<td>勝ちトレード数</td>
</tr>
<tr>
<td>STAT_LOSS_TRADES</td>
<td>負けトレード数</td>
</tr>
<tr>
<td>STAT_EXPECTED_PAYOFF</td>
<td>期待利得（1トレードあたりの平均損益）</td>
</tr>
<tr>
<td>STAT_EQUITY_DD</td>
<td>最大ドローダウン（金額）</td>
</tr>
<tr>
<td>STAT_EQUITY_DDREL_PERCENT</td>
<td>最大ドローダウン（%）</td>
</tr>
<tr>
<td>STAT_SHARPE_RATIO</td>
<td>シャープレシオ</td>
</tr>
<tr>
<td>STAT_PROFIT_FACTOR</td>
<td>プロフィットファクター</td>
</tr>
<tr>
<td>STAT_RECOVERY_FACTOR</td>
<td>リカバリーファクター</td>
</tr>
</tbody>
</table>
<h2><span id="toc12">最適化での使い方</span></h2>
<p>OnTester()の戻り値をカスタム最適化基準として使用するには、ストラテジーテスターの最適化設定で以下の手順を行います。</p>
<ol>
<li>ストラテジーテスターを開き、EAを選択する</li>
<li>「最適化」にチェックを入れる</li>
<li>最適化基準のドロップダウンから<strong>「Custom max」</strong>を選択する</li>
<li>最適化するパラメータの範囲を設定する</li>
<li>「スタート」を押して最適化を実行する</li>
</ol>
<p>「Custom max」を選択すると、OnTester()の戻り値が<strong>最大化</strong>される方向でパラメータの最適な組み合わせが探索されます。逆に最小化したい場合は、戻り値の符号を反転させる（負の値にする）ことで対応できます。</p>
<h2><span id="toc13">注意点・よくある間違い</span></h2>
<ul>
<li><strong>ライブ取引では呼ばれない</strong>：OnTester()はストラテジーテスター内でのみ動作します。ライブ取引や通常のチャートにEAをアタッチした場合は呼ばれません。</li>
<li><strong>戻り値の型はdouble</strong>：int型ではなくdouble型を返す必要があります。整数値を返しても問題はありませんが、関数の宣言はdoubleにしてください。</li>
<li><strong>TesterStatistics()はOnTester()内でのみ有効</strong>：この関数はバックテスト終了後にしか正確な値を返しません。OnTick()内で呼んでも意味のある値は取得できません。</li>
<li><strong>最適化時のパフォーマンス</strong>：OnTester()内で重い計算を行うと、最適化全体の速度に影響します。特に数千パスの最適化を行う場合は、計算量に注意してください。</li>
<li><strong>取引回数のフィルターは必須</strong>：取引回数が極端に少ない結果は統計的に信頼できません。必ず最低取引回数のチェックを入れてから評価値を計算しましょう。</li>
</ul>
<p>投稿 <a href="https://mql-programing.com/archives/3050/ontester/">【MQL4関数】OnTester関数とは？バックテスト終了時に実行されるイベント関数</a> は <a href="https://mql-programing.com">自動売買を作ろう！</a> に最初に表示されました。</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>【MQL4関数】OnTimer関数とは？一定時間ごとに実行されるイベント関数</title>
		<link>https://mql-programing.com/archives/3030/ontimer/</link>
		
		<dc:creator><![CDATA[朝日奈りさ]]></dc:creator>
		<pubDate>Tue, 17 May 2022 05:09:20 +0000</pubDate>
				<category><![CDATA[関数]]></category>
		<category><![CDATA[【中級編】MQLプログラムの読み方・書き方]]></category>
		<category><![CDATA[【辞書】MQLリファレンス]]></category>
		<category><![CDATA[その他]]></category>
		<category><![CDATA[リファレンス]]></category>
		<category><![CDATA[自動売買]]></category>
		<category><![CDATA[イベント関数]]></category>
		<category><![CDATA[OnTimer]]></category>
		<category><![CDATA[EventSetTimer]]></category>
		<category><![CDATA[EventKillTimer]]></category>
		<category><![CDATA[MQL]]></category>
		<guid isPermaLink="false">https://mql-programing.com/?p=3030</guid>

					<description><![CDATA[<p>OnTimer関数の基本 OnTimer関数は、一定時間（タイマー間隔）が経過するたびに自動的に呼び出されるイベントハンドラ関数です。チャートのティック（価格変動）に依存せず、指定した時間間隔で定期的に処理を実行できるた [&#8230;]</p>
<p>投稿 <a href="https://mql-programing.com/archives/3030/ontimer/">【MQL4関数】OnTimer関数とは？一定時間ごとに実行されるイベント関数</a> は <a href="https://mql-programing.com">自動売買を作ろう！</a> に最初に表示されました。</p>
]]></description>
										<content:encoded><![CDATA[<h2><span id="toc1">OnTimer関数の基本</span></h2>
<p>OnTimer関数は、一定時間（タイマー間隔）が経過するたびに自動的に呼び出されるイベントハンドラ関数です。チャートのティック（価格変動）に依存せず、指定した時間間隔で定期的に処理を実行できるため、定期的なデータ取得、ログ出力、監視処理などに非常に便利です。</p>
<h3><span id="toc2">関数のシグネチャ</span></h3>
<pre><code class="language-mql4">void OnTimer(void);
</code></pre>
<p>OnTimer関数には引数も戻り値もありません。この関数を使うためには、事前に<code>EventSetTimer()</code>または<code>EventSetMillisecondTimer()</code>でタイマーを起動しておく必要があります。</p>
<h3><span id="toc3">タイマーの起動と停止</span></h3>
<table>
<thead>
<tr>
<th>関数名</th>
<th>説明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>EventSetTimer(int seconds)</code></td>
<td>秒単位でタイマー間隔を設定（最小1秒）</td>
</tr>
<tr>
<td><code>EventSetMillisecondTimer(int milliseconds)</code></td>
<td>ミリ秒単位でタイマー間隔を設定（最小数十ms程度）</td>
</tr>
<tr>
<td><code>EventKillTimer()</code></td>
<td>タイマーを停止する</td>
</tr>
</tbody>
</table>
<h3><span id="toc4">基本的な使い方の流れ</span></h3>
<ol>
<li><code>OnInit()</code>内で<code>EventSetTimer()</code>を呼び出してタイマーを起動する</li>
<li>指定した間隔ごとに<code>OnTimer()</code>が自動的に呼ばれる</li>
<li><code>OnDeinit()</code>内で<code>EventKillTimer()</code>を呼び出してタイマーを停止する</li>
</ol>
<pre><code class="language-mql4">// OnTimer関数の最も基本的な構造
int OnInit()
{
   // 5秒ごとにOnTimerを呼び出すよう設定
   EventSetTimer(5);
   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
   // タイマーを停止（リソース解放）
   EventKillTimer();
}

void OnTimer()
{
   // 5秒ごとにここが実行される
   Print("タイマーイベント発生: ", TimeToString(TimeCurrent(), TIME_SECONDS));
}
</code></pre>
<h2><span id="toc5">プログラム例1：定期的にスプレッドを記録するインジケーター</span></h2>
<p>最もシンプルな例として、一定間隔でスプレッドをエキスパートログに記録するインジケーターです。ティックが発生しない閑散相場でも確実にスプレッド情報を取得できます。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| スプレッド定期記録インジケーター                                    |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property strict

// 入力パラメータ：記録間隔（秒）
input int InpInterval = 10; // 記録間隔（秒）

//+------------------------------------------------------------------+
//| 初期化関数                                                        |
//+------------------------------------------------------------------+
int OnInit()
{
   // 指定秒数ごとにタイマーイベントを発生させる
   EventSetTimer(InpInterval);
   
   Print("スプレッド記録開始 - 間隔: ", InpInterval, "秒");
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| 終了処理関数                                                      |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // タイマーを確実に停止する
   EventKillTimer();
   Print("スプレッド記録終了");
}

//+------------------------------------------------------------------+
//| タイマーイベントハンドラ                                           |
//+------------------------------------------------------------------+
void OnTimer()
{
   // 現在のスプレッドをポイント単位で取得
   int spreadPoints = (int)MarketInfo(Symbol(), MODE_SPREAD);
   
   // Bid/Askからスプレッドを実数値で計算
   double spreadValue = MarketInfo(Symbol(), MODE_ASK) - MarketInfo(Symbol(), MODE_BID);
   
   // ログに出力
   Print(Symbol(), " スプレッド: ", spreadPoints, " points (", 
         DoubleToString(spreadValue, (int)MarketInfo(Symbol(), MODE_DIGITS)), ")");
}

//+------------------------------------------------------------------+
//| OnCalculate（インジケーター必須関数）                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
   return(rates_total);
}
</code></pre>
<p>この例では<code>EventSetTimer(10)</code>により10秒ごとにスプレッドを記録しています。ティックベースの<code>OnCalculate</code>では価格変動がないとデータを取れませんが、タイマーを使えば一定間隔で確実にデータを収集できます。</p>
<h2><span id="toc6">プログラム例2：タイマーでチャート上に時計を表示するインジケーター</span></h2>
<p>チャート上にサーバー時間とローカル時間をリアルタイム表示する実用的なインジケーターです。1秒ごとにタイマーで更新するため、ティックがなくても時計が動き続けます。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| チャート時計インジケーター                                         |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property strict

// 入力パラメータ
input color InpColorServer = clrWhite;  // サーバー時間の文字色
input color InpColorLocal  = clrYellow; // ローカル時間の文字色
input int   InpFontSize    = 12;        // フォントサイズ
input ENUM_BASE_CORNER InpCorner = CORNER_RIGHT_UPPER; // 表示コーナー

//+------------------------------------------------------------------+
//| 初期化                                                            |
//+------------------------------------------------------------------+
int OnInit()
{
   // 1秒ごとに更新
   EventSetTimer(1);
   
   // サーバー時間ラベルを作成
   CreateLabel("LabelServerTime", "Server: --:--:--", InpColorServer, 10, 20);
   // ローカル時間ラベルを作成
   CreateLabel("LabelLocalTime", "Local: --:--:--", InpColorLocal, 10, 40);
   
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| 終了処理                                                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   EventKillTimer();
   
   // ラベルオブジェクトを削除
   ObjectDelete(0, "LabelServerTime");
   ObjectDelete(0, "LabelLocalTime");
}

//+------------------------------------------------------------------+
//| タイマーイベント：1秒ごとに時計を更新                              |
//+------------------------------------------------------------------+
void OnTimer()
{
   // サーバー時間を取得して表示を更新
   string serverTimeStr = "Server: " + TimeToString(TimeCurrent(), TIME_SECONDS);
   ObjectSetString(0, "LabelServerTime", OBJPROP_TEXT, serverTimeStr);
   
   // ローカル時間を取得して表示を更新
   string localTimeStr = "Local: " + TimeToString(TimeLocal(), TIME_SECONDS);
   ObjectSetString(0, "LabelLocalTime", OBJPROP_TEXT, localTimeStr);
   
   // チャートを再描画して即座に反映
   ChartRedraw(0);
}

//+------------------------------------------------------------------+
//| ラベルオブジェクト作成用のヘルパー関数                              |
//+------------------------------------------------------------------+
void CreateLabel(string name, string text, color clr, int xDist, int yDist)
{
   // 既存のオブジェクトがあれば削除
   ObjectDelete(0, name);
   
   // ラベルオブジェクトを作成
   ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, name, OBJPROP_CORNER, InpCorner);
   ObjectSetInteger(0, name, OBJPROP_XDISTANCE, xDist);
   ObjectSetInteger(0, name, OBJPROP_YDISTANCE, yDist);
   ObjectSetString(0, name, OBJPROP_TEXT, text);
   ObjectSetString(0, name, OBJPROP_FONT, "Arial Bold");
   ObjectSetInteger(0, name, OBJPROP_FONTSIZE, InpFontSize);
   ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
}

//+------------------------------------------------------------------+
//| OnCalculate（インジケーター必須）                                  |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
   return(rates_total);
}
</code></pre>
<p>このインジケーターはタイマーの代表的な活用例です。<code>EventSetTimer(1)</code>で毎秒更新することで、秒単位の時計をチャート上に実現しています。<code>ChartRedraw()</code>を呼ぶことで、オブジェクトの変更が即座に画面に反映されます。</p>
<h2><span id="toc7">プログラム例3：一定時間ごとにトレーリングストップを実行するEA</span></h2>
<p>ティックベースではなくタイマーベースでトレーリングストップを管理するEAです。ティックが少ない通貨ペアや時間帯でも、確実にストップロスの調整が行われるメリットがあります。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| タイマー式トレーリングストップEA                                    |
//+------------------------------------------------------------------+
#property strict

// 入力パラメータ
input int    InpCheckInterval  = 3;     // チェック間隔（秒）
input double InpTrailDistance  = 30.0;  // トレーリング距離（pips）
input double InpTrailStep     = 10.0;  // トレーリングステップ（pips）
input int    InpMagicNumber   = 12345; // マジックナンバー

//+------------------------------------------------------------------+
//| 初期化                                                            |
//+------------------------------------------------------------------+
int OnInit()
{
   // 指定秒数ごとにトレーリングストップをチェック
   EventSetTimer(InpCheckInterval);
   
   Print("タイマー式トレーリングストップ起動 - チェック間隔: ", InpCheckInterval, "秒");
   Print("トレーリング距離: ", InpTrailDistance, " pips, ステップ: ", InpTrailStep, " pips");
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| 終了処理                                                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   EventKillTimer();
   Print("タイマー式トレーリングストップ停止");
}

//+------------------------------------------------------------------+
//| タイマーイベント：定期的にトレーリングストップを適用                 |
//+------------------------------------------------------------------+
void OnTimer()
{
   // 全オーダーをスキャンしてトレーリングストップを適用
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      // オーダーを選択
      if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
         continue;
      
      // 対象通貨ペアとマジックナンバーをフィルタ
      if(OrderSymbol() != Symbol())
         continue;
      if(OrderMagicNumber() != InpMagicNumber)
         continue;
      
      // トレーリングストップ処理を実行
      ApplyTrailingStop(OrderTicket(), OrderType(), OrderOpenPrice(), OrderStopLoss());
   }
}

//+------------------------------------------------------------------+
//| トレーリングストップの適用                                         |
//+------------------------------------------------------------------+
void ApplyTrailingStop(int ticket, int orderType, double openPrice, double currentSL)
{
   double point = MarketInfo(Symbol(), MODE_POINT);
   int digits   = (int)MarketInfo(Symbol(), MODE_DIGITS);
   
   // pipsをポイントに変換（3桁/5桁対応）
   double pipMultiplier = (digits == 3 || digits == 5) ? 10.0 : 1.0;
   double trailPoints   = InpTrailDistance * pipMultiplier * point;
   double stepPoints    = InpTrailStep * pipMultiplier * point;
   
   double newSL = 0;
   
   if(orderType == OP_BUY)
   {
      double bid = MarketInfo(Symbol(), MODE_BID);
      
      // 現在価格がトレーリング距離以上の利益に達しているか
      if(bid - openPrice >= trailPoints)
      {
         // 新しいストップロスを計算
         newSL = NormalizeDouble(bid - trailPoints, digits);
         
         // 現在のSLよりステップ分以上改善される場合のみ変更
         if(newSL > currentSL + stepPoints || currentSL == 0)
         {
            if(OrderModify(ticket, openPrice, newSL, OrderTakeProfit(), 0, clrGreen))
               Print("Buy #", ticket, " トレーリングSL更新: ", DoubleToString(newSL, digits));
            else
               Print("トレーリングSL更新失敗 #", ticket, " Error: ", GetLastError());
         }
      }
   }
   else if(orderType == OP_SELL)
   {
      double ask = MarketInfo(Symbol(), MODE_ASK);
      
      // 現在価格がトレーリング距離以上の利益に達しているか
      if(openPrice - ask >= trailPoints)
      {
         // 新しいストップロスを計算
         newSL = NormalizeDouble(ask + trailPoints, digits);
         
         // 現在のSLよりステップ分以上改善される場合のみ変更
         if(newSL < currentSL - stepPoints || currentSL == 0)
         {
            if(OrderModify(ticket, openPrice, newSL, OrderTakeProfit(), 0, clrRed))
               Print("Sell #", ticket, " トレーリングSL更新: ", DoubleToString(newSL, digits));
            else
               Print("トレーリングSL更新失敗 #", ticket, " Error: ", GetLastError());
         }
      }
   }
}

//+------------------------------------------------------------------+
//| OnTick（EA必須関数 - ここではエントリーロジック等を記述可能）       |
//+------------------------------------------------------------------+
void OnTick()
{
   // エントリーロジックはOnTickに記述
   // トレーリングストップはOnTimerで処理しているため、ここでは不要
}
</code></pre>
<p>OnTickでトレーリングストップを処理する一般的な方法と異なり、タイマー方式では価格変動がなくても定期的にチェックが行われます。特に流動性の低い通貨ペアや早朝の時間帯において、ストップロスの調整漏れを防ぐ効果があります。</p>
<h2><span id="toc8">プログラム例4：ミリ秒タイマーで高頻度監視を行うEA</span></h2>
<p><code>EventSetMillisecondTimer()</code>を使い、ミリ秒単位の高頻度で複数通貨ペアの価格を監視する例です。急変検知やアラート通知などの用途に適しています。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| 高頻度マルチ通貨監視EA                                             |
//+------------------------------------------------------------------+
#property strict

// 入力パラメータ
input int    InpTimerMs       = 500;    // タイマー間隔（ミリ秒）
input double InpAlertPips     = 5.0;    // アラート閾値（pips）
input bool   InpSendPush      = false;  // プッシュ通知を送信するか

// 監視対象通貨ペア
string WatchSymbols[] = {"EURUSD", "GBPUSD", "USDJPY", "AUDUSD"};

// 前回の価格を保存する配列
double PrevBid[];
// 最後にアラートを出した時刻（連続アラート防止用）
datetime LastAlertTime[];

//+------------------------------------------------------------------+
//| 初期化                                                            |
//+------------------------------------------------------------------+
int OnInit()
{
   int count = ArraySize(WatchSymbols);
   
   // 配列を初期化
   ArrayResize(PrevBid, count);
   ArrayResize(LastAlertTime, count);
   
   // 各通貨ペアの初期Bid価格を取得
   for(int i = 0; i < count; i++)
   {
      PrevBid[i] = MarketInfo(WatchSymbols[i], MODE_BID);
      LastAlertTime[i] = 0;
      
      // 通貨ペアがマーケットウォッチに存在するか確認
      if(PrevBid[i] == 0)
         Print("警告: ", WatchSymbols[i], " の価格を取得できません。マーケットウォッチに追加してください。");
   }
   
   // ミリ秒単位でタイマーを設定
   if(!EventSetMillisecondTimer(InpTimerMs))
   {
      Print("ミリ秒タイマーの設定に失敗しました");
      return(INIT_FAILED);
   }
   
   Print("マルチ通貨監視開始 - 間隔: ", InpTimerMs, "ms, 閾値: ", InpAlertPips, " pips");
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| 終了処理                                                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   EventKillTimer();
   Print("マルチ通貨監視終了");
}

//+------------------------------------------------------------------+
//| タイマーイベント：ミリ秒間隔で価格変動を監視                       |
//+------------------------------------------------------------------+
void OnTimer()
{
   int count = ArraySize(WatchSymbols);
   
   for(int i = 0; i < count; i++)
   {
      // 現在のBid価格を取得
      double currentBid = MarketInfo(WatchSymbols[i], MODE_BID);
      
      // 価格が取得できない場合はスキップ
      if(currentBid == 0)
         continue;
      
      // 前回からの変動幅を計算
      double change = currentBid - PrevBid[i];
      
      // pipsに変換
      int digits = (int)MarketInfo(WatchSymbols[i], MODE_DIGITS);
      double point = MarketInfo(WatchSymbols[i], MODE_POINT);
      double pipMultiplier = (digits == 3 || digits == 5) ? 10.0 : 1.0;
      double changePips = change / (point * pipMultiplier);
      
      // 変動幅が閾値を超えた場合にアラート
      if(MathAbs(changePips) >= InpAlertPips)
      {
         // 連続アラート防止（最低30秒間隔）
         if(TimeCurrent() - LastAlertTime[i] >= 30)
         {
            string direction = (changePips > 0) ? "上昇" : "下落";
            string msg = StringFormat("%s 急変検知！ %s %.1f pips (%.5f → %.5f)",
                                      WatchSymbols[i], direction, 
                                      MathAbs(changePips), PrevBid[i], currentBid);
            
            // ログとアラート出力
            Print(msg);
            Alert(msg);
            
            // プッシュ通知（設定が有効な場合）
            if(InpSendPush)
               SendNotification(msg);
            
            LastAlertTime[i] = TimeCurrent();
         }
      }
      
      // 現在の価格を次回比較用に保存
      PrevBid[i] = currentBid;
   }
}

//+------------------------------------------------------------------+
//| OnTick（EA必須関数）                                              |
//+------------------------------------------------------------------+
void OnTick()
{
   // メインのトレードロジックはここに記述可能
   // 監視処理はOnTimerで独立して動作する
}
</code></pre>
<p><code>EventSetMillisecondTimer(500)</code>により0.5秒ごとに複数通貨ペアの価格を監視し、急激な変動を検知してアラートを出します。連続アラート防止のために最低30秒の間隔を設けている点がポイントです。</p>
<h2><span id="toc9">OnTimer関数のポイントと注意事項</span></h2>
<h3><span id="toc10">タイマーの基本ルール</span></h3>
<ul>
<li><strong>1つのEA/インジケーターにつきタイマーは1つだけ</strong> — <code>EventSetTimer()</code>を複数回呼ぶと、最後の設定で上書きされます。複数の異なる間隔で処理を行いたい場合は、最小間隔でタイマーを設定し、OnTimer内でカウンタを使って分岐させます。</li>
<li><strong>OnDeinit()でEventKillTimer()を必ず呼ぶ</strong> — タイマーの停止を忘れると、EAやインジケーターを削除した後もリソースが解放されない可能性があります。</li>
<li><strong>タイマーの精度には限界がある</strong> — 特にミリ秒タイマーの場合、OSやMT4の処理負荷により正確な間隔にならないことがあります。</li>
</ul>
<h3><span id="toc11">複数間隔の処理をOnTimerで実現する方法</span></h3>
<pre><code class="language-mql4">// 1秒タイマーで複数の処理間隔を実現する例
int timerCount = 0; // タイマーカウンター

int OnInit()
{
   // 最小間隔（1秒）でタイマーを設定
   EventSetTimer(1);
   timerCount = 0;
   return(INIT_SUCCEEDED);
}

void OnTimer()
{
   timerCount++;
   
   // 毎秒実行する処理
   UpdateClockDisplay();
   
   // 5秒ごとに実行する処理
   if(timerCount % 5 == 0)
   {
      CheckSpread();
   }
   
   // 60秒（1分）ごとに実行する処理
   if(timerCount % 60 == 0)
   {
      LogAccountStatus();
   }
   
   // 300秒（5分）ごとに実行する処理
   if(timerCount % 300 == 0)
   {
      CheckMultiTimeframeSignals();
      timerCount = 0; // オーバーフロー防止のためリセット
   }
}
</code></pre>
<h3><span id="toc12">OnTimerとOnTickの使い分け</span></h3>
<table>
<thead>
<tr>
<th>項目</th>
<th>OnTimer</th>
<th>OnTick</th>
</tr>
</thead>
<tbody>
<tr>
<td>呼び出しタイミング</td>
<td>指定した時間間隔ごと</td>
<td>新しいティック（価格変動）が来るたび</td>
</tr>
<tr>
<td>閑散時の動作</td>
<td>価格変動がなくても実行される</td>
<td>価格変動がないと実行されない</td>
</tr>
<tr>
<td>適した用途</td>
<td>定期監視、UI更新、時刻ベース処理</td>
<td>価格ベースの売買判断</td>
</tr>
<tr>
<td>バックテスト</td>
<td>ストラテジーテスターでは動作しない</td>
<td>正常にテスト可能</td>
</tr>
</tbody>
</table>
<h3><span id="toc13">重要な注意事項</span></h3>
<ul>
<li><strong>ストラテジーテスターでは動作しない</strong> — OnTimerはバックテスト中に呼び出されません。タイマーに依存するロジックは、バックテストでは別の方法（OnTick内での時刻チェック等）で代替する必要があります。</li>
<li><strong>重い処理は避ける</strong> — OnTimer内で重い処理（大量のオーダー検索、複雑な計算など）を頻繁に実行すると、MT4全体のパフォーマンスに影響します。特にミリ秒タイマーを使う場合は処理を軽量に保ちましょう。</li>
<li><strong>MarketInfo()で最新価格を取得する</strong> — OnTimer内ではBid/Ask変数が更新されない場合があるため、<code>MarketInfo(Symbol(), MODE_BID)</code>等で明示的に取得するのが確実です。</li>
<li><strong>週末・市場クローズ中もタイマーは動作する</strong> — MT4が起動している限りタイマーイベントは発生します。市場が閉まっている間に不要な処理が走らないよう、必要に応じて曜日や時刻のチェックを入れましょう。</li>
</ul>
<p>投稿 <a href="https://mql-programing.com/archives/3030/ontimer/">【MQL4関数】OnTimer関数とは？一定時間ごとに実行されるイベント関数</a> は <a href="https://mql-programing.com">自動売買を作ろう！</a> に最初に表示されました。</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>【MQL4関数】OnTick関数とは？ロウソク足が動いたときに実行されるイベント関数</title>
		<link>https://mql-programing.com/archives/2949/ontick/</link>
		
		<dc:creator><![CDATA[朝日奈りさ]]></dc:creator>
		<pubDate>Sun, 15 May 2022 07:54:53 +0000</pubDate>
				<category><![CDATA[関数]]></category>
		<category><![CDATA[【中級編】MQLプログラムの読み方・書き方]]></category>
		<category><![CDATA[【辞書】MQLリファレンス]]></category>
		<category><![CDATA[その他]]></category>
		<category><![CDATA[MQL]]></category>
		<category><![CDATA[リファレンス]]></category>
		<category><![CDATA[自動売買]]></category>
		<category><![CDATA[OnTick]]></category>
		<category><![CDATA[イベント関数]]></category>
		<guid isPermaLink="false">https://mql-programing.com/?p=2949</guid>

					<description><![CDATA[<p>OnTick関数とは？ OnTick関数は、EAがアタッチされたチャートの通貨ペアに新しいティック（価格変動）が発生するたびに自動的に呼び出されるイベント関数です。MT4のEA（Expert Advisor）において最も [&#8230;]</p>
<p>投稿 <a href="https://mql-programing.com/archives/2949/ontick/">【MQL4関数】OnTick関数とは？ロウソク足が動いたときに実行されるイベント関数</a> は <a href="https://mql-programing.com">自動売買を作ろう！</a> に最初に表示されました。</p>
]]></description>
										<content:encoded><![CDATA[<h2><span id="toc1">OnTick関数とは？</span></h2>
<p>OnTick関数は、EAがアタッチされたチャートの通貨ペアに<strong>新しいティック（価格変動）が発生するたびに自動的に呼び出されるイベント関数</strong>です。MT4のEA（Expert Advisor）において最も基本的かつ重要な関数であり、売買ロジックのほとんどはこの関数の中に記述します。</p>
<p>「ロウソク足が動いたとき」という表現は、正確には<strong>「新しい価格データ（ティック）がサーバーから配信されたとき」</strong>を意味します。1本のロウソク足の中でも価格が変動するたびに何度も呼び出されます。</p>
<h2><span id="toc2">基本的な書式</span></h2>
<pre><code class="language-mql4">void OnTick()
{
   // ティック発生時に実行したい処理をここに記述
}
</code></pre>
<h3><span id="toc3">引数</span></h3>
<p>OnTick関数は<strong>引数を取りません</strong>。</p>
<h3><span id="toc4">戻り値</span></h3>
<p>戻り値の型は<strong>void</strong>（なし）です。値を返す必要はありません。</p>
<h3><span id="toc5">対象ファイル</span></h3>
<p>OnTick関数は<strong>EA（.mq4ファイル）専用</strong>です。カスタムインジケーターやスクリプトでは使用できません（インジケーターではOnCalculate関数が同様の役割を果たします）。</p>
<h2><span id="toc6">OnTick関数の呼び出しタイミング</span></h2>
<p>OnTick関数が呼び出される条件を正しく理解することが重要です。</p>
<ul>
<li><strong>呼び出される条件：</strong>EAがアタッチされたチャートの通貨ペアで新しいティックが発生したとき</li>
<li><strong>呼び出し頻度：</strong>相場が活発なときは1秒間に何度も呼ばれ、相場が閑散としているときはほとんど呼ばれない</li>
<li><strong>週末・祝日：</strong>マーケットが閉まっている間はティックが発生しないため、OnTick関数は呼ばれない</li>
<li><strong>他の通貨ペア：</strong>アタッチした通貨ペア以外の価格変動では呼ばれない</li>
</ul>
<h2><span id="toc7">プログラム例1：基本的なティック情報の表示</span></h2>
<p>最もシンプルな例として、ティックが発生するたびに現在の価格情報をログに出力するEAです。OnTick関数の動作を確認するのに最適です。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| 基本的なティック情報表示EA                                         |
//+------------------------------------------------------------------+
void OnTick()
{
   // 現在のBid価格とAsk価格を取得
   double currentBid = Bid;
   double currentAsk = Ask;
   
   // スプレッドをポイント単位で計算
   double spread = (Ask - Bid) / Point;
   
   // ログに価格情報を出力
   Print("通貨ペア: ", Symbol(),
         " | Bid: ", DoubleToString(currentBid, Digits),
         " | Ask: ", DoubleToString(currentAsk, Digits),
         " | スプレッド: ", DoubleToString(spread, 1), " pips");
   
   // チャート上にも情報を表示
   Comment("最終ティック時刻: ", TimeToString(TimeCurrent(), TIME_SECONDS),
           "\nBid: ", DoubleToString(currentBid, Digits),
           "\nAsk: ", DoubleToString(currentAsk, Digits),
           "\nスプレッド: ", DoubleToString(spread, 1), " points");
}
</code></pre>
<p>このEAをチャートにアタッチすると、ティックが発生するたびにエキスパートタブにログが出力され、チャート左上に価格情報がリアルタイム表示されます。</p>
<h2><span id="toc8">プログラム例2：新しいロウソク足の確定を検出する</span></h2>
<p>OnTick関数はティックごとに呼ばれるため、「新しいロウソク足が形成されたとき（＝前の足が確定したとき）だけ処理を実行する」というパターンは非常によく使われます。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| 新しいバー検出EA                                                   |
//+------------------------------------------------------------------+

// 前回のバーの時刻を記録するためのグローバル変数
datetime lastBarTime = 0;

//+------------------------------------------------------------------+
//| 新しいバーが形成されたかを判定する関数                              |
//+------------------------------------------------------------------+
bool IsNewBar()
{
   // 現在のバー（最新の未確定バー）の開始時刻を取得
   datetime currentBarTime = iTime(Symbol(), Period(), 0);
   
   // 前回記録した時刻と異なれば新しいバーが形成された
   if(currentBarTime != lastBarTime)
   {
      lastBarTime = currentBarTime;  // 時刻を更新
      return true;                    // 新しいバーが出現
   }
   
   return false;  // まだ同じバーの中
}

//+------------------------------------------------------------------+
//| OnTick関数                                                        |
//+------------------------------------------------------------------+
void OnTick()
{
   // 新しいバーが形成されたときだけ処理を実行
   if(!IsNewBar())
      return;  // 新しいバーでなければ何もしない
   
   // ここに新しいバー確定時の処理を記述
   // インデックス1が直前に確定したバー
   double closePrice = iClose(Symbol(), Period(), 1);
   double openPrice  = iOpen(Symbol(), Period(), 1);
   double highPrice  = iHigh(Symbol(), Period(), 1);
   double lowPrice   = iLow(Symbol(), Period(), 1);
   
   Print("=== 新しいバー検出 ===");
   Print("確定したバーの情報:");
   Print("  始値: ", DoubleToString(openPrice, Digits));
   Print("  高値: ", DoubleToString(highPrice, Digits));
   Print("  安値: ", DoubleToString(lowPrice, Digits));
   Print("  終値: ", DoubleToString(closePrice, Digits));
   
   // 陽線か陰線かを判定
   if(closePrice > openPrice)
      Print("  → 陽線（上昇）");
   else if(closePrice < openPrice)
      Print("  → 陰線（下降）");
   else
      Print("  → 十字線（同事線）");
}
</code></pre>
<p>この「新しいバー検出パターン」は、EAの処理負荷を軽減し、確定した価格データのみで売買判断を行う際に不可欠なテクニックです。</p>
<h2><span id="toc9">プログラム例3：移動平均線クロスによる売買EA</span></h2>
<p>OnTick関数の中で実際に売買注文を出す実践的な例です。短期移動平均線と長期移動平均線のクロスを検出して売買を行います。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| 移動平均線クロスEA                                                 |
//+------------------------------------------------------------------+

// 外部パラメータ（ユーザーが変更可能）
extern int    FastMA_Period = 10;     // 短期移動平均線の期間
extern int    SlowMA_Period = 25;     // 長期移動平均線の期間
extern double LotSize       = 0.1;   // 取引ロット数
extern int    Slippage      = 3;     // 許容スリッページ
extern int    MagicNumber   = 12345; // マジックナンバー

// 新しいバー検出用の変数
datetime g_lastBarTime = 0;

//+------------------------------------------------------------------+
//| 新しいバー判定                                                     |
//+------------------------------------------------------------------+
bool IsNewBar()
{
   datetime currentBarTime = iTime(Symbol(), Period(), 0);
   if(currentBarTime != g_lastBarTime)
   {
      g_lastBarTime = currentBarTime;
      return true;
   }
   return false;
}

//+------------------------------------------------------------------+
//| 現在のポジション数を取得する関数                                    |
//+------------------------------------------------------------------+
int CountPositions(int type)
{
   int count = 0;
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         // 同じ通貨ペアかつ同じマジックナンバーのポジションをカウント
         if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
         {
            if(OrderType() == type)
               count++;
         }
      }
   }
   return count;
}

//+------------------------------------------------------------------+
//| 指定タイプのポジションをすべて決済する関数                          |
//+------------------------------------------------------------------+
void ClosePositions(int type)
{
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
         {
            if(OrderType() == type)
            {
               double closePrice;
               if(type == OP_BUY)
                  closePrice = Bid;   // 買いポジションはBidで決済
               else
                  closePrice = Ask;   // 売りポジションはAskで決済
               
               bool result = OrderClose(OrderTicket(), OrderLots(), closePrice, Slippage, clrNONE);
               if(!result)
                  Print("決済エラー: ", GetLastError());
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| OnTick関数 - メインロジック                                        |
//+------------------------------------------------------------------+
void OnTick()
{
   // 新しいバーが確定したときだけ判定を行う
   if(!IsNewBar())
      return;
   
   // 移動平均線の値を取得（バーインデックス1 = 直前の確定バー）
   double fastMA_current  = iMA(Symbol(), Period(), FastMA_Period, 0, MODE_SMA, PRICE_CLOSE, 1);
   double slowMA_current  = iMA(Symbol(), Period(), SlowMA_Period, 0, MODE_SMA, PRICE_CLOSE, 1);
   
   // 1本前の移動平均線の値（バーインデックス2）
   double fastMA_previous = iMA(Symbol(), Period(), FastMA_Period, 0, MODE_SMA, PRICE_CLOSE, 2);
   double slowMA_previous = iMA(Symbol(), Period(), SlowMA_Period, 0, MODE_SMA, PRICE_CLOSE, 2);
   
   // ゴールデンクロス判定（短期が長期を下から上に抜ける）
   bool goldenCross = (fastMA_previous <= slowMA_previous) &#038;&#038; (fastMA_current > slowMA_current);
   
   // デッドクロス判定（短期が長期を上から下に抜ける）
   bool deadCross = (fastMA_previous >= slowMA_previous) && (fastMA_current < slowMA_current);
   
   // ゴールデンクロス → 売りポジションを決済して買い注文
   if(goldenCross)
   {
      Print("★ ゴールデンクロス検出！買いエントリー");
      
      // 売りポジションがあれば決済
      ClosePositions(OP_SELL);
      
      // 買いポジションがなければ新規買い注文
      if(CountPositions(OP_BUY) == 0)
      {
         int ticket = OrderSend(Symbol(), OP_BUY, LotSize, Ask, Slippage,
                                0, 0, "MA Cross Buy", MagicNumber, 0, clrBlue);
         if(ticket < 0)
            Print("買い注文エラー: ", GetLastError());
         else
            Print("買い注文成功 チケット: ", ticket, " 価格: ", Ask);
      }
   }
   
   // デッドクロス → 買いポジションを決済して売り注文
   if(deadCross)
   {
      Print("★ デッドクロス検出！売りエントリー");
      
      // 買いポジションがあれば決済
      ClosePositions(OP_BUY);
      
      // 売りポジションがなければ新規売り注文
      if(CountPositions(OP_SELL) == 0)
      {
         int ticket = OrderSend(Symbol(), OP_SELL, LotSize, Bid, Slippage,
                                0, 0, "MA Cross Sell", MagicNumber, 0, clrRed);
         if(ticket < 0)
            Print("売り注文エラー: ", GetLastError());
         else
            Print("売り注文成功 チケット: ", ticket, " 価格: ", Bid);
      }
   }
}
</code></pre>
<p>この例では、OnTick関数内で新しいバーの確定を検出し、移動平均線のクロスを判定して自動売買を行っています。実際のEA開発の基本パターンとして参考にしてください。</p>
<h2><span id="toc10">プログラム例4：複数の処理を整理して管理する構造</span></h2>
<p>実際のEA開発では、OnTick関数の中に全ての処理を詰め込むと可読性が下がります。役割ごとに関数を分割し、OnTick関数はそれらを呼び出すだけのシンプルな構造にするのがベストプラクティスです。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| 構造化されたEAのテンプレート                                        |
//+------------------------------------------------------------------+

extern double LotSize     = 0.1;
extern int    MagicNumber = 99999;

datetime g_lastBarTime = 0;

//+------------------------------------------------------------------+
//| 初期化関数                                                        |
//+------------------------------------------------------------------+
int OnInit()
{
   Print("EA初期化完了: ", Symbol(), " ", EnumToString((ENUM_TIMEFRAMES)Period()));
   return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| 終了処理関数                                                      |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   Print("EA停止: 理由コード = ", reason);
   Comment("");  // チャート上のコメントをクリア
}

//+------------------------------------------------------------------+
//| OnTick関数 - すべての処理の起点                                    |
//+------------------------------------------------------------------+
void OnTick()
{
   // ステップ1: チャート上に情報を表示（毎ティック更新）
   DisplayInfo();
   
   // ステップ2: 新しいバーでなければ以降の処理をスキップ
   if(!IsNewBar())
      return;
   
   // ステップ3: 既存ポジションの管理（トレーリングストップなど）
   ManagePositions();
   
   // ステップ4: エントリーシグナルの確認
   int signal = CheckSignal();
   
   // ステップ5: シグナルに基づいてエントリー
   if(signal != 0)
      ExecuteTrade(signal);
}

//+------------------------------------------------------------------+
//| チャート上に情報を表示する関数                                      |
//+------------------------------------------------------------------+
void DisplayInfo()
{
   int buyCount  = CountMyPositions(OP_BUY);
   int sellCount = CountMyPositions(OP_SELL);
   double totalProfit = CalculateTotalProfit();
   
   string info = "";
   info += "━━━ EA情報パネル ━━━\n";
   info += "通貨ペア: " + Symbol() + "\n";
   info += "時間足: " + IntegerToString(Period()) + "分\n";
   info += "現在時刻: " + TimeToString(TimeCurrent(), TIME_SECONDS) + "\n";
   info += "━━━━━━━━━━━━━━━\n";
   info += "買いポジション: " + IntegerToString(buyCount) + "\n";
   info += "売りポジション: " + IntegerToString(sellCount) + "\n";
   info += "合計損益: " + DoubleToString(totalProfit, 2) + " " + AccountCurrency() + "\n";
   
   Comment(info);
}

//+------------------------------------------------------------------+
//| 新しいバーの判定                                                   |
//+------------------------------------------------------------------+
bool IsNewBar()
{
   datetime currentBarTime = iTime(Symbol(), Period(), 0);
   if(currentBarTime != g_lastBarTime)
   {
      g_lastBarTime = currentBarTime;
      return true;
   }
   return false;
}

//+------------------------------------------------------------------+
//| 既存ポジションの管理                                               |
//+------------------------------------------------------------------+
void ManagePositions()
{
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
         continue;
      if(OrderSymbol() != Symbol() || OrderMagicNumber() != MagicNumber)
         continue;
      
      // 例：利益が50pips以上なら建値にストップロスを移動
      double profitPips = 0;
      if(OrderType() == OP_BUY)
         profitPips = (Bid - OrderOpenPrice()) / Point;
      else if(OrderType() == OP_SELL)
         profitPips = (OrderOpenPrice() - Ask) / Point;
      
      // 5桁ブローカー対応（10で割る）
      if(Digits == 3 || Digits == 5)
         profitPips /= 10.0;
      
      if(profitPips >= 50.0 && OrderStopLoss() != OrderOpenPrice())
      {
         bool result = OrderModify(OrderTicket(), OrderOpenPrice(),
                                    OrderOpenPrice(), OrderTakeProfit(), 0, clrGreen);
         if(result)
            Print("建値ストップに移動: チケット ", OrderTicket());
      }
   }
}

//+------------------------------------------------------------------+
//| エントリーシグナルの確認                                            |
//| 戻り値: 1=買い, -1=売り, 0=シグナルなし                            |
//+------------------------------------------------------------------+
int CheckSignal()
{
   // RSIを使った簡単なシグナル例
   double rsi = iRSI(Symbol(), Period(), 14, PRICE_CLOSE, 1);
   
   // RSIが30以下で買いシグナル
   if(rsi < 30.0)
   {
      Print("買いシグナル検出 RSI=", DoubleToString(rsi, 2));
      return 1;
   }
   
   // RSIが70以上で売りシグナル
   if(rsi > 70.0)
   {
      Print("売りシグナル検出 RSI=", DoubleToString(rsi, 2));
      return -1;
   }
   
   return 0;  // シグナルなし
}

//+------------------------------------------------------------------+
//| 売買を実行する関数                                                 |
//+------------------------------------------------------------------+
void ExecuteTrade(int signal)
{
   // 既にポジションがある場合はエントリーしない
   if(CountMyPositions(OP_BUY) + CountMyPositions(OP_SELL) > 0)
      return;
   
   int ticket = -1;
   
   if(signal == 1)  // 買い
   {
      ticket = OrderSend(Symbol(), OP_BUY, LotSize, Ask, 3,
                          0, 0, "RSI Buy", MagicNumber, 0, clrBlue);
   }
   else if(signal == -1)  // 売り
   {
      ticket = OrderSend(Symbol(), OP_SELL, LotSize, Bid, 3,
                          0, 0, "RSI Sell", MagicNumber, 0, clrRed);
   }
   
   if(ticket < 0)
      Print("注文エラー: ", GetLastError());
   else
      Print("注文成功: チケット ", ticket);
}

//+------------------------------------------------------------------+
//| 自分のポジション数を数える関数                                      |
//+------------------------------------------------------------------+
int CountMyPositions(int type)
{
   int count = 0;
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() == type)
            count++;
      }
   }
   return count;
}

//+------------------------------------------------------------------+
//| 全ポジションの合計損益を計算する関数                                |
//+------------------------------------------------------------------+
double CalculateTotalProfit()
{
   double profit = 0;
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
            profit += OrderProfit() + OrderSwap() + OrderCommission();
      }
   }
   return profit;
}
</code></pre>
<p>この構造では、OnTick関数は5つのステップ（情報表示→新バー判定→ポジション管理→シグナル確認→売買実行）を順に呼び出すだけのシンプルな形になっています。各処理が独立した関数に分離されているため、メンテナンスや機能追加が容易です。</p>
<h2><span id="toc11">OnTick関数の重要なポイントと注意事項</span></h2>
<h3><span id="toc12">1. ティック頻度は一定ではない</span></h3>
<p>OnTick関数の呼び出し頻度は相場の活発さに依存します。ロンドン・ニューヨーク時間は1秒に何十回も呼ばれることがありますが、週末前やアジア早朝は数秒〜数十秒に1回しか呼ばれないこともあります。<strong>「一定間隔で処理を実行したい」場合はOnTimer関数の使用</strong>を検討してください。</p>
<h3><span id="toc13">2. 他の通貨ペアのティックでは呼ばれない</span></h3>
<p>EAをEURUSDのチャートにアタッチした場合、USDJPYの価格が変動してもOnTick関数は呼ばれません。複数通貨ペアを監視したい場合は工夫が必要です。</p>
<h3><span id="toc14">3. 重い処理を入れない</span></h3>
<p>OnTick関数内の処理が完了するまで次のティックの処理は待機状態になります。大量のループや複雑な計算を毎ティック実行すると、<strong>ティックの取りこぼしや動作の遅延</strong>が発生します。重い処理は新しいバー確定時だけ実行するなど、工夫しましょう。</p>
<h3><span id="toc15">4. バックテストでの動作</span></h3>
<p>ストラテジーテスターでは、テストモデル（全ティック、コントロールポイント、始値のみ）によってOnTick関数の呼び出し頻度が大きく異なります。「始値のみ」モデルでは各バーにつき1回しか呼ばれないため、ティックごとの処理は正しくテストできません。</p>
<h3><span id="toc16">5. エラー処理を忘れない</span></h3>
<p>OnTick関数内で注文を出す場合、通信エラーやスリッページなどで失敗することがあります。OrderSend関数やOrderClose関数の戻り値を必ず確認し、エラー時の処理（リトライやログ出力）を実装しましょう。</p>
<h3><span id="toc17">6. OnTick関数とOnInit/OnDeinitの関係</span></h3>
<p>EA全体のライフサイクルは以下の順序で進みます。</p>
<ul>
<li><strong>OnInit()</strong> → EAがチャートにアタッチされたとき1回だけ実行（初期化処理）</li>
<li><strong>OnTick()</strong> → ティック発生のたびに繰り返し実行（メイン処理）</li>
<li><strong>OnDeinit()</strong> → EAが停止・削除されるとき1回だけ実行（終了処理）</li>
</ul>
<h2><span id="toc18">まとめ</span></h2>
<p>OnTick関数はMQL4のEA開発における<strong>心臓部</strong>であり、価格変動に応じた自動売買ロジックのすべてがこの関数を起点に動作します。以下のポイントを押さえておきましょう。</p>
<ul>
<li>ティック（価格変動）が発生するたびに自動的に呼ばれるイベント関数</li>
<li>引数なし、戻り値なし（void）のシンプルな関数</li>
<li>EA専用の関数であり、インジケーターやスクリプトでは使えない</li>
<li>新しいバーの確定検出と組み合わせて使うのが定番パターン</li>
<li>処理の構造化（関数分割）を意識して、保守しやすいコードを書く</li>
</ul>
<p>投稿 <a href="https://mql-programing.com/archives/2949/ontick/">【MQL4関数】OnTick関数とは？ロウソク足が動いたときに実行されるイベント関数</a> は <a href="https://mql-programing.com">自動売買を作ろう！</a> に最初に表示されました。</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>【MQL4関数】OnDeinit関数とは？アンイニシャルコード発生時に実行されるイベント関数</title>
		<link>https://mql-programing.com/archives/2930/ondeinit/</link>
		
		<dc:creator><![CDATA[朝日奈りさ]]></dc:creator>
		<pubDate>Sun, 15 May 2022 05:28:14 +0000</pubDate>
				<category><![CDATA[関数]]></category>
		<category><![CDATA[【中級編】MQLプログラムの読み方・書き方]]></category>
		<category><![CDATA[【辞書】MQLリファレンス]]></category>
		<category><![CDATA[その他]]></category>
		<category><![CDATA[イベント関数]]></category>
		<category><![CDATA[MQL]]></category>
		<category><![CDATA[リファレンス]]></category>
		<category><![CDATA[自動売買]]></category>
		<category><![CDATA[reason_]]></category>
		<category><![CDATA[OnDeinit]]></category>
		<category><![CDATA[アンイニシャル定数]]></category>
		<guid isPermaLink="false">https://mql-programing.com/?p=2930</guid>

					<description><![CDATA[<p>OnDeinit関数とは？ OnDeinit関数は、Expert Advisor（EA）やカスタムインジケーターがチャートから削除されるとき、またはアンイニシャライズ（非初期化）イベントが発生したときに自動的に呼び出され [&#8230;]</p>
<p>投稿 <a href="https://mql-programing.com/archives/2930/ondeinit/">【MQL4関数】OnDeinit関数とは？アンイニシャルコード発生時に実行されるイベント関数</a> は <a href="https://mql-programing.com">自動売買を作ろう！</a> に最初に表示されました。</p>
]]></description>
										<content:encoded><![CDATA[<h2><span id="toc1">OnDeinit関数とは？</span></h2>
<p>OnDeinit関数は、Expert Advisor（EA）やカスタムインジケーターがチャートから削除されるとき、またはアンイニシャライズ（非初期化）イベントが発生したときに自動的に呼び出されるイベント関数です。</p>
<p>プログラムの「後片付け」を行うための関数であり、リソースの解放やファイルのクローズ、オブジェクトの削除など、プログラム終了時に必要な処理を記述します。</p>
<h2><span id="toc2">基本的な書式</span></h2>
<pre><code class="language-mql4">void OnDeinit(const int reason)
{
   // 終了時の処理をここに記述
}
</code></pre>
<h3><span id="toc3">引数</span></h3>
<table>
<thead>
<tr>
<th>引数名</th>
<th>型</th>
<th>説明</th>
</tr>
</thead>
<tbody>
<tr>
<td>reason</td>
<td>const int</td>
<td>アンイニシャライズの理由コード（なぜOnDeinitが呼ばれたかを示す）</td>
</tr>
</tbody>
</table>
<h3><span id="toc4">戻り値</span></h3>
<p>戻り値はありません（void型）。</p>
<h2><span id="toc5">アンイニシャライズ理由コード（reason）一覧</span></h2>
<p>OnDeinit関数の引数<code>reason</code>には、以下の定数が渡されます。これにより、なぜプログラムが終了するのかを判別できます。</p>
<table>
<thead>
<tr>
<th>定数名</th>
<th>値</th>
<th>説明</th>
</tr>
</thead>
<tbody>
<tr>
<td>REASON_PROGRAM</td>
<td>0</td>
<td>ExpertRemove()関数による終了</td>
</tr>
<tr>
<td>REASON_REMOVE</td>
<td>1</td>
<td>チャートからプログラムが削除された</td>
</tr>
<tr>
<td>REASON_RECOMPILE</td>
<td>2</td>
<td>プログラムが再コンパイルされた</td>
</tr>
<tr>
<td>REASON_CHARTCHANGE</td>
<td>3</td>
<td>通貨ペアまたは時間足が変更された</td>
</tr>
<tr>
<td>REASON_CHARTCLOSE</td>
<td>4</td>
<td>チャートが閉じられた</td>
</tr>
<tr>
<td>REASON_PARAMETERS</td>
<td>5</td>
<td>入力パラメータが変更された</td>
</tr>
<tr>
<td>REASON_ACCOUNT</td>
<td>6</td>
<td>別のアカウントが有効化された</td>
</tr>
<tr>
<td>REASON_TEMPLATE</td>
<td>7</td>
<td>別のテンプレートが適用された</td>
</tr>
<tr>
<td>REASON_INITFAILED</td>
<td>8</td>
<td>OnInit()がゼロ以外の値を返した（初期化失敗）</td>
</tr>
<tr>
<td>REASON_CLOSE</td>
<td>9</td>
<td>ターミナルが閉じられた</td>
</tr>
</tbody>
</table>
<h2><span id="toc6">プログラム例1：基本的なOnDeinit関数の使い方</span></h2>
<p>最もシンプルな例として、OnDeinitが呼ばれた理由をログに出力する基本パターンです。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| Expert Advisor - OnDeinit基本例                                    |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| 初期化関数                                                         |
//+------------------------------------------------------------------+
int OnInit()
{
   Print("EAが初期化されました");
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| ティック処理関数                                                    |
//+------------------------------------------------------------------+
void OnTick()
{
   // メイン処理（ここでは省略）
}

//+------------------------------------------------------------------+
//| 終了処理関数                                                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // 終了理由をログに出力する
   Print("EAが終了しました。理由コード: ", reason);
   
   // 理由コードに応じたメッセージを表示
   switch(reason)
   {
      case REASON_PROGRAM:
         Print("→ ExpertRemove()による終了");
         break;
      case REASON_REMOVE:
         Print("→ チャートからEAが削除されました");
         break;
      case REASON_RECOMPILE:
         Print("→ プログラムが再コンパイルされました");
         break;
      case REASON_CHARTCHANGE:
         Print("→ 通貨ペアまたは時間足が変更されました");
         break;
      case REASON_CHARTCLOSE:
         Print("→ チャートが閉じられました");
         break;
      case REASON_PARAMETERS:
         Print("→ パラメータが変更されました");
         break;
      case REASON_ACCOUNT:
         Print("→ アカウントが変更されました");
         break;
      case REASON_TEMPLATE:
         Print("→ テンプレートが適用されました");
         break;
      case REASON_INITFAILED:
         Print("→ 初期化に失敗しました");
         break;
      case REASON_CLOSE:
         Print("→ ターミナルが閉じられました");
         break;
      default:
         Print("→ 不明な理由コード");
         break;
   }
}
</code></pre>
<p>このコードでは、switch文を使ってすべてのアンイニシャライズ理由を判別し、それぞれに対応するメッセージをエキスパートログに出力しています。デバッグ時に非常に役立つ基本パターンです。</p>
<h2><span id="toc7">プログラム例2：チャートオブジェクトの後片付け</span></h2>
<p>EAやインジケーターがチャート上にライン・ラベル・矢印などのオブジェクトを描画した場合、OnDeinitで確実に削除しないとチャートにゴミが残ってしまいます。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| Expert Advisor - オブジェクト削除の例                                |
//+------------------------------------------------------------------+
#define PREFIX "MyEA_"  // オブジェクト名のプレフィックス

//+------------------------------------------------------------------+
//| 初期化関数                                                         |
//+------------------------------------------------------------------+
int OnInit()
{
   // 水平ラインを作成
   string lineName = PREFIX + "HLine";
   ObjectCreate(0, lineName, OBJ_HLINE, 0, 0, Ask);
   ObjectSetInteger(0, lineName, OBJPROP_COLOR, clrRed);
   ObjectSetInteger(0, lineName, OBJPROP_WIDTH, 2);
   
   // テキストラベルを作成
   string labelName = PREFIX + "Label";
   ObjectCreate(0, labelName, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, labelName, OBJPROP_XDISTANCE, 20);
   ObjectSetInteger(0, labelName, OBJPROP_YDISTANCE, 30);
   ObjectSetString(0, labelName, OBJPROP_TEXT, "EA稼働中");
   ObjectSetInteger(0, labelName, OBJPROP_COLOR, clrWhite);
   ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, 12);
   
   // 矢印オブジェクトを作成
   string arrowName = PREFIX + "Arrow";
   ObjectCreate(0, arrowName, OBJ_ARROW, 0, TimeCurrent(), Bid);
   ObjectSetInteger(0, arrowName, OBJPROP_ARROWCODE, 233);
   ObjectSetInteger(0, arrowName, OBJPROP_COLOR, clrLime);
   
   Print("オブジェクトを3個作成しました");
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| ティック処理関数                                                    |
//+------------------------------------------------------------------+
void OnTick()
{
   // メイン処理（ここでは省略）
}

//+------------------------------------------------------------------+
//| 終了処理関数 - オブジェクトの後片付け                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // プレフィックスが一致するオブジェクトをすべて検索して削除
   int totalObjects = ObjectsTotal(0);
   int deletedCount = 0;
   
   // 後ろから順にループする（削除時のインデックスずれを防ぐ）
   for(int i = totalObjects - 1; i >= 0; i--)
   {
      string objName = ObjectName(0, i);
      
      // プレフィックスが一致するオブジェクトのみ削除
      if(StringFind(objName, PREFIX) == 0)
      {
         ObjectDelete(0, objName);
         deletedCount++;
      }
   }
   
   Print("OnDeinit: ", deletedCount, "個のオブジェクトを削除しました");
   
   // チャートを再描画して反映
   ChartRedraw(0);
}
</code></pre>
<p>ポイントは、オブジェクト名に共通の<strong>プレフィックス</strong>をつけておくことです。OnDeinitではプレフィックスが一致するオブジェクトだけを削除するため、他のEAやインジケーターが作成したオブジェクトを誤って消してしまう心配がありません。また、オブジェクトをループで削除する際は、インデックスがずれないよう<strong>後ろから前に向かってループ</strong>する点も重要です。</p>
<h2><span id="toc8">プログラム例3：ファイルハンドルの安全なクローズ</span></h2>
<p>EAでログファイルやCSVファイルに書き込んでいる場合、OnDeinitでファイルを確実に閉じる必要があります。ファイルを閉じないまま終了すると、データが破損する可能性があります。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| Expert Advisor - ファイルクローズの例                                |
//+------------------------------------------------------------------+
int gFileHandle = INVALID_HANDLE;  // グローバル変数でファイルハンドルを保持

//+------------------------------------------------------------------+
//| 初期化関数                                                         |
//+------------------------------------------------------------------+
int OnInit()
{
   // ログファイルを開く（追記モード）
   string fileName = "TradeLog_" + Symbol() + ".csv";
   gFileHandle = FileOpen(fileName, FILE_WRITE | FILE_CSV | FILE_COMMON, ',');
   
   if(gFileHandle == INVALID_HANDLE)
   {
      Print("エラー: ファイルを開けませんでした。エラーコード: ", GetLastError());
      return(INIT_FAILED);  // 初期化失敗 → OnDeinitがREASON_INITFAILEDで呼ばれる
   }
   
   // ヘッダー行を書き込む
   FileWrite(gFileHandle, "日時", "種別", "通貨ペア", "ロット", "価格", "備考");
   Print("ログファイルを開きました: ", fileName);
   
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| ティック処理関数                                                    |
//+------------------------------------------------------------------+
void OnTick()
{
   // 例：何らかの条件でトレード情報をファイルに書き込む
   static datetime lastWriteTime = 0;
   
   // 1分に1回だけ書き込む例
   if(TimeCurrent() - lastWriteTime >= 60)
   {
      if(gFileHandle != INVALID_HANDLE)
      {
         FileWrite(gFileHandle,
                   TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS),
                   "INFO",
                   Symbol(),
                   "0.00",
                   DoubleToString(Bid, Digits),
                   "定期記録");
      }
      lastWriteTime = TimeCurrent();
   }
}

//+------------------------------------------------------------------+
//| 終了処理関数 - ファイルの安全なクローズ                               |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // ファイルハンドルが有効な場合のみクローズする
   if(gFileHandle != INVALID_HANDLE)
   {
      // 最終行として終了情報を書き込む
      FileWrite(gFileHandle,
                TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS),
                "DEINIT",
                Symbol(),
                "",
                "",
                "終了理由: " + IntegerToString(reason));
      
      // ファイルをフラッシュ（バッファの内容をディスクに書き出す）
      FileFlush(gFileHandle);
      
      // ファイルを閉じる
      FileClose(gFileHandle);
      gFileHandle = INVALID_HANDLE;  // ハンドルを無効値に戻す
      
      Print("ログファイルを正常にクローズしました");
   }
   else
   {
      Print("ファイルハンドルは無効でした（クローズ不要）");
   }
}
</code></pre>
<p>ファイル操作で重要なのは、<code>FileClose()</code>の前に<code>FileFlush()</code>を呼んでバッファの内容を確実にディスクに書き出すことと、クローズ後にハンドル変数を<code>INVALID_HANDLE</code>にリセットすることです。これにより、万が一OnDeinitが複数回呼ばれるような状況でも安全に動作します。</p>
<h2><span id="toc9">プログラム例4：理由コードに応じた条件分岐処理</span></h2>
<p>実践的なEAでは、終了理由によって処理を分けたいケースがあります。例えば、パラメータ変更時はポジション情報を保持し、完全な終了時のみグローバル変数をクリアする、といった使い分けです。</p>
<pre><code class="language-mql4">//+------------------------------------------------------------------+
//| Expert Advisor - 理由コードに応じた条件分岐                          |
//+------------------------------------------------------------------+
#define EA_NAME   "MyTradingEA"
#define PREFIX    EA_NAME + "_"

input double Lots         = 0.1;   // ロット数
input int    MagicNumber  = 12345; // マジックナンバー

//+------------------------------------------------------------------+
//| 初期化関数                                                         |
//+------------------------------------------------------------------+
int OnInit()
{
   // グローバル変数から前回の状態を復元（パラメータ変更・再コンパイル時）
   string gvName = PREFIX + "LastTicket";
   if(GlobalVariableCheck(gvName))
   {
      int lastTicket = (int)GlobalVariableGet(gvName);
      Print("前回のチケット番号を復元: ", lastTicket);
   }
   
   // タイマーを設定（1秒間隔）
   EventSetTimer(1);
   
   Print(EA_NAME, " が初期化されました");
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| ティック処理関数                                                    |
//+------------------------------------------------------------------+
void OnTick()
{
   // メインのトレードロジック（ここでは省略）
}

//+------------------------------------------------------------------+
//| タイマーイベント関数                                                 |
//+------------------------------------------------------------------+
void OnTimer()
{
   // 定期処理（ここでは省略）
}

//+------------------------------------------------------------------+
//| 終了処理関数 - 理由コードに応じた条件分岐                             |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // ------------------------------------
   // 1. すべての終了理由で共通の処理
   // ------------------------------------
   
   // タイマーを停止
   EventKillTimer();
   Print("タイマーを停止しました");
   
   // ------------------------------------
   // 2. 一時的な終了の場合（状態を保持）
   // ------------------------------------
   if(reason == REASON_PARAMETERS  ||   // パラメータ変更
      reason == REASON_RECOMPILE   ||   // 再コンパイル
      reason == REASON_CHARTCHANGE ||   // 通貨ペア・時間足変更
      reason == REASON_TEMPLATE)        // テンプレート変更
   {
      Print("一時的な終了です。状態情報を保持します。");
      
      // 現在のポジション情報をグローバル変数に保存
      SaveCurrentState();
      
      // チャートオブジェクトは削除しない（再起動後にそのまま使うため）
      return;
   }
   
   // ------------------------------------
   // 3. 完全な終了の場合（クリーンアップ）
   // ------------------------------------
   if(reason == REASON_REMOVE     ||   // EA削除
      reason == REASON_CHARTCLOSE ||   // チャート閉じ
      reason == REASON_CLOSE      ||   // ターミナル閉じ
      reason == REASON_PROGRAM    ||   // ExpertRemove()
      reason == REASON_ACCOUNT)        // アカウント変更
   {
      Print("完全な終了です。フルクリーンアップを実行します。");
      
      // チャート上のすべての自作オブジェクトを削除
      DeleteAllObjects();
      
      // グローバル変数を削除
      CleanupGlobalVariables();
      
      // チャートのコメントをクリア
      Comment("");
      
      Print(EA_NAME, " のクリーンアップが完了しました");
      return;
   }
   
   // ------------------------------------
   // 4. 初期化失敗の場合
   // ------------------------------------
   if(reason == REASON_INITFAILED)
   {
      Print("初期化に失敗したため終了します");
      // 最小限のクリーンアップのみ実行
      DeleteAllObjects();
      Comment("");
      return;
   }
}

//+------------------------------------------------------------------+
//| 現在の状態をグローバル変数に保存する関数                               |
//+------------------------------------------------------------------+
void SaveCurrentState()
{
   // 保有ポジションのチケット番号を保存
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         if(OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol())
         {
            string gvName = PREFIX + "LastTicket";
            GlobalVariableSet(gvName, OrderTicket());
            Print("チケット番号 ", OrderTicket(), " をグローバル変数に保存しました");
            break;
         }
      }
   }
}

//+------------------------------------------------------------------+
//| 自作オブジェクトをすべて削除する関数                                   |
//+------------------------------------------------------------------+
void DeleteAllObjects()
{
   int count = 0;
   for(int i = ObjectsTotal(0) - 1; i >= 0; i--)
   {
      string objName = ObjectName(0, i);
      if(StringFind(objName, PREFIX) == 0)
      {
         ObjectDelete(0, objName);
         count++;
      }
   }
   if(count > 0)
   {
      Print(count, "個のオブジェクトを削除しました");
      ChartRedraw(0);
   }
}

//+------------------------------------------------------------------+
//| グローバル変数をクリーンアップする関数                                 |
//+------------------------------------------------------------------+
void CleanupGlobalVariables()
{
   int count = 0;
   int total = GlobalVariablesTotal();
   
   // 後ろからループして削除
   for(int i = total - 1; i >= 0; i--)
   {
      string gvName = GlobalVariableName(i);
      if(StringFind(gvName, PREFIX) == 0)
      {
         GlobalVariableDel(gvName);
         count++;
      }
   }
   
   if(count > 0)
      Print(count, "個のグローバル変数を削除しました");
}
</code></pre>
<p>この例では、終了理由を3つのカテゴリ（一時的な終了・完全な終了・初期化失敗）に分類し、それぞれ異なる処理を行っています。パラメータ変更や再コンパイル時にはオブジェクトやグローバル変数を保持し、EA削除やチャートクローズ時には完全なクリーンアップを実行します。</p>
<h2><span id="toc10">よくある使い方のポイント・注意事項</span></h2>
<h3><span id="toc11">1. OnDeinitの実行時間には制限がある</span></h3>
<p>OnDeinit関数の実行には<strong>約2.5秒のタイムリミット</strong>があります。時間のかかる処理（大量のファイル操作やネットワーク通信など）をOnDeinitに入れると、処理が途中で打ち切られる可能性があります。</p>
<h3><span id="toc12">2. パラメータ変更時にもOnDeinitは呼ばれる</span></h3>
<p>EAのプロパティ画面でパラメータを変更して「OK」を押すと、OnDeinit → OnInit の順で呼ばれます。このとき理由コードは<code>REASON_PARAMETERS</code>(5)です。パラメータ変更のたびにオブジェクトが消えてしまう問題を防ぐために、理由コードによる条件分岐が有効です。</p>
<h3><span id="toc13">3. 再コンパイル時にも呼ばれる</span></h3>
<p>MetaEditorでコードを修正してコンパイルすると、稼働中のEAに対してOnDeinit（<code>REASON_RECOMPILE</code>）→ OnInitの順で自動的に呼ばれます。開発中にファイルハンドルのリークなどが起きないよう、OnDeinitでの後片付けは確実に行いましょう。</p>
<h3><span id="toc14">4. グローバル変数のハンドルは必ずリセットする</span></h3>
<p>ファイルハンドルやその他のリソースハンドルをグローバル変数で保持している場合、OnDeinitでクローズした後は必ず<code>INVALID_HANDLE</code>などの無効値に戻してください。これにより、二重解放の問題を防止できます。</p>
<h3><span id="toc15">5. Comment(&#8220;&#8221;)でチャート表示をクリアする</span></h3>
<p><code>Comment()</code>関数でチャート上に情報を表示している場合、OnDeinitで<code>Comment("")</code>を呼んでクリアしないと、EA削除後もテキストがチャートに残り続けます。</p>
<h3><span id="toc16">6. EventKillTimer()を忘れない</span></h3>
<p>OnInitで<code>EventSetTimer()</code>を呼んでタイマーを設定している場合は、OnDeinitで必ず<code>EventKillTimer()</code>を呼んでタイマーを停止しましょう。</p>
<pre><code class="language-mql4">void OnDeinit(const int reason)
{
   // タイマー停止（OnInitでEventSetTimerを使用した場合は必須）
   EventKillTimer();
   
   // チャートコメントのクリア
   Comment("");
   
   // 自作オブジェクトの削除
   ObjectsDeleteAll(0, "MyPrefix_");
   
   // ファイルハンドルのクローズ
   if(fileHandle != INVALID_HANDLE)
   {
      FileClose(fileHandle);
      fileHandle = INVALID_HANDLE;
   }
   
   Print("すべてのクリーンアップが完了しました");
}
</code></pre>
<h3><span id="toc17">まとめ</span></h3>
<p>OnDeinit関数は、プログラムの「品質」を決める重要な関数です。適切な後片付けを行うことで、メモリリークやファイル破損を防ぎ、チャートをきれいな状態に保てます。特に<strong>理由コードによる条件分岐</strong>を活用することで、パラメータ変更時と完全な終了時で異なるクリーンアップ処理を実装でき、より使いやすいEAやインジケーターを作成できます。</p>
<p>投稿 <a href="https://mql-programing.com/archives/2930/ondeinit/">【MQL4関数】OnDeinit関数とは？アンイニシャルコード発生時に実行されるイベント関数</a> は <a href="https://mql-programing.com">自動売買を作ろう！</a> に最初に表示されました。</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>【MQL4関数】OnInit関数とは？自動売買の初期化に使われるイベント関数</title>
		<link>https://mql-programing.com/archives/2903/oninit/</link>
		
		<dc:creator><![CDATA[朝日奈りさ]]></dc:creator>
		<pubDate>Sun, 15 May 2022 02:51:39 +0000</pubDate>
				<category><![CDATA[関数]]></category>
		<category><![CDATA[【中級編】MQLプログラムの読み方・書き方]]></category>
		<category><![CDATA[【辞書】MQLリファレンス]]></category>
		<category><![CDATA[その他]]></category>
		<category><![CDATA[MQL]]></category>
		<category><![CDATA[リファレンス]]></category>
		<category><![CDATA[自動売買]]></category>
		<category><![CDATA[ENUM_INIT_RETCODE]]></category>
		<category><![CDATA[OnInit]]></category>
		<category><![CDATA[イベント関数]]></category>
		<guid isPermaLink="false">https://mql-programing.com/?p=2903</guid>

					<description><![CDATA[<p>OnInit関数とは 「自動売買の変数を初期化したい！」 という方は、OnInit関数を使ってみましょう！ OnInit関数は、イベント関数の一つで、自動売買が起動したときに、自動的に一度だけ実行される関数です。 そのた [&#8230;]</p>
<p>投稿 <a href="https://mql-programing.com/archives/2903/oninit/">【MQL4関数】OnInit関数とは？自動売買の初期化に使われるイベント関数</a> は <a href="https://mql-programing.com">自動売買を作ろう！</a> に最初に表示されました。</p>
]]></description>
										<content:encoded><![CDATA[
<div class="wp-block-cocoon-blocks-balloon-ex-box-1 speech-wrap sb-id-11 sbs-stn sbp-l sbis-cb cf block-box"><div class="speech-person"><figure class="speech-icon"><img decoding="async" src="https://mql-programing.com/main29/wp-content/uploads/2021/10/アイコン.png" alt="朝日奈りさ" class="speech-icon-image"/></figure><div class="speech-name">朝日奈りさ</div></div><div class="speech-balloon">
<p class="wp-block-paragraph">OnInit関数は、自動売買の初期化に使われるイベント関数です。<strong>OnInit関数は、自動売買を起動すると、自動的に一度だけ実行される関数です。</strong>そのため、変数の初期化などの処理に使うと良いでしょう。</p>
</div></div>



<h2 class="wp-block-heading"><span id="toc1">OnInit関数とは</span></h2>



<p class="wp-block-paragraph"><strong>「自動売買の変数を初期化したい！」</strong></p>



<p class="wp-block-paragraph">という方は、OnInit関数を使ってみましょう！</p>



<p class="wp-block-paragraph"><strong><span class="marker-under-red">OnInit関数は、イベント関数の一つで、自動売買が起動したときに、自動的に一度だけ実行される関数です。</span></strong></p>



<p class="wp-block-paragraph">そのため、自動売買開発者の多くの方が、変数の初期化などの処理に使っています。</p>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph"><strong><span class="marker-under-red">また、OnInit関数は、自動売買（エキスパートアドバイザ）を新規作成すると、自動的にプログラミングされています。</span></strong></p>



<p class="wp-block-paragraph">自動的にプログラミングされているのは、主に以下の3つなので、確認しておくと良いでしょう。</p>



<ul class="wp-block-list"><li><a href="https://mql-programing.com/oninit/"><strong>OnInit関数</strong></a></li><li><a href="https://mql-programing.com/ondeinit/" title="【MQL4関数】OnDeinit関数とは？アンイニシャルコード発生時に実行されるイベント関数"><strong>OnDeinit関数</strong></a></li><li><strong><a href="https://mql-programing.com/ontick/">OnTick関数</a></strong></li></ul>



<p class="wp-block-paragraph">今回は、OnInit関数に注目して解説していきます。</p>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><span id="toc2">自動売買を新規作成したときのプログラム</span></h3>



<p class="wp-block-paragraph"><strong>自動売買を新規作成すると、以下のようなプログラムが自動作成されます。</strong></p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-cpp" data-lang="C++"><code>//+------------------------------------------------------------------+
//|                                             newExpertAdviser.mq4 |
//|                                     Copyright 2022, Asahina Risa |
//|                                      https://mql-programing.com/ |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, Asahina Risa"
#property link      "https://mql-programing.com/"
#property version   "1.00"
#property strict
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+</code></pre></div>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">このように、すでに<strong>OnInit関数・OnDeinit関数・OnTick関数</strong>が記述されています。</p>



<p class="wp-block-paragraph"><strong><span class="marker-under-red">OnInit関数は、13行目にあります。</span></strong></p>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><span id="toc3">OnInit関数の特徴</span></h3>



<p class="wp-block-paragraph">OnInit関数はイベント関数なので、イベントが起こったときに実行されます。</p>



<p class="wp-block-paragraph"><strong><span class="marker-under-red">そのイベントとは、「自動売買が起動したとき」です。</span></strong></p>



<p class="wp-block-paragraph">つまり、自動売買をチャートにドラッグアンドドロップしたときに、一度だけ実行されます。</p>



<p class="wp-block-paragraph">そのため、OnInit関数は、変数などのパラメータの初期化をするために使用されることが多いです。</p>



<p class="wp-block-paragraph">別名<strong>「初期化関数」</strong>（initialization function）とも呼ばれる関数です。</p>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><span id="toc4">OnInit関数の書き方</span></h2>



<p class="wp-block-paragraph">OnInit関数の使い方は、基本的な関数と同じです。</p>



<p class="wp-block-paragraph">関数をマスターしていない方はこちらの記事が参考になります。</p>



<figure class="wp-block-embed is-type-wp-embed is-provider-自動売買を作ろう！ wp-block-embed-自動売買を作ろう！"><div class="wp-block-embed__wrapper">

<a href="https://mql-programing.com/function/" title="【MQLプログラミング基礎】関数は難しい？関数の使い方を理解しよう！プログラム例あり" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img decoding="async" src="https://mql-programing.com/main29/wp-content/uploads/cocoon-resources/blog-card-cache/624de21c236a113367001dd95952af29.jpg" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">【MQLプログラミング基礎】関数は難しい？関数の使い方を理解しよう！プログラム例あり</div><div class="blogcard-snippet external-blogcard-snippet">関数とは？ 「プログラムは、何度も同じことを書かないといけない」 と思っている方、ぜひ関数を使ってみてください。 関数とは、何度も繰り返すプログラムを、まとめた状態のものです。 事前に、まとまりを作っておくことで、同じプ</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img decoding="async" src="https://www.google.com/s2/favicons?domain=https://mql-programing.com/archives/183/function/" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">mql-programing.com</div></div></div></div></a>
</div></figure>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><span id="toc5">基本的な書き方</span></h3>



<p class="wp-block-paragraph">OnInit関数はとてもシンプルな関数です。</p>



<p class="wp-block-paragraph"><strong><span class="marker-under-red">引数なしで実行できます。</span></strong></p>



<p class="wp-block-paragraph"><strong><span class="marker-under-red">戻り値はint型</span></strong>ですが、以下のプログラムの4行目にあるように、「<a href="https://mql-programing.com/enum_init_retcode/">INIT_SUCCEEDED</a>」という定数を戻すことが多いです。</p>



<p class="wp-block-paragraph"><strong>INIT_SUCCEEDED定数は、「OnInit関数が正常に終了しました。」</strong>ということを表している定数なので、このまま使用して問題ありません。</p>



<p class="wp-block-paragraph">逆に異常終了した場合は、<a href="https://mql-programing.com/enum_init_retcode/">INIT_FAILED</a>定数を使用しましょう。</p>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">OnInit関数の基本的な書き方は以下の通りです。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-cpp" data-lang="C++"><code>int OnInit()
  {
   //初期化したい変数などを記述
   return(INIT_SUCCEEDED);
  }</code></pre></div>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">戻り値は int型なので、returnで値を戻すようにしましょう。</p>



<p class="wp-block-paragraph">戻す値は、定数が用意されています。</p>



<p class="wp-block-paragraph">OnInit関数の戻り値はこちらの記事が詳しいので、よろしければどうぞ。</p>



<figure class="wp-block-embed is-type-wp-embed is-provider-自動売買を作ろう！ wp-block-embed-自動売買を作ろう！"><div class="wp-block-embed__wrapper">

<a href="https://mql-programing.com/enum_init_retcode/" title="【MQL4定数】初期化関数の戻り値の定数（ENUM_INIT_RETCODE）" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img decoding="async" src="https://i0.wp.com/mql-programing.com/main29/wp-content/uploads/2022/05/ENUM_INIT_RETCODE.jpg?fit=1200%2C675&#038;ssl=1" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">【MQL4定数】初期化関数の戻り値の定数（ENUM_INIT_RETCODE）</div><div class="blogcard-snippet external-blogcard-snippet">目次 初期化関数の戻り値の定数（ENUM_INIT_RETCODE）初期化関数の戻り値の定数（ENUM_INIT_RETCODE）初期化関数の戻り値の定数の使い方【補足】初期化関数の戻り値の定数の列挙型（ENUM_INI</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://mql-programing.com/archives/2908/enum_init_retcode/" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">mql-programing.com</div></div></div></div></a>
</div></figure>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><span id="toc6">戻り値</span></h3>



<p class="wp-block-paragraph">int型（<a href="https://mql-programing.com/enum_init_retcode/">ENUM_INIT_RETCODE</a>）</p>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><span id="toc7">引数</span></h3>



<p class="wp-block-paragraph">なし</p>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><span id="toc8">OnInit関数のサンプルプログラム</span></h2>



<p class="wp-block-paragraph">サンプルプログラムは、こちらになります。</p>



<p class="wp-block-paragraph">このプログラムには4つの機能を実装しております。</p>



<ul class="wp-block-list"><li><strong>パラメータの異常検知</strong></li><li><strong>メモリの異常検知</strong></li><li><strong>関数の異常検知</strong></li><li><strong>正常終了</strong></li></ul>



<div class="hcb_wrap"><pre class="prism line-numbers lang-cpp" data-lang="C++"><code>//異常値を検出するためのパラメータ
input int ma_period=10;

int OnInit()
  {


   //パラメータが異常かどうかをチェック
   if(ma_period &lt;= 0)
     {
      //パラメータが異常なので、INIT_PARAMETERS_INCORRECT定数を戻す
      return (INIT_PARAMETERS_INCORRECT);
     }


   //メモリに異常がないかチェック
   int memory = TerminalInfoInteger(TERMINAL_MEMORY_TOTAL);
   if(memory &lt; 2000)
     {
      //メモリが異常なので、INIT_AGENT_NOT_SUITABLE定数を戻す
      return (INIT_AGENT_NOT_SUITABLE);
     }


   //その他異常がないかチェック
   double ma = iMA(Symbol(), PERIOD_CURRENT, 14, 0, MODE_SMA, PRICE_CLOSE, 0);
   if(ma &lt;= 0)
     {
      //値が異常なので、INIT_FAILED定数を戻す
      return(INIT_FAILED);
     }


   //正常なので、INIT_SUCCEEDED定数を戻す
   return(INIT_SUCCEEDED);
  }</code></pre></div>



<p class="wp-block-paragraph">　</p>



<p class="wp-block-paragraph">このサンプルプログラムでは、OnInit関数の中で以下の処理を行っています。</p>



<ul class="wp-block-list"><li><strong>9行目：</strong>入力パラメータ「ma_period」が0以下の場合、INIT_PARAMETERS_INCORRECT定数を返して異常終了します。</li><li><strong>18行目：</strong>TerminalInfoInteger関数でメモリ情報を取得し、2000MB未満であればINIT_AGENT_NOT_SUITABLE定数を返します。</li><li><strong>27行目：</strong>iMA関数で移動平均値を取得し、値が0以下であればINIT_FAILED定数を返します。</li><li><strong>35行目：</strong>すべてのチェックを通過した場合、INIT_SUCCEEDED定数を返して正常終了します。</li></ul>



<p class="wp-block-paragraph">　</p>



<div class="wp-block-cocoon-blocks-button-1 button-block"><a href="https://mql-programing.com/download/2910/" class="btn btn-l btn-circle" target="_self">サンプルプログラムはこちら</a></div>



<p class="wp-block-paragraph">　</p>



<h2 class="wp-block-heading"><span id="toc9">まとめ</span></h2>



<p class="wp-block-paragraph"><strong><span class="marker-under-red">OnInit関数は、自動売買を起動すると一度だけ実行されるイベント関数です。</span></strong></p>



<p class="wp-block-paragraph">自動売買を、チャートにドラッグアンドドロップすると実行されます。</p>



<p class="wp-block-paragraph">そのため、パラメータの初期化などに使用されることが多いので、サンプルプログラムを参考にして、プログラミングしてみてください。</p>



<p class="wp-block-paragraph">ちなみに、OnInit関数は、自動的に実行されますが、<strong>手動で実行することも可能です。</strong></p>



<p class="wp-block-paragraph">他の関数と同じように、OnInit関数を呼び出すことで、実行できますので、プログラムの途中で初期化したいときに実行すると良いでしょう。</p>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-embed is-type-wp-embed is-provider-自動売買を作ろう！ wp-block-embed-自動売買を作ろう！"><div class="wp-block-embed__wrapper">

<a href="https://mql-programing.com/sample_program/" title="MQL4サンプルプログラム" class="blogcard-wrap internal-blogcard-wrap a-wrap cf"><div class="blogcard internal-blogcard ib-left cf"><div class="blogcard-label internal-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail internal-blogcard-thumbnail"><img loading="lazy" decoding="async" width="160" height="90" src="https://mql-programing.com/main29/wp-content/uploads/2021/04/サンプルプログラム一覧-160x90.png" class="blogcard-thumb-image internal-blogcard-thumb-image wp-post-image" alt="" srcset="https://mql-programing.com/main29/wp-content/uploads/2021/04/サンプルプログラム一覧-160x90.png 160w, https://mql-programing.com/main29/wp-content/uploads/2021/04/サンプルプログラム一覧-300x169.png 300w, https://mql-programing.com/main29/wp-content/uploads/2021/04/サンプルプログラム一覧-1024x576.png 1024w, https://mql-programing.com/main29/wp-content/uploads/2021/04/サンプルプログラム一覧-768x432.png 768w, https://mql-programing.com/main29/wp-content/uploads/2021/04/サンプルプログラム一覧-1536x864.png 1536w, https://mql-programing.com/main29/wp-content/uploads/2021/04/サンプルプログラム一覧-2048x1152.png 2048w, https://mql-programing.com/main29/wp-content/uploads/2021/04/サンプルプログラム一覧-120x68.png 120w, https://mql-programing.com/main29/wp-content/uploads/2021/04/サンプルプログラム一覧-320x180.png 320w, https://mql-programing.com/main29/wp-content/uploads/2021/04/サンプルプログラム一覧.png 1200w" sizes="(max-width: 160px) 100vw, 160px" /></figure><div class="blogcard-content internal-blogcard-content"><div class="blogcard-title internal-blogcard-title">MQL4サンプルプログラム</div><div class="blogcard-snippet internal-blogcard-snippet">朝日奈りさ作成したMQL4サンプルプログラムの一覧です。当サイトは、MQL4のサンプルプログラムを公開しています。プログラムの内容を見たり、変更したりできます。　当サイトはMQLプログラミングを学習するサイトです。ご利用の際には、プライバシ</div></div><div class="blogcard-footer internal-blogcard-footer cf"><div class="blogcard-site internal-blogcard-site"><div class="blogcard-favicon internal-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://mql-programing.com" alt="" class="blogcard-favicon-image internal-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain internal-blogcard-domain">mql-programing.com</div></div></div></div></a>
</div></figure>



<figure class="wp-block-embed is-type-wp-embed is-provider-自動売買を作ろう！ wp-block-embed-自動売買を作ろう！"><div class="wp-block-embed__wrapper">

<a href="https://mql-programing.com/make_autotrade_by_myself/" title="【MQL4プログラミング】自動売買の作り方！FXで使えるテンプレートあり" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://i0.wp.com/mql-programing.com/main29/wp-content/uploads/2021/09/make_autotrade_by_myself.jpg?fit=1200%2C675&#038;ssl=1" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">【MQL4プログラミング】自動売買の作り方！FXで使えるテンプレートあり</div><div class="blogcard-snippet external-blogcard-snippet">目次 はじめにFX自動売買とはFX自動売買を作るための準備FX自動売買の作り方口座凍結の対策をするプログラムテンプレートを作る決済ロジックをプログラミングするプログラムテンプレートを作るエントリーロジックをプログラミング</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://mql-programing.com/archives/1605/make_autotrade_by_myself/" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">mql-programing.com</div></div></div></div></a>
</div></figure>



<figure class="wp-block-embed is-type-wp-embed is-provider-自動売買を作ろう！ wp-block-embed-自動売買を作ろう！"><div class="wp-block-embed__wrapper">

<a href="https://mql-programing.com/mql-reference/?elementor-preview=1202&#038;ver=1634090664#toc78" title="MQL4リファレンス【全データ一覧】" class="blogcard-wrap internal-blogcard-wrap a-wrap cf"><div class="blogcard internal-blogcard ib-left cf"><div class="blogcard-label internal-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail internal-blogcard-thumbnail"><img loading="lazy" decoding="async" width="160" height="90" src="https://mql-programing.com/main29/wp-content/uploads/2021/02/MQLリファレンス-160x90.jpg" class="blogcard-thumb-image internal-blogcard-thumb-image wp-post-image" alt="" srcset="https://mql-programing.com/main29/wp-content/uploads/2021/02/MQLリファレンス-160x90.jpg 160w, https://mql-programing.com/main29/wp-content/uploads/2021/02/MQLリファレンス-300x169.jpg 300w, https://mql-programing.com/main29/wp-content/uploads/2021/02/MQLリファレンス-1024x576.jpg 1024w, https://mql-programing.com/main29/wp-content/uploads/2021/02/MQLリファレンス-768x432.jpg 768w, https://mql-programing.com/main29/wp-content/uploads/2021/02/MQLリファレンス-120x68.jpg 120w, https://mql-programing.com/main29/wp-content/uploads/2021/02/MQLリファレンス-320x180.jpg 320w, https://mql-programing.com/main29/wp-content/uploads/2021/02/MQLリファレンス.jpg 1120w" sizes="(max-width: 160px) 100vw, 160px" /></figure><div class="blogcard-content internal-blogcard-content"><div class="blogcard-title internal-blogcard-title">MQL4リファレンス【全データ一覧】</div><div class="blogcard-snippet internal-blogcard-snippet">/*! elementor - v3.6.5 - 27-04-2022 */.e-container.e-container--row .elementor-spacer-inner{width:var(--spacer-size)}.e-</div></div><div class="blogcard-footer internal-blogcard-footer cf"><div class="blogcard-site internal-blogcard-site"><div class="blogcard-favicon internal-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://mql-programing.com" alt="" class="blogcard-favicon-image internal-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain internal-blogcard-domain">mql-programing.com</div></div></div></div></a>
</div></figure>
<p>投稿 <a href="https://mql-programing.com/archives/2903/oninit/">【MQL4関数】OnInit関数とは？自動売買の初期化に使われるイベント関数</a> は <a href="https://mql-programing.com">自動売買を作ろう！</a> に最初に表示されました。</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
