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

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

Код статьи доступен в this Jupyter Notebook.

Это необходимый импорт. Не забудьте установить pip для следующих библиотек, если у вас их еще нет (pandas-datareader, PyPortfolioOpt и plotly).

from pandas_datareader.data import DataReader
from pypfopt.discrete_allocation import DiscreteAllocation, get_latest_prices
from pypfopt import EfficientFrontier
from pypfopt import risk_models
from pypfopt import expected_returns
from pypfopt import plotting
import copy
import numpy as np
import pandas as pd
import plotly.express as px
import matplotlib.pyplot as plt
import seaborn as sns

Получите цены на акции, используя библиотеку pandas-datareader

Давайте получим некоторые данные для биржевых тикеров, которые мы хотим включить в наш портфель. Библиотека pandas-datareader предоставляет метод для извлечения данных о ценах на акции из Интернета и сохранения их в DataFrame.

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

start_date = '2013/01/01' 
end_date = '2022/4/17'
tickers = ['MA', 'FB', 'V', 'AMZN', 'JPM', 'BA']
stocks_df = DataReader(tickers, 'yahoo', start = start_date, end = end_date)['Adj Close']
stocks_df.head()

График цен отдельных акций

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

fig_price = px.line(stocks_df, title='Price of Individual Stocks')
fig_price.show()

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

Ежедневные возвраты

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

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

daily_returns = stocks_df.pct_change().dropna()
daily_returns.head()

Давайте построим график дневной доходности двух акций, Boeing (BA) и Visa (V), за весь период времени.

fig = px.line(daily_returns[['BA', 'V']], title='Daily Returns')
fig.show()

Мы видим, что они имеют тенденцию плавно колебаться около 0. Примечательно, что колебания намного больше в период высокой волатильности (например, во время краха Covid в марте 2020 года).

Волатильность

Ежедневная волатильность

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

daily_returns.std()

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

sns.displot(data=daily_returns[['BA', 'V']], kind = 'kde', aspect = 2.5)
plt.xlim(-0.1, 0.1)

Годовая волатильность

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

Постройте отдельные совокупные доходы

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

def plot_cum_returns(data, title):    
    daily_cum_returns = 1 + data.dropna().pct_change()
    daily_cum_returns = daily_cum_returns.cumprod()*100
    fig = px.line(daily_cum_returns, title=title)
    return fig
    
fig_cum_returns = plot_cum_returns(stocks_df, 'Cumulative Returns of Individual Stocks Starting with $100')
fig_cum_returns.show()

Матрица корреляции между акциями

Матрица корреляции дает нам коэффициенты корреляции между каждой парой акций. Коэффициенты корреляции — это индикаторы силы линейной зависимости между двумя разными переменными. Это значение от 0 до 1, где 1 указывает на самую сильную связь.

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

В Pandas есть удобный метод corr() для генерации матрицы.

corr_df = stocks_df.corr().round(2) # round to 2 decimal places
fig_corr = px.imshow(corr_df, text_auto=True, title = 'Correlation between Stocks')
fig_corr.show()

В целом (хотя и не всегда) цены акций имеют тенденцию двигаться вместе (рост на бычьем рынке, снижение на медвежьем рынке), поэтому корреляция, скорее всего, будет положительной, как показано ниже. Также обратите внимание на слабую корреляцию акций Boeing (BA) с другими акциями в этот период времени. Это может быть связано с тем, что это совсем другая отрасль, или с плохими новостями, которые обрушились на нее за последние годы. И обратите внимание, как похожие компании Mastercard (MA) и Visa (V) почти идеально коррелируют друг с другом.

Ожидаемая доходность и ковариационная матрица

Точно так же матрица ковариации измеряет, движутся ли акции в одном и том же направлении (положительная ковариация) или в противоположных направлениях (отрицательная ковариация).

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

mu = expected_returns.mean_historical_return(stocks_df)
S = risk_models.sample_cov(stocks_df)
print(mu)

Доходность портфеля, безрисковая ставка, волатильность и коэффициент Шарпа

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

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

Коэффициент Шарпа портфеля измеряет его доходность по отношению к безрисковой ставке (например, ставке Казначейства США) и его риску (стандартное отклонение). Его дают:

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

Визуализируйте границы эффективности и максимальное портфолио с коэффициентом Шарпа

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

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

Чтобы представить все визуально, функция ниже (код из документации) генерирует 1000 портфелей наших акций с случайными весами и отображает их доходность и волатильность. Граница эффективности и портфель с максимальным коэффициентом Шарпа также нанесены на график.

def plot_efficient_frontier_and_max_sharpe(mu, S):  
    # Optimize portfolio for maximal Sharpe ratio 
    ef = EfficientFrontier(mu, S)
    fig, ax = plt.subplots(figsize=(8,6))
    ef_max_sharpe = copy.deepcopy(ef)
    plotting.plot_efficient_frontier(ef, ax=ax, show_assets=False)
    # Find the max sharpe portfolio
    ef_max_sharpe.max_sharpe(risk_free_rate=0.02)
    ret_tangent, std_tangent, _ =   ef_max_sharpe.portfolio_performance()
    ax.scatter(std_tangent, ret_tangent, marker="*", s=100, c="r",     label="Max Sharpe")
# Generate random portfolios
    n_samples = 1000
    w = np.random.dirichlet(np.ones(ef.n_assets), n_samples)
    rets = w.dot(ef.expected_returns)
    stds = np.sqrt(np.diag(w @ ef.cov_matrix @ w.T))
    sharpes = rets / stds
    ax.scatter(stds, rets, marker=".", c=sharpes, cmap="viridis_r")
# Output
    ax.set_title("Efficient Frontier with Random Portfolios")
    ax.legend()
    plt.tight_layout()
    plt.show()
    
plot_efficient_frontier_and_max_sharpe(mu, S)

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

Получите веса для оптимизированного портфеля

Портфель с максимальным коэффициентом Шарпа и веса составляющих его акций можно легко получить с помощью PyPortfolioOpt, используя следующий код. Здесь мы используем безрисковую ставку в размере 2 %, что примерно соответствует текущей доходности казначейских облигаций США. Веса задаются в виде объекта OrderedDict, который для удобства преобразования преобразуется в DataFrame.

ef = EfficientFrontier(mu, S)
ef.max_sharpe(risk_free_rate=0.02)
weights = ef.clean_weights()
print(weights)

weights_df = pd.DataFrame.from_dict(weights, orient = 'index')
weights_df.columns = ['weights']
weights_df

Обратите внимание, как определенные веса имеют тенденцию доминировать у высокоэффективных акций, таких как Amazon (высокая доходность при относительно хорошей волатильности), в то время как некоторые веса имеют тенденцию быть равными 0. Мы вернемся к этому позже, ближе к концу статьи. .

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

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

expected_annual_return, annual_volatility, sharpe_ratio = ef.portfolio_performance()
print('Expected annual return: {}%'.format((expected_annual_return*100).round(2)))
print('Annual volatility: {}%'.format((annual_volatility*100).round(2)))
print('Sharpe ratio: {}'.format(sharpe_ratio.round(2)))

Создайте портфель с оптимизированными весами

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

stocks_df['Optimized Portfolio'] = 0
for ticker, weight in weights.items():
    stocks_df['Optimized Portfolio'] += stocks_df[ticker]*weight
stocks_df.head()

Постройте кумулятивную доходность оптимизированного портфеля

fig_cum_returns_optimized = plot_cum_returns(stocks_df['Optimized Portfolio'], 'Cumulative Returns of Optimized Portfolio Starting with $100')
fig_cum_returns_optimized.show()

Заключительные слова

Из приведенного выше графика видно, что если бы мы инвестировали 100 долларов в начале 2013 года, сегодня мы получили бы 1146 долларов, что довольно выгодно.

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

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

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

Обновление: веб-приложение Dashboard

Я создал веб-приложение панели инструментов Stock Portfolio Optimizer (снимок экрана ниже), используя концепции, описанные в этом посте. Смело читайте об этом в этой статье!



Если вам понравилась эта статья, не стесняйтесь проверить мои другие статьи. :)







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



Запланируйте сеанс DDIChat в Data Science / AI / ML / DL:



Подайте заявку на участие в программе DDIChat Expert здесь.
Работайте с DDI: https://datadriveninvestor.com/collaborate
Подпишитесь на DDIntel здесь.