preview
Балансировка риска при одновременной торговле нескольких торговых инструментов

Балансировка риска при одновременной торговле нескольких торговых инструментов

MetaTrader 5Примеры | 9 февраля 2024, 14:17
618 6
Aleksandr Seredin
Aleksandr Seredin

В данной статье будет затронута тема балансировки риска при торговле нескольких торговых инструментов внутри дня одновременно. Цель данной статьи — дать возможность пользователю с нуля написать код для балансировки инструментов, а опытных пользователей познакомить с другими, возможно ранее неиспользованными, реализациями старых идей. Для этого мы рассмотрим определение понятия риска, выберем критерии для оптимизации, затронем технические моменты реализации нашего решения, проанализируем набор стандартных возможностей терминала для подобной реализации, а также коснёмся других возможных способов интеграции данного алгоритма в вашу программную инфраструктуру.


Критерии для балансировки торговых инструментов по риску

При одновременной торговле нескольких финансовых инструментов в качестве критериев балансировки рисков мы будем учитывать два основных фактора.

  • Стоимость тика на инструменте
  • Среднедневная волатильность инструмента

Стоимость тика - это значение в валюте минимального изменения цены на инструменте при стандартном лоте данного инструмента. Мы берём данный критерий в расчёт потому, что на разных инструментах величина стоимости тика может существенно варьироваться. Например от значения 1.27042 у валютной пары EURGBP до значения 0.61374 у пары AUDNZD.

Среднедневная волатильность - это характерное изменение цены инструмента за один день. Данная величина на разных инструментах менее постоянна, чем предыдущий выбранный критерий и может меняться во времени в зависимости от стадии рынка. Например валютная пара EURGBP обычно в среднем ходит около 336 пунктов, а такая валютная пара CHFJPY может за тот же день сходить 1271 пункт, что почти в четыре раза больше. Представленные здесь данные характеризуют "обычные", "наиболее вероятные" величины изменчивости цены без учёта паранормально высокой волатильности инструментов в определённые моменты на рынке. То есть, когда цена очень сильно начинает безоткатно двигаться в одну сторону. Как, например, на следующем рисунке по инструменту USDJPY.

Рисунок 1. Повышенная волатильность инструмента на дневном графике

Рисунок 1. Повышенная волатильность инструмента на дневном графике

Такое поведение инструментов может вызвать очень серьёзные риски на депозит, которые достаточно подробно описаны в статье "Как снизить риски трейдера". Мы же в этой статье предложим тезис о том, что от такого риска невозможно защититься балансировкой инструментов, это совсем другая категория риска. Балансировкой мы можем защитить депозит именно от риска, что рынок пойдёт против нашей позиции в рамках среднестатистической волатильности. Если же мы хотим защитить свои средства от паранормальных движений, их ещё называют "черными лебедями", то целесообразно использование следующих принципов. 

  • не брать большой процент риска на позицию одного инструмента, 
  • не стремиться находится в позиции постоянно, 
  • не класть все средства в управлении на один счёт у одного брокера 
  • и при этом не торговать одновременно одни и те же входы по разным счетам и брокерам одновременно. 

При соблюдении этих принципов можно минимизировать потери, если уже так сложится, что инструмент "полетит" против вас, когда вы будете в открытой позиции. Ну а мы возвращаемся именно к рассмотрению рисков в рамках стандартной волатильности.

Одновременный учёт двух указанных факторов позволит сбалансировать риски и нормализовать ожидаемые прибыли по каждой валютной паре при одновременной торговле без перевеса рисков на какой-то один инструмент. Данный подход в дальнейшем даст более однородную статистику торговли для дальнейшего её анализа в истории сделок и позволит снизить погрешность при работе оптимизатора стратегий с этими данными и соответственно снизить стандартное отклонение выборки от средних данных. Чтобы разобраться подробнее как величина стандартного отклонения влияет на качество анализа множества можно ознакомится со статьей "Математика в трейдинге. Оценка результатов торговых сделок", а мы перейдём к выбору контейнера для хранения данных.


