| // 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 <algorithm>
|
| #include <chrono>
|
| #include <cmath>
|
| #include <cstdint>
|
| #include <cstdio>
|
| #include <cstdlib>
|
| #include <cstring>
|
| #include <string>
|
| #include <vector>
|
|
|
| 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 <typename T>
|
| struct MqlArray {
|
| std::vector<T> 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 <typename T, long N>
|
| 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 <typename T>
|
| int ArraySize(const MqlArray<T> &a) { return (int)a.v.size(); }
|
| template <typename T, long N>
|
| int ArraySize(const MqlFixed<T, N> &) { return (int)N; }
|
|
|
| template <typename T>
|
| int ArrayResize(MqlArray<T> &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 <typename T>
|
| void ArrayFree(MqlArray<T> &a) { std::vector<T>().swap(a.v); }
|
|
|
| template <typename T, typename V>
|
| int ArrayInitialize(MqlArray<T> &a, V value) {
|
| for (auto &x : a.v) x = (T)value;
|
| return (int)a.v.size();
|
| }
|
| template <typename T, long N, typename V>
|
| int ArrayInitialize(MqlFixed<T, N> &a, V value) {
|
| for (long i = 0; i < N; ++i) a.a[i] = (T)value;
|
| return (int)N;
|
| }
|
| template <typename T, typename V>
|
| void ArrayFill(MqlArray<T> &a, int start, int count, V value) {
|
| for (int i = 0; i < count; ++i) a[start + i] = (T)value;
|
| }
|
|
|
| template <typename T>
|
| int ArrayCopy(MqlArray<T> &dst, const MqlArray<T> &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 <typename A, typename B>
|
| inline A MathMax(A a, B b) { return a > (A)b ? a : (A)b; }
|
| template <typename A, typename B>
|
| 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::microseconds>(
|
| 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 <typename T>
|
| inline T MqlFmtArg(T v) { return v; }
|
| inline const char *MqlFmtArg(const std::string &s) { return s.c_str(); }
|
| template <typename... A>
|
| 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 <typename... A>
|
| 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<uchar> &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<uchar> &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<MqlFileSlot> &MqlFiles() {
|
| static std::vector<MqlFileSlot> 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 <typename T>
|
| uint FileWriteArray(int h, const MqlArray<T> &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 <typename T>
|
| uint FileReadArray(int h, MqlArray<T> &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 <ctime>
|
| #include <map>
|
|
|
| #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<MqlTick> ticks; // sorted history of `symbol`
|
| std::map<string, std::vector<MqlTick>> custom; // custom symbol -> stored ticks
|
| std::map<string, int> 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<MqlTick> &src, ulong from_msc, ulong to_msc, MqlArray<MqlTick> &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<MqlTick> &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<MqlTick> &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<MqlTick> &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<MqlTick> 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
|