どうも潤奈です( ・Д・)
今回は波のゆくさきArmadaさんがnoteに書かれているゴトー日を研究してEAを作ろう!の記事の途中過程にある、価格変動を日本時間基準でCSV出力させるコードを参考したCSV出力用EAを書いて行きたいと思います( ・Д・)
エキスパートアドバイザ作成
まずはMetaEditorで新規作成「エキスパートアドバイザ」を作成します。
任意の名前を決めてそのまま「次へ」をクリック。
そのまま「次へ」をクリック。
「OnTester」にチェックを入れて「完了」をクリックして下さい。
※OnTesterがCSV出力させるタイミングで必要になります。
ソースコード
ではコードを書き込んで行きます。
パラメーター作成
1 2 3 4 5 6 7 8 9 10 |
#property copyright "Copyright 2021, Zyuna32246" #property link "https://zyunafx.com/" #property version "1.00" #property strict //---パラメーター設定 input int GMT_Adjust=7; //日本時間とMT4時間の差(冬季) input int sHour=10; //基準となる時間(時) input int sMinutes=0; //基準となる時間(分) input int eHour=10; //基準から遡る時間数(時) |
#property~の4行は作成した段階で挿入されている定型文なので、その下から入力して行きます。
1文づつの構成は、「修飾子 型 固有名=値; //コメント」となります。
まずMT4のサーバー時間は日本時間とタイムゾーン(GMT)が違うのでその差を修正する為に、GMT_Adjust変数でその時間差を宣言します。(証券会社によっては日本時間表示のMT4もあるのでチャートを開いて確認して下さい。)
次に基準となる時間からの価格変動を取得したいので、基準となる「sHour=時」と「sMinutes=分」をそれぞれ宣言し、その基準からどこまで(eHour=時)取得するのか?の変数を宣言します。
全体に関わる変数/配列を宣言
1 2 3 4 5 6 7 |
//---全体に関わる変数/配列 double Pips; //価格をpipsに置き換える為の値を入れる変数 double rate[]; //各分毎の価格を入れる配列 string rateTime[]; //各分毎の「時:分」を入れる配列 int Count[]; //各分毎に価格を入れた回数を入れる配列 int pNum; //配列の数を入れる変数 string sTime,eTime; //計測開始と終わりの時間を入れる変数 |
それぞれの変数/配列の説明はコメントで加えていますので詳細は割愛します。
ここで宣言する理由としては、{ }で閉じた中で宣言すると、別の{ }内で値を引き継いで利用が出来ない為(方法はありますが)、{ }の外であるこの段階で宣言します。
OnInit()内のコード
OnInit()はチャートに適用した時や、チャートの時間軸を変更した時に最初に呼び出される関数です。
なので、1回だけ計算すれば良い物などはここに書いてしまいます。
point→pipsに変換用の値を求める
1 2 3 4 5 6 7 |
//point→pipsに変換用の値を求める switch(_Digits){ case 2: Pips=0.01; break; case 3: Pips=0.01; break; case 4: Pips=0.0001; break; case 5: Pips=0.0001; break; } |
_Digits変数で適用したチャートの通貨ペアの価格の小数点以下の桁数を取得します。
その取得した桁数をswitch処理で一致するcase(条件)に進み処理を行い、break処理でswitch処理から抜けます。
小数点以下の桁数が2桁と3桁の業者があったり、決済通貨(右側)が円かドルなどでも桁数が違うのでこういった調整が必要になります。
配列数の準備
1 2 3 4 5 6 7 |
//配列に必要な数を計算 pNum=eHour*60; //遡る時間数を分数に置き換える //配列のサイズを指定 ArrayResize(rate,pNum+1); ArrayResize(rateTime,pNum+1); ArrayResize(Count,pNum+1); |
各配列には各分毎×時の値を入れる為の箱数が必要なので、eHour=遡る時間に60分を掛けた値をpNum変数に入れておきます。
次にArrayResize関数でそれぞれの配列サイズ(数)を指定します。
+1にしているのは、基準となる時間も1枠必要なので〇〇時間+1分の配列サイズが必要になります。
例)10:00を含む10分後までの必要な配列サイズは、10分+1分必要になります。
計測開始年月日を取得
1 2 3 4 5 |
//計測開始年月日として取得 例)20100217 sTime=(string)Year()+IntegerToString(Month(),2,'0')+IntegerToString(Day(),2,'0'); return(INIT_SUCCEEDED); }//OnInit()END |
最初に処理される所なので、計測開始年月日としてここで現在年月日を取得し、「+」で連結させてsTime変数に入れておきます。
Year()の頭に(string)が付いているのは、datetime型であるYear()をstring型に変換する。という意味になります。
IntegerToString関数は、指定した桁数に足らない場合、指定した文字で埋める関数です。
例えばMonth()=1の場合、1桁しかないので’0’で文字埋めして2桁にするようにしているので01となります。
OnTick()内のコード
OnTick()はティックを受信する度に処理される関数です。
サマータイム切り替え日を求める
1 2 3 4 5 6 7 |
//3月第2日曜日の日付を求める(86400=(60秒×60分×24時間)=1日の秒数) datetime SummerTime=StrToTime((string)Year()+".03.14"); SummerTime-=TimeDayOfWeek(SummerTime)*86400; //11月第1日曜日の日付を求める datetime WinterTime=StrToTime((string)Year()+".11.07"); WinterTime-=TimeDayOfWeek(WinterTime)*86400; |
アメリカ基準時刻の業者では夏時間:3月第2日曜日で切替わり、冬時間:11月第1日曜日で切替わります。
MT4には第〇〇曜日が〇日です。という事を求める関数がないので計算しないといけません。
夏時間と冬時間の求め方は同じで、StrToTime関数で現在年と”.03.14″(冬は”.11.07″)を連結させた文字をdatetime型の時間に変換して、SummerTime(WinterTime)変数に入れます。
そしてTimeDayOfWeek関数でその年月日の曜日を取得(日曜日:0~土曜日:6)して86400を掛けて秒数に変換します。
その値をSummerTime変数の年月日から減算(-=)します。
つまり、その年の3月14日が日曜日であれば0なので値に変更なしで、水曜日であれば3×86400=3日分の秒数を引いた3月11日が日曜日という事になります。
サマータイム判定
1 2 3 |
datetime CurrentTime=TimeCurrent(); //サーバー時間 int Summer=0; if(CurrentTime>=SummerTime && CurrentTime<WinterTime)Summer=1; //サマータイム期間の場合 |
TimeCurrent()関数でサーバー時間を取得してCurrentTime変数に入れます。
Summer変数には0を代入しておきます。
そしてif()関数で先ほど求めた夏時間と冬時間の切り替え日の間に、現在のサーバー時間が該当すれば、Summer変数に1を入れます。
日本時間を求める
1 2 3 |
datetime jTime=CurrentTime+(GMT_Adjust-Summer)*3600; //3600=(60秒×60分)=1時間の秒数 int jH=TimeHour(jTime); //日本時間の時を取得 int jM=TimeMinute(jTime); //日本時間の分を取得 |
GMT+2(夏時間GMT+3)のブローカーの場合、日本時間はGMT+9なので9-2=7をGMT_Adjust変数にパラメーターで設定します。
よってサーバー時間にGMT差7×3600(7時間分の秒数)を足した日本時間をjTime変数に入れます。
サマータイムの場合、MT4のサーバー時間はGMT+3になる為、GMT差は6になります。
次にTimeHour()関数とTimeMinute()関数を使って、日本時間の時と分を取得してjH変数に時、jM変数に分を入れます。
計測開始時分を確認して処理{ }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
if(jH==sHour && jM==sMinutes){ //計測開始時分の確認 double kOpen=Open[0]; //基準時間の始値を入れる datetime cTime=Time[0]; //計測開始の時間を入れる int Blank=0; //データがないローソク足計測用 static int First=0; //初回だけ動かしたい処理制御に使用 //--- //この間に計測する分数をループ処理を記述 //--- First=1; //1を入れてrateTimeにデータ入れる処理をさせない } |
if()文でパラメーターで設定した時と分が現在の日本時間と同じか確認します。
次に基準となる現在の始値Open[0]と現在の時間Time[0]をそれぞれの変数に入れます。
Blank変数には0を、First変数にはstatic変数をint型の前に書いて初回だけ0が入るようにしておきます。
最後に計測するループ処理後にFirst変数に1を入れます。
計測する分数のループ処理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//計測する分数をループ処理 for(int i=0;i<=pNum;i++){ if(TimeMinute(cTime)==TimeMinute(Time[i-Blank])){ //計測時間と取得時間が同じか確認 rate[i]+=(Open[i-Blank]-kOpen)/Pips; //取得時間の価格と基準価格の差を取得 Count[i]++; //取得時間の計測出来た回数をカウント }else{ //計測時間と取得時間が違う場合 Blank++; //データがない足の数をカウント } //--- //時間を取得してrateTime[]配列に入れる処理 //--- } |
for()文で現在値0から遡る分数の回数ループ処理させます。
ここでのi変数に入る数は基準時間からの〇分前の数になります。
まずif()文で計測開始時間cTimeの時間(分)とこれから取得する時間(分)が同じかを確認します。
同じ場合はrate[i]配列に、取得した始値から基準価格の差(point)の値を、Pips変数で割ってpoint→Pipsに単位を変換して配列に加算(+=)します。
Count[i]配列には、加算した回数として1を足して行きます。
}else{ は、もし時間が違う場合は、ヒストリカルデータの欠損が考えられるので、Blank変数にその回数を足して行きます。
ところで、Time[i-Blank]という書き方をしているのは何故かについて詳しく説明します。
まずi変数は〇分前という数で使用しますので以下の表のようになります。
しかし、例えば9:54のヒストリカルデータが欠損していたとしたら次の表のように、欠損した所から1分ズレてしまいます。(これが結構あるんです…)
理想の挙動としては欠損していた場合、もう一度同じi変数の値から取得して行かないといけません。
なので取得したい時間と違った場合、Blank変数に1を足してTime[i-Blank]とする事で欠損していた場合は値を取得せずに、次の処理でもう一度同じ値を取得するという処理になるようにしています。
1列目の時間情報を取得して格納
出力させるイメージはA列のようなデータです。
これは変動するデータではないので1回だけ処理出来れば良いです。
1 2 3 4 5 6 7 8 |
if(First==0){ //初回のループのみ処理 int tHour=TimeHour(jTime-60*i); //計測開始時間から1分前の時を取得 int tMinute=TimeMinute(cTime); //計測時間の分を取得 rateTime[i]=(string)tHour+"時"+(string)tMinute+"分"; //計測の時分を入れる } cTime-=60; //次の計測時間を1分過去に戻す }//for(int i=0;i<=pNum;i++)END First=1; //1を入れてrateTimeにデータ入れる処理をさせない |
if()文で初回のみ処理させます。
まず、日本時間jTimeからループで回ってくるi=〇分に60を掛けた秒数を引いて、その時間(時)をtHour変数に入れます。
次にcTime変数の時間(分)をtMinute変数に入れます。
そして、rateTime[]配列に取得した時間(時、分)を文字列として繋げて入れます。
if()文を抜けた所で、cTime変数の時間を手動で60秒減算(-=)しておきます。
更に、for()文を抜けた所でFirst変数に1を入れます。そうする事で最初のfor()文のループ処理だけrateTime[]配列にデータを入れる処理をする事が出来ます。
OnTester()内のコード
OnTester()関数はバックテストをした際、最後に処理される関数です。
ここにCSV出力のコードを書いて行きます。
計測終了年月日を取得
1 2 |
//計測終了年月日として取得 例)20220217 eTime=(string)Year()+IntegerToString(Month(),2,'0')+IntegerToString(Day(),2,'0'); |
コード構成は開始年月日を取得時と同じです。
この最後に処理されるタイミングの年月日を取得して繋げたデータをeTime変数に入れます。
CSVファイル準備
1 2 3 4 |
//ファイル名 例)CSV_Analysis_220100217_20220217_1000.csv string fName="CSV_Analysis_"+sTime+"_"+eTime+"_"+(string)sHour+IntegerToString(sMinutes,2,'0')+".csv"; int fDelete=FileDelete(fName,0); //同じ名前のファイルを消す int fOpen=FileOpen(fName,FILE_CSV | FILE_READ | FILE_WRITE,","); //ファイルを作る |
ファイルデータの名前として必要な情報を連結してfName変数に入れます。
CSVファイルなので最後に”.csv”を付け忘れないように注意して下さい。
FileDelete()関数でfName変数の名前のデータがあれば削除させます。
FileOpen()変数でcsvファイルを作成します。
各引数はCSVファイルを作成するのに必要な引数として覚えて頂いたら問題ないです。
見出しを書き込む
1 2 |
FileWrite(fOpen,"日本時間","Pips"); //データ1行目に時間、Pipsを書き込む FileSeek(fOpen,0,SEEK_END); //行末にシフト |
FileOpen関数で開いているファイルfOpenに”日本時間”,”Pips”を書き込みます。それぞれの要素を「,」で区切る事でセルが分かれます。
FileSeek関数で行末(ファイルの最後)に移動します。
各分毎のデータを書き込む
1 2 3 4 5 6 7 8 9 |
for(int i=pNum;i>=0;i--){ //ループ処理で書き込む double pRange=0; //価格変動幅を入れる変数 pRange=rate[i]/Count[i]; //取得合計した価格差を取得回数で割って平均値を出す FileWrite(fOpen,rateTime[i],pRange); //書き込む } FileClose(fOpen); //ファイルを閉じる return(true); }//OnTester()END |
for()文でi変数には取得した分数を入れて、減算させながらループ処理させて行きます。
まず価格変動幅を入れる変数pRangeを宣言します。
そのpRange変数に取得合計したrate[]配列を取得回数Count[]配列で割って平均値を入れます。
FileWrite関数で指定ファイルfOpenに時間rateTime[]と変動幅(平均)pRangeを書き込みます。
FileWrite関数はファイルの書き込み後に改行文字が追加されるので自動で改行されます。
すべて書き込み後、FileClose関数で指定ファイルfOpenを閉じます。
コンパイル
コードをすべて書き込んだ後に「コンパイル」を実行します。
コンパイルを実行して、下にエラーが出なければこれでこのコードが使用する事が出来ます。
バックテスト時の設定
このコードをバックテストする際は以下の設定でお願いします。
期間(時間足):M1(1分足)
モデル:始値のみ(全ティックを選択しないでください)
このコードは1分毎に価格変動のデータを集計しますので、1分足の設定をして下さい。
そして1分毎の時間はコード内で確認していますが、1分の間でのティック処理までの制御は行っていません
従って全ティックでバックテストを行ってしまうと1分×ティック数のデータを集計してしまう事になりますので、値が変化してしまいます。
※より正確になるという事ではありません。
まとめ
今回は指定した時間に該当した時点からの価格変動幅を取得しましたが、そこに日付条件を入れる事で特定の日の動きや、インジケーターである条件になった時の動きなど応用は沢山出来ます。
バックテストなどで勝てるロジック(結果を見て)探すのも良いですが、そのベースになるローソク足(価格の動き)を分析する事はロジックを考える上でもかなり重要だと思います。
そういった点で波のゆくさきArmadaさんのnoteにはその考え方が書かれておりとても勉強になります( ・Д・)
EAの全ソースコード
以下の記事をご購入頂きましたら今回のEAの全ソースコードをまとめて閲覧する事が出来ますので、コピーして作成すればすぐにバックテストで試す事が出来ます( ・Д・)
尚、この無料記事にすべて記述していますので、模写して行けば同じ物が出来ます。
時短を考える方、この記事を評価して頂ける方だけご購入ご検討下さい( ・Д・)
コメント
CSV_Analysis003 USDJPY,M1: array out of range in ‘CSV_Analysis003.mq4’ (83,47)
とエラーが出て、CSVが出力できなくなりました。
何が原因かわかりますか?
お手数ですがよかったら教えてください。
追記
始値のみのビジュアルモードにチェックを入れたら吐き出されますがそういった仕様なのでしょうか??
お問合わせありがとうございます!
まずarray out of rangeのエラーに関しては、恐らく全ティックでのバックテストを行った際に発生していると思われます。
こちらに関して、私でも事象を確認致しましたが、原因の特定には至っていません。
このコードは1分毎のデータを取得する為の物ですので、バックテスト時のモデルは「始値のみ」で問題ありません。勿論、期間は「M1」に設定して下さい。
そしてビジュアルモードにチェックは入れなくても処理はされるはずですので、お試し下さい( ・Д・)
特定の時間におけるRSIやMAの数値を条件に入れたいのですが、思い当たる場所に何個かコードを入れたのですが価格変動が取得できませんでした。どこに条件文を挿入すればよろしいでしょうか?
if(rsi>70 && Close[1]>Open[1] && hour==10 && minute==45)
のようなコードを入れてこの条件での価格変動を取得したいです。
お手隙の際にご返信いただけますと幸いです。
すみません。RSIやMAを「条件」に入れて価格を取得したい意味がよく分からないかもです。
例に書いて頂いている条件がEA等のエントリー条件なら分かるのですが。
価格変動は条件で絞って取得するのではなく、前後のデータも拾ってその変動を見るものだと思います。
その結果、10時45分が高値になりやすいけど、精度を上げる為にRSIで条件を絞ってエントリー。という使い方ではないでしょうか。
もしくはやりたい事はこちらでしょうか?
RSIの値をRSI変動として取得して、RSIが70以上になりやすい時間帯を探す。であれば、こちらの記事のコードは参考になると思います。
Open[i]→iRSI(_Symbol,0,14,PRICE_CLOSE,i)に置き換えるような形で調整してみては如何でしょうか。( ・Д・)
説明不足ですみません。
EAのエントリー条件です。その条件でエントリーした時のみの前後の価格変動を取得するという意味でした。
返信が遅くなりました。
質問の意図が上手く汲み取れずすみません。
記事の通りでは価格変動取得出来ましたか?要は特定の条件下のみでの価格変動に絞って取得したいという事ですね!
それであれば最初のコメントに書いて頂いているif文の中で問題ないかと思うのですが、取得出来ないというのが何故でしょうね( ・Д・)?
全く取得出来ていないのか、取得している回数が極端に少ない場合は条件が思ったより厳しいのかもしれません。
if文内に入った回数をカウントする変数を用意してみるのもいいかもしれません( ・Д・)
記事の通りで条件を入れなければ取得することができました!非常に有益な記事を記述してくださりありがとうございます。
エントリーの条件式をif文で{計測時間~OnTick終了}の手前まで囲ってテスターを回しましたが、CSVに価格変動は反映されませんでした。条件式は通過してカウントされているので原因がわかりませんでした。
//条件式
int count = 0;
double rsi = iRSI(NULL,PERIOD_M1,14,PRICE_CLOSE,1);
double rsi2 = iRSI(NULL,PERIOD_M1,14,PRICE_CLOSE,2);
if(rsi>=70&&rsi2Open[1]){
//計測開始時分を確認して処理{ }
if(jH==sHour && jM==sMinutes){ //計測開始時分の確認
double kOpen=Open[0]; //基準時間の始値を入れる
datetime cTime=Time[0]; //計測開始の時間を入れる
int Blank=0; //データがないローソク足計測用
static int First=0; //初回だけ動かしたい処理制御に使用
…….
if文で囲っているのに条件式を通過してカウントしているのは謎ですね。
ちなみにコメントで書いて下さっている条件式でrsi2とOpen[1]を比較しているのは記入間違いですか?
RSI値と価格は比較出来ないと思いますので気になりました。
ただ、rsi>=70は正しいと思いますので通過しているのが謎です。
バックテストしている条件が分かりませんが、RSI値を1分足から取得している様ですのでrsiとrsi2をPrint関数で値を表示してみて正しく取得出来ているか確認してみて下さい。
何か分かるかもしれません( ・Д・)
if(rsi>=70&&rsi2Open[1]){
記入ミスでした。正しくはこのように書いていました。
式は通過しているのであっていると思います。
すみません。コメントを見逃していました。。。
ん?正しくはの式がありませんが、どういう事でしょうか?
ちなみにRSI値を条件に加えた記事を書いてみましたので、こちらを参考にしたらどうでしょうか。
https://zyunafx.com/csv_analysis-4/
ソースコードを購入させていただきました。
コピペしてストラテジーテスターでセットしたのですが、記録が取れているのか、CSVに落とせているのかが分かりません。
①購入コードコピペ
②1分足ヒストリカルデータ取得済み
③TDSではありません
④ストラテジーテスターでEAセット
⑤モデル:始値のみ
⑥期間:M1
⑦パラメータ入力↓
夕方16時から夜の21時までの1分足価格変動を調べたいので
・日本時間とMT4時間の差:GMT+2のスリトレなので【7】を入力
・基準となる時間(時):夕方16時からなので【16】を入力
・基準となる時間(分):0分からなので【0】を入力
・基準から遡る時間数(時):夜の21時までなので【21】を入力
⑧スタート → レポートを見るとMT4バックテストのモデリング品質がn/aでメーターが全て赤色
➈ファイル → データフォルダ開く → MQL4 → Files → 何も入っていない
どこか手順が違うのかもしくは用意したデータが悪いのか判断できないので、大変申し訳ないのですがお手すきの時にお返事いただけますでしょうか?よろしくお願いいたします。
Nanaho様
ご購入ありがとうございます。
まず手順の①~⑥は問題ないと思われますが、⑦パラメータ入力に間違いがありますので訂正させて頂きます。
バックテストは過去から未来に向かって行われる中で、このプログラムは基準時間から過去に向かって計測を行います。
つまり夕方16時から夜21時までの価格変動であればパラメーターは以下の通りです。
・基準となる時間(時):21
・基準となる時間(分):0
・基準から遡る時間数(時):5(21時から16時までの時間数)
そしてバックテスト出力されるCSVデータは、MQL4ではなく tester → filesになりますのでそちらのフォルダを確認して下さい。
あと、モデリング品質は全ティックでしか表示されませんので気にされなくて大丈夫です。
よろしくお願い致します( ゚Д゚)
ご返答いただきありがとうございます。
たった今動作確認しましたら、希望の時間帯、CSVファイルの出力もされていました。
今後ともよろしくお願いいたします。
Yusuke