mql5-tick-compression/tests/mql5_shim.h
454 satır
15 KiloByte
C++
HamKalıcı BağlantıNormal GörünümGeçmiş
// 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