Анализ портфеля акций

  • Анализ портфеля акций с помощью Python
  • Построение оптимального портфеля
  • Портфель по методу Монте-Карло

Анализ портфеля акций с помощью Python

Анализ портфеля акций с помощью Python включает в себя использование различных библиотек и методов для понимания производительности, риска и состава коллекции акций. Python предоставляет мощные инструменты, такие как Pandas, NumPy, Matplotlib и другие, которые облегчают анализ, визуализацию и оптимизацию данных, что делает его популярным выбором для портфельного анализа.

Для анализа мы создадим портфель из 4 акций, а именно: Tech Mahindra, Aurobindo Pharma, TATA Consumer Products и Havells. Акции были выбраны случайным образом из различных отраслевых индексов NIFTY, таких как NIFTY FMCG, NIFTY Pharma, NIFTY IT и NIFTY Consumer Durables. Данные по акциям этих компаний были взяты из Yahoo finance.

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

  1. Были импортированы необходимые библиотеки python, такие как Numpy, Pandas, Matplotlib и Seaborn.
  2. Данные считывались в датафрейм с названием компании с помощью read_csv метода.
  3. Столбец даты в данных, полученных из Yahoo Finance, был изменен на datetime, чтобы библиотека pandas распознала его как дату и установила его в качестве столбца индекса.
  4. Названия акций были сохранены в списке для дальнейшего использования в коде

Код для описанных выше шагов показан ниже.

В приведенном выше коде показаны библиотеки python, которые были импортированы для анализа. Он показывает фрейм данных акций отдельных компаний, которые были прочитаны с помощью метода read_csv(). Также был создан список stock_names, содержащий названия компаний. С помощью pd.to_datetime() и .set_index() столбец даты был сделан индексным столбцом для выполнения вычислений, связанных с датой.

На рисунке выше показана часть о получении информации о датафрейме tech_m, содержащем стоковые данные о Tech Mahindra. Он показывает, что в данных нет нулевых значений. Аналогичный процесс выполняется и для других фреймов данных, чтобы проверить нулевые значения и получить общее представление о данных в фрейме данных.

Чтобы проанализировать тенденцию движения дневной цены, мы посмотрим на нормализованную доходность для каждой отдельной акции. Нормализованная доходность рассчитывается путем деления цены на каждый день на цену первого дня для рассматриваемого таймфрейма. Расчет на Python показан ниже.

В приведенном выше коде показан расчет нормализованной доходности с использованием цикла for для деления скорректированной цены закрытия для каждого дня на скорректированную цену закрытия в первый день для каждого фрейма данных акций. Нормализованная доходность для tech_m (Tech Mahindra) показана с колонкой Нормированная доходность за период времени. Столбец нормированной доходности создается для каждого фрейма данных акций и для других данных по акциям компании.

Предположим гипотетическое распределение 30% в Tech Mahindra, 30% в Aurobindo Pharma, 20% в TATA Consumer Products, 20% в Havells. Для каждого распределения нормированная доходность будет умножена на распределение, чтобы получить базовое число для использования нашего общего распределения портфеля для расчета индивидуального распределения для каждой акции.

В приведенном выше коде показано использование функции zip для присоединения распределения к фрейму данных акций той компании, в котором есть данные.

В приведенном выше коде показано создание нового столбца с именем «Распределение» в каждом отдельном кадре данных акций путем умножения распределения на нормализованный доход. Например, в датафрейме tech_m можно увидеть, что новый столбец «Распределение» создается путем умножения 0,3 (или 30%) на нормированную возвращаемость. Это делается и для других датафреймов.

Предположим, мы хотим выделить в этот портфель в общей сложности 1 00 000 рупий. Чтобы найти индивидуальную позицию для каждой акции, мы умножаем общую сумму (в нашем случае 1 00 000 рупий) на распределение. Этот расчет в Python показан ниже.

В приведенном выше коде показан расчет отдельной позиции для каждой акции с помощью цикла for путем умножения общей выделенной суммы для портфеля с распределением в отдельном новом столбце под названием «Позиция». Это делается для всех акций. Например, tech_m кадре данных можно увидеть столбец «Позиция», полученный путем умножения значений в столбце «Распределение» для Tech Mahindra на общую выделенную сумму в 1 00 000 рупий.

К настоящему моменту мы создали позиции для каждой акции. Давайте теперь соберем все позиции по каждой акции в отдельный фрейм данных под названием «portfolio_val», что является сокращением от стоимости портфеля, с помощью функции pd.concat(). Код показан ниже.

В приведенном выше коде показан отдельный фрейм данных под названием «portfolio_val», содержащий позиции для каждой акции.

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

В приведенном выше коде показан столбец «Total Position», созданный путем сложения отдельных позиций. Это будет использоваться для анализа эффективности портфеля.

Приведенный выше код показывает движение нашего портфеля за рассматриваемый таймфрейм с помощью столбца «Total Position» в датафрейме portfolio_val.

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

Давайте посмотрим на дневную доходность портфеля, как показано в коде ниже.

Приведенный выше код показывает дневную доходность портфеля в отдельном столбце под названием «Daily Returns». Ежедневная доходность рассчитывается с использованием ежедневного процентного изменения общей стоимости портфеля, указанного в столбце «Общая позиция». Первое значение — NaN (не число), так как перед ним нет значения.

Используя значение дневной доходности, мы можем рассчитать среднегодовую доходность, умножив ее на 250 (так как в году 250 торговых дней), и среднегодовую волатильность, умножив дневное стандартное отклонение доходности на квадратный корень из 250. Расчет на Python показан ниже.

На изображении выше видно, что портфель имеет среднегодовую доходность около 15,86 % и среднегодовую волатильность около 18,85 %. Распределение дневной доходности можно увидеть ниже в визуализации гистограммы.

На визуализации выше показано распределение дневной доходности портфеля с доходностью по оси x.

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

Совокупная доходность, как показано на рисунке выше, рассчитывается путем вычисления процентного изменения с начала до конца периода времени. Совокупная доходность портфеля составляет около 51 %.

К настоящему моменту мы рассчитали дневную доходность, среднегодовую доходность и среднегодовую волатильность. Теперь воспользуемся этими данными для вычисления коэффициента Шарпа. Коэффициент Шарпа дает доходность на единицу принятого риска.

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

В приведенном выше коде показан расчет коэффициента Шарпа. Значения среднегодовой доходности и среднегодовой волатильности, рассчитанные на вышеуказанных шагах, берутся напрямую. Кроме того, доходность 10-летних индийских государственных облигаций в размере 7,5% считается безрисковой доходностью. Коэффициент Шарпа составляет около 0,44 или 44%. Чем выше коэффициент Шарпа, тем больше потенциальная доходность инвестиций на каждую единицу принятого риска. Как правило, если коэффициент Шарпа инвестиций больше 1, он считается благоприятным, а если ниже 1, то неблагоприятным.

Для нашего распределения и выбора акций коэффициент Шарпа не является благоприятным, так как он ниже 1. Мы можем либо выбрать другой портфель акций, либо другое распределение по существующим акциям, чтобы получить лучший коэффициент Шарпа. Хотя, коэффициент Шарпа не следует читать изолированно, так как это относительная мера. Мы также должны смотреть на стандартное отклонение (SD) при сравнении коэффициента Шарпа.

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

В приведенном выше коде показаны данные, которые считываются для дальнейшего анализа. Данные по ценам на золото были взяты из Nippon India Gold ETF, который отражает цену золота. Ежедневная доходность также рассчитывается для отдельных активов с использованием процентного изменения ежедневных цен.

Во-первых, давайте посмотрим на точечную диаграмму между доходностью NIFTY 50 и нашим портфелем, как показано ниже.

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

В приведенном выше коде показан регрессионный анализ между доходностью NIFTY 50 и нашим портфелем. Во-первых, модуль stats импортируется из библиотеки scipy на Python. Функция stats.linregress() из SciPy выполняет простой линейный регрессионный анализ между доходами обоих. Результаты регрессии хранятся в regress_result переменной.

Наклон регрессии равен 0,88, что также известно как значение бета. Таким образом, значение бета нашего портфеля составляет 0,88. Коэффициент бета, равный 1, указывает на то, что актив имеет тенденцию двигаться в соответствии с NIFTY 50 (или рынком). Бета больше 1 означает более высокую волатильность (большую чувствительность), в то время как бета меньше 1 означает меньшую волатильность (меньшую чувствительность). Кроме того, значение rvalue равно 0,69, что означает, что корреляция между доходностью составляет около 69 %.