Выбор контейнеров для хранения данных 

При выборе контейнеров для хранения данных в нашем проекте будем учитывать следующие факторы:

  • быстродействие контейнера
  • потребность в памяти для его инициализации
  • наличие встроенного функционала для анализа данных
  • в нашем случае ещё и простота инициализации через пользовательский интерфейс

Самым распространёнными критериями при выборе контейнеров для хранения данных являются быстродействие контейнеров и потребность в памяти компьютера для их хранения. Разные типы хранилищ, как правило, могут дать лучшее быстродействие при обработки данных, либо выигрыш в количестве занимаемой памяти.

Для проверки объявим простой массив и специальный контейнер типа vector, предварительно проинициализировав их одним типом данных double и идентичными значениями.

   double arr[] = {1.5, 2.3};
   vector<double> vect = {1.5, 2.3};
С помощью операции sizeof определим размер памяти, которая соответствует указанным выше типам на этапе компиляции.
Print(sizeof(arr));
Print(sizeof(vect));

В результате получим значение 16 байт и 128 байт. Данная разница потребности в памяти для типа данных vector определяется наличием у него встроенного функционала, в том числе за счет дополнительного избыточного выделения памяти под резервирование для обеспечения лучшего быстродействия.

Исхода из этого, с учетом наших задач для контейнеров, которые будут требовать только хранения ранее выбранных данных, мы будем использовать простой тип хранилища как массив. Для типов однородных данных, которые мы впоследствии будем обрабатывать по ходу нашего алгоритма, целесообразно будет использовать специальный тип данных vector. Использования данного типа позволит также сэкономить время на разработку в части написания пользовательских функций для типовых операций, которые в vector уже реализованы "из коробки".

В итоге необходимые для расчёта хранилища данных будут выглядеть следующим образом.
   string symbols[];       // символы инструментов по которым будет осуществляться балансировка

   double tick_val[],      // цена тика инструментов
          atr[],           // волатильность инструментов
          volume[],        // рассчитанный объем позиции по инструментам с учетом балансировки
          point[];         // значение одного пункта изменения цены

   vector<double> risk_contract; // размер риска на стандартный контракт

Теперь перейдём к рассмотрению вариантов реализации решений по вводу данных по балансируемым инструментам.


Выбор способа ввода пользователем символов инструментов

Существует множество решений при выборе способов ввода данных в терминале MetaTrader 5. Глобально они делятся на решения направленные на взаимодействие непосредственно с пользователем через стандартное диалоговое окно терминала, либо на взаимодействие с другими приложениями через сохранённые на диске файлы, либо интерфейсы удаленного взаимодействия.

Учитывая необходимость ввода множества символов инструментов, которые хранятся в терминале в формате string, можно выделить следующие возможные варианты реализации ввода данных в наш скрипт:

  1. чтение данных из табличного файла типа *.csv
  2. чтение данных из бинарного файла типа *.bin
  3. чтение данных из файла базы данных типа .sqlite
  4. использование сторонних способов взаимодействия терминала с удалёнными базами данных
  5. использование web api решения
  6. стандартное для терминала использование переменной/переменных соответствующего типа с модификатором класса памяти типа  input

Перед выбором способа ввода данных в нашем проекте, коротко рассмотрим плюсы и минусы каждого из вариантов реализации данной задачи. При выборе способа описанного в пункте 1 чтение данных из табличного файла типа  *.csv можно реализовать через стандартные функции терминала по работе с файлами. В общем виде код может выглядеть следующим образом.

   string file_name = "Inputs.csv";                         // имя файла

   int handle=FileOpen(file_name,FILE_CSV|FILE_READ,";");   // попытка найти и открыть файл

   if(handle!=INVALID_HANDLE)                               // если файл найден, то
     {
      while(FileIsEnding(handle)==false)                    // начинаем читать файл
        {
         string str_follow = FileReadString(handle);        // чтение
        
        // здесь реализуем заполнение контейнера в зависимости от его вида
        }
     }

