204 lines
16 KiB
MQL5
204 lines
16 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| HistoryStrategy.mqh |
|
|
//| Copyright 2024, Yuriy Bykov |
|
|
//| https://www.mql5.com/ru/users/antekov |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2024, Yuriy Bykov"
|
|
#property link "https://www.mql5.com/ru/articles/15330"
|
|
#property version "1.01"
|
|
|
|
#include "../Utils/NewBarEvent.mqh"
|
|
#include "../Virtual/VirtualStrategy.mqh"
|
|
|
|
// Индексы нужных столбцов в истории сделок
|
|
#define DATE 0
|
|
#define TYPE 2
|
|
#define SYMBOL 3
|
|
#define VOLUME 4
|
|
#define ENTRY 5
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Торговая стратегия воспроизведения истории сделок |
|
|
//+------------------------------------------------------------------+
|
|
class CHistoryStrategy : public CVirtualStrategy {
|
|
protected:
|
|
string m_symbols[]; // Символы (торговые инструменты)
|
|
string m_history[][15]; // Массив истории сделок (N строк * 15 столбцов)
|
|
int m_totalDeals; // Количество сделок в истории
|
|
int m_currentDeal; // Текущий номер сделки
|
|
|
|
CSymbolInfo m_symbolInfo; // Объект для получения информации о свойствах символа
|
|
|
|
public:
|
|
CHistoryStrategy(string p_params); // Конструктор
|
|
virtual void Tick() override; // Обработчик события OnTick
|
|
virtual string operator~() override; // Преобразование объекта в строку
|
|
};
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Конструктор |
|
|
//+------------------------------------------------------------------+
|
|
CHistoryStrategy::CHistoryStrategy(string p_params) {
|
|
m_params = p_params;
|
|
|
|
// Читаем имя файла из параметров
|
|
string fileName = ReadString(p_params);
|
|
|
|
// Если имя прочитано, то
|
|
if(IsValid()) {
|
|
// Пробуем открыть файл в папке данных
|
|
int f = FileOpen(fileName, FILE_READ | FILE_CSV | FILE_ANSI | FILE_SHARE_READ, ',');
|
|
|
|
// Если открыть не получилось, то пробуем открыть файл из общей папки
|
|
if(f == INVALID_HANDLE) {
|
|
f = FileOpen(fileName, FILE_COMMON | FILE_READ | FILE_CSV | FILE_ANSI | FILE_SHARE_READ, ',');
|
|
}
|
|
|
|
// Если не получилось, то сообщаем об ошибке и выходим
|
|
if(f == INVALID_HANDLE) {
|
|
SetInvalid(__FUNCTION__,
|
|
StringFormat("Can't open file %s from common folder %s, error code: %d",
|
|
fileName, TerminalInfoString(TERMINAL_COMMONDATA_PATH), GetLastError()));
|
|
return;
|
|
}
|
|
|
|
// Читаем файл до строки заголовка (обычно она идёт первой)
|
|
while(!FileIsEnding(f)) {
|
|
string s = FileReadString(f);
|
|
// Если нашли строку заголовка, то читаем названия всех столбцов не сохраняя их
|
|
if(s == "DATE") {
|
|
FOR(14) FileReadString(f);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Читаем остальные строки до конца файла
|
|
while(!FileIsEnding(f)) {
|
|
// Если массив для хранения прочитанной истории заполнен, то увеличиваем его размер
|
|
if(m_totalDeals == ArraySize(m_history)) {
|
|
ArrayResize(m_history, ArraySize(m_history) + 10000, 100000);
|
|
}
|
|
|
|
// Читаем 15 значений из очередной строки файла в строку массива
|
|
FOR(15) m_history[m_totalDeals][i] = FileReadString(f);
|
|
|
|
// Если символ у сделки не пустой, то
|
|
if(m_history[m_totalDeals][SYMBOL] != "") {
|
|
// Добавляем его в массив символов, если такого символа там ещё нет
|
|
ADD(m_symbols, m_history[m_totalDeals][SYMBOL]);
|
|
}
|
|
|
|
// Увеличиваем счётчик прочитанных сделок
|
|
m_totalDeals++;
|
|
}
|
|
|
|
// Закрываем файл
|
|
FileClose(f);
|
|
|
|
PrintFormat(__FUNCTION__" | OK: Found %d rows in %s", m_totalDeals, fileName);
|
|
|
|
// Если есть прочитанные сделки кроме самой первой (пополнения счёта), то
|
|
if(m_totalDeals > 1) {
|
|
// Устанавливаем точный размер для массива истории
|
|
ArrayResize(m_history, m_totalDeals);
|
|
|
|
// Текущее время
|
|
datetime ct = TimeCurrent();
|
|
|
|
PrintFormat(__FUNCTION__" |\n"
|
|
"Start time in tester: %s\n"
|
|
"Start time in history: %s",
|
|
TimeToString(ct, TIME_DATE), m_history[0][DATE]);
|
|
|
|
// Если дата начала тестирования больше даты начала истории, то сообщаем об ошибке
|
|
if(StringToTime(m_history[0][DATE]) < ct) {
|
|
SetInvalid(__FUNCTION__,
|
|
StringFormat("For this history file [%s] set start date less than %s",
|
|
fileName, m_history[0][DATE]));
|
|
}
|
|
}
|
|
|
|
// Создаём виртуальные позиции для каждого символа
|
|
CVirtualReceiver::Get(GetPointer(this), m_orders, ArraySize(m_symbols));
|
|
|
|
// Регистрируем обработчик события нового бара на минимальном таймфрейме
|
|
FOREACH(m_symbols) IsNewBar(m_symbols[i], PERIOD_M1);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Обработчик события OnTick |
|
|
//+------------------------------------------------------------------+
|
|
void CHistoryStrategy::Tick() override {
|
|
//---
|
|
while(m_currentDeal < m_totalDeals && StringToTime(m_history[m_currentDeal][DATE]) <= TimeCurrent()) {
|
|
// Символ сделки
|
|
string symbol = m_history[m_currentDeal][SYMBOL];
|
|
|
|
// Ищем индекс символа текущей сделки в массиве символов
|
|
int index;
|
|
FIND(m_symbols, symbol, index);
|
|
|
|
// Если не нашли, то пропускаем текущую сделку
|
|
if(index == -1) {
|
|
m_currentDeal++;
|
|
continue;
|
|
}
|
|
|
|
// Тип сделки
|
|
ENUM_DEAL_TYPE type = (ENUM_DEAL_TYPE) StringToInteger(m_history[m_currentDeal][TYPE]);
|
|
|
|
// Объем текущей сделки
|
|
double volume = NormalizeDouble(StringToDouble(m_history[m_currentDeal][VOLUME]), 2);
|
|
|
|
// Если это пополнение/снятие со счёта, то пропускаем эту сделку
|
|
if(volume == 0) {
|
|
m_currentDeal++;
|
|
continue;
|
|
}
|
|
|
|
// Сообщаем информацию о прочитанной сделке
|
|
PrintFormat(__FUNCTION__" | Process deal #%d: %s %.2f %s",
|
|
m_currentDeal, (type == DEAL_TYPE_BUY ? "BUY" : (type == DEAL_TYPE_SELL ? "SELL" : EnumToString(type))),
|
|
volume, symbol);
|
|
|
|
// Если это сделка на продажу, то делаем объём отрицательным
|
|
if(type == DEAL_TYPE_SELL) {
|
|
volume *= -1;
|
|
}
|
|
|
|
// Если виртуальная позиция для символа текущей сделки открыта, то
|
|
if(m_orders[index].IsOpen()) {
|
|
// Добавляем её объем к объёму текущей сделки
|
|
volume += m_orders[index].Volume();
|
|
|
|
// Закрываем виртуальную позицию
|
|
m_orders[index].Close();
|
|
}
|
|
|
|
// Если объём по текущему символу не равен 0, то
|
|
if(MathAbs(volume) > 0.00001) {
|
|
// Открываем виртуальную позицию нужного объёма и направления
|
|
m_orders[index].Open(symbol, (volume > 0 ? ORDER_TYPE_BUY : ORDER_TYPE_SELL), MathAbs(volume));
|
|
}
|
|
|
|
// Увеличиваем счётчик обработанных сделок
|
|
m_currentDeal++;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Преобразование объекта в строку |
|
|
//+------------------------------------------------------------------+
|
|
string CHistoryStrategy::operator~() {
|
|
return StringFormat("%s(%s)", typename(this), m_params);
|
|
}
|
|
|
|
// Освобожаем имена констант
|
|
#undef DATE
|
|
#undef TYPE
|
|
#undef SYMBOL
|
|
#undef VOLUME
|
|
#undef ENTRY
|
|
//+------------------------------------------------------------------+
|