Корреляция и ковариация в финансах: анализ взаимосвязи между активами

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

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

Cov(X, Y) = E[(X — E[X])(Y — E[Y])]

где E[X] и E[Y] — математические ожидания (средние значения) X и Y соответственно.

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

Cov(X, Y) = Σ[(Xᵢ — X̄)(Yᵢ — Ȳ)] / (n — 1)

где X̄ и Ȳ — выборочные средние, а n — размер выборки.

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

Корреляция представляет собой нормализованную версию ковариации, которая масштабирует ее значение в диапазон от -1 до 1. Чаще всего для измерения корреляции используют коэффициент корреляции Пирсона. Он вычисляется следующим образом:

ρ(X, Y) = Cov(X, Y) / (σₓ × σᵧ)

где σₓ и σᵧ — стандартные отклонения X и Y соответственно.

Для выборочных данных формула принимает вид:

r = Σ[(Xᵢ — X̄)(Yᵢ — Ȳ)] / √[Σ(Xᵢ — X̄)² × Σ(Yᵢ — Ȳ)²]

Интерпретация значений корреляции:

  • +1 означает идеальную положительную линейную взаимосвязь;
  • 0 означает отсутствие линейной взаимосвязи;
  • -1 означает идеальную отрицательную линейную взаимосвязь.

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

Интерпретация корреляции и ковариации в финансовом контексте

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

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

Отрицательная корреляция указывает на то, что активы обычно движутся в противоположных направлениях. Классическим примером являются золото и доллар США — когда доллар слабеет, золото часто растет в цене, и наоборот.

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

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

Расчет корреляции и ковариации на примере финансовых данных

Перед расчетом корреляции и ковариации необходимо правильно подготовить финансовые данные. Обычно для анализа используются не абсолютные цены активов, а их относительные изменения — доходности. Это делается по нескольким причинам:

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

Существует два основных способа расчета доходности:

Простая доходность:

R_t = (P_t — P_{t-1}) / P_{t-1}

Логарифмическая доходность:

r_t = ln(P_t / P_{t-1}) = ln(P_t) — ln(P_{t-1})

где P_t — цена актива в момент времени t.

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

Рассмотрим пример расчета корреляции и ковариации на реальных данных с использованием Python. Для этого воспользуемся библиотекой alpha_vantage для получения данных о ценах акций техногигантов и индекса Nasdaq-100 как бенчмарка.

from alpha_vantage.timeseries import TimeSeries
import pandas as pd

api_key = '___________' # Замените на свой ключ https://www.alphavantage.co/support/#api-key
ts = TimeSeries(key=api_key, output_format='pandas')
tickers = ['AAPL', 'MSFT', 'GOOG', 'AMZN', 'VCNIX']
price_data = pd.DataFrame()

for ticker in tickers:
    try:
        df, _ = ts.get_daily(symbol=ticker, outputsize='full')  
        close_series = df['4. close'].copy()  
        close_series.name = ticker
        price_data = close_series.to_frame() if price_data.empty else price_data.join(close_series, how='outer')
        print(f"Данные по {ticker} загружены.")
    except Exception as e:
        print(f"Ошибка по {ticker}: {e}")

print("\nЦены закрытия:")
print(price_data.tail())
Цены закрытия:
              AAPL    MSFT    GOOG    AMZN  VCNIX
date                                             
2025-05-05  198.89  436.17  166.05  186.35  21.56
2025-05-06  198.51  433.31  165.20  185.01  21.37
2025-05-07  196.25  433.35  152.80  188.71  21.46
2025-05-08  197.49  438.17  155.75  192.08  21.67
2025-05-09  198.53  438.73  154.38  193.06  21.66

Мы получили цены, теперь рассчитаем логарифмические доходности:

# Ограничиваем данные последними 2 годами для анализа
price_data = price_data.iloc[-500:]

# Рассчитываем логарифмические доходности
log_returns = np.log(price_data / price_data.shift(1)).dropna()

# Проверяем результаты
log_returns.head()

После выполнения этого кода мы получим датафрейм с логарифмическими доходностями по пяти активам: акциям Apple, Microsoft, Google, Amazon и ETF на индекс Nasdaq-100. Теперь данные готовы к расчету ковариации и корреляции.

Вычисление ковариации и корреляции в Python

С помощью библиотеки pandas вычисление ковариационной и корреляционной матриц является тривиальной задачей:

# Вычисляем матрицу ковариации
cov_matrix = log_returns.cov()
print("Ковариационная матрица:")
print(cov_matrix)

# Вычисляем матрицу корреляции
corr_matrix = log_returns.corr()
print("\nКорреляционная матрица:")
print(corr_matrix)

# Визуализируем корреляционную матрицу с помощью тепловой карты
plt.figure(figsize=(10, 8))
sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', vmin=-1, vmax=1)
plt.title('Корреляционная матрица доходностей активов')
plt.tight_layout()
plt.show()
Ковариационная матрица:
           AAPL      MSFT      GOOG      AMZN     VCNIX
AAPL   0.000286  0.000134  0.000147  0.000159  0.000144
MSFT   0.000134  0.000218  0.000151  0.000190  0.000154
GOOG   0.000147  0.000151  0.000349  0.000222  0.000157
AMZN   0.000159  0.000190  0.000222  0.000386  0.000200
VCNIX  0.000144  0.000154  0.000157  0.000200  0.000323

Корреляционная матрица:
           AAPL      MSFT      GOOG      AMZN     VCNIX
AAPL   1.000000  0.536918  0.466648  0.478570  0.475189
MSFT   0.536918  1.000000  0.546198  0.655397  0.580542
GOOG   0.466648  0.546198  1.000000  0.603500  0.467482
AMZN   0.478570  0.655397  0.603500  1.000000  0.565592
VCNIX  0.475189  0.580542  0.467482  0.565592  1.000000

