Phân tích rủi ro danh mục đầu tư với Python

2024-03-15 — QuantTrade

Phân tích rủi ro danh mục đầu tư với Python

Giới thiệu

Phân tích rủi ro là một yếu tố quan trọng trong quản lý danh mục đầu tư. Nó không chỉ giúp nhà đầu tư hiểu rõ hơn về mức độ rủi ro mà họ đang gánh chịu mà còn cung cấp cái nhìn sâu sắc về cách cải thiện hiệu suất đầu tư thông qua việc tối ưu hóa danh mục. Python, với hệ sinh thái phong phú các thư viện phân tích dữ liệu, cung cấp một nền tảng mạnh mẽ để thực hiện phân tích rủi ro danh mục đầu tư một cách toàn diện.

Trong bài viết này, chúng ta sẽ khám phá các phương pháp và kỹ thuật để phân tích rủi ro danh mục đầu tư sử dụng Python, từ việc tính toán các chỉ số rủi ro cơ bản đến các phương pháp phân tích nâng cao như tối ưu hóa danh mục và mô phỏng Monte Carlo.

I. Cài đặt và chuẩn bị dữ liệu

1. Cài đặt các thư viện cần thiết

pip install numpy pandas matplotlib seaborn scipy statsmodels yfinance scikit-learn pypfopt

2. Thu thập dữ liệu giá cổ phiếu

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import yfinance as yf
from datetime import datetime, timedelta

# Thiết lập style cho biểu đồ
plt.style.use('seaborn')
sns.set_palette("Set2")

# Định nghĩa danh mục đầu tư mẫu
tickers = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'META', 'TSLA', 'NVDA', 'JPM', 'V', 'JNJ']
weights = np.array([0.15, 0.15, 0.10, 0.10, 0.10, 0.10, 0.10, 0.07, 0.07, 0.06])

# Thiết lập khoảng thời gian
end_date = datetime.now()
start_date = end_date - timedelta(days=365*3)  # 3 năm dữ liệu

# Tải dữ liệu giá đóng cửa
data = yf.download(tickers, start=start_date, end=end_date)['Adj Close']

# Kiểm tra dữ liệu
print(data.head())
print(f"Kích thước dữ liệu: {data.shape}")

3. Tính toán lợi nhuận

# Tính lợi nhuận theo ngày
returns_daily = data.pct_change().dropna()

# Tính lợi nhuận theo tháng
returns_monthly = data.resample('M').last().pct_change().dropna()

# Tính lợi nhuận theo năm
returns_annual = data.resample('Y').last().pct_change().dropna()

# Hiển thị thống kê mô tả
print("\nThống kê mô tả cho lợi nhuận hàng ngày:")
print(returns_daily.describe())

# Vẽ biểu đồ lợi nhuận tích lũy
cumulative_returns = (1 + returns_daily).cumprod()
plt.figure(figsize=(14, 7))
for ticker in tickers:
    plt.plot(cumulative_returns.index, cumulative_returns[ticker], label=ticker)
plt.title('Lợi nhuận tích lũy theo thời gian')
plt.xlabel('Ngày')
plt.ylabel('Lợi nhuận tích lũy')
plt.legend()
plt.grid(True)
plt.show()

II. Phân tích rủi ro cơ bản

1. Tính toán các chỉ số rủi ro và hiệu suất cơ bản

# Tính lợi nhuận trung bình hàng năm
mean_daily_returns = returns_daily.mean()
annual_returns = (1 + mean_daily_returns) ** 252 - 1

# Tính độ lệch chuẩn hàng năm
daily_std = returns_daily.std()
annual_std = daily_std * np.sqrt(252)

# Tính tỷ lệ Sharpe (giả sử lãi suất phi rủi ro là 0.02 hoặc 2%)
risk_free_rate = 0.02
sharpe_ratios = (annual_returns - risk_free_rate) / annual_std

# Tạo DataFrame để hiển thị các chỉ số
performance_metrics = pd.DataFrame({
    'Lợi nhuận hàng năm': annual_returns,
    'Biến động hàng năm': annual_std,
    'Tỷ lệ Sharpe': sharpe_ratios
})

print(performance_metrics.sort_values('Tỷ lệ Sharpe', ascending=False))

# Vẽ biểu đồ so sánh lợi nhuận và rủi ro
plt.figure(figsize=(10, 6))
plt.scatter(annual_std, annual_returns, s=100)
for i, ticker in enumerate(tickers):
    plt.annotate(ticker, (annual_std[i], annual_returns[i]), 
                xytext=(5, 5), textcoords='offset points')
plt.xlabel('Biến động (Rủi ro)')
plt.ylabel('Lợi nhuận kỳ vọng')
plt.title('Lợi nhuận vs. Rủi ro')
plt.grid(True)
plt.show()

2. Phân tích hiệu suất danh mục đầu tư

# Tính lợi nhuận của danh mục đầu tư
portfolio_returns_daily = returns_daily.dot(weights)