Другие варианты реализации функционала работы с файлами достаточно подробно описаны в документации к терминалу. В данном варианте пользователю нужно лишь подготовить файл входных параметров используя стороннее приложение работы с таблицами, например MS Excel или OpenOffice. Для "неприхотливых" в данном случае может подойти даже стандартный блокнот для Windows.

Во втором пункте для использования файлов типа  *.bin подойдёт стандартная функция терминала FileLoad(). Для её использования вам нужно будет заранее знать какую структуру данных использовало приложение при сохранении данного файла, чтобы осуществить считывание данных из файла с данным расширением. Реализация подобной идеи может выглядеть следующим образом.

   struct InputsData                   // берём структуру по которой создавался бинарный файл
     {
      int                  symbol_id;  // id символа для балансировки
      ENUM_POSITION_TYPE   type;       // тип позиции
     };

   InputsData inputsData[];            // хранилище входных параметров

   string  filename="Inputs.bin";      // имя файла

   ArrayFree(inputsData);              // освободили массив

   long count=FileLoad(filename,inputsData,FILE_COMMON); // грузим файл

Главным неудобством данного подхода является то, что функция  FileLoad() не работает со структурами данных, которые содержат объектные типы данных, и соответственно не будет работать со структурой, если она содержит тип данных string. В данном случае придётся дополнительно использовать контейнерные пользовательские словари, чтобы id символов в целочисленной величине как int переводить в соответствующий тип данных string. Либо делать дополнительный запрос к соответствующей базе данных. В общем, данный метод для нашей реализации будет не самым удачным из-за излишней сложности в исполнении достаточно простых операций.

В третьем пункте как раз предлагается использовать встроенный функционал терминала по работе с базой данных файлов .sqlite. Это встроенный в терминал вариант работы с реляционной базой данных построенной на взаимодействии с файлом сохранённым на жестком диске компьютера.

   string filename="Inputs.sqlite"; // имя файла с заранее подготовленными входными параметрами

   int db=DatabaseOpen(filename, DATABASE_OPEN_READWRITE |
                       DATABASE_OPEN_CREATE | DATABASE_OPEN_COMMON); // открываем базу

   if(db!=INVALID_HANDLE)                                            // если открылась, то
     {
      // здесь реализуем запросы к базе данных через функцию DatabaseExecute()
      // структура запросов будет зависеть от структуры таблиц базы данных
     }

В реализации данного подхода важным будет первоначальное построение структуры таблиц базы данных, и от этого уже будет зависеть структура запросов для получения необходимых данных. Основным преимуществам данного подхода, будет являться возможность реляционного хранения данных, при котором в таблицах будут храниться id инструментов в целочисленном формате, а не в строчном, что может дать очень существенный выигрыш в оптимизации дискового пространства компьютера. Так же стоит отметить, что при определённых условиях, данный вариант базы данных может быть очень даже производительным. Более подробно в статье "SQLite: нативная работа с базами данных на SQL в MQL5".

В четвертом пункте описан вариант использование удалённых баз данных, который потребует для своей реализации использовать дополнительные библиотеки сторонних разработчиков. Данный вариант будет более трудоёмким, чем способ описанный в предыдущем пункте, так как не может быть полностью реализован через стандартный функционал терминала. Есть много публикаций на этот счёт, в том числе хороший вариант реализации взаимодействия терминала с базой данный MySQL описан автором в статье "Работа с СУБД MySQL из MQL5 (MQL4)".

Применение web api запросов для получения входных параметров, описанный в пятом пункте, вероятно может считаться наиболее универсальным и кроссплатформенным решением для нашей задачи. Данный функционал является встроенным в терминал через предопределённую функцию WebRequest() и будет являться идеальным, если у вас уже реализована инфраструктура для back-end и front-end приложений, из-за своей универсальности. В противном случае, это может занять достаточно много времени и ресурсов для разработки данных приложений с нуля, даже не смотря на то, что данные решения могут быть написаны на многих современных языках программирования и интерпретаторах.

