// Minimal emulation of the MQL5 runtime used by this project, so that the // MQL5 sources (translated by mql2cpp.py) can be compiled and tested with a // regular C++ compiler. Array accesses are bounds-checked like in MQL5, and // memory added by ArrayResize is poisoned to catch reads of uninitialised data. #pragma once #include #include #include #include #include #include #include #include #include typedef unsigned char uchar; typedef unsigned short ushort; typedef unsigned int uint; typedef unsigned long ulong; typedef long datetime; typedef std::string string; static_assert(sizeof(long) == 8, "LP64 data model required (MQL5 long is 64-bit)"); static_assert(sizeof(ulong) == 8, "ulong must be 64-bit"); #define WHOLE_ARRAY (-1) #define INVALID_HANDLE (-1) #define CP_ACP 0 #define CP_UTF8 65001 #define TICK_FLAG_BID 2 #define TICK_FLAG_ASK 4 #define TICK_FLAG_LAST 8 #define TICK_FLAG_VOLUME 16 #define TICK_FLAG_BUY 32 #define TICK_FLAG_SELL 64 struct MqlTick { datetime time; double bid; double ask; double last; ulong volume; long time_msc; uint flags; double volume_real; }; [[noreturn]] inline void MqlFatal(const char *what, long idx, long size) { fprintf(stderr, "MQL5 runtime error: %s (index %ld, size %ld)\n", what, idx, size); abort(); } template struct MqlArray { std::vector v; T &operator[](long i) { if (i < 0 || i >= (long)v.size()) MqlFatal("array out of range", i, (long)v.size()); return v[(size_t)i]; } const T &operator[](long i) const { if (i < 0 || i >= (long)v.size()) MqlFatal("array out of range", i, (long)v.size()); return v[(size_t)i]; } }; template struct MqlFixed { T a[N]; T &operator[](long i) { if (i < 0 || i >= N) MqlFatal("static array out of range", i, N); return a[i]; } const T &operator[](long i) const { if (i < 0 || i >= N) MqlFatal("static array out of range", i, N); return a[i]; } }; // ------------------------------------------------------------------ arrays template int ArraySize(const MqlArray &a) { return (int)a.v.size(); } template int ArraySize(const MqlFixed &) { return (int)N; } template int ArrayResize(MqlArray &a, int n, int reserve = 0) { if (n < 0) return -1; size_t old = a.v.size(); // like MQL5: memory is only reallocated when the new size exceeds the // allocation, and then `reserve` extra elements are allocated if ((size_t)n > a.v.capacity()) a.v.reserve((size_t)n + (size_t)(reserve > 0 ? reserve : 0)); a.v.resize((size_t)n); if ((size_t)n > old) memset((void *)(a.v.data() + old), 0xA5, ((size_t)n - old) * sizeof(T)); return n; } template void ArrayFree(MqlArray &a) { std::vector().swap(a.v); } template int ArrayInitialize(MqlArray &a, V value) { for (auto &x : a.v) x = (T)value; return (int)a.v.size(); } template int ArrayInitialize(MqlFixed &a, V value) { for (long i = 0; i < N; ++i) a.a[i] = (T)value; return (int)N; } template void ArrayFill(MqlArray &a, int start, int count, V value) { for (int i = 0; i < count; ++i) a[start + i] = (T)value; } template int ArrayCopy(MqlArray &dst, const MqlArray &src, int dst_start = 0, int src_start = 0, int count = WHOLE_ARRAY) { int ssize = (int)src.v.size(); if (src_start < 0 || dst_start < 0) MqlFatal("ArrayCopy bad start", src_start, ssize); int n = ssize - src_start; if (count >= 0 && count < n) n = count; if (n <= 0) return 0; if (dst_start + n > (int)dst.v.size()) ArrayResize(dst, dst_start + n); memmove((void *)(dst.v.data() + dst_start), (const void *)(src.v.data() + src_start), (size_t)n * sizeof(T)); return n; } // ------------------------------------------------------------------ math inline double MathRound(double x) { return std::round(x); } inline double MathLog(double x) { return std::log(x); } inline double MathAbs(double x) { return std::fabs(x); } inline double MathPow(double a, double b) { return std::pow(a, b); } inline double MathSqrt(double x) { return std::sqrt(x); } inline double MathFloor(double x) { return std::floor(x); } inline bool MathIsValidNumber(double x) { return std::isfinite(x); } template inline A MathMax(A a, B b) { return a > (A)b ? a : (A)b; } template inline A MathMin(A a, B b) { return a < (A)b ? a : (A)b; } // ------------------------------------------------------------------ misc runtime inline ulong GetMicrosecondCount() { return (ulong)std::chrono::duration_cast( std::chrono::steady_clock::now().time_since_epoch()).count(); } inline uint GetTickCount() { return (uint)(GetMicrosecondCount() / 1000); } inline int GetLastError() { return 0; } inline void ResetLastError() {} inline bool IsStopped() { return false; } // everything printed by the MQL5 code is also collected here (used by the script tests) inline std::string &MqlLog() { static std::string log; return log; } inline bool &MqlQuiet() { static bool quiet = false; return quiet; } inline void Print(const string &s) { MqlLog() += s + "\n"; if (!MqlQuiet()) printf("%s\n", s.c_str()); } // MQL5 format strings use "I64" as the 64-bit size prefix; with LP64 "l" is equivalent. inline std::string MqlFixFormat(const char *fmt) { std::string f(fmt); size_t p; while ((p = f.find("I64")) != std::string::npos) f.replace(p, 3, "l"); return f; } template inline T MqlFmtArg(T v) { return v; } inline const char *MqlFmtArg(const std::string &s) { return s.c_str(); } template inline string StringFormat(const char *fmt, A... args) { std::string f = MqlFixFormat(fmt); int n = snprintf(nullptr, 0, f.c_str(), MqlFmtArg(args)...); std::string out((size_t)(n > 0 ? n : 0), '\0'); if (n > 0) snprintf(&out[0], (size_t)n + 1, f.c_str(), MqlFmtArg(args)...); return out; } template inline void PrintFormat(const char *fmt, A... args) { Print(StringFormat(fmt, args...)); } inline int StringLen(const string &s) { return (int)s.size(); } inline int StringToCharArray(const string &s, MqlArray &a, int start = 0, int count = -1, uint cp = CP_ACP) { (void)cp; int n = (int)s.size() + 1; // includes terminating zero if (count >= 0 && count < n) n = count; if (start + n > ArraySize(a)) ArrayResize(a, start + n); for (int i = 0; i < n; ++i) a[start + i] = i < (int)s.size() ? (uchar)s[(size_t)i] : 0; return n; } inline string CharArrayToString(const MqlArray &a, int start = 0, int count = -1, uint cp = CP_ACP) { (void)cp; string r; int end = ArraySize(a); if (count >= 0 && start + count < end) end = start + count; for (int i = start; i < end; ++i) { if (a[i] == 0) break; r.push_back((char)a[i]); } return r; } // ------------------------------------------------------------------ files #define FILE_READ 1 #define FILE_WRITE 2 #define FILE_BIN 4 #define FILE_SHARE_READ 128 #define FILE_SHARE_WRITE 256 #define FILE_COMMON 4096 struct MqlFileSlot { FILE *f = nullptr; }; inline std::vector &MqlFiles() { static std::vector files(64); return files; } inline string MqlFilesRoot(int flags) { const char *root = getenv("MQL5_FILES_DIR"); string r = root ? root : "./mql5_files"; if (flags & FILE_COMMON) r += "/common"; return r; } inline string MqlFilePath(const string &name, int flags) { string p = name; for (auto &c : p) if (c == '\\') c = '/'; return MqlFilesRoot(flags) + "/" + p; } inline int FileOpen(const string &name, int flags, short delim = '\t', uint cp = CP_ACP) { (void)delim; (void)cp; string path = MqlFilePath(name, flags); const char *mode = (flags & FILE_WRITE) ? ((flags & FILE_READ) ? "r+b" : "wb") : "rb"; FILE *f = fopen(path.c_str(), mode); if (!f && (flags & FILE_WRITE) && (flags & FILE_READ)) f = fopen(path.c_str(), "w+b"); if (!f) return INVALID_HANDLE; auto &fs = MqlFiles(); for (size_t i = 1; i < fs.size(); ++i) if (!fs[i].f) { fs[i].f = f; return (int)i; } fclose(f); return INVALID_HANDLE; } inline FILE *MqlFileGet(int h) { auto &fs = MqlFiles(); if (h <= 0 || h >= (int)fs.size() || !fs[(size_t)h].f) MqlFatal("invalid file handle", h, 0); return fs[(size_t)h].f; } inline void FileClose(int h) { FILE *f = MqlFileGet(h); fclose(f); MqlFiles()[(size_t)h].f = nullptr; } inline void FileFlush(int h) { fflush(MqlFileGet(h)); } inline ulong FileSize(int h) { FILE *f = MqlFileGet(h); long cur = ftell(f); fseek(f, 0, SEEK_END); long sz = ftell(f); fseek(f, cur, SEEK_SET); return (ulong)sz; } inline ulong FileTell(int h) { return (ulong)ftell(MqlFileGet(h)); } inline bool FileSeek(int h, long off, int origin) { return fseek(MqlFileGet(h), off, origin) == 0; } inline bool FileIsEnding(int h) { FILE *f = MqlFileGet(h); return (ulong)ftell(f) >= FileSize(h); } template uint FileWriteArray(int h, const MqlArray &a, int start = 0, int count = WHOLE_ARRAY) { int n = ArraySize(a) - start; if (count >= 0 && count < n) n = count; if (n <= 0) return 0; return (uint)fwrite((const void *)(a.v.data() + start), sizeof(T), (size_t)n, MqlFileGet(h)); } template uint FileReadArray(int h, MqlArray &a, int start = 0, int count = WHOLE_ARRAY) { FILE *f = MqlFileGet(h); if (count < 0) { long cur = ftell(f); long left = (long)FileSize(h) - cur; count = (int)(left / (long)sizeof(T)); } if (start + count > ArraySize(a)) ArrayResize(a, start + count); return (uint)fread((void *)(a.v.data() + start), sizeof(T), (size_t)count, f); } inline bool FileIsExist(const string &name, int flags = 0) { FILE *f = fopen(MqlFilePath(name, flags).c_str(), "rb"); if (!f) return false; fclose(f); return true; } inline bool FileDelete(const string &name, int flags = 0) { return remove(MqlFilePath(name, flags).c_str()) == 0; } inline string IntegerToString(long v, int len = 0, ushort fill = ' ') { string s = std::to_string(v); while ((int)s.size() < len) s.insert(s.begin(), (char)fill); return s; } inline string DoubleToString(double v, int digits = 8) { char buf[64]; snprintf(buf, sizeof(buf), "%.*f", digits, v); return buf; } // ------------------------------------------------------------------ terminal emulation for scripts // Only compiled into the script tests (tests/test_scripts.cpp). #ifdef MQL5_TERMINAL_SIM #include #include #define input #define sinput #define COPY_TICKS_ALL (-1) #define COPY_TICKS_INFO 1 #define COPY_TICKS_TRADE 2 #define TIME_DATE 1 #define TIME_MINUTES 2 #define TIME_SECONDS 4 enum ENUM_SYMBOL_INFO_INTEGER { SYMBOL_DIGITS = 17, SYMBOL_CUSTOM = 1000 }; enum ENUM_TERMINAL_INFO_STRING { TERMINAL_DATA_PATH = 3, TERMINAL_COMMONDATA_PATH = 4 }; struct MqlDateTime { int year, mon, day, hour, min, sec, day_of_week, day_of_year; }; struct MqlTerminalSim { string symbol = "EURUSD"; int digits = 5; std::vector ticks; // sorted history of `symbol` std::map> custom; // custom symbol -> stored ticks std::map custom_digits; int copy_calls = 0; string last_comment; }; inline MqlTerminalSim &MqlSim() { static MqlTerminalSim sim; return sim; } #define _Symbol (MqlSim().symbol) #define _Digits (MqlSim().digits) inline datetime TimeCurrent() { auto &t = MqlSim().ticks; return t.empty() ? 0 : t.back().time; } inline bool SymbolSelect(const string &name, bool) { return name == MqlSim().symbol || MqlSim().custom.count(name) > 0; } inline bool SymbolExist(const string &name, bool &is_custom) { is_custom = MqlSim().custom.count(name) > 0; return is_custom || name == MqlSim().symbol; } inline long SymbolInfoInteger(const string &name, ENUM_SYMBOL_INFO_INTEGER prop) { if (prop == SYMBOL_DIGITS) return MqlSim().custom_digits.count(name) ? MqlSim().custom_digits[name] : MqlSim().digits; return 0; } inline string TerminalInfoString(ENUM_TERMINAL_INFO_STRING p) { return p == TERMINAL_DATA_PATH ? "C:\\Data" : "C:\\Common"; } inline void Comment(const string &s) { MqlSim().last_comment = s; } inline void Sleep(int) {} inline void MqlCopyRange(const std::vector &src, ulong from_msc, ulong to_msc, MqlArray &out) { ArrayResize(out, 0); for (const auto &t : src) if ((ulong)t.time_msc >= from_msc && (ulong)t.time_msc <= to_msc) { int n = ArraySize(out); ArrayResize(out, n + 1, 4096); out[n] = t; } } inline int CopyTicksRange(const string &sym, MqlArray &out, uint flags, ulong from_msc, ulong to_msc) { (void)flags; MqlSim().copy_calls++; if (MqlSim().custom.count(sym)) MqlCopyRange(MqlSim().custom[sym], from_msc, to_msc, out); else if (sym == MqlSim().symbol) MqlCopyRange(MqlSim().ticks, from_msc, to_msc, out); else return -1; return ArraySize(out); } inline int CopyTicks(const string &sym, MqlArray &out, uint flags, ulong from, uint count) { (void)flags; (void)from; if (sym != MqlSim().symbol) return -1; auto &t = MqlSim().ticks; size_t n = count < t.size() ? count : t.size(); ArrayResize(out, (int)n); for (size_t i = 0; i < n; ++i) out[(long)i] = t[t.size() - n + i]; return (int)n; } inline bool CustomSymbolCreate(const string &name, const string &path = "", const string &origin = "") { (void)path; if (MqlSim().custom.count(name)) return false; MqlSim().custom[name] = {}; MqlSim().custom_digits[name] = origin == MqlSim().symbol ? MqlSim().digits : 4; return true; } inline bool CustomSymbolSetInteger(const string &name, ENUM_SYMBOL_INFO_INTEGER prop, long v) { if (prop == SYMBOL_DIGITS) MqlSim().custom_digits[name] = (int)v; return true; } inline int CustomTicksReplace(const string &name, long from_msc, long to_msc, const MqlArray &ticks, uint count = (uint)WHOLE_ARRAY) { auto it = MqlSim().custom.find(name); if (it == MqlSim().custom.end()) return -1; int n = count == (uint)WHOLE_ARRAY ? ArraySize(ticks) : (int)count; std::vector kept; for (auto &t : it->second) if (t.time_msc < from_msc || t.time_msc > to_msc) kept.push_back(t); for (int i = 0; i < n; ++i) kept.push_back(ticks[i]); std::stable_sort(kept.begin(), kept.end(), [](const MqlTick &a, const MqlTick &b) { return a.time_msc < b.time_msc; }); it->second = kept; return n; } inline bool TimeToStruct(datetime t, MqlDateTime &s) { time_t tt = (time_t)t; struct tm g; gmtime_r(&tt, &g); s.year = g.tm_year + 1900; s.mon = g.tm_mon + 1; s.day = g.tm_mday; s.hour = g.tm_hour; s.min = g.tm_min; s.sec = g.tm_sec; s.day_of_week = g.tm_wday; s.day_of_year = g.tm_yday; return true; } inline datetime StructToTime(MqlDateTime &s) { struct tm g; memset(&g, 0, sizeof(g)); g.tm_year = s.year - 1900; g.tm_mon = s.mon - 1; g.tm_mday = s.day; g.tm_hour = s.hour; g.tm_min = s.min; g.tm_sec = s.sec; return (datetime)timegm(&g); } inline string TimeToString(datetime t, int mode = TIME_DATE | TIME_MINUTES) { MqlDateTime s; TimeToStruct(t, s); char buf[64]; if (mode == TIME_DATE) snprintf(buf, sizeof(buf), "%04d.%02d.%02d", s.year, s.mon, s.day); else snprintf(buf, sizeof(buf), "%04d.%02d.%02d %02d:%02d", s.year, s.mon, s.day, s.hour, s.min); return buf; } #endif