Создание самооптимизирующихся советников на MQL5 (Часть 13): Введение в теорию управления с использованием факторизации матриц
Финансовые рынки зачастую сложно спланировать или предсказать заранее. Настроения инвесторов зачастую неустойчивы и могут быстро меняться в зависимости от глобальной конъюнктуры и актуальных проблем, доминирующих в повестке дня. Поэтому торговые стратегии, которые кажутся прибыльными в историческом контексте, зачастую оказываются неэффективными при применении на рынках в режиме реального времени.
Существует множество причин, которыми можно объяснить такое поведение в торговых приложениях. Однако важно понимать, что после разработки и развертывания наших приложений их поведение остается неизменным и, как правило, не может быть изменено без вмешательства человека. Это означает, что наши стратегии подвержены риску повторения одних и тех же ошибок снова и снова, не позволяя нам извлечь пользу из неудач или извлечь уроки из прошлых ошибок.

Рисунок 1: Стандартная архитектура, используемая для развертывания торговых приложений на финансовых рынках
Было предложено множество решений этой постоянно возникающей проблемы. Однако одно из решений, обладающее огромным потенциалом, лежит в области теории управления. Теория управления в первую очередь занимается корректировкой поведения системы, функционирующей в динамичной или хаотичной среде, с целью приведения системы в соответствие с заданной целью.
Периодически передавая данные о результатах нашей стратегии в контроллер обратной связи — который фиксирует и отслеживает взаимодействие стратегии с рынками — мы сможем приблизительно определить взаимосвязь между поведением нашей стратегии и рыночными результатами. Это указывает на то, что контроллер с обратной связью научился выявлять преобладающие закономерности, отличающие убыточные сделки от прибыльных. Если такая структура существует и мы можем извлечь из неё уроки, то теоретически контроллер с обратной связью должен быть способен корректировать динамику нашей торговой системы и направлять её к прибыльности даже в условиях хаоса и постоянных изменений на рынке.
Это фактически изменит структуру развертывания нашей стратегии по сравнению со схемой, представленной на рисунке 1. На рисунке 2 мы знакомим читателя с простыми обозначениями, используемыми в литературе по теории управления, и обозначили входные сигналы рынка как (M), а выходные сигналы нашей стратегии — как (S).

Рисунок 2: Мы можем переопределить наше торговое приложение, используя сокращённую нотацию для обозначения рыночных входных данных (M) и результатов стратегии (S)
В большинстве наших предыдущих обсуждений, посвященных факторизации матриц, мы в первую очередь уделяли внимание построению регрессионных и классификационных моделей для прогнозирования будущих уровней цен или изменений технических индикаторов. Однако в данном обсуждении мы обратимся к теории управления, а именно к разделу, известному как контроллеры с обратной связью. Этот аспект часто упускается из виду при обсуждении торговых приложений, основанных на численных методах, хотя его основополагающие теории можно адаптировать для удовлетворения наших потребностей как трейдеров.
Наша цель — доказать, что контроллеры с обратной связью обеспечивают высокую степень точного управления торговыми системами. При правильной настройке эти контроллеры могут корректировать работу системы и поддерживать её на курсе, обеспечивая прибыльность сделок.
Мы начали с базовой версии нашей торговой стратегии, в которой использовались две скользящие средние: одна по максимальным ценам, а другая — по минимальным. Обе скользящие средние имели одинаковый период, что фактически сформировало канал скользящих средних. Когда цена пробила верхнюю границу этого канала, мы открыли длинные позиции; когда цена пробила нижнюю границу канала, мы открыли короткие позиции. Эта исходная версия определила порог рентабельности, который мы намеревались превзойти с помощью нашего контроллера с обратной связью. Наша торговая стратегия представлена ниже на рисунке 3.

