Разработка системы Monte Carlo симуляции портфеля
Monte Carlo симуляция генерирует тысячи возможных будущих траекторий портфеля на основе статистических свойств рыночных данных. Это позволяет оценить распределение возможных исходов, а не просто одно «ожидаемое» значение.
Принцип Monte Carlo симуляции
Вместо «портфель вырастет на X%» говорим: «с вероятностью 70% портфель вырастет на 20–80%, с вероятностью 15% потеряет 10–30%».
Базовый GBM (Geometric Brownian Motion):
import numpy as np
def simulate_gbm(initial_price, mu, sigma, days, n_simulations=10000):
"""
mu: среднедневная доходность
sigma: среднедневная волатильность
"""
dt = 1 # 1 день
random_returns = np.random.normal(
mu * dt,
sigma * np.sqrt(dt),
(n_simulations, days)
)
# Кумулятивные returns
cumulative = np.cumprod(1 + random_returns, axis=1)
price_paths = initial_price * cumulative
return price_paths
Мультиактивная симуляция с корреляциями
Для портфеля важно учитывать корреляции между активами:
from numpy.linalg import cholesky
def simulate_correlated_portfolio(initial_prices, means, cov_matrix,
days=365, n_sims=10000):
"""
initial_prices: dict {symbol: price}
means: dict {symbol: daily_mean}
cov_matrix: ковариационная матрица daily returns
"""
n_assets = len(initial_prices)
symbols = list(initial_prices.keys())
# Cholesky decomposition для генерации коррелированных случайных чисел
L = cholesky(cov_matrix)
portfolio_paths = []
for _ in range(n_sims):
# Генерируем некоррелированные нормальные случайные числа
z = np.random.standard_normal((days, n_assets))
# Применяем корреляционную структуру
correlated_returns = z @ L.T
# Добавляем drifts (mean returns)
daily_means = np.array([means[s] for s in symbols])
actual_returns = correlated_returns + daily_means
# Эволюция цен
prices = np.zeros((days + 1, n_assets))
prices[0] = [initial_prices[s] for s in symbols]
for t in range(1, days + 1):
prices[t] = prices[t-1] * (1 + actual_returns[t-1])
# Стоимость портфеля (предполагаем равные веса)
weights = np.ones(n_assets) / n_assets
portfolio_value = (prices * weights).sum(axis=1)
portfolio_paths.append(portfolio_value)
return np.array(portfolio_paths)
Улучшенные модели returns
GBM предполагает нормальное распределение returns. Для крипты это неверно — есть fat tails и кластеризация волатильности.
Student's t-distribution для fat tails:
from scipy.stats import t as t_dist
def simulate_fat_tail(mu, sigma, df, n_sims, days):
returns = t_dist.rvs(df=df, loc=mu, scale=sigma, size=(n_sims, days))
return np.cumprod(1 + returns, axis=1)
GARCH(1,1) conditional volatility: волатильность завтрашнего дня зависит от сегодняшней доходности и вчерашней волатильности.
from arch import arch_model
def fit_garch_and_simulate(returns_history, n_sims=10000, horizon=252):
model = arch_model(returns_history * 100, vol='GARCH', p=1, q=1)
result = model.fit(disp='off')
# Симулируем из fitted GARCH модели
simulations = result.forecast(horizon=horizon, method='simulation',
simulations=n_sims)
return simulations.simulations.values
Анализ результатов симуляции
def analyze_simulation_results(portfolio_paths, initial_value,
confidence_levels=[0.05, 0.25, 0.50, 0.75, 0.95]):
final_values = portfolio_paths[:, -1]
# Распределение финальных значений
percentiles = {f'p{int(c*100)}': np.percentile(final_values, c*100)
for c in confidence_levels}
# Вероятность убытка
prob_loss = (final_values < initial_value).mean()
# Expected return и std
returns = (final_values - initial_value) / initial_value
# VaR из симуляции
var_95 = np.percentile(final_values - initial_value, 5)
cvar_95 = (final_values - initial_value)[
final_values - initial_value <= var_95
].mean()
# Max drawdown distribution
max_drawdowns = []
for path in portfolio_paths:
peaks = np.maximum.accumulate(path)
drawdowns = (peaks - path) / peaks
max_drawdowns.append(drawdowns.max())
return {
'percentiles': percentiles,
'prob_loss': prob_loss,
'expected_return': returns.mean(),
'return_std': returns.std(),
'var_95': var_95,
'cvar_95': cvar_95,
'avg_max_drawdown': np.mean(max_drawdowns),
'worst_max_drawdown': np.max(max_drawdowns)
}
Visualisation результатов
Fan chart: показывает диапазон возможных траекторий портфеля. Центральная линия — медиана (50-й перцентиль). Более тёмные зоны — вероятные диапазоны (25–75%), светлее — редкие (5–95%).
Return distribution histogram: гистограмма финальных returns из всех симуляций. Нормальное vs реальное распределение.
Drawdown distribution: гистограмма максимальных drawdown по симуляциям. Показывает вероятность различных уровней просадки.
Применения в управлении портфелем
Вероятность достижения цели: «какова вероятность что портфель вырастет на 50% за год при текущей стратегии?»
Сравнение стратегий: запустить симуляцию для двух стратегий, сравнить распределения outcomes.
Optimal rebalancing frequency: симулировать портфель с разными частотами ребалансировки, выбрать оптимальную.
Capital allocation: сколько выделить на рискованную vs консервативную стратегию для достижения target при допустимом риске?
Технический стек
Python (numpy, scipy, arch для GARCH), Numba для ускорения симуляций (JIT компиляция даёт 10–50x ускорение), pandas для обработки данных, matplotlib/plotly для визуализации fan charts. 10,000 симуляций на 252 дня занимают < 1 секунды с Numba.
Разрабатываем систему Monte Carlo симуляции с поддержкой GBM, fat-tail distributions и GARCH моделью, мультиактивной симуляцией с корреляциями, анализом результатов (percentiles, VaR, drawdown distribution) и интерактивными visualisations.







