Adwizard/Experts/Expert.mqh

448 lines
20 KiB
MQL5
Raw Permalink Normal View History

2025-09-19 13:47:10 +03:00
//+------------------------------------------------------------------+
//| Expert.mq5 |
//| 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/17608"
2026-05-14 00:00:27 +03:00
#property version "1.26"
2025-09-19 13:47:10 +03:00
#include "../Virtual/VirtualAdvisor.mqh"
#include "../Utils/ExpertHistory.mqh"
#include "../Utils/ConsoleDialog.mqh"
// Если не задана константа с именем итогового советника, то
#ifndef __NAME__
// Задаём её равной названию файла советника
#define __NAME__ MQLInfoString(MQL_PROGRAM_NAME)
//+------------------------------------------------------------------+
//| Функция формирования строки инициализации стратегии |
//| из входных параметров по умолчанию (если не было задано имя). |
//| Импортирует строку инициализации из базы данных советника |
//| по идентификатору группы стратегий |
//+------------------------------------------------------------------+
string GetStrategyParams() {
// Берём строку инициализации из новой библиотеки для выбранной группы
// (из базы данных эксперта)
string strategiesParams = CVirtualAdvisor::Import(
CVirtualAdvisor::FileName(__NAME__, magic_),
groupId_
);
// Если группа стратегий из библиотеки не задана, то прерываем работу
if(strategiesParams == NULL && useAutoUpdate_) {
strategiesParams = "";
}
return strategiesParams;
}
#endif
2026-05-14 00:00:27 +03:00
// Если во внешнем файле, куда подключается этот файл,
2026-01-19 21:28:59 +03:00
// есть константа __INPUT_PARAMS__, то входные параметры должны:
// - быть полностью объявлены во внешнем файле
// - повторять все параметры, которые перечислены ниже
2026-05-14 00:00:27 +03:00
// Если константа __INPUT_PARAMS__ не объявлена,
2026-01-19 21:28:59 +03:00
// то входные параметры берутся из блока ниже
2025-11-25 08:47:29 +03:00
#ifndef __INPUT_PARAMS__
2025-09-19 13:47:10 +03:00
//+------------------------------------------------------------------+
//| Входные параметры |
//+------------------------------------------------------------------+
input group "::: Использовать группу стратегий"
sinput int groupId_ = 0; // - ID группы из новой библиотеки (0 - последняя)
sinput bool useAutoUpdate_ = true; // - Использовать автообновление?
input group "::: Управление капиталом"
sinput double expectedDrawdown_ = 10; // - Максимальный риск (%)
sinput double fixedBalance_ = 10000; // - Используемый депозит (0 - использовать весь) в валюте счета
input double scale_ = 1.00; // - Масштабирующий множитель для группы
input group "::: Менеджер закрытия"
input bool cmIsActive_ = true; // - Активен?
input double cmStartBaseBalance_ = 0; // - Базовый баланс
input ENUM_CM_CALC_LOSS
cmCalcLossLimit_ = CM_CALC_LOSS_MONEY_BB; // - Способ расчёта убытка
input double cmLossLimit_ = 100; // - Значение убытка для фиксации
input ENUM_CM_CALC_PROFIT
cmCalcProfitLimit_ = CM_CALC_PROFIT_MONEY_BB; // - Способ расчёта общей прибыли
input double cmProfitLimit_ = 1000000; // - Значение общей прибыли для фиксации
input group "::: Риск-менеджер"
input bool rmIsActive_ = true; // - Активен?
input double rmStartBaseBalance_ = 10000; // - Базовый баланс
input ENUM_RM_CALC_DAILY_LOSS
rmCalcDailyLossLimit_ = RM_CALC_DAILY_LOSS_MONEY_BB; // - Способ расчёта дневного убытка
input double rmMaxDailyLossLimit_ = 500; // - Значение дневного убытка
input double rmCloseDailyPart_ = 1.0; // - Значение пороговой части дневного убытка
input ENUM_RM_CALC_OVERALL_LOSS
rmCalcOverallLossLimit_ = RM_CALC_OVERALL_LOSS_MONEY_BB; // - Способ расчёта общего убытка
input double rmMaxOverallLossLimit_ = 1000; // - Значение общего убытка
input double rmCloseOverallPart_ = 1.0; // - Значение пороговой части общего убытка
input ENUM_RM_CALC_OVERALL_PROFIT
rmCalcOverallProfitLimit_ = RM_CALC_OVERALL_PROFIT_MONEY_BB; // - Способ расчёта общей прибыли
input double rmMaxOverallProfitLimit_ = 1000000; // - Значение общей прибыли
input int rmMaxOverallProfitDate_ = 0; // - Предельное время ожидания общей прибыли (дней)
input double rmMaxRestoreTime_ = 0; // - Время ожидания лучшего входа на просадке
input double rmLastVirtualProfitFactor_ = 1; // - Множитель начальной лучшей просадки
2026-05-14 00:00:27 +03:00
input double slPercent_ = 0.03; // - Размер SL одной позиции в % от депозита
2025-09-19 13:47:10 +03:00
input group "::: Прочие параметры"
input ulong magic_ = 27183; // - Magic
input bool useOnlyNewBars_ = true; // - Работать только на открытии бара
input bool usePrevState_ = true; // - Загружать предыдущее состояние
input string symbolsReplace_ = ""; // - Правила замены символов
2025-11-25 08:47:29 +03:00
#endif
2025-09-19 13:47:10 +03:00
CVirtualAdvisor *expert; // Объект эксперта
CConsoleDialog *dialog; // Диалог для вывода текста с результатами
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit() {
// Создаём и запускаем диалог для вывода результатов
dialog = new CConsoleDialog();
2025-11-27 18:45:58 +03:00
dialog.Create(__NAME__ + " | " + (string) magic_);
dialog.Run();
dialog.Text("Initialization...");
2025-09-19 13:47:10 +03:00
// Устанавливаем параметры в классе управления капиталом
CMoney::DepoPart(expectedDrawdown_ / 10.0);
CMoney::FixedBalance(fixedBalance_);
2026-05-14 00:00:27 +03:00
CVirtualReceiver::s_slPercent = slPercent_;
2025-09-19 13:47:10 +03:00
// Строка инициализации с наборами параметров стратегий
string strategiesParams = NULL;
// Берём строку инициализации из новой библиотеки для выбранной группы
// (из базы данных эксперта)
strategiesParams = GetStrategyParams();
// Если группа стратегий из библиотеки не задана, то прерываем работу
if(strategiesParams == NULL) {
return INIT_FAILED;
}
// Подготавливаем строку инициализации для эксперта с группой из нескольких стратегий
string expertParams = StringFormat(
"class CVirtualAdvisor(\n"
" class CVirtualStrategyGroup(\n"
" [\n"
" %s\n"
" ],%f\n"
" ),\n"
" class CVirtualRiskManager(\n"
" %d,%.2f,%d,%.2f,%.2f,%d,%.2f,%.2f,%d,%.2f,%d,%.2f,%.2f"
" ),\n"
" class CVirtualCloseManager(\n"
" %d,%.2f,%d,%.2f,%d,%.2f"
" )\n"
" ,%d,%s,%d\n"
")",
strategiesParams, scale_,
rmIsActive_, rmStartBaseBalance_,
rmCalcDailyLossLimit_, rmMaxDailyLossLimit_, rmCloseDailyPart_,
rmCalcOverallLossLimit_, rmMaxOverallLossLimit_, rmCloseOverallPart_,
rmCalcOverallProfitLimit_, rmMaxOverallProfitLimit_, rmMaxOverallProfitDate_,
rmMaxRestoreTime_, rmLastVirtualProfitFactor_,
cmIsActive_, cmStartBaseBalance_,
2025-11-29 19:43:43 +03:00
cmCalcLossLimit_, cmLossLimit_,
2025-09-19 13:47:10 +03:00
cmCalcProfitLimit_, cmProfitLimit_,
magic_, __NAME__, useOnlyNewBars_
);
PrintFormat(__FUNCTION__" | Expert Params:\n%s", expertParams);
// Создаем эксперта, работающего с виртуальными позициями
expert = NEW(expertParams);
// Если эксперт не создан, то возвращаем ошибку
if(!expert) return INIT_FAILED;
// Если при замене символов возникла ошибка, то возвращаем ошибку
if(!expert.SymbolsReplace(symbolsReplace_)) return INIT_FAILED;
// Если требуется восстанавливать состояние, то
if(usePrevState_) {
// Загружаем прошлое состояние при наличии
if(!expert.Load()) return INIT_FAILED;
}
2026-05-14 00:00:27 +03:00
2025-11-29 19:43:43 +03:00
expert.Tick();
2025-10-20 17:57:47 +03:00
dialog.Text(expert.Text());
2025-09-19 13:47:10 +03:00
// Успешная инициализация
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick() {
expert.Tick();
// Если одновременно выполнено:
if(groupId_ == 0 // - не задан конкретный идентификатор группы
&& useAutoUpdate_ // - разрешено автообновление
&& IsNewBar(Symbol(), PERIOD_D1) // - наступил новый день
&& expert.CheckUpdate() // - обнаружена новая группа стратегий
) {
// Сохраняем текущее состояние эксперта
expert.Save();
// Удаляем объект эксперта
2025-10-20 17:57:47 +03:00
OnDeinit(REASON_RECOMPILE);
2025-09-19 13:47:10 +03:00
// Вызываем функцию инициализации советника для загрузки новой группы стратегий
OnInit();
}
2025-10-20 17:57:47 +03:00
if (IsNewBar(Symbol(), PERIOD_M1) && !!dialog) {
2025-09-19 13:47:10 +03:00
dialog.Text(expert.Text());
}
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
2025-10-20 17:57:47 +03:00
PrintFormat(__FUNCTION__" : Reason: %d", reason);
2025-09-19 13:47:10 +03:00
if(!!expert) delete expert;
2025-11-27 10:20:58 +03:00
if(!!dialog) {
dialog.Destroy(reason);
delete dialog;
}
2025-09-19 13:47:10 +03:00
}
//+------------------------------------------------------------------+
//| Результат тестирования |
//+------------------------------------------------------------------+
double OnTester(void) {
CExpertHistory::Export();
return expert.Tester();
}
2025-10-20 17:57:47 +03:00
//+------------------------------------------------------------------+
//| Обработка событий |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, // event ID
const long & lparam, // event parameter of the long type
const double & dparam, // event parameter of the double type
const string & sparam) { // event parameter of the string type
if(!!dialog) {
dialog.ChartEvent(id, lparam, dparam, sparam);
}
}
2026-05-14 00:00:27 +03:00
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
const MqlTradeRequest& request,
const MqlTradeResult& result) {
CVirtualReceiver *r = CVirtualReceiver::Instance();
r.Changed();
//---
static int counter = 0; // счетчик вызовов OnTradeTransaction()
static uint lasttime = 0; // время последнего вызова OnTradeTransaction()
//---
uint time = GetTickCount();
//--- если последняя транзакция была больше 1 секунды назад
if(time - lasttime > 1000) {
counter = 0; // значит, это новая торговая операция и можно сбросить счетчик
}
lasttime = time;
counter++;
Print(counter, ". ", __FUNCTION__);
//--- результат выполнения торгового запроса
ulong lastOrderID = trans.order;
ENUM_ORDER_TYPE lastOrderType = trans.order_type;
ENUM_ORDER_STATE lastOrderState = trans.order_state;
//--- имя символа, по которому произошла транзакция
string trans_symbol = trans.symbol;
//r[trans_symbol].Changed();
//--- тип транзакции
ENUM_TRADE_TRANSACTION_TYPE trans_type = trans.type;
switch(trans.type) {
case TRADE_TRANSACTION_POSITION: { // изменение позиции
ulong pos_ID = trans.position;
PrintFormat("MqlTradeTransaction: Position #%I64u %s modified: SL=%.5f TP=%.5f Magic=%I64u",
pos_ID, trans_symbol, trans.price_sl, trans.price_tp, request.magic);
}
break;
case TRADE_TRANSACTION_REQUEST: // отправка торгового запроса
PrintFormat("MqlTradeTransaction: TRADE_TRANSACTION_REQUEST");
break;
case TRADE_TRANSACTION_DEAL_ADD: { // добавление сделки
ulong lastDealID = trans.deal;
ENUM_DEAL_TYPE lastDealType = trans.deal_type;
double lastDealVolume = trans.volume;
//--- идентификатор сделки во внешней системе - тикет, присваиваемый биржей
string Exchange_ticket = "";
if(HistoryDealSelect(lastDealID))
Exchange_ticket = HistoryDealGetString(lastDealID, DEAL_EXTERNAL_ID);
if(Exchange_ticket != "")
Exchange_ticket = StringFormat("(Exchange deal=%s)", Exchange_ticket);
PrintFormat("MqlTradeTransaction: %s deal #%I64u %s %s %.2f lot %s Magic=%I64u", EnumToString(trans_type),
lastDealID, EnumToString(lastDealType), trans_symbol, lastDealVolume, Exchange_ticket, request.magic);
}
break;
case TRADE_TRANSACTION_HISTORY_ADD: { // добавление ордера в историю
//--- идентификатор ордера во внешней системе - тикет, присваиваемый биржей
string Exchange_ticket = "";
if(lastOrderState == ORDER_STATE_FILLED) {
if(HistoryOrderSelect(lastOrderID))
Exchange_ticket = HistoryOrderGetString(lastOrderID, ORDER_EXTERNAL_ID);
if(Exchange_ticket != "")
Exchange_ticket = StringFormat("(Exchange ticket=%s)", Exchange_ticket);
}
PrintFormat("MqlTradeTransaction: %s order #%I64u %s %s %s %s", EnumToString(trans_type),
lastOrderID, EnumToString(lastOrderType), trans_symbol, EnumToString(lastOrderState), Exchange_ticket);
}
break;
default: { // прочие транзакции
//--- идентификатор ордера во внешней системе - тикет, присваиваемый Московской биржей
string Exchange_ticket = "";
if(lastOrderState == ORDER_STATE_PLACED) {
if(OrderSelect(lastOrderID))
Exchange_ticket = OrderGetString(ORDER_EXTERNAL_ID);
if(Exchange_ticket != "")
Exchange_ticket = StringFormat("Exchange ticket=%s", Exchange_ticket);
}
PrintFormat("MqlTradeTransaction: %s order #%I64u %s %s %s", EnumToString(trans_type),
lastOrderID, EnumToString(lastOrderType), EnumToString(lastOrderState), Exchange_ticket);
}
break;
}
//--- тикет ордера
ulong orderID_result = result.order;
string retcode_result = GetRetcodeID(result.retcode);
if(orderID_result != 0)
PrintFormat("MqlTradeResult: order #%d retcode=%s ", orderID_result, retcode_result);
//---
}
//+------------------------------------------------------------------+
//| переводит числовые коды ответов в строковые мнемокоды |
//+------------------------------------------------------------------+
string GetRetcodeID(int retcode) {
switch(retcode) {
case 10004:
return("TRADE_RETCODE_REQUOTE");
break;
case 10006:
return("TRADE_RETCODE_REJECT");
break;
case 10007:
return("TRADE_RETCODE_CANCEL");
break;
case 10008:
return("TRADE_RETCODE_PLACED");
break;
case 10009:
return("TRADE_RETCODE_DONE");
break;
case 10010:
return("TRADE_RETCODE_DONE_PARTIAL");
break;
case 10011:
return("TRADE_RETCODE_ERROR");
break;
case 10012:
return("TRADE_RETCODE_TIMEOUT");
break;
case 10013:
return("TRADE_RETCODE_INVALID");
break;
case 10014:
return("TRADE_RETCODE_INVALID_VOLUME");
break;
case 10015:
return("TRADE_RETCODE_INVALID_PRICE");
break;
case 10016:
return("TRADE_RETCODE_INVALID_STOPS");
break;
case 10017:
return("TRADE_RETCODE_TRADE_DISABLED");
break;
case 10018:
return("TRADE_RETCODE_MARKET_CLOSED");
break;
case 10019:
return("TRADE_RETCODE_NO_MONEY");
break;
case 10020:
return("TRADE_RETCODE_PRICE_CHANGED");
break;
case 10021:
return("TRADE_RETCODE_PRICE_OFF");
break;
case 10022:
return("TRADE_RETCODE_INVALID_EXPIRATION");
break;
case 10023:
return("TRADE_RETCODE_ORDER_CHANGED");
break;
case 10024:
return("TRADE_RETCODE_TOO_MANY_REQUESTS");
break;
case 10025:
return("TRADE_RETCODE_NO_CHANGES");
break;
case 10026:
return("TRADE_RETCODE_SERVER_DISABLES_AT");
break;
case 10027:
return("TRADE_RETCODE_CLIENT_DISABLES_AT");
break;
case 10028:
return("TRADE_RETCODE_LOCKED");
break;
case 10029:
return("TRADE_RETCODE_FROZEN");
break;
case 10030:
return("TRADE_RETCODE_INVALID_FILL");
break;
case 10031:
return("TRADE_RETCODE_CONNECTION");
break;
case 10032:
return("TRADE_RETCODE_ONLY_REAL");
break;
case 10033:
return("TRADE_RETCODE_LIMIT_ORDERS");
break;
case 10034:
return("TRADE_RETCODE_LIMIT_VOLUME");
break;
case 10035:
return("TRADE_RETCODE_INVALID_ORDER");
break;
case 10036:
return("TRADE_RETCODE_POSITION_CLOSED");
break;
default:
return("TRADE_RETCODE_UNKNOWN=" + IntegerToString(retcode));
break;
}
//---
}
2025-09-19 13:47:10 +03:00
//+------------------------------------------------------------------+