Мы выполняем ту же операцию, что и выше, но на этот раз с возвратом золота, как показано ниже.

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

Наклон регрессии равен -0,019, а значение rvalue равно -0,011. Это означает, что движение там не очень хорошо коррелирует, и доходность золота оказывает очень незначительное негативное влияние на доходность нашего портфеля. Аналогичный анализ может быть проведен для понимания взаимосвязи между нашим портфелем и другими активами, такими как цены на нефть, S&P 500, Euro Stoxx 50, Nikkei 225 и т.д.

Кроме того, мы также можем посмотреть на коэффициент Трейнора для нашего портфеля. Коэффициент Трейнора — это скорректированная на риск мера эффективности портфеля, которая оценивает избыточную доходность, полученную на единицу систематического риска, также известную как бета. Он особенно полезен для оценки эффективности инвестиций или портфеля по отношению к их систематическому риску (или рыночному риску, в нашем случае индексу NIFTY 50).

В приведенном выше коде показан расчет коэффициента Трейнора. Его можно рассчитать, разделив избыточную доходность (в нашем случае разницу доходности нашего портфеля и 7,5% безрисковой доходности от доходности 10-летних государственных облигаций) на коэффициент бета портфеля, который является мерой чувствительности портфеля к движениям рынка. Значение бета уже было рассчитано как величина наклона регрессии между доходностью индекса NIFTY 50 и нашего портфеля.

На этом мы подходим к концу нашего анализа. Мы рассмотрели различные метрики, расчеты и визуализации для анализа эффективности нашего портфеля. Однако в нашем анализе мы предположили случайное распределение 30%, 30%, 20% и 20% в четырех акциях. В другом проекте мы рассмотрим оптимизацию нашего портфеля с помощью моделирования по методу Монте-Карло, теории Марковица и алгоритмов оптимизации, чтобы найти оптимальное распределение для одних и тех же акций.

Построение оптимального портфеля

1. Введение

Современная теория портфеля (MPT) — это математическая основа, используемая для выбора активов, которые могут помочь построить наиболее эффективный портфель для заданного уровня риска. Основная концепция MPT заключается в диверсификации по нескольким активам с различным риском и доходностью таким образом, чтобы можно было снизить общий риск при сохранении желаемой доходности, тем самым создавая оптимальный портфель. Целью данного исследования является анализ эффективности MPT с использованием исторических данных OMXH25 (25 крупнейших компаний, котирующихся на фондовой бирже Хельсинки по их рыночной стоимости) и сравнение результатов с равновзвешенным портфелем с использованием тестирования на истории.

Исследование построено следующим образом. Глава 2 представляет собой введение в MPT и представляет его основные предположения. В главе 3 показано распределение доходности акций и объясняется, почему логарифмическая доходность используется для расчета годовых ставок. В главе 4 объясняется, как рассчитывается общая волатильность портфеля и что ограничивает минимальный уровень риска. В главе 5 вычисляется эффективная граница MPT и демонстрируется взаимосвязь между размером портфеля и достигнутыми параметрами риска и доходности. Глава 5 проверяет вычисленные результаты с использованием тестирования на истории на двух исторических периодах времени и, наконец, использует предстоящие данные для анализа только самой теории. Глава 6 завершает исследование обсуждением основных результатов и недостатков, а также предоставляет исходный код и данные OMXH25, использованные в анализе.

2. Современная портфельная теория

Современная теория портфеля (MPT) — это математическая основа для выбора активов с целью построения наиболее эффективного портфеля для любого заданного уровня риска. Теория была впервые введена Гарри Марковицем в 1952 году, и позже он был удостоен Нобелевской премии за свою работу.

Основная концепция MPT заключается в диверсификации по нескольким активам с различным риском и доходностью таким образом, чтобы можно было снизить общий риск при сохранении желаемой доходности, тем самым создавая оптимальный портфель. На практике ни один актив не должен рассматриваться отдельно, но оценивается, как он влияет на общий портфель, который определяется

где r — средняя логарифмическая доходность, w — вектор веса активов, сигма в квадрате — дисперсия, а капитальная сигма — ковариационная матрица. Малая сигма также является стандартным отклонением, которое обычно используется для измерения волатильности (риска) в финансовом мире. В этом исследовании в качестве средней доходности используется годовая историческая доходность, что является довольно абсурдной идеей с точки зрения инвестирования, но позволяет нам сосредоточиться на теории.

Теория MPT содержит некоторые основополагающие предположения:

  • Инвестор, делающий инвестиции, рационален и избежит всех ненужных рисков, связанных с этим
  • Инвесторы предпочитают более высокую доходность низкой
  • Рынки эффективны
  • Доходы от активов распределяются нормально
  • Инвесторов волнуют только дисперсия и среднее значение
  • Неопределенность активов может быть отнесена к систематическому риску (недиверсифицируемый рыночный риск) и отсутствию систематического риска (диверсифицируемому)
  • Инвесторов волнует только остаточный риск, определяемый как портфельный риск минус систематический риск
  • Инвесторы получают компенсацию только за систематический риск

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

3. Доходность акций

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

4. Ковариация портфеля

Корреляция относится к степени связи между двумя случайными величинами и может принимать значения в диапазоне от 1 (указывая на идеальную положительную корреляцию) до -1 (представляющую идеальную отрицательную корреляцию). По сути, корреляция является мерой ковариации, но она ограничена между -1 и 1, тогда как ковариация не имеет таких ограничений. Ниже представлена корреляционная матрица доходности акций OMXH25, охватывающая период с 2000 по 2022 год, которая отображает уровень корреляции между отдельными активами.

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

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

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

5. Эффективная граница

Инвесторы могут создавать инвестиционные портфели, состоящие из различных комбинаций акций из индекса OMXH25, взвешенных в соответствии с их предпочтениями, для достижения определенных уровней риска и доходности. Однако современная портфельная теория (MPT) постулирует, что лишь немногие из этих комбинаций портфелей являются эффективными. Чтобы создать полный набор возможных портфелей, было проведено моделирование методом Монте-Карло с одним миллионом испытаний. Результирующие точки данных показаны на рисунке ниже, причем каждая точка представляет собой отдельный портфель. Треугольные маркеры на рисунке соответствуют фактическим запасам в индексе OMXH25.

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

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

В ходе моделирования не было наложено никаких ограничений по минимальному размеру портфелей, и было установлено, что оптимальный портфель состоял только из трех акций, а именно QT Group (вес 42%), Valmet (вес 18%) и Kojamo (вес 40%). Небольшой размер оптимального портфеля во многом обусловлен чрезвычайно высокой доходностью QT Group, что обуславливает необходимость большого вложения в эту акцию. Следует, однако, отметить, что оптимальный портфель основан на исторической доходности, и что будущая доходность может значительно отличаться от прошлых результатов.

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

Хотя теоретически возможно построить максимальный портфель коэффициентов Шарпа, удерживая только три актива, на самом деле это значительно увеличит риск, поскольку цены на акции не могут быть точно предсказаны. Поэтому в тестировании на истории были изучены две разные версии: истинный глобальный максимальный портфель и максимальный портфель, содержащий максимальное количество активов.

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

6. Тестирование на истории

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

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

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

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

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

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

7. Результаты

Метод моделирования методом Монте-Карло является эффективным средством вычисления границы эффективности, а анализ его результатов на исторических данных дает представление о том, как на самом деле работал бы вычисленный портфель.

Современная портфельная теория (MPT) основана на нескольких предположениях, основным ограничением которых является сложность прогнозирования будущих цен на акции. Эта проблема вносит значительную неопределенность в результаты, особенно для глобального портфеля Шарпа, который сильно зависит от нескольких основных активов. Напротив, все вычисленные портфели средней дисперсии неизменно предлагали наименьший риск в каждой тестовой ситуации. Эти выводы, наряду с минимальной достижимой дисперсией портфеля, описанной в главе 4, являются основными результатами настоящего исследования, подчеркивающими эффективность диверсификации инвестиций, которая может быть применена на практике.

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

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

Портфель по методу Монте-Карло

  • Прогноз производительности портфеля по методу Монте-Карло
  • Оптимизация портфеля с помощью моделирования по методу Монте-Карло
  • Оценка портфеля акций по методу Монте-Карло

Прогноз производительности портфеля по методу Монте-Карло