Рисунок 3: Визуализация нашей торговой стратегии в действии на дневном графике EURUSD
Наш контроллер с обратной связью сначала отслеживал эффективность нашей стратегии в течение 90 дней, прежде чем ему было разрешено вмешиваться. То есть в течение первых 90 дней контроллер не подавал никаких сигналов в систему, а лишь собирал данные наблюдений. Читателям следует иметь в виду, что этот 90-дневный период является настраиваемым параметром, хотя его оптимальное значение не всегда известно заранее. Мы произвольно выбрали 90 дней, полагая, что этот срок будет соответствовать бизнес-циклам финансовых институтов, доминирующих на валютных рынках. Тем не менее, читатели могут экспериментировать с этим параметром по своему усмотрению.
В течение этих 90 дней контроллер фиксировал несколько ключевых показателей: рыночные цены предложения, баланс счета, средства на счете, значения индикаторов и типы открытых нами позиций. Эти наблюдения были сохранены в матрице, называемой «моментальным снимком». Затем мы построили линейную модель, которая отражала изменение снимков во времени. Другими словами, первые 89 снимков были сопоставлены с последними 89 снимками, что позволило системе понять, как состояние стратегии менялось с течением времени.
По истечении 90 дней линейная система собрала все данные о состоянии системы и предсказала, будет ли следующая сделка прибыльной или убыточной. Если следующая сделка, по прогнозам, должна была оказаться убыточной, система просто приостанавливала торговлю до тех пор, пока не наступали более благоприятные рыночные условия.
Таким образом, задача нашего контроллера с обратной связью заключается в том, чтобы отслеживать выходные данные нашей системы (S) и обучаться новой функции управления (F), которая изменяет поведение нашей системы (FS) с целью вернуть её к прибыльности, чтобы наша стратегия не зависела исключительно от прямого влияния рынка (M). По истечении 90-дневного периода наблюдения наша стратегия больше не будет зависеть исключительно от рынка (M); теперь она будет определяться сочетанием рыночных данных и рекомендаций нашего контроллера обратной связи (FS + M)

