//+------------------------------------------------------------------+ //| TF_Dev.mq5 - DEVELOPER Expert for Tick Forge (not the product). | //| Runs the same window as the indicator inside an Expert, and adds | //| headless generation + screenshot modes used by tools/tf_run.py. | //| Copy next to TF_Gui.mqh / TF_Engine.mqh / TF_Expr.mqh to compile. | //| Copyright (c) 2026 Joshua Dunnink. MIT License. | //+------------------------------------------------------------------+ #property copyright "Copyright (c) 2026 Joshua Dunnink - MIT License" #property link "https://forge.mql5.io/joshuadunnink/Tick_Forge" #property version "1.04" #define TF_VERSION "1.04" #property description "Tick Forge builds custom symbols for the Strategy Tester from your broker's own history." #property description "Shift the timestamps, rescale or reshape the prices with a typed function, test your EA on the result." #property description "Utility: it never trades. Free and open source (MIT). No DLL, no internet access." #property strict #include "TF_Gui.mqh" bool g_gui_run = false; input group "Automation (leave on defaults to use the window)" input bool InpAuto = false; // Run without the window, using the values below input string InpSource = ""; // Source symbol input string InpTarget = ""; // Custom symbol name input string InpFromDate = ""; // First day YYYY.MM.DD input string InpToDate = ""; // Last day YYYY.MM.DD input int InpDataMode = 0; // 0 = real ticks + M1, 1 = M1 bars only input double InpOffsetHours = 0; // Timestamp shift in hours (negative = earlier) input string InpFunction = "p"; // Price function input bool InpScaleSpread = true; // Spread follows the price factor input bool InpOverwrite = false; // Overwrite an existing custom symbol without asking input bool InpCloseTerminal = false; // Automation only: close the terminal when done (flushes the custom symbol list to disk) input bool InpGuiRun = false; // Developer: open the window, fill it from the values above, press GENERATE, report, exit input bool InpGuiAudit = false; // Developer: like InpGuiRun but press "Audit set" (7 symbols named InpTarget + suffix) input string InpScreenshot = ""; // Developer: open the window, preview the values above, save a screenshot (file name), then exit //+------------------------------------------------------------------+ void RunAuto() { CTFJob job; job.src = InpSource; job.name = InpTarget; job.from = StringToTime(InpFromDate + " 00:00"); job.to = StringToTime(InpToDate + " 00:00"); job.mode = InpDataMode; job.offset_sec = (long)MathRound(InpOffsetHours * 3600.0); job.expr = InpFunction; job.scale_spread = InpScaleSpread; job.auto_digits = true; bool custom, exists = TF_SymbolExists(job.name, custom); if(job.src == "" || job.name == "" || job.from <= 0 || job.to <= 0) { Print("TickForge: automation needs InpSource, InpTarget, InpFromDate, InpToDate"); return; } if(exists && !custom) { Print("TickForge: '", job.name, "' is a real symbol - refusing"); return; } if(exists && !InpOverwrite) { Print("TickForge: '", job.name, "' exists - set InpOverwrite to replace it"); return; } SymbolSelect(job.src, true); CTFEngine eng; CTFResult res; bool ok = eng.Run(job, res); if(ok) { MqlRates a[], b[]; MqlTick ta[], tb[]; datetime fb = job.from + (datetime)job.offset_sec; int na = CopyRates(job.src, PERIOD_M1, job.from + 3600, job.from + 3720, a), nb = CopyRates(job.name, PERIOD_M1, fb + 3600, fb + 3720, b); if(na > 0 && nb > 0) PrintFormat("TICKFORGE_VERIFY bar src %s O=%.2f C=%.2f spr=%d | dst %s O=%.2f C=%.2f spr=%d", TimeToString(a[0].time), a[0].open, a[0].close, a[0].spread, TimeToString(b[0].time), b[0].open, b[0].close, b[0].spread); int ka = CopyTicksRange(job.src, ta, COPY_TICKS_ALL, (ulong)job.from * 1000, (ulong)job.from * 1000 + 60000); int kb = CopyTicksRange(job.name, tb, COPY_TICKS_ALL, (ulong)fb * 1000, (ulong)fb * 1000 + 60000); if(ka > 0 && kb > 0) PrintFormat("TICKFORGE_VERIFY tick n=%d/%d src %s bid=%.2f ask=%.2f | dst %s bid=%.2f ask=%.2f", ka, kb, TimeToString(ta[0].time, TIME_DATE|TIME_SECONDS), ta[0].bid, ta[0].ask, TimeToString(tb[0].time, TIME_DATE|TIME_SECONDS), tb[0].bid, tb[0].ask); } PrintFormat("TICKFORGE_RESULT ok=%d name=%s ticks=%I64d bars=%I64d digits=%d p0=%.5f err=%s bars_path=%s", ok, job.name, res.ticks, res.bars, res.digits, res.p0, res.err, TF_DataPath(job.name)); } int OnInit() { PrintFormat("Tick Forge v1.04 | chart %s", _Symbol); if(MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_OPTIMIZATION)) { Print("Tick Forge is a utility for a live chart (it creates symbols); it does nothing in the Strategy Tester."); return INIT_SUCCEEDED; } if(InpAuto) { RunAuto(); if(InpCloseTerminal) { Sleep(3000); TerminalClose(0); } else ExpertRemove(); return INIT_SUCCEEDED; } g_dlg = new CTickForgeDlg(); if(!g_dlg.Create(0, "Tick Forge 1.04", 0, 20, 20, 20 + TF_W, 20 + TF_H)) { const string need = StringFormat("Tick Forge needs a chart of at least %d x %d pixels (enlarge the window or maximise the chart).", TF_W + 40, TF_H + 40); Print(need); Comment(need); delete g_dlg; g_dlg = NULL; return INIT_SUCCEEDED; } g_dlg.Run(); EventSetMillisecondTimer(30); if(InpGuiAudit) { g_dlg.FillAndAudit(InpSource, InpTarget, InpFromDate, InpToDate, InpDataMode); g_gui_run = true; } else if(InpGuiRun) { g_dlg.FillAndGenerate(InpSource, InpTarget, InpFromDate, InpToDate, InpDataMode, InpOffsetHours, InpFunction); g_gui_run = true; } if(InpScreenshot != "") { g_dlg.ShotPreview(InpSource, InpFromDate, InpToDate, InpOffsetHours, InpFunction); ChartRedraw(); Sleep(1500); PrintFormat("TICKFORGE_SHOT %s ok=%d chart=%dx%d", InpScreenshot, ChartScreenShot(0, InpScreenshot, 1100, 700), (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS), (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS)); ExpertRemove(); } return INIT_SUCCEEDED; } void OnDeinit(const int reason) { EventKillTimer(); if(g_dlg != NULL) { g_dlg.Destroy(reason); delete g_dlg; g_dlg = NULL; } Comment(""); } void OnTick() {} void OnTimer() { if(g_dlg == NULL) return; g_dlg.Tick(); if(g_gui_run && !g_dlg.Busy()) { g_gui_run = false; bool custom, ex = TF_SymbolExists(InpTarget, custom); PrintFormat("TICKFORGE_RESULT guirun exists=%d bars_in_tester_view=%d status=%s", ex, (int)Bars(InpTarget, PERIOD_M1), g_dlg.Status()); if(InpCloseTerminal) { TerminalClose(0); return; } } if(g_dlg.Closed()) ExpertRemove(); } void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(g_dlg != NULL) g_dlg.ChartEvent(id, lparam, dparam, sparam); } //+------------------------------------------------------------------+