日足EAの組み立て方
第3回で見たとおり、売買判断は OnTick に書きます。ただし OnTick は価格が動くたびに何度も呼ばれるので、そのまま書くと1日に何回も買い判定をしてしまいます。
月末ロングは1日1回の判断で十分なので、「新しい日足が確定したときだけ処理する」という仕掛けを最初に入れます。これが日足EAの定番の組み立てです。
新しい日足を検出する
新しい日足かどうかは、今の日足の時刻を覚えておき、前回と変わったら「新しい足だ」と判断することで分かります。iTime(_Symbol, PERIOD_D1, 0) は「今の日足の開始時刻」を返すので、これを記録した値と比べます。
datetime lastBarTime = 0; // 前回処理した日足の時刻を覚えておく箱
void OnTick()
{
datetime curBar = iTime(_Symbol, PERIOD_D1, 0); // 今の日足の時刻
if(curBar == lastBarTime) return; // 同じ足なら何もしないで戻る
lastBarTime = curBar; // 新しい足なので記録を更新
// …ここから下は、1日1回だけ動く…
}
return; は「ここで処理を打ち切って戻る」命令。同じ足のあいだは return で抜けるので、下の売買判定は1日1回しか動きません。
EAの全コード
新しい足の検出に、第3回の日付取り出し・if文・売買命令を足すと、EAはこれで完成です。全体でもこの短さです。
//+------------------------------------------------------------------+
//| 月末ロング(簡易 Turn-of-Month): S&P500の日足で使う教育用EA |
//+------------------------------------------------------------------+
#include <Trade/Trade.mqh> // 取引の道具を読み込む
CTrade trade; // trade という名前で使えるようにする
datetime lastBarTime = 0; // 前回処理した日足の時刻
void OnTick()
{
// 新しい日足が確定したときだけ、1日1回判定する
datetime curBar = iTime(_Symbol, PERIOD_D1, 0);
if(curBar == lastBarTime) return;
lastBarTime = curBar;
// 直近で確定した足(1本前)の日付を取り出す
MqlDateTime dt;
TimeToStruct(iTime(_Symbol, PERIOD_D1, 1), dt);
int dom = dt.day;
bool hasPos = PositionSelect(_Symbol); // 今ポジションを持っているか
if(!hasPos && dom >= 25) // ノーポジ かつ 日付25以上 → 買い
trade.Buy(1.0);
else if(hasPos && dom >= 3 && dom <= 15) // 保有中 かつ 日付3〜15 → 決済
trade.PositionClose(_Symbol);
}
1行ずつ読む
ルールがコードのどこに対応するかを確認します。
- 新しい足の検出(
curBarの比較):1日1回だけ下を動かすための仕掛け。 - 日付の取り出し(
TimeToStruct→dt.day):直近で確定した1本前の足の日付を使います。まだ確定していない今の足を使うと先読みになるので、iTime(...,1)と1本前を指定しているのがポイントです。 - ポジションの有無(
PositionSelect):持っていればtrue。これで「買う場面か、決済する場面か」を分けます。 - 買い:
!hasPos(ノーポジ)かつdom >= 25のときtrade.Buy(1.0)。 - 決済:
hasPos(保有中)かつdom >= 3 && dom <= 15のとき決済。else ifなので、買いと決済が同じ日に両方動くことはありません。
ストラテジーテスターで動かす
第2回のストラテジーテスターに、このEAをかけます。設定は次のようにします。
- このコードを MetaEditor で
.mq5として保存し、コンパイルします(エラーが出たら直します)。 - ストラテジーテスターで、このEA・銘柄はS&P500系(業者により名称が異なる)・時間足はD1(日足)を選びます。
- 期間は、データがある範囲でできるだけ長く(10年前後)。
- スプレッドは、0ではなく現実的な値にします。指数CFDは手数料・スプレッドが結果に効きます。
- 実行し、損益・勝率・最大ドローダウン・トレード数を記録します。この数字を、第10回でPine・Pythonと突き合わせます。
この実装の約束ごと
このEAは、確定した1本前の足の日付で判定し、新しい足に入った瞬間(その足のはじめ)に trade.Buy() を出します。つまり「昨日の引けで条件を確認し、今日の頭で約定」という動きです。
これは先読みを避けるための正しい作りですが、約定のタイミングを「足の頭」に固定しているとも言えます。第9回のPythonでは、あえて「その足の引けで約定」として計算します。同じルールなのに約定の瞬間が1本ぶんずれます。この違いが、第10回で見る結果のズレの一因になります。今はEAが正しく1日1回・先読みなしで動くことを確認できれば十分です。
チェックリスト
次の記事に進む前に、以下が自分の言葉で説明できるか確認してください。
- 3環境共通のルール(25日以上で買い/3〜15日で決済)
- OnTickで「新しい日足のときだけ処理する」仕掛けが要る理由
iTime(...,1)と1本前を使うのは先読みを避けるため- PositionSelectで買い場面と決済場面を分けていること
- ストラテジーテスターでスプレッドを0にしない理由
- このEAが「前の足で判定→次の足で約定」になっていること
ミニテスト
最後に3問だけ確認です。不正解なら別の選択肢を選び直せます。正解すると解説が表示されます。
Q1. OnTickの先頭で「新しい日足のときだけ処理する」仕掛けを入れるのはなぜか?
OnTickは値が動くたびに何度も呼ばれます。新しい足の検出で1日1回に絞らないと、同じ日に何度も売買判定をしてしまいます。
Q2. 日付の取り出しで iTime(...,1) と1本前の足を使うのはなぜか?
今の足はまだ動いている最中で、その終値などを判定に使うと「未来を知った」状態になります。確定済みの1本前を使うことで先読みを避けます。
Q3. このEAの約定タイミングは、どう説明できるか?
確定した1本前の足で判定し、新しい足に入った瞬間に発注するので「昨日の引けで確認、今日の頭で約定」です。第9回のPythonは引けで約定として計算し、ここがズレの一因になります。