Рисунок 4: Визуализация того, как именно наш контроллер обратной связи изменит поведение нашего торгового приложения после развертывания
Мы провели бэктестинг обеих систем на пятилетнем массиве ежедневных данных по паре EUR/USD. Контроллер с обратной связью повысил рентабельность системы на 82%. Изначально наша базовая стратегия принесла прибыль в размере 134 долларов. После внедрения системы управления с обратной связью прибыль выросла до 245 долларов. Кроме того, усовершенствованная система обеспечила такую прибыль при меньшем количестве сделок: число позиций сократилось с 180 до 152, что означает снижение торговой активности на 15 %. Это означало, что наша новая система приносила более высокую прибыль при меньшем риске, что является крайне желательной характеристикой для любого торгового приложения.
Кроме того, был снижен общий уровень риска. В базовой системе общий убыток составил –1 092 доллара, тогда как в системе с обратной связью этот показатель удалось снизить до –838 долларов, при этом сохранив практически неизменную валовую прибыль. Это ещё один весьма желательный результат.
Коэффициент Шарпа базовой системы составил 0,34. После внедрения контроллера с обратной связью этот показатель вырос до 0,68 — это улучшение на 100 %, что является выдающимся результатом для такого сложного рынка, как EUR/USD. Доля прибыльных сделок также выросла на 6% — с 53,89% до 57,24%. Соответственно, доля убыточных сделок сократилась на ту же величину, что свидетельствует о том, что контроллер с обратной связью успешно выделил доминирующие закономерности, позволяющие отличать убыточные сделки от прибыльных. Наконец, ожидаемая выгода выросла с 0,75 до 1,61, что означает улучшение на 114 %.
Очевидно, что контроллеры с обратной связью могут играть ключевую роль в торговых приложениях, основанных на численных методах. При правильной настройке они способны надежно научиться вмешиваться и не допускать повторения тех же ошибок в рамках стратегии. Построение контроллера непосредственно на основе данных наблюдений за состоянием системы — это метод, известный как идентификация системы.
Это семейство алгоритмов было первоначально разработано инженерами, занимающимися динамикой жидкостей. Эти инженеры часто сталкивались с необходимостью разрабатывать системы управления для компонентов, составляющих крылья самолетов, однако четких формул для прогнозирования или корректировки последствий турбулентности не существует. Естественно, им нужно было найти способы вычисления оптимальных управляющих воздействий на основе наблюдений за поведением системы и воздействий, вызывающих это поведение.
В ходе нашего обсуждения мы построили линейную модель системы, поэтому этот подход более точно называется идентификацией линейной системы. Представленные здесь результаты побуждают нас уделить больше времени постановке дополнительных задач перед контроллером с обратной связью и изучению методов идентификации нелинейных систем в будущем. Давайте начнём.
Начало работы с MQL5
Как и в случае с большинством наших торговых приложений, мы начинаем с определения важных системных параметров. Для нашей базовой стратегии требуется всего одно системное определение, которое задает период скользящего среднего.
//+------------------------------------------------------------------+ //| Closed Loop Feedback.mq5 | //| Gamuchirai Ndawana | //| https://www.mql5.com/en/users/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Ndawana" #property link "https://www.mql5.com/en/users/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| System definitions | //+------------------------------------------------------------------+ #define MA_PERIOD 10
Следующим важным аспектом проектирования нашей системы является набор глобальных переменных. Для данного конкретного базового сценария нам потребуется лишь несколько глобальных переменных, связанных с техническими индикаторами, на которые мы опираемся. В частности, у нас будут обработчики для двух индикаторов скользящих средних и ещё один обработчик для индикатора ATR, который мы используем для установки стоп-лоссов. Каждому из этих индикаторов также требуется собственный буфер.
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ int ma_h_handler,ma_l_handler,atr_handler; double ma_h[],ma_l[],atr[];
Практически все торговые приложения, которые мы создаем в ходе этих обсуждений, имеют зависимости, поскольку мы не всегда пишем каждый фрагмент кода с нуля. В этом приложении мы используем стандартную библиотеку, а также две собственные библиотеки: одну для отслеживания формирования новых свечей, а другую — для получения ключевой информации о ценах, такой как цены спроса и предложения.
//+------------------------------------------------------------------+ //| Dependencies | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> #include <VolatilityDoctor\Time\Time.mqh> #include <VolatilityDoctor\Trade\TradeInfo.mqh> CTrade Trade; Time *DailyTimeHandler; TradeInfo *TradeInfoHandler;
При инициализации системы мы начинаем с создания новых экземпляров наших пользовательских классов. Мы также создаем новые экземпляры наших индикаторов.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- DailyTimeHandler = new Time(Symbol(),PERIOD_D1); TradeInfoHandler = new TradeInfo(Symbol(),PERIOD_D1); ma_h_handler = iMA(Symbol(),PERIOD_D1,MA_PERIOD,0,MODE_EMA,PRICE_HIGH); ma_l_handler = iMA(Symbol(),PERIOD_D1,MA_PERIOD,0,MODE_EMA,PRICE_LOW); atr_handler = iATR(Symbol(),PERIOD_D1,14); //--- return(INIT_SUCCEEDED); }
Когда приложение больше не используется, мы удаляем созданные нами динамические объекты для эффективного управления памятью и освобождаем неиспользуемые индикаторы для безопасного управления ресурсами.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- delete DailyTimeHandler; delete TradeInfoHandler; IndicatorRelease(ma_h_handler); IndicatorRelease(ma_l_handler); }
При получении нового значения цены вызывается обработчик OnTick. Этот обработчик использует нашу собственную библиотеку для проверки, сформировалась ли новая дневная свеча. При обнаружении новой свечи мы обновляем показания индикатора, хранящиеся в наших буферах, и сохраняем копию текущей цены закрытия. Если открытых позиций нет, мы применяем наши торговые правила, чтобы принять решение о покупке или продаже: если цена закрытия находится выше верхней границы нашего канала скользящих средних, мы открываем длинную позицию; если она находится ниже нижней границы нашего канала скользящих средних, мы открываем короткую позицию.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- if(DailyTimeHandler.NewCandle()) { CopyBuffer(ma_h_handler,0,0,1,ma_h); CopyBuffer(ma_l_handler,0,0,1,ma_l); CopyBuffer(atr_handler,0,0,1,atr); double c = iClose(Symbol(),PERIOD_D1,0); if(PositionsTotal() == 0) { if(c > ma_h[0]) Trade.Buy(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetAsk(),(TradeInfoHandler.GetBid()-(atr[0]*2)),(TradeInfoHandler.GetBid()+(atr[0]*2)),""); if(c < ma_l[0]) Trade.Sell(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetBid(),(TradeInfoHandler.GetAsk()+(atr[0]*2)),(TradeInfoHandler.GetAsk()-(atr[0]*2)),""); } } }
Наконец, когда все операции завершены, мы убираем за собой, удаляя системные определения, которые были созданы в начале работы приложения. В целом, это и составляет базовую версию нашей торговой системы.
//+------------------------------------------------------------------+ //| Undefine system constants | //+------------------------------------------------------------------+ #undef MA_PERIOD //+------------------------------------------------------------------+
Теперь мы можем приступить к тестированию нашего приложения на исторических данных. Для данного теста мы отбираем данные по курсу EUR/USD за пять лет и применяем наше базовое приложение.