Основные моменты

  • Сложность:★★★☆☆
  • Смоделируйте будущее портфеля с помощью метода Монте-Карло.
  • Напоминание:Сначала мы кратко опишем метод и принцип Монте-Карло, а затем проведем отбор данных, процесс моделирования и представление результатов, чтобы читатели могли понять реализацию процесса моделирования по методу Монте-Карло.

Предисловие

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

Среда редактирования и необходимые модули

MacOS и Jupyter Notebook# Basic
import numpy as np
import pandas as pd# Graph
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns
sns.set()# TEJ API
import tejapi
tejapi.ApiConfig.api_key = ‘Your Key’
tejapi.ApiConfig.ignoretz = True

Таблица данных о сделках с ценными бумагами:Котируемые ценные бумаги с нескорректированной ценой и индексом. Код: ‘TWN/EWPRCD’.

Таблица данных о возврате:Котируемые ценные бумаги с ежедневной доходностью. Код: ‘TWN/EWPRCD2’.

Отбор и предварительная обработка данных

Шаг 1. Возвращаемые данныеticker = [‘1476’, ‘2330’, ‘2603’, ‘2882’]
# Eclat Textile, TSMC, Cathay Financial Holdings and EVERGREENret = tejapi.get(‘TWN/EWPRCD2’, # 公司交易資料-已調整股價(收盤價)
coid = ticker,
mdate = {‘gte’:’20200101′},
opts = {‘columns’: [‘coid’, ‘mdate’, ‘roia’]},
chinese_column_name = True,
paginate = True)
ret = ret.set_index(‘年月日’)

# Transpose Table
RetData = {}for i in ticker:
r = ret[ret[‘證券代碼’] == i]
r = r[‘日報酬率 %’]
RetData.setdefault(i, r)RetData = pd.concat(RetData, axis = 1)
RetData = RetData * 0.01

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

Шаг 2. Средняя доходность, ковариация, расчет основного долга# Average Return
Mean = pd.DataFrame(
list(np.mean(RetData[i]) for i in RetData.columns), index=RetData.columns, columns = [‘平均值’])# Covariance Matrix
covMatrix = RetData.cov()

Слева: Средняя доходность; Справа: Ковариационная матрица

price = tejapi.get(‘TWN/EWPRCD’, # Unadjusted Price
coid = ticker,
mdate = {‘gte’:’20220525′, ‘lte’:’20220525′},
opts = {‘columns’: [‘coid’, ‘mdate’, ‘close_d’]},
chinese_column_name = True,
paginate = True)price = price.set_index(‘年月日’)principal = (price.loc[‘2020-05-25’][‘收盤價(元)’].sum()) * 1000

Сложив цены закрытия четырех базовых активов на 25 мая 2020 года и предположив, что есть хотя бы одна акция (1 000 акций), первоначальная основная сумма составит 1 193 900 долларов США.

Настройки условий моделирования

Весовые коэффициенты, период моделирования и количество симуляций# Weights
weights = list()for i in range(4):
weights.append(list(price[‘收盤價(元)’])[i]/price[‘收盤價(元)’].sum())# Number
number_of_trial = 100# Period
sim_period = 30

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

Eclat Textile: 40%、TSMC: 44%、EVERGREEN: 12%、Cathay Financial Holdings: 4%

Моделирование процесса

sim_mean = np.full(shape = (sim_period, 4), fill_value = Mean.T.loc[‘平均報酬’])
sim_mean = sim_mean.T

Во-первых, определите sim_mean таблицы хранения и заполните среднюю доходность каждого целевого объекта, а затем транспонируйте его, чтобы разделить отдельные цели.sim_portfolio = np.full(shape = (sim_period, number_of_trial), fill_value = 0)

Затем определите sim_portfolio таблицы хранения в качестве поля прогноза портфеля и заполните каждое поле сначала 0, а затем заполните значение прогноза пути прогноза для периода вычисления.for i in range(0, number_of_trial):
multi_normal = np.random.normal(size = (sim_period, 4))
cholesky = np.linalg.cholesky(covMatrix)

sim_return = sim_mean + np.inner(cholesky, multi_normal)

sim_portfolio[:,i] =
np.cumprod(np.inner(weights, sim_return.T)+1) * principal

multi_normal:

Это видно из приведенного выше рисунка, за исключением Evergreen (2603), который имеет серьезную проблему с жирным хвостом, распределение доходности для других 3 целей примерно близко к нормальному распределению, поэтому мы применяем нормальное распределение к каждой цели.

Холеский:

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

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

Визуализация результата моделирования

plt.rcParams[‘font.sans-serif’] = [‘Arial Unicode MS’] # Enter Chineseplt.figure(figsize=(15,8)) #Set the size of graph
plt.plot(sim_portfolio)
plt.ylabel(‘投資組合累積價值變動’, fontsize = 15)
plt.xlabel(‘模擬期間’, fontsize = 15)
plt.title(‘模擬路徑’, fontsize = 20)
plt.show()

По результатам моделирования на этот раз можно сделать вывод из траектории в нижней части графика, что риск падения портфеля в следующем месяце не превысит максимум -16%; В то время как доходность вверх может составить не более 25%. Таким образом, портфель имеет меньший риск снижения и более высокую вероятность роста.

Заключение

Я полагаю, что благодаря описанному выше процессу читатели смогут понять процесс выполнения метода Монте-Карло и знать, что он поддерживается статистической теорией; Однако, поскольку метод Монте-Карло основан на случайной вероятности, возможный будущий тренд не означает, что рынок будет полностью таким же, как и прогнозируемый результат. Если на рынке происходит большое колебание, приводящее к экстремальным значениям, то референция метода Монте-Карло будет снижена. Поэтому, чтобы удерживать лидирующие позиции на рынке, необходимо не только иметь четкие и логичные результаты анализа, подкрепленные данными, но и держать руку на пульсе потока информации. TEJ также приглашает читателей приобрести решения в TEJ E Shop, я считаю, что читатели получили полную базу данных После базы данных вы можете легко завершить моделирование тренда вашего собственного распределения активов и уловить общий пульс рынка.

Исходный код

https://medium.com/media/703869149766ec1d521b11f176282339

Расширенное чтение

Ссылки по теме

Оптимизация портфеля с помощью моделирования по методу Монте-Карло

Обзор

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

Объективный

Целью данного проекта является разработка имитационной модели по методу Монте-Карло для оптимизации портфеля. Участники должны будут создавать и анализировать портфели, состоящие из различных классов активов (например, акций, облигаций и альтернативных инвестиций), чтобы максимизировать ожидаемую доходность при управлении рисками.

Модель

Сбор и предварительная обработка данных: Собрали данные из Kaggle о ценах на актив и использовали CSV-файлы для анализа. Вы также можете использовать yfinance, чтобы получить доступ к ценам на акции в режиме реального времени в течение фиксированного периода времени (ч/б дата начала и окончания).

Фрейм данных запасов

Затем, перейдя к предварительному процессингу, я сосредоточился на анализе «Скорректированного закрытия» акций из-за ряда факторов. Раздел данных «Скорректированное закрытие» означает денежную стоимость последней транзакции по цене перед закрытием рынка. Скорректированная цена закрытия относится ко всему, что может повлиять на цену акций после закрытия рынка в течение дня.

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

Визуализация

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

Для этого я построил тепловую карту ковариации и корреляции

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

Корреляцию можно рассматривать как масштабированную версию ковариации, где значения ограничены диапазоном от -1 до +1. Корреляция +1 означает положительную связь, т.е. если корреляция между активом А и активом Б равна 1, если актив А увеличивается, актив Б увеличивается. А 0 означает отсутствие связи.

Функция pairplot() компании Seaborn используется для создания матрицы точечной диаграммы. В этой матрице вы можете увидеть, как переменные в stock_returns связаны попарно. Полученный график может дать представление о корреляциях и закономерностях между ежедневной доходностью различных компаний.

Для визуализации корреляций между stock_returns и матрицей корреляции, созданной в предоставленном фрагменте кода, создается диаграмма тепловой карты.

Возвраты журналов и простые возвраты

Обычно мы предпочитаем возвраты журналов простым возвратам по следующим причинам.

  1. Его временная добавка.
  2. Он следует нормальному распределению Гаусса

Мы также можем видеть из гистограммы логарифмических возвратов, что она в основном сосредоточена вокруг 0, выглядит немного нормально распределенной.