В данной реализации мы будем использовать переменные с модификатором класса памяти типа input типа данных string из пункта шесть, просто потому, что данный вариант способен обеспечить полностью необходимый функционал без необходимости дополнительной разработки пользовательских программ. Объявлять множество переменных для каждого символа мы естественно не будем, так как мы не можем знать заранее, сколько символов мы будем балансировать, да и для этого есть более изящное и гибкое решение. Будем работать с одной строкой, содержащей все значения, с дальнейшей сепарацией данных в ней. Для этого объявляем на глобальном уровне переменную в следующем виде:

input string input_symbols = "EURCHFz USDJPYz";

Обязательно инициализируем её значением по умолчанию для того, чтобы пользователь, который не будет являться разработчиком, понимал, как именно нужно вводить символы инструментов для корректной работы приложения. В данном случае в качестве сепаратора строки будем использовать обычный пробел для удобства пользователя.

Получение данных из этой переменной и заполнение нашего массива symbols[] организуем с помощью предопределённой функции терминала для работы со строками StringSplit() в следующем виде:

   string symbols[];                         // хранилище введённых пользователем символов
   
   StringSplit(input_symbols,' ',symbols);   // разделяем строку на нужные нам символы

   int size = ArraySize(symbols);            // сразу запоминаем размер получившегося массива

При выполнении функции StringSplit() переданный в неё по ссылке массив symbols[] заполняется данными выделенными из строки с использованием разделителя (' ')  в виде пробела.

Теперь, когда мы имеем заполненный массив со значениями имен символов инструментов для балансировки рисков, перейдём к запросу необходимых данных по выбранны инструментам из терминала для дальнейших расчётов.


Получение необходимых данных по инструментам через предопределённые функции терминала

Для расчётов нам потребуется знать по каждому введённому символу значение минимального изменения цены инструмента и сколько нам будет стоить в валюте депозита это изменение. Реализовать это можно через предопределённую функцию терминала SymbolInfoDouble() с применением нужного нам перечисления ENUM_SYMBOL_INFO_DOUBLE в качестве одного из параметров функции. Реализация переборки данных будет организована через популярный цикл for следующим образом:

for(int i=0; i<size; i++)  // перебираем введённые ранее символы
     {
      point[i] = SymbolInfoDouble(symbols[i],SYMBOL_POINT);                	// запросили минимальны размер изменения цены - тик
      tick_val[i] = SymbolInfoDouble(symbols[i],SYMBOL_TRADE_TICK_VALUE_LOSS);  // запросили цену тика в валюте
     }

Здесь стоит отметить, что перечисление ENUM_SYMBOL_INFO_DOUBLE содержит не только значение SYMBOL_TRADE_TICK_VALUE_LOSS для запроса цены тика в валюте, но также и значение SYMBOL_TRADE_TICK_VALUE и равное ему значение SYMBOL_TRADE_TICK_VALUE_PROFIT. В принципе, запрос любого из указанных значений может быть применён в нашем расчёте так как разница данных значений не очень значительна. Например значения указанных аргументов по кроссу AUDNZD представлены в следующей таблице:

Параметр функции Возвращаемое функцией значение цены тика
SYMBOL_TRADE_TICK_VALUE 0.6062700000000001
SYMBOL_TRADE_TICK_VALUE_LOSS 0.6066200000000002
SYMBOL_TRADE_TICK_VALUE_PROFIT 0.6062700000000001

Таблица 1. Разница возвращаемых значений цены тика при различных параметрах функции SymbolInfoDouble() по символу AUDNZD

Несмотря на то, что в нашем случае наиболее правильным будет использование параметра SYMBOL_TRADE_TICK_VALUE_LOSS, на мой взгляд, мы можем использовать любой из предложенных здесь вариантов. Как ещё в 1962 году сказал Richard Hamming