Рисунок 5: Бэктестинг нашего торгового приложения на основе 5-летних исторических данных по паре EUR/USD
Чтобы сделать тест более реалистичным, мы также вводим случайные задержки, поскольку реальные рыночные условия непредсказуемы, а задержки помогают смоделировать эту неопределённость.

Рисунок 6: Выбор реалистичных условий для бэктеста в рамках нашего анализа
После этого можно сформировать подробный отчет о результатах работы приложения. В введении к этой статье я уже представил общий обзор результатов бэктеста. Здесь мы видим, что коэффициент Шарпа системы низкий — он составляет 0,34, а средний убыток по сделке превышает среднюю прибыль. Справедливости ради стоит отметить, что даже в нашей усовершенствованной версии приложения убыточные сделки по-прежнему в среднем были немного больше, чем прибыльные. Однако нашей системе обратной связи с замкнутым контуром удалось сократить этот разрыв.

Рисунок 7: Подробная статистика, отражающая эффективность работы нашего торгового приложения
Кроме того, при анализе кривой капитала нашей базовой системы мы видим, что она действительно демонстрирует восходящую тенденцию. Однако ей не хватает стабильности: она часто месяцами колеблется в узком диапазоне, поочередно принося прибыль и убытки, в результате чего стратегия фактически остается на одном месте в течение длительных периодов. Именно эту нестабильность мы и стремимся устранить, применяя наш контроллер с обратной связью для корректировки поведения системы.