Но здесь есть одна загвоздка: является ли нормальность хорошим предположением для финансовых данных? Предположение о том, что цены или, точнее, логарифмические доходы нормально распределены.

Как мы видим, отклонение огромно, так как в нормальном распределении около 99,75% данных находятся в пределах 3 стандартных отклонений, чего здесь просто нет. Но как на самом деле проверить нормальность и насколько хорошо аппроксимировать ее как гауссову.

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

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

Как видно из приведенного выше фрагмента, возврат лога точно предсказывает его.

В то время как если бы мы использовали простые возвраты, то,

Как мы видим, эти простые возвраты хотя и давали предсказание фактического значения, но не фактического значения из-за того, что произведение нормально распределенных переменных не является нормально распределенным.

Простая скользящая средняя (SMA)

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

Я использовал 10,20,30-дневные скользящие средние, более короткие скользящие средние обычно используются для краткосрочной торговли, а длинные — для долгосрочной.

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

Расчет доходности и распределение тиражей

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

Наблюдая, мы видим, что он следует почти нормальному распределению, мы говорим «почти», так как на головной и хвостовой частях гистограммы (на которых мы сделали сглаживание, чтобы прийти к этому выводу) не очень строго следуют распределению Гаусса (почему? И как можно так говорить? Я объяснил это в разделе «Разговор о нормальности»)

Случайные акции, комбинации и коэффициент Макса Шарпа

Предоставленный код определяет функцию randomPortfolio(), которая отвечает за генерацию случайного портфеля акций.

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

IncomePortfolio(Rand) и RiskPortfolio(Rand). Эти функции предназначены для выполнения расчетов, связанных с доходом и риском для портфеля.

Функция IncomePortfolio(Rand) вычисляет ожидаемый доход портфеля на основе средних значений дохода и распределения активов. Функция RiskPortfolio(Rand), с другой стороны, вычисляет риск портфеля на основе ковариационной матрицы доходностей и распределения активов. Вместе эти функции обеспечивают важные показатели для оценки характеристик дохода и риска портфеля.

Сначала переменная «combinations» инициализируется значением 10000. Эта переменная определяет количество комбинаций портфеля, которые будут сгенерированы и оценены

Затем создаются три массива с именами «риск», «доход» и «портфель», которые инициализируются нулями. В этих массивах будут храниться данные о риске, доходе и портфеле для каждой комбинации.

Затем случайно сгенерированный портфель помещается в i-ю строку массива «portfolio». Каждая строка в массиве «portfolio» представляет собой различную комбинацию акций.

Далее вызывается функция «RiskPortfolio()», передающая в качестве аргумента текущий портфель. Эта функция рассчитывает риск, связанный с данным портфелем.

После этого вызывается функция «IncomePortfolio()» с текущим портфелем в качестве аргумента. Эта функция вычисляет доход или ожидаемую доходность портфеля.

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

Безрисковая норма доходности — это доходность инвестиций с нулевым риском, то есть это доходность, на которую инвесторы могут рассчитывать, не рискуя.

Оптимальным рискованным портфелем считается портфель с наибольшим коэффициентом Шарпа.

Максимальный коэффициент Шарпа путем добавления красной точки при соответствующих значениях риска и дохода и включения условных обозначений для его идентификации. Точечная диаграмма обеспечивает визуальное представление взаимосвязи риска и дохода для портфелей.

Лучший портфель — это портфель с коэффициентом Макса Шарпа, и его вес также может быть получен.

Этот код идентифицирует портфель с наибольшим коэффициентом Шарпа, а затем отображает распределение или вес, присвоенный каждой компании в этом портфеле. Это позволяет нам увидеть, как активы или компании распределены в наиболее эффективном портфеле.

Прогнозы будущих цен с использованием моделирования по методу Монте-Карло

Приведенный фрагмент кода вводит функцию с именем monte_carlo, которая использует метод Монте-Карло для моделирования будущей цены акции.

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

Арифметическое броуновское движение

Чем больше дисперсия, тем больше разброс, и тем меньше крутизна.

В контексте моделирования методом Монте-Карло сгенерированные случайные пути будут менее дифференцированными, если дисперсия меньше, и большей, если дисперсия больше, что приведет к более плоской кривой.

Функция monte_carlo использует метод Монте-Карло для создания смоделированных цен на акции за указанное количество дней. Он учитывает начальную цену акций, среднедневную доходность и стандартное отклонение дневной доходности. Функция включает в себя компоненты случайных толчков и дрейфа для расчета смоделированных цен за каждый день.

Моделирование по методу Монте-Карло

Этот код выполняет анализ акций Twitter по методу Монте-Карло, предсказывая их будущие цены с помощью 1000 симуляций. Конечные цены из этих симуляций сохраняются в массиве ‘sim’ и наносятся на график. Таким образом, код предлагает потенциальную информацию о будущем ценовом диапазоне акций Twitter, определенном с помощью моделирования по методу Монте-Карло.

Предоставленный код строит гистограмму, чтобы проиллюстрировать распределение смоделированных цен акций Twitter, полученных на основе моделирования по методу Монте-Карло. Визуализация включает текстовые аннотации, в которых описывается среднее значение, стандартное отклонение и начальная цена моделируемых цен. Это визуальное представление дает ценную информацию о потенциальном диапазоне и характеристиках будущих цен на акции Twitter в соответствии с моделированием по методу Монте-Карло.

Дополнительные рекомендации:

  1. Включите меры нормалей, такие как график Q-Q (использованный выше), ящичковые диаграммы, критерий Колмогонова-Смирмова для количественной оценки нормальности, это поможет визуализировать количественную оценку нормальности данных.
  2. Использование экспоненциальной скользящей средней (EMA) в расчете для EMA подчеркивает последние точки данных. EMA быстрее реагирует на изменение цен, чем простая скользящая средняя (SMA).
  3. Влияние количества дней, учитываемых при расчете скользящей средней, и то, как оно влияет на сглаживание.
  4. Используйте другие показатели соотношения риска и доходности, такие как коэффициент Сортино, коэффициент М2, коэффициент Кальмара, и откалибруйте их различия, и используйте наиболее подходящий в соответствии со сценарием.
  5. Использование геометрического броуновского движения (GBM) вместо арифметического броуновского движения (ABM) для генерации случайных траекторий, которые будут введены в моделирование по методу Монте-Карло.
  6. Понаблюдайте за тем, как изменение фактора риска влияет на оптимальный портфель.

Для получения кодовой базы вы можете обратиться к моему https://github.com/beingamanforever/Monte-Carlo-Simulation на Github

Использованные источники:

  1. Инвестопедия
  2. Канал QuantPie yt
  3. Лекции Массачусетского технологического института по моделированию методом Монте-Карло

Оценка портфеля акций по методу Монте-Карло

Автомобильный завод Форда 1920-х годов (в соответствии с темой, как пример портфеля позже, все акции американской автомобильной промышленности)

Введение

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

Содержание

  • Современная портфельная теория и эффективные рубежи
  • Показатели эффективности портфеля — коэффициенты Шарпа и Сортино
  • Реализация в коде Python
  • Заключение

Современная портфельная теория и эффективные рубежи

Изобретенный лауреатом Нобелевской премии доктором Гарри Марковицем в 1950-х годах, MPT представляет собой подход к определению «оптимальных» весовых коэффициентов для инвестиционного портфеля, который максимизирует доходность (представленную ожидаемой доходностью портфеля) при минимизации рисков (что отражается в стандартном отклонении доходности)

На первый взгляд, наивным решением может быть просто инвестировать все свои средства в акции с самой высокой средней доходностью и наименьшим стандартным отклонением, но именно совместное движение доходности акций (которое пытаются смоделировать как GBM, так и подход Bootstrap Sampling) управляет общим уровнем риска портфеля. Более низкая ковариация между доходностью акций приводит к более низкому общему стандартному отклонению портфеля. Другими словами, диверсификация, как правило, снижает риск.

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

Согласно современной портфельной теории, существует «эффективная граница» весовых коэффициентов портфеля, которая отражается в верхней левой части кривой «С», поскольку она обеспечивает самую высокую доходность для данного стандартного отклонения. Существует также идея «безрисковых» активов — т.е. активов, которые приносят доход, но с нулевым стандартным отклонением. Как правило, это такие инструменты, как государственные казначейские векселя (Примечание: к сожалению, на практике часто не существует такого понятия, как полностью нулевое стандартное отклонение)