"The purpose of computing is insight, not numbers" ("Цель вычислений - понимание, а не цифры")

А мы переходим к запросу необходимых данных по волатильности.


Получение необходимых данных о волатильности через стандартный пользовательский класс CiATR

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

  • учитываем ли мы не закрытые гэпы на дневных графиках
  • применяем ли мы усреднение и если да, то за какой промежуток времени
  • исключаем ли мы из расчёта бары с "паранормальной" (исключительно редко встречающейся) волатильностью
  • ведём ли мы расчёт по high/low дневного бара или берём только открытия и закрытия
  • либо вообще торгуем Ренко-бары и время усреднения нам вообще не так важно

Учёт гэпов при расчёте волатильности очень часто применяется в работе на фондовых биржах в отличии от валютного рынка просто по той причине, что на валютном рынке очень редко встречаются незакрытые в течение дня гэпы и итоговый расчёт среднедневной волатильности от этого не меняется. Расчёт здесь будет отличаться только тем, что мы берём максимальное значение от двух значений. Первое это разница между high и low каждого бара, а второе это разница между high и low текущего бара и закрытия предыдущего. Для этого можно использовать встроенную функцию терминала MathMax().

При применении усреднения полученных значений необходимо учитывать то, что чем больше период усреднения, тем более медленным становится данный показатель на изменение волатильности рынка. Как правило для усреднения дневной волатильности на валютном рынке используется период от 3 до 5 дней. Также при расчёте показателя волатильности желательно исключить паранормальные движения на рынке. Автоматически это можно сделать с использованием медианного значения выборки. Для этого можно использовать вызываемый встроенный метод Median() у экземпляра класса типа vector.

Многие предпочитают делать усреднение через учёт волатильности только через цены открытия и закрытия без учёта хвостов свечей. Данный способ не является рекомендуемым, так как способен выдать значение намного ниже, чем волатильность может быть на рынке. При применении Ренко-баров логика меняется очень сильно и здесь уже агрегация волатильности идёт не от торгового периода, а торговый период определяется волатильностью. Поэтому данный подход в нашей статье нам не подойдёт.

В своей реализации мы будем использовать запрос волатильности через индикатор терминала ATR с использованием стандартного пользовательского класса CiATR, который с открытым кодом хранится в библиотеке терминала. Для усреднения показателя будем использовать быстрое значение 3. В общем виде код запроса волатильности будет выглядеть следующем виде. На глобальном уровне объявляем имя переменной класса с вызовом конструктора по умолчанию в следующем виде.

CiATR indAtr[];

Здесь следует отметить, что мы используем именно массив индикаторов, чтобы хранит данные одновременно, а не перегружать один имеющийся индикатор, в основном для удобства и возможности дальнейшего расширения функционала кода. Далее добавляем в наш цикл итерации по символам, где мы уже запрашиваем данные по инструментам следующий код для запроса значений индикатора.

indAtr[i].Create(symbols[i],PERIOD_D1, atr_period);   // создаём символ и период
   
indAtr[i].Refresh();          // обязательно обновляем данные

atr[i] = indAtr[i].Main(1);   // запрашиваем данные по закрытым барам на дневке

Теперь когда все необходимые исходные данные для расчёта получены, переходим непосредственно к расчётам.


Два варианта логики расчёта риска при различных способах выхода из позиции

При написании логики балансировки рисков при одновременной торговле нескольких инструментов осталось учесть следующие важные моменты. Как предусмотрены в нашей торговой системе выходы из сбалансированных позиций и как мы учитываем корреляцию инструментов, которые мы будем балансировать. Даже на первый взгляд понятный критерий корреляции кроссов на валютных рынках, не всегда дают гарантированный уровень корреляции в рассматриваемый промежуток времени и зачастую могут рассматриваться как отдельные инструменты. Например, если мы в начале торгового дня определились с набором инструментов, которые мы будем сегодня торговать и направлением входов, мы должны заранее знать следующее. Будем ли мы закрывать все позиции в конце торгового дня или нет. Будем ли мы рассматривать выход из позиций в течение дня отдельно на каждую позицию, либо просто по времени будем закрывать все позиции. Здесь не может быть какого-то универсального рецепта для всех, просто потому, что каждый трейдер принимает решения о входе и выходе из достаточно большого набора критериев на основании собственных знаний и опыта.

