Tick_Forge/tools/TF_Dev.mq5
144 라인
7.2 KiB
MQL5
//+------------------------------------------------------------------+
//| 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);
}
//+------------------------------------------------------------------+