Если мы построим линию, которая пересекает «безрисковую» доходность по касательной кривой, то получим линию распределения капитала (CAL) с наибольшим наклоном, а портфель, лежащий по этой касательной, будет иметь «наилучшее» соотношение риска к прибыли.

Показатели эффективности портфеля — коэффициент Шарпа и коэффициент Сортино

Существует ряд различных показателей эффективности портфеля, но сейчас мы сосредоточимся только на двух относительно простых из них: коэффициент Шарпа и коэффициент Сортино.

Коэффициент Шарпа

Коэффициент Шарпа можно рассматривать как наклон линии распределения капитала и описывает, какую избыточную доходность по «безрисковому» активу вы получаете за дополнительную волатильность. Она представлена уравнением:

Используя это, мы можем оценить портфель с самым высоким коэффициентом Шарпа, который отражает портфель, который дает «лучший» профиль риска и доходности.

Типичные значения коэффициентов Шарпа находятся в диапазоне:

  • Меньше 1: Плохо
  • 1–1,99: Адекватно/хорошо
  • 2–2,99: Очень хорошо
  • Больше 3: Отлично

Источник: CorpFinanceInstitute.com

Соотношение Сортино

Одним из недостатков коэффициента Шарпа является то, что он предполагает, что изменчивость вверх и вниз одинаково важна (т.е. стандартное отклонение — это просто стандартное отклонение всего распределения доходности, независимо от того, равны ли доходности +ve или -ve).

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

Источник: Investopedia.com

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

При сравнении этих двух акций величина «волатильности» (при условии, что стандартное отклонение логарифмической доходности является хорошим показателем риска) для акций GM выше на 34% (т.е. 0,0266/0,0204). Однако, если вы используете все распределение доходности, но только примерно на 29% выше, если вы используете только отрицательную доходность

Реализация в коде Python

Часть кода здесь будет ссылаться на предыдущую статью, где я уже описывал некоторые функции, которые extract_prices из Yahoo Finance, calculate_returns и прогнозировать будущие цены с помощью геометрического броуновского движения и начальной выборки, на которые я снова сошлюсь ниже.

Ранее моделирование по методу Монте-Карло для будущей доходности (и, следовательно, цен) проводилось с определенными пользователем весами портфеля.

На этот раз мы будем использовать случайно сгенерированные веса портфеля, и вместо портфеля акций технологических компаний я подумал, что буду использовать акции автомобильного сектора (GM-General Motors, TSLA-Tesla, F-Ford, RACE-Ferrari) за 3-летний период 2018–2020 годов.

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

В коде это выглядело бы примерно так:def EfficientPortfolioHistorical(start_date,end_date,symbols,portfolioValue,NoOfIterationsMC,AnnualRiskFreeRate,imagecounter,targetfolder):

RiskFreeRate=(1+AnnualRiskFreeRate)**(1/252)-1
#Effective rate for period = (1 + annual rate)**(1 / # of periods) – 1

for symbol in symbols:
dfprices = data.DataReader(symbols, start=start_date, end=end_date, data_source=’yahoo’)
dfprices = dfprices[[‘Adj Close’]]
dfprices.columns=[‘ ‘.join(col).strip() for col in dfprices.columns.values]

priceAtEndDate=[]
for symbol in symbols:
priceAtEndDate.append(dfprices[[f’Adj Close {symbol}’]][-(1):].values[0][0])

symbolsWPortfolio=symbols+[«Portfolio»]

ResultsTable=[]

for i in range(0,NoOfIterationsMC):

dfprices_inner=dfprices
portfolioWeightsRandom=list(np.random.dirichlet(np.ones(len(symbols)),size=1)[0])

noOfShares=[]
portfolioValPerSymbol=[x * portfolioValue for x in portfolioWeightsRandom]
for j in range(0,len(symbols)):
noOfShares.append(portfolioValPerSymbol[j]/priceAtEndDate[j])
noOfShares=[round(element, 5) for element in noOfShares]
listOfColumns=dfprices_inner.columns.tolist()
dfprices_inner[«Adj Close Portfolio»]=dfprices_inner[listOfColumns].mul(noOfShares).sum(1)dfreturns ,df_mean_stdev=calc_returns(dfprices_inner,symbols)

mu=np.array(df_mean_stdev[«Mean Log Daily Return»].values.tolist())
sigma=np.array(df_mean_stdev[«StdDev Log Daily Return»].values.tolist())

IterationStdDev=df_mean_stdev.tail(1).values[0][2]
IterationMean=df_mean_stdev.tail(1).values[0][1]

negativereturnsonly=pd.DataFrame(dfreturns.iloc[:,len(dfreturns.columns)-1])
negativereturnsonly=negativereturnsonly[negativereturnsonly[‘Log Daily Returns Adj Close Portfolio’]<0]
IterationNegativeReturnsStdDev=negativereturnsonly[‘Log Daily Returns Adj Close Portfolio’].std()

# Note to go from LOG returns to Simple returns , I used simple returns =exp(log returns)−1
IterationSharpeRatio=round(((np.exp(IterationMean)-1)-RiskFreeRate)/(np.exp(IterationStdDev)-1),3)

IterationSortinoRatio=round(((np.exp(IterationMean)-1)-RiskFreeRate)/(np.exp(IterationNegativeReturnsStdDev)-1),3)

X=[portfolioWeightsRandom,IterationStdDev,IterationMean,IterationSharpeRatio,IterationSortinoRatio]

ResultsTable.append(X)

dfprices_inner.drop(‘Adj Close Portfolio’,inplace=True, axis=1)

FinalResultsTable=pd.DataFrame(ResultsTable,columns=[«Weights»,»Std Dev»,»Mean»,»Sharpe Ratio»,»Sortino Ratio»])

historical_dfreturns ,historical_df_mean_stdev=calc_returns(dfprices,symbols)

historical_df_mean_stdev=historical_df_mean_stdev[[‘Stock’,’StdDev Log Daily Return’,’Mean Log Daily Return’]]
historical_df_mean_stdev.columns=[‘Stock’,’Std Dev’,’Mean’]

fig, ax = plt.subplots(figsize=(10, 5))

FinalResultsTable.plot.scatter(x=»Std Dev»,y=’Mean’,ax=ax)
historical_df_mean_stdev.plot.scatter(x=»Std Dev»,y=’Mean’,c=’r’,marker=’x’,ax=ax)SharpeStdDev=FinalResultsTable.nlargest(1,[‘Sharpe Ratio’])[‘Std Dev’].values[0]
SharpeMean=FinalResultsTable.nlargest(1,[‘Sharpe Ratio’])[‘Mean’].values[0]
Sharperoundedweights=[round(num, 4) for num in FinalResultsTable.nlargest(1,[‘Sharpe Ratio’])[‘Weights’].values[0]]
Sharpeweightstring=[]
for i in range(0,len(symbols)):
Sharpeweightstring.append([symbols[i]+»:»,Sharperoundedweights[i]])
SharpeLabel=»Optimal Sharpe Ratio»
SharpeDetail=’Optimal Sharpe Ratio: ‘+str(FinalResultsTable.nlargest(1,[‘Sharpe Ratio’])[‘Sharpe Ratio’].values[0])+» with Weights «+str(Sharpeweightstring)SortinoStdDev=FinalResultsTable.nlargest(1,[‘Sortino Ratio’])[‘Std Dev’].values[0]
SortinoMean=FinalResultsTable.nlargest(1,[‘Sortino Ratio’])[‘Mean’].values[0]
Sortinoroundedweights=[round(num, 4) for num in FinalResultsTable.nlargest(1,[‘Sortino Ratio’])[‘Weights’].values[0]]
Sortinoweightstring=[]
for i in range(0,len(symbols)):
Sortinoweightstring.append([symbols[i]+»:»,Sortinoroundedweights[i]])
SortinoLabel=’Optimal Sortino Ratio’
SortinoDetail=’Optimal Sortino Ratio: ‘+str(FinalResultsTable.nlargest(1,[‘Sortino Ratio’])[‘Sortino Ratio’].values[0])+» with Weights «+str(Sortinoweightstring)

SharpeSortino=pd.DataFrame(zip([SharpeStdDev,SortinoStdDev],[SharpeMean,SortinoMean]),index=[‘Optimal Sharpe’,’Optimal Sortino’],columns=[‘Std Dev’,’Mean’])
SharpeSortino.plot.scatter(x=»Std Dev»,y=’Mean’,c=’g’,marker=’x’,ax=ax)