В этой реализации мы сделаем универсальный расчёт, который позволит гибко определять объем входа в позицию исходя из того, что вы торгуете просто за счёт изменения входных параметров риска на инструмент и включения/исключения инструментов, которые по мнению трейдера являются коррелированными в настоящий момент. Для этого объявим входной параметр риска на один инструмент следующим образом.

input double risk_per_instr = 50;

Также, для универсальности использования, мы предусмотрим одновременный вывод результатов балансировки при регрессе введённого пользователем риска на инструмент для торговли взятый в отдельности и с учётом корреляции данных инструментов. Это даст возможность трейдеру получить диапазон варьирования объемов позиций и главное пропорций этих инструментов для одновременной торговли. Для этого в первую очередь нужно добавить следующую запись в наш основной цикл расчёта.

risk_contract[i] = tick_val[i]*atr[i]/point[i]; // считаем риск на стандартный контракт с учётом волатильности и цены пункта

Далее, уже вне указанного цикла находим инструмент в нашем сете с максимальным значением риска, чтобы от него построить пропорцию для балансировки объемов торговли по всему сету. Для этого как раз пригодится встроенный функционал нашего контейнера.

double max_risk = risk_contract.Max();          // вызываем встроенный метод контейнера для поиска максимального значения

Теперь, когда мы знаем максимальный риск в нашем сете, организуем ещё один цикл, чтобы посчитать сбалансированный объем по каждому инструменту в нашей выборке при условии отсутствии их корреляции следующим образом и сразу выведем результаты в журнал.

for(int i=0; i<size; i++)	// идём по размеру нашего массива символов
     {
      volume[i] = NormalizeDouble((max_risk / risk_contract[i]) * (risk_per_instr / max_risk),calc_digits); // считаем сбалансированный объем
     }

Print("Separate");		// предварительный вывод в журнал заголовка

for(int i=0; i<size; i++)	// снова идём по массиву
     {
      Print(symbols[i]+"\t"+DoubleToString(volume[i],calc_digits));	// выводим получившиеся значения объема
     }

Можно сказать, что это получился максимальный объем для торговли внутри дня по нашим позициям, сбалансированный по среднедневной наиболее вероятной и ожидаемой волатильности с учётом заложенного нами риска на инструмент.

Далее посчитаем объем наших позиций исходя из предпосылки, что инструменты могут начать коррелироваться в торговый день, что может по факту существенно увеличить ожидаемый риск на инструмент относительно того, что мы ввели во входных параметрах. Для этого добавим следующий код где просто разделим полученные значение на количество торгуемых инструментов.

Print("Complex");		// предварительный вывод в журнал заголовка
   
for(int i=0; i<size; i++)	// идем по нашему циклу
     {
      Print(symbols[i]+"\t"+DoubleToString(volume[i]/size,calc_digits));	// считаем минимальный объем для входа
     }

Теперь мы получили максимальные и минимальные границы объемов позиций сбалансированных по риску. Исходя из них трейдер самостоятельно определять объем входов в рамках данного диапазона. Главное при этом соблюдать указанные в расчёте пропорции. Следует также отметить, что вывод результатов в журнал является самым простым, но не единственным. Вы также можете использовать другие стандартные функции терминала для вывода информации пользователю. В данном случае терминал предоставляет очень широкий функционал, среди возможных вариантов использование таких функций как MessageBox(), Alert(), SendNotification(), SendMail() и многие другие. Мы переходим к полному коду советника, как он должен выглядеть в компилированном файле.


Итоговая реализация решения в скрипте 

В итоге код нашей реализации будет выглядеть следующим образом.

#property strict		

#include <Indicators\Oscilators.mqh>