# Tính lợi nhuận tích lũy
portfolio_cumulative_returns = (1 + portfolio_returns_daily).cumprod()

# Tính chỉ số hiệu suất của danh mục
portfolio_mean_daily_return = portfolio_returns_daily.mean()
portfolio_annual_return = (1 + portfolio_mean_daily_return) ** 252 - 1

portfolio_daily_std = portfolio_returns_daily.std()
portfolio_annual_std = portfolio_daily_std * np.sqrt(252)

portfolio_sharpe_ratio = (portfolio_annual_return - risk_free_rate) / portfolio_annual_std

print(f"Lợi nhuận hàng năm của danh mục: {portfolio_annual_return:.4f}")
print(f"Biến động hàng năm của danh mục: {portfolio_annual_std:.4f}")
print(f"Tỷ lệ Sharpe của danh mục: {portfolio_sharpe_ratio:.4f}")

# Vẽ biểu đồ lợi nhuận tích lũy của danh mục
plt.figure(figsize=(14, 7))
plt.plot(portfolio_cumulative_returns.index, portfolio_cumulative_returns, 'b', linewidth=2)
plt.title('Lợi nhuận tích lũy của danh mục đầu tư')
plt.xlabel('Ngày')
plt.ylabel('Lợi nhuận tích lũy')
plt.grid(True)
plt.show()

3. Tính toán Value at Risk (VaR) và Conditional Value at Risk (CVaR)

from scipy.stats import norm

# Tính Value at Risk (VaR) sử dụng phương pháp tham số
def parametric_var(returns, level=0.05, investment=1000000):
    """
    Tính VaR sử dụng phương pháp tham số
    
    Parameters:
    -----------
    returns : Pandas Series
        Chuỗi lợi nhuận
    level : float
        Mức độ tin cậy (ví dụ: 0.05 cho 95% VaR)
    investment : float
        Giá trị đầu tư
        
    Returns:
    --------
    float
        Value at Risk
    """
    mean = returns.mean()
    std = returns.std()
    var = norm.ppf(level, mean, std) * investment
    return abs(var)

# Tính Conditional Value at Risk (CVaR) sử dụng phương pháp phi tham số
def historical_cvar(returns, level=0.05, investment=1000000):
    """
    Tính CVaR sử dụng phương pháp phi tham số
    
    Parameters:
    -----------
    returns : Pandas Series
        Chuỗi lợi nhuận
    level : float
        Mức độ tin cậy (ví dụ: 0.05 cho 95% CVaR)
    investment : float
        Giá trị đầu tư
        
    Returns:
    --------
    float
        Conditional Value at Risk
    """
    var_cutoff = returns.quantile(level)
    cvar = returns[returns <= var_cutoff].mean() * investment
    return abs(cvar)

# Tính VaR và CVaR cho danh mục
portfolio_value = 1000000  # Giả sử giá trị danh mục là 1 triệu $
confidence_levels = [0.01, 0.05, 0.1]

print("\nValue at Risk (VaR) của danh mục:")
for level in confidence_levels:
    var = parametric_var(portfolio_returns_daily, level, portfolio_value)
    print(f"VaR {(1-level)*100:.0f}%: ${var:.2f} ({var/portfolio_value*100:.2f}% của danh mục)")

print("\nConditional Value at Risk (CVaR) của danh mục:")
for level in confidence_levels:
    cvar = historical_cvar(portfolio_returns_daily, level, portfolio_value)
    print(f"CVaR {(1-level)*100:.0f}%: ${cvar:.2f} ({cvar/portfolio_value*100:.2f}% của danh mục)")

# Vẽ biểu đồ phân phối lợi nhuận và VaR
plt.figure(figsize=(12, 6))
sns.histplot(portfolio_returns_daily, kde=True, bins=50)

# Thêm VaR vào biểu đồ
colors = ['r', 'g', 'b']
for i, level in enumerate(confidence_levels):
    var_value = portfolio_returns_daily.quantile(level)
    plt.axvline(x=var_value, color=colors[i], 
                linestyle='--', label=f'VaR {(1-level)*100:.0f}%')

plt.title('Phân phối lợi nhuận và Value at Risk')
plt.xlabel('Lợi nhuận hàng ngày')
plt.ylabel('Tần suất')
plt.legend()
plt.grid(True)
plt.show()

III. Phân tích tương quan và đa dạng hóa

1. Ma trận tương quan

# Tính ma trận tương quan
correlation_matrix = returns_daily.corr()

# Vẽ heatmap tương quan
plt.figure(figsize=(12, 10))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', linewidths=0.5, fmt='.2f')
plt.title('Ma trận tương quan giữa các cổ phiếu')
plt.show()

# Tính eigenvalues của ma trận tương quan để đánh giá mức độ đa dạng hóa
from numpy.linalg import eig
eigenvalues, eigenvectors = eig(correlation_matrix)
eigenvalues = sorted(eigenvalues, reverse=True)

# Vẽ biểu đồ eigenvalues
plt.figure(figsize=(10, 6))
plt.bar(range(1, len(eigenvalues) + 1), eigenvalues)
plt.xlabel('Eigenvalue Index')
plt.ylabel('Eigenvalue')
plt.title('Eigenvalues của ma trận tương quan')
plt.grid(True)
plt.show()

# Tính Diversification Ratio
portfolio_variance = (weights.T @ correlation_matrix @ weights) * (daily_std ** 2).values
weighted_avg_variance = np.sum(weights * (daily_std ** 2).values)
diversification_ratio = np.sqrt(weighted_avg_variance / portfolio_variance)

print(f"Tỷ lệ đa dạng hóa của danh mục: {diversification_ratio:.4f}")
print("Tỷ lệ càng cao, mức độ đa dạng hóa càng lớn")

2. Phân tích thành phần chính (PCA)

from sklearn.decomposition import PCA

# Chuẩn hóa dữ liệu lợi nhuận
from sklearn.preprocessing import StandardScaler
scaled_returns = StandardScaler().fit_transform(returns_daily)

# Áp dụng PCA
pca = PCA()
pca.fit(scaled_returns)

# Vẽ biểu đồ giải thích phương sai
explained_variance = pca.explained_variance_ratio_
cumulative_variance = np.cumsum(explained_variance)

plt.figure(figsize=(10, 6))
plt.bar(range(1, len(explained_variance) + 1), explained_variance, alpha=0.7, label='Individual')
plt.step(range(1, len(cumulative_variance) + 1), cumulative_variance, where='mid', label='Cumulative')
plt.xlabel('Thành phần chính')
plt.ylabel('Tỷ lệ phương sai giải thích')
plt.title('Phân tích thành phần chính (PCA)')
plt.legend()
plt.grid(True)
plt.show()

# Vẽ biểu đồ loadings của 2 thành phần chính đầu tiên
pca_loadings = pd.DataFrame(pca.components_.T, columns=[f'PC{i+1}' for i in range(pca.n_components_)], index=tickers)
plt.figure(figsize=(12, 8))
plt.scatter(pca_loadings['PC1'], pca_loadings['PC2'])
for i, ticker in enumerate(tickers):
    plt.annotate(ticker, (pca_loadings['PC1'][i], pca_loadings['PC2'][i]),
                xytext=(5, 5), textcoords='offset points')
plt.axhline(y=0, color='gray', linestyle='-', alpha=0.3)
plt.axvline(x=0, color='gray', linestyle='-', alpha=0.3)
plt.xlabel('Thành phần chính 1')
plt.ylabel('Thành phần chính 2')
plt.title('Loadings PCA')
plt.grid(True)
plt.show()

# Phân tích nguồn rủi ro
print("\nPhân tích nguồn rủi ro dựa trên PCA:")
for i in range(3):  # Chỉ hiển thị 3 thành phần chính đầu tiên
    print(f"\nThành phần chính {i+1} (giải thích {explained_variance[i]*100:.2f}% phương sai):")
    component_loadings = pd.Series(pca.components_[i], index=tickers)
    print(component_loadings.sort_values(ascending=False))

IV. Tối ưu hóa danh mục đầu tư

1. Tối ưu hóa Markowitz (Mean-Variance Optimization)

from pypfopt.efficient_frontier import EfficientFrontier
from pypfopt import risk_models, expected_returns

# Tính ma trận hiệp phương sai
cov_matrix = risk_models.sample_cov(data, frequency=252)

# Ước tính lợi nhuận kỳ vọng sử dụng phương pháp trung bình lịch sử
mu = expected_returns.mean_historical_return(data, frequency=252)

# Tạo đối tượng Efficient Frontier
ef = EfficientFrontier(mu, cov_matrix)

# Tối ưu hóa cho tỷ lệ Sharpe tối đa
ef.max_sharpe(risk_free_rate=risk_free_rate)
optimal_weights = ef.clean_weights()

print("\nTrọng số tối ưu cho tỷ lệ Sharpe tối đa:")
for ticker, weight in optimal_weights.items():
    print(f"{ticker}: {weight:.4f}")

# Tính các chỉ số hiệu suất của danh mục tối ưu
ef_performance = ef.portfolio_performance(risk_free_rate=risk_free_rate)
print(f"\nLợi nhuận kỳ vọng: {ef_performance[0]:.4f}")
print(f"Biến động: {ef_performance[1]:.4f}")
print(f"Tỷ lệ Sharpe: {ef_performance[2]:.4f}")

# Vẽ đường hiệu quả (Efficient Frontier)
from pypfopt.plotting import plot_efficient_frontier
ef = EfficientFrontier(mu, cov_matrix)
fig, ax = plt.subplots(figsize=(10, 6))
ef_max_sharpe = plot_efficient_frontier(ef, ax=ax, show_assets=True)

# Đánh dấu các danh mục đặc biệt
from pypfopt.discrete_allocation import DiscreteAllocation
ef = EfficientFrontier(mu, cov_matrix)
ef.max_sharpe(risk_free_rate=risk_free_rate)
ret_tangent, std_tangent, _ = ef.portfolio_performance()
ax.scatter(std_tangent, ret_tangent, marker='*', s=100, c='r', label='Max Sharpe')

ef = EfficientFrontier(mu, cov_matrix)
ef.min_volatility()
ret_min_vol, std_min_vol, _ = ef.portfolio_performance()
ax.scatter(std_min_vol, ret_min_vol, marker='*', s=100, c='g', label='Min Volatility')

# Thêm danh mục hiện tại vào biểu đồ
current_portfolio_return = portfolio_annual_return
current_portfolio_std = portfolio_annual_std
ax.scatter(current_portfolio_std, current_portfolio_return, marker='*', s=100, c='b', label='Current Portfolio')

ax.set_title('Efficient Frontier')
ax.set_xlabel('Expected Volatility')
ax.set_ylabel('Expected Return')
ax.legend()
plt.show()

# So sánh danh mục hiện tại và danh mục tối ưu
optimal_weights_array = np.array([optimal_weights[ticker] for ticker in tickers])

# Tạo biểu đồ so sánh
plt.figure(figsize=(12, 6))
x = np.arange(len(tickers))
width = 0.35

plt.bar(x - width/2, weights, width, label='Danh mục hiện tại')
plt.bar(x + width/2, optimal_weights_array, width, label='Danh mục tối ưu')

plt.xlabel('Cổ phiếu')
plt.ylabel('Trọng số')
plt.title('So sánh trọng số danh mục hiện tại và danh mục tối ưu')
plt.xticks(x, tickers, rotation=45)
plt.legend()
plt.tight_layout()
plt.show()

2. Tối ưu hóa rủi ro và tạo danh mục phân tán tối ưu

# Tối ưu hóa rủi ro (Risk Parity)
from pypfopt.risk_models import CovarianceShrinkage
from pypfopt import objective_functions, base_optimizer

# Tính ma trận hiệp phương sai sử dụng phương pháp co (Shrinkage)
shrunk_cov = CovarianceShrinkage(data).ledoit_wolf()

# Tạo optimizer
def risk_parity_weights(cov_matrix, risk_target=None):
    """
    Tính trọng số danh mục theo Risk Parity
    
    Parameters:
    -----------
    cov_matrix : pd.DataFrame
        Ma trận hiệp phương sai
    risk_target : float, optional
        Mục tiêu rủi ro
        
    Returns:
    --------
    np.array
        Trọng số danh mục
    """
    n = cov_matrix.shape[0]
    init_guess = np.ones(n) / n
    
    def risk_parity_objective(weights, cov_matrix):
        weights = np.clip(weights, 0.01, 1.0)
        # Chuẩn hóa trọng số
        weights = weights / np.sum(weights)
        
        # Tính đóng góp rủi ro từng thành phần
        portfolio_risk = np.sqrt(np.dot(weights, np.dot(cov_matrix, weights)))
        risk_contribution = weights * (np.dot(cov_matrix, weights)) / portfolio_risk
        
        # Tính tổng bình phương độ lệch
        risk_target_per_asset = portfolio_risk / n
        deviation = np.sum((risk_contribution - risk_target_per_asset) ** 2)
        return deviation
    
    from scipy.optimize import minimize
    constraints = {'type': 'eq', 'fun': lambda w: np.sum(w) - 1}
    bounds = [(0.01, 1) for _ in range(n)]
    
    result = minimize(
        risk_parity_objective, 
        init_guess, 
        args=(cov_matrix,),
        method='SLSQP',
        bounds=bounds,
        constraints=[constraints],
        options={'ftol': 1e-9, 'maxiter': 1000}
    )
    
    # Chuẩn hóa trọng số
    weights = result['x']
    weights = weights / np.sum(weights)
    return weights

# Tính trọng số Risk Parity
risk_parity_w = risk_parity_weights(shrunk_cov.values)
risk_parity_weights_dict = {ticker: weight for ticker, weight in zip(tickers, risk_parity_w)}

print("\nTrọng số danh mục Risk Parity:")
for ticker, weight in risk_parity_weights_dict.items():
    print(f"{ticker}: {weight:.4f}")

# Tính hiệu suất danh mục Risk Parity
risk_parity_returns = returns_daily.dot(risk_parity_w)
risk_parity_annual_return = (1 + risk_parity_returns.mean()) ** 252 - 1
risk_parity_annual_std = risk_parity_returns.std() * np.sqrt(252)
risk_parity_sharpe = (risk_parity_annual_return - risk_free_rate) / risk_parity_annual_std

print(f"\nHiệu suất danh mục Risk Parity:")
print(f"Lợi nhuận kỳ vọng: {risk_parity_annual_return:.4f}")
print(f"Biến động: {risk_parity_annual_std:.4f}")
print(f"Tỷ lệ Sharpe: {risk_parity_sharpe:.4f}")

# Vẽ biểu đồ so sánh danh mục
plt.figure(figsize=(12, 8))

# Vẽ biểu đồ trọng số
plt.subplot(2, 1, 1)
width = 0.25
x = np.arange(len(tickers))

plt.bar(x - width, weights, width, label='Danh mục hiện tại')
plt.bar(x, optimal_weights_array, width, label='Danh mục Max Sharpe')
plt.bar(x + width, risk_parity_w, width, label='Danh mục Risk Parity')

plt.xlabel('Cổ phiếu')
plt.ylabel('Trọng số')
plt.title('So sánh trọng số các danh mục đầu tư')
plt.xticks(x, tickers, rotation=45)
plt.legend()

# Vẽ biểu đồ rủi ro/lợi nhuận
plt.subplot(2, 1, 2)
plt.scatter(portfolio_annual_std, portfolio_annual_return, s=100, label='Danh mục hiện tại')
plt.scatter(std_tangent, ret_tangent, s=100, label='Danh mục Max Sharpe')
plt.scatter(risk_parity_annual_std, risk_parity_annual_return, s=100, label='Danh mục Risk Parity')

plt.xlabel('Biến động (Rủi ro)')
plt.ylabel('Lợi nhuận kỳ vọng')
plt.title('So sánh rủi ro/lợi nhuận các danh mục đầu tư')
plt.grid(True)
plt.legend()

plt.tight_layout()
plt.show()

V. Phân tích rủi ro nâng cao

1. Phân tích nhân tố (Factor Analysis)

import pandas_datareader.data as web
from statsmodels.regression.linear_model import OLS

# Tải dữ liệu nhân tố Fama-French 3 nhân tố
try:
    ff_data = web.DataReader('F-F_Research_Data_Factors_daily', 'famafrench', 
                             start=start_date, end=end_date)[0]
    ff_data = ff_data / 100  # Chuyển từ phần trăm sang thập phân
    ff_data.rename(columns={'Mkt-RF': 'MKT'}, inplace=True)
    
    # Đảm bảo các ngày trùng khớp
    common_dates = returns_daily.index.intersection(ff_data.index)
    returns_aligned = returns_daily.loc[common_dates]
    ff_aligned = ff_data.loc[common_dates]
    
    # Thực hiện hồi quy nhân tố cho từng cổ phiếu
    factor_exposures = {}
    
    for ticker in tickers:
        # Tính excess return (lợi nhuận vượt trội so với lãi suất phi rủi ro)
        excess_return = returns_aligned[ticker] - ff_aligned['RF']
        
        # Hồi quy lên 3 nhân tố
        X = ff_aligned[['MKT', 'SMB', 'HML']]
        X = sm.add_constant(X)
        model = OLS(excess_return, X).fit()
        
        # Lưu các hệ số
        factor_exposures[ticker] = {
            'alpha': model.params['const'],
            'beta': model.params['MKT'],
            'smb': model.params['SMB'],
            'hml': model.params['HML'],
            'r_squared': model.rsquared
        }
    
    # Tạo DataFrame từ kết quả
    factor_df = pd.DataFrame.from_dict(factor_exposures, orient='index')
    print("\nPhân tích nhân tố (Fama-French 3 Factor Model):")
    print(factor_df)
    
    # Vẽ biểu đồ so sánh beta
    plt.figure(figsize=(12, 6))
    plt.bar(factor_df.index, factor_df['beta'])
    plt.axhline(y=1.0, color='r', linestyle='--')
    plt.xlabel('Cổ phiếu')
    plt.ylabel('Beta (Độ nhạy thị trường)')
    plt.title('Độ nhạy thị trường (Beta) của các cổ phiếu')
    plt.xticks(rotation=45)
    plt.grid(True)
    plt.show()
    
    # Vẽ biểu đồ so sánh phơi nhiễm SMB và HML
    plt.figure(figsize=(12, 6))
    x = np.arange(len(tickers))
    width = 0.35
    
    plt.bar(x - width/2, factor_df['smb'], width, label='SMB (Size)')
    plt.bar(x + width/2, factor_df['hml'], width, label='HML (Value)')
    
    plt.xlabel('Cổ phiếu')
    plt.ylabel('Độ nhạy nhân tố')
    plt.title('Phơi nhiễm nhân tố Size và Value')
    plt.xticks(x, tickers, rotation=45)
    plt.legend()
    plt.grid(True)
    plt.tight_layout()
    plt.show()
    
except:
    print("Không thể tải dữ liệu Fama-French. Bỏ qua phân tích nhân tố.")

2. Phân tích độ nhạy và stress testing

# Phân tích độ nhạy với các thay đổi lớn trên thị trường
def stress_test(returns, weights, scenarios):
    """
    Thực hiện stress test trên danh mục đầu tư
    
    Parameters:
    -----------
    returns : pd.DataFrame
        DataFrame chứa lợi nhuận lịch sử
    weights : np.array
        Trọng số danh mục
    scenarios : dict
        Dictionary mô tả các kịch bản stress test
        
    Returns:
    --------
    pd.DataFrame
        Kết quả stress test
    """
    results = {}
    
    for scenario_name, scenario_params in scenarios.items():
        # Copy dữ liệu lợi nhuận
        scenario_returns = returns.copy()
        
        # Áp dụng kịch bản
        for ticker, shock in scenario_params.items():
            if ticker in scenario_returns.columns:
                scenario_returns[ticker] = scenario_returns[ticker] + shock
        
        # Tính lợi nhuận danh mục
        portfolio_return = scenario_returns.dot(weights).sum()
        results[scenario_name] = portfolio_return * 100  # Chuyển sang phần trăm
    
    return pd.Series(results)

# Định nghĩa một số kịch bản stress test
stress_scenarios = {
    'Market Crash (-15%)': {ticker: -0.15 for ticker in tickers},
    'Tech Selloff': {'AAPL': -0.2, 'MSFT': -0.2, 'GOOGL': -0.2, 'AMZN': -0.2, 'META': -0.2, 'TSLA': -0.2, 'NVDA': -0.2},
    'Financial Crisis': {'JPM': -0.25, 'V': -0.15},
    'Healthcare Boom': {'JNJ': 0.15},
    'Tech Rally': {'AAPL': 0.15, 'MSFT': 0.15, 'GOOGL': 0.15, 'AMZN': 0.15, 'META': 0.15, 'TSLA': 0.15, 'NVDA': 0.15}
}

# Thực hiện stress test
stress_results = stress_test(returns_daily, weights, stress_scenarios)
print("\nKết quả Stress Testing (% thay đổi danh mục):")
print(stress_results)

# Vẽ biểu đồ kết quả stress test
plt.figure(figsize=(12, 6))
colors = ['r' if x < 0 else 'g' for x in stress_results]
plt.bar(stress_results.index, stress_results, color=colors)
plt.axhline(y=0, color='black', linestyle='-')
plt.xlabel('Kịch bản')
plt.ylabel('Lợi nhuận danh mục (%)')
plt.title('Stress Testing: Tác động lên danh mục đầu tư')
plt.xticks(rotation=45)
plt.grid(True)
plt.tight_layout()
plt.show()

3. Mô phỏng Monte Carlo

# Thực hiện mô phỏng Monte Carlo để dự đoán giá trị danh mục trong tương lai
def monte_carlo_simulation(returns, weights, initial_investment=1000000, sim_count=1000, days=252, percentiles=[0.05, 0.25, 0.5, 0.75, 0.95]):
    """
    Thực hiện mô phỏng Monte Carlo
    
    Parameters:
    -----------
    returns : pd.DataFrame
        DataFrame chứa lợi nhuận lịch sử
    weights : np.array
        Trọng số danh mục
    initial_investment : float
        Giá trị đầu tư ban đầu
    sim_count : int
        Số lượng mô phỏng
    days : int
        Số ngày mô phỏng
    percentiles : list
        Các mức phân vị cần tính
        
    Returns:
    --------
    pd.DataFrame, pd.DataFrame
        Kết quả mô phỏng và thống kê
    """
    # Tính các thông số thống kê
    mean_daily_returns = returns.dot(weights).mean()
    cov_matrix = returns.cov()
    
    # Thực hiện mô phỏng
    results = np.zeros((days, sim_count))
    for sim in range(sim_count):
        # Mô phỏng một đường dẫn
        path = [initial_investment]
        for day in range(days):
            # Tính lợi nhuận một ngày sử dụng phân phối chuẩn đa biến
            daily_return = np.random.multivariate_normal(
                mean=returns.mean().values, 
                cov=cov_matrix.values
            ).dot(weights)
            
            # Cập nhật giá trị danh mục
            path.append(path[-1] * (1 + daily_return))
        
        # Lưu kết quả
        results[:, sim] = path[1:]
    
    # Tính các phân vị
    percentile_results = np.percentile(results, [p*100 for p in percentiles], axis=1)
    
    # Tạo DataFrame kết quả
    sim_results = pd.DataFrame(results, columns=[f'Sim_{i}' for i in range(sim_count)])
    percentile_df = pd.DataFrame(percentile_results.T, columns=[f'{p*100:.0f}%' for p in percentiles])
    
    return sim_results, percentile_df

# Thực hiện mô phỏng
mc_results, mc_percentiles = monte_carlo_simulation(returns_daily, weights)

# Vẽ biểu đồ kết quả mô phỏng
plt.figure(figsize=(12, 6))

# Vẽ 100 đường mô phỏng đầu tiên (để tránh quá rối)
for i in range(100):
    plt.plot(mc_results.index, mc_results[f'Sim_{i}'], 'b-', alpha=0.05)

# Vẽ các đường phân vị
plt.plot(mc_results.index, mc_percentiles['5%'], 'r--', linewidth=2, label='5%')
plt.plot(mc_results.index, mc_percentiles['50%'], 'g-', linewidth=2, label='50% (Trung vị)')
plt.plot(mc_results.index, mc_percentiles['95%'], 'r--', linewidth=2, label='95%')

plt.xlabel('Ngày')
plt.ylabel('Giá trị danh mục ($)')
plt.title('Mô phỏng Monte Carlo: Giá trị danh mục trong 1 năm')
plt.legend()
plt.grid(True)
plt.show()

# Tính xác suất lỗ vốn
final_values = mc_results.iloc[-1, :]
loss_prob = (final_values < 1000000).mean() * 100

print(f"\nKết quả mô phỏng Monte Carlo (1000 mô phỏng, 252 ngày):")
print(f"Xác suất lỗ vốn: {loss_prob:.2f}%")
print(f"Giá trị trung bình sau 1 năm: ${final_values.mean():.2f}")
print(f"Giá trị tồi nhất (5%): ${mc_percentiles['5%'].iloc[-1]:.2f}")
print(f"Giá trị tốt nhất (95%): ${mc_percentiles['95%'].iloc[-1]:.2f}")

VI. Kết luận và khuyến nghị đầu tư

1. Tóm tắt phân tích

# Tạo bảng tóm tắt các danh mục
summary_data = {
    'Danh mục': ['Hiện tại', 'Max Sharpe', 'Risk Parity'],
    'Lợi nhuận kỳ vọng': [portfolio_annual_return, ret_tangent, risk_parity_annual_return],
    'Biến động': [portfolio_annual_std, std_tangent, risk_parity_annual_std],
    'Tỷ lệ Sharpe': [portfolio_sharpe_ratio, (ret_tangent - risk_free_rate) / std_tangent, risk_parity_sharpe],
    'VaR (95%)': [
        parametric_var(portfolio_returns_daily, 0.05, 1000000) / 1000000,
        parametric_var(returns_daily.dot(optimal_weights_array), 0.05, 1000000) / 1000000,
        parametric_var(returns_daily.dot(risk_parity_w), 0.05, 1000000) / 1000000
    ],
    'Tỷ lệ đa dạng hóa': [
        diversification_ratio,
        np.nan,  # Tính sau
        np.nan   # Tính sau
    ]
}

# Tính tỷ lệ đa dạng hóa cho các danh mục khác
optimal_portfolio_variance = (optimal_weights_array.T @ correlation_matrix.values @ optimal_weights_array) * (daily_std ** 2).values
optimal_weighted_avg_variance = np.sum(optimal_weights_array * (daily_std ** 2).values)
optimal_diversification_ratio = np.sqrt(optimal_weighted_avg_variance / optimal_portfolio_variance)

risk_parity_portfolio_variance = (risk_parity_w.T @ correlation_matrix.values @ risk_parity_w) * (daily_std ** 2).values
risk_parity_weighted_avg_variance = np.sum(risk_parity_w * (daily_std ** 2).values)
risk_parity_diversification_ratio = np.sqrt(risk_parity_weighted_avg_variance / risk_parity_portfolio_variance)

summary_data['Tỷ lệ đa dạng hóa'][1] = optimal_diversification_ratio
summary_data['Tỷ lệ đa dạng hóa'][2] = risk_parity_diversification_ratio

summary_df = pd.DataFrame(summary_data)
summary_df.set_index('Danh mục', inplace=True)

# Định dạng số thập phân
summary_df['Lợi nhuận kỳ vọng'] = summary_df['Lợi nhuận kỳ vọng'].map('{:.2%}'.format)
summary_df['Biến động'] = summary_df['Biến động'].map('{:.2%}'.format)
summary_df['Tỷ lệ Sharpe'] = summary_df['Tỷ lệ Sharpe'].map('{:.2f}'.format)
summary_df['VaR (95%)'] = summary_df['VaR (95%)'].map('{:.2%}'.format)
summary_df['Tỷ lệ đa dạng hóa'] = summary_df['Tỷ lệ đa dạng hóa'].map('{:.2f}'.format)

print("\nTóm tắt phân tích danh mục đầu tư:")
print(summary_df)

2. Khuyến nghị đầu tư

# Tạo khuyến nghị dựa trên phân tích
def generate_recommendations(current_weights, optimal_weights, risk_parity_weights):
    """
    Tạo khuyến nghị đầu tư dựa trên phân tích
    
    Parameters:
    -----------
    current_weights : np.array
        Trọng số danh mục hiện tại
    optimal_weights : np.array
        Trọng số danh mục tối ưu (Max Sharpe)
    risk_parity_weights : np.array
        Trọng số danh mục Risk Parity
        
    Returns:
    --------
    pd.DataFrame
        Khuyến nghị đầu tư
    """
    # Tính chênh lệch
    diff_optimal = optimal_weights - current_weights
    diff_risk_parity = risk_parity_weights - current_weights
    
    # Tạo DataFrame
    recommendations = pd.DataFrame({
        'Trọng số hiện tại': current_weights,
        'Trọng số Max Sharpe': optimal_weights,
        'Chênh lệch (Max Sharpe)': diff_optimal,
        'Trọng số Risk Parity': risk_parity_weights,
        'Chênh lệch (Risk Parity)': diff_risk_parity
    }, index=tickers)
    
    # Thêm cột khuyến nghị
    def get_recommendation(row):
        # Nếu cả hai chiến lược đều khuyến nghị tăng hoặc giảm
        if (row['Chênh lệch (Max Sharpe)'] > 0.01 and row['Chênh lệch (Risk Parity)'] > 0.01):
            return "TĂNG MẠNH"
        elif (row['Chênh lệch (Max Sharpe)'] < -0.01 and row['Chênh lệch (Risk Parity)'] < -0.01):
            return "GIẢM MẠNH"
        # Nếu một chiến lược khuyến nghị tăng mạnh
        elif (row['Chênh lệch (Max Sharpe)'] > 0.03 or row['Chênh lệch (Risk Parity)'] > 0.03):
            return "TĂNG"
        # Nếu một chiến lược khuyến nghị giảm mạnh
        elif (row['Chênh lệch (Max Sharpe)'] < -0.03 or row['Chênh lệch (Risk Parity)'] < -0.03):
            return "GIẢM"
        else:
            return "GIỮ NGUYÊN"
    
    recommendations['Khuyến nghị'] = recommendations.apply(get_recommendation, axis=1)
    
    return recommendations

# Tạo khuyến nghị
recommendations = generate_recommendations(weights, optimal_weights_array, risk_parity_w)

# Định dạng phần trăm
for col in ['Trọng số hiện tại', 'Trọng số Max Sharpe', 'Chênh lệch (Max Sharpe)', 
           'Trọng số Risk Parity', 'Chênh lệch (Risk Parity)']:
    recommendations[col] = recommendations[col].map('{:.2%}'.format)

print("\nKhuyến nghị điều chỉnh danh mục đầu tư:")
print(recommendations)

# Tạo biểu đồ khuyến nghị
plt.figure(figsize=(14, 8))

# Tạo mã màu cho khuyến nghị
color_map = {
    'TĂNG MẠNH': 'darkgreen',
    'TĂNG': 'green',
    'GIỮ NGUYÊN': 'gray',
    'GIẢM': 'red',
    'GIẢM MẠNH': 'darkred'
}

colors = [color_map[rec] for rec in recommendations['Khuyến nghị']]

# Vẽ biểu đồ
plt.barh(recommendations.index, recommendations['Chênh lệch (Max Sharpe)'].str.rstrip('%').astype(float), 
        color=colors, alpha=0.7)

plt.axvline(x=0, color='black', linestyle='-', alpha=0.5)
plt.xlabel('Chênh lệch trọng số theo chiến lược Max Sharpe (%)')
plt.title('Khuyến nghị điều chỉnh danh mục đầu tư')
plt.grid(True, alpha=0.3)

# Thêm nhãn khuyến nghị
for i, ticker in enumerate(recommendations.index):
    plt.text(0.5, i, recommendations.loc[ticker, 'Khuyến nghị'], 
             ha='left', va='center', weight='bold', color=colors[i])

plt.tight_layout()
plt.show()

VII. Kết luận

Phân tích rủi ro danh mục đầu tư là một quá trình không thể thiếu đối với bất kỳ nhà đầu tư nào muốn tối đa hóa lợi nhuận trong khi kiểm soát rủi ro. Trong bài viết này, chúng ta đã khám phá nhiều kỹ thuật phân tích rủi ro, từ cơ bản đến nâng cao, sử dụng Python như một công cụ mạnh mẽ.

Các phương pháp như tính toán Value at Risk, phân tích tương quan, tối ưu hóa Markowitz, và mô phỏng Monte Carlo không chỉ giúp đánh giá rủi ro hiện tại mà còn cung cấp cái nhìn sâu sắc về cách cải thiện hiệu suất danh mục trong tương lai. Việc kết hợp nhiều phương pháp khác nhau giúp đưa ra các khuyến nghị đầu tư đáng tin cậy hơn.

Điều quan trọng cần nhớ là không có chiến lược đầu tư nào hoàn hảo và phù hợp với tất cả mọi người. Việc lựa chọn chiến lược phù hợp phụ thuộc vào mục tiêu đầu tư, khẩu vị rủi ro, và thời gian đầu tư của mỗi cá nhân. Phân tích rủi ro nên được thực hiện định kỳ để đảm bảo danh mục đầu tư luôn phù hợp với thay đổi của thị trường và mục tiêu cá nhân.

Python, với hệ sinh thái các thư viện phân tích dữ liệu mạnh mẽ, đã trở thành công cụ không thể thiếu cho các nhà đầu tư hiện đại trong việc quản lý rủi ro và tối ưu hóa danh mục đầu tư.

← Xem tất cả bài viết · Trang chủ