Adwizard/Utils/MTTester.mqh

1437 lines
86 KiB
MQL5
Raw Permalink Normal View History

2025-04-11 13:28:40 +03:00
<EFBFBD><EFBFBD>#property strict
#include <WinAPI\WinAPI.mqh>
#define GA_ROOT 0x00000002
#define BM_CLICK 0x000000F5
#define WM_KEYDOWN 0x0100
#define WM_CHAR 0x0102
#define WM_LBUTTONDOWN 0x0201
#define WM_CLOSE 0x0010
#define WM_COMMAND 0x0111
#define VK_RETURN 0x0D
#define VK_ESCAPE 0x1B
#define VK_HOME 0x24
#define VK_LEFT 0x25
#define VK_RIGHT 0x27
#define VK_DOWN 0x28
#define VK_DELETE 0x2E
#define DTM_SETSYSTEMTIME 0x1002
#define GW_HWNDNEXT 2
#define GW_CHILD 5
#define CB_GETLBTEXT 0x0148
#define CB_GETLBTEXTLEN 0x0149
#define GMEM_MOVEABLE 2
#define CF_UNICODETEXT 13
#define CF_TEXT 1
#define ID_EDIT_PASTE 0xE125
#define ID_EDIT_COPY 0xE122
#define PROCESS_QUERY_INFORMATION 0x0400
#define FILE_ATTRIBUTE_DIRECTORY 0x00000010
#define INVALID_FILE_ATTRIBUTES UINT_MAX
#define LVM_GETITEMCOUNT 0x1004
#import "user32.dll"
PVOID SendMessageW( HANDLE, uint, PVOID, int &[] );
PVOID SendMessageW( HANDLE, uint, PVOID, short &[] );
#import
#import "kernel32.dll"
bool CopyFileW( string lpExistingFileName, string lpNewFileName, bool bFailIfExists );
int ReadFile( HANDLE file, uchar &buffer[], uint number_of_bytes_to_read, uint &number_of_bytes_read, PVOID overlapped );
int WriteFile( HANDLE file, const uchar &buffer[], uint number_of_bytes_to_write, uint &number_of_bytes_written, PVOID overlapped );
string lstrcatW( HANDLE Dst, string Src );
int lstrcpyW( HANDLE ptrhMem, string Text );
int QueryFullProcessImageNameW( HANDLE process, uint flags, short &Buffer[], uint &size );
#import
class MTTESTER
{
private:
static string arrayToHex(uchar &arr[])
{
string res = "";
for(int i = 0; i < ::ArraySize(arr); i++)
{
res += ::StringFormat("%.2X", arr[i]);
}
return(res);
}
// https://www.mql5.com/en/code/26945
static string instance_id(const string strPath)
{
string strTest = strPath;
::StringToUpper(strTest);
// Convert the string to widechar Unicode array (it will include a terminating 0)
ushort arrShort[];
const int n = ::StringToShortArray(strTest, arrShort); // n includes terminating 0, and should be dropped
// Convert data to uchar array for hashing
uchar widechars[];
::ArrayResize(widechars, (n - 1) * 2);
for(int i = 0; i < n - 1; i++)
{
widechars[i * 2] = (uchar)(arrShort[i] & 0xFF);
widechars[i * 2 + 1] = (uchar)((arrShort[i] >> 8) & 0xFF);
}
// Do an MD5 hash of the uchar array, containing the Unicode string
uchar dummykey[1] = {0};
uchar result[];
if(::CryptEncode(CRYPT_HASH_MD5, widechars, dummykey, result) == 0)
{
::Print("Error ", ::GetLastError());
return NULL;
}
return arrayToHex(result);
}
static long GetHandle( const int &ControlID[] )
{
long Handle = MTTESTER::GetTerminalHandle();
const int Size = ::ArraySize(ControlID);
for (int i = 0; i < Size; i++)
Handle = user32::GetDlgItem(Handle, ControlID[i]);
return(Handle);
}
static int GetLastPos( const string &Str, const short Char )
{
int Pos = ::StringLen(Str) - 1;
while ((Pos >= 0) && (Str[Pos] != Char))
Pos--;
return(Pos);
}
static string GetPathExe( const HANDLE Handle )
{
string Str = NULL;
uint processId = 0;
if (user32::GetWindowThreadProcessId(Handle, processId))
{
const HANDLE processHandle = kernel32::OpenProcess(PROCESS_QUERY_INFORMATION, false, processId);
if (processHandle)
{
short Buffer[MAX_PATH] = {0};
uint Size = ::ArraySize(Buffer);
if (kernel32::QueryFullProcessImageNameW(processHandle, 0, Buffer, Size))
Str = ::ShortArrayToString(Buffer, 0, Size);
kernel32::CloseHandle(processHandle);
}
}
return(Str);
}
static string GetClassName( const HANDLE Handle )
{
string Str = NULL;
short Buffer[MAX_PATH] = {0};
if (user32::GetClassNameW(Handle, Buffer, ::ArraySize(Buffer)))
Str = ::ShortArrayToString(Buffer);
return(Str);
}
static string StringBetween( string &Str, const string StrBegin, const string StrEnd = NULL )
{
string Res = NULL;
int PosBegin = ::StringFind(Str, StrBegin);
if ((PosBegin >= 0) || (StrBegin == NULL))
{
PosBegin = (PosBegin >= 0) ? PosBegin + ::StringLen(StrBegin) : 0;
const int PosEnd = ::StringFind(Str, StrEnd, PosBegin);
if (PosEnd != PosBegin)
Res = ::StringSubstr(Str, PosBegin, (PosEnd >= 0) ? PosEnd - PosBegin : -1);
Str = (PosEnd >= 0) ? ::StringSubstr(Str, PosEnd + ::StringLen(StrEnd)) : NULL;
if (Str == "")
Str = NULL;
}
return((Res == "") ? NULL : Res);
}
static bool GetClipboard( string &Str, const int Attempts = 3 )
{
bool Res = false;
Str = NULL;
for (int j = 0; (j < Attempts) && !Res/* && !::IsStopped()*/; j++) // ;>10;L=><C 45AB@C:B>@C <>65B ?>=04>18BLAO
if (user32::OpenClipboard(0))
{
const HANDLE hglb = user32::GetClipboardData(CF_TEXT);
if (hglb)
{
const HANDLE lptstr = kernel32::GlobalLock(hglb);
if (lptstr)
{
kernel32::GlobalUnlock(hglb);
short Array[];
const int Size = ::StringToShortArray(kernel32::lstrcatW(lptstr, ""), Array) - 1;
//Str = ::CharArrayToString(_R(Array).Bytes); // TypeToBytes.mqh
if ((Size > 0) && ::StringReserve(Str, Size << 1))
for (int i = 0; i < Size; i++)
{
const uchar Char1 = (uchar)Array[i];
if (Char1)
Str += ::CharToString(Char1);
else
break;
const uchar Char2 = uchar(Array[i] >> 8);
if (Char2)
Str += ::CharToString(Char2);
else
break;
}
Res = true;
}
}
user32::CloseClipboard();
}
else
::Sleep(10);
return(Res);
}
static bool SetClipboard( const string Str, const int Attempts = 3 )
{
bool Res = false;
for (int j = 0; (j < Attempts) && !Res/* && !::IsStopped()*/; j++) // ;>10;L=><C 45AB@C:B>@C <>65B ?>=04>18BLAO
if (user32::OpenClipboard(0))
{
if (user32::EmptyClipboard())
{
const HANDLE hMem = kernel32::GlobalAlloc(GMEM_MOVEABLE, (::StringLen(Str) + 1) << 1);
if (hMem)
{
const HANDLE ptrMem = kernel32::GlobalLock(hMem);
if (ptrMem)
{
kernel32::lstrcpyW(ptrMem, Str);
kernel32::GlobalUnlock(hMem);
Res = user32::SetClipboardData(CF_UNICODETEXT, hMem);
}
if (!ptrMem || !Res)
kernel32::GlobalFree(hMem);
}
}
user32::CloseClipboard();
}
else
::Sleep(10);
return (Res);
}
static string GetFreshFileName( const string Path, const string Mask )
{
string Str = NULL;
FIND_DATAW FindData;
const HANDLE handle = kernel32::FindFirstFileW(Path + Mask, FindData);
if (handle != INVALID_HANDLE)
{
ulong MaxTime = 0;
// ulong Size = 0;
do
{
const ulong TempTime = ((ulong)FindData.ftLastWriteTime.dwHighDateTime << 32) + FindData.ftLastWriteTime.dwLowDateTime;
if (TempTime > MaxTime)
{
MaxTime = TempTime;
Str = ::ShortArrayToString(FindData.cFileName);
// Size = ((ulong)FindData.nFileSizeHigh << 32) + FindData.nFileSizeLow;;
}
}
while (kernel32::FindNextFileW(handle, FindData));
kernel32::FindClose(handle);
}
return((Str == NULL) ? NULL : Path + Str);
}
static int DeleteFolder( const string FolderName )
{
int Res = 0;
FIND_DATAW FindData;
const HANDLE handle = kernel32::FindFirstFileW(FolderName + "\\*", FindData);
if (handle != INVALID_HANDLE)
{
do
{
if (FindData.cFileName[0] != '.')
{
const string Name = FolderName + "\\" + ::ShortArrayToString(FindData.cFileName);
Res += (bool)(FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? MTTESTER::DeleteFolder(Name) : kernel32::DeleteFileW(Name);
}
}
while (kernel32::FindNextFileW(handle, FindData));
kernel32::FindClose(handle);
Res += kernel32::RemoveDirectoryW(FolderName);
}
return(Res);
}
static int GetFileNames( const string Path, const string Mask, string &FileNames[] )
{
::ArrayFree(FileNames);
ulong Times[];
FIND_DATAW FindData;
const HANDLE handle = kernel32::FindFirstFileW(Path + Mask, FindData);
if (handle != INVALID_HANDLE)
{
do
{
FileNames[::ArrayResize(FileNames, ::ArraySize(FileNames) + 1) - 1] = ::ShortArrayToString(FindData.cFileName);
Times[::ArrayResize(Times, ::ArraySize(Times) + 1) - 1] = ((ulong)FindData.ftLastWriteTime.dwHighDateTime << 32) + FindData.ftLastWriteTime.dwLowDateTime;
}
while (kernel32::FindNextFileW(handle, FindData));
kernel32::FindClose(handle);
}
ulong Pos[][2];
const int Size = ::ArrayResize(Pos, ::ArraySize(FileNames));
for (int i = 0; i < Size; i++)
{
Pos[i][0] = Times[i];
Pos[i][1] = i;
}
::ArraySort(Pos);
string Array[];
for (int i = ::ArrayResize(Array, Size) - 1; i >= 0; i--)
Array[i] = FileNames[(int)Pos[i][1]];
::ArraySwap(Array, FileNames);
return(Size);
}
static string GetLastTstCacheFileName2( void )
{
return(MTTESTER::GetFreshFileName(::TerminalInfoString(TERMINAL_DATA_PATH)+ "\\Tester\\cache\\", "*.tst"));
}
static string GetLastOptCacheFileName2( void )
{
return(MTTESTER::GetFreshFileName(::TerminalInfoString(TERMINAL_DATA_PATH)+ "\\Tester\\cache\\", "*.opt"));
}
static string GetLastConfigFileName( void )
{
return(MTTESTER::GetFreshFileName(::TerminalInfoString(TERMINAL_DATA_PATH)+ "\\MQL5\\Profiles\\Tester\\", "*.ini"));
}
static bool IsChart( const long Handle )
{
/*
bool Res = false;
for (long Chart = ::ChartFirst(), handle = user32::GetDlgItem(Handle, 0xE900);
(Chart != -1) && !(Res = (::ChartGetInteger(Chart, CHART_WINDOW_HANDLE) == handle));
Chart = ::ChartNext(Chart))
;
return(Res);
*/
return((bool)user32::GetDlgItem(user32::GetDlgItem(Handle, 0xE900), 0x27CE));
}
static bool SetTime( const long Handle, const datetime time )
{
const bool Res = time && MTTESTER::IsReady();
if (Res)
{
MqlDateTime TimeStruct;
::TimeToStruct(time, TimeStruct);
int SysTime[2];
SysTime[0] = (TimeStruct.mon << 16) | TimeStruct.year;
SysTime[1] = (TimeStruct.day << 16) | TimeStruct.day_of_week;
user32::SendMessageW(Handle, DTM_SETSYSTEMTIME, 0, SysTime);
}
return(Res || !time);
}
static string GetComboBoxString( const long Handle )
{
short Buf[];
// https://www.mql5.com/ru/forum/318305/page20#comment_17747389
::ArrayResize(Buf, (int)user32::SendMessageW(Handle, CB_GETLBTEXTLEN, 0, 0 ) + 1);
user32::SendMessageW(Handle, CB_GETLBTEXT, 0, Buf);
return(::ShortArrayToString(Buf));
}
static void Sleep2( const uint Pause )
{
const uint StartTime = ::GetTickCount();
while (!::IsStopped() && (::GetTickCount() - StartTime) < Pause)
::Sleep(0);
return;
}
static bool StartTester( void )
{
string Str;
return(MTTESTER::GetSettings2(Str));
}
static bool IsReady2( void )
{
static bool bInit = MTTESTER::StartTester();
static const int ControlID[] = {0xE81E, 0x804E, 0x2712, 0x4196};
static const long Handle = MTTESTER::GetHandle(ControlID);
ushort Str[6];
if (!::IsStopped()) // =0G5 7028A0=85 ?@8 A=OB88 A>25B=8:0.
user32::GetWindowTextW(Handle, Str, sizeof(Str) / sizeof(ushort));
const string Name = ::ShortArrayToString(Str);
bool Res = (Name == "!B0@B") || (Name == "Start");
static int Count = 0;
if (!Res && (Name == "") && (Count < 10))
{
MTTESTER::StartTester();
Count++;
Res = MTTESTER::IsReady2();
}
Count = 0;
return(Res);
}
static string GetStatusString( void )
{
static const int ControlID[] = {0xE81E, 0x804E, 0x2791};
static const long Handle = MTTESTER::GetHandle(ControlID);
ushort Str[64];
if (!::IsStopped()) // =0G5 7028A0=85 ?@8 A=OB88 A>25B=8:0.
user32::GetWindowTextW(Handle, Str, sizeof(Str) / sizeof(ushort));
return(::ShortArrayToString(Str));
}
static int GetAgentNames( string &AgentNames[] )
{
return(MTTESTER::GetFileNames(::TerminalInfoString(TERMINAL_DATA_PATH)+ "\\Tester\\", "Agent*.*", AgentNames));
}
static string GetBeginFileName( void )
{
string Res = NULL;
string Str;
if (MTTESTER::GetSettings2(Str))
{
string ExpertName = MTTESTER::GetValue(Str, "Expert");
const int Pos = MTTESTER::GetLastPos(ExpertName, '\\') + 1;
ExpertName = ::StringSubstr(ExpertName, Pos, ::StringLen(ExpertName) - Pos - 4);
Res = ExpertName + "." + MTTESTER::GetValue(Str, "Symbol") + "." + MTTESTER::GetValue(Str, "Period");
string FromDate = MTTESTER::GetValue(Str, "FromDate");
::StringReplace(FromDate, ".", NULL);
string ToDate = MTTESTER::GetValue(Str, "ToDate");
::StringReplace(ToDate, ".", NULL);
Res += "." + FromDate + "_" + ToDate + "." + MTTESTER::GetValue(Str, "Model");
}
return(Res);
}
static int GetJournalItemCount( void )
{
static const int ControlID[] = {0xE81E, 0x804E, 0x28ED};
static const long Handle = MTTESTER::GetHandle(ControlID);
return((int)user32::SendMessageW(Handle, LVM_GETITEMCOUNT, 0, 0));
}
#define GENERIC_READ 0x80000000
#define GENERIC_WRITE 0x40000000
#define SHARE_READ 1
#define OPEN_EXISTING 3
#define OPEN_ALWAYS 4
#define CREATE_ALWAYS 2
static bool IsLibraryOrService( const string FileName )
{
bool Res = true;
const HANDLE handle = kernel32::CreateFileW(FileName, GENERIC_READ, SHARE_READ, 0, OPEN_EXISTING, 0, 0);
if (handle != INVALID_HANDLE)
{
uchar Buffer[4];
uint Read;
kernel32::ReadFile(handle, Buffer, sizeof(Buffer), Read, 0);
Res = (Read < sizeof(Buffer)) || ((Buffer[3] == 3) || (Buffer[3] == 5)); // 1 - Script, 2 - Expert, 3 - Library, 4 - Indicator, 5 - Service.
kernel32::CloseHandle(handle);
}
return(Res);
}
static int GetEX5FileNames( const string FolderName, string &FileNames[] )
{
int Res = 0;
FIND_DATAW FindData;
const HANDLE handle = kernel32::FindFirstFileW(FolderName + "\\*", FindData);
if (handle != INVALID_HANDLE)
{
do
{
if (FindData.cFileName[0] != '.')
{
string Name = FolderName + "\\" + ::ShortArrayToString(FindData.cFileName);
if ((bool)(FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
Res += MTTESTER::GetEX5FileNames(Name, FileNames);
else if (::StringToLower(Name) && ::StringSubstr(Name, ::StringLen(Name) - 4) == ".ex5" && !MTTESTER::IsLibraryOrService(Name))
{
FileNames[::ArrayResize(FileNames, ::ArraySize(FileNames) + 1) - 1] = Name;
Res++;
}
}
}
while (kernel32::FindNextFileW(handle, FindData));
kernel32::FindClose(handle);
}
return(Res);
}
static string GetTerminalPath( const HANDLE Handle )
{
string Path = MTTESTER::GetPathExe(Handle);
return(::StringSubstr(Path, 0, MTTESTER::GetLastPos(Path, '\\')));
}
static void RefreshNumbersEX5( const HANDLE TerminalHandle )
{
const uint MT5InternalMsg = user32::RegisterWindowMessageW("MetaTrader5_Internal_Message");
static const uchar Codes[] = {0x01, 0x02, 0x03, 0x1A};
user32::SendMessageW(TerminalHandle, WM_COMMAND, 0X8288, 0);
for (int i = 0; i < sizeof(Codes); i++)
user32::SendMessageW(TerminalHandle, MT5InternalMsg, Codes[i], 0);
return;
}
static int GetNumberEX5( const HANDLE TerminalHandle, string FileName )
{
int Res = -1;
const string Path = MTTESTER::GetTerminalPath(TerminalHandle);
if (Path != "")
{
MTTESTER::RefreshNumbersEX5(TerminalHandle);
string FileNames[];
MTTESTER::GetEX5FileNames(Path + "\\MQL5\\Experts", FileNames);
MTTESTER::GetEX5FileNames(Path + "\\MQL5\\Indicators", FileNames);
MTTESTER::GetEX5FileNames(Path + "\\MQL5\\Scripts", FileNames);
::StringToLower(FileName);
const int Len = ::StringLen(Path + "\\MQL5\\");
for (Res = ::ArraySize(FileNames) - 1; (Res >= 0) && (::StringSubstr(FileNames[Res], Len) != FileName); Res--)
;
}
return(Res);
}
static HANDLE GetChartHandle( const HANDLE TerminalHandle, const uchar ChartNumber = 0 )
{
const HANDLE Handle = user32::GetDlgItem(TerminalHandle, 0xE900);
HANDLE Chart = 0;
uchar Pos = 0;
uchar Amount = 0;
for (Chart = user32::GetDlgItem(Handle, 0xFF00); Chart; Chart = user32::GetDlgItem(Handle, 0xFF00 + ++Pos))
if (MTTESTER::IsChart(Chart) && (ChartNumber == Amount++))
{
Chart = user32::GetDlgItem(Chart, 0xE900);
break;
}
return(Chart);
}
template <typename T>
static int Sort( const T &Array[], int &Indexes[] )
{
const int Size = ::ArrayResize(Indexes, ::ArraySize(Array));
for (int i = 0; i < Size - 1; i++)
{
int Pos = i;
for (int j = i + 1; j < Size; j++)
if (Array[Pos] > Array[j])
Pos = j;
Indexes[i] = Pos;
}
return(Size);
}
static void SortByPath( HANDLE &Handles[] )
{
string Path[];
for (int i = ::ArrayResize(Path, ::ArraySize(Handles)) - 1; i >= 0; i--)
Path[i] = MTTESTER::GetPathExe(Handles[i]);
HANDLE NewHandles[];
int Indexes[];
for (int i = ::ArrayResize(NewHandles, MTTESTER::Sort(Path, Indexes)) - 1; i >= 0; i--)
NewHandles[i] = Handles[Indexes[i]];
::ArraySwap(Handles, NewHandles);
return;
}
public:
static HANDLE GetTerminalHandle( void )
{
static HANDLE Handle = 0;
if (!Handle)
{
if (::MQLInfoInteger(MQL_TESTER) || (::MQLInfoInteger(MQL_PROGRAM_TYPE) == PROGRAM_SERVICE))
{
const string TerminalPath = ::TerminalInfoString(TERMINAL_PATH);
for (Handle = user32::GetTopWindow(NULL); Handle; Handle = user32::GetWindow(Handle, GW_HWNDNEXT))
if (MTTESTER::GetClassName(Handle) == "MetaQuotes::MetaTrader::5.00")
{
const string ExePath = MTTESTER::GetPathExe(Handle);
if (::StringSubstr(ExePath, 0, MTTESTER::GetLastPos(ExePath, '\\')) == TerminalPath)
break;
}
}
else
Handle = user32::GetAncestor(::ChartGetInteger(0, CHART_WINDOW_HANDLE), GA_ROOT);
}
return(Handle);
}
static string GetTerminalCaption( void )
{
ushort Str[128];
if (!::IsStopped()) // =0G5 7028A0=85 ?@8 A=OB88 A>25B=8:0.
user32::GetWindowTextW(MTTESTER::GetTerminalHandle(), Str, sizeof(Str) / sizeof(ushort));
return(::ShortArrayToString(Str));
}
static int GetPassesDone( void )
{
string Status = MTTESTER::GetStatusString();
return((int)MTTESTER::StringBetween(Status, ": ", " /"));
}
static bool GetSettings( string &Str, const int Attempts = 10 )
{
bool Res = false;
Str = NULL;
if (/*!::IsStopped() &&*/ (::StringFind(Str, "[Tester]") || MTTESTER::SetClipboard("", Attempts))) // ;>10;L=><C 45AB@C:B>@C <>65B ?>=04>18BLAO
{
const uint MT5InternalMsg = user32::RegisterWindowMessageW("MetaTrader5_Internal_Message");
const HANDLE HandleRoot = MTTESTER::GetTerminalHandle();
static const int ControlID[] = {0xE81E, 0x804E};
static const long Handle = MTTESTER::GetHandle(ControlID);
for (int j = 0; (j < Attempts) && !Res/* && !::IsStopped()*/; j++) // ;>10;L=><C 45AB@C:B>@C <>65B ?>=04>18BLAO
{
user32::SendMessageW(HandleRoot, MT5InternalMsg, 9, INT_MAX);
// SetFocus(Handle);
// MT4build2209+ - =5 0:BC0;L=>.
// user32::SendMessageW(user32::GetDlgItem(Handle, 0x2712), WM_LBUTTONDOWN, 1, 0x17007C); // K1>@ 2:;04:8 "0AB@>9:8"
user32::SendMessageW(Handle, WM_COMMAND, ID_EDIT_COPY, 0);
::Sleep(10);
Res = MTTESTER::GetClipboard(Str, Attempts) && !::StringFind(Str, "[Tester]");
}
}
return(Res);
}
static bool SetSettings( const string Str )
{
const bool Res = MTTESTER::SetClipboard(Str);
if (Res)
{
const uint MT5InternalMsg = user32::RegisterWindowMessageW("MetaTrader5_Internal_Message");
user32::SendMessageW(MTTESTER::GetTerminalHandle(), MT5InternalMsg, 9, INT_MAX);
static const int ControlID[] = {0xE81E, 0x804E};
static const long Handle = MTTESTER::GetHandle(ControlID);
// MT4build2209+ - =5 0:BC0;L=>.
//user32::SendMessageW(user32::GetDlgItem(Handle, 0x2712), WM_LBUTTONDOWN, 1, 0x17007C); // K1>@ 2:;04:8 "0AB@>9:8"
user32::SendMessageW(Handle, WM_COMMAND, ID_EDIT_PASTE, 0);
// MT4build2209+ - =5 0:BC0;L=>.
// user32::SendMessageW(user32::GetDlgItem(Handle, 0x2712), WM_LBUTTONDOWN, 1, 0x1200EF); // https://www.mql5.com/ru/forum/321656/page25#comment_13873612 - 0@0<5B@K
// user32::SendMessageW(user32::GetDlgItem(Handle, 0x2712), WM_LBUTTONDOWN, 1, 0x17007C); // K1>@ 2:;04:8 "0AB@>9:8"
::Sleep(100);
}
return(Res);
}
static bool SetSettings2( string Str, const int Attempts = 5 )
{
bool Res = false;
if (MTTESTER::LockWaiting())
{
for (int j = 0; (j < Attempts) && !Res; j++)
{
string Str1;
string Str2;
string Str3;
Res = MTTESTER::SetSettings(Str) && MTTESTER::GetSettings(Str1) &&
MTTESTER::SetSettings(Str) && MTTESTER::GetSettings(Str2) &&
MTTESTER::SetSettings(Str) && MTTESTER::GetSettings(Str3) &&
(Str1 == Str2) && (Str1 == Str3);
}
MTTESTER::Lock(false);
}
return(Res);
}
static bool GetSettings2( string &Str, const int Attempts = 10 )
{
bool Res = false;
if (MTTESTER::LockWaiting())
{
Res = MTTESTER::GetSettings(Str, Attempts);
MTTESTER::Lock(false);
}
return(Res);
}
static bool SetSettingsPart( string Str, string StrPrev, const int Attempts = 5 )
{
return(MTTESTER::SetSettings2(MTTESTER::StringBetween(Str, NULL, "[TesterInputs]") + "[TesterInputs]" +
MTTESTER::StringBetween(StrPrev, "[TesterInputs]") + Str, Attempts));
}
// A;8 2E>4=K5 ?0@0<5B@K A>25B=8:0 7040=K =5 2A5, B> 8E 7=0G5=8O 15@CBAO A ?@54K4CI53> A>25B=8:0.
static bool SetSettingsPart( string Str, const int Attempts = 5 )
{
string StrPrev;
/*
string StrTmp = Str;
StrTmp = MTTESTER::GetValue(MTTESTER::StringBetween(StrTmp, NULL, "[TesterInputs]"), "Expert");
return(((StrTmp == NULL) || MTTESTER::GetSettings2(StrPrev)) && MTTESTER::SetSettings2(Str, Attempts) &&
((StrTmp == NULL) || (MTTESTER::GetValue(MTTESTER::StringBetween(StrPrev, NULL, "[TesterInputs]"), "Expert") ==
MTTESTER::GetValue(MTTESTER::StringBetween(Str, NULL, "[TesterInputs]"), "Expert")) ||
(MTTESTER::SetSettings2("[TesterInputs]" + StrPrev, Attempts) &&
MTTESTER::SetSettings2("[TesterInputs]" + Str, Attempts))));
*/
return(MTTESTER::GetSettings2(StrPrev) && MTTESTER::SetSettingsPart(Str, StrPrev, Attempts));
}
static int GetLastOptCache( uchar &Bytes[] )
{
const string FileName = MTTESTER::GetLastOptCacheFileName2();
return((FileName != NULL) ? MTTESTER::FileLoad(FileName, Bytes) : -1);
}
static int GetLastTstCache( uchar &Bytes[], const bool FromSettings = false )
{
int Count = 0;
string FileName = NULL;
const string BeginFileName = FromSettings ? MTTESTER::GetBeginFileName() : NULL;
while (!::IsStopped() && (Count++ < 10) && (FileName == NULL))
{
FileName = MTTESTER::GetLastTstCacheFileName2();
if (FileName == NULL || (FromSettings && (::StringFind(FileName, BeginFileName) == -1)))
{
FileName = NULL;
::Sleep(500);
}
}
return((FileName != NULL) ? MTTESTER::FileLoad(FileName, Bytes) : -1);
}
static string GetExpertName( void )
{
static const int ControlID[] = {0xE81E, 0x804E, 0x28EC, 0x28F5};
static const long Handle = MTTESTER::GetHandle(ControlID);
return(MTTESTER::GetComboBoxString(Handle));
}
static string GetSymbolName( void )
{
string Str;;
return(MTTESTER::GetSettings(Str) ? MTTESTER::StringBetween(Str, "Symbol=", "\r\n") : NULL);
}
static bool SetExpertName( const string ExpertName = NULL )
{
bool Res = (ExpertName == NULL);
if (!Res)
{
const string PrevExpertName = MTTESTER::GetExpertName();
if (!(Res = (PrevExpertName == ExpertName)))
{
static const int ControlID[] = {0xE81E, 0x804E, 0x28EC, 0x28F5};
static const long Handle = MTTESTER::GetHandle(ControlID);
user32::SendMessageW(Handle, WM_LBUTTONDOWN, 0, 0);
const long Handle2 = user32::GetLastActivePopup(MTTESTER::GetTerminalHandle());
user32::SendMessageW(Handle2, WM_KEYDOWN, VK_LEFT, 0);
user32::SendMessageW(Handle2, WM_KEYDOWN, VK_LEFT, 0);
// C6=> 4;O 8=8F80;870F88.
for (int i = 0; i < 3; i++)
user32::SendMessageW(Handle2, WM_CHAR, ':', 0);
user32::SendMessageW(Handle2, WM_KEYDOWN, VK_HOME, 0);
const int Size = ::StringLen(ExpertName);
for (int i = 0; i < Size; i++)
if (ExpertName[i] == '\\')
{
user32::SendMessageW(Handle2, WM_KEYDOWN, VK_RIGHT, 0);
user32::SendMessageW(Handle2, WM_KEYDOWN, VK_RIGHT, 0);
user32::SendMessageW(Handle2, WM_KEYDOWN, VK_LEFT, 0);
}
else
user32::SendMessageW(Handle2, WM_CHAR, ExpertName[i], 0);
user32::SendMessageW(Handle2, WM_KEYDOWN, VK_RETURN, 0);
user32::SendMessageW(Handle2, WM_KEYDOWN, VK_ESCAPE, 0);
const string NewExpertName = MTTESTER::GetExpertName();
Res = (NewExpertName == ExpertName);
if (!Res && (NewExpertName != PrevExpertName))
MTTESTER::SetExpertName(PrevExpertName);
}
}
return(Res);
}
static bool CloseNotChart( void )
{
static const int ControlID[] = {0xE900};
static const long Handle = MTTESTER::GetHandle(ControlID);
bool Res = false;
for (long handle = user32::GetWindow(Handle, GW_CHILD); handle; handle = user32::GetWindow(handle, GW_HWNDNEXT))
if (!MTTESTER::IsChart(handle))
{
user32::SendMessageW(handle, WM_CLOSE, 0, 0);
Res = true;
break;
}
return(Res);
}
static bool IsReady( const uint Pause = 100 )
{
if (MTTESTER::IsReady2())
MTTESTER::Sleep2(Pause);
return(MTTESTER::IsReady2());
}
static bool ClickStart( const bool Check = true, const int Attempts = 50 )
{
// static const int ControlID[] = {0xE81E, 0x804E, 0x2712, 0x4196}; // Start Button
static const int ControlID[] = {0xE81E, 0x804E};
static const long Handle = MTTESTER::GetHandle(ControlID);
bool Res = !Check || MTTESTER::IsReady2();
if (Res)
{
if (Check)
{
// MTTESTER::StartTester();
// user32::ShowWindow(user32::GetDlgItem(Handle, 0x28ED), 1); // Journal
for (int i = 0; (i < Attempts)/* && !::IsStopped() */; i++) // ;>10;L=><C 45AB@C:B>@C <>65B ?>=04>18BLAO
{
// https://www.mql5.com/ru/forum/1111/page3471#comment_51964006
for (int X = 300; (X <= 1000) && !MTTESTER::GetJournalItemCount(); X += 50)
user32::SendMessageW(user32::GetDlgItem(Handle, 0x2712), WM_LBUTTONDOWN, 1, X); // lparam - X
::Sleep(20);
if (MTTESTER::GetJournalItemCount())
break;
}
for (int i = 0; (i < Attempts)/* && !::IsStopped() */; i++) // ;>10;L=><C 45AB@C:B>@C <>65B ?>=04>18BLAO
{
// https://www.mql5.com/ru/forum/1111/page3471#comment_51964006
for (int X = 300; (X <= 1000)/* && MTTESTER::GetJournalItemCount()*/; X += 50)
{
user32::SendMessageW(user32::GetDlgItem(Handle, 0x2712), WM_LBUTTONDOWN, 1, X); // lparam - X
user32::SendMessageW(MTTESTER::GetTerminalHandle(), WM_COMMAND, 0X8176, 0);
}
::Sleep(30);
if (!MTTESTER::GetJournalItemCount())
break;
}
}
const int PrevCount = Check ? MTTESTER::GetJournalItemCount() : 0;
// user32::SendMessageW(Handle, BM_CLICK, 0, 0); // Start Button
user32::SendMessageW(Handle, user32::RegisterWindowMessageW("MetaTrader5_Internal_Message"), 0X31, 0);
if (Check)
for (int i = 0; (i < Attempts) && !(Res = !MTTESTER::IsReady2()); i++) // 0BL CA?5BL ?>A;5 =060B8O =0 Start ?5@5:;NG8BLAO =0 Stop.
::Sleep(1);
if (!Res) // >A;5 =060B8O =0 Start ?5@5:;NG5=85 =0 Stop >AB0;>AL =570<5G5==K<.
{
::Alert(__FILE__ + ": Start->Stop - is not detected!");
for (int i = 0; (i < Attempts)/* && !::IsStopped() */; i++) // ;>10;L=><C 45AB@C:B>@C <>65B ?>=04>18BLAO
{
// https://www.mql5.com/ru/forum/1111/page3471#comment_51964006
for (int X = 300; (X <= 1000)/* && (MTTESTER::GetJournalItemCount() == PrevCount)*/; X += 50)
user32::SendMessageW(user32::GetDlgItem(Handle, 0x2712), WM_LBUTTONDOWN, 1, X); // lparam - X
::Sleep(100); // 5=LH55 7=0G5=85 <>65B =5 40BL CA?5BL >1=>28BLAO 6C@=0;C.
if (Res = (MTTESTER::GetJournalItemCount() - PrevCount > 1))
break;
}
if (!Res || !(Res = (MTTESTER::GetJournalItemCount() - PrevCount > 1)))
::Alert(__FILE__ + ": problem with Start-button!");
}
// if (Check)
// user32::ShowWindow(user32::GetDlgItem(Handle, 0x28ED), 0); // Journal
}
return(Res);
}
static bool SetTimeFrame( ENUM_TIMEFRAMES period )
{
const bool Res = MTTESTER::IsReady();
if (Res)
{
static const int ControlID[] = {0xE81E, 0x804E, 0x28EC, 0x28F7};
static const long Handle = MTTESTER::GetHandle(ControlID);
user32::SendMessageW(Handle, WM_KEYDOWN, VK_HOME, 0);
static const ENUM_TIMEFRAMES Periods[] = {PERIOD_M1, PERIOD_M2, PERIOD_M3, PERIOD_M4, PERIOD_M5, PERIOD_M6, PERIOD_M10,
PERIOD_M12, PERIOD_M15, PERIOD_M20, PERIOD_M30, PERIOD_H1, PERIOD_H2, PERIOD_H3,
PERIOD_H4, PERIOD_H6, PERIOD_H8, PERIOD_H12, PERIOD_D1, PERIOD_W1, PERIOD_MN1};
if (period == PERIOD_CURRENT)
period = ::_Period;
for (int i = 0; (i < sizeof(Periods) / sizeof(ENUM_TIMEFRAMES)) && (period != Periods[i]); i++)
user32::SendMessageW(Handle, WM_KEYDOWN, VK_DOWN, 0);
}
return(Res);
}
static bool SetSymbol( const string SymbName = NULL )
{
const bool Res = (SymbName == NULL) || (::SymbolInfoInteger(SymbName, SYMBOL_VISIBLE) && MTTESTER::IsReady());
const int Size = ::StringLen(SymbName);
if (Res && Size && (SymbName != MTTESTER::GetSymbolName()))
{
static const int ControlID[] = {0xE81E, 0x804E, 0x28EC, 0x28F6, 0x2855};
static const long Handle = MTTESTER::GetHandle(ControlID);
user32::SendMessageW(Handle, WM_LBUTTONDOWN, 0, 0);
user32::SendMessageW(Handle, WM_KEYDOWN, VK_DELETE, 0);
for (int i = 0; i < Size; i++)
user32::SendMessageW(Handle, WM_CHAR, SymbName[i], 0);
user32::SendMessageW(Handle, WM_KEYDOWN, VK_RETURN, 0);
}
return(Res);
}
static bool SetBeginTime( const datetime time )
{
static const int ControlID[] = {0xE81E, 0x804E, 0x28EC, 0x2936};
static const long Handle = MTTESTER::GetHandle(ControlID);
return(MTTESTER::SetTime(Handle, time));
}
static bool SetEndTime( const datetime time )
{
static const int ControlID[] = {0xE81E, 0x804E, 0x28EC, 0x2937};
static const long Handle = MTTESTER::GetHandle(ControlID);
return(MTTESTER::SetTime(Handle, time));
}
static bool Run( const string ExpertName = NULL,
const string Symb = NULL,
const ENUM_TIMEFRAMES period = PERIOD_CURRENT,
const datetime iBeginTime = 0,
const datetime iEndTime = 0 )
{
string Str = "[Tester]\n";
Str += (ExpertName != NULL) ? "Expert=" + ExpertName + "\n" : NULL;
Str += (Symb != NULL) ? "Symbol=" + Symb + "\n" : NULL;
Str += iBeginTime ? "FromDate=" + ::TimeToString(iBeginTime, TIME_DATE) + "\n" : NULL;
Str += iEndTime ? "ToDate=" + ::TimeToString(iEndTime, TIME_DATE) + "\n" : NULL;
return(MTTESTER::SetSettings2(Str) &&
MTTESTER::SetTimeFrame(period) && MTTESTER::ClickStart());
/*
return(MTTESTER::SetExpertName(ExpertName) &&
MTTESTER::SetSymbol(Symb) &&
MTTESTER::SetBeginTime(iBeginTime) && MTTESTER::SetEndTime(iEndTime) &&
MTTESTER::SetTimeFrame(period) && MTTESTER::ClickStart());
*/
}
static string GetValue( string Settings, const string Name )
{
const string Str = (Name == "") ? NULL : MTTESTER::StringBetween(Settings, Name + "=", "\n");
const int Len = ::StringLen(Str);
return((Len && (Str[Len - 1] == '\r')) ? ::StringSubstr(Str, 0, Len - 1) : Str);
}
static string SetValue( string &Settings, const string Name, const string Value = NULL )
{
const string PrevValue = MTTESTER::GetValue(Settings, Name);
if (PrevValue == NULL)
Settings += "\n" + Name + "=" + Value;
else
::StringReplace(Settings, Name + "=" + PrevValue, (Value == NULL) ? NULL : Name + "=" + Value); // NULL - delete.
return(Settings);
}
static int GetOptCacheFileNames( string &Path, string &FileNames[] )
{
Path = ::TerminalInfoString(TERMINAL_DATA_PATH)+ "\\Tester\\cache\\";
return(MTTESTER::GetFileNames(Path, "*.opt", FileNames));
}
template <typename T>
static int FileLoad( const string FileName, T &Buffer[] )
{
int Res = -1;
const HANDLE handle = kernel32::CreateFileW(FileName, GENERIC_READ, SHARE_READ, 0, OPEN_EXISTING, 0, 0);
if (handle != INVALID_HANDLE)
{
long Size;
kernel32::GetFileSizeEx(handle, Size);
uint Read;
::ArrayResize(Buffer, (int)Size / sizeof(T));
kernel32::ReadFile(handle, Buffer, (uint)Size / sizeof(T), Read, 0);
Res = ::ArrayResize(Buffer, Read);
kernel32::CloseHandle(handle);
}
return(Res);
}
static bool FileCopy( const string FileNameIn, const string FileNameOut, const bool Overwrite = false )
{
string Path = FileNameOut;
string Directory = NULL;
// >6=> 3>@074> M:>=><=55 =0E>48BL ?CB8 ?>4?0?>:.
while (::StringFind(Path, "\\") > 0)
kernel32::CreateDirectoryW(Directory += MTTESTER::StringBetween(Path, NULL, "\\") + "\\", 0);
return(kernel32::CopyFileW(FileNameIn, FileNameOut, !Overwrite));
}
template <typename T>
static int FileSave( const string FileName, const T &Buffer[] )
{
string Path = FileName;
string Directory = NULL;
// >6=> 3>@074> M:>=><=55 =0E>48BL ?CB8 ?>4?0?>:.
while (::StringFind(Path, "\\") > 0)
kernel32::CreateDirectoryW(Directory += MTTESTER::StringBetween(Path, NULL, "\\") + "\\", 0);
uint Read = 0;
const HANDLE handle = kernel32::CreateFileW(FileName, GENERIC_WRITE, SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
if (handle != INVALID_HANDLE)
{
kernel32::WriteFile(handle, Buffer, (uint)::ArraySize(Buffer) * sizeof(T), Read, 0);
kernel32::CloseHandle(handle);
}
return((int)Read / sizeof(T));
}
static string GetLastOptCacheFileName( void )
{
const string Path = ::TerminalInfoString(TERMINAL_DATA_PATH)+ "\\Tester\\cache\\";
return(::StringSubstr(MTTESTER::GetFreshFileName(Path, "*.opt"), ::StringLen(Path)));
}
static string GetLastTstCacheFileName( void )
{
const string Path = ::TerminalInfoString(TERMINAL_DATA_PATH)+ "\\Tester\\cache\\";
return(::StringSubstr(MTTESTER::GetFreshFileName(Path, "*.tst"), ::StringLen(Path)));
}
static bool DeleteLastINI( void )
{
const string Path = ::TerminalInfoString(TERMINAL_DATA_PATH)+ "\\MQL5\\Profiles\\Tester\\";
string FileNames[];
const int Size = MTTESTER::GetFileNames(Path, "*.ini", FileNames);
return(Size && kernel32::DeleteFileW(Path + FileNames[Size - 1]));
}
static bool DeleteLastTST( void )
{
const string Path = ::TerminalInfoString(TERMINAL_DATA_PATH)+ "\\Tester\\cache\\";
string FileNames[];
const int Size = MTTESTER::GetFileNames(Path, "*.tst", FileNames);
return(Size && kernel32::DeleteFileW(Path + FileNames[Size - 1]));
}
static bool IsFolderON( const string Name, const bool Log = false )
{
const uint FileAttribute = kernel32::GetFileAttributesW(Name);
const bool Res = (FileAttribute == INVALID_FILE_ATTRIBUTES) || (bool)(FileAttribute & FILE_ATTRIBUTE_DIRECTORY);
if (Log)
::Print(Name + " - " + (Res ? "ON." : "OFF"));
return(Res);
}
static bool FolderOFF( const string Name, const bool Log = false )
{
MTTESTER::DeleteFolder(Name);
const HANDLE handle = kernel32::CreateFileW(Name, GENERIC_WRITE, SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
if (handle != INVALID_HANDLE)
kernel32::CloseHandle(handle);
return(!MTTESTER::IsFolderON(Name, Log));
}
static bool FolderON( const string Name, const bool Log = false )
{
kernel32::DeleteFileW(Name);
return(!MTTESTER::IsFolderON(Name, Log));
}
#define TERMINAL_UPDATE(A) \
static const string Name = ::StringSubstr(::TerminalInfoString(TERMINAL_COMMONDATA_PATH), 0, \
::StringLen(::TerminalInfoString(TERMINAL_COMMONDATA_PATH)) - 6) + \
MTTESTER::instance_id(::TerminalInfoString(TERMINAL_PATH)); \
\
return(MTTESTER::##A(Name, Log));
static bool IsTerminalLiveUpdate( const bool Log = false )
{
TERMINAL_UPDATE(IsFolderON);
}
static bool TerminalLiveUpdateOFF( const bool Log = false )
{
TERMINAL_UPDATE(FolderOFF);
}
static bool TerminalLiveUpdateON( const bool Log = false )
{
TERMINAL_UPDATE(FolderON);
}
#undef TERMINAL_UPDATE
#define TESTER_LOG(A) \
static const string Path = ::TerminalInfoString(TERMINAL_DATA_PATH)+ "\\Tester\\"; \
\
bool Res = MTTESTER::##A(Path + "logs", Log); \
\
string AgentNames[]; \
\
for (int i = MTTESTER::GetAgentNames(AgentNames) - 1; i >= 0; i--) \
Res &= MTTESTER::##A(Path + AgentNames[i] + "\\logs", Log);
static bool IsTesterLogON( const bool Log = false )
{
TESTER_LOG(IsFolderON)
return(Res);
}
static bool TesterLogOFF( const bool Log = false )
{
TESTER_LOG(FolderOFF)
return(!MTTESTER::IsTesterLogON());
}
static bool TesterLogON( const bool Log = false )
{
TESTER_LOG(FolderON)
return(MTTESTER::IsTesterLogON());
}
#undef TESTER_LOG
static bool Lock( const bool Flag = true )
{
static int handle = INVALID_HANDLE;
if (handle != INVALID_HANDLE)
{
::FileClose(handle);
handle = INVALID_HANDLE;
}
return(Flag && ((handle = ::FileOpen(__FILE__, FILE_WRITE | FILE_COMMON)) != INVALID_HANDLE));
}
static bool LockWaiting( const int Attempts = 10 )
{
bool Res = MTTESTER::Lock();
for (int i = 0; (i < Attempts) && !Res && !::IsStopped(); i++)
{
::Sleep(500);
Res = MTTESTER::Lock();
}
return(Res);
}
static int GetTerminalHandles( HANDLE &Handles[], const bool SortPath = false )
{
::ArrayFree(Handles);
for (HANDLE Handle = user32::GetTopWindow(NULL); Handle; Handle = user32::GetWindow(Handle, GW_HWNDNEXT))
if (MTTESTER::GetClassName(Handle) == "MetaQuotes::MetaTrader::5.00")
Handles[::ArrayResize(Handles, ::ArraySize(Handles) + 1) - 1] = Handle;
if (SortPath)
MTTESTER::SortByPath(Handles);
return(::ArraySize(Handles));
}
#define FORCE_PATH "Scripts\\" + __FILE__ + "\\"
static bool RunEX5( string FileName, HANDLE TerminalHandle = 0, const bool Force = false, const uchar ChartNumber = 0 )
{
bool Res = false;
if (!TerminalHandle)
TerminalHandle = MTTESTER::GetTerminalHandle();
const HANDLE MyChartHandle = ::ChartGetInteger(0, CHART_WINDOW_HANDLE);
HANDLE ChartHandle = MTTESTER::GetChartHandle(TerminalHandle, ChartNumber);
if ((ChartHandle && (ChartHandle != MyChartHandle)) ||
(Force && (bool)(ChartHandle = (ChartHandle || ChartNumber) ? MTTESTER::GetChartHandle(TerminalHandle, (uchar)(!ChartNumber)) : 0)))
{
if (Force && (TerminalHandle != MTTESTER::GetTerminalHandle()) &&
(MTTESTER::FileCopy(MTTESTER::GetTerminalPath(MTTESTER::GetTerminalHandle()) + "\\MQL5\\" + FileName,
MTTESTER::GetTerminalPath(TerminalHandle) + "\\MQL5\\" + FORCE_PATH + FileName, true)))
FileName = FORCE_PATH + FileName;
const int NumberEX5 = MTTESTER::GetNumberEX5(TerminalHandle, FileName);
Res = (NumberEX5 >= 0) && !user32::SendMessageW(ChartHandle, user32::RegisterWindowMessageW("MetaTrader5_Internal_Message"), 0x1D, NumberEX5);
}
return(Res);
}
static bool IsForceScript( void )
{
return(::StringFind(::MQLInfoString(MQL_PROGRAM_PATH), "\\MQL5\\" + FORCE_PATH) > 0);
}
#undef FORCE_PATH
};