txt=list(historical_df_mean_stdev[‘Stock’])+[SharpeLabel,SortinoLabel]
z=list(historical_df_mean_stdev[‘Std Dev’])+[SharpeStdDev,SortinoStdDev]
y=list(historical_df_mean_stdev[‘Mean’])+[SharpeMean,SortinoMean]for i, text in enumerate(txt):
ax.annotate(text, (z[i], y[i]))

plt.title(«Mean vs Std Dev Of Log Returns For «+str(NoOfIterationsMC)+» Different Portfolio Weights»)
plt.savefig(f’static/{targetfolder}/{imagecounter}_efficientportfolio.png’)
print(SharpeDetail)
print(SortinoDetail)

FinalResultsTable[‘Log Returns Std Dev’]=FinalResultsTable[‘Std Dev’]
FinalResultsTable[‘Log Returns Mean’]=FinalResultsTable[‘Mean’]
FinalResultsTable=FinalResultsTable[[‘Weights’,’Log Returns Std Dev’,’Log Returns Mean’,’Sharpe Ratio’,’Sortino Ratio’]]

return FinalResultsTable, SharpeDetail, SortinoDetail

Желтая подсветка отражает эффективную границу

Что касается будущих результатов — становится немного интереснее. Концептуальный подход, который я использовал, заключался в том, чтобы использовать моделирование по методу Монте-Карло для создания случайного набора весов портфеля, а затем для каждого портфеля использовать либо геометрическое броуновское движение, либо выборку бутстрапа для прогнозирования будущей доходности общей доходности портфеля и вычисления коэффициента Шарпа и Сортино. Затем все эти отдельные портфели ранжируются, чтобы найти веса портфелей с «лучшими» коэффициентами Шарпа и Сортино.

Моя первая попытка реализовать это выглядела следующим образомdef EfficientPortfolioWRONGFuture(start_date,end_date,symbols,portfolioValue,T,N,NoOfIterationsMC,AnnualRiskFreeRate,SimMethod,imagecounter,targetfolder):

RiskFreeRate=(1+AnnualRiskFreeRate)**(1/252)-1
#Effective rate for period = (1 + annual rate)**(1 / # of periods) – 1

for symbol in symbols:
dfprices = data.DataReader(symbols, start=start_date, end=end_date, data_source=’yahoo’)
dfprices = dfprices[[‘Adj Close’]]
dfprices.columns=[‘ ‘.join(col).strip() for col in dfprices.columns.values]

priceAtEndDate=[]
for symbol in symbols:
priceAtEndDate.append(dfprices[[f’Adj Close {symbol}’]][-(1):].values[0][0])

symbolsWPortfolio=symbols+[«Portfolio»]

ResultsTable=[]

for i in range(0,NoOfIterationsMC):

dfprices_inner=dfprices
portfolioWeightsRandom=list(np.random.dirichlet(np.ones(len(symbols)),size=1)[0])

noOfShares=[]
portfolioValPerSymbol=[x * portfolioValue for x in portfolioWeightsRandom]
for j in range(0,len(symbols)):
noOfShares.append(portfolioValPerSymbol[j]/priceAtEndDate[j])
noOfShares=[round(element, 5) for element in noOfShares]
listOfColumns=dfprices_inner.columns.tolist()
dfprices_inner[«Adj Close Portfolio»]=dfprices_inner[listOfColumns].mul(noOfShares).sum(1)dfreturns ,df_mean_stdev=calc_returns(dfprices_inner,symbols)

S0=np.array(dfprices.tail(1).values.tolist()[0])
mu=np.array(df_mean_stdev[«Mean Log Daily Return»].values.tolist())
sigma=np.array(df_mean_stdev[«StdDev Log Daily Return»].values.tolist())

if SimMethod==»GBM»:
if len(symbols)==1:
stocks, time = GBMsimulatorUniVar(S0, mu, sigma, T, N)
prediction=pd.DataFrame(stocks)
prediction=prediction.T
prediction.columns=dfprices.columnselse:
Cov=create_covar(dfreturns)
stocks, time = GBMsimulatorMultiVar(S0, mu, sigma, Cov, T, N)
prediction=pd.DataFrame(stocks)
prediction=prediction.T
prediction.columns=dfprices.columns

IterationReturn,Iteration_Mean_Stdev=calc_returns(prediction,symbols)
IterationStdDev=Iteration_Mean_Stdev.tail(1).values[0][2]
IterationMean=Iteration_Mean_Stdev.tail(1).values[0][1]

IterationMeanComponentStocks=Iteration_Mean_Stdev.T.loc[«Mean Log Daily Return»][:-1].values.tolist()
IterationStdDevComponentStocks=Iteration_Mean_Stdev.T.loc[«StdDev Log Daily Return»][:-1].values.tolist()

negativereturnsonly=pd.DataFrame(IterationReturn.iloc[:,len(IterationReturn.columns)-1])
negativereturnsonly=negativereturnsonly[negativereturnsonly[‘Log Daily Returns Adj Close Portfolio’]<0]
IterationNegativeReturnsStdDev=negativereturnsonly[‘Log Daily Returns Adj Close Portfolio’].std()

elif SimMethod==»Bootstrap»:

prediction=bootstrapforecast(dfreturns,T)
IterationStdDev=prediction.iloc[:,0].std()
IterationMean=prediction.iloc[:,0].mean()
negativereturnsonly=prediction[prediction[‘Log Daily Returns Adj Close Portfolio’]<0].iloc[:,0]
IterationNegativeReturnsStdDev=negativereturnsonly.std()

# Note to go from LOG returns to Simple returns , I used simple returns =exp(log returns)−1
IterationSharpeRatio=round(((np.exp(IterationMean)-1)-RiskFreeRate)/(np.exp(IterationStdDev)-1),3)

IterationSortinoRatio=round(((np.exp(IterationMean)-1)-RiskFreeRate)/(np.exp(IterationNegativeReturnsStdDev)-1),3)

X=[portfolioWeightsRandom,IterationStdDev,IterationMean,IterationSharpeRatio,IterationSortinoRatio,\
IterationStdDevComponentStocks,IterationMeanComponentStocks]

ResultsTable.append(X)

dfprices_inner.drop(‘Adj Close Portfolio’,inplace=True, axis=1)

FinalResultsTable=pd.DataFrame(ResultsTable,columns=[«Weights»,»Std Dev»,»Mean»,\
«Sharpe Ratio»,»Sortino Ratio»,»Components Log Returns Std Dev»,»Components Log Returns Mean»])

historical_dfreturns ,historical_df_mean_stdev=calc_returns(dfprices,symbols)

historical_df_mean_stdev=historical_df_mean_stdev[[‘Stock’,’StdDev Log Daily Return’,’Mean Log Daily Return’]]
historical_df_mean_stdev.columns=[‘Stock’,’Std Dev’,’Mean’]

fig, ax = plt.subplots(figsize=(10, 5))

FinalResultsTable.plot.scatter(x=»Std Dev»,y=’Mean’,ax=ax)
historical_df_mean_stdev.plot.scatter(x=»Std Dev»,y=’Mean’,c=’r’,marker=’x’,ax=ax)SharpeStdDev=FinalResultsTable.nlargest(1,[‘Sharpe Ratio’])[‘Std Dev’].values[0]
SharpeMean=FinalResultsTable.nlargest(1,[‘Sharpe Ratio’])[‘Mean’].values[0]
Sharperoundedweights=[round(num, 4) for num in FinalResultsTable.nlargest(1,[‘Sharpe Ratio’])[‘Weights’].values[0]]
Sharpeweightstring=[]
for i in range(0,len(symbols)):
Sharpeweightstring.append([symbols[i]+»:»,Sharperoundedweights[i]])
SharpeLabel=»Optimal Sharpe Ratio»
SharpeDetail=’Optimal Sharpe Ratio: ‘+str(FinalResultsTable.nlargest(1,[‘Sharpe Ratio’])[‘Sharpe Ratio’].values[0])+» with Weights «+str(Sharpeweightstring)SortinoStdDev=FinalResultsTable.nlargest(1,[‘Sortino Ratio’])[‘Std Dev’].values[0]
SortinoMean=FinalResultsTable.nlargest(1,[‘Sortino Ratio’])[‘Mean’].values[0]
Sortinoroundedweights=[round(num, 4) for num in FinalResultsTable.nlargest(1,[‘Sortino Ratio’])[‘Weights’].values[0]]
Sortinoweightstring=[]
for i in range(0,len(symbols)):
Sortinoweightstring.append([symbols[i]+»:»,Sortinoroundedweights[i]])
SortinoLabel=’Optimal Sortino Ratio’
SortinoDetail=’Optimal Sortino Ratio: ‘+str(FinalResultsTable.nlargest(1,[‘Sortino Ratio’])[‘Sortino Ratio’].values[0])+» with Weights «+str(Sortinoweightstring)

SharpeSortino=pd.DataFrame(zip([SharpeStdDev,SortinoStdDev],[SharpeMean,SortinoMean]),index=[‘Optimal Sharpe’,’Optimal Sortino’],columns=[‘Std Dev’,’Mean’])
SharpeSortino.plot.scatter(x=»Std Dev»,y=’Mean’,c=’g’,marker=’x’,ax=ax)

txt=list(historical_df_mean_stdev[‘Stock’])+[SharpeLabel,SortinoLabel]
z=list(historical_df_mean_stdev[‘Std Dev’])+[SharpeStdDev,SortinoStdDev]
y=list(historical_df_mean_stdev[‘Mean’])+[SharpeMean,SortinoMean]for i, text in enumerate(txt):
ax.annotate(text, (z[i], y[i]))

plt.title(«Mean vs Std Dev Of P50 Log Returns For «+str(NoOfIterationsMC)+» Different Portfolio Weights Simulated Using «+SimMethod)
plt.savefig(f’static/{targetfolder}/{imagecounter}_efficientportfolio.png’)
print(SharpeDetail)
print(SortinoDetail)

FinalResultsTable[‘Log Returns Std Dev’]=FinalResultsTable[‘Std Dev’]
FinalResultsTable[‘Log Returns Mean’]=FinalResultsTable[‘Mean’]
FinalResultsTable=FinalResultsTable[[‘Weights’,’Log Returns Std Dev’,’Log Returns Mean’,’Sharpe Ratio’,’Sortino Ratio’,»Components Log Returns Std Dev»,»Components Log Returns Mean»]]

return FinalResultsTable, SharpeDetail, SortinoDetail

Если вы сможете следовать приведенной выше логике, вы можете заметить, что я допустил ошибку… Это связано с тем, что для каждого случайного веса портфеля приведенный выше код выполнит только ОДНУ итерацию для прогнозирования потенциальной будущей доходности. В результате вы получите форму, которая выглядит примерно так:

Результаты при выполнении только ОДНОГО прогноза будущей доходности для каждого портфеля

Это создает форму «круглого овального пузыря», которая выглядела немного странно, так как я ожидал более «изогнутой» формы, основанной на теории. Что еще более важно, если вы посмотрите на предполагаемое «лучшее» соотношение Шарпа или Сортино, то увидите, что оно странным образом рекомендовало больше инвестировать в акции Ferrari. Это не имело смысла, так как исторически «лучший» портфель был в значительной степени ориентирован на акции Tesla.

Затем я понял, что это происходит потому, что КАЖДЫЙ НАБОР ВЕСОВ ПОРТФЕЛЯ ДОЛЖЕН ИМЕТЬ НЕСКОЛЬКО ИТЕРАЦИЙ БУДУЩЕЙ ДОХОДНОСТИ, потому что одна итерация будущей доходности для каждого портфеля не обеспечивает хорошего отражения возможной будущей доходности. Таким образом, «исправленная версия кода» выглядит следующим образом:def EfficientPortfolioFuture(start_date,end_date,symbols,portfolioValue,T,N,NoOfIterationsMC,NoOfIterationsInnerLoop,AnnualRiskFreeRate,SimMethod,imagecounter,targetfolder):

symbolsWPortfolio=symbols+[«Portfolio»]RiskFreeRate=(1+AnnualRiskFreeRate)**(1/252)-1
#Effective rate for period = (1 + annual rate)**(1 / # of periods) – 1

for symbol in symbols:
dfprices = data.DataReader(symbols, start=start_date, end=end_date, data_source=’yahoo’)
dfprices = dfprices[[‘Adj Close’]]
dfprices.columns=[‘ ‘.join(col).strip() for col in dfprices.columns.values]

priceAtEndDate=[]
for symbol in symbols:
priceAtEndDate.append(dfprices[[f’Adj Close {symbol}’]][-(1):].values[0][0])

symbolsWPortfolio=symbols+[«Portfolio»]

ResultsTable=[]

for i in range(0,NoOfIterationsMC):

dfprices_inner=dfprices
portfolioWeightsRandom=list(np.random.dirichlet(np.ones(len(symbols)),size=1)[0])

noOfShares=[]
portfolioValPerSymbol=[x * portfolioValue for x in portfolioWeightsRandom]
for j in range(0,len(symbols)):
noOfShares.append(portfolioValPerSymbol[j]/priceAtEndDate[j])
noOfShares=[round(element, 5) for element in noOfShares]
listOfColumns=dfprices_inner.columns.tolist()
dfprices_inner[«Adj Close Portfolio»]=dfprices_inner[listOfColumns].mul(noOfShares).sum(1)dfreturns ,df_mean_stdev=calc_returns(dfprices_inner,symbols)

S0=np.array(dfprices.tail(1).values.tolist()[0])
mu=np.array(df_mean_stdev[«Mean Log Daily Return»].values.tolist())
sigma=np.array(df_mean_stdev[«StdDev Log Daily Return»].values.tolist())

if SimMethod==»GBM»:forecastresults=pd.DataFrame()
percentiles=pd.DataFrame()

for x in range(1,int(NoOfIterationsInnerLoop)):

Cov=create_covar(dfreturns)
stocks, time = GBMsimulatorMultiVar(S0, mu, sigma, Cov, T, N)
prediction=pd.DataFrame(stocks)
prediction=prediction.T
prediction.columns=dfprices.columns
forecastresults=pd.concat([forecastresults,prediction], axis=1, sort=False)

for y in range(0,len(symbolsWPortfolio)):
percentiles[«P50_»+symbolsWPortfolio[y]]=forecastresults.filter(regex=symbolsWPortfolio[y]).quantile(0.5,1)

IterationReturn,Iteration_Mean_Stdev=calc_returns(percentiles,symbols)
IterationStdDev=Iteration_Mean_Stdev.tail(1).values[0][2]
IterationMean=Iteration_Mean_Stdev.tail(1).values[0][1]
IterationMeanComponentStocks=Iteration_Mean_Stdev.T.loc[«Mean Log Daily Return»][:-1].values.tolist()
IterationStdDevComponentStocks=Iteration_Mean_Stdev.T.loc[«StdDev Log Daily Return»][:-1].values.tolist()

negativereturnsonly=pd.DataFrame(IterationReturn.iloc[:,len(IterationReturn.columns)-1])
negativereturnsonly=negativereturnsonly[negativereturnsonly[negativereturnsonly.columns[0]]<0]
IterationNegativeReturnsStdDev=negativereturnsonly[negativereturnsonly.columns[0]].std()

elif SimMethod==»Bootstrap»:

forecastresults=pd.DataFrame()
returnspercentiles=pd.DataFrame()

for x in range(1,int(NoOfIterationsInnerLoop)):

prediction=bootstrapforecast(dfreturns,T)
prediction=prediction.add_prefix(‘Iter_’+str(x)+’_’)
forecastresults=pd.concat([forecastresults,prediction], axis=1, sort=False)

for y in range(0,len(symbolsWPortfolio)):
returnspercentiles[«P50_»+symbolsWPortfolio[y]]=forecastresults.filter(regex=symbolsWPortfolio[y]).quantile(0.5,1)

IterationMeanComponentStocks=[]
IterationStdDevComponentStocks=[]

for y in range(0,int(len(returnspercentiles.columns)-1)):
IterationMeanComponentStocks.append(returnspercentiles[returnspercentiles.columns[y]].mean())
IterationStdDevComponentStocks.append(returnspercentiles[returnspercentiles.columns[y]].std())

IterationStdDev=returnspercentiles[returnspercentiles.columns[-1]].std()
IterationMean=returnspercentiles[returnspercentiles.columns[-1]].mean()

negativereturnsonly=returnspercentiles[returnspercentiles[returnspercentiles.columns[-1]]<0]
IterationNegativeReturnsStdDev=negativereturnsonly[negativereturnsonly.columns[0]].std()

