Портфельная теория Марковица для криптовалют: от нуля до героя
Создаем оптимальные крипто-портфели с помощью Python - потому что YOLO это не стратегия
Введение: Почему вашему крипто-портфелю нужна математика (а не только вайбы)
Привет, крипто-дегены! 👋
Помните тот раз, когда вы вложили всю свою стопку в DOGE из-за твита Илона? Или когда вы в панике распродали всё во время последнего краха? Да, мы все там были. Сегодня мы поговорим о том, что может спасти ваш портфель (и ваше психическое здоровье): портфельная теория Марковица.
Гарри Марковиц буквально получил Нобелевскую премию за эту штуку в 1990 году. Основная идея? Вы можете математически оптимизировать свой портфель, чтобы получить наилучшую возможную доходность для любого заданного уровня риска. Это как иметь GPS для ваших инвестиций вместо вождения с завязанными глазами.
Основная концепция: Риск против доходности (вечный танец)
Прежде чем мы погрузимся в код, давайте поймем, с чем мы имеем дело:
- Ожидаемая доходность: Сколько денег вы ожидаете заработать
- Риск (волатильность): Насколько сильно колеблется стоимость вашего портфеля
- Корреляция: Насколько похоже движутся разные активы
Магия происходит, когда вы комбинируете активы, которые не движутся в идеальной синхронизации. Когда биткоин падает, возможно, некоторые DeFi-токены держатся лучше. Это диверсификация работает на вас.
Настройка нашей Python-среды
Прежде всего - подготовим наши инструменты:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.optimize import minimize
import yfinance as yf
import warnings
warnings.filterwarnings('ignore')
plt.style.use('dark_background')
sns.set_palette("husl")
Уровень 1: Детские шаги - простая портфельная математика
Начнем с основ. Мы рассчитаем доходность и риск для простого портфеля из 2 активов.
def get_crypto_data(symbols, period="1y"):
"""
Получение данных криптовалют с Yahoo Finance
symbols: список символов криптовалют (например, ['BTC-USD', 'ETH-USD'])
period: временной период для данных
"""
data = yf.download(symbols, period=period)['Adj Close']
return data
crypto_symbols = ['BTC-USD', 'ETH-USD']
prices = get_crypto_data(crypto_symbols)
returns = prices.pct_change().dropna()
print("Предварительный просмотр дневных доходностей:")
print(returns.head())
Теперь давайте рассчитаем некоторые базовые метрики портфеля:
def portfolio_performance(weights, returns):
"""
Расчет доходности и волатильности портфеля
weights: массив весов портфеля
returns: датафрейм доходностей активов
"""
portfolio_return = np.sum(returns.mean() * weights) * 252
portfolio_vol = np.sqrt(np.dot(weights.T, np.dot(returns.cov() * 252, weights)))
return portfolio_return, portfolio_vol
weights_5050 = np.array([0.5, 0.5])
ret_5050, vol_5050 = portfolio_performance(weights_5050, returns)
print(f"Портфель 50/50:")
print(f"Ожидаемая годовая доходность: {ret_5050:.2%}")
print(f"Годовая волатильность: {vol_5050:.2%}")
print(f"Коэффициент Шарпа: {ret_5050/vol_5050:.3f}")
Уровень 2: Серьезный подход - граница эффективности
Теперь мы готовим! Граница эффективности показывает нам все возможные оптимальные портфели. Каждая точка представляет лучшую возможную доходность для заданного уровня риска.
def generate_random_portfolios(returns, num_portfolios=10000):
"""
Генерация случайных комбинаций портфелей
"""
num_assets = len(returns.columns)
results = np.zeros((4, num_portfolios))
for i in range(num_portfolios):
weights = np.random.random(num_assets)
weights /= np.sum(weights) # Нормализуем к сумме 1
portfolio_return, portfolio_vol = portfolio_performance(weights, returns)
sharpe_ratio = portfolio_return / portfolio_vol
results[0,i] = portfolio_return
results[1,i] = portfolio_vol
results[2,i] = sharpe_ratio
results[3,i:] = weights
return results
results = generate_random_portfolios(returns)
portfolio_results = pd.DataFrame({
'Returns': results[0],
'Volatility': results[1],
'Sharpe_Ratio': results[2]
})
plt.figure(figsize=(12, 8))
scatter = plt.scatter(portfolio_results['Volatility'],
portfolio_results['Returns'],
c=portfolio_results['Sharpe_Ratio'],
cmap='viridis', alpha=0.6)
plt.colorbar(scatter, label='Коэффициент Шарпа')
plt.xlabel('Волатильность (Риск)')
plt.ylabel('Ожидаемая доходность')
plt.title('Граница эффективности - случайные портфели')
plt.show()
Уровень 3: Мастер оптимизации - поиск идеального портфеля
Случайная выборка - это весело, но мы хотим математически оптимальное решение. Время достать тяжелую артиллерию - scipy optimization!
def negative_sharpe_ratio(weights, returns, risk_free_rate=0.02):
"""
Расчет отрицательного коэффициента Шарпа (мы минимизируем это)
"""
portfolio_return, portfolio_vol = portfolio_performance(weights, returns)
sharpe = (portfolio_return - risk_free_rate) / portfolio_vol
return -sharpe
def minimize_volatility(weights, returns):
"""
Расчет волатильности портфеля (мы минимизируем это)
"""
_, portfolio_vol = portfolio_performance(weights, returns)
return portfolio_vol
def portfolio_return_objective(weights, returns):
"""
Расчет доходности портфеля (мы максимизируем это)
"""
portfolio_return, _ = portfolio_performance(weights, returns)
return -portfolio_return # Отрицательное, потому что мы минимизируем
def optimize_portfolio(returns, objective='sharpe', target_return=None):
"""
Оптимизация портфеля на основе различных целей
"""
num_assets = len(returns.columns)
constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1}) # Веса суммируются в 1
bounds = tuple((0, 1) for _ in range(num_assets)) # Без коротких продаж
initial_guess = num_assets * [1. / num_assets]
if objective == 'sharpe':
result = minimize(negative_sharpe_ratio, initial_guess,
args=(returns,), method='SLSQP',
bounds=bounds, constraints=constraints)
elif objective == 'min_vol':
result = minimize(minimize_volatility, initial_guess,
args=(returns,), method='SLSQP',
bounds=bounds, constraints=constraints)
elif objective == 'target_return':
constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1},
{'type': 'eq', 'fun': lambda x: portfolio_performance(x, returns)[0] - target_return})
result = minimize(minimize_volatility, initial_guess,
args=(returns,), method='SLSQP',
bounds=bounds, constraints=constraints)
return result
max_sharpe = optimize_portfolio(returns, 'sharpe')
min_vol = optimize_portfolio(returns, 'min_vol')
print("🎯 Портфель с максимальным коэффициентом Шарпа:")
for i, symbol in enumerate(crypto_symbols):
print(f"{symbol}: {max_sharpe.x[i]:.3f}")
ret_sharpe, vol_sharpe = portfolio_performance(max_sharpe.x, returns)
print(f"Доходность: {ret_sharpe:.2%}, Волатильность: {vol_sharpe:.2%}")
print(f"Коэффициент Шарпа: {ret_sharpe/vol_sharpe:.3f}\n")
print("🛡️ Портфель с минимальной волатильностью:")
for i, symbol in enumerate(crypto_symbols):
print(f"{symbol}: {min_vol.x[i]:.3f}")
ret_minvol, vol_minvol = portfolio_performance(min_vol.x, returns)
print(f"Доходность: {ret_minvol:.2%}, Волатильность: {vol_minvol:.2%}")
Уровень 4: Многоактивное безумие - реальный крипто-портфель
Давайте расширим это до настоящего крипто-портфеля с несколькими активами:
crypto_portfolio = ['BTC-USD', 'ETH-USD', 'BNB-USD', 'ADA-USD', 'SOL-USD', 'DOT-USD']
prices_multi = get_crypto_data(crypto_portfolio, period="2y")
returns_multi = prices_multi.pct_change().dropna()
correlation_matrix = returns_multi.corr()
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='RdYlBu_r', center=0)
plt.title('Матрица корреляций криптовалютных активов')
plt.show()
def efficient_frontier(returns, num_portfolios=50):
"""
Расчет границы эффективности
"""
ret_range = np.linspace(returns.mean().min()*252, returns.mean().max()*252, num_portfolios)
efficient_portfolios = []
for target_ret in ret_range:
try:
result = optimize_portfolio(returns, 'target_return', target_ret)
if result.success:
ret, vol = portfolio_performance(result.x, returns)
efficient_portfolios.append([ret, vol, result.x])
except:
continue
return np.array(efficient_portfolios)
efficient_port = efficient_frontier(returns_multi)
plt.figure(figsize=(14, 10))
random_results = generate_random_portfolios(returns_multi, 5000)
plt.scatter(random_results[1], random_results[0],
c=random_results[2], cmap='viridis', alpha=0.3, s=10)
if len(efficient_port) > 0:
plt.plot(efficient_port[:,1], efficient_port[:,0], 'r-', linewidth=3, label='Граница эффективности')
max_sharpe_multi = optimize_portfolio(returns_multi, 'sharpe')
min_vol_multi = optimize_portfolio(returns_multi, 'min_vol')
ret_sharpe_multi, vol_sharpe_multi = portfolio_performance(max_sharpe_multi.x, returns_multi)
ret_minvol_multi, vol_minvol_multi = portfolio_performance(min_vol_multi.x, returns_multi)
plt.scatter(vol_sharpe_multi, ret_sharpe_multi, marker='*', color='gold', s=500, label='Макс. Шарп')
plt.scatter(vol_minvol_multi, ret_minvol_multi, marker='*', color='red', s=500, label='Мин. волатильность')
plt.colorbar(label='Коэффициент Шарпа')
plt.xlabel('Волатильность (Риск)')
plt.ylabel('Ожидаемая доходность')
plt.title('Оптимизация многоактивного крипто-портфеля')
plt.legend()
plt.show()
print("🚀 Оптимальные многоактивные распределения:")
print("\nПортфель с максимальным коэффициентом Шарпа:")
sharpe_weights = pd.Series(max_sharpe_multi.x, index=crypto_portfolio).sort_values(ascending=False)
for asset, weight in sharpe_weights.items():
if weight > 0.01: # Показываем только значимые распределения
print(f"{asset}: {weight:.1%}")
print(f"\nМетрики портфеля:")
print(f"Ожидаемая доходность: {ret_sharpe_multi:.1%}")
print(f"Волатильность: {vol_sharpe_multi:.1%}")
print(f"Коэффициент Шарпа: {ret_sharpe_multi/vol_sharpe_multi:.2f}")
Уровень 5: Продвинутые техники - Black-Litterman и Risk Parity
Для настоящих ниндзя оптимизации портфелей, давайте реализуем некоторые продвинутые техники:
def risk_parity_portfolio(returns):
"""
Портфель Risk Parity - каждый актив вносит равный вклад в риск портфеля
"""
def risk_contribution(weights, cov_matrix):
portfolio_vol = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights)))
marginal_contrib = np.dot(cov_matrix, weights) / portfolio_vol
contrib = weights * marginal_contrib
return contrib
def risk_parity_objective(weights, cov_matrix):
contrib = risk_contribution(weights, cov_matrix)
target_contrib = np.ones(len(weights)) / len(weights)
return np.sum((contrib - target_contrib)**2)
num_assets = len(returns.columns)
cov_matrix = returns.cov() * 252
constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1})
bounds = tuple((0.001, 1) for _ in range(num_assets))
initial_guess = num_assets * [1. / num_assets]
result = minimize(risk_parity_objective, initial_guess,
args=(cov_matrix,), method='SLSQP',
bounds=bounds, constraints=constraints)
return result
risk_parity_result = risk_parity_portfolio(returns_multi)
print("⚖️ Портфель Risk Parity:")
rp_weights = pd.Series(risk_parity_result.x, index=crypto_portfolio).sort_values(ascending=False)
for asset, weight in rp_weights.items():
print(f"{asset}: {weight:.1%}")
ret_rp, vol_rp = portfolio_performance(risk_parity_result.x, returns_multi)
print(f"\nМетрики Risk Parity:")
print(f"Ожидаемая доходность: {ret_rp:.1%}")
print(f"Волатильность: {vol_rp:.1%}")
print(f"Коэффициент Шарпа: {ret_rp/vol_rp:.2f}")
def backtest_portfolio(weights, prices):
"""
Простой бэктест производительности портфеля
"""
returns = prices.pct_change().dropna()
portfolio_returns = (returns * weights).sum(axis=1)
cumulative_returns = (1 + portfolio_returns).cumprod()
total_return = cumulative_returns.iloc[-1] - 1
annualized_return = (1 + total_return) ** (252 / len(portfolio_returns)) - 1
annualized_vol = portfolio_returns.std() * np.sqrt(252)
sharpe_ratio = annualized_return / annualized_vol
max_dd = (cumulative_returns / cumulative_returns.expanding().max() - 1).min()
return {
'total_return': total_return,
'annualized_return': annualized_return,
'annualized_volatility': annualized_vol,
'sharpe_ratio': sharpe_ratio,
'max_drawdown': max_dd,
'cumulative_returns': cumulative_returns
}
strategies = {
'Макс. Шарп': max_sharpe_multi.x,
'Мин. волатильность': min_vol_multi.x,
'Risk Parity': risk_parity_result.x,
'Равные веса': np.ones(len(crypto_portfolio)) / len(crypto_portfolio)
}
plt.figure(figsize=(14, 8))
for name, weights in strategies.items():
backtest_results = backtest_portfolio(weights, prices_multi)
plt.plot(backtest_results['cumulative_returns'], label=f"{name} (Шарп: {backtest_results['sharpe_ratio']:.2f})")
plt.title('Бэктесты стратегий портфеля')
plt.xlabel('Дата')
plt.ylabel('Кумулятивные доходности')
plt.legend()
plt.yscale('log')
plt.grid(True, alpha=0.3)
plt.show()
performance_summary = pd.DataFrame()
for name, weights in strategies.items():
results = backtest_portfolio(weights, prices_multi)
performance_summary[name] = [
f"{results['annualized_return']:.1%}",
f"{results['annualized_volatility']:.1%}",
f"{results['sharpe_ratio']:.2f}",
f"{results['max_drawdown']:.1%}"
]
performance_summary.index = ['Годовая доходность', 'Годовая волатильность', 'Коэффициент Шарпа', 'Макс. просадка']
print("\n📊 Сводка производительности стратегий:")
print(performance_summary)
Проверка реальностью: о чем не говорит Марковиц
Прежде чем вы полностью погрузитесь в математическую оптимизацию, вот несколько жестких истин о криптовалютах:
1. Прошлые результаты ≠ Будущие результаты Криптовалютный рынок молод и хаотичен. Те корреляции, которые вы рассчитали? Они могут перевернуться за ночь, когда изменятся регулирования или произойдет следующий крупный хак.
2. Транзакционные расходы имеют значение Ребалансировка вашего портфеля стоит денег. В DeFi комиссии за газ могут съесть ваш обед. Учитывайте это в своей стратегии.
3. Проблемы с ликвидностью Не все криптовалюты одинаково ликвидны. Тот токен с малой капитализацией может выглядеть отлично в вашей оптимизации, но попробуйте продать его во время краха.
4. Изменения режимов Криптовалютные рынки имеют разные "режимы" - бычьи рынки, медвежьи рынки, боковые рынки. То, что работает в одном, может не работать в другом.
Практические советы по реализации
def practical_portfolio_rebalancing(target_weights, current_weights, threshold=0.05):
"""
Ребалансировка только когда веса отклоняются за пределы порога
"""
weight_diff = np.abs(target_weights - current_weights)
needs_rebalancing = np.any(weight_diff > threshold)
if needs_rebalancing:
print("🔄 Необходима ребалансировка!")
for i, (target, current) in enumerate(zip(target_weights, current_weights)):
if abs(target - current) > threshold:
print(f"Актив {i}: {current:.1%} → {target:.1%}")
else:
print("✅ Портфель в пределах допуска, ребалансировка не нужна")
return needs_rebalancing
current_allocation = np.array([0.35, 0.25, 0.15, 0.10, 0.10, 0.05])
target_allocation = max_sharpe_multi.x
practical_portfolio_rebalancing(target_allocation, current_allocation)
Заключение: Ваш набор инструментов для оптимизации портфеля
Теперь у вас есть полный набор инструментов для оптимизации крипто-портфеля:
- Базовые расчеты для риска и доходности
- Визуализация границы эффективности
- Математическая оптимизация для различных целей
- Продвинутые стратегии как risk parity
- Фреймворк бэктестинга для валидации ваших стратегий
- Практические соображения для реальной реализации
Ключевые выводы
- Диверсификация - это бесплатный обед - единственный бесплатный обед в инвестировании
- Оптимизируйте на основе вашей терпимости к риску - максимальный Шарп не всегда лучший для вас
- Ребалансируйте систематически, но не торгуйте чрезмерно
- Оставайтесь скромными - модели это инструменты, а не хрустальные шары
- Начинайте просто и добавляйте сложность по мере обучения
Помните: В криптовалютах даже лучшие математические модели не могут предсказать, когда Илон твитнет о Dogecoin или когда следующая биржа будет взломана. Используйте портфельную теорию как основу, но всегда держите немного пороха сухим и никогда не инвестируйте больше, чем можете позволить себе потерять.
Теперь идите и оптимизируйте ответственно! 🚀
Дисклеймер: Это только для образовательных целей. Не является финансовым советом. DYOR. Рынок криптовалют высоко волатилен и рискован. Прошлые результаты не гарантируют будущих результатов.
Дополнительное чтение
- Оригинальная работа Марковица (1952)
- Современная портфельная теория на Investopedia
- Количественное управление портфелем на Python
Репозиторий кода
Весь код из этого туториала доступен на GitHub: https://github.com/suenot/markowitz
Удачной оптимизации! 📈
MarketMaker.cc Team
Количественные исследования и стратегии