
Создание самооптимизирующихся советников на MQL5 (Часть 4): Динамическое изменение размера позиции
Цифровые компьютеры появились еще в 1950-х годах, но финансовые рынки существуют уже столетия. Традиционно трейдеры добивались успеха без использования передовых вычислительных инструментов, что создает трудности при разработке современного программного обеспечения для торговли. Стоит ли нам использовать всю вычислительную мощность или следовать принципам успешной человеческой торговли? В этой статье предлагается соблюсти баланс между простотой и современными технологиями. Несмотря на современные передовые инструменты, многим трейдерам удалось добиться успеха в сложных децентрализованных системах без использования мощного программного обеспечения, такого как API MQL5.
Большую часть повседневных процессов принятия решений, которые мы используем, сложно формализовать для передачи компьютеру. Например, в трейдинге часто можно услышать: «Я был очень уверен в своем решении, поэтому увеличил размер лота». Как мы можем поручить нашему торговому приложению сделать то же самое и увеличить размер позиции, если оно "уверено" в своем решении по сделке?
Надеюсь, читателю уже стало очевидно, что этой цели невозможно достичь, не внося в систему сложность, позволяющую измерить, насколько "уверенно" себя "чувствует" компьютер. Один из подходов заключается в построении вероятностных моделей для количественной оценки "уверенности" в сделке. В этой статье мы построим простую модель логистической регрессии для измерения уверенности в наших сделках, что позволит нашему приложению самостоятельно масштабировать наши позиции.
Мы сосредоточимся на стратегии полос Боллинджера, первоначально предложенной Джоном Боллинджером. Наша цель — усовершенствовать эту стратегию, устранив ее недостатки, не теряя при этом сути идеи.
Наше торговое приложение будет:
- Размещать дополнительную сделку с большим размером лота, если модель уверена в сделке.
- Если модель менее надежна, размещать одну сделку с меньшим размером лота.
Первоначальная торговая стратегия, предложенная Джоном Боллинджером, показала 493 сделки в ходе тестирования на истории. Из всех заключенных сделок 62% оказались прибыльными. Хотя это и значительная доля, ее оказалось недостаточно для создания прибыльной торговой стратегии. За период тестирования мы потеряли USD 813 и получили коэффициент Шарпа -0,33. Наша усовершенствованная версия алгоритма совершила в общей сложности 495 сделок, из которых 63% оказались прибыльными. Наша общая прибыль по итогам тестирования на истории резко возросла до USD 2 427 за тот же период времени, а наш коэффициент Шарпа установился на уровне 0,74.
Я не собираюсь ставить под сомнение эффективность современных вычислительных инструментов, таких как глубокие нейронные сети или алгоритмы обучения с подкреплением. Напротив, я глубоко воодушевлен возможностями, которые открывают эти технологии. Однако важно осознавать, что сложность сама по себе не обязательно приводит к лучшим результатам.
Я понимаю трудности, с которыми сталкиваются новички в сообществе алгоритмической торговли. Я сам был в такой ситуации - я был полон амбиций, но не знал, с чего начать. Легко растеряться от огромного количества доступных инструментов, методов и опций.
Эта статья призвана предоставить дорожную карту тем, кто только начинает. Начав с простого, вы обретете уверенность в самостоятельном решении более сложных задач, более глубоко понимая их применение. Результаты, опубликованные в этой статье, были получены нами с использованием оригинальных правил, предложенных Джоном Боллинджером, которые мы усложнили для имитации принятия решений человеком.
Обзор торговой стратегии
Рис. 1. Изображение нашей стратегии полос Боллинджера в действии
Наша торговая стратегия основана на следовании торговым сигналам, предложенным Джоном Боллинджером. Первоначальные правила стратегии выполняются, если мы продаем всякий раз, когда уровни цен пробивают верхнюю полосу Боллинджера, и покупаем, если уровни цен опускаются ниже нижней полосы.
В общем случае мы можем расширить эти правила, используя их и как условия выхода. Другими словами, всякий раз, когда уровни цен оказываются выше самой верхней полосы, мы закрываем все открытые сделки на покупку в дополнение к открытию сделок на продажу. Этих наборов правил достаточно для создания самоуправляемой системы, которая сама знает, когда открывать и закрывать свои позиции.
Мы протестируем нашу торговую стратегию на паре с 1 января 2022 по 30 декабря 2024 на таймфрейме M15.
Начало работы с MQL5
Чтобы запустить процесс в MQL5, начнем с определения системных констант, таких как предполагаемая торговая пара, размер используемого лота и другие константы, которые пользователь не должен изменять.
//+------------------------------------------------------------------+ //| GBPUSD BB Breakout Benchmark.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 constants | //+------------------------------------------------------------------+ #define BB_SHIFT 0 // Our bollinger band should not be shifted #define SYMBOL "GBPUSD" // The intended pair for our trading system #define BB_PRICE PRICE_CLOSE // The price our bollinger band should work on #define LOT 0.1 // Our intended lot size
Теперь загрузим торговую библиотеку.
//+------------------------------------------------------------------+ //| Dependencies | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> CTrade Trade;
Некоторые аспекты торговой стратегии могут контролироваться конечным пользователем, например, таймфрейм, который следует использовать для расчетов технических индикаторов, и период индикатора полос Боллинджера.
//+------------------------------------------------------------------+ //| User inputs | //+------------------------------------------------------------------+ input group "Technical Indicators" input ENUM_TIMEFRAMES TF = PERIOD_M15; // Intended time frame input int BB_PERIOD = 30; // The period for our bollinger bands input double BB_SD = 2.0; // The standard deviation for our bollinger bands
Нам также потребуется определить глобальные переменные, используемые в нашей программе.
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Technical indicators | //+------------------------------------------------------------------+ int bb_handler; double bb_u[],bb_m[],bb_l[]; //+------------------------------------------------------------------+ //| System variables | //+------------------------------------------------------------------+ int state; double o,h,l,c,bid,ask;
При первой загрузке нашего торгового приложения мы вызовем нашу функцию инициализации.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Setup our system if(!setup()) return(INIT_FAILED); //--- return(INIT_SUCCEEDED); }
Если наше приложение больше не будет использоваться, мы опубликуем технические индикаторы, которые мы не используем.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Release resources we no longer need release(); }
Получив обновленную информацию о ценах, нам необходимо сохранить ее и обработать для принятия торгового решения.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Update our system variables update(); } //+------------------------------------------------------------------+
Эта функция отвечает за настройку нашего технического индикатора.
//+------------------------------------------------------------------+ //| Custom functions | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Setup our technical indicators and other variables | //+------------------------------------------------------------------+ bool setup(void) { //--- Setup our system bb_handler = iBands(SYMBOL,TF,BB_PERIOD,BB_SHIFT,BB_SD,BB_PRICE); state = 0; //--- Validate our system has been setup correctly if((bb_handler != INVALID_HANDLE) && (Symbol() == SYMBOL)) return(true); //--- Something went wrong! return(false); }
Если мы больше не используем торговое приложение, нам следует освободить память, которая была связана с выбранным нами техническим индикатором.
//+------------------------------------------------------------------+ //| Release the resources we no longer need | //+------------------------------------------------------------------+ void release(void) { //--- Free up system resources for our end user IndicatorRelease(bb_handler); }
Получив обновленную информацию о ценах с рынка, мы обновляем наши глобальные переменные, а затем проверяем наличие допустимых торговых настроек, если у нас нет открытых позиций.
//+------------------------------------------------------------------+ //| Update our system variables | //+------------------------------------------------------------------+ void update(void) { static datetime timestamp; datetime current_time = iTime(Symbol(),PERIOD_CURRENT,0); if(timestamp != current_time) { timestamp = current_time; //--- Update our system CopyBuffer(bb_handler,0,1,1,bb_m); CopyBuffer(bb_handler,1,1,1,bb_u); CopyBuffer(bb_handler,2,1,1,bb_l); Comment("U: ",bb_u[0],"\nM: ",bb_m[0],"\nL: ",bb_l[0]); //--- Market prices o = iOpen(SYMBOL,PERIOD_CURRENT,1); c = iClose(SYMBOL,PERIOD_CURRENT,1); h = iHigh(SYMBOL,PERIOD_CURRENT,1); l = iLow(SYMBOL,PERIOD_CURRENT,1); bid = SymbolInfoDouble(SYMBOL,SYMBOL_BID); ask = SymbolInfoDouble(SYMBOL,SYMBOL_ASK); //--- Should we reset our system state? if(PositionsTotal() == 0) { state = 0; find_setup(); } if(PositionsTotal() == 1) { manage_setup(); } } }
Наши правила поиска торговых точек — это оригинальные правила, предложенные Джоном Боллинджером.
//+------------------------------------------------------------------+ //| Find an oppurtunity to trade | //+------------------------------------------------------------------+ void find_setup(void) { //--- Check if we have breached the bollinger bands if(c > bb_u[0]) { Trade.Sell(LOT,SYMBOL,bid); state = -1; return; } if(c < bb_l[0]) { Trade.Buy(LOT,SYMBOL,ask); state = 1; } }
Как мы уже говорили ранее, правила, предоставленные Джоном Боллинджером, также могут быть использованы для создания правил выхода, которые точно определяют, когда следует закрывать позицию.
//+------------------------------------------------------------------+ //| Manage our open trades | //+------------------------------------------------------------------+ void manage_setup(void) { if(((c < bb_l[0]) && (state == -1))||((c > bb_u[0]) && (state == 1))) Trade.PositionClose(SYMBOL); } //+------------------------------------------------------------------+
Начнем с выбора предполагаемого временного интервала для M15. Эти короткие таймфреймы отлично подходят для скальпинговых стратегий, таких как наша, которые направлены на использование закономерностей, ежедневно формируемых на финансовых рынках. Нашим выбранным символом является пара GBPUSD, и мы проведем наше тестирование с 1 января 2022 года по 30 декабря 2024 года.
Рис. 2. Выбор таймфрейма для тестирования на истории
Теперь нам предстоит тонкая настройка параметров нашего теста. Режим "Произвольной задержки" позволяет увидеть, насколько надежна наша торговая система в условиях нестабильности рынка. Кроме того, я выбрал вариант "Каждый тик на основе реальных тиков", поскольку он обеспечивает наиболее реалистичную симуляцию прошлых рыночных данных. В этом режиме моделирования наш терминал MetaTrader 5 будет получать все тики в реальном времени, отправленные брокером в этот день. В зависимости от скорости вашего интернет-соединения этот процесс может занять много времени. Однако в конечном итоге вы, скорее всего, получите результаты, близкие к истине.
Рис. 3. Выбор условий тестирования на истории
Наконец, мы определим параметры, которые будут управлять поведением нашего приложения. Обратите внимание, что во втором тесте настройки, выбранные нами на рис. 4, будут сохраняться постоянными с помощью наших системных переменных. То есть мы не будем давать второй версии нашего приложения несправедливого преимущества перед текущей версией, которую мы собираемся протестировать.
Рис. 4. Входные параметры нашего советника во время тестирования на истории
Кривая прибыли, полученная с помощью нашего текущего алгоритма, по своей природе нестабильна. Она непредсказуемо переживает периоды быстрого роста и чрезмерных потерь. Текущая версия нашей торговой стратегии большую часть времени тратила на восстановление после периодов просадки, а не на накопление прибыли и периодические убыточные сделки. Это далеко от идеала. К концу теста наш алгоритм потерял капитал. Очевидно, что предстоит еще много работы, прежде чем мы вообще сможем подумаьт об использовании этого алгоритма.
Рис. 5. Кривая эквити, полученная с помощью нашей текущей версии исходной торговой стратегии
При внимательном изучении результатов нашего тестирования на истории мы видим, что наша система имела значительную долю выигрышных сделок: 63% всех заключенных сделок были прибыльными. Проблема в том, что наша прибыль была почти вдвое меньше наших убытков. Поскольку мы не желаем менять первоначальные правила торговли, наша новая цель — направить рост нашей средней прибыли ближе к максимуму, гарантируя при этом, что наши убыточные сделки будут расти меньшими темпами. Этот тонкий баланс даст нам желаемые результаты.
Рис. 6. Подробный анализ результатов, полученных с помощью исходной версии торговой стратегии
Улучшаем наши первоначальные результаты
Как мы видим, первые результаты не слишком обнадеживают. Однако мы знаем, что автор полос Боллинджера, предложивший эти правила, сам был успешным трейдером по любым меркам. Так где же разрыв между правилами, созданными Джоном Боллинджером, и результатами, которые мы получили, алгоритмически следуя его правилам?
Рис. 7. Изобретатель полос Боллинджера Джон Боллинджер
Частично он может заключаться в применении этих правил человеком. Вполне вероятно, что со временем у Боллинджера развилась интуиция относительно рыночных условий, в которых его стратегия процветает, и условий, в которых она, как правило, терпит неудачу. Наше текущее приложение всегда рискует одинаковой суммой в каждой сделке и рассматривает все торговые возможности одинаково. Однако люди могут по своему усмотрению рисковать больше или меньше в зависимости от своих усвоенных ожиданий и уверенности в будущем.
Трейдеры стремятся идти на риск тогда, когда считают, что это с наибольшей вероятностью окупится, они не следуют строго заданному набору правил. Мы хотим придать машине дополнительный уровень гибкости в дополнение к изначальной стратегии. Осуществление этой цели, как мы надеемся, может объяснить разрыв между ожидаемыми нами результатами и теми, которые мы получили на данный момент. Поэтому мы введем сложность, чтобы попытаться приблизить наш алгоритм к тому, чем ежедневно занимаются профессионалы, а не просто пытаться напрямую прогнозировать будущие уровни цен.
Мы можем построить модель логистической регрессии, чтобы придать нашему приложению ощущение "уверенности". Параметры нашей модели будут оптимизированы с использованием исторических рыночных данных, которые мы получим из терминала MetaTrader 5. Наша собственная реализация MQL5 означает, что наш советник может работать на любом таймфрейме при условии наличия достаточного количества данных на этом таймфрейме.
Модель логистической регрессии, возможно, является самой простой моделью, которую мы можем построить сегодня. Существует множество форм логистических моделей, однако форма, которую мы рассмотрим сегодня, может быть использована для моделирования только двух классов. Читателям, желающим классифицировать более двух классов, я советую обратиться к дополнительной литературе по логистическим моделям.
Чтобы реализовать желаемые изменения и приблизить процесс принятия решений в нашем приложении к процессу принятия решений человеком, мы внесем несколько важных изменений в нашу текущую версию торговой системы:
Предлагаемое изменение | Предполагаемая цель |
---|---|
Дополнительные системные константы | Нам потребуется создать новые системные константы для размещения вероятностной модели, которую мы хотим построить, а также всех других новых частей системы, которые нам понадобятся. |
Дополнительный технический анализ | Использование двух стратегий одновременно может сделать нашу систему более прибыльной. Мы также будем запрашивать подтверждение от стохастического осциллятора перед открытием сделок, чтобы увеличить вероятность заключения прибыльных сделок. |
Новые пользовательские входные параметры | Чтобы позволить пользователю управлять новыми частями нашей системы, нам необходимо создать новые пользовательские входные параметры, которые будут управлять добавленным функционалом. |
Модификация пользовательских функций | Настраиваемые функции, которые мы создали до сих пор, необходимо пересмотреть и расширить, чтобы они учитывали все новые переменные и задачи, которые должно выполнять наше приложение. |
Начало работы
Чтобы приступить к созданию обновленной версии торгового приложения, нам сначала потребуется создать новые системные константы, чтобы обеспечить единообразие наших тестов во всех предлагаемых нами версиях алгоритма.
//+------------------------------------------------------------------+ //| GBPUSD BB Breakout Benchmark.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 constants | //+------------------------------------------------------------------+ #define BB_SHIFT 0 // Our bollinger band should not be shifted #define SYMBOL "GBPUSD" // The intended pair for our trading system #define BB_PRICE PRICE_CLOSE // The price our bollinger band should work on #define BB_PERIOD 90 // The period for our bollinger bands #define BB_SD 2.0 // The standard deviation for our bollinger bands #define LOT 0.1 // Our intended lot size #define TF PERIOD_M15 // Our intended time frame #define ATR_MULTIPLE 20 // ATR Multiple #define ATR_PERIOD 14 // ATR Period #define K_PERIOD 12 // Stochastic K period #define D_PERIOD 20 // Stochastic D period #define STO_SMOOTHING 12 // Stochastic smoothing #define LOGISTIC_MODEL_PARAMS 5 // Total inputs to our logistic model
Кроме того, мы хотим, чтобы пользователь мог контролировать функциональность нашей модели логистической регрессии. Входной параметр fetch определяет, какой объем данных следует использовать для построения нашей модели. Обратите внимание, что, как правило, чем более крупный таймфрейм хочет использовать пользователь, тем меньше данных у нас есть. С другой стороны, look_ahead определяет, насколько далеко в будущее должна пытаться строить прогноз наша модель.
//+------------------------------------------------------------------+ //| User inputs | //+------------------------------------------------------------------+ input int fetch = 5; // How many historical bars of data should we fetch? input int look_ahead = 10; // How far ahead into the future should we forecast?
Более того, нам понадобятся новые глобальные переменные в нашем приложении. Эти переменные будут служить обработчиками наших новых технических индикаторов, а также движущимися частями нашей модели логистической регрессии.
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Technical indicators | //+------------------------------------------------------------------+ int bb_handler,atr_handler,stoch_handler; double bb_u[],bb_m[],bb_l[],atr[],stoch[]; double logistic_prediction; double learning_rate = 5E-3; vector open_price = vector::Zeros(fetch); vector open_price_old = vector::Zeros(fetch); vector close_price = vector::Zeros(fetch); vector close_price_old = vector::Zeros(fetch); vector high_price = vector::Zeros(fetch); vector high_price_old = vector::Zeros(fetch); vector low_price = vector::Zeros(fetch); vector low_price_old = vector::Zeros(fetch); vector target = vector::Zeros(fetch); vector coef = vector::Zeros(LOGISTIC_MODEL_PARAMS); double max_forecast = 0; double min_forecast = 0; double baseline_forecast = 0;
Большинство других частей нашей торговой системы останутся прежними, за исключением нескольких функций, которые необходимо расширить, и новых функций, которые необходимо определить. Первой в списке для редактирования идет наша функция инициализации. Прежде чем мы будем готовы приступить к торговле, нам необходимо выполнить дополнительные шаги. Нам потребуется настроить ATR и стохастическую модель, а также определить функцию setup_logistic_model().
//+------------------------------------------------------------------+ //| Setup our technical indicators and other variables | //+------------------------------------------------------------------+ bool setup(void) { //--- Setup our system bb_handler = iBands(SYMBOL,TF,BB_PERIOD,BB_SHIFT,BB_SD,BB_PRICE); atr_handler = iATR(SYMBOL,TF,ATR_PERIOD); stoch_handler = iStochastic(SYMBOL,TF,K_PERIOD,D_PERIOD,STO_SMOOTHING,MODE_EMA,STO_LOWHIGH); state = 0; higher_state = 0; setup_logistic_model(); //--- Validate our system has been setup correctly if((bb_handler != INVALID_HANDLE) && (Symbol() == SYMBOL)) return(true); //--- Something went wrong! return(false); }
Наша модель логистической регрессии принимает набор входных данных и прогнозирует вероятность от нуля до единицы того, что целевая переменная будет принадлежать классу по умолчанию, учитывая текущее значение x. Для расчета этой вероятности модель использует сигмоидальную функцию, изображенную на рис. 8 ниже.
Представьте, что нам нужно решить следующую задачу: «Если учесть вес и рост человека, какова вероятность того, что он мужчина?». В этом примере классом по умолчанию является мужской пол. Вероятности выше 0,5 подразумевают, что человек предположительно является мужчиной, а вероятности ниже 0,5 подразумевают, что предполагаемый пол — женский. Это простейшая возможная версия логистической модели. Существуют версии логистической модели, которые могут классифицировать более 2 целей, но здесь мы их рассматривать не будем.
Сигмоидальная функция, показанная на рис. 8 выше, преобразует любое значение x и дает нам выходное значение между 0 и 1, как показано на рис. 9 ниже.
Рис. 9. Визуализация преобразования сигмоидальной функции
Мы можем тщательно откалибровать нашу сигмоидальную функцию так, чтобы она выдавала оценки, близкие к 1, для всех наблюдений в наших обучающих данных, которые принадлежали к классу 1, и аналогичным образом оценки, близкие к 0, для всех значений в наших обучающих данных, которые принадлежали к классу 0. Этот алгоритм известен как оценка максимального правдоподобия. Мы можем аппроксимировать эти результаты, используя гораздо более простой алгоритм, известный как градиентный спуск.
В приведенном ниже коде мы начинаем с подготовки входных данных. Мы находим изменение цены открытия, максимума, минимума и закрытия — это будут наши входные данные для модели. После этого мы фиксируем соответствующее будущее изменение цены. Если уровень цен упал, мы зафиксируем это как класс 0. Класс 0 — наш класс по умолчанию. Прогнозы выше нашего порогового значения подразумевают, что наша модель ожидает падения будущих уровней цен. Аналогично, прогнозы ниже точки отсечения означают, что класс по умолчанию не соответствует действительности, или, в нашем случае, наша модель ожидает роста уровня цен. Обычно предпочтительным является пороговое значение 0,5.
После маркировки наших данных мы инициализируем все коэффициенты нашей модели до 0, а затем приступаем к созданию первого прогноза с этими плохими коэффициентами. При каждом прогнозе мы корректируем коэффициенты, используя разницу между нашим прогнозом и фактической меткой. Этот процесс повторяется для каждого извлеченного нами бара.
Наконец, я уже говорил ранее, что классически предпочтительным является пороговое значение 0,5. Однако финансовые рынки не славятся своим прогнозируемым поведением. Классический подход не давал вероятностей, полезных для нас как трейдеров, поэтому я расширил классический алгоритм и откалибровал его еще больше.
Я включил дополнительный шаг для расчета оптимальной точки отсечения, сначала зафиксировав максимальные и минимальные шансы, спрогнозированные нашей моделью. Затем мы разделили пополам истинный диапазон прогнозов, предоставленных нашей моделью, чтобы найти точку отсечения. Учитывая, что финансовый рынок может быть шумным, нашим моделям может быть сложно эффективно обучаться, и нам, возможно, придется проявить творческий подход и найти новые способы интерпретации наших моделей. Эта динамическая точка отсечения поможет нашей модели принимать решения независимо от наших внутренних предубеждений.
Рис. 10. Визуализация того, как мы динамически устанавливаем точку отсечения
Таким образом, в нашем случае вероятности выше нашей динамической точки отсечения будут интерпретироваться как класс по умолчанию, то есть наша модель считает, что нам следует "продавать". И обратное справедливо для прогнозов, которые лежат ниже нашей динамической точки отсечения.
//+------------------------------------------------------------------+ //| Setup our logistic regression model | //+------------------------------------------------------------------+ void setup_logistic_model(void) { open_price.CopyRates(SYMBOL,TF,COPY_RATES_OPEN,(fetch + look_ahead),fetch); open_price_old.CopyRates(SYMBOL,TF,COPY_RATES_OPEN,(fetch + (look_ahead * 2)),fetch); high_price.CopyRates(SYMBOL,TF,COPY_RATES_HIGH,(fetch + look_ahead),fetch); high_price_old.CopyRates(SYMBOL,TF,COPY_RATES_HIGH,(fetch + (look_ahead * 2)),fetch); low_price.CopyRates(SYMBOL,TF,COPY_RATES_LOW,(fetch + look_ahead),fetch); low_price_old.CopyRates(SYMBOL,TF,COPY_RATES_LOW,(fetch + (look_ahead * 2)),fetch); close_price.CopyRates(SYMBOL,TF,COPY_RATES_CLOSE,(fetch + look_ahead),fetch); close_price_old.CopyRates(SYMBOL,TF,COPY_RATES_CLOSE,(fetch + (look_ahead * 2)),fetch); open_price = open_price - open_price_old; high_price = high_price - high_price_old; low_price = low_price - low_price_old; close_price = close_price - close_price_old; CopyBuffer(atr_handler,0,0,fetch,atr); for(int i = (fetch + look_ahead); i > look_ahead; i--) { if(iClose(SYMBOL,TF,i) > iClose(SYMBOL,TF,i - look_ahead)) target[i-look_ahead-1] = 0; if(iClose(SYMBOL,TF,i) < iClose(SYMBOL,TF,i - look_ahead)) target[i-look_ahead-1] = 1; } //Fitting our coefficients coef[0] = 0; coef[1] = 0; coef[2] = 0; coef[3] = 0; coef[4] = 0; for(int i =0; i < fetch; i++) { double prediction = 1 / (1 + MathExp(-(coef[0] + (coef[1] * open_price[i]) + (coef[2] * high_price[i]) + (coef[3] * low_price[i]) + (coef[4] * close_price[i])))); coef[0] = coef[0] + (learning_rate * (target[i] - prediction)) * prediction * (1 - prediction) * 1.0; coef[1] = coef[1] + (learning_rate * (target[i] - prediction)) * prediction * (1 - prediction) * open_price[i]; coef[2] = coef[2] + (learning_rate * (target[i] - prediction)) * prediction * (1 - prediction) * high_price[i]; coef[3] = coef[3] + (learning_rate * (target[i] - prediction)) * prediction * (1 - prediction) * low_price[i]; coef[4] = coef[4] + (learning_rate * (target[i] - prediction)) * prediction * (1 - prediction) * close_price[i]; } for(int i =0; i < fetch; i++) { double prediction = 1 / (1 + MathExp(-(coef[0] + (coef[1] * open_price[i]) + (coef[2] * high_price[i]) + (coef[3] * low_price[i]) + (coef[4] * close_price[i])))); if(i == 0) { max_forecast = prediction; min_forecast = prediction; } max_forecast = (prediction > max_forecast) ? (prediction) : max_forecast; min_forecast = (prediction < min_forecast) ? (prediction) : min_forecast; } baseline_forecast = ((max_forecast + min_forecast) / 2); Print(coef); Print("Baseline: ",baseline_forecast); }
Если мы не используем нашего советника, нам необходимо опубликовать несколько дополнительных технических индикаторов.
//+------------------------------------------------------------------+ //| Release the resources we no longer need | //+------------------------------------------------------------------+ void release(void) { //--- Free up system resources for our end user IndicatorRelease(bb_handler); IndicatorRelease(atr_handler); IndicatorRelease(stoch_handler); }
Наши условия открытия позиций остаются в целом теми же, за исключением того, что если прогнозы нашей модели совпадают с торговыми правилами, предложенными Джоном Боллинджером, мы удвоим ставку на эту возможность и дадим указание нашему приложению брать на себя больший риск только при этих условиях.
//+------------------------------------------------------------------+ //| Find an oppurtunity to trade | //+------------------------------------------------------------------+ void find_setup(void) { double open_input = iOpen(SYMBOL,TF,0) - iOpen(SYMBOL,TF,look_ahead); double close_input = iClose(SYMBOL,TF,0) - iClose(SYMBOL,TF,look_ahead); double high_input = iHigh(SYMBOL,TF,0) - iHigh(SYMBOL,TF,look_ahead); double low_input = iLow(SYMBOL,TF,0) - iLow(SYMBOL,TF,look_ahead); double prediction = 1 / (1 + MathExp(-(coef[0] + (coef[1] * open_input) + (coef[2] * high_input) + (coef[3] * low_input) + (coef[4] * close_input)))); Print("Odds: ",prediction - baseline_forecast); //--- Check if we have breached the bollinger bands if((c > bb_u[0]) && (stoch[0] < 50)) { Trade.Sell(LOT,SYMBOL,bid); state = -1; if(((prediction - baseline_forecast) > 0)) { Trade.Sell((LOT * 2),SYMBOL,bid); Trade.Sell((LOT * 2),SYMBOL,bid); state = -1; } return; } if((c < bb_l[0]) && (stoch[0] > 50)) { Trade.Buy(LOT,SYMBOL,ask); state = 1; if(((prediction - baseline_forecast) < 0)) { Trade.Buy((LOT * 2),SYMBOL,ask); Trade.Buy((LOT * 2),SYMBOL,ask); state = 1; } return; } }
Кроме того, нам нужен стоп-лосс, который будет перемещаться, если наша сделка прибыльна, а в противном случае он должен оставаться на месте. Это гарантирует снижение риска в случае выигрыша, что является разумным решением, которое применяют трейдеры-люди.
//+------------------------------------------------------------------+ //| Manage our open positions | //+------------------------------------------------------------------+ void manage_setup(void) { if(((c < bb_l[0]) && (state == -1))||((c > bb_u[0]) && (state == 1))) Trade.PositionClose(SYMBOL); //--- Update the stop loss for(int i = PositionsTotal() -1; i >= 0; i--) { string symbol = PositionGetSymbol(i); if(_Symbol == symbol) { double position_size = PositionGetDouble(POSITION_VOLUME); double risk_factor = 1; if(position_size == (LOT * 2)) risk_factor = 2; double atr_stop = atr[0] * ATR_MULTIPLE * risk_factor; ulong ticket = PositionGetInteger(POSITION_TICKET); double position_price = PositionGetDouble(POSITION_PRICE_OPEN); long type = PositionGetInteger(POSITION_TYPE); double current_take_profit = PositionGetDouble(POSITION_TP); double current_stop_loss = PositionGetDouble(POSITION_SL); if(type == POSITION_TYPE_BUY) { double atr_stop_loss = (bid - (atr_stop)); double atr_take_profit = (bid + (atr_stop)); if((current_stop_loss < atr_stop_loss) || (current_stop_loss == 0)) { Trade.PositionModify(ticket,atr_stop_loss,current_take_profit); } } else+ if(type == POSITION_TYPE_SELL) { double atr_stop_loss = (ask + (atr_stop)); double atr_take_profit = (ask - (atr_stop)); if((current_stop_loss > atr_stop_loss) || (current_stop_loss == 0)) { Trade.PositionModify(ticket,atr_stop_loss,current_take_profit); } } } } } //+------------------------------------------------------------------+
Настройки, управляющие продолжительностью и временными рамками тестирования на истории, останутся прежними, единственная переменная, которую нам нужно здесь изменить, — это выбранный советник. Мы выбрали пересмотренную версию приложения, которую мы только что совместно рефакторили в предыдущем разделе статьи. При выборе новой версии приложения не забудьте сохранить прежние настройки.
Рис. 11. Выбор таймфреймов и периода для нашего второго тестирования на истории с целью оценки эффективности выбранных нами настроек
Как всегда, обязательно выбирайте настройки кредитного плеча, соответствующие вашему соглашению с брокером. Неправильное указание настроек кредитного плеча может дать вам нереалистичные ожидания относительно прибыльности ваших торговых приложений. Хуже того, вам может быть сложно воспроизвести результаты, полученные в ходе тестирования, особенно если настройки кредитного плеча на вашем реальном счете не соответствуют настройкам кредитного плеча, которые вы используете в тестировании. Это часто упускаемый из виду источник ошибок при проведении тестирования на истории, поэтому не торопитесь.
Рис. 12. Тестирование на истории чувствительно к настройкам, выбранным при его запуске Убедитесь, что вы сделаете все правильно с первого раза
Теперь мы определим, какой объем данных должно извлечь наше торговое приложение для оценки параметров нашей модели логистической регрессии, а также горизонт прогнозирования для нашей модели. Не пытайтесь получить больше данных, чем вам предоставил ваш брокер. В противном случае приложение не будет работать так, как задумано! Кроме того, установите горизонт прогнозирования, соответствующий вашему отношению к риску.
Например, вы можете захотеть обучить свое приложение заглядывать на 2000 шагов вперед. Однако следует помнить, что 2000 шагов в будущее на М15 соответствуют примерно 20 дням. Если вы сами не можете реалистично прогнозировать столь далекое будущее при размещении сделок, то не заставляйте приложение делать это. Напомним, что наша цель — создать приложение, которое имитирует повседневную деятельность трейдера-человека.
Рис. 13. Параметры, управляющие поведением нашего торгового приложения и нашей модели логистической регрессии
Теперь мы подошли к самой информативной части нашего теста. Наша новая система принесла среднюю прибыль в размере USD 79. Первоначально мы ожидали среднюю прибыль в размере USD 45. Таким образом, разница между нашей текущей ожидаемой прибылью (USD 79) и нашей предыдущей ожидаемой прибылью (USD 45) составляет USD 34. Эта разница в USD 34 соответствует росту примерно на 75% от первоначально ожидаемой прибыли.
Одновременно наш новый ожидаемый убыток составляет USD 122, тогда как наш первоначальный ожидаемый убыток составлял USD 81. Разница составляет USD 41 и соответствует увеличению размера наших средних убытков примерно на 50%. Итак, мы успешно достигли своей цели!
Наши новые настройки гарантируют, что наша прибыль будет расти большими темпами, чем наши убытки. По этой же причине нам удалось успешно скорректировать наш коэффициент Шарпа и матожидание. Наша первоначальная версия торговой стратегии принесла убыток в размере USD 791, в то время как наша новая система принесла прибыль в размере USD 2274 без изменения правил алгоритма или периода тестирования на истории.
Рис. 14. Мы хотели бы, чтобы наши потери имели темп роста 0, но реальный мир не идеален
Если теперь рассмотреть кривую эквити, которую выдает наш алгоритм, то можно ясно увидеть, что он стал более стабильным, чем был изначально. Все торговые стратегии проходят через периоды просадки. Однако нас интересует способность стратегии в конечном итоге оправиться от убытков и сохранить прибыль. Слишком консервативная стратегия вряд ли принесет прибыль, и наоборот, стратегия, которая сильно склонна к риску, может быстро потерять всю полученную прибыль. Нам удалось найти баланс.
Рис. 15. Кривая эквити, полученная с помощью нашей новой версии торгового алгоритма, более привлекательна, чем наши первоначальные результаты
Заключение
Управление размер риска, принимаемого нашими торговыми приложениями, имеет решающее значение для обеспечения прибыльной и устойчивой торговли. В статье было показано, как вы можете разрабатывать приложения, способные самостоятельно увеличивать размер лота, если наша сделка имеет высокие шансы на прибыльность. В противном случае, если мы ожидаем, что сделка не сработает, приложение будет рисковать минимально возможной суммой. Такое динамическое определение размера позиции имеет решающее значение для прибыльной торговли, поскольку оно гарантирует, что мы извлекаем максимальную выгоду из каждой возможности и управляем нашими уровнями риска ответственно. Совместно создавая вероятностную логистическую модель, мы узнали один из возможных способов обеспечения того, чтобы наше приложение выбирало оптимальный размер позиции на основе того, что оно узнало о текущем рынке. Прикрепленный файл | Описание |
---|---|
GBPUSD BB Breakout Benchmark | Это первоначальная версия нашего торгового приложения, которая не принесла прибыли при первом тестировании. |
GBPUSD BB Breakout Benchmark V2 | Усовершенствованный алгоритм, основанный на тех же правилах торговли, но разработанный так, чтобы разумно увеличивать размеры наших позиций, если он обнаруживает, что у нас есть хорошие шансы на прибыль. |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/16925
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.




- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования