Розробка системи стрес-тестування портфеля
Стрес-тестування — перевірка портфеля в умовах екстремальних сценаріїв, що виходять за межи звичайного статистичного ризику. VaR показує «нормальний» ризик. Стрес-тест показує, що відбувається під час справжніх катастроф.
Типи стрес-тестів
Історичні сценарії стресу: застосовуємо історично сталі кризи до поточного портфеля.
Гіпотетичні сценарії: моделюємо можливі, але не сталі події.
Аналіз чутливості: змінюємо один параметр (ціну, волатильність, кореляцію) і спостерігаємо P&L.
Зворотне стрес-тестування: шукаємо сценарії, при яких портфель втрачає критичну суму.
Історичні сценарії для крипторинку
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
)
# Сортуємо за гіршим випадком
return sorted(results.values(), key=lambda x: x['total_pnl'])
Аналіз чутливості
Аналіз чутливості: як змінюється 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
Стрес кореляцій: під час кризи всі активи корелюють сильніше. Що якщо кореляція всіх активів = 0.95?
def correlation_stress_test(portfolio_returns_history, stressed_correlation=0.95):
cov_matrix = portfolio_returns_history.cov()
# Замінюємо всі позадіагональні елементи на 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
Зворотне стрес-тестування
Зворотна задача: знайти сценарій, при якому втрати досягають критичного рівня (наприклад, 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.
Звітність
Звіт стрес-тесту — щомісячно або при значних змінах портфеля:
- Таблиця результатів для всіх сценаріїв
- Гірший випадок P&L в абсолютних значеннях і % від капіталу
- Розбір: які позиції привели до найбільших втрат
- Крива чутливості (графік P&L від % шоку)
- Зворотний стрес: при якому шоці досягається критичний убиток
Візуалізація: діаграма tornado (вклад кожної позиції в стрес-убиток), водоспад графік (накопичення втрат за позиціями), графік кривої чутливості.
Розробляємо систему стрес-тестування з бібліотекою історичних сценаріїв, аналізом чутливості, зворотним стрес-тестуванням та автоматичною генерацією звітів.







