В мире финансовых инвестиций необходимо уметь объективно оценивать эффективность портфеля и отдельных активов. Ключевые метрики: Альфа, Бета и коэффициент Шарпа — являются фундаментальными инструментами для принятия взвешенных инвестиционных решений. Эти показатели помогают численно оценить риск, доходность и эффективность вложений относительно рынка.
В этой статье я детально рассмотрю природу этих важнейших коэффициентов, проанализирую их математическую основу и покажу, как рассчитывать их с помощью Python. Мы рассмотрим не только теоретические аспекты, но и практическое применение в современном количественном анализе. Я покажу, как профессионалы используют эти метрики для оценки эффективности портфелей, стратегий и отдельных активов, а также предложу несколько нетривиальных подходов к их интерпретации.
Понятие риска и доходности в инвестициях
Прежде чем погрузиться в расчеты и интерпретацию показателей альфа, бета и коэффициента Шарпа, необходимо разобраться с фундаментальными понятиями риска и доходности. Эти два параметра лежат в основе всей современной теории портфельных инвестиций.
Доходность: виды и методы расчета
Доходность — это относительный показатель, отражающий изменение стоимости актива или портфеля за определенный период времени. При работе с финансовыми данными обычно рассматривают два основных вида доходности:
Абсолютная доходность показывает процентное изменение стоимости актива за выбранный период. Она рассчитывается по формуле:
где:
- Pt — цена актива в момент времени t;
- Pt-1 — цена актива в предыдущий момент времени t-1;
- Rt — доходность за период.
Логарифмическая доходность имеет ряд математических преимуществ и часто используется в количественном анализе. Она рассчитывается как:
Основное преимущество логарифмической доходности заключается в том, что она позволяет просто суммировать доходности за последовательные периоды, что удобно для анализа временных рядов.
Когда я работаю с реальными данными, обычно использую логарифмические доходности для алгоритмического анализа, поскольку они обладают лучшими статистическими свойствами, особенно при анализе длинных временных рядов.
Риск: волатильность и ее измерение
Риск в финансовой математике чаще всего отождествляется с волатильностью, то есть с изменчивостью цены актива. Основной статистической мерой риска является стандартное отклонение доходности:
где:
- σ — стандартное отклонение, мера волатильности (риск);
- r(t) — доходность актива в момент времени t;
- T — количество наблюдений (например, дней торгов).
Стандартное отклонение показывает, насколько в среднем отклоняется доходность от своего среднего значения. Чем выше стандартное отклонение, тем более волатильным (и, следовательно, более рисковым) считается актив.
При анализе финансовых временных рядов я заметил, что волатильность часто демонстрирует кластеризацию — периоды высокой волатильности сменяются периодами низкой волатильности. Это одна из причин, почему простые статистические модели часто оказываются недостаточными для адекватного описания рыночного риска.
Коэффициент Бета: измерение систематического риска
Бета — один из ключевых показателей в современной теории портфельных инвестиций, который позволяет оценить систематический (рыночный) риск актива. Это мера чувствительности доходности актива к изменениям доходности рыночного индекса или бенчмарка.
Что такое Бета и как ее интерпретировать
Бета-коэффициент показывает, насколько сильно изменяется доходность актива при изменении доходности рынка. Интерпретация значений бета следующая:
- β = 1: доходность актива изменяется пропорционально рынку;
- β > 1: актив более волатилен, чем рынок (агрессивная инвестиция);
- β < 1: актив менее волатилен, чем рынок (консервативная инвестиция);
- β < 0: доходность актива имеет тенденцию двигаться в направлении, противоположном рынку.
В процессе работы с различными портфелями я обнаружил, что бета не является постоянной величиной и может существенно меняться со временем, особенно в периоды рыночной турбулентности. Этот факт часто упускается из виду при построении инвестиционных моделей.
Математическая формула расчета Беты
Бета-коэффициент рассчитывается как отношение ковариации доходности актива и рынка к дисперсии доходности рынка:
где:
- β(i) — бета-коэффициент актива i;
- r(i) — вектор доходностей актива i;
- r(m) — вектор доходностей рынка (например, индекс SP500);
- Cov(ri, rm) — ковариация между доходностью актива и рынка;
- Var(rm) — дисперсия доходности рыночного портфеля.
Другое выражение для расчета беты через коэффициент корреляции:
где:
- p(i,m) — коэффициент корреляции между доходностью актива i и рыночной доходностью m;
- σ(i) — стандартное отклонение доходности актива i (его волатильность);
- σ(m) — стандартное отклонение доходнности рынка.
Расчет коэффициента Бета в Python
Давайте рассмотрим, как рассчитать бета-коэффициент с использованием Python. Для этого мы будем использовать библиотеки pandas, numpy и Alpha Vantage API для получения исторических данных. Для этого в коде в строке alpha_vantage_api_key нужно ввести свой API токен. Получить его можно по этой ссылке https://www.alphavantage.co/support/#api-key . А для определения тикеров нужных активов можно воспользоваться следующей ссылкой: https://www.alphavantage.co/query?function=SYMBOL_SEARCH&keywords=tesco&apikey=demo , нужно заменить tesco на интересующую вас акцию, бонд или фьючерс, а в apikey подставить свой ключ.
Alpha Vantage — мощная альтернатива Yahoo Finance. Однако нужно учитывать, что на бесплатном тарифе доступно до 25 выгрузок данных в сутки на API.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
import pandas_datareader.data as web
import time
# Задаем даты, тикер актива, тикер рынка, API ключ
start_date = '2022-09-01'
end_date = '2025-05-01'
stock_ticker = 'GOOG'
market_ticker = 'VCNIX' #NASDAQ-100
# Замените строку ниже на ваш собственный API-ключ Alpha Vantage
alpha_vantage_api_key = '_____________'
# Загрузка данных
def get_stock_data(ticker, start, end):
for attempt in range(5):
try:
print(f"Загружаем данные для {ticker}...")
data = web.DataReader(
ticker,
'av-daily',
start=start,
end=end,
api_key=alpha_vantage_api_key
)
return data['close']
except Exception as e:
print(f"Ошибка при загрузке {ticker}: {e}. Повтор через 5 сек...")
time.sleep(5)
raise ConnectionError(f"Не удалось загрузить данные для {ticker}")
# Получаем данные по акции и по рынку
try:
stock_prices = get_stock_data(stock_ticker, start_date, end_date)
market_prices = get_stock_data(market_ticker, start_date, end_date)
except ConnectionError as e:
print("Ошибка:", e)
exit()
# Объединяем в один DataFrame и удаляем NaN
data = pd.DataFrame({
stock_ticker: stock_prices,
market_ticker: market_prices
}).dropna()
if len(data) < 2: print("Недостаточно данных для анализа.") exit() # Расчет доходностей (Close -> log returns)
returns = np.log(data / data.shift(1)).dropna()
# Расчет бета
def calculate_beta(stock_returns, market_returns):
beta_cov = np.cov(stock_returns, market_returns)[0, 1] / np.var(market_returns)
slope, intercept, r_value, _, _ = stats.linregress(market_returns, stock_returns)
return beta_cov, slope, r_value**2
try:
stock_returns = returns[stock_ticker]
market_returns = returns[market_ticker]
beta_cov, beta_reg, r_squared = calculate_beta(stock_returns, market_returns)
except Exception as e:
print("Ошибка в расчётах:", e)
exit()
# Вывод результатов
print(f"\nБета коэффициент для {stock_ticker} (метод ковариации): {beta_cov:.4f}")
print(f"Бета коэффициент для {stock_ticker} (метод регрессии): {beta_reg:.4f}")
print(f"Коэффициент детерминации R²: {r_squared:.4f}")
# Визуализация
plt.figure(figsize=(10, 6))
plt.scatter(market_returns, stock_returns, alpha=0.6, label='Дневные доходности')
plt.plot(market_returns, beta_reg * market_returns + np.mean(stock_returns - beta_reg * market_returns),
color='red', label=f'Линия регрессии (β = {beta_reg:.2f})')
plt.title(f'Бета для {stock_ticker} относительно {market_ticker}')
plt.xlabel(f'Доходность рынка ({market_ticker})')
plt.ylabel(f'Доходность акции ({stock_ticker})')
plt.ylim(-0.5, 0.5)
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
Бета коэффициент для GOOG (метод ковариации): 0.5944
Бета коэффициент для GOOG (метод регрессии): 0.5935
Коэффициент детерминации R²: 0.3053
Рис. 1: Визуализация коэффициента Бета для Google относительно Nasdaq-100
В этом коде мы реализуем два метода расчета бета-коэффициента:
- Метод ковариации/дисперсии, соответствующий математической формуле;
- Метод линейной регрессии, где бета является угловым коэффициентом (slope) регрессионной прямой.
Оба метода должны давать очень близкие результаты при достаточном объеме данных. Регрессионный метод дополнительно предоставляет значение R-квадрат, которое показывает, какая доля вариации доходности актива объясняется вариацией доходности рынка.
Динамический расчет беты с использованием скользящего окна
В реальной аналитике часто важно отслеживать изменение беты во времени. Рассмотрим реализацию расчета в Python динамической беты с использованием скользящего окна:
# Расчет динамической беты с использованием скользящего окна
def calculate_rolling_beta(stock_returns, market_returns, window=252):
# Создаем DataFrame с доходностями
df = pd.DataFrame({'stock': stock_returns, 'market': market_returns})
# Рассчитываем ковариацию и дисперсию с использованием скользящего окна
rolling_cov = df['stock'].rolling(window=window).cov(df['market'])
rolling_var = df['market'].rolling(window=window).var()
# Рассчитываем бету
rolling_beta = rolling_cov / rolling_var
return rolling_beta
# Расчет динамической беты
window_size = 252 # примерно 1 год торговых дней
rolling_beta = calculate_rolling_beta(returns[stock_ticker], returns[market_ticker], window=window_size)
# Визуализация динамической беты
plt.figure(figsize=(12, 6))
rolling_beta.plot()
plt.axhline(y=1, color='r', linestyle='--')
plt.title(f'Динамический бета-коэффициент для {stock_ticker} (окно {window_size} дней)')
plt.ylabel('Бета')
plt.grid(True)
plt.tight_layout()
plt.show()
Рис. 2: График изменения коэффициента Бета в динамике для акций Google с окном 252 дней
Этот код позволяет наблюдать эволюцию бета-коэффициента во времени. Горизонтальная красная линия при β = 1 помогает визуально определить периоды, когда актив был более или менее волатильным относительно рынка.
Коэффициент Альфа: оценка избыточной доходности
Альфа — один из наиболее важных показателей для оценки эффективности управления инвестициями. Этот коэффициент показывает, насколько доходность актива или портфеля превышает ожидаемую доходность с учетом принятого риска.
Что такое Альфа и как ее интерпретировать
Альфа-коэффициент представляет собой разницу между фактической доходностью актива и его теоретической ожидаемой доходностью, рассчитанной на основе модели ценообразования капитальных активов (CAPM — Capital Asset Pricing Model).
Интерпретация значений Альфа:
- α > 0: актив/портфель демонстрирует доходность выше ожидаемой с учетом риска (переоценен);
- α = 0: актив/портфель показывает доходность, точно соответствующую его уровню риска (справедливо оценен);
- α < 0: актив/портфель демонстрирует доходность ниже ожидаемой с учетом риска (недооценен).
В своей практике я заметил, что устойчиво положительная альфа на длительных промежутках времени — довольно редкое явление. Большинство активно управляемых фондов на горизонте 5-10 лет показывают нейтральную или даже отрицательную альфу после учета комиссий и издержек.
Математическая формула расчета Альфы
Альфа-коэффициент рассчитывается на основе модели CAPM следующим образом:
где:
- a(i) — альфа актива i, показывает избыточную доходность относительно ожидаемой;
- r(i) — средняя фактическая доходность актива за период;
- r(f) — безрисковая ставка (например, доходность по краткосрочным государственным облигациям);
- b(i) — бета-коэффициент актива, измеряющий его чувствительность к рыночным движениям;
- r(m) — средняя доходность рыночного портфеля (например, индекс SP500)
Альфа также может быть получена как свободный член (intercept) в регрессионной модели, где зависимая переменная — избыточная доходность актива (над безрисковой ставкой), а независимая переменная — избыточная доходность рынка:
где:
- r(i) — доходность актива;
- r(f) — безрисковая ставка;
- r(m) — доходность рыночного портфеля;
- b — коэффициент Бета (чувствительность к рынку);
- epsilon — случайная ошибка.
Расчет коэффициента Альфа в Python
Рассмотрим реализацию расчета показателя Альфа с использованием Python.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
import pandas_datareader.data as web
import time
# Параметры
start_date = '2022-09-01'
end_date = '2025-05-01'
stock_ticker = 'GOOG' # Акция
market_ticker = 'VCNIX' #NASDAQ-100
alpha_vantage_api_key = '___________' # Замените на свой ключ
# Безрисковая ставка
risk_free_rate = 0.05 / 252 # Пример: 5% годовых → дневная ставка
# Или можно использовать реальную ставку из FRED (через DataReader)
# treasury_data = web.DataReader('DGS3MO', 'fred', start=start_date, end=end_date)
# risk_free_rate = float(treasury_data.iloc[-1] / 100 / 252)
# Загрузка данных
def get_stock_data(ticker, start, end):
for attempt in range(5):
try:
print(f"Загружаем данные для {ticker}...")
data = web.DataReader(
ticker,
'av-daily',
start=start,
end=end,
api_key=alpha_vantage_api_key
)
return data['close']
except Exception as e:
print(f"Ошибка при загрузке {ticker}: {e}. Повтор через 5 сек...")
time.sleep(5)
raise ConnectionError(f"Не удалось загрузить данные для {ticker}")
# Получаем данные по акции и рынку
try:
stock_prices = get_stock_data(stock_ticker, start_date, end_date)
market_prices = get_stock_data(market_ticker, start_date, end_date)
except ConnectionError as e:
print("Ошибка:", e)
exit()
# Объединяем в один DataFrame и удаляем NaN
data = pd.DataFrame({
stock_ticker: stock_prices,
market_ticker: market_prices
}).dropna()
if len(data) < 2:
print("Недостаточно данных для анализа.")
exit()
# Расчет логдоходностей
returns = np.log(data / data.shift(1)).dropna()
# Расчет дневной и годовой альфы через регрессию избыточной доходности
def calculate_alpha(stock_returns, market_returns, risk_free_rate):
excess_stock = stock_returns - risk_free_rate
excess_market = market_returns - risk_free_rate
slope, intercept, r_value, p_value, std_err = stats.linregress(excess_market, excess_stock)
alpha_daily = intercept
alpha_annual = alpha_daily * 252 # Аннуализация (252 торговых дня)
return alpha_daily, alpha_annual, slope, r_value**2
# Вычисление альфы
try:
stock_returns = returns[stock_ticker]
market_returns = returns[market_ticker]
alpha_daily, alpha_annual, beta, r_squared = calculate_alpha(
stock_returns, market_returns, risk_free_rate
)
except Exception as e:
print("Ошибка в расчётах:", e)
exit()
# Вывод результатов
print(f"Дневная альфа для {stock_ticker}: {alpha_daily:.6f}")
print(f"Годовая альфа для {stock_ticker}: {alpha_annual:.4f} ({alpha_annual*100:.2f}%)")
print(f"Бета: {beta:.4f}")
print(f"R-квадрат: {r_squared:.4f}")
# Визуализация
excess_stock = stock_returns - risk_free_rate
excess_market = market_returns - risk_free_rate
plt.figure(figsize=(10, 6))
plt.scatter(excess_market, excess_stock, alpha=0.6, label='Избыточная доходность')
plt.plot(np.array([-0.1, 0.1]), beta * np.array([-0.1, 0.1]) + alpha_daily, 'r', label='Линия регрессии')
plt.title(f'Альфа для {stock_ticker} относительно {market_ticker}')
plt.xlabel(f'Избыточная доходность рынка ({market_ticker})')
plt.ylabel(f'Избыточная доходность акции ({stock_ticker})')
plt.ylim(-0.5, 0.5)
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
Дневная альфа для GOOG: 0.000418
Годовая альфа для GOOG: 0.1052 (10.52%)
Бета: 0.5935
R-квадрат: 0.3053
Рис. 3: Визуализация коэффициента Альфа (избыточной доходности) и линии регрессии для акций Google относительно индекса NASDAQ
Дневная альфа для GOOG: 0.000418
Годовая альфа для GOOG: 0.1052 (10.52%)
Бета: 0.5935
R-квадрат: 0.3053
В этом коде мы:
- Рассчитываем избыточную доходность (excess return) как разницу между фактической доходностью и безрисковой ставкой;
- Используем линейную регрессию для получения альфы (свободный член регрессии);
- Аннуализируем полученное значение альфы для лучшей интерпретации.
Динамический расчет Альфы с использованием скользящего окна
Аналогично Бете, Альфа не является константой и может меняться со временем. Давайте также рассмотрим реализацию динамического расчета данного показателя:
# Расчет динамической альфы с использованием скользящего окна
def calculate_rolling_alpha(stock_returns, market_returns, risk_free_rate, window=252):
# Расчёт избыточных доходностей
excess_stock = stock_returns - risk_free_rate
excess_market = market_returns - risk_free_rate
# Объединяем в DataFrame
df = pd.DataFrame({
'excess_stock': excess_stock,
'excess_market': excess_market
}).dropna()
# Проверка достаточности данных
if len(df) < window:
raise ValueError(f"Недостаточно данных для анализа (минимум {window} точек)")
rolling_alpha = []
rolling_beta = []
# Простой цикл по окнам
for i in range(window, len(df) + 1):
window_data = df.iloc[i - window:i]
x = window_data['excess_market']
y = window_data['excess_stock']
# Линейная регрессия
try:
slope, intercept, _, _, _ = stats.linregress(x, y)
except:
slope, intercept = np.nan, np.nan
rolling_alpha.append(intercept)
rolling_beta.append(slope)
# Преобразуем в pandas Series
rolling_alpha_series = pd.Series(rolling_alpha, index=df.index[window-1:])
rolling_beta_series = pd.Series(rolling_beta, index=df.index[window-1:])
# Аннуализируем альфу
rolling_alpha_annual = rolling_alpha_series * 252
return rolling_alpha_series, rolling_alpha_annual, rolling_beta_series
# Расчет динамической альфы
window_size = 252 # примерно 1 год торговых дней
rolling_alpha, rolling_alpha_annual, rolling_beta_from_alpha = calculate_rolling_alpha(
returns[stock_ticker], returns[market_ticker], risk_free_rate, window=window_size
)
# Визуализация динамической альфы
plt.figure(figsize=(12, 6))
rolling_alpha_annual.plot()
plt.axhline(y=0, color='r', linestyle='--')
plt.title(f'Динамическая годовая альфа для {stock_ticker} (окно {window_size} дней)')
plt.ylabel('Альфа (%)')
plt.grid(True)
plt.tight_layout()
plt.show()
Рис. 4: График изменения коэффициента Альфа в динамике для акций Google с окном 252 дней
Этот код позволяет наблюдать изменение альфа-коэффициента во времени. Горизонтальная красная линия при α = 0 помогает визуально определить периоды избыточной доходности (альфа > 0) и недостаточной доходности (альфа < 0).
Для лучшей интерпретируемости мы также можем построить график кумулятивных доходностей сравниваемого актива и рынка. В нашей случае это наложенные друг на друга графики акций Google и Nasdaq-100.
cumulative_returns = (1 + returns).cumprod()
cumulative_returns[[stock_ticker, market_ticker]].plot(figsize=(12, 6))
plt.title('Сравнение кумулятивной доходности за период: Акция vs Индекс')
plt.grid(True)
plt.show()
Рис. 5: Сравнение кумулятивной доходности за период: акции Google vs индекс Nasdaq-100
По графику выше видно что акции Google в последние годы часто опережали индекс Nasdaq-100 в доходности, что подтверждает график Альфы и ее положительные значения дневной (0.000418) и годовой (0.1052).
Интересно связана ли лучшая кумулятивная доходность акций Google относительно рынка с большей волатильностью их котировок? Давайте проверим эту гипотезу:
# Расчёт исторической волатильности
def calculate_volatility(returns_series, annualize=True):
volatility = np.std(returns_series, ddof=1)
if annualize:
volatility *= np.sqrt(252)
return volatility
stock_volatility = calculate_volatility(returns[stock_ticker])
market_volatility = calculate_volatility(returns[market_ticker])
print(f"Годовая волатильность {stock_ticker}: {stock_volatility:.4f}")
print(f"Годовая волатильность {market_ticker}: {market_volatility:.4f}")
print(f"Волатильность {stock_ticker} / {market_ticker}: {stock_volatility / market_volatility:.2f}")
Годовая волатильность GOOG: 0.3186
Годовая волатильность VCNIX: 0.2966
Волатильность GOOG / VCNIX: 1.07
Видим что волатильность акций Google и индекса Nasdaq не сильно отличаются друг от друга. Акции техногиганта лишь на 7% более волатильны.
Мы также можем визуализировать периоды когда акции были более волатильны чем индекс.
vol_ratio = rolling_stock_vol / rolling_market_vol
plt.figure(figsize=(12, 4))
vol_ratio.plot()
plt.axhline(y=1.0, color='red', linestyle='--', alpha=0.7)
plt.title('Отношение волатильностей: Акция / Рынок')
plt.ylabel('Volatility Ratio')
plt.grid(True)
plt.tight_layout()
plt.show()
Рис. 6: Отношение волатильностей: Акции Google vs Индекс Nasdaq
Видим что каких-то резких всплесков на горизонте последних лет не было, акции Google стабильно более волатильны, чем индекс Nasdaq.
Коэффициент Шарпа: оценка эффективности с учетом риска
Коэффициент Шарпа — один из наиболее широко используемых показателей эффективности инвестиций, который учитывает не только доходность, но и риск. Этот показатель разработан нобелевским лауреатом Уильямом Шарпом и назван в его честь.
Что такое коэффициент Шарпа и как его интерпретировать
Коэффициент Шарпа измеряет избыточную доходность (превышение над безрисковой ставкой) на единицу общего риска, выраженного стандартным отклонением доходности. Он отвечает на вопрос: «Сколько дополнительной доходности получает инвестор за каждую дополнительную единицу принятого риска?»
Интерпретация значений коэффициента Шарпа:
- SR < 0: инвестиция имеет доходность ниже безрисковой ставки (плохо);
- 0 < SR < 0.5: плохое соотношение риск/доходность;
- 0.5 < SR < 1: приемлемое соотношение риск/доходность;
- 1 < SR < 2: хорошее соотношение риск/доходность;
- 2 < SR < 3: очень хорошее соотношение риск/доходность;
- SR > 3: отличное соотношение риск/доходность (встречается редко).
В теории все предельно просто — инвестору нужно фокусироваться на активах с максимальными коэффициентами Шарпа. Однако на практике, увы, все не так радужно — значения коэффициента Шарпа выше 2 на длительных промежутках времени (год и более) на современном рынке встречаются крайне редко и часто свидетельствуют о возможных проблемах с данными или методологией расчета, а не о действительно исключительной эффективности стратегии.
Математическая формула расчета коэффициента Шарпа
Коэффициент Шарпа рассчитывается по формуле:
где:
- r(p) — средняя доходность портфеля/актива;
- r(f) — безрисковая ставка доходности;
- E( r(p) — r(f) ) — ожидаемая избыточная доходность портфеля над безрисковой ставкой;
- σ(p) — стандартное отклонение доходности портфеля / актива (волатильность / мера риска).
Для аннуализации коэффициента Шарпа, рассчитанного на основе данных с другой периодичностью (например, дневных или месячных), используется формула:
где N — количество периодов в году (например, 252 для торговых дней, 52 недельных данных, 12 для месяцев).
Расчет коэффициента Шарпа в Python
Рассмотрим реализацию расчета коэффициента Шарпа с использованием Python:
# Расчет коэффициента Шарпа для заданного ряда доходностей
def calculate_sharpe_ratio(returns, risk_free_rate, periods_per_year=252):
# Расчет избыточной доходности
excess_returns = returns - risk_free_rate
# Расчет средней избыточной доходности
mean_excess_return = excess_returns.mean()
# Расчет стандартного отклонения доходности
std_dev = returns.std()
# Расчет коэффициента Шарпа
sharpe_ratio = mean_excess_return / std_dev
# Аннуализация коэффициента Шарпа
sharpe_ratio_annual = sharpe_ratio * np.sqrt(periods_per_year)
return sharpe_ratio, sharpe_ratio_annual
# Расчет коэффициента Шарпа для акции и индекса
sharpe_stock, sharpe_stock_annual = calculate_sharpe_ratio(returns[stock_ticker], risk_free_rate)
sharpe_market, sharpe_market_annual = calculate_sharpe_ratio(returns[market_ticker], risk_free_rate)
print(f"Коэффициент Шарпа для {stock_ticker} (дневной): {sharpe_stock:.4f}")
print(f"Коэффициент Шарпа для {stock_ticker} (годовой): {sharpe_stock_annual:.4f}")
print(f"Коэффициент Шарпа для {market_ticker} (дневной): {sharpe_market:.4f}")
print(f"Коэффициент Шарпа для {market_ticker} (годовой): {sharpe_market_annual:.4f}")
Коэффициент Шарпа для GOOG (дневной): 0.0190
Коэффициент Шарпа для GOOG (годовой): 0.3020
Коэффициент Шарпа для VCNIX (дневной): -0.0032
Коэффициент Шарпа для VCNIX (годовой): -0.0513
В этом коде мы:
- Рассчитываем избыточную доходность как разницу между фактической доходностью и безрисковой ставкой;
- Вычисляем среднюю избыточную доходность и стандартное отклонение;
- Рассчитываем коэффициент Шарпа как отношение средней избыточной доходности к стандартному отклонению;
- Аннуализируем полученное значение, умножая на квадратный корень из количества периодов в году.
Динамический расчет коэффициента Шарпа с использованием скользящего окна
Коэффициент Шарпа также может значительно меняться со временем. Рассмотрим реализацию динамического расчета этого коэффициента:
# Расчет динамического коэффициента Шарпа с использованием скользящего окна
def calculate_rolling_sharpe_ratio(returns, risk_free_rate, window=252, periods_per_year=252):
# Расчет избыточной доходности
excess_returns = returns - risk_free_rate
# Расчет скользящего среднего избыточной доходности
rolling_mean = excess_returns.rolling(window=window).mean()
# Расчет скользящего стандартного отклонения
rolling_std = returns.rolling(window=window).std()
# Расчет коэффициента Шарпа
rolling_sharpe = rolling_mean / rolling_std
# Аннуализация коэффициента Шарпа
rolling_sharpe_annual = rolling_sharpe * np.sqrt(periods_per_year)
return rolling_sharpe, rolling_sharpe_annual
# Расчет динамического коэффициента Шарпа
window_size = 252 # примерно 1 год торговых дней
rolling_sharpe, rolling_sharpe_annual = calculate_rolling_sharpe_ratio(
returns[stock_ticker], risk_free_rate, window=window_size
)
# Визуализация динамического коэффициента Шарпа
plt.figure(figsize=(12, 6))
rolling_sharpe_annual.plot()
plt.axhline(y=0, color='r', linestyle='--')
plt.axhline(y=1, color='g', linestyle='--')
plt.title(f'Динамический коэффициент Шарпа для {stock_ticker} (окно {window_size} дней)')
plt.ylabel('Коэффициент Шарпа (годовой)')
plt.grid(True)
plt.tight_layout()
plt.show()
Рис. 7: График динамического коэффициента Шарпа с использованием скользящего окна (252 дней)
Этот код позволяет наблюдать эволюцию коэффициента Шарпа во времени. Горизонтальные линии при значениях 0 и 1 помогают визуально оценить периоды с различным соотношением риска и доходности.
Улучшенные методы расчета и интерпретации
Кроме стандартных методов расчета показателей альфа, бета и коэффициента Шарпа, существуют продвинутые подходы, которые могут дать более точную оценку эффективности инвестиций в реальных рыночных условиях.
Модифицированный коэффициент Шарпа с учетом скошенности и эксцесса
Классический коэффициент Шарпа имеет ограничение: он предполагает нормальное распределение доходностей. Однако на практике распределения часто имеют «тяжелые хвосты» (высокий эксцесс) и асимметрию (скошенность). Модифицированный коэффициент Шарпа учитывает эти особенности:
import pandas as pd
import numpy as np
from scipy.stats import skew, kurtosis
# Расчет классического и модифицированного коэффициентов Шарпа
def calculate_modified_sharpe_ratio(returns, risk_free_rate, periods_per_year=252):
excess_returns = returns - risk_free_rate
mean = excess_returns.mean()
std = returns.std()
s = skew(returns)
k = kurtosis(returns) # Это уже эксцесс над нормальным распределением (Fisher), то есть kurtosis - 3
if std == 0:
return float('nan'), float('nan'), s, k
# Формула Cornish-Fisher (CF-adjusted Sharpe Ratio)
z_cf = mean / std * (1 +
s * (mean / std) / 6 +
k * (mean / std)**2 / 24 -
s**2 * (mean / std)**3 / 36)
# Аннуализация
modified_sharpe_annual = z_cf * np.sqrt(periods_per_year)
return mean / std * np.sqrt(periods_per_year), modified_sharpe_annual, s, k
# Расчет классического и модифицированного коэффициентов Шарпа
results = {}
for ticker in [stock_ticker, market_ticker]:
classic_sharpe, modified_sharpe, skewness, kurtosis_val = calculate_modified_sharpe_ratio(
returns[ticker], risk_free_rate
)
results[ticker] = {
'Классический Шарп': round(classic_sharpe, 4),
'Модифицированный Шарп': round(modified_sharpe, 4),
'Скошенность': round(skewness, 4),
'Эксцесс': round(kurtosis_val, 4)
}
# Вывод результатов
print("\nСравнение классического и модифицированного коэффициентов Шарпа:")
sharpe_df = pd.DataFrame(results).T
print(sharpe_df)
# Визуализация
plt.figure(figsize=(10, 6))
x = np.arange(len(sharpe_df.index))
width = 0.35
plt.bar(x - width/2, sharpe_df['Классический Шарп'], width, label='Классический Шарп', color='skyblue')
plt.bar(x + width/2, sharpe_df['Модифицированный Шарп'], width, label='Модифицированный Шарп', color='salmon')
plt.xticks(x, sharpe_df.index)
plt.title('Сравнение коэффициентов Шарпа (годовых)')
plt.ylabel('Значение коэффициента Шарпа')
plt.legend()
plt.grid(True, axis='y')
plt.tight_layout()
plt.show()
Сравнение классического и модифицированного коэффициентов Шарпа:
Классический Шарп Модифицированный Шарп Скошенность Эксцесс
GOOG 0.3020 0.3018 -0.2227 3.9976
VCNIX -0.0513 -0.0514 -5.0806 72.5633
Модифицированный коэффициент Шарпа использует расширение Корниша-Фишера для учета скошенности и эксцесса распределения доходностей. Это позволяет получить более точную оценку эффективности инвестиций, особенно для стратегий с асимметричным профилем риска.
В примере выше разница между классическим Шарпом и модифицированным крайне мала и составляет всего 0.0001-0.0002. Это связано с тем что в котировках Google, Nasdaq за рассматриваемый период не было сильной положительной или отрицательной скошенности (skewness) и высокого эксцесса (kurtosis). Однако так бывает не всегда. К примеру, в оционных стратегиях или в котировках криптовалют разница между значениями этого показателя бывает весьма значительной.
Условная бета и альфа с учетом рыночной волатильности
Классический расчет беты предполагает, что взаимосвязь между доходностью актива и рынка остается постоянной. Однако исследования показывают, что бета может зависеть от состояния рынка, в частности от его волатильности. Рассмотрим как можно выполнить расчет условной беты с помощью Python, Pandas, Scipy:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
import pandas_datareader.data as web
import time
# Функция расчёта альфы и беты
def calculate_alpha(stock_returns, market_returns, risk_free_rate):
excess_stock = stock_returns - risk_free_rate
excess_market = market_returns - risk_free_rate
if len(excess_stock) < 2 or len(excess_market) < 2: return np.nan, np.nan, np.nan, np.nan slope, intercept, r_value, _, _ = stats.linregress(excess_market, excess_stock) alpha_daily = intercept alpha_annual = alpha_daily * 252 # Аннуализация альфы return alpha_daily, alpha_annual, slope, r_value**2 # Расчет условной беты по квантилям волатильности def calculate_conditional_beta(stock_returns, market_returns, risk_free_rate, quantiles=5): # Скользящая месячная волатильность рынка (21 день) rolling_market_vol = market_returns.rolling(window=21).std() # Удаляем NaN перед разбиением на квантили valid_indices = rolling_market_vol.dropna().index stock_returns = stock_returns[valid_indices] market_returns = market_returns[valid_indices] rolling_market_vol = rolling_market_vol.loc[valid_indices] # Разделение на квантили по волатильности try: vol_quantiles = pd.qcut(rolling_market_vol, q=quantiles, labels=False, duplicates='drop') except ValueError as e: print("Ошибка при разделении на квантили:", e) return pd.DataFrame() # Возвращаем пустой DataFrame # Подготовка таблицы с результатами conditional_betas = pd.DataFrame(index=range(quantiles), columns=['Квантиль волатильности', 'Условная бета', 'Условная альфа (%)', 'R-квадрат']) # Цикл по квантилям for i in range(quantiles): mask = (vol_quantiles == i) if sum(mask) > 30: # Проверяем, достаточно ли данных
try:
alpha, alpha_annual, beta, r_squared = calculate_alpha(
stock_returns[mask], market_returns[mask], risk_free_rate
)
annualized_volatility = rolling_market_vol[mask].mean() * np.sqrt(252) * 100
conditional_betas.loc[i] = [
f'Q{i+1} ({annualized_volatility:.2f}%)',
round(beta, 2),
round(alpha_annual * 100, 2),
round(r_squared, 2)
]
except Exception as e:
print(f"Ошибка при расчёте для квантиля {i}: {e}")
conditional_betas.loc[i] = [f'Q{i+1}', np.nan, np.nan, np.nan]
else:
conditional_betas.loc[i] = [f'Q{i+1}', np.nan, np.nan, np.nan]
return conditional_betas.dropna(how='all').reset_index(drop=True)
# Объединение и проверка
if len(stock_prices) < 2 or len(market_prices) < 2:
print("Недостаточно данных для анализа.")
exit()
data = pd.DataFrame({
stock_ticker: stock_prices,
market_ticker: market_prices
}).dropna()
# Расчет логдоходностей
returns = np.log(data / data.shift(1)).dropna()
# Рассчитываем условную бету
ticker_to_analyze = 'GOOG'
market_ticker = 'VCNIX'
conditional_betas = calculate_conditional_beta(
returns[ticker_to_analyze],
returns[market_ticker],
risk_free_rate,
quantiles=5
)
# Вывод результатов
print(f"\nУсловная бета для {ticker_to_analyze} в зависимости от волатильности рынка:")
print(conditional_betas)
# Визуализация
plt.figure(figsize=(10, 6))
plt.bar(conditional_betas['Квантиль волатильности'], conditional_betas['Условная бета'])
plt.title(f'Условная бета для {ticker_to_analyze} в зависимости от волатильности рынка')
plt.xlabel('Квантиль волатильности рынка (годовая)')
plt.ylabel('Бета-коэффициент')
plt.grid(True, axis='y')
plt.tight_layout()
plt.show()
Условная бета для GOOG в зависимости от волатильности рынка:
Квантиль волатильности Условная бета Условная альфа (%) R-квадрат
0 Q1 (13.17%) 0.82 51.34 0.2
1 Q2 (16.37%) 1.06 2.09 0.42
2 Q3 (18.41%) 1.05 -35.27 0.42
3 Q4 (22.99%) 1.06 7.0 0.48
4 Q5 (50.03%) 0.39 5.4 0.28
Рис. 8: Распределение условной Беты акций Google по квантилям волатильности рынка
Этот код анализирует, как бета-коэффициент меняется в зависимости от уровня рыночной волатильности. Разделяя данные на квантили по волатильности рынка, мы можем оценить, как актив реагирует на различные рыночные условия. Это особенно полезно для управления риском в периоды высокой волатильности.
Заключение
В данной статье мы рассмотрели три ключевых показателя оценки эффективности инвестиций: Альфа, Бета и коэффициент Шарпа. Эти метрики позволяют инвесторам и трейдерам количественно оценивать доходность, риск и рыночную чувствительность активов, что критически важно для принятия обоснованных решений.
Основные выводы:
- Бета отражает систематический риск актива относительно рынка. Мы убедились, что этот коэффициент непостоянен и может значительно варьироваться в зависимости от рыночных условий, особенно в периоды высокой волатильности.
- Альфа показывает избыточную доходность актива после учета рыночного риска. Положительная Альфа свидетельствует о превосходстве стратегии или управления, но на длительных горизонтах устойчиво добиваться такой доходности крайне сложно.
- Коэффициент Шарпа оценивает эффективность инвестиций с поправкой на риск. Его модифицированные версии, учитывающие асимметрию и «тяжелые хвосты» распределения, дают более точную оценку, особенно для активов с нестандартной динамикой.
С помощью Python и библиотек, таких как pandas, numpy и scipy, мы автоматизировали расчет этих показателей, что позволяет:
- Сравнивать активы и портфели по уровню риска и доходности;
- Анализировать динамику Альфы и Беты во времени, выявляя изменения рыночной чувствительности;
- Оптимизировать инвестиционные стратегии, отбирая инструменты с лучшим соотношением риск/доходность.
Понимание того как рассчитываются показатели Альфы, Беты и коэффициента Шарпа в Python открывает широкие возможности для автоматизированного анализа, но интерпретировать результаты следует с учетом рыночного контекста и экономических условий.
Для более точной оценки инвестиций стоит комбинировать эти метрики с другими подходами:
- Анализом drawdown (максимальных просадок);
- Многопериодными стресс-тестами;
- Моделями, учитывающими нелинейные зависимости (например, machine learning).
Грамотное использование этих инструментов поможет не только оценить прошлую эффективность, но и строить более устойчивые инвестиционные стратегии в будущем.