Рисунок 8: Кривая капитала, построенная нашим торговым приложением, не выглядит удовлетворительно из-за нестабильной формы
Улучшение наших первоначальных результатов
Теперь мы готовы приступить к оптимизации наших первоначальных результатов с помощью контроллера с обратной связью. Чтобы избежать ненужного повторения, я опустил те части кода, которые остались без изменений, и сосредоточусь в основном на изменениях, внесённых в исходную версию нашего приложения.
Как можно сразу заметить, количество системных определений, необходимых для нашего приложения, увеличилось. В нашей первоначальной версии приложения требовалось всего одно системное определение, но обновленная версия зависит от четырёх. Эти новые определения связаны со следующим: (1) общее количество наблюдений, которое мы хотим собрать до того, как контроллер с обратной связью начнёт работать в режиме реального времени, (2) количество признаков, за которыми мы хотим следить — в данном примере это двенадцать признаков, отражающих эффективность нашей торговой стратегии, — и (3) вектор, отражающий тип позиции, которую мы в данный момент удерживаем.
//+------------------------------------------------------------------+ //| Closed Loop Feedback.mq5 | //| Gamuchirai Ndawana | //| https://www.mql5.com/en/users/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Ndawana" #property link "https://www.mql5.com/en/users/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| System definitions | //+------------------------------------------------------------------+ #define MA_PERIOD 10 #define OBSERVATIONS 90 #define FEATURES 12 #define ACCOUNT_STATES 3
По мере расширения нашего приложения глобальные переменные также становятся все более сложными. Теперь нам понадобятся матрицы для хранения моментальных снимков, векторы для записи прогнозов, полученных из нашей линейной системы, а также булевы флаги, чтобы определять, когда система готова перейти от режима наблюдения к реальной торговле.
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ int ma_h_handler,ma_l_handler,atr_handler,scenes,b_matrix_scenes; double ma_h[],ma_l[],atr[]; matrix snapshots,OB_SIGMA,OB_VT,OB_U,b_vector,b_matrix; vector S,prediction; vector account_state; bool predict,permission;
Во время инициализации выполняется ряд этапов предварительной обработки. Первые несколько шагов уже знакомы: мы настраиваем технические индикаторы. Затем мы инициализируем матрицу моментальных снимков, состоящую из 12 строк и 90 столбцов. На данном этапе все значения установлены на ноль. Флаг «разрешение» инициализируется как true, что означает, что система запускается с разрешением на торговлю. Однако по истечении 90-дневного периода наблюдения этот флаг устанавливается в значение false, а флаг «predict» — в значение true. В этот момент система больше не совершает сделки без ограничений; прежде чем приступить к действиям, она должна сначала получить разрешение от линейной модели.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- DailyTimeHandler = new Time(Symbol(),PERIOD_D1); TradeInfoHandler = new TradeInfo(Symbol(),PERIOD_D1); ma_h_handler = iMA(Symbol(),PERIOD_D1,MA_PERIOD,0,MODE_EMA,PRICE_HIGH); ma_l_handler = iMA(Symbol(),PERIOD_D1,MA_PERIOD,0,MODE_EMA,PRICE_LOW); atr_handler = iATR(Symbol(),PERIOD_D1,14); snapshots = matrix::Ones(FEATURES,OBSERVATIONS); scenes = 0; b_matrix_scenes = 0; account_state = vector::Zeros(3); b_matrix = matrix::Zeros(1,1); prediction = vector::Zeros(2); predict = false; permission = true; //--- return(INIT_SUCCEEDED); }
Обработчик OnTick претерпел значительные изменения по сравнению с базовой версией системы. Одно из ключевых изменений заключается в том, что состояние учетной записи теперь хранится в векторе, состоящем из трёх элементов. В этом векторе используется метод «one-hot»-кодирования: если открыта позиция на покупку, первая ячейка принимает значение 1; если открыта позиция на продажу, вторую ячейку устанавливают на 1; если позиции нет, третью ячейку устанавливают на 1. Это позволяет передавать категориальную информацию линейной системе в структурированном формате.
Сама по себе торговая логика остается прежней: если цена закрытия находится выше верхней границы канала скользящих средних, мы рассматриваем возможность покупки; если она ниже нижней границы, мы рассматриваем возможность продажи. В течение первых 90 дней, пока флаг прогноза имеет значение «false», система имеет безусловное разрешение на торговлю. После этого все торговые решения должны быть подтверждены линейной системой. Как только будет собрано необходимое количество наблюдений, матрица моментального снимка изменяется в размерах для учета новых данных, и выявленная нами линейная система фильтрует наши торговые решения перед выходом на рынок.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- if(DailyTimeHandler.NewCandle()) { CopyBuffer(ma_h_handler,0,0,1,ma_h); CopyBuffer(ma_l_handler,0,0,1,ma_l); CopyBuffer(atr_handler,0,0,1,atr); double c = iClose(Symbol(),PERIOD_D1,0); if(PositionsTotal() == 0) { account_state = vector::Zeros(ACCOUNT_STATES); if(c > ma_h[0]) { if(!predict) { if(permission) Trade.Buy(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetAsk(),(TradeInfoHandler.GetBid()-(atr[0]*2)),(TradeInfoHandler.GetBid()+(atr[0]*2)),""); } account_state[0] = 1; } else if(c < ma_l[0]) { if(!predict) { if(permission) Trade.Sell(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetBid(),(TradeInfoHandler.GetAsk()+(atr[0]*2)),(TradeInfoHandler.GetAsk()-(atr[0]*2)),""); } account_state[1] = 1; } else { account_state[2] = 1; } } if(scenes < OBSERVATIONS) { take_snapshots(); } else { matrix temp; temp.Assign(snapshots); snapshots = matrix::Ones(FEATURES,scenes+1); //--- The first row is the intercept and must be full of ones for(int i=0;i<FEATURES;i++) snapshots.Row(temp.Row(i),i); take_snapshots(); fit_snapshots(); predict = true; permission = false; } scenes++; } }
На данном этапе система фиксирует полную картину соответствующих характеристик: вчерашние цены открытия, максимума, минимума и закрытия, состояние технических индикаторов, информацию о счете, а также вектор состояния счета, закодированный по методу «one-hot». Благодаря включению этих переменных система может научиться моделировать взаимосвязь нашей стратегии с рынком. Наши интересы уже вышли за рамки простого прогнозирования будущих ценовых уровней — теперь мы хотим прогнозировать будущий баланс нашего торгового счета. И, надеюсь, если такая взаимосвязь существует и её можно выявить, то наш контроллер с обратной связью научится корректировать поведение стратегии без вмешательства человека.
//+------------------------------------------------------------------+ //| Record the current state of our system | //+------------------------------------------------------------------+ void take_snapshots(void) { snapshots[1,scenes] = iOpen(Symbol(),PERIOD_D1,1); snapshots[2,scenes] = iHigh(Symbol(),PERIOD_D1,1); snapshots[3,scenes] = iLow(Symbol(),PERIOD_D1,1); snapshots[4,scenes] = iClose(Symbol(),PERIOD_D1,1); snapshots[5,scenes] = AccountInfoDouble(ACCOUNT_BALANCE); snapshots[6,scenes] = AccountInfoDouble(ACCOUNT_EQUITY); snapshots[7,scenes] = ma_h[0]; snapshots[8,scenes] = ma_l[0]; snapshots[9,scenes] = account_state[0]; snapshots[10,scenes] = account_state[1]; snapshots[11,scenes] = account_state[2]; } //+------------------------------------------------------------------+
Затем мы решаем линейную систему с помощью двух матриц: X — входные данные, y — целевые значения. Это система с несколькими выходами, которая одновременно прогнозирует несколько результатов. Матрица y смещена на один временной шаг, так что входные данные соответствуют предыдущим 89 моментальным снимкам, а выходные — последующим 89 моментальным снимкам. По мере поступления новых данных этот цикл естественным образом адаптируется к растущему набору данных, отслеживая общее количество прошедших сцен.
Сцена — это промежуток времени между двумя последовательными моментальными снимками. В ходе нашего обсуждения мы ежедневно собираем моментальные снимки системы, поэтому количество сцен также можно настроить в соответствии с предпочтениями читателя. Используя решение псевдообратной матрицы (функцию PInv() в MQL5), мы находим оптимальные коэффициенты, отображающие X в y. Эта функция подробно описана в предыдущих статьях нашей серии. Тем не менее, читатели, не знакомые с важностью роли, которую играет функция PInv, могут обратиться к материалу по ссылке здесь, поскольку функция PInv() является мощным инструментом, который мы используем.
После настройки система выводит снимки, входные данные и целевые значения, обученные коэффициенты, а также свои прогнозы. Затем мы анализируем эти прогнозы в режиме реального времени. Если модель прогнозирует рост остатка на счете, выдается разрешение на торговлю. Если модель прогнозирует восходящий импульс по максимуму скользящей средней в то время, когда рассматривается возможность открытия позиции на покупку, разрешение также дается. Аналогичным образом, если модель прогнозирует нисходящую динамику минимума скользящей средней и рассматривается возможность открытия позиции на продажу, разрешение выдается. Во всех остальных случаях система отказывает в разрешении.
Наконец, если разрешение получено, но открытых позиций нет, система выполняет желаемую сделку. По завершении этой процедуры программа выводит текущий баланс, прогнозируемый баланс, а также информацию о том, было ли дано разрешение. На этом завершаются изменения, необходимые для того, чтобы увидеть улучшение результатов нашей стратегии. Написанный нами код не дает контроллеру явных указаний о том, когда следует покупать или продавать; вместо этого мы определяем для контроллера набор действий, которые он может выполнять на основе выводов, сделанных им на основании накопленных данных.
//+------------------------------------------------------------------+ //| Fit our linear model to our collected snapshots | //+------------------------------------------------------------------+ void fit_snapshots(void) { matrix X,y; X.Reshape(FEATURES,scenes); y.Reshape(FEATURES-1,scenes); for(int i=0;i<scenes;i++) { X[0,i] = snapshots[0,i]; X[1,i] = snapshots[1,i]; X[2,i] = snapshots[2,i]; X[3,i] = snapshots[3,i]; X[4,i] = snapshots[4,i]; X[5,i] = snapshots[5,i]; X[6,i] = snapshots[6,i]; X[7,i] = snapshots[7,i]; X[8,i] = snapshots[8,i]; X[9,i] = snapshots[9,i]; X[10,i] = snapshots[10,i]; X[11,i] = snapshots[11,i]; y[0,i] = snapshots[1,i+1]; y[1,i] = snapshots[2,i+1]; y[2,i] = snapshots[3,i+1]; y[3,i] = snapshots[4,i+1]; y[4,i] = snapshots[5,i+1]; y[5,i] = snapshots[6,i+1]; y[6,i] = snapshots[7,i+1]; y[7,i] = snapshots[8,i+1]; y[8,i] = snapshots[9,i+1]; y[9,i] = snapshots[10,i+1]; y[10,i] = snapshots[11,i+1]; } //--- Find optimal solutions b_vector = y.MatMul(X.PInv()); Print("Day Number: ",scenes+1); Print("Snapshot"); Print(snapshots); Print("Input"); Print(X); Print("Target"); Print(y); Print("Coefficients"); Print(b_vector); Print("Prediciton"); Print(y.Col(scenes-1)); prediction = b_vector.MatMul(snapshots.Col(scenes-1)); if(prediction[4] > AccountInfoDouble(ACCOUNT_BALANCE)) permission = true; else if((account_state[0] == 1) && (prediction[6] > ma_h[0])) permission = true; else if((account_state[1] == 1) && (prediction[7] < ma_l[0])) permission = true; else permission = false; if(permission) { if(PositionsTotal() == 0) { if(account_state[0] == 1) Trade.Buy(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetAsk(),(TradeInfoHandler.GetBid()-(atr[0]*2)),(TradeInfoHandler.GetBid()+(atr[0]*2)),""); else if(account_state[1] == 1) Trade.Sell(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetBid(),(TradeInfoHandler.GetAsk()+(atr[0]*2)),(TradeInfoHandler.GetAsk()-(atr[0]*2)),""); } } Print("Current Balabnce: ",AccountInfoDouble(ACCOUNT_BALANCE)," Predicted Balance: ",prediction[4]," Permission: ",permission); } //+------------------------------------------------------------------+
Теперь, когда эта структура готова, мы можем провести бэктестинг нашего приложения в тех же исторических условиях, что и ранее. Чтобы избежать повторения, я не буду здесь повторять параметры испытания, но отмечу, что они идентичны параметрам, использованным на рисунке 6.

Рисунок 9: Подготовка к тестированию усовершенствованной версии нашей торговой стратегии
Подробный анализ показывает резкое отличие между базовой системой и системой с обратной связью. Чистая прибыль значительно выросла, в то время как валовая прибыль практически не изменилась — это свидетельствует о том, что контролер с достоинством справляется с убыточными сделками. Коэффициент Шарпа значительно вырос, а средний размер прибыльных и убыточных сделок теперь практически одинаков, что является заметным улучшением по сравнению с исходными показателями.

Рисунок 10: Подробный анализ результатов, полученных с помощью нашего торгового приложения, демонстрирует достигнутые нами улучшения
Кривая капитала демонстрирует снижение волатильности и более сильный, стабильный восходящий тренд. Именно такого результата мы и надеялись добиться, внедрив в нашу торговую стратегию систему управления с обратной связью.

Рисунок 11: Кривая капитала, построенная на основе нашей доработанной торговой стратегии, демонстрирует меньшую волатильность с течением времени и более стабильную доходность.
Я также приложил скриншот снимков, которые наша система делала во время бэктеста. К сожалению, на одном скриншоте не удалось поместить все 12 признаков. Однако читатель может заметить, что последняя строка матрицы моментального снимка содержит тот же баланс в размере 500 долларов, с которого начинала наша моделируемая торговая стратегия (см. рисунок 6). Затем мы отслеживаем, как рыночная конъюнктура и процесс принятия решений в рамках нашей торговой стратегии влияют на собственный капитал и остаток на нашем счете.

Рисунок 12: Показатели эффективности нашей системы, которые мы отслеживаем для мониторинга и корректировки нашей стратегии
Снимок журнала результатов подтверждает это поведение. Например, на 1645-й день флаг разрешения был установлен в значение «false», поскольку линейная система ожидала убытка и заблокировала сделку.

Рисунок 13: Наш контроллер линейной системы отказал нашей торговой стратегии в разрешении на торговлю, поскольку проанализировал неблагоприятные рыночные условия
Уже на следующий день, когда ожидалось достижение прибыльности, система дала разрешение и выполнила сделку. В целом, вот как работает обновленное приложение от начала до конца. Это свидетельствует о том, что у прогнозных моделей существует множество вариантов применения, выходящих за рамки обычного прогнозирования цен. Мы можем использовать эти обученные модели, чтобы проверить, существует ли какая-либо структура, на основе которой можно выявить закономерности, определяющие наши прибыльные и убыточные сделки. Чтобы помочь нам научиться не повторять прошлых ошибок.

Рисунок 14: Когда рыночная конъюнктура, по-видимому, соответствует нашей стратегии, наш контроллер с обратной связью дает нам разрешение на продолжение торговли
Заключение
В заключение можно сказать, что контроллеры с обратной связью позволяют создавать торговые системы, которые не только приносят прибыль, но и способны адаптироваться к изменяющимся условиям. Благодаря анализу собственной деятельности эти системы могут помочь нам избежать повторения прошлых ошибок и сохранить эффективность даже в условиях меняющейся рыночной конъюнктуры. Наши результаты демонстрируют заметное повышение рентабельности, эффективности и снижение рисков, что доказывает реальную практическую ценность теории управления в торговле. Смотря в будущее, эта работа побуждает нас выйти за рамки линейных моделей и изучить нелинейные контроллеры с обратной связью, которые могут отражать ещё более сложные закономерности в поведении рынка. Благодаря этим достижениям мы можем и дальше повышать стабильность и рентабельность наших систем в условиях неопределённости.
Однако прежде чем выходить за рамки нашей линейной системы, мы можем внести в неё ряд ценных улучшений. Оценка эффективности каждого из этих возможных усовершенствований на примере простых линейных систем даст нам надежный ориентир для любой другой нелинейной системы, которую мы захотим создать.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/19132
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Python + MetaTrader 5: быстрый исследовательский контур для данных, признаков и прототипов
Статистический арбитраж на коинтегрированных акциях (Часть 3): Настройка базы данных
Нейронные сети на практике: Практика ведет к совершенству
Разработка инструментария для анализа Price Action (Часть 28): Инструмент для торговли пробоя диапазона открытия
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Вы не прикрепили включаемые файлы volatilityDoctor/Time..Trade. Ваше представление не может быть протестировано без этих двух включаемых файлов.