Разработка системы стресс-тестирования портфеля
Стресс-тестирование — проверка портфеля в условиях экстремальных сценариев, которые выходят за рамки обычного статистического риска. VaR показывает «нормальный» риск. Стресс-тест показывает что происходит при настоящих катастрофах.
Типы стресс-тестов
Historical stress scenarios: применяем исторически случившиеся кризисы к текущему портфелю.
Hypothetical scenarios: моделируем возможные, но не произошедшие события.
Sensitivity analysis: изменяем один параметр (цена, волатильность, корреляция) и смотрим на P&L.
Reverse stress testing: ищем сценарии, при которых портфель теряет критическую сумму.
Исторические сценарии для крипторынка
CRYPTO_STRESS_SCENARIOS = {
'covid_march_2020': {
'BTC': -0.50, # -50% за неделю
'ETH': -0.60,
'DEFAULT': -0.55,
'USDT': 0.0,
'duration_days': 7
},
'china_ban_may_2021': {
'BTC': -0.53,
'ETH': -0.60,
'DEFAULT': -0.65,
'duration_days': 30
},
'luna_terra_collapse_2022': {
'LUNA': -0.99,
'UST': -0.95,
'BTC': -0.30,
'ETH': -0.35,
'DEFAULT': -0.45,
'USDC': 0.0,
'duration_days': 7
},
'ftx_collapse_2022': {
'FTT': -0.97,
'SOL': -0.60,
'BTC': -0.25,
'ETH': -0.28,
'DEFAULT': -0.35,
'duration_days': 14
},
'full_crypto_winter': {
'BTC': -0.80, # 2022 год
'ETH': -0.82,
'DEFAULT': -0.90,
'duration_days': 365
}
}
Реализация стресс-тестирования
class StressTester:
def __init__(self, scenarios):
self.scenarios = scenarios
def run_scenario(self, positions, current_prices, scenario_name):
scenario = self.scenarios[scenario_name]
total_pnl = 0
position_results = {}
for symbol, qty in positions.items():
current_price = current_prices.get(symbol, 0)
shock = scenario.get(symbol, scenario.get('DEFAULT', 0))
stressed_price = current_price * (1 + shock)
pnl = qty * (stressed_price - current_price)
position_results[symbol] = {
'shock': shock,
'pnl': pnl,
'current_value': qty * current_price,
'stressed_value': qty * stressed_price
}
total_pnl += pnl
return {
'scenario': scenario_name,
'total_pnl': total_pnl,
'position_results': position_results
}
def run_all_scenarios(self, positions, current_prices):
results = {}
for scenario_name in self.scenarios:
results[scenario_name] = self.run_scenario(
positions, current_prices, scenario_name
)
# Сортируем по worst case
return sorted(results.values(), key=lambda x: x['total_pnl'])
Sensitivity Analysis
Анализ чувствительности: как меняется P&L при изменении одного фактора?
def sensitivity_analysis(positions, current_prices, factor='price',
range_pct=(-0.50, 0.50), steps=20):
"""
Изменяем цены всех активов на X% и смотрим P&L
"""
results = []
for shock in np.linspace(range_pct[0], range_pct[1], steps):
total_pnl = 0
for symbol, qty in positions.items():
price = current_prices[symbol]
total_pnl += qty * price * shock
results.append({'shock_pct': shock * 100, 'pnl': total_pnl})
return results
Correlation stress: при кризисе все активы коррелируют сильнее. Что если корреляция всех активов = 0.95?
def correlation_stress_test(portfolio_returns_history, stressed_correlation=0.95):
cov_matrix = portfolio_returns_history.cov()
# Заменяем все off-diagonal элементы на stressed_correlation
std_devs = np.sqrt(np.diag(cov_matrix))
stressed_cov = np.outer(std_devs, std_devs) * stressed_correlation
np.fill_diagonal(stressed_cov, np.diag(cov_matrix)) # оставляем дисперсии
return stressed_cov
Reverse Stress Testing
Обратная задача: найти сценарий, при котором потери достигают критического уровня (например, 20% капитала).
from scipy.optimize import minimize
def reverse_stress_test(positions, current_prices, target_loss,
max_shock_per_asset=0.99):
"""
Минимальный суммарный шок, при котором потери = target_loss
"""
symbols = list(positions.keys())
current_values = [positions[s] * current_prices[s] for s in symbols]
def portfolio_loss(shocks):
return sum(v * s for v, s in zip(current_values, shocks))
constraints = [
{'type': 'eq', 'fun': lambda x: portfolio_loss(x) - (-target_loss)}
]
bounds = [(-max_shock_per_asset, 0) for _ in symbols]
# Минимизируем L2 норму шоков (ищем наименьший шок)
result = minimize(
lambda x: np.sum(x**2),
x0=np.full(len(symbols), -0.1),
constraints=constraints,
bounds=bounds
)
return dict(zip(symbols, result.x))
Корреляции во время кризиса
Одно из ключевых наблюдений: в кризис корреляции между активами резко растут. Диверсификация «схлопывается» именно тогда, когда она нужна больше всего.
Стресс-тест «correlation crisis»: заменяем матрицу ковариаций на «stressed» версию с высокими корреляциями и пересчитываем VaR.
Отчётность
Stress Test Report — ежемесячный или при значительных изменениях портфеля:
- Таблица результатов по всем сценариям
- Worst case P&L в абсолютных значениях и % от капитала
- Breakdown: какие позиции привели к наибольшим потерям
- Sensitivity curve (график P&L от % шока)
- Reverse stress: при каком шоке достигается критический убыток
Визуализация: tornado chart (вклад каждой позиции в стресс-убыток), waterfall chart (суммирование потерь по позициям), sensitivity curve chart.
Разрабатываем систему стресс-тестирования с библиотекой исторических сценариев, sensitivity analysis, reverse stress testing и автоматической генерацией отчётов.