//---
input string input_symbols = "EURCHFz USDJPYz";
input double risk_per_instr = 50;
input int atr_period = 3;
input int calc_digits = 3;
CiATR indAtr[];


//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  { 
   string symbols[];
   StringSplit(input_symbols,' ',symbols);   

   int size = ArraySize(symbols);
   double tick_val[], atr[], volume[], point[];
   vector<double> risk_contract;

   ArrayResize(tick_val,size);
   ArrayResize(atr,size);
   ArrayResize(volume,size);
   ArrayResize(point,size);
   ArrayResize(indAtr,size);
   risk_contract.Resize(size);

   for(int i=0; i<size; i++)
     {
      indAtr[i].Create(symbols[i],PERIOD_D1, atr_period);
      indAtr[i].Refresh();

      point[i] = SymbolInfoDouble(symbols[i],SYMBOL_POINT);
      tick_val[i] = SymbolInfoDouble(symbols[i],SYMBOL_TRADE_TICK_VALUE);

      atr[i] = indAtr[i].Main(1);
      risk_contract[i] = tick_val[i]*atr[i]/point[i];
     }

   double max_risk = risk_contract.Max();
   Print("Max risk in set\t"+symbols[risk_contract.ArgMax()]+"\t"+DoubleToString(max_risk));

   for(int i=0; i<size; i++)
     {
      volume[i] = NormalizeDouble((max_risk / risk_contract[i]) * (risk_per_instr / max_risk),calc_digits);
     }

   Print("Separate");
   for(int i=0; i<size; i++)
     {
      Print(symbols[i]+"\t"+DoubleToString(volume[i],calc_digits));
     }

   Print("Complex");
   for(int i=0; i<size; i++)
     {
      Print(symbols[i]+"\t"+DoubleToString(volume[i]/size,calc_digits));
     }
  }
//+------------------------------------------------------------------+

После компиляции запускается окно входных параметров. Например, мы хотим сбалансировать три инструмента и максимальным риском 500 долларов.


Рисунок 2. Входные параметры

Рисунок 2. Входные параметры

В результате выполнение скрипта будут получены следующие данные по объему каждого инструмента с учетом баланса риска.

Рисунок 3. Выходные данные

Рисунок 3. Выходные данные

Здесь представлен полнофункциональный код для расчёта объемов одновременной торговли нескольких инструментов, сбалансированных по риску при применении самого простого, но минимально необходимого функционала предоставляемого терминалом. При желании мы сможете масштабировать данный алгоритм с применением указанных в данной статье дополнительных возможностей, а также собственных наработок.


Заключение

В итоге мы получили полнофункциональный скрипт, который позволит трейдерам, которые не торгуют через алгоритмический трейдинг, быстро и точно корректировать объемы позиций при торговли внутри дня. Я надеюсь, что даже для тех кто торгует алгоритмически, здесь найдутся новые идеи для улучшения своей программной инфраструктуры и возможно для улучшения текущих результатов торговли. Спасибо за прочтение и обратную связь.

Прикрепленные файлы |
RiskBallance.mq5 (2.27 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (6)
Maxim Kuznetsov
Maxim Kuznetsov | 9 февр. 2024 в 16:59

вообще ни о чём...

ни про риски, ни про мультисимвол. 

позор

Aleksandr Seredin
Aleksandr Seredin | 9 февр. 2024 в 18:59
Yevgeniy Koshtenko #:
Отличная статья, идея супер!!!Риск - это наше все. Торговать мы должны от риска. Сам думаю в своих ботов вшить более профессиональное управление рисками, но какое?

Спасибо. Тут в первую очередь нужно смотреть какой инструмент принятия решения применяется для входа. Как долго сидим в позе, какие критерии входа. Если коротко, то этот расчёт с учётом одного дня на вход и выход. Если же мы принимаем решение на овернайт, то риск на инструмент соответственно увеличивается в два раза и придётся заново балансировать с учётом вчерашних позиций.

Aleksandr Seredin
Aleksandr Seredin | 9 февр. 2024 в 18:59
Maxim Kuznetsov #:

вообще ни о чём...

ни про риски, ни про мультисимвол. 

позор

Спасибо за ваш комментарий

Anatoliy Migachyov
Anatoliy Migachyov | 14 февр. 2024 в 12:27

Многие про риск не хотят слышать, а тут баланс, а вообще больная тема многих трейдеров

Aleksandr Seredin
Aleksandr Seredin | 14 февр. 2024 в 17:10
Anatoliy Migachyov #:

Многие про риск не хотят слышать, а тут баланс, а вообще больная тема многих трейдеров

Полностью согласен. В одном предложении, а сколько вопросов затронуто: 

- слышать не хотят, надеясь, что если "не звать лихо, то будет тихо". А вот на рынке так не работает, считаю здесь не место пралогическому мистическому мышлению

- о рисках как правило начинают задумываться не раньше чем, после первого слитого депозита :)

- и даже после первого слитого депозита всех начинает "бесить" слово "баланс", просто потому, что уже становится понятно, что от баланса - начинает падать доходность

- отсюда и "больная тема" для тех, кто так и не смог разобраться с рисками и не смог построить устойчивую торговую систему, а потом эти люди начинают строчить комментарии типа "ни о чем" и "позор" )))

А автору этого комментария респект за такую ёмкую и содержательную формулировку. Спасибо 

Нейросети — это просто (Часть 76): Изучение разнообразных режимов взаимодействия (Multi-future Transformer) Нейросети — это просто (Часть 76): Изучение разнообразных режимов взаимодействия (Multi-future Transformer)
В данной статье мы продолжаем тему прогнозирования предстоящего ценового движения. И предлагаю Вам познакомиться с архитектурой Multi-future Transformer. Основная идея которого заключается в разложении мультимодального распределение будущего на несколько унимодальных распределений, что позволяет эффективно моделировать разнообразные модели взаимодействия между агентами на сцене.
Создаем простой мультивалютный советник с использованием MQL5 (Часть 2): Сигналы индикатора - мультитаймфреймовый Parabolic SAR Создаем простой мультивалютный советник с использованием MQL5 (Часть 2): Сигналы индикатора - мультитаймфреймовый Parabolic SAR
Под мультивалютным советником в этой статье понимается советник, или торговый робот, который может торговать (открывать/закрывать ордера, управлять ордерами, например, трейлинг-стоп-лоссом и трейлинг-профитом) более чем одной парой символов с одного графика. На этот раз мы будем использовать только один индикатор, а именно Parabolic SAR или iSAR на нескольких таймфреймах, начиная с PERIOD_M15 и заканчивая PERIOD_D1.
Разработка системы репликации - Моделирование рынка (Часть 24): FOREX (V) Разработка системы репликации - Моделирование рынка (Часть 24): FOREX (V)
Сегодня мы снимем ограничение, которое препятствовало выполнению моделирований, основанных на построении LAST, и введем новую точку входа специально для этого типа моделирования. Обратите внимание на то, что весь механизм работы будет основан на принципах валютного рынка. Основное различие в данной процедуре заключается в разделении моделирований BID и LAST. Однако важно отметить, что методология, используемая при рандомизации времени и его корректировке для совместимости с классом C_Replay, остается идентичной в обоих видах моделирования. Это хорошо, поскольку изменения в одном режиме приводят к автоматическим улучшениям в другом, особенно если это касается обработки времени между тиками.
Теория категорий в MQL5 (Часть 22): Другой взгляд на скользящие средние Теория категорий в MQL5 (Часть 22): Другой взгляд на скользящие средние
В этой статье мы попытаемся упростить описание концепций, рассматриваемых в этой серии, остановившись только на одном индикаторе - наиболее распространенном и, вероятно, самом легком для понимания. Речь идет о скользящей средней. Также мы рассмотрим значение и возможные применения вертикальных естественных преобразований.