Перейти к основному содержимому

Формирование инвестиционного портфеля

Современная портфельная теория, также известная как модель Марковица, была разработана американским экономистом Гарри Марковицем в 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",
Формат(ОбщаяКапитализация, ФорматВывода),
Формат(МинимальнаяКапитализация, ФорматВывода),
Символы.ПС
);

Сообщить(Сообщение);
Иначе
Сообщить("Задача не имеет решения!");
КонецЕсли;
  Скачать пример