![Применение функции TesterWithdrawal() для моделирования снятия прибыли](https://c.mql5.com/i/articles/overlay.png)
Применение функции TesterWithdrawal() для моделирования снятия прибыли
Введение
Цель спекулятивной торговли - получение прибыли. Чаще всего при тестировании торговых систем рассматриваются все аспекты, кроме одного - снятие трейдером части заработанного на существование. Даже если торговля для трейдера не является единственным источником дохода, вопросы о планируемой прибыли за торговый период (месяц, квартал или год) рано или поздно встают. Функция TesterWithdrawal() в MQL5 как раз и предназначена для эмуляции снятия денег с торгового счета.
1. Что мы можем проверить
Есть определенные взгляды на трейдинг и существуют такие торговые системы, где при высоком уровне риска необходимо время от времени снимать образовавшийся излишек в "резервный" фонд. При этом на торговом счете всегда находится только небольшая часть от всего капитала, и это является защитой от того, что в силу каких-то обстоятельств трейдер вдруг сольет сразу все деньги.
Фактически, это является одной из разновидностей управления капиталом (Money Management). Неслучайно во многих фондах, принимающих капитал в управление, каждому трейдеру выделяется определенная квота (часть от общего капитала), весь капитал трейдерам не передается никогда. То есть, помимо лимитов на размеры открываемых позиций, существуют лимиты на капитал, которым может распоряжаться трейдер. В результате каждому трейдеру доступна только небольшая часть денежных средств, для совершения высокорисковых спекуляций.
Еще одна проблема - агрессивное реинвестирование заработанного при тестировании. Трейдеры стремятся в результате оптимизации добиться таких параметров, при которых кривая баланса и средств уходит вертикально вверх. Хотя умом-то все понимают, что на каждом денежном горизонте действуют свои правила, и нельзя надеяться на то, что торговая система, которая прибыльна при торговле 0.1 лотом, будет также хорошо работать при торговле объемом в 100 лотов.
Да и проблема с психологией - любой практикующий трейдер может признаться, что на каком-то уровне денег на счете он вмешивался в работу своей автоматической системы, хотя и зарекался этого не делать. Поэтому снятие излишков в этом смысле также является стабилизирующим фактором торговли.
Для реализации снятия средств со счета в ходе тестирования советника в MQL5 существует специальная функция, рассмотрению которой посвящен следующий раздел.
2. Как пользоваться функцией
Функция TesterWithdrawal() предназначена исключительно для моделирования снятия средств в тестере и не влияет на работу советника в штатном режиме.
bool TesterWithdrawal(double money);
В качестве входного параметра money указывается размер снимаемой суммы в валюте депозита. По возвращаемому значению можно судить об успешности проведенной операции. Каждая выполненная операция по снятию средств, при тестировании, отражается соответствующей записью в "Журнале" а также на закладке "Результаты", как показано на рисунке 1.
Рисунок 1. Записи об успешном снятии средств при тестировании
В случае, если величина снимаемой суммы превысит размер свободных средств и снятия денег не произойдет, то в "Журнале" вы найдёте следующую запись, показанную на рисунке 2.
Рисунок 2. Запись о невыполненной операции снятия средств при тестировании
При вызове данной функции одновременно уменьшается величина текущего баланса, собственных средств (эквити) на величину снимаемой суммы. Однако тестер не учитывает снятие в значениях прибыли и убытка, а подсчитывает общий размер снятия в отдельном показателе "Снятие", как показано на рисунке 3.
Рисунок 3. Показатель общей величины снятия в отчете
Стоит отметить, что обратной функции - зачисления денежных средств на торговый счет в MQL5 пока нет, и, видимо, не будет. Согласитесь, кому нужны торговые системы, нуждающиеся в регулярном денежном пополнении.
В следующем разделе будет рассмотрен пример советника, в котором помимо торговой стратегии реализован набор средств для моделирования снятия прибыли с использованием исследуемой функции.
3. Подопытный кролик для тестирования
Тестирование будем производить на относительно простом советнике. Для тех, кому интересен принцип работы советника, ниже приведено описание его торговой системы.
На определенном периоде, задаваемом внешней переменной PERIOD, 5-и минутных данных, рассчитывается минимальное и максимальное значение цены. Полученные расчетные уровни образуют горизонтальный канал с линией поддержки (переменная CalcLow) и сопротивления (переменная CalcHigh). На границах канала устанавливаются отложенные ордера. Игра ведется на пробой канала.
Условием установки отложенного ордера является факт захода цены вглубь канала на величину не меньше, чем значение входной переменной INSIDE_LEVEL. Причем, всего может быть открыто не более одной позиции или отложенного ордера в каждом из направлений.
Если при пересчёте уровней канал сужается, и цена всё еще находится внутри канала, то происходит перемещение отложенного ордера в сторону рыночной цены. Шаг перемещения отложенных ордеров задается входной переменной ORDER_STEP.
Размер прибыли от сделки указывается во входной переменной TAKE_PROFIT, а максимальный убыток - в переменной STOP_LOSS.
В советнике реализовано перемещение стоп лосса с шагом, указанным во входной переменной TRAILING_STOP.
Торговать можно как фиксированным лотом, так и лотом, рассчитанным как процент от депозита. Значение лота задается входной переменной LOT, а метод расчета лота - переменной LOT_TYPE. Существует возможность коррекции лота (LOT_CORRECTION = true). В случае, если вы торгуете фиксированным лотом, и его величина превышает допустимую для открытия позиции, тогда значение лота будет скорректировано до ближайшего допустимого значения.
Для отладки алгоритма работы советника можно включить функцию ведения журнала (WRITE_LOG_FILE = true). При этом в текстовый файл log.txt будут сохранены записи о всех торговых операциях.
Дополним советник несколькими входными переменными для управления снятием средств.
Первый параметр будет отвечать за возможность снятия средств.
input bool WDR_ENABLE = true; // Разрешение на снятие средств
Второй параметр будет определять периодичность снятия средств.
enum wdr_period { days = -2, // День weeks = -1, // Неделя months = 1, // Месяц quarters = 3, // Квартал halfyears = 6, // Полгода years = 12 // Год };
input wdr_period WDR_PERIOD = weeks; // Периодичность снятия средствТретий параметр будет задавать величину снимаемой суммы.
input double WDR_VALUE = 1; // Величина снимаемой суммы
Четвертый параметр будет определять метод расчета величины снимаемой суммы.
enum lot_type { fixed, // Фиксированный percent // Процент от депозита };
input lot_type WDR_TYPE = percent; // Способ расчета величины снятия
Для расчета величины однократно выводимой со счета суммы используется переменная wdr_value. Для подсчета общего объема выведенных со счета средств, служит переменная wdr_summa.
Также мы будем подсчитывать общее количество успешных операций вывода - переменная wdr_count. Значения этих переменных будут нам нужны для формирования собственного отчета о результатах тестирования. Весь функционал по снятию средств реализован в приведенной ниже функции.
//--- Функция снятия средств со счета //+------------------------------------------------------------------+ bool TimeOfWithDrawal() //+------------------------------------------------------------------+ { if(!WDR_ENABLE) return(false); // выход, если снятие средств запрещено if( tick.time > dt_debit + days_delay * DAY) // периодическое снятие с заданным интервалом { dt_debit = dt_debit + days_delay * DAY; days_delay = Calc_Delay();// Обновление значения периода-числа дней между операциями снятия if(WDR_TYPE == fixed) wdr_value = WDR_VALUE; else wdr_value = AccountInfoDouble(ACCOUNT_BALANCE) * 0.01 * WDR_VALUE; if(TesterWithdrawal(wdr_value)) { wdr_count++; wdr_summa = wdr_summa + wdr_value; return(true); } } return(false); }
Теперь нам необходимо подготовить наш советник для тестирования в режиме оптимизации. Собственных параметров оптимизации будет несколько, поэтому введем входную переменную, с помощью которой можно будет выбирать требуемый параметр.
enum opt_value { opt_total_wdr, // Общая сумма снятия opt_edd_with_wdr, // Просадка с учетом снятия opt_edd_without_wdr // Просадка без учета снятия };
input opt_value OPT_PARAM = opt_total_wdr; // Оптимизация по параметру
Также нам необходимо дополнить советник важной функцией OnTester(), в которой возвращаемое значение определяет критерий оптимизации.
//--- Вывод информации о тестировании //+------------------------------------------------------------------+ double OnTester(void) //+------------------------------------------------------------------+ { //--- Расчет параметров для отчета CalculateSummary(initial_deposit); CalcEquityDrawdown(initial_deposit, true, false); //--- Создание отчета GenerateReportFile("report.txt"); //--- Возвращаемое значение - критерий оптимизации if (OPT_PARAM == opt_total_wdr) return(wdr_summa); else return(RelEquityDrawdownPercent); }
В следующем разделе будет подробно рассмотрен процесс тестирования нашего советника.
4. Тестирование советника
Для чистоты эксперимента, для начала важно получить результаты тестирования нашего советника с отключенной функцией снятия средств. Для этого достаточно перед тестированием установить параметр "Разрешение на снятие средств" равным false, как показано на рисунке 5.
Для тех, кому будет интересно повторить результаты тестирования, ниже приведены настройки и значения входных параметров советника, показанные на рисунках 4 и 5.
Рисунок 4. Настройки советника для тестирования
Рисунок 5. Параметры советника с отключенной функцией снятия средств
По окончании тестирования получены следующие результаты, показанные на рисунках 6 и 7.
Рисунок 6. Изменение баланса за полгода тестовой работы советника
Рисунок 7. Таблица результатов работы советника
Из всей таблицы результатов нам интересен параметр относительной просадки по средствам. В нашем случае он равен 7.75%.
Теперь проверим, как изменится этот параметр при включенной функции снятия средств. Тестирование произведем как для разных значений размера снимаемой суммы, так и разной периодичности снятия.
На рисунке 8 приведены параметры советника с включенной оптимизацией и снятием, а на рисунке 9 показаны результаты такого тестирования.
Рисунок 8. Параметры советника с включенной оптимизацией и снятием средств
Рисунок 9. Результаты расчета относительной просадки по средствам
Результаты теста несколько удивили, так как при периодичности снятия, равной дню и неделе, относительная просадка на определенном интервале меньше просадки с отключенным снятием денег.
Кроме того, кривая дневной просадки увеличивается до 100% а затем возвращается к уровню 8% и продолжает падать. Чтобы правильно истолковать полученные результаты, необходимо знать, как рассчитывается относительная просадка по собственным средствам в тестере. Об этом пойдет речь в следующем разделе.
5. Расчет просадок по средствам
Пытливым умам будет крайне интересно узнать, каким образом происходит расчет просадки по собственным средствам непосредственно в самом тестере, и каким образом можно рассчитать этот параметр самостоятельно.
В случае, если в вашем советнике не применяется функция TesterWithdrawal(), то расчет анализируемого нами параметра - относительной просадки по средствам, ничем не отличается от расчета последнего в MetaTrader 4, который описан в статье "Что означают цифры в отчёте тестирования эксперта", а код расчета этого и многих других параметров результирующего отчета тестера приведен в статье "Самостоятельная оценка результатов тестирования эксперта".
Наглядным пояснением того, как происходит расчет относительной и максимальной просадки в тестере, служит рисунок 10, приведенный ниже.
Рисунок 10. Расчет просадок по средствам без учета снятия
В процессе работы тестер определяет текущее максимальное и минимальное значения средств. С приходом нового максимума по эквити, отмеченного на графике синей птичкой, происходит пересчет максимальной и относительной просадок и сохранение большего значения для вывода в результирующий отчет.
Важным является также последний пересчет параметров в конце тестирования, т.к. вполне возможна ситуация, когда последние незафиксированные экстремумы по средствам дадут максимальное значение просадки. Изменение максимальных значений просадок показаны синим и красным цветом соответственно. Серым цветом показана просадка фиксируемая с приходом каждого нового максимума.
Важно отметить, что вызов функции TesterWithDrawal() меняет алгоритм расчета просадок в тестере. Разница в сравнении с предыдущим вариантом сводится к тому, что пересчет значений просадок происходит не только с приходом нового максимума по эквити, но и в моменты снятия средств. Наглядно это показано на рисунке 11.
Рисунок 11. Расчет просадок со снятием средств
На приведенном рисунке моменты снятия средств отмечены зелеными птичками. Если снятие средств происходит сравнительно часто, это не дает возможности зафиксироваться максимальным значениям просадок, определяемых экстремумами графика, и поэтому результирующая просадка со снятием средств может быть меньше, чем просадка без снятия, как это уже было замечено на рисунке 9.
Если же снятие средств будет происходить в значительно большем объеме, чем рост эквити за счет получения прибыли, это приведет к тому, что значения относительной просадки также будут низкими, т.к. подобная ситуация не даст сформироваться большим экстремумам на графике, и в пределе он будет похож на прямую ниспадающую линию, где относительная просадка стремится к нулю. Этот эффект начал проявляться при ежедневном снятии средств больше 1000$ на графике рисунка 9.
Воспроизведем расчет максимальной и относительной просадки по средствам на языке MQL5, сведя весь алгоритм расчета в одну процедуру CalcEquityDrawdown приведенную ниже.
double RelEquityDrawdownPercent; // относительная просадка по средствам, в процентах double MaxEquityDrawdown; // максимальная просадка по средствам
//--- Расчет просадок по средствам //+------------------------------------------------------------------+ void CalcEquityDrawdown(double initial_deposit, // начальный депозит bool finally) // признак расчета, фиксирующий экстремумы //+------------------------------------------------------------------+ { double drawdownpercent; double drawdown; double equity; static double maxpeak = 0.0, minpeak = 0.0; //--- исключение учета снятия прибыли для расчета просадок if(wdr_ignore) equity = AccountInfoDouble(ACCOUNT_EQUITY) + wdr_summa; else equity = AccountInfoDouble(ACCOUNT_EQUITY); if(maxpeak == 0.0) maxpeak = equity; if(minpeak == 0.0) minpeak = equity; //--- проверка условия экстремума if((maxpeak < equity)||(finally)) { //--- расчет просадок drawdown = maxpeak - minpeak; drawdownpercent = drawdown / maxpeak * 100.0; //--- Сохранение максимальных значений просадок if(MaxEquityDrawdown < drawdown) MaxEquityDrawdown = drawdown; if(RelEquityDrawdownPercent < drawdownpercent) RelEquityDrawdownPercent = drawdownpercent; //--- обнуление значений экстремумов maxpeak = equity; minpeak = equity; } if(minpeak > equity) minpeak = equity; }
Для сохранения точности расчета, необходимо, чтобы приведенная выше процедура вызывалась в теле вашего советника, с приходом каждого тика, с параметрами приведенными ниже.
CalcEquityDrawdown(initial_deposit, false);
Кроме этого, для получения достоверных значений просадок, она должна быть также вызвана в конце работы советника в функции OnTester() с параметром finally = true, указывающим на завершение расчета. При этом, если текущая незафиксированная просадка окажется больше максимальной учтенной, то она заменит максимальную и попадёт в результирующий отчет.
CalcEquityDrawdown(initial_deposit, true);
Если в советнике реализован алгоритм снятия средств, то для правильного расчета значений просадок необходимо вызывать функцию CalcEquityDrawdown с параметром finally=true всякий раз, когда производится снятие средств. Порядок вызова может быть следующим.
//--- Снятие средств и расчет просадок по средствам if(TimeOfWithDrawal()) CalcEquityDrawdown(initial_deposit, true); else CalcEquityDrawdown(initial_deposit, false);
Чтобы удостоверится в правильности описанной выше методики расчета, произведем сравнение данных, полученных расчетным путем с данными тестера. Для этого необходимо, чтобы функция OnTester() возвращала значение того параметра, который мы хотим проверить - относительную просадку по средствам, хранящуюся в переменной RelEquityDrawdownPercent.
В коде советника это реализуется, если установить значение входного параметра "Оптимизация по параметру" равным "Просадка с учетом снятия". Остальные параметры оставим идентичными тем, что указанны на рисунке 8. На рисунке 12 содержатся результаты такого тестирования.
Рисунок 12. Сравнение результатов самостоятельного расчета с данными тестера
Сравнивая полученные результаты, видно, что описанный алгоритм расчета для относительной просадки верен.
Дополним расчет просадки еще одним вариантом. В нем мы исключим влияние операции вывода средств на изменение эквити и посмотрим, как изменится относительная просадка по средствам.
Для этого дополним советник переменной, которая будет определять, нужно ли учитывать вывод средств при расчете параметров советника.
bool wdr_ignore; // расчет показателей отчета не учитывая снятия
Значение wdr_ignore будет равным true в случае установки входной переменной "Оптимизация по параметру" равной "Просадка без учета снятия".
Помимо этого, необходимо внести изменения в саму процедуру расчета просадки CalcEquityDrawdown, добавив в неё обработку этого параметра, как это показано ниже.
if (wdr_ignore) equity = AccountInfoDouble(ACCOUNT_EQUITY) + wdr_summa; else equity = AccountInfoDouble(ACCOUNT_EQUITY);
Теперь всё готово для получения новых значений просадок. Произведем тестирование с включенным новым алгоритмом расчета. Результат тестирования показан на рисунке 13.
Рисунок 13. Результаты расчета просадки без учета снятия средств
Из результата видно, что ни сам факт снятия средств, ни величина снимаемой суммы, не влияет на просадку. По сути, получен чистый показатель, на который не влияет работа функции TesterWithDrawal(). Однако, при таком расчете, необходимо скорректировать изменившиеся значения прибыли и убытка, т.к. их реальные значения изменились и в результирующем отчете тестера эти показатели не являются правильными.
Произведем расчет прибыли, убытка и их отношения (прибыльности) и сохраним полученные значения в виде отчета в текстовый файл. Процедура расчета перечисленных параметров приведена ниже.
double SummaryProfit; // чистая прибыль double GrossProfit; // общая прибыль double GrossLoss; // общий убыток double ProfitFactor; // прибыльность
//--- Расчет параметров для отчета //+------------------------------------------------------------------+ void CalculateSummary(double initial_deposit) //+------------------------------------------------------------------+ { double drawdownpercent, drawdown; double maxpeak = initial_deposit, minpeak = initial_deposit, balance = initial_deposit; double profit = 0.0; //--- Выбираем всю историю HistorySelect(0, TimeCurrent()); int trades_total = HistoryDealsTotal(); //--- Перебор сделок в истории for(int i=0; i < trades_total; i++) { long ticket = HistoryDealGetTicket(i); long type = HistoryDealGetInteger(ticket, DEAL_TYPE); //--- Начальный депозит не учитывается if((i == 0)&&(type == DEAL_TYPE_BALANCE)) continue; //--- Расчет прибыли profit = HistoryDealGetDouble(ticket, DEAL_PROFIT) + HistoryDealGetDouble(ticket, DEAL_COMMISSION) + HistoryDealGetDouble(ticket, DEAL_SWAP); balance += profit; if(minpeak > balance) minpeak = balance; //--- if((!wdr_ignore)&&(type != DEAL_TYPE_BUY)&&(type != DEAL_TYPE_SELL)) continue; //--- if(profit < 0) GrossLoss += profit; else GrossProfit += profit; SummaryProfit += profit; } if(GrossLoss < 0.0) GrossLoss *= -1.0; //--- Прибыльность if(GrossLoss > 0.0) ProfitFactor = GrossProfit / GrossLoss; }
Ниже приведена функция для генерации отчета в текстовый файл.
//--- Формирование отчета //+------------------------------------------------------------------+ void GenerateReportFile(string filename) //+------------------------------------------------------------------+ { string str, msg; ResetLastError(); hReportFile = FileOpen(filename, FILE_READ|FILE_WRITE|FILE_TXT|FILE_ANSI); if(hReportFile != INVALID_HANDLE) { StringInit(str,65,'-'); // разделитель WriteToReportFile(str); WriteToReportFile("| Период тестирования: " + TimeToString(first_tick.time, TIME_DATE) + " - " + TimeToString(tick.time,TIME_DATE) + "\t\t\t|"); WriteToReportFile(str); //---- WriteToReportFile("| Начальный депозит \t\t\t"+DoubleToString(initial_deposit, 2)); WriteToReportFile("| Чистая прибыль \t\t\t"+DoubleToString(SummaryProfit, 2)); WriteToReportFile("| Общая прибыль \t\t\t"+DoubleToString(GrossProfit, 2)); WriteToReportFile("| Общий убыток \t\t\t"+DoubleToString(-GrossLoss, 2)); if(GrossLoss > 0.0) WriteToReportFile("| Прибыльность \t\t\t"+DoubleToString(ProfitFactor,2)); WriteToReportFile("| Относительная просадка по средствам \t"+ StringFormat("%1.2f%% (%1.2f)", RelEquityDrawdownPercent, MaxEquityDrawdown)); if(WDR_ENABLE) { StringInit(msg, 10, 0); switch(WDR_PERIOD) { case day: msg = "день"; break; case week: msg = "неделя"; break; case month: msg = "месяц"; break; case quarter: msg = "квартал"; break; case year: msg = "год"; break; } WriteToReportFile(str); WriteToReportFile("| Периодичность вывода \t\t" + msg); if(WDR_TYPE == fixed) msg = DoubleToString(WDR_VALUE, 2); else msg = DoubleToString(WDR_VALUE, 1) + " % от депозита " + DoubleToString(initial_deposit, 2); WriteToReportFile("| Размер выводимой суммы \t\t" + msg); WriteToReportFile("| Количество операций вывода \t\t" + IntegerToString(wdr_count)); WriteToReportFile("| Выведено со счета \t\t" + DoubleToString(wdr_summa, 2)); } WriteToReportFile(str); WriteToReportFile(" "); FileClose(hReportFile); } }
В результате выполнения данной функции будет создан текстовый файл, заполненный рассчитанными данными, как показано на рисунке 14.
Рисунок 14. Файл отчета генерируемый процедурой GenerateReportFile
Функция написана таким образом, что новый отчет добавляется к содержимому файла. Благодаря этому есть возможность сравнить значения результатов тестирования при различных входных параметрах советника.
Как видно из отчета, при расчете, не учитывающим снятие средств (нижняя таблица), чистая прибыль будет меньше, а общий убыток получается больше ровно на величину снятой суммы, по сравнению с расчетом тестера (верхняя таблица). Следует заметить, что для получения реальных значений прибыли и убытка при снятии средств в тестере, вам необходимо самостоятельно вычитать значение параметра "Cнятие" из чистой прибыли,и прибавлять к общему убытку.
6. Анализ результатов
Из результатов всех проделанных экспериментов можно сделать ряд выводов:
- Применение функции TesterWithDrawal() приводит к изменению алгоритма расчета просадок в тестере. Сравнение нескольких разных советников по значению относительной просадки, в одном из которых будет работать механизм вывода средств, может быть некорректным. Применяя данную функцию, можно вывести прагматичный расчет того, сколько денег вы можете периодически снимать со счета, исходя из заданного, допустимого для вас процента просадки по эквити.
- Применение данной функции может быть реализовано в виде синтетического дестабилизирующего фактора торговли, для проверки устойчивости работы вашего советника, и отладки логики работы кода отвечающего за управление капиталом. Если в вашем советнике логика принятия решений зависит от уровня баланса или средств, то применение данной функции дает дополнительные возможности для тестирования и отладки.
- При пересчете относительной просадки без учета вывода средств, используя алгоритм, изложенный в статье, можно получить чистое значение относительной просадки, на которое не влияет применение данной функции
Заключение
В статье рассмотрено применение функции TesterWithdrawal() для моделирования снятия средств со счета в тестере стратегий и показано, как применение данной функции влияет на алгоритм расчета просадки по средствам в тестере.
![Прототип торгового робота](https://c.mql5.com/2/0/Prototype_Expert_Advisor_MQL5.png)
![Несколько способов определения тренда на MQL5](https://c.mql5.com/2/0/Determine_Trend_MQL5.png)
![20 торговых сигналов на MQL5](https://c.mql5.com/2/0/20_Trading_Signals_MQL5__1.png)
![Как создать свой Trailing Stop](https://c.mql5.com/2/0/Trailing_Stop_MQL5.png)
![MQL5 - Язык торговых стратегий для клиентского терминала MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования