Формирование инвестиционного портфеля
Современная портфельная теория, также известная как модель Марковица, была разработана американским экономистом Гарри Марковицем в 1952 году в его работе "Portfolio Selection". За эту революционную теорию он получил Нобелевскую премию по экономике в 1990 году (совместно с Мертоном Миллером и Уильямом Шарпом).
Марковиц впервые формализовал принципы диверсификации инвестиций, показав, как инвесторы могут снизить риск портфеля без ущерба для ожидаемой доходности. Его ключевая идея заключалась в том, что риск портфеля зависит не только от рисков отдельных активов, но и от корреляции между ними. Это означает, что даже рискованные активы могут снижать общий риск портфеля, если они имеют низкую или отрицательную корреляцию друг с другом.
Первоначально модель Марковица была сложна для практического применения из-за вычислительных ограничений того времени. Решение квадратичной оптимизационной задачи требовало значительных ресурсов, особенно при большом количестве активов. Однако с развитием компьютерных технологий и методов оптимизации, модель стала широко применяться в управлении активами, став основой для множества инвестиционных стратегий и финансовых инструментов.
Упрощенная линейная формулировка
В данном примере мы рассматриваем упрощенную линейную версию модели Марковица, где:
- Риск портфеля аппроксимируется как взвешенная сумма волатильностей отдельных активов;
- Корреляции между активами не учитываются для упрощения модели;
- Задача сводится к линейной оптимизации, что обеспечивает быстрое и точное решение.
Такая формулировка подходит для демонстрации основных принципов портфельной оптимизации и может использоваться как отправная точка для более сложных моделей.
Постановка задачи
- Требуется минимизировать общий риск портфеля (взвешенную волатильность);
- Доходность портфеля должна быть не ниже заданного минимума;
- Взвешенная капитализация портфеля должна быть не ниже заданного минимума для обеспечения ликвидности;
- Бета портфеля не должна превышать установленный максимум для контроля системного риска;
- Рассматривается 9 различных активов;
- Учитывается 4 характеристики каждого актива: ожидаемая доходность, волатильность, бета-коэффициент, рыночная капитализация;
- Доля каждого актива в портфеле не должна превышать 40%;
Решение задачи
1. Выбор модели
Для решения задачи оптимизации портфеля воспользуемся линейной непрерывной моделью. Хотя классическая модель Марковица представляет собой квадратичную задачу оптимизации, наша упрощенная версия является линейной: переменные (доли активов) могут принимать дробные значения от 0 до 40%, все ограничения и целевая функция представлены в виде линейных выражений.
2. Создание модели
Начинаем с создания линейной непрерывной модели. Это означает, что переменные (доли активов в портфеле) могут принимать любые вещественные значения в заданных границах.
// Создаем линейную непрерывную модель (переменные имеют вещественный тип)
Модель = О2
.Модели()
.ЛинейнаяНепрерывнаяМодель()
.СоздатьМодель();
3. Ввод данных
Зададим массив Активы, содержащий информацию о каждом финансовом инструменте:
- Наименование актива (для вывода результата);
- Ожидаемую доходность в процентах годовых;
- Волатильность (стандартное отклонение доходности) в процентах;
- Бета-коэффициент (мера системного риска относительно рынка);
- Рыночную капитализацию в миллиардах рублей.
// Входные данные по активам: название, ожидаемая доходность (%), волатильность (%), бета, капитализация (млрд руб)
Активы = Новый Массив;
Активы.Добавить("SBER, 12.5, 25.0, 0.95, 4200"); // Сбербанк
Активы.Добавить("GAZP, 8.3, 30.5, 1.10, 2800"); // Газпром
Активы.Добавить("LKOH, 15.2, 28.7, 0.87, 2600"); // Лукойл
Активы.Добавить("ROSN, 10.7, 32.1, 1.05, 2100"); // Роснефть
Активы.Добавить("NVTK, 18.1, 35.8, 0.92, 1800"); // Новатэк
Активы.Добавить("YNDX, 22.4, 45.2, 1.25, 1200"); // Яндекс
Активы.Добавить("VTBR, 9.8, 28.9, 1.15, 800"); // ВТБ
Активы.Добавить("GMKN, 14.6, 31.4, 0.98, 700"); // ГМК Норникель
Активы.Добавить("TCSG, 19.3, 38.7, 1.08, 600"); // Тинькофф
Преобразуем строки с данными в числовой формат с помощью метода МассивЧиселИзСтроки библиотеки О2.
// Оцифровываем массив входных данных
Для К = 0 По Активы.ВГраница() Цикл
Активы[К] = О2.Утилиты().МассивЧиселИзСтроки(Активы[К]);
КонецЦикла;
Определим параметры оптимизации - целевые показатели портфеля, которые должны быть достигнуты.
// Параметры оптимизации
МинимальнаяДоходность = 19.0; // Минимальная ожидаемая доходность портфеля 19%
МаксимальнаяБета = 1.10; // Максимальная бета портфеля 1.10
МинимальнаяКапитализация = 1500; // Минимальная взвешенная капитализация
4. Регистрация переменных
Для каждого актива создаем переменную, которая будет представлять его долю в портфеле. Доли могут варьироваться от 0% (актив отсутствует в портфеле)
до 40% (максимальная доля в одном активе). Используем метод МассивПеременныхДиапазона для создания всех переменных одновременно.
// Регистрируем переменные (доли активов в портфеле от 0 до 40%)
ДолиАктивов = Модель.МассивПеременныхДиапазона(
Активы.Количество(),
0.0, // Минимальная доля 0% (можем не инвестировать в актив)
0.40 // Максимальная доля 40%
);
5. Описание ограничений
Фундаментальное требование любого портфеля - сумма всех долей должна равняться единице (100% капитала).
// Основное ограничение: сумма долей = 100%
СуммаДолей = Модель.Выражения().Сумма(ДолиАктивов);
Модель.Ограничения().ЗначениеРавно(СуммаДолей, 1.0);
Взвешенная доходность портфеля должна быть не менее заданного минимума. Используем построитель выражений для формирования линейной комбинации долей и доходностей.
// Ограничение 1: Минимальная доходность портфеля
ВыражениеДоходности = Модель.Выражения().СоздатьПостроительВыражений();
Для К = 0 По Активы.ВГраница() Цикл
ОжидаемаяДоходность = Активы[К][1];
ВыражениеДоходности.ДобавитьТерм(ДолиАктивов[К], ОжидаемаяДоходность);
КонецЦикла;
Модель.Ограничения().ЗначениеБольшеИлиРавно(ВыражениеДоходности.ПолучитьВыражение(), МинимальнаяДоходность);
Бета-коэффициент портфеля характеризует его чувствительность к движениям рынка. Ограничиваем его сверху для контроля системного риска.
// Ограничение 2: Максимальная бета портфеля
ВыражениеБеты = Модель.Выражения().СоздатьПостроительВыражений();
Для К = 0 По Активы.ВГраница() Цикл
БетаАктива = Активы[К][3];
ВыражениеБеты.ДобавитьТерм(ДолиАктивов[К], БетаАктива);
КонецЦикла;
Модель.Ограничения().ЗначениеМеньшеИлиРавно(ВыражениеБеты.ПолучитьВыражение(), МаксимальнаяБета);
Это ограничение обеспечивает достаточную ликвидность портфеля, требуя минимального уровня взвешенной рыночной капитализации.
// Ограничение 3: Минимальная взвешенная капитализация
ВыражениеКапитализации = Модель.Выражения().СоздатьПостроительВыражений();
Для К = 0 По Активы.ВГраница() Цикл
Капитализация = Активы[К][4];
ВыражениеКапитализации.ДобавитьТерм(ДолиАктивов[К], Капитализация);
КонецЦикла;
Модель.Ограничения().ЗначениеБольшеИлиРавно(ВыражениеКапитализации.ПолучитьВыражение(), МинимальнаяКапитализация);
6. Описание целевой функции
Определяем цель оптимизации - минимизировать общий риск портфеля, который аппроксимируется взвешенной волатильностью. Это упрощенная версия оригинальной квадратичной целевой функции Марковица.
// Целевая функция: минимизируем общий риск портфеля (взвешенную волатильность)
ВыражениеРиска = Модель.Выражения().СоздатьПостроительВыражений();
Для К = 0 По Активы.ВГраница() Цикл
Волатильность = Активы[К][2];
ВыражениеРиска.ДобавитьТерм(ДолиАктивов[К], Волатильность);
КонецЦикла;
Модель.Минимизировать(ВыражениеРиска.ПолучитьВыражение());
В этом фрагменте ВыражениеРиска - это построитель выражений, формирующий линейную комбинацию долей активов и их волатильностей. Метод Минимизировать устанавливает задачу поиска решения с минимальным значением риска.
7. Решение модели
Запускаем процесс оптимизации методом Решить. Полученный объект Решение будет содержать оптимальные доли активов и другую информацию о результате.
// Решение модели
Решение = Модель.Решить();
8. Вывод результатов
Для каждого актива с ненулевой долей выводим его долю в портфеле и характеристики. Также рассчитываем и отображаем агрегированные характеристики портфеля.
// Вывод результатов
Если Решение.РешениеНайдено() Тогда
ФорматВывода = "ЧДЦ=2; ЧН=0; ЧГ=0";
Сообщение = "Оптимальный инвестиционный портфель" + Символы.ПС + Символы.ПС;
Сообщение = Сообщение + "Состав портфеля:" + Символы.ПС;
// Переменные для расчета характеристик
ОбщаяДоходность = 0;
ОбщийРиск = 0;
СредняяБета = 0;
ОбщаяКапитализация = 0;
КоличествоПозиций = 0;
// Выводим активы в исходном порядке
Для К = 0 По ДолиАктивов.ВГраница() Цикл
ДоляАктива = Решение.ЗначениеПеременной(ДолиАктивов[К]);
Если ДоляАктива > 0.001 Тогда // Показываем позиции больше 0.1%
ПроцентДоли = ДоляАктива * 100;
Сообщение = Сообщение + СтрШаблон(
"%1: %2%% (доходность: %3%%, риск: %4%%, бета: %5)%6",
Активы[К][0],
Формат(ПроцентДоли, ФорматВывода),
Формат(Активы[К][1], ФорматВывода),
Формат(Активы[К][2], ФорматВывода),
Формат(Активы[К][3], ФорматВывода),
Символы.ПС
);
// Рассчитываем общие характеристики портфеля
ОбщаяДоходность = ОбщаяДоходность + ДоляАктива * Активы[К][1];
ОбщийРиск = ОбщийРиск + ДоляАктива * Активы[К][2];
СредняяБета = СредняяБета + ДоляАктива * Активы[К][3];
ОбщаяКапитализация = ОбщаяКапитализация + ДоляАктива * Активы[К][4];
КоличествоПозиций = КоличествоПозиций + 1;
КонецЕсли;
КонецЦикла;
// Выводим характеристики портфеля
Сообщение = Сообщение + Символы.ПС + "Характеристики портфеля:" + Символы.ПС;
Сообщение = Сообщение + СтрШаблон(
"Ожидаемая доходность: %1 (цель ≥ %2)%3",
Формат(ОбщаяДоходность, ФорматВывода),
Формат(МинимальнаяДоходность, ФорматВывода),
Символы.ПС
);
Сообщение = Сообщение + СтрШаблон(
"Ожидаемый риск: %1%2",
Формат(ОбщийРиск, ФорматВывода),
Символы.ПС
);
Сообщение = Сообщение + СтрШаблон(
"Средняя бета: %1 (цель ≤ %2)%3",
Формат(СредняяБета, ФорматВывода),
Формат(МаксимальнаяБета, ФорматВывода),
Символы.ПС
);
Сообщение = Сообщение + СтрШаблон(
"Количество позиций: %1%2",
КоличествоПозиций,
Символы.ПС
);
Сообщение = Сообщение + СтрШаблон(
"Взвешенная капитализация: %1 млрд ₽ (цель ≥ %2)%3",
Формат(ОбщаяКапитализация, ФорматВывода),
Формат(МинимальнаяКапитализация, ФорматВывода),
Символы.ПС
);
Сообщить(Сообщение);
Иначе
Сообщить("Задача не имеет решения!");
КонецЕсли;
Полный код решения задачи
Ниже представлен полный код решения. Вы также можете скачать пример в виде готовой обработки.
// Создаем линейную непрерывную модель (переменные имеют вещественный тип)
Модель = О2
.Модели()
.ЛинейнаяНепрерывнаяМодель()
.СоздатьМодель();
// Входные данные по активам: название, ожидаемая доходность (%), волатильность (%), бета, капитализация (млрд руб)
Активы = Новый Массив;
Активы.Добавить("SBER, 12.5, 25.0, 0.95, 4200"); // Сбербанк
Активы.Добавить("GAZP, 8.3, 30.5, 1.10, 2800"); // Газпром
Активы.Добавить("LKOH, 15.2, 28.7, 0.87, 2600"); // Лукойл
Активы.Добавить("ROSN, 10.7, 32.1, 1.05, 2100"); // Роснефть
Активы.Добавить("NVTK, 18.1, 35.8, 0.92, 1800"); // Новатэк
Активы.Добавить("YNDX, 22.4, 45.2, 1.25, 1200"); // Яндекс
Активы.Добавить("VTBR, 9.8, 28.9, 1.15, 800"); // ВТБ
Активы.Добавить("GMKN, 14.6, 31.4, 0.98, 700"); // ГМК Норникель
Активы.Добавить("TCSG, 19.3, 38.7, 1.08, 600"); // Тинькофф
// Оцифровываем массив входных данных
Для К = 0 По Активы.ВГраница() Цикл
Активы[К] = О2.Утилиты().МассивЧиселИзСтроки(Активы[К]);
КонецЦикла;
// Параметры оптимизации
МинимальнаяДоходность = 19.0; // Минимальная ожидаемая доходность портфеля 19%
МаксимальнаяБета = 1.10; // Максимальная бета портфеля 1.10
МинимальнаяКапитализация = 1500; // Минимальная взвешенная капитализация
// Регистрируем переменные (доли активов в портфеле от 0 до 40%)
ДолиАктивов = Модель.МассивПеременныхДиапазона(
Активы.Количество(),
0.0, // Минимальная доля 0% (можем не инвестировать в актив)
0.40 // Максимальная доля 40%
);
// Основное ограничение: сумма долей = 100%
СуммаДолей = Модель.Выражения().Сумма(ДолиАктивов);
Модель.Ограничения().ЗначениеРавно(СуммаДолей, 1.0);
// Ограничение 1: Минимальная доходность портфеля
ВыражениеДоходности = Модель.Выражения().СоздатьПостроительВыражений();
Для К = 0 По Активы.ВГраница() Цикл
ОжидаемаяДоходность = Активы[К][1];
ВыражениеДоходности.ДобавитьТерм(ДолиАктивов[К], ОжидаемаяДоходность);
КонецЦикла;
Модель.Ограничения().ЗначениеБольшеИлиРавно(ВыражениеДоходности.ПолучитьВыражение(), МинимальнаяДоходность);
// Ограничение 2: Максимальная бета портфеля
ВыражениеБеты = Модель.Выражения().СоздатьПостроительВыражений();
Для К = 0 По Активы.ВГраница() Цикл
БетаАктива = Активы[К][3];
ВыражениеБеты.ДобавитьТерм(ДолиАктивов[К], БетаАктива);
КонецЦикла;
Модель.Ограничения().ЗначениеМеньшеИлиРавно(ВыражениеБеты.ПолучитьВыражение(), МаксимальнаяБета);
// Ограничение 3: Минимальная взвешенная капитализация
ВыражениеКапитализации = Модель.Выражения().СоздатьПостроительВыражений();
Для К = 0 По Активы.ВГраница() Цикл
Капитализация = Активы[К][4];
ВыражениеКапитализации.ДобавитьТерм(ДолиАктивов[К], Капитализация);
КонецЦикла;
Модель.Ограничения().ЗначениеБольшеИлиРавно(ВыражениеКапитализации.ПолучитьВыражение(), МинимальнаяКапитализация);
// Целевая функция: минимизируем общий риск портфеля (взвешенную волатильность)
ВыражениеРиска = Модель.Выражения().СоздатьПостроительВыражений();
Для К = 0 По Активы.ВГраница() Цикл
Волатильность = Активы[К][2];
ВыражениеРиска.ДобавитьТерм(ДолиАктивов[К], Волатильность);
КонецЦикла;
Модель.Минимизировать(ВыражениеРиска.ПолучитьВыражение());
// Решение модели
Решение = Модель.Решить();
// Вывод результатов
Если Решение.РешениеНайдено() Тогда
ФорматВывода = "ЧДЦ=2; ЧН=0; ЧГ=0";
Сообщение = "Оптимальный инвестиционный портфель" + Символы.ПС + Символы.ПС;
Сообщение = Сообщение + "Состав портфеля:" + Символы.ПС;
// Переменные для расчета характеристик
ОбщаяДоходность = 0;
ОбщийРиск = 0;
СредняяБета = 0;
ОбщаяКапитализация = 0;
КоличествоПозиций = 0;
// Выводим активы в исходном порядке
Для К = 0 По ДолиАктивов.ВГраница() Цикл
ДоляАктива = Решение.ЗначениеПеременной(ДолиАктивов[К]);
Если ДоляАктива > 0.001 Тогда // Показываем позиции больше 0.1%
ПроцентДоли = ДоляАктива * 100;
Сообщение = Сообщение + СтрШаблон(
"%1: %2%% (доходность: %3%%, риск: %4%%, бета: %5)%6",
Активы[К][0],
Формат(ПроцентДоли, ФорматВывода),
Формат(Активы[К][1], ФорматВывода),
Формат(Активы[К][2], ФорматВывода),
Формат(Активы[К][3], ФорматВывода),
Символы.ПС
);
// Рассчитываем общие характеристики портфеля
ОбщаяДоходность = ОбщаяДоходность + ДоляАктива * Активы[К][1];
ОбщийРиск = ОбщийРиск + ДоляАктива * Активы[К][2];
СредняяБета = СредняяБета + ДоляАктива * Активы[К][3];
ОбщаяКапитализация = ОбщаяКапитализация + ДоляАктива * Активы[К][4];
КоличествоПозиций = КоличествоПозиций + 1;
КонецЕсли;
КонецЦикла;
// Выводим характеристики портфеля
Сообщение = Сообщение + Символы.ПС + "Характеристики портфеля:" + Символы.ПС;
Сообщение = Сообщение + СтрШаблон(
"Ожидаемая доходность: %1 (цель ≥ %2)%3",
Формат(ОбщаяДоходность, ФорматВывода),
Формат(МинимальнаяДоходность, ФорматВывода),
Символы.ПС
);
Сообщение = Сообщение + СтрШаблон(
"Ожидаемый риск: %1%2",
Формат(ОбщийРиск, ФорматВывода),
Символы.ПС
);
Сообщение = Сообщение + СтрШаблон(
"Средняя бета: %1 (цель ≤ %2)%3",
Формат(СредняяБета, ФорматВывода),
Формат(МаксимальнаяБета, ФорматВывода),
Символы.ПС
);
Сообщение = Сообщение + СтрШаблон(
"Количество позиций: %1%2",
КоличествоПозиций,
Символы.ПС
);
Сообщение = Сообщение + СтрШаблон(
"Взвешенная капитализация: %1 млрд ₽ (цель ≥ %2)%3",
Формат(ОбщаяКапитализация, ФорматВывода),
Формат(МинимальнаяКапитализация, ФорматВывода),
Символы.ПС
);
Сообщить(Сообщение);
Иначе
Сообщить("Задача не имеет решения!");
КонецЕсли;