# Note to go from LOG returns to Simple returns , I used simple returns =exp(log returns)−1
IterationSharpeRatio=round(((np.exp(IterationMean)-1)-RiskFreeRate)/(np.exp(IterationStdDev)-1),3)

IterationSortinoRatio=round(((np.exp(IterationMean)-1)-RiskFreeRate)/(np.exp(IterationNegativeReturnsStdDev)-1),3)

X=[portfolioWeightsRandom,IterationStdDev,IterationMean,IterationSharpeRatio,IterationSortinoRatio,\
IterationStdDevComponentStocks,IterationMeanComponentStocks]

ResultsTable.append(X)

dfprices_inner.drop(‘Adj Close Portfolio’,inplace=True, axis=1)

FinalResultsTable=pd.DataFrame(ResultsTable,columns=[«Weights»,»Std Dev»,»Mean»,\
«Sharpe Ratio»,»Sortino Ratio»,»Components Log Returns Std Dev»,»Components Log Returns Mean»])SharpeStdDev=FinalResultsTable.nlargest(1,[‘Sharpe Ratio’])[‘Std Dev’].values[0]
SharpeMean=FinalResultsTable.nlargest(1,[‘Sharpe Ratio’])[‘Mean’].values[0]
Sharperoundedweights=[round(num, 4) for num in FinalResultsTable.nlargest(1,[‘Sharpe Ratio’])[‘Weights’].values[0]]
Sharpeweightstring=[]
for i in range(0,len(symbols)):
Sharpeweightstring.append([symbols[i]+»:»,Sharperoundedweights[i]])
SharpeLabel=»Optimal Sharpe Ratio»
SharpeDetail=’Optimal Sharpe Ratio: ‘+str(FinalResultsTable.nlargest(1,[‘Sharpe Ratio’])[‘Sharpe Ratio’].values[0])+» with Weights «+str(Sharpeweightstring)

SortinoStdDev=FinalResultsTable.nlargest(1,[‘Sortino Ratio’])[‘Std Dev’].values[0]
SortinoMean=FinalResultsTable.nlargest(1,[‘Sortino Ratio’])[‘Mean’].values[0]
Sortinoroundedweights=[round(num, 4) for num in FinalResultsTable.nlargest(1,[‘Sortino Ratio’])[‘Weights’].values[0]]
Sortinoweightstring=[]
for i in range(0,len(symbols)):
Sortinoweightstring.append([symbols[i]+»:»,Sortinoroundedweights[i]])
SortinoLabel=’Optimal Sortino Ratio’
SortinoDetail=’Optimal Sortino Ratio: ‘+str(FinalResultsTable.nlargest(1,[‘Sortino Ratio’])[‘Sortino Ratio’].values[0])+» with Weights «+str(Sortinoweightstring)

SharpeSortino=pd.DataFrame(zip([SharpeStdDev,SortinoStdDev],[SharpeMean,SortinoMean]),index=[‘Optimal Sharpe’,’Optimal Sortino’],columns=[‘Std Dev’,’Mean’])

SharpeRatio_Best=pd.DataFrame(FinalResultsTable.nlargest(1,[‘Sharpe Ratio’])[«Components Log Returns Mean»].values[0],index=symbols,columns=[«Mean Log Daily Return»])
SharpeRatio_Best[«StdDev Log Daily Return»]=FinalResultsTable.nlargest(1,[‘Sharpe Ratio’])[«Components Log Returns Std Dev»].values[0]
SharpeRatio_Best

fig, ax = plt.subplots(figsize=(10, 5))

SharpeSortino.plot.scatter(x=»Std Dev»,y=’Mean’,c=’g’,marker=’x’,ax=ax)

FinalResultsTable.plot.scatter(x=»Std Dev»,y=’Mean’,ax=ax)
SharpeRatio_Best.plot.scatter(x=»StdDev Log Daily Return»,y=’Mean Log Daily Return’,c=’r’,marker=’x’,ax=ax)txt=list(SharpeRatio_Best.index)+[SharpeLabel,SortinoLabel]
z=list(SharpeRatio_Best[‘StdDev Log Daily Return’])+[SharpeStdDev,SortinoStdDev]
y=list(SharpeRatio_Best[‘Mean Log Daily Return’])+[SharpeMean,SortinoMean]for i, text in enumerate(txt):
ax.annotate(text, (z[i], y[i]))plt.title(«Mean vs Std Dev Of P50 Log Returns For «+str(NoOfIterationsMC)+» Different Portfolio Weights Simulated Using «+SimMethod+» over «+str(NoOfIterationsInnerLoop)+» Iters»)
plt.savefig(f’static/{targetfolder}/{imagecounter}_efficientportfolio.png’)
print(SharpeDetail)
print(SortinoDetail)

FinalResultsTable[‘Log Returns Std Dev’]=FinalResultsTable[‘Std Dev’]
FinalResultsTable[‘Log Returns Mean’]=FinalResultsTable[‘Mean’]
FinalResultsTable=FinalResultsTable[[‘Weights’,’Log Returns Std Dev’,’Log Returns Mean’,’Sharpe Ratio’,’Sortino Ratio’,»Components Log Returns Std Dev»,»Components Log Returns Mean»]]

return FinalResultsTable, SharpeDetail, SortinoDetail

После исправления кода и выполнения 100 итераций будущей доходности для каждой из 1000 итераций различных весов портфеля, а затем извлечения соответствующей доходности P50 для каждого портфеля, диаграмма среднего значения STD стала больше похожа на теоретическую форму, а «лучший» портфель соответствовал тому, что мы интуитивно ожидали — т.е. инвестировали больше в Tesla (предполагая, что ее хорошие показатели в 2018-2020 годах продолжатся и в 2021 году)

Результаты при выполнении 100 итераций для создания прогноза будущей доходности P50 для каждого портфеля

Честно говоря, я все еще пытаюсь сделать это сам, но один ключевой вывод заключается в том, что исторический «лучший портфель» не всегда точно совпадает с будущим прогнозом «лучший портфель». В соответствии с приведенным выше примером, он советует мне избавиться от некоторых акций TSLA и приобрести больше акций RACE (что было удивительно, потому что в историческом портфеле «оптимального коэффициента Шарпа» наиболее взвешенной акцией «No 2» была GM)

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

Заключение

Надеюсь, вам понравилась эта статья. Если вы хотите попробовать это сами, я с тех пор обновил код в веб-приложении, но обратите внимание, однако, что это требует больших вычислительных ресурсов — чтобы запустить 1000 итераций (для разных весов портфеля) X 100 итераций (для будущих возвратов), потребовалось около 25 минут, и это только значения P50.

(Поэтому в веб-приложении, чтобы избежать ошибки Server Time Out, я «жестко закодировал» его таким образом, чтобы пользователи могли определять только количество итераций для различных весов портфеля, но каждый портфель «жестко запрограммирован» на выполнение только 10 итераций для получения будущих доходов P50 — я бы посоветовал вам использовать отдельный файл записной книжки ipynb, который я настроил вместо этого внутри ссылки на Github)

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

  • Эффективные модели Frontier предполагают, что доходность акций может быть точно представлена нормальным распределением, где доходность и волатильность, как правило, постоянны с течением времени (и то, и другое может быть неверно на практике)
  • Оценки эффективной модели Frontier очень чувствительны к небольшим изменениям входных данных, поэтому незначительные изменения в ожидаемой будущей доходности (из-за различных диапазонов исторических данных или методов прогнозирования или даже просто стохастического шума) могут привести к значительным изменениям в оптимальном распределении
  • В моделях Efficient Frontier «лучший» портфель оценивается исключительно по узкому определению рыночного риска, представленного волатильностью доходности. Тем не менее, могут существовать факторы риска, которые не учитываются волатильностью (например, срочные риски и риски дефолта) и другими соображениями, не связанными с риском (например, рыночная капитализация, размер компаний, входящих в портфель)

Так что, если вы планируете использовать это для своего инвестиционного планирования — считайте, что вы предупреждены!

Если вам это показалось интересным — посетите сам сайт и оставьте мне комментарий ниже, дайте мне знать в комментариях, какую тему вы хотели бы, чтобы я осветил!

Далее — в статье выше говорилось о том, как оптимизировать веса для ЗАДАННОГО набора акций в портфеле, но как выбрать, какие акции поместить в портфель в первую очередь? Если вы хотите узнать, как это сделать, нажмите на ссылку на мою следующую статью, которая отвечает именно на этот вопрос:

Источник

Источник

Источник

Источник

Источник