//+------------------------------------------------------------------+ //| VariableSpreadCSV.mq4 | //| Copyright 2024, TNK/TNK_SYSTEM | //| https://note.com/tnk_system | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, TNK/TNK_SYSTEM" #property link "https://note.com/tnk_system" #property version "1.0" // #property icon "TNKIcon.ico" // アイコン取り込み #property strict #include #property description "1. TDSの変動スプレッドは、当EAをバックテストすることで計測できます" #property description "2. チャートにセットすることで、リアルタイムスプレッドの計測も行えます" #property description "3. 計測したデータは、csv出力を行います(単位はpips)" #property description "4. パラメータで稼働時間を入力することで、指定時間のみの計測も可能です" #property description "5. 当ツールはEAですので、Expertsフォルダ内に配置してください" // オープンパラメータ extern int StartHour = 0; // 開始時刻 extern int StartMinute = 0; // 開始分 extern int EndHour = 0; // 終了時刻 extern int EndMinute = 0; // 終了分 //+------------------------------------------------------------------+ //| 内部パラメータ | //+------------------------------------------------------------------+ // スプレッド計測用 double myPoint; double MeasureSpread, SpreadMin = 1000, SpreadMax = 0, SpreadSum = 0, SpreadAve = 0; double SpreadMin1 = 100, SpreadMax1 = 0, SpreadSum1 = 0, SpreadAve1 = 0; int TickCount = 0, TickCount1 = 0; datetime lastBars; // ファイル出力用 string Timeframe = (Period() == 1? "M1": Period() == 5? "M5": Period() == 15? "M15": Period() == 30? "M30": Period() == 60? "H1": Period() == 240? "H4": Period() == 1440? "D1": Period() == 10080? "W1": "MN1"); string filename; int filehandle; bool StartWriting = false; // DPI換算 double DPIAdjust; //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //+------------------------------------------------------------------+ // 桁合わせ myPoint = Point; if (Digits % 2 == 1) myPoint *= 10; //+------------------------------------------------------------------+ // ファイル初期処理 filename = "Spread_" + Symbol() + "_" + Timeframe + "_" + TimeToStr(iTime(NULL, 0, 1),TIME_DATE) + "~.csv"; filehandle = FileOpen(filename, FILE_CSV|FILE_WRITE|FILE_READ, ","); //+------------------------------------------------------------------+ // CSVヘッダーを書き込む if (FileTell(filehandle) == 0) { FileWrite(filehandle, "日付", "時刻", "最大", "最小", "平均", "ティック"); //FileWrite(filehandle, "Date", "Time","Max", "Minimum", "Average", "Volume"); FileClose(filehandle); } //+------------------------------------------------------------------+ // DPI換算 double USERdpi = TerminalInfoInteger(TERMINAL_SCREEN_DPI); double DevPCdpi = 144; DPIAdjust = USERdpi / DevPCdpi; //+------------------------------------------------------------------+ // チャートコメント初期 PipsObject(); //+------------------------------------------------------------------+ return(0); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if (!IsTesting()) { ObjectsDeleteAll(); } } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| expert start function | //+------------------------------------------------------------------+ void OnTick() { if (isTime()) { TickCount++; MeasureSpread = (Ask - Bid) / myPoint; SpreadSum += MeasureSpread; SpreadMin = SpreadMin == 0 ? SpreadMin = MeasureSpread : SpreadMin; if (TickCount > 0 && SpreadSum > 0) SpreadAve = SpreadSum / TickCount; if (SpreadMax < MeasureSpread) SpreadMax = MeasureSpread; if (SpreadMin > MeasureSpread) SpreadMin = MeasureSpread; if (lastBars != Time[0]) { lastBars = Time[0]; //ファイル書込み if (StartWriting) // 二回目以降は書込み { FileWriting(); if (!IsTesting()) {PipsObject();} SpreadSum = 0; TickCount = 0; SpreadMax = 0; SpreadMin = 0; } if (!StartWriting) // 初回は書き込みせずに終了 { StartWriting = true; SpreadSum = 0; TickCount = 0; SpreadMax = 0; SpreadMin = 0; } } } } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| 書込み処理 | //+------------------------------------------------------------------+ void FileWriting() { for(int i = 0; i < 3000; i++) { FileClose(filehandle); // ファイルが開きっぱなしで開けないエラーを防ぐ filehandle = FileOpen(filename, FILE_READ|FILE_WRITE|FILE_CSV, ',');// ファイルオープン(csv)、失敗戻り値は-1 if (filehandle != INVALID_HANDLE) { FileSeek(filehandle, 0, SEEK_END); // ファイルの末尾に移動 FileWrite(filehandle, TimeToStr(iTime(NULL, 0, 1),TIME_DATE), TimeToStr(iTime(NULL, 0, 1),TIME_MINUTES), DoubleToStr(SpreadMax, 1), DoubleToStr(SpreadMin, 1), DoubleToStr(SpreadAve, 2), TickCount); FileClose(filehandle); break; } } if (filehandle == -1) Print("Error=", GetLastError(), " ファイルの書込み処理に失敗しました"); //if (filehandle == -1) Print("Error=", GetLastError(), " Failed to write file"); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| 書込みの時間制御 | //+------------------------------------------------------------------+ bool isTime() { bool result = false; if (StartHour == EndHour) { if (StartMinute >= EndMinute) { // 23+ hour interval if (Hour() != StartHour) { result = true; } else { if (Minute() >= StartMinute || Minute() < EndMinute) { result = true; } } } } else if (StartHour < EndHour) { if (Hour() == StartHour) { if (Minute() >= StartMinute) { result = true; } } else if (Hour() == EndHour) { if (Minute() < EndMinute) { result = true; } } else if (Hour() > StartHour && Hour() < EndHour) { result = true; } } else { // StartHour > EndHour if (Hour() == StartHour) { if (Minute() >= StartMinute) { result = true; } } else if (Hour() == EndHour) { if (Minute() < EndMinute) { result = true; } } else if (Hour() > StartHour || Hour() < EndHour) { result = true; } } return (result); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| コメント部に計測データ表示 | //+------------------------------------------------------------------+ void PipsObject() { // 1:00スプ考慮しない場合 string Data1 = "[Spread log (pips)]"; string Data2 = TimeToStr(iTime(NULL, 0, 1),TIME_DATE); string Data3 = TimeToStr(iTime(NULL, 0, 1),TIME_MINUTES); string Data4 = DoubleToStr(SpreadMax,1) + " [Max]"; string Data5 = SpreadMin==1000? "--- [Min]" : DoubleToStr(SpreadMin,1) + " [Min]"; string Data6 = DoubleToStr(SpreadAve,2) + " [Average]"; string Data7 = (string)TickCount + " [Volume]"; label("SP1", Data1, 4, 112, 8, clrWhite); label("SP2", Data2, 4, 94, 8, clrWhite); label("SP3", Data3, 4, 76, 8, clrWhite); label("SP4", Data4, 4, 58, 8, clrWhite); label("SP5", Data5, 4, 40, 8, clrWhite); label("SP6", Data6, 4, 22, 8, clrWhite); label("SP7", Data7, 4, 4, 8, clrWhite); } //+------------------------------------------------------------------+ void label(string name, string label_text, int pos_x, int pos_y, int s, color clr= clrBlack) { pos_x = (int)NormalizeDouble(pos_x * DPIAdjust, 0); // 別途DPI換算コードで調整 pos_y = (int)NormalizeDouble(pos_y * DPIAdjust, 0); // 別途DPI換算コードで調整 ObjectCreate(name, OBJ_LABEL, 0, 0, 0); ObjectSetString (0, name, OBJPROP_TEXT, label_text); ObjectSetInteger(0, name, OBJPROP_FONTSIZE, s); ObjectSetString (0, name, OBJPROP_FONT, "Segoe UI"); ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_RIGHT_LOWER); ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_RIGHT_LOWER); ObjectSetInteger(0, name, OBJPROP_XDISTANCE, pos_x); ObjectSetInteger(0, name, OBJPROP_YDISTANCE, pos_y); ObjectSetInteger(0, name, OBJPROP_COLOR, clr); ObjectSetInteger(0, name, OBJPROP_BACK, true); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); } //+------------------------------------------------------------------+