Корреляционная матрица доходностей активов

Рис. 1: Корреляционная матрица доходностей активов

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

Анализ полученных результатов

Полученная корреляционная матрица позволяет сделать ряд наблюдений о взаимосвязях между активами:

  1. Диагональные элементы матрицы всегда равны 1, так как любой актив идеально коррелирует сам с собой;
  2. Матрица симметрична относительно диагонали, поскольку корреляция между A и B равна корреляции между B и A;
  3. Акции технологических компаний (AAPL, MSFT, GOOG, AMZN) обычно демонстрируют значительную положительную корреляцию между собой, что указывает на их подверженность общим факторам риска;
  4. ETF на индекс NASDAQ-100 (VCNIX) также положительно коррелирует с акциями технологических гигантов, но степень корреляции может быть ниже, так как индекс включает не только эти компании.

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

  1. Как меняются корреляции со временем? Для этого можно использовать скользящую корреляцию;
  2. Меняются ли корреляции в периоды высокой волатильности?
  3. Существуют ли долгосрочные и краткосрочные компоненты в корреляциях между активами?

Рассмотрим пример расчета и визуализации скользящей корреляции:

# Выбираем пару активов для анализа скользящей корреляции
asset1 = 'AAPL'
asset2 = 'MSFT'

# Создаем временной ряд из пары активов
pair_returns = log_returns[[asset1, asset2]]

# Рассчитываем скользящую корреляцию с окном 60 дней
rolling_corr = pair_returns[asset1].rolling(window=60).corr(pair_returns[asset2])

# Визуализируем результаты
plt.figure(figsize=(12, 6))
plt.plot(rolling_corr.index, rolling_corr.values)
plt.axhline(y=pair_returns.corr().iloc[0, 1], color='b', linestyle='--')
plt.title(f'Скользящая корреляция между {asset1} и {asset2} (окно 60 дней)')
plt.ylabel('Корреляция')
plt.grid(True)
plt.tight_layout()
plt.show()

График скользящей корреляции между акциями Apple и Microsoft

Рис. 2: График скользящей корреляции между акциями Apple и Microsoft

Этот код позволяет увидеть, как меняется корреляция между акциями Apple и Microsoft с течением времени, что дает более глубокое представление о стабильности их взаимосвязи.

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

Теория оптимального портфеля Марковица

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

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

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

Читайте также:  Эконометрика в биржевой аналитике: современные подходы и методы

E[R_p] = Σ(w_i × E[R_i])

где:

  • E[R_p] — ожидаемая доходность портфеля;
  • w_i — вес i-го актива в портфеле;
  • E[R_i] — ожидаемая доходность i-го актива.

Риск портфеля, измеряемый его дисперсией, вычисляется с использованием весов активов и ковариационной матрицы:

σ²_p = Σ(Σ(w_i × w_j × Cov(R_i, R_j)))

В матричной форме:

σ²_p = w^T × Σ × w

где:

  • σ²_p — дисперсия (квадрат стандартного отклонения) доходности портфеля;
  • w — вектор весов активов;
  • Σ — ковариационная матрица доходностей активов.

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

Реализация оптимизации портфеля в Python

Давайте реализуем оптимизацию портфеля по Марковицу с использованием Python. Мы найдем портфели с минимальным риском и максимальным коэффициентом Шарпа (отношение избыточной доходности к риску).

from scipy.optimize import minimize
import numpy as np

# Рассчитываем годовые доходности и ковариационную матрицу
# (предполагаем, что log_returns содержит дневные доходности)
annual_returns = log_returns.mean() * 252  # 252 торговых дня в году
cov_matrix_annual = log_returns.cov() * 252

# Задаем безрисковую ставку
risk_free_rate = 0.02  # например, 2% годовых

# Количество активов
n_assets = len(tickers)

# Функция для расчета доходности портфеля
def portfolio_return(weights, returns):
    return np.sum(weights * returns)

# Функция для расчета волатильности (риска) портфеля
def portfolio_volatility(weights, cov_matrix):
    return np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights)))

# Функция для расчета коэффициента Шарпа (с отрицательным знаком для минимизации)
def negative_sharpe_ratio(weights, returns, cov_matrix, risk_free_rate):
    portfolio_ret = portfolio_return(weights, returns)
    portfolio_vol = portfolio_volatility(weights, cov_matrix)
    sharpe_ratio = (portfolio_ret - risk_free_rate) / portfolio_vol
    return -sharpe_ratio

# Ограничения: сумма весов равна 1
constraints = {'type': 'eq', 'fun': lambda x: np.sum(x) - 1}

# Границы: веса могут быть от 0 до 1 (без коротких позиций)
bounds = tuple((0, 1) for _ in range(n_assets))

# Начальное предположение о равных весах
initial_weights = np.array([1/n_assets] * n_assets)

# Оптимизация для получения портфеля с максимальным коэффициентом Шарпа
optimal_sharpe = minimize(
    negative_sharpe_ratio,
    initial_weights,
    args=(annual_returns, cov_matrix_annual, risk_free_rate),
    method='SLSQP',
    bounds=bounds,
    constraints=constraints
)

# Оптимальные веса для максимального коэффициента Шарпа
optimal_sharpe_weights = optimal_sharpe['x']

# Ожидаемая доходность и риск оптимального портфеля
optimal_return = portfolio_return(optimal_sharpe_weights, annual_returns)
optimal_volatility = portfolio_volatility(optimal_sharpe_weights, cov_matrix_annual)
optimal_sharpe_ratio = (optimal_return - risk_free_rate) / optimal_volatility

# Вывод результатов
print("Оптимальный портфель (максимальный коэффициент Шарпа):")
for i, ticker in enumerate(tickers):
    print(f"{ticker}: {optimal_sharpe_weights[i]:.4f}")
print(f"Ожидаемая годовая доходность: {optimal_return:.4f}")
print(f"Ожидаемый годовой риск (волатильность): {optimal_volatility:.4f}")
print(f"Коэффициент Шарпа: {optimal_sharpe_ratio:.4f}")
Оптимальный портфель (максимальный коэффициент Шарпа):
AAPL: 0.0000
MSFT: 0.2806
GOOG: 0.0000
AMZN: 0.7194
VCNIX: 0.0000
Ожидаемая годовая доходность: 0.2532
Ожидаемый годовой риск (волатильность): 0.2722
Коэффициент Шарпа: 0.8568

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

Согласно вычислениям, нам нужно включить в портфель две акции: Amazon с весом 72% и Microsoft с весом 28%. Остальные включать не нужно. При таком раскладе мы можем рассчитывать на ожидаемую годовую доходность в 25%.

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

Построение эффективной границы

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

# Функция для оптимизации портфеля с заданной целевой доходностью
def portfolio_min_volatility_for_return(weights, returns, cov_matrix, target_return):
    # Риск портфеля
    portfolio_vol = portfolio_volatility(weights, cov_matrix)
    # Штраф за отклонение от целевой доходности
    return_penalty = 100 * np.abs(portfolio_return(weights, returns) - target_return)
    return portfolio_vol + return_penalty

# Создаем диапазон целевых доходностей
min_return = np.min(annual_returns)
max_return = np.max(annual_returns)
target_returns = np.linspace(min_return, max_return, 50)

# Для каждой целевой доходности находим оптимальный портфель
efficient_portfolios = []

for target in target_returns:
    # Дополнительное ограничение на целевую доходность
    target_constraints = [
        {'type': 'eq', 'fun': lambda x: np.sum(x) - 1},
        {'type': 'eq', 'fun': lambda x: portfolio_return(x, annual_returns) - target}
    ]
    
    # Оптимизация
    efficient_portfolio = minimize(
        portfolio_volatility,
        initial_weights,
        args=(cov_matrix_annual,),
        method='SLSQP',
        bounds=bounds,
        constraints=target_constraints
    )
    
    if efficient_portfolio['success']:
        efficient_weights = efficient_portfolio['x']
        portfolio_ret = portfolio_return(efficient_weights, annual_returns)
        portfolio_vol = portfolio_volatility(efficient_weights, cov_matrix_annual)
        efficient_portfolios.append((portfolio_ret, portfolio_vol, efficient_weights))

# Распаковываем результаты
returns, volatilities, weights_array = zip(*efficient_portfolios)

# Создаем график эффективной границы
plt.figure(figsize=(12, 8))

# Отображаем эффективную границу
plt.plot(volatilities, returns, 'b-', linewidth=3)

# Отмечаем отдельные активы
for i, ticker in enumerate(tickers):
    individual_return = annual_returns[i]
    individual_volatility = np.sqrt(cov_matrix_annual.iloc[i, i])
    plt.scatter(individual_volatility, individual_return, label=ticker)

# Отмечаем оптимальный портфель с максимальным коэффициентом Шарпа
plt.scatter(optimal_volatility, optimal_return, color='red', marker='*', s=200,
            label='Портфель с максимальным коэффициентом Шарпа')

# Добавляем оси и легенду
plt.xlabel('Ожидаемый риск (волатильность)', fontsize=12)
plt.ylabel('Ожидаемая доходность', fontsize=12)
plt.title('Эффективная граница', fontsize=16)
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()

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

Рис. 3: Визуализация эффективной границы портфеля для максимизации коэффициента Шарпа

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

Динамические аспекты корреляции в финансах

Непостоянство корреляций во времени

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

Особенно заметным является феномен «корреляционного заражения» (correlation contagion) во время рыночных кризисов, когда корреляции между большинством активов стремятся к единице. Это явление подрывает саму основу диверсификации именно в тот момент, когда она наиболее необходима.

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

  1. Скользящие корреляции с фиксированными окнами;
  2. Экспоненциально взвешенные корреляции, придающие больший вес недавним наблюдениям;
  3. Условные корреляции, моделируемые с помощью многомерных GARCH-моделей.

Рассмотрим пример реализации динамических корреляций с использованием экспоненциально взвешенного подхода:

# Экспоненциально взвешенные скользящие корреляции
def ewma_correlation(returns, span=60):

    # Рассчитываем экспоненциально взвешенные ковариации
    ewma_cov = returns.ewm(span=span).cov()
    
    # Создаем пустой DataFrame для хранения корреляций
    n_assets = returns.shape[1]
    asset_names = returns.columns
    columns = []
    
    # Формируем имена для столбцов (пары активов)
    for i in range(n_assets):
        for j in range(i+1, n_assets):
            columns.append(f"{asset_names[i]}_{asset_names[j]}")
    
    ewma_corr = pd.DataFrame(index=returns.index, columns=columns)
    
    # Заполняем DataFrame корреляциями
    for date in returns.index:
        cov_date = ewma_cov.loc[date]
        k = 0
        for i in range(n_assets):
            sigma_i = np.sqrt(cov_date.loc[asset_names[i], asset_names[i]])
            for j in range(i+1, n_assets):
                sigma_j = np.sqrt(cov_date.loc[asset_names[j], asset_names[j]])
                cov_ij = cov_date.loc[asset_names[i], asset_names[j]]
                corr_ij = cov_ij / (sigma_i * sigma_j)
                ewma_corr.loc[date, columns[k]] = corr_ij
                k += 1
    
    return ewma_corr

# Рассчитываем экспоненциально взвешенные корреляции для наших данных
ewma_correlations = ewma_correlation(log_returns)

# Визуализируем динамические корреляции для нескольких пар активов
plt.figure(figsize=(14, 8))

for column in ewma_correlations.columns[:3]:  # Ограничиваемся тремя парами для ясности
    plt.plot(ewma_correlations.index, ewma_correlations[column], label=column)

plt.axhline(y=0, color='k', linestyle='--', alpha=0.3)
plt.title('Динамика экспоненциально взвешенных корреляций')
plt.xlabel('Дата')
plt.ylabel('Корреляция')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()

Динамика экспоненциально взвешенных корреляций котировок Apple к котировкам Microsoft, Google, Amazon

Рис. 4: Динамика экспоненциально взвешенных корреляций котировок Apple к котировкам Microsoft, Google, Amazon

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

Анализ условных корреляций с помощью многомерных GARCH-моделей

Многомерные обобщенные авторегрессионные условно гетероскедастичные (MGARCH) модели представляют собой более сложный, но и более точный подход к моделированию динамических взаимосвязей между активами. Одной из популярных спецификаций является модель DCC-GARCH (Dynamic Conditional Correlation GARCH).

Модель DCC-GARCH состоит из двух этапов: сначала моделируются одномерные GARCH-процессы для отдельных активов, а затем моделируется динамика условных корреляций между стандартизованными остатками. Этот подход позволяет учесть как изменения волатильности отдельных активов, так и изменения их взаимосвязей.

Реализация DCC-GARCH модели в Python требует использования специализированных библиотек. Рассмотрим пример с использованием библиотеки arch:

import numpy as np
import pandas as pd
from arch import arch_model
import matplotlib.pyplot as plt

def dcc_garch(returns, p=1, q=1):

    n_assets = returns.shape[1]
    asset_names = returns.columns
    T = len(returns)
    
    # Оценка одномерных GARCH моделей
    std_residuals = pd.DataFrame(index=returns.index, columns=asset_names)
    
    for asset in asset_names:
        model = arch_model(returns[asset], vol='GARCH', p=p, o=0, q=q, dist='Normal')
        result = model.fit(disp='off')
        # Получаем условные волатильности
        h_t = result.conditional_volatility
        # Рассчитываем стандартизованные остатки
        std_residuals[asset] = returns[asset] / h_t
    
    # Оценка DCC модели (упрощенная версия)
    # Рассчитываем безусловную корреляционную матрицу
    unconditional_corr = std_residuals.corr().values
    
    # Инициализируем массив для хранения условных корреляций
    conditional_corr = np.zeros((T, n_assets, n_assets))
    
    # Параметры DCC (в простой реализации используем фиксированные значения)
    alpha = 0.05
    beta = 0.90
    
    # Начальная условная корреляция = безусловная корреляция
    Q_t = unconditional_corr.copy()
    
    # Итеративно обновляем условные корреляции
    for t in range(1, T):
        # Вектор стандартизованных остатков на момент t-1
        e_t_1 = std_residuals.iloc[t-1].values.reshape(-1, 1)
        
        # Обновляем Q_t
        Q_t = (1 - alpha - beta) * unconditional_corr + \
              alpha * np.dot(e_t_1, e_t_1.T) + \
              beta * Q_t
        
        # Нормализуем Q_t для получения корреляционной матрицы
        q_diag = np.sqrt(np.diag(Q_t))
        R_t = Q_t / np.outer(q_diag, q_diag)
        
        # Сохраняем результат
        conditional_corr[t] = R_t
    
    # Преобразуем результаты в более удобный формат
    corr_pairs = []
    for i in range(n_assets):
        for j in range(i+1, n_assets):
            pair_name = f"{asset_names[i]}_{asset_names[j]}"
            corr_series = pd.Series(
                [conditional_corr[t, i, j] for t in range(T)],
                index=returns.index
            )
            corr_pairs.append((pair_name, corr_series))
    
    # Создаем DataFrame с условными корреляциями
    dcc_results = pd.DataFrame({name: series for name, series in corr_pairs})
    
    return dcc_results

# Применяем DCC-GARCH к нашим данным
# Обратите внимание, что это упрощенная реализация, для реальных приложений
# рекомендуется использовать специализированные библиотеки
dcc_results = dcc_garch(log_returns)

# Визуализируем результаты
plt.figure(figsize=(14, 8))

for column in dcc_results.columns[:3]:  # Ограничиваемся тремя парами для ясности
    plt.plot(dcc_results.index, dcc_results[column], label=column)

plt.axhline(y=0, color='k', linestyle='--', alpha=0.3)
plt.title('Условные корреляции по DCC-GARCH модели')
plt.xlabel('Дата')
plt.ylabel('Условная корреляция')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()

График динамики условных корреляций по DCC-GARCH модели

Рис. 5: График динамики условных корреляций по DCC-GARCH модели

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

Читайте также:  Волатильность акций, облигаций, деривативов. Как ее посчитать?

Корреляции в периоды рыночных кризисов

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

Этот феномен можно проиллюстрировать, сравнив корреляции до, во время и после финансового кризиса 2008 года или недавнего кризиса, вызванного пандемией COVID-19:

# Определяем периоды для анализа (например, для COVID-19)
# Предполагаем, что данные содержат этот период
pre_crisis = log_returns.loc['2019-07-01':'2020-01-31']
during_crisis = log_returns.loc['2020-02-01':'2020-04-30']
post_crisis = log_returns.loc['2020-05-01':'2020-12-31']

# Рассчитываем корреляционные матрицы для каждого периода
corr_pre = pre_crisis.corr()
corr_during = during_crisis.corr()
corr_post = post_crisis.corr()

# Функция для визуализации корреляционных матриц
def plot_correlation_comparison(corr_list, period_names, figsize=(18, 6)):

    fig, axes = plt.subplots(1, len(corr_list), figsize=figsize)
    
    for i, (corr, name) in enumerate(zip(corr_list, period_names)):
        sns.heatmap(corr, annot=True, cmap='coolwarm', vmin=-1, vmax=1, ax=axes[i])
        axes[i].set_title(f'Корреляции: {name}')
    
    plt.tight_layout()
    plt.show()

# Визуализируем корреляции для разных периодов
plot_correlation_comparison(
    [corr_pre, corr_during, corr_post],
    ['До кризиса', 'Во время кризиса', 'После кризиса']
)

# Рассчитываем средние корреляции для каждого периода
def average_correlation(corr_matrix):
    """Рассчитывает среднюю корреляцию, исключая диагональ"""
    n = corr_matrix.shape[0]
    sum_corr = corr_matrix.sum().sum() - n  # Вычитаем диагональ (всегда = 1)
    return sum_corr / (n * (n - 1))

avg_corr_pre = average_correlation(corr_pre)
avg_corr_during = average_correlation(corr_during)
avg_corr_post = average_correlation(corr_post)

print(f"Средняя корреляция до кризиса: {avg_corr_pre:.4f}")
print(f"Средняя корреляция во время кризиса: {avg_corr_during:.4f}")
print(f"Средняя корреляция после кризиса: {avg_corr_post:.4f}")

Тепловая карта корреляций цен акций техногигантов до кризиса COVID, во время, и после него

Рис. 6: Тепловая карта корреляций цен акций техногигантов до кризиса COVID, во время, и после него

Средняя корреляция до кризиса: 0.6287
Средняя корреляция во время кризиса: 0.8942
Средняя корреляция после кризиса: 0.5265

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

Продвинутые метрики для измерения взаимосвязи между финансовыми активами

Ранговая корреляция Спирмена и другие непараметрические метрики

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

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

# Рассчитываем ранговую корреляцию Спирмена
spearman_corr = log_returns.corr(method='spearman')

# Сравниваем с корреляцией Пирсона
pearson_corr = log_returns.corr(method='pearson')

# Визуализируем разницу
fig, axes = plt.subplots(1, 2, figsize=(16, 8))

sns.heatmap(pearson_corr, annot=True, cmap='coolwarm', vmin=-1, vmax=1, ax=axes[0])
axes[0].set_title('Корреляция Пирсона')

sns.heatmap(spearman_corr, annot=True, cmap='coolwarm', vmin=-1, vmax=1, ax=axes[1])
axes[1].set_title('Ранговая корреляция Спирмена')

plt.tight_layout()
plt.show()

Сравнение тепловых карт корреляций активов методами Пирсона и Спирмена

Рис. 7: Сравнение тепловых карт корреляций активов методами Пирсона и Спирмена

Другой полезной непараметрической метрикой является тау Кендалла, которая измеряет ранговую корреляцию, но основана на конкордантности пар наблюдений. Ее можно рассчитать с помощью функции corr с параметром method=’kendall’.

import seaborn as sns
import matplotlib.pyplot as plt

# Рассчитываем три типа корреляций
pearson_corr = log_returns.corr(method='pearson')
spearman_corr = log_returns.corr(method='spearman')
kendall_corr = log_returns.corr(method='kendall')

# Визуализируем все три
fig, axes = plt.subplots(1, 3, figsize=(20, 7))

sns.heatmap(pearson_corr, annot=True, cmap='coolwarm', vmin=-1, vmax=1, ax=axes[0], fmt='.2f')
axes[0].set_title('Корреляция Пирсона')

sns.heatmap(spearman_corr, annot=True, cmap='coolwarm', vmin=-1, vmax=1, ax=axes[1], fmt='.2f')
axes[1].set_title('Ранговая корреляция Спирмена')

sns.heatmap(kendall_corr, annot=True, cmap='coolwarm', vmin=-1, vmax=1, ax=axes[2], fmt='.2f')
axes[2].set_title('Тау-Кендалл')

plt.tight_layout()
plt.show()

Сравнение корреляций биржевых активов методами Пирсона, Спирмена, Кендалла

Рис. 8: Сравнение корреляций биржевых активов методами Пирсона, Спирмена, Кендалла

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

Копулы для моделирования сложных взаимосвязей

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

Копула — это многомерная функция распределения на единичном кубе [0, 1]^n с равномерными маргинальными распределениями. Согласно теореме Склара, любое многомерное распределение может быть представлено через его маргинальные распределения и копулу, которая описывает структуру зависимости.

Существует множество типов копул, каждый из которых моделирует различные виды зависимостей:

  • Гауссова копула — основана на многомерном нормальном распределении;
  • t-копула — основана на многомерном t-распределении Стьюдента, лучше моделирует зависимость в хвостах;
  • Архимедовы копулы (Клэйтона, Гумбеля, Франка) — асимметричные копулы, позволяющие моделировать разную зависимость в верхнем и нижнем хвостах;

Рассмотрим пример моделирования зависимости с помощью t-копулы:

from scipy.stats import t, norm
import numpy as np
from scipy.optimize import minimize

def fit_t_copula(data, df_init=5):
    """
    Подгоняет t-копулу к данным.
    
    Parameters:
    data: DataFrame с наблюдениями
    df_init: начальное значение степеней свободы
    
    Returns:
    Параметры t-копулы: корреляционная матрица и степени свободы
    """
    # Преобразуем данные в псевдонаблюдения (ранги)
    u = pd.DataFrame(index=data.index, columns=data.columns)
    for col in data.columns:
        # Эмпирическая функция распределения
        u[col] = data[col].rank() / (len(data) + 1)
    
    # Преобразуем в квантили t-распределения
    x = pd.DataFrame(index=data.index, columns=data.columns)
    for col in u.columns:
        x[col] = t.ppf(u[col], df=100)  # Используем большое df для начала
    
    # Оцениваем корреляционную матрицу
    corr_matrix = x.corr().values
    
    # Функция логарифма правдоподобия для t-копулы
    def neg_log_likelihood(params):
        df = params[0]
        if df <= 2:  # Ограничение на степени свободы
            return 1e10
        
        log_lik = 0
        n = x.shape[1]
        
        # Для каждого наблюдения
        for i in range(len(x)):
            x_i = x.iloc[i].values.reshape(-1, 1)
            
            # Рассчитываем плотность многомерного t
            # (упрощенно, без нормализующих констант)
            quad_form = x_i.T @ np.linalg.inv(corr_matrix) @ x_i
            log_lik += -0.5 * (df + n) * np.log(1 + quad_form / df)
            
            # Вычитаем логарифм плотности одномерных t
            for j in range(n):
                log_lik -= t.logpdf(x.iloc[i, j], df=df)
        
        return -log_lik
    
    # Оптимизируем степени свободы
    result = minimize(neg_log_likelihood, [df_init], bounds=[(2.1, 50)])
    df_optimal = result['x'][0]
    
    return corr_matrix, df_optimal

# Применяем к нашим данным
copula_corr, copula_df = fit_t_copula(log_returns)

print(f"Оптимальные степени свободы t-копулы: {copula_df:.2f}")
print("Корреляционная матрица t-копулы:")
print(pd.DataFrame(copula_corr, index=log_returns.columns, columns=log_returns.columns))
Оптимальные степени свободы t-копулы: 50.00

Корреляционная матрица t-копулы:
           AAPL      MSFT      GOOG      AMZN     VCNIX
AAPL   1.000000  0.513620  0.462330  0.451384  0.606532
MSFT   0.513620  1.000000  0.599480  0.675818  0.766550
GOOG   0.462330  0.599480  1.000000  0.619058  0.650961
AMZN   0.451384  0.675818  0.619058  1.000000  0.732494
VCNIX  0.606532  0.766550  0.650961  0.732494  1.000000

Копулы позволяют более точно моделировать вероятность совместных экстремальных событий, что особенно важно для управления рисками. Например, t-копула с низким числом степеней свободы показывает более высокую зависимость в хвостах распределения (так называемую «tail dependence»), что лучше соответствует наблюдаемому поведению финансовых рынков в периоды кризисов.

Мера экстремальной зависимости: tail dependence

Tail dependence (зависимость в хвостах) — это мера вероятности совместных экстремальных событий. Она особенно важна для управления рисками, так как позволяет оценить вероятность одновременных крупных потерь по нескольким активам.

Различают верхнюю и нижнюю зависимость в хвостах:

  • Нижняя зависимость в хвостах (lower tail dependence) измеряет вероятность того, что одна переменная примет экстремально низкое значение при условии, что другая переменная также принимает экстремально низкое значение;
  • Верхняя зависимость в хвостах (upper tail dependence) измеряет аналогичную вероятность для экстремально высоких значений.

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

Коэффициент зависимости в нижнем хвосте определяется как:

λₗ = lim(u→0+) P(F₁(X₁) ≤ u | F₂(X₂) ≤ u)

где F₁ и F₂ — функции распределения случайных величин X₁ и X₂.

Давайте рассмотрим код для оценки зависимости в хвостах на эмпирических данных:

def empirical_tail_dependence(returns, q=0.05, upper=False):
    """
    Оценивает эмпирическую зависимость в хвостах.
    
    Parameters:
    returns: DataFrame с доходностями активов
    q: квантиль для определения экстремальных событий
    upper: если True, рассчитывает верхнюю зависимость, иначе нижнюю
    
    Returns:
    DataFrame с коэффициентами зависимости в хвостах для всех пар активов
    """
    n_assets = returns.shape[1]
    asset_names = returns.columns
    
    # Создаем матрицу для хранения результатов
    tail_dep = pd.DataFrame(np.zeros((n_assets, n_assets)),
                           index=asset_names, columns=asset_names)
    
    for i in range(n_assets):
        for j in range(n_assets):
            if i == j:
                tail_dep.iloc[i, j] = 1.0
                continue
                
            x = returns.iloc[:, i]
            y = returns.iloc[:, j]
            
            if upper:
                # Верхняя зависимость в хвостах
                q_x = x.quantile(1 - q)
                q_y = y.quantile(1 - q)
                
                # P(Y > q_y | X > q_x)
                p_y_given_x = ((y > q_y) & (x > q_x)).sum() / (x > q_x).sum()
                # P(X > q_x | Y > q_y)
                p_x_given_y = ((x > q_x) & (y > q_y)).sum() / (y > q_y).sum()
            else:
                # Нижняя зависимость в хвостах
                q_x = x.quantile(q)
                q_y = y.quantile(q)
                
                # P(Y < q_y | X < q_x)
                p_y_given_x = ((y < q_y) & (x < q_x)).sum() / (x < q_x).sum()
                # P(X < q_x | Y < q_y)
                p_x_given_y = ((x < q_x) & (y < q_y)).sum() / (y < q_y).sum()
            
            # Берем среднее двух условных вероятностей
            tail_dep.iloc[i, j] = (p_y_given_x + p_x_given_y) / 2
    
    return tail_dep

# Рассчитываем нижнюю зависимость в хвостах (вероятность совместных потерь)
lower_tail_dep = empirical_tail_dependence(log_returns, q=0.05, upper=False)

# Визуализируем результаты
plt.figure(figsize=(12, 10))
sns.heatmap(lower_tail_dep, annot=True, cmap='YlOrRd')
plt.title('Эмпирическая нижняя зависимость в хвостах (q=0.05)')
plt.tight_layout()
plt.show()

# Сравниваем с обычной корреляцией
fig, axes = plt.subplots(1, 2, figsize=(18, 8))

sns.heatmap(log_returns.corr(), annot=True, cmap='coolwarm', vmin=-1, vmax=1, ax=axes[0])
axes[0].set_title('Корреляция Пирсона')

sns.heatmap(lower_tail_dep, annot=True, cmap='YlOrRd', ax=axes[1])
axes[1].set_title('Нижняя зависимость в хвостах')

plt.tight_layout()
plt.show()

Тепловая карта эмпирической нижней зависимости в хвостах цен биржевых активов

Рис. 9: Тепловая карта эмпирической нижней зависимости в хвостах цен биржевых активов

Сравнение тепловых карт корреляции Пирсона и Нижней зависимости в хвостах акций техногигантов и индекса Nasdaq-100

Рис. 10: Сравнение тепловых карт корреляции Пирсона и Нижней зависимости в хвостах акций техногигантов и индекса Nasdaq-100

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

Читайте также:  Рынки капитала: акции, облигации, деривативы. Основы ценообразования и оценки

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

Иерархическая кластеризация активов

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

from scipy.cluster.hierarchy import linkage, dendrogram
from scipy.spatial.distance import squareform

# Преобразуем корреляционную матрицу в матрицу расстояний
# Активы с высокой корреляцией должны быть близки друг к другу
distance_matrix = np.sqrt(2 * (1 - log_returns.corr()))

# Применяем иерархическую кластеризацию
linkage_matrix = linkage(squareform(distance_matrix), method='ward')

# Визуализируем дендрограмму
plt.figure(figsize=(14, 8))
dendrogram(linkage_matrix, labels=log_returns.columns, leaf_font_size=12)
plt.title('Иерархическая кластеризация активов на основе корреляций')
plt.xlabel('Активы')
plt.ylabel('Расстояние (√2(1-ρ))')
plt.grid(True)
plt.tight_layout()
plt.show()

Иерархическая кластеризация биржевых активов на основе корреляций

Рис. 11: Иерархическая кластеризация биржевых активов на основе корреляций

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

Детерминант корреляционной матрицы как мера диверсификации

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

# Рассчитываем детерминант корреляционной матрицы
corr_det = np.linalg.det(log_returns.corr())
print(f"Детерминант корреляционной матрицы: {corr_det:.6f}")

# Для сравнения рассчитаем детерминанты для разных периодов
# (используя ранее определенные периоды)
det_pre = np.linalg.det(pre_crisis.corr())
det_during = np.linalg.det(during_crisis.corr())
det_post = np.linalg.det(post_crisis.corr())

print(f"Детерминант корреляционной матрицы до кризиса: {det_pre:.6f}")
print(f"Детерминант корреляционной матрицы во время кризиса: {det_during:.6f}")
print(f"Детерминант корреляционной матрицы после кризиса: {det_post:.6f}")

Детерминант корреляционной матрицы: 0.129395
Детерминант корреляционной матрицы до кризиса: 0.012683
Детерминант корреляционной матрицы во время кризиса: 0.000172
Детерминант корреляционной матрицы после кризиса: 0.019579

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

Максимальная диверсификация и риск-паритет

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

Портфель максимальной диферсификации

Портфель максимальной диверсификации стремится максимизировать отношение взвешенной суммы волатильностей активов к волатильности портфеля:

DR = (w⊤σ) / √(w⊤Σw)

где σ — вектор волатильностей активов, Σ — ковариационная матрица.

def portfolio_diversification_ratio(weights, cov_matrix):
    """
    Рассчитывает коэффициент диверсификации портфеля.
    
    Parameters:
    weights: вектор весов активов
    cov_matrix: ковариационная матрица
    
    Returns: коэффициент диверсификации
    """
    # Вектор волатильностей
    vol = np.sqrt(np.diag(cov_matrix))
    # Волатильность портфеля
    port_vol = np.sqrt(weights.T @ cov_matrix @ weights)
    # Взвешенная сумма волатильностей
    weighted_vol_sum = weights @ vol
    
    return weighted_vol_sum / port_vol

def maximize_diversification(cov_matrix):
    """
    Находит веса портфеля максимальной диверсификации.
    
    Parameters: cov_matrix: ковариационная матрица
    
    Returns: Оптимальные веса
    """
    n = cov_matrix.shape[0]
    
    # Целевая функция (отрицательный коэффициент диверсификации)
    def objective(w):
        return -portfolio_diversification_ratio(w, cov_matrix)
    
    # Ограничения
    constraints = {'type': 'eq', 'fun': lambda x: np.sum(x) - 1}
    bounds = tuple((0, 1) for _ in range(n))
    
    # Начальное предположение о равных весах
    initial_weights = np.ones(n) / n
    
    # Оптимизация
    result = minimize(objective, initial_weights, method='SLSQP', 
                      bounds=bounds, constraints=constraints)
    
    return result['x']

# Применяем к нашим данным
max_div_weights = maximize_diversification(cov_matrix_annual)

# Выводим результаты
print("Веса портфеля максимальной диверсификации:")
for i, ticker in enumerate(tickers):
    print(f"{ticker}: {max_div_weights[i]:.4f}")

# Рассчитываем ожидаемую доходность и риск
max_div_return = portfolio_return(max_div_weights, annual_returns)
max_div_volatility = portfolio_volatility(max_div_weights, cov_matrix_annual)
max_div_sharpe = (max_div_return - risk_free_rate) / max_div_volatility

print(f"Ожидаемая годовая доходность: {max_div_return:.4f}")
print(f"Ожидаемый годовой риск (волатильность): {max_div_volatility:.4f}")
print(f"Коэффициент Шарпа: {max_div_sharpe:.4f}")

Веса портфеля максимальной диверсификации:
AAPL: 0.2789
MSFT: 0.1446
GOOG: 0.2235
AMZN: 0.1230
VCNIX: 0.2300
Ожидаемая годовая доходность: 0.1292
Ожидаемый годовой риск (волатильность): 0.2200
Коэффициент Шарпа: 0.4966

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

Функция maximize_diversification делает следующее:

  1. Принимает ковариационную матрицу активов;
  2. Определяет целевую функцию: минимизировать отрицательный коэффициент диверсификации (objective) -> максимизировать сам коэффициент;
  3. Накладывает ограничения: сумма весов = 1, каждый вес ∈ [0, 1] (нет коротких позиций);
  4. Использует оптимизатор SLSQP для нахождения оптимальных весов.

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

Портфель диверсификации по Risk-Parity (Риск-паритет)

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

В классическом равновзвешенном портфеле (equal-weight) все активы имеют одинаковый вес, но их вклад в общий риск может сильно различаться из-за разной волатильности и корреляций. А в портфеле risk parity мы добиваемся, чтобы:

Risk Contribution(i) = const,∀i

то есть каждый актив вносит одинаковый вклад в общий риск портфеля.

Вот как можно реализовать расчет риск-паритета разных акций в Python:

import numpy as np
from scipy.optimize import minimize

def risk_contributions(weights, cov_matrix):
    """
    Рассчитывает нормализованные вклады риска активов в портфель.
    
    Parameters:
    weights: вектор весов активов
    cov_matrix: ковариационная матрица
    
    Returns: вектор нормализованных вкладов риска
    """
    port_vol = np.sqrt(weights.T @ cov_matrix @ weights)
    mrc = (cov_matrix @ weights) / port_vol
    rc = weights * mrc
    nrc = rc / port_vol  # Нормализованный вклад риска
    return nrc

def target_risk_parity(weights, cov_matrix):
    """
    Целевая функция для оптимизации портфеля risk parity.
    
    Parameters:
    weights: вектор весов активов
    cov_matrix: ковариационная матрица
    
    Returns: значение целевой функции (чем меньше, тем лучше)
    """
    nrc = risk_contributions(weights, cov_matrix)
    n = len(weights)
    target_nrc = np.ones(n) / n  # Все активы должны вносить равный вклад
    return np.sum((nrc - target_nrc) ** 2)

def risk_parity_portfolio(cov_matrix):
    """
    Находит веса портфеля с равными вкладами риска.
    
    Parameters:
    cov_matrix: ковариационная матрица
    
    Returns: оптимальные веса
    """
    n = cov_matrix.shape[0]
    
    # Ограничения
    constraints = {'type': 'eq', 'fun': lambda x: np.sum(x) - 1}
    bounds = tuple((0, 1) for _ in range(n))  # Только лонги
    
    # Начальное приближение — равные веса
    initial_weights = np.ones(n) / n
    
    # Оптимизация
    result = minimize(target_risk_parity, initial_weights,
                      args=(cov_matrix,),
                      method='SLSQP',
                      bounds=bounds,
                      constraints=constraints)
    
    return result['x']

# Рассчитываем веса
rp_weights = risk_parity_portfolio(cov_matrix_annual)

# Выводим веса
print("Веса портфеля риск-паритет:")
for i, ticker in enumerate(tickers):
    print(f"{ticker}: {rp_weights[i]:.4f}")

# Рассчитываем годовые ожидаемые доходности
annual_returns = log_returns.mean() * 252  # Аннуализируем средние дневные доходности

def portfolio_return(weights, returns):
    return np.sum(returns * weights)

def portfolio_volatility(weights, cov_matrix):
    return np.sqrt(weights.T @ cov_matrix @ weights)

# Параметры
risk_free_rate = 0.02  # Безрисковая ставка

# Метрики портфеля
rp_return = portfolio_return(rp_weights, annual_returns)
rp_volatility = portfolio_volatility(rp_weights, cov_matrix_annual)
rp_sharpe = (rp_return - risk_free_rate) / rp_volatility

# Выводим метрики
print(f"\nОжидаемая годовая доходность: {rp_return:.4f}")
print(f"Ожидаемая годовая волатильность (риск): {rp_volatility:.4f}")
print(f"Коэффициент Шарпа: {rp_sharpe:.4f}")

Веса портфеля риск-паритет:
AAPL: 0.2173
MSFT: 0.2252
GOOG: 0.1902
AMZN: 0.1699
VCNIX: 0.1973

Ожидаемая годовая доходность: 0.1452
Ожидаемая годовая волатильность (риск): 0.2193
Коэффициент Шарпа: 0.5710

Таблица сравнения подходов Maximum Diversification и Risk Parity:

Критерий Maximum Diversification Risk Parity
Цель Максимизировать коэффициент диверсификации Сделать равным вклад риска каждого актива
Подход Использует волатильности и корреляции Основан на балансе риска
Устойчивость Хорошо работает при низкой корреляции Устойчив к ошибкам в прогнозах доходности
Чувствителен к Корреляциям Волатильностям активов
Преимущество Более высокая диверсификация Более сбалансированное распределение риска

Заключение

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

Ключевые выводы:

  1. Ковариация показывает направление совместного движения активов, но ее значение сложно интерпретировать, потому что она зависима от масштаба данных;
  2. Корреляция, будучи нормализованной величиной, упрощает сравнение взаимосвязей между разными парами активов, но измеряет только линейную зависимость;
  3. Динамический характер корреляций, особенно их рост в периоды кризисов, подчеркивает важность постоянного мониторинга и адаптации портфеля;
  4. Теория Марковица демонстрирует, как использование ковариационной матрицы помогает оптимизировать риск и доходность, однако она не всегда эффективна;
  5. Альтернативные подходы, такие как портфель максимальной диверсификации или риск-паритет, могут быть более устойчивыми к ошибкам в прогнозах;
  6. Непараметрические методы (ранговая корреляция, копулы) и анализ хвостовой зависимости позволяют точнее оценивать риски в экстремальных условиях.

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