Quantitative Risk Management & FE¶

最大回撤计算¶

In [1]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 生成累积收益曲线
np.random.seed(42)
returns = np.random.normal(0.001, 0.02, 1000)
cumulative_returns = (1 + returns).cumprod()

# 计算最大回撤
def calculate_max_drawdown(cumulative_returns):
    # 计算历史最高点
    running_max = np.maximum.accumulate(cumulative_returns)
    # 计算相对于历史最高点的回撤
    drawdown = (cumulative_returns - running_max) / running_max
    # 最大回撤
    max_drawdown = drawdown.min()
    return max_drawdown

mdd = calculate_max_drawdown(cumulative_returns)
print(f"最大回撤: {mdd:.2%}")

# 可视化
plt.figure(figsize=(12, 6))
plt.plot(cumulative_returns)
plt.title('累积收益与最大回撤')
plt.xlabel('交易日')
plt.ylabel('累积收益')
plt.show()
Duplicate key in file WindowsPath('d:/Users/wukek/anaconda3/Lib/site-packages/matplotlib/mpl-data/matplotlibrc'), line 412 ('axes.unicode_minus: True  # use Unicode for the minus symbol rather than hyphen.  See')
最大回撤: -33.99%
No description has been provided for this image

VaR计算¶

In [2]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import stats

# 假设收益率数据
returns = np.random.normal(0.001, 0.02, 1000)

# 参数法VaR
def parametric_var(returns, alpha=0.05):
    mu = np.mean(returns)
    sigma = np.std(returns)
    var = mu + sigma * stats.norm.ppf(alpha)
    return -var

# 历史模拟法VaR
def historical_var(returns, alpha=0.05):
    return -np.percentile(returns, alpha*100)

# 打印结果
print(f"参数法VaR(95%): {parametric_var(returns):.4f}")
print(f"历史模拟法VaR(95%): {historical_var(returns):.4f}")
参数法VaR(95%): 0.0304
历史模拟法VaR(95%): 0.0305

VaR的基础实现¶

In [3]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import yfinance as yf
from scipy.stats import norm

### 下载股票数据
tickers = ['AAPL', 'MSFT', 'AMZN', 'GOOGL']
weights = np.array([0.3, 0.3, 0.2, 0.2])
start_date = '2021-01-01'
end_date = '2023-01-01'

### 获取历史数据
data = yf.download(tickers, start=start_date, end=end_date, auto_adjust=True, proxy="http://127.0.0.1:7897")['Close']
returns = data.pct_change().dropna()

### 计算组合收益
portfolio_returns = returns.dot(weights)

### 设置参数
confidence_level = 0.95
investment_value = 1000000  ### 100万投资

### 计算历史模拟法VaR
historical_var = -np.percentile(portfolio_returns, (1-confidence_level)*100)
historical_var_value = historical_var * investment_value
print(f"95% 历史模拟法VaR: {historical_var:.4f} ({historical_var_value:.2f}元)")

### 计算参数法VaR
mean = np.mean(portfolio_returns)
std = np.std(portfolio_returns)
parametric_var = -(mean + std * norm.ppf(1-confidence_level))
parametric_var_value = parametric_var * investment_value
print(f"95% 参数法VaR: {parametric_var:.4f} ({parametric_var_value:.2f}元)")

### 蒙特卡洛模拟法VaR
np.random.seed(42)  ### 设置随机种子以确保结果可重复
n_simulations = 10000  ### 模拟次数
### 使用历史均值和标准差生成随机收益率
mc_returns = np.random.normal(mean, std, n_simulations)
mc_var = -np.percentile(mc_returns, (1-confidence_level)*100)
mc_var_value = mc_var * investment_value
print(f"95% 蒙特卡洛模拟法VaR: {mc_var:.4f} ({mc_var_value:.2f}元)")

### 可视化收益率分布与VaR
plt.figure(figsize=(12, 8))

### 绘制收益率分布直方图
plt.hist(portfolio_returns, bins=50, alpha=0.5, density=True, 
         label='历史收益率分布')

### 添加VaR线
plt.axvline(-historical_var, color='r', linestyle='--', 
           label=f'历史模拟法VaR (95%): {historical_var:.4f}')
plt.axvline(-parametric_var, color='g', linestyle='--', 
           label=f'参数法VaR (95%): {parametric_var:.4f}')
plt.axvline(-mc_var, color='b', linestyle='--', 
           label=f'蒙特卡洛模拟法VaR (95%): {mc_var:.4f}')

### 添加正态分布曲线
x = np.linspace(min(portfolio_returns), max(portfolio_returns), 1000)
plt.plot(x, norm.pdf(x, mean, std), 'k-', lw=2, 
         label='正态分布拟合')

plt.legend(fontsize=10)
plt.title('投资组合收益率分布与VaR比较', fontsize=15)
plt.xlabel('日收益率', fontsize=12)
plt.ylabel('概率密度', fontsize=12)
plt.grid(True, alpha=0.3)

### 计算10天VaR (使用平方根法则)
time_horizon = 10  ### 10天持有期
historical_var_10d = historical_var * np.sqrt(time_horizon)
parametric_var_10d = parametric_var * np.sqrt(time_horizon)
mc_var_10d = mc_var * np.sqrt(time_horizon)

print("\n10天持有期VaR (平方根法则):")
print(f"历史模拟法VaR (10天): {historical_var_10d:.4f} ({historical_var_10d*investment_value:.2f}元)")
print(f"参数法VaR (10天): {parametric_var_10d:.4f} ({parametric_var_10d*investment_value:.2f}元)")
print(f"蒙特卡洛模拟法VaR (10天): {mc_var_10d:.4f} ({mc_var_10d*investment_value:.2f}元)")

### 计算各资产对组合VaR的贡献
cov_matrix = returns.cov()
portfolio_variance = weights.T @ cov_matrix @ weights
portfolio_std = np.sqrt(portfolio_variance)

### 边际贡献
marginal_contrib = (cov_matrix @ weights) / portfolio_std
component_var = weights * marginal_contrib * norm.ppf(confidence_level)
percent_contrib = component_var / np.sum(component_var) * 100

var_contrib = pd.DataFrame({
    'Asset': tickers,
    'Weight': weights * 100,
    'VaR Contribution': component_var,
    'Contribution %': percent_contrib
})

print("\n各资产VaR贡献:")
print(var_contrib)
YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)
[*********************100%***********************]  4 of 4 completed
95% 历史模拟法VaR: 0.0310 (30966.71元)
95% 参数法VaR: 0.0308 (30766.11元)
95% 蒙特卡洛模拟法VaR: 0.0310 (30952.66元)

10天持有期VaR (平方根法则):
历史模拟法VaR (10天): 0.0979 (97925.33元)
参数法VaR (10天): 0.0973 (97290.97元)
蒙特卡洛模拟法VaR (10天): 0.0979 (97880.90元)

各资产VaR贡献:
        Asset  Weight  VaR Contribution  Contribution %
Ticker                                                 
AAPL     AAPL    30.0          0.008500       27.685683
AMZN     MSFT    30.0          0.010901       35.505576
GOOGL    AMZN    20.0          0.005891       19.188091
MSFT    GOOGL    20.0          0.005410       17.620650
d:\Users\wukek\anaconda3\Lib\site-packages\IPython\core\events.py:82: UserWarning: Glyph 8722 (\N{MINUS SIGN}) missing from current font.
  func(*args, **kwargs)
d:\Users\wukek\anaconda3\Lib\site-packages\IPython\core\pylabtools.py:152: UserWarning: Glyph 8722 (\N{MINUS SIGN}) missing from current font.
  fig.canvas.print_figure(bytes_io, **kw)
No description has been provided for this image
In [4]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import yfinance as yf
from scipy.stats import norm

### 下载股票数据
tickers = ['AAPL', 'MSFT', 'AMZN', 'GOOGL']
weights = np.array([0.3, 0.3, 0.2, 0.2])
start_date = '2021-01-01'
end_date = '2023-01-01'

### 获取历史数据
data = yf.download(tickers, start=start_date, end=end_date, auto_adjust=True, proxy="http://127.0.0.1:7897")['Close']
returns = data.pct_change().dropna()

### 计算组合收益
portfolio_returns = returns.dot(weights)

### 历史模拟法ES
def calculate_historical_es(returns, alpha=0.95):
    var = -np.percentile(returns, (1-alpha)*100)
    es = -np.mean(returns[returns <= -var])
    return es

### 参数法ES (正态分布假设)
def calculate_parametric_es(mu, sigma, alpha=0.95):
    z_score = norm.ppf(1-alpha)
    es = -(mu + sigma * norm.pdf(z_score)/(1-alpha))
    return es

### 计算实例
hist_es = calculate_historical_es(portfolio_returns)
para_es = -calculate_parametric_es(
    np.mean(portfolio_returns), 
    np.std(portfolio_returns))

### 打印结果
print(f"历史模拟法ES (95%): {hist_es:.4f}")
print(f"参数法ES (95%): {para_es:.4f}")
### 可视化ES
plt.figure(figsize=(12, 6))
plt.hist(portfolio_returns, bins=50, alpha=0.5, density=True, 
         label='历史收益率分布')
plt.axvline(-hist_es, color='r', linestyle='--', 
            label=f'历史模拟法ES (95%): {hist_es:.4f}')
plt.axvline(-para_es, color='g', linestyle='--',
            label=f'参数法ES (95%): {para_es:.4f}')
plt.title('投资组合收益率分布与ES比较', fontsize=15)
plt.xlabel('日收益率', fontsize=12)
plt.ylabel('概率密度', fontsize=12)
plt.legend(fontsize=10)
plt.grid(True, alpha=0.3)
plt.show()

            
[*********************100%***********************]  4 of 4 completed
d:\Users\wukek\anaconda3\Lib\site-packages\IPython\core\pylabtools.py:152: UserWarning: Glyph 8722 (\N{MINUS SIGN}) missing from current font.
  fig.canvas.print_figure(bytes_io, **kw)
历史模拟法ES (95%): 0.0414
参数法ES (95%): 0.0384
No description has been provided for this image
In [5]:
# 计算10天ES (使用平方根法则)
time_horizon = 10  ### 10天持有期
hist_es_10d = hist_es * np.sqrt(time_horizon)
para_es_10d = para_es * np.sqrt(time_horizon)
print("\n10天持有期ES (平方根法则):")
print(f"历史模拟法ES (10天): {hist_es_10d:.4f}")
print(f"参数法ES (10天): {para_es_10d:.4f}")
10天持有期ES (平方根法则):
历史模拟法ES (10天): 0.1309
参数法ES (10天): 0.1213

夏普比率(Sharp Ratio)计算¶

In [6]:
import numpy as np
import pandas as pd

# 假设数据
returns = np.random.normal(0.001, 0.02, 252)  # 252个交易日
risk_free_rate = 0.03 / 252  # 日化无风险利率

# 计算夏普比率
def sharpe_ratio(returns, risk_free_rate, periods=252):
    """
    计算年化夏普比率
    periods: 年化系数,日频数据为252,月频为12
    """
    excess_returns = returns - risk_free_rate
    return (np.mean(excess_returns) * periods) / (np.std(excess_returns, ddof=1) * np.sqrt(periods))

sr = sharpe_ratio(returns, risk_free_rate)
print(f"年化夏普比率: {sr:.4f}")
年化夏普比率: 0.1010

索提诺比率(Sortino Ratio)计算¶

In [7]:
import numpy as np

# 计算索提诺比率
def sortino_ratio(returns, risk_free_rate, target_return=0, periods=252):
    """
    计算年化索提诺比率
    target_return: 目标收益率,默认为0
    """
    excess_returns = returns - risk_free_rate
    downside_returns = returns[returns < target_return] - target_return
    
    # 计算下行偏差
    downside_deviation = np.sqrt(np.sum(downside_returns**2) / len(returns)) * np.sqrt(periods)
    
    # 防止分母为零
    if downside_deviation == 0:
        return np.nan
        
    return (np.mean(excess_returns) * periods) / downside_deviation

sr = sortino_ratio(returns, risk_free_rate)
print(f"年化索提诺比率: {sr:.4f}")
年化索提诺比率: 0.1435

均值-方差优化¶

In [8]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.optimize import minimize

# 假设数据:5个资产的期望收益和协方差矩阵
mu = np.array([0.05, 0.08, 0.12, 0.07, 0.10])  # 期望收益
Sigma = np.array([
    [0.04, 0.02, 0.01, 0.02, 0.01],
    [0.02, 0.06, 0.02, 0.03, 0.01],
    [0.01, 0.02, 0.09, 0.03, 0.02],
    [0.02, 0.03, 0.03, 0.05, 0.02],
    [0.01, 0.01, 0.02, 0.02, 0.04]
])  # 协方差矩阵

# 目标函数:组合方差
def portfolio_variance(weights, cov_matrix):
    return weights.T @ cov_matrix @ weights

# 约束条件
def return_constraint(weights, expected_returns, target_return):
    return weights.T @ expected_returns - target_return

# 优化求解
n_assets = len(mu)
target_returns = np.linspace(min(mu), max(mu), 100)
efficient_weights = []

for target in target_returns:
    constraints = [
        {'type': 'eq', 'fun': lambda w: np.sum(w) - 1},
        {'type': 'eq', 'fun': lambda w: return_constraint(w, mu, target)}
    ]
    bounds = tuple((0, 1) for _ in range(n_assets))
    
    initial_weights = np.ones(n_assets) / n_assets
    result = minimize(
        portfolio_variance, 
        initial_weights, 
        args=(Sigma,), 
        method='SLSQP', 
        bounds=bounds, 
        constraints=constraints
    )
    
    if result.success:
        efficient_weights.append(result.x)

print(f"有效前沿的权重:\n{np.array(efficient_weights)}")

# 可视化有效前沿
plt.figure(figsize=(10, 6))
# plt.plot(target_returns, [portfolio_variance(w, Sigma) for w in efficient_weights], label='有效前沿', color='blue')
plt.plot([portfolio_variance(w, Sigma) for w in efficient_weights], target_returns,  label='有效前沿', color='blue')

plt.title('有效前沿')
plt.ylabel('期望收益率')
plt.xlabel('组合波动率')
plt.grid()
plt.legend()
plt.show()
有效前沿的权重:
[[9.99999991e-01 4.99600361e-16 0.00000000e+00 8.56816768e-09
  1.66533454e-16]
 [9.64646457e-01 0.00000000e+00 0.00000000e+00 3.53535431e-02
  5.55111512e-17]
 [9.29292922e-01 8.32667268e-17 1.94289029e-16 7.07070776e-02
  0.00000000e+00]
 [8.93939388e-01 0.00000000e+00 8.32667268e-17 1.06060612e-01
  2.77555756e-17]
 [8.58585853e-01 0.00000000e+00 0.00000000e+00 1.41414147e-01
  8.32667268e-17]
 [8.23232319e-01 0.00000000e+00 0.00000000e+00 1.76767681e-01
  0.00000000e+00]
 [7.87878784e-01 0.00000000e+00 1.11022302e-16 2.12121216e-01
  0.00000000e+00]
 [7.76188870e-01 0.00000000e+00 8.67361738e-19 2.08035384e-01
  1.57757454e-02]
 [7.61296936e-01 0.00000000e+00 1.08420217e-18 2.09286252e-01
  2.94168123e-02]
 [7.50506958e-01 2.88288157e-03 0.00000000e+00 2.01778604e-01
  4.48315566e-02]
 [7.37454915e-01 1.07087244e-02 7.58941521e-19 1.94745757e-01
  5.70906041e-02]
 [7.23781826e-01 1.73909581e-02 8.67361738e-19 1.89510393e-01
  6.93168235e-02]
 [7.10565298e-01 2.30081371e-02 0.00000000e+00 1.84224130e-01
  8.22024353e-02]
 [6.97348863e-01 2.86252870e-02 0.00000000e+00 1.78937730e-01
  9.50881198e-02]
 [6.84132483e-01 3.42423373e-02 0.00000000e+00 1.73651306e-01
  1.07973873e-01]
 [6.70916123e-01 3.98592901e-02 0.00000000e+00 1.68364915e-01
  1.20859672e-01]
 [6.57699843e-01 4.54761896e-02 1.30104261e-18 1.63078425e-01
  1.33745543e-01]
 [6.44483700e-01 5.10929726e-02 8.67361738e-19 1.57791785e-01
  1.46631543e-01]
 [6.31267615e-01 5.67096313e-02 0.00000000e+00 1.52505130e-01
  1.59517624e-01]
 [6.18051582e-01 6.23262504e-02 1.73472348e-18 1.47218415e-01
  1.72403752e-01]
 [6.04835569e-01 6.79429036e-02 0.00000000e+00 1.41931644e-01
  1.85289883e-01]
 [5.91619556e-01 7.35594580e-02 0.00000000e+00 1.36644940e-01
  1.98176046e-01]
 [5.78403604e-01 7.91760499e-02 0.00000000e+00 1.31358107e-01
  2.11062238e-01]
 [5.65187649e-01 8.47925895e-02 0.00000000e+00 1.26071315e-01
  2.23948446e-01]
 [5.51971741e-01 9.04091343e-02 1.51788304e-18 1.20784442e-01
  2.36834683e-01]
 [5.38755856e-01 9.60256293e-02 0.00000000e+00 1.15497564e-01
  2.49720950e-01]
 [5.25539930e-01 1.01642206e-01 0.00000000e+00 1.10210699e-01
  2.62607165e-01]
 [5.14950772e-01 1.05255814e-01 6.92509883e-04 1.02342873e-01
  2.76758030e-01]
 [5.05826858e-01 1.07918183e-01 9.37027615e-03 9.79906394e-02
  2.78894044e-01]
 [4.94973265e-01 1.12346523e-01 1.30718921e-02 9.20264535e-02
  2.87581866e-01]
 [4.85510169e-01 1.15779177e-01 1.83768953e-02 8.54774910e-02
  2.94856268e-01]
 [4.76039002e-01 1.19220106e-01 2.36806169e-02 7.89356064e-02
  3.02124669e-01]
 [4.66560172e-01 1.22668820e-01 2.89837546e-02 7.24009163e-02
  3.09386337e-01]
 [4.57074177e-01 1.26124811e-01 3.42870401e-02 6.58734125e-02
  3.16640559e-01]
 [4.47581559e-01 1.29587463e-01 3.95911582e-02 5.93530633e-02
  3.23886756e-01]
 [4.38082942e-01 1.33056124e-01 4.48967971e-02 5.28397216e-02
  3.31124416e-01]
 [4.28578956e-01 1.36530087e-01 5.02046240e-02 4.63332489e-02
  3.38353084e-01]
 [4.19070348e-01 1.40008595e-01 5.55153078e-02 3.98333543e-02
  3.45572394e-01]
 [4.09557851e-01 1.43490790e-01 6.08294639e-02 3.33398022e-02
  3.52782092e-01]
 [4.00042318e-01 1.46975790e-01 6.61477747e-02 2.68522073e-02
  3.59981910e-01]
 [3.90524717e-01 1.50462559e-01 7.14709076e-02 2.03700943e-02
  3.67171722e-01]
 [3.81006174e-01 1.53949906e-01 7.67996250e-02 1.38928902e-02
  3.74351405e-01]
 [3.71488061e-01 1.57436381e-01 8.21347983e-02 7.41985451e-03
  3.81520906e-01]
 [3.61972154e-01 1.60920136e-01 8.74774401e-02 9.49932705e-04
  3.88680337e-01]
 [3.49666731e-01 1.62746204e-01 9.24685892e-02 0.00000000e+00
  3.95118475e-01]
 [3.36917454e-01 1.64223873e-01 9.74266006e-02 0.00000000e+00
  4.01432072e-01]
 [3.24200318e-01 1.65598462e-01 1.02361884e-01 0.00000000e+00
  4.07839336e-01]
 [3.11559098e-01 1.67088329e-01 1.07602237e-01 0.00000000e+00
  4.13750335e-01]
 [2.98847468e-01 1.68458498e-01 1.12546865e-01 0.00000000e+00
  4.20147169e-01]
 [2.86332193e-01 1.69757128e-01 1.17910843e-01 0.00000000e+00
  4.25999836e-01]
 [2.73828982e-01 1.70421675e-01 1.22670897e-01 0.00000000e+00
  4.33078446e-01]
 [2.61371770e-01 1.71614258e-01 1.28073987e-01 0.00000000e+00
  4.38939985e-01]
 [2.48895968e-01 1.72500892e-01 1.33124649e-01 2.95445092e-18
  4.45478492e-01]
 [2.36477592e-01 1.73857039e-01 1.38788392e-01 6.23416249e-19
  4.50876978e-01]
 [2.24013928e-01 1.75014706e-01 1.44140434e-01 8.45677695e-18
  4.56830932e-01]
 [2.11416219e-01 1.75181743e-01 1.48166736e-01 4.40457133e-18
  4.65235302e-01]
 [1.98927794e-01 1.76570930e-01 1.53688395e-01 4.01154804e-18
  4.70812882e-01]
 [1.86442926e-01 1.77919682e-01 1.59178513e-01 0.00000000e+00
  4.76458879e-01]
 [1.74346446e-01 1.75971530e-01 1.62342695e-01 0.00000000e+00
  4.87339329e-01]
 [1.62636102e-01 1.78669437e-01 1.71118279e-01 0.00000000e+00
  4.87576181e-01]
 [1.48922454e-01 1.81674212e-01 1.75192467e-01 3.51010453e-18
  4.94210868e-01]
 [1.36380822e-01 1.82832133e-01 1.80349844e-01 6.55942314e-18
  5.00437201e-01]
 [1.23788150e-01 1.83952369e-01 1.85341938e-01 0.00000000e+00
  5.06917542e-01]
 [1.11126473e-01 1.85031987e-01 1.90120896e-01 0.00000000e+00
  5.13720644e-01]
 [9.85809262e-02 1.86155944e-01 1.95234523e-01 1.07336015e-17
  5.20028607e-01]
 [8.60328680e-02 1.87282629e-01 2.00344596e-01 2.16840434e-18
  5.26339907e-01]
 [7.34800101e-02 1.88410814e-01 2.05444173e-01 6.07153217e-18
  5.32665002e-01]
 [6.09228006e-02 1.89541243e-01 2.10535113e-01 3.03576608e-18
  5.39000844e-01]
 [4.83611347e-02 1.90675069e-01 2.15618311e-01 1.51788304e-18
  5.45345485e-01]
 [3.57945245e-02 1.91813879e-01 2.20694130e-01 0.00000000e+00
  5.51697466e-01]
 [2.32221584e-02 1.92959768e-01 2.25762638e-01 0.00000000e+00
  5.58055436e-01]
 [1.06428349e-02 1.94115470e-01 2.30823567e-01 6.93889390e-18
  5.64418129e-01]
 [0.00000000e+00 1.94558627e-01 2.40013171e-01 0.00000000e+00
  5.65428203e-01]
 [3.10316767e-18 1.75230553e-01 2.56038632e-01 1.15313467e-17
  5.68730815e-01]
 [0.00000000e+00 1.56785240e-01 2.72946856e-01 9.74113741e-18
  5.70267903e-01]
 [6.20633538e-18 1.38339915e-01 2.89855066e-01 4.69122533e-17
  5.71805019e-01]
 [1.26778476e-17 1.19894595e-01 3.06763281e-01 0.00000000e+00
  5.73342125e-01]
 [0.00000000e+00 1.01449273e-01 3.23671495e-01 0.00000000e+00
  5.74879231e-01]
 [0.00000000e+00 8.30039472e-02 3.40579704e-01 1.99625115e-17
  5.76416348e-01]
 [0.00000000e+00 6.45586872e-02 3.57487983e-01 0.00000000e+00
  5.77953330e-01]
 [7.75791923e-18 4.61133059e-02 3.74396134e-01 0.00000000e+00
  5.79490560e-01]
 [0.00000000e+00 2.76679843e-02 3.91304349e-01 0.00000000e+00
  5.81027666e-01]
 [0.00000000e+00 9.22265223e-03 4.08212551e-01 7.79527894e-18
  5.82564797e-01]
 [0.00000000e+00 0.00000000e+00 4.34343434e-01 0.00000000e+00
  5.65656566e-01]
 [1.24126708e-17 0.00000000e+00 4.69696972e-01 4.95362919e-18
  5.30303028e-01]
 [0.00000000e+00 1.38777878e-17 5.05050504e-01 6.76971646e-18
  4.94949496e-01]
 [7.75791923e-19 2.08166817e-17 5.40404040e-01 4.30549104e-18
  4.59595960e-01]
 [0.00000000e+00 0.00000000e+00 5.75757576e-01 1.80454191e-17
  4.24242424e-01]
 [1.55158385e-17 0.00000000e+00 6.11111111e-01 0.00000000e+00
  3.88888889e-01]
 [0.00000000e+00 1.95156391e-17 6.46464646e-01 0.00000000e+00
  3.53535354e-01]
 [0.00000000e+00 2.77555756e-17 6.81818183e-01 2.77555756e-17
  3.18181817e-01]
 [0.00000000e+00 0.00000000e+00 7.17171718e-01 8.32667268e-17
  2.82828282e-01]
 [0.00000000e+00 1.94289029e-16 7.52525252e-01 0.00000000e+00
  2.47474748e-01]
 [0.00000000e+00 1.11022302e-16 7.87878787e-01 1.94289029e-16
  2.12121213e-01]
 [5.55111512e-17 1.11022302e-16 8.23232321e-01 0.00000000e+00
  1.76767679e-01]
 [0.00000000e+00 3.05311332e-16 8.58585856e-01 1.11022302e-16
  1.41414144e-01]
 [5.55111512e-17 0.00000000e+00 8.93939390e-01 0.00000000e+00
  1.06060610e-01]
 [0.00000000e+00 0.00000000e+00 9.29292925e-01 0.00000000e+00
  7.07070753e-02]
 [0.00000000e+00 8.32667268e-17 9.64646459e-01 1.11022302e-16
  3.53535408e-02]
 [2.77555756e-16 8.32667268e-17 9.99999994e-01 0.00000000e+00
  6.33299382e-09]]
No description has been provided for this image

风险平价投资组合¶

In [9]:
import numpy as np
from scipy.optimize import minimize

# 假设协方差矩阵
Sigma = np.array([
    [0.04, 0.02, 0.01],
    [0.02, 0.06, 0.02],
    [0.01, 0.02, 0.05]
])

# 风险贡献函数
def risk_contribution(weights, cov_matrix):
    portfolio_vol = np.sqrt(weights.T 
        @ cov_matrix @ weights)
    marginal_contrib = cov_matrix @ weights
    risk_contrib = weights * marginal_contrib / portfolio_vol
    return risk_contrib

# 风险平价目标函数:各资产风险贡献的方差
def risk_parity_objective(weights, cov_matrix):
    risk_contrib = risk_contribution(weights, cov_matrix)
    target_risk = 1.0 / len(weights)# 目标风险贡献相等
    return np.sum((risk_contrib - target_risk)**2)

# 优化求解
n_assets = Sigma.shape[0]
constraints = [{'type': 'eq', 'fun': lambda w: np.sum(w) - 1}]
bounds = tuple((0.01, 1) for _ in range(n_assets))

initial_weights = np.ones(n_assets) / n_assets
result = minimize(
    risk_parity_objective, 
    initial_weights, 
    args=(Sigma,), 
    method='SLSQP', 
    bounds=bounds, 
    constraints=constraints
)

risk_parity_weights = result.x
print("风险平价权重:", risk_parity_weights)
print("风险贡献:", risk_contribution(risk_parity_weights, Sigma))
风险平价权重: [0.01       0.53086716 0.45913284]
风险贡献: [0.00080595 0.11302956 0.07983188]

Black-Litterman模型¶

In [10]:
import numpy as np

# 市场数据
# 市场权重
mkt_weights = np.array([0.3, 0.4, 0.3])
Sigma = np.array([
    [0.04, 0.02, 0.01],
    [0.02, 0.06, 0.02],
    [0.01, 0.02, 0.05]
])  # 协方差矩阵
delta = 2.5  # 市场风险厌恶系数

# 步骤1:计算市场隐含收益
Pi = delta * Sigma @ mkt_weights

# 步骤2:表达投资者观点
# 观点1:资产1的收益率为7%
# 观点2:资产2的收益率比资产3高2%
P = np.array([
    [1, 0, 0],
    [0, 1, -1]
])
Q = np.array([0.07, 0.02])
tau = 0.05  # 先验分布缩放因子
Omega = np.diag([0.01, 0.02])  # 观点不确定性

# 步骤3:结合市场与观点
temp1 = np.linalg.inv(tau * Sigma)
temp2 = P.T @ np.linalg.inv(Omega) @ P
temp3 = temp1 @ Pi
temp4 = P.T @ np.linalg.inv(Omega) @ Q

mu_bl = np.linalg.inv(temp1 + temp2) @ (temp3 + temp4)

print("Black-Litterman期望收益:", mu_bl)
Black-Litterman期望收益: [0.05948536 0.09058119 0.06587844]

凯利准则实现¶

In [11]:
import numpy as np
import matplotlib.pyplot as plt

# 凯利准则函数
def kelly_criterion(win_rate, win_loss_ratio):
    """
    计算凯利比例
    win_rate: 胜率
    win_loss_ratio: 盈亏比(赢时的收益/输时的损失)
    """
    return win_rate - (1 - win_rate) / win_loss_ratio

# 模拟不同资金管理策略的表现
def simulate_strategy(initial_capital, win_rate, win_loss_ratio, fraction, n_trades):
    capital = initial_capital
    capital_history = [capital]
    
    for _ in range(n_trades):
        # 决定交易结果
        is_win = np.random.random() < win_rate
        
        # 计算盈亏
        bet_size = capital * fraction
        if is_win:
            capital += bet_size * (win_loss_ratio - 1)
        else:
            capital -= bet_size
        
        capital_history.append(capital)
        
        # 破产检查
        if capital <= 0:
            break
    
    return capital_history


# 参数设置
win_rate = 0.6
win_loss_ratio = 2
kelly = kelly_criterion(win_rate, win_loss_ratio)
print(f"凯利比例: {kelly:.4f}")
凯利比例: 0.4000

固定分数风险模型¶

In [12]:
import numpy as np

# 假设交易数据
account_value = 100000  # 账户价值
risk_per_trade = 0.01  # 每笔交易风险1%
stop_loss_points = 50  # 止损点数
point_value = 10  # 每点价值(美元)

# 计算头寸规模
def fixed_fractional_position_size(account_value,
    risk_percent, stop_loss_points, point_value):
    # 计算每笔交易可承受的最大损失金额
    max_loss_amount = account_value * risk_percent
    
    # 计算头寸规模(合约数量)
    position_size = max_loss_amount / (stop_loss_points * point_value)
    
    # 通常需要向下取整
    return int(position_size)

# 计算头寸规模
position_size = fixed_fractional_position_size(
    account_value, risk_per_trade, 
    stop_loss_points, point_value
)

print(f"账户价值: ${account_value}")
print(f"每笔交易风险: {risk_per_trade*100}%")
print(f"止损点数: {stop_loss_points}点")
print(f"建议头寸规模: {position_size}手合约")
账户价值: $100000
每笔交易风险: 1.0%
止损点数: 50点
建议头寸规模: 2手合约

目标波动率控制¶

In [13]:
import numpy as np
import pandas as pd

# 假设数据
np.random.seed(42)
returns = pd.Series(np.random.normal(0.001, 0.02, 252), 
                    index=pd.date_range('2024-01-01', 
                    periods=252, freq='B'))

def target_volatility_scaling(returns, target_vol, 
    lookback=60, cap=2.0):
    """
    基于目标波动率的头寸规模调整
    returns: 收益率序列
    target_vol: 年化目标波动率
    lookback: 波动率计算的回看天数
    cap: 杠杆上限
    """
    # 计算波动率(使用简单滚动标准差)
    vol = returns.rolling(window=lookback).std() * np.sqrt(252)
    
    # 计算权重调整因子
    scaling_factor = target_vol / vol
    
    # 应用上限
    scaling_factor = np.minimum(scaling_factor, cap)
    
    # 填充开始的NaN值
    scaling_factor = scaling_factor.fillna(1.0)
    
    return scaling_factor

# 应用目标波动率控制
target_vol = 0.15  # 15%年化目标波动率
scaling = target_volatility_scaling(returns, target_vol)
scaled_returns = returns * scaling

# 比较原始策略和调整后策略的表现
cumulative_returns = (1 + returns).cumprod() - 1
scaled_cumulative_returns = (1 + scaled_returns).cumprod() - 1


# 可视化结果
import matplotlib.pyplot as plt
plt.figure(figsize=(12, 6))
plt.plot(cumulative_returns, label='原始策略', color='blue')
plt.plot(scaled_cumulative_returns, label='目标波动率调整策略', color='orange')
plt.title('目标波动率调整策略 vs 原始策略')
plt.xlabel('日期')
plt.ylabel('累计收益率')
plt.legend()
plt.grid()
plt.show()
No description has been provided for this image
In [14]:
# 计算夏普比率
def calculate_sharpe_ratio(returns, risk_free_rate=0.0):
    excess_returns = returns - risk_free_rate
    return np.mean(excess_returns) / np.std(excess_returns)

# 计算原始策略和调整后策略的夏普比率
original_sharpe = calculate_sharpe_ratio(returns)
scaled_sharpe = calculate_sharpe_ratio(scaled_returns)
print(f"原始策略夏普比率: {original_sharpe:.4f}")
print(f"目标波动率调整策略夏普比率: {scaled_sharpe:.4f}")

# 计算最大回撤
def calculate_max_drawdown(returns):
    cumulative_returns = (1 + returns).cumprod()
    peak = cumulative_returns.cummax()
    drawdown = (cumulative_returns - peak) / peak
    return drawdown.min()

# 计算原始策略和调整后策略的最大回撤
original_max_drawdown = calculate_max_drawdown(returns)
scaled_max_drawdown = calculate_max_drawdown(scaled_returns)
print(f"原始策略最大回撤: {original_max_drawdown:.4f}")
print(f"目标波动率调整策略最大回撤: {scaled_max_drawdown:.4f}")

# 计算年化收益率
def calculate_annualized_return(returns):
    return (1 + returns.mean()) ** 252 - 1

# 计算原始策略和调整后策略的年化收益率
original_annualized_return = calculate_annualized_return(returns)
scaled_annualized_return = calculate_annualized_return(scaled_returns)
print(f"原始策略年化收益率: {original_annualized_return:.4f}")
print(f"目标波动率调整策略年化收益率: {scaled_annualized_return:.4f}")

# 计算信息比率
def calculate_information_ratio(returns, benchmark_returns):
    excess_returns = returns - benchmark_returns
    return np.mean(excess_returns) / np.std(excess_returns)

# 假设基准收益率为0
benchmark_returns = pd.Series(0, index=returns.index)

# 计算原始策略和调整后策略的信息比率
original_information_ratio = calculate_information_ratio(returns, benchmark_returns)
scaled_information_ratio = calculate_information_ratio(scaled_returns, benchmark_returns)
print(f"原始策略信息比率: {original_information_ratio:.4f}")
print(f"目标波动率调整策略信息比率: {scaled_information_ratio:.4f}")

# 计算年化波动率
def calculate_annualized_volatility(returns):
    return returns.std() * np.sqrt(252)

# 计算原始策略和调整后策略的年化波动率
original_annualized_volatility = calculate_annualized_volatility(returns)
scaled_annualized_volatility = calculate_annualized_volatility(scaled_returns)
print(f"原始策略年化波动率: {original_annualized_volatility:.4f}")
print(f"目标波动率调整策略年化波动率: {scaled_annualized_volatility:.4f}")

# 计算年化收益率与年化波动率的比率
def calculate_return_to_volatility_ratio(returns):
    annualized_return = calculate_annualized_return(returns)
    annualized_volatility = calculate_annualized_volatility(returns)
    return annualized_return / annualized_volatility

# 计算原始策略和调整后策略的年化收益率与年化波动率比率
original_return_to_volatility = calculate_return_to_volatility_ratio(returns)
scaled_return_to_volatility = calculate_return_to_volatility_ratio(scaled_returns)
print(f"原始策略年化收益率与年化波动率比率: {original_return_to_volatility:.4f}")
print(f"目标波动率调整策略年化收益率与年化波动率比率: {scaled_return_to_volatility:.4f}")

# 计算年化收益率与最大回撤的比率
def calculate_return_to_drawdown_ratio(returns):
    annualized_return = calculate_annualized_return(returns)
    max_drawdown = calculate_max_drawdown(returns)
    return annualized_return / abs(max_drawdown)

# 计算原始策略和调整后策略的年化收益率与最大回撤比率
original_return_to_drawdown = calculate_return_to_drawdown_ratio(returns)
scaled_return_to_drawdown = calculate_return_to_drawdown_ratio(scaled_returns)
print(f"原始策略年化收益率与最大回撤比率: {original_return_to_drawdown:.4f}")
print(f"目标波动率调整策略年化收益率与最大回撤比率: {scaled_return_to_drawdown:.4f}")

# 计算年化收益率与信息比率的比率
def calculate_return_to_information_ratio(returns, benchmark_returns):
    annualized_return = calculate_annualized_return(returns)
    information_ratio = calculate_information_ratio(returns, benchmark_returns)
    return annualized_return / information_ratio if information_ratio != 0 else np.nan

# 计算原始策略和调整后策略的年化收益率与信息比率比率
original_return_to_information = calculate_return_to_information_ratio(returns, benchmark_returns)
scaled_return_to_information = calculate_return_to_information_ratio(scaled_returns, benchmark_returns)
print(f"原始策略年化收益率与信息比率比率: {original_return_to_information:.4f}")
print(f"目标波动率调整策略年化收益率与信息比率比率: {scaled_return_to_information:.4f}")
原始策略夏普比率: 0.0479
目标波动率调整策略夏普比率: 0.0133
原始策略最大回撤: -0.2551
目标波动率调整策略最大回撤: -0.2551
原始策略年化收益率: 0.2623
目标波动率调整策略年化收益率: 0.0414
原始策略信息比率: 0.0479
目标波动率调整策略信息比率: 0.0133
原始策略年化波动率: 0.3071
目标波动率调整策略年化波动率: 0.1930
原始策略年化收益率与年化波动率比率: 0.8541
目标波动率调整策略年化收益率与年化波动率比率: 0.2146
原始策略年化收益率与最大回撤比率: 1.0283
目标波动率调整策略年化收益率与最大回撤比率: 0.1623
原始策略年化收益率与信息比率比率: 5.4758
目标波动率调整策略年化收益率与信息比率比率: 3.1196

止损策略实现¶

In [15]:
import numpy as np
import pandas as pd

# 模拟价格数据
np.random.seed(42)
price_data = pd.DataFrame({
    'close': np.random.normal(0, 1, 100).cumsum() + 100,
    'high': None,
    'low': None
})
price_data['high'] = price_data['close'] + np.random.uniform(0, 2, 100)
price_data['low'] = price_data['close'] - np.random.uniform(0, 2, 100)

# 固定止损
def fixed_stop_loss(entry_price, current_price,
    stop_percent=0.05, position='long'):
    if position == 'long':
        stop_price = entry_price * (1 - stop_percent)
        return current_price <= stop_price
    else:  # short position
        stop_price = entry_price * (1 + stop_percent)
        return current_price >= stop_price

# 跟踪止损
def trailing_stop_loss(prices, stop_percent=0.05, position='long'):
    """
    prices: 价格序列
    stop_percent: 跟踪止损百分比
    position: 'long'或'short'
    """
    stops = np.zeros_like(prices)
    triggered = False
    
    if position == 'long':
        # 初始化止损水平
        highest = prices[0]
        stops[0] = highest * (1 - stop_percent)
        
        for i in range(1, len(prices)):
            if triggered:
                stops[i] = -1  # 已触发止损
            else:
                # 更新最高价
                highest = max(highest, prices[i])
                # 更新止损水平
                stops[i] = highest * (1 - stop_percent)
                # 检查是否触发止损
                if prices[i] <= stops[i]:
                    triggered = True
    
    # short position类似实现
    
    return stops, triggered
In [16]:
# 计算固定止损
entry_price = price_data['close'][0]
fixed_stop = fixed_stop_loss(entry_price, price_data['close'], 
                             stop_percent=0.05, position='long')
# 计算跟踪止损
trailing_stops, trailing_triggered = trailing_stop_loss(
    price_data['close'], stop_percent=0.05, position='long')

# 打印结果
print(f"固定止损触发: {fixed_stop}")
print(f"跟踪止损触发: {trailing_triggered}")

# 可视化止损水平
import matplotlib.pyplot as plt
plt.figure(figsize=(12, 6))
plt.plot(price_data['close'], label='价格', color='blue')
plt.plot(trailing_stops, label='跟踪止损', color='red', linestyle='--')
plt.axhline(y=entry_price * (1 - 0.05), color='green', linestyle='--', 
            label='固定止损水平')
plt.title('固定止损与跟踪止损比较')
plt.xlabel('时间')
plt.ylabel('价格')
plt.legend()
plt.grid()
plt.show()

# 计算固定止损水平
fixed_stop_level = entry_price * (1 - 0.05)

# 可视化固定止损水平
plt.figure(figsize=(12, 6))
plt.plot(price_data['close'], label='价格', color='blue')
plt.axhline(y=fixed_stop_level, color='green', linestyle='--', 
            label='固定止损水平')
plt.title('固定止损水平')
plt.xlabel('时间')
plt.ylabel('价格')
plt.legend()
plt.grid()
固定止损触发: 0     False
1     False
2     False
3     False
4     False
      ...  
95     True
96     True
97     True
98     True
99     True
Name: close, Length: 100, dtype: bool
跟踪止损触发: True
No description has been provided for this image
No description has been provided for this image

压力测试实现¶

In [17]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 假设我们有一个投资组合权重
weights = np.array([0.3, 0.4, 0.3])

# 正常市场条件下的收益和协方差
# 年化期望收益
returns = np.array([0.08, 0.10, 0.06])
cov_matrix = np.array([
    [0.04, 0.02, 0.01],
    [0.02, 0.06, 0.02],
    [0.01, 0.02, 0.05]
])  # 年化协方差矩阵

# 正常情况下的投资组合统计
port_return = weights @ returns
port_vol = np.sqrt(weights @ cov_matrix @ weights)
print(f"正常情况 - 期望收益: {port_return:.2%}, 波动率: {port_vol:.2%}")

# 历史情景分析 - 2008金融危机
# 危机期间资产收益
crisis_returns = np.array([-0.30, -0.40, -0.20])
crisis_port_return = weights @ crisis_returns
print(f"2008金融危机情景 - 投资组合收益: {crisis_port_return:.2%}")

# 假设情景 - 相关性趋于1
crisis_corr = np.ones((3, 3))
np.fill_diagonal(crisis_corr, 1)
std_devs = np.sqrt(np.diag(cov_matrix))
crisis_cov = np.outer(std_devs, std_devs) * crisis_corr
crisis_port_vol = np.sqrt(weights @ crisis_cov @ weights)
print(f"相关性趋于1情景 - 投资组合波动率: {crisis_port_vol:.2%}")
正常情况 - 期望收益: 8.20%, 波动率: 17.06%
2008金融危机情景 - 投资组合收益: -31.00%
相关性趋于1情景 - 投资组合波动率: 22.51%
In [18]:
# 可视化正常市场条件下的投资组合
plt.figure(figsize=(10, 6))
plt.bar(['资产1', '资产2', '资产3'], weights, color=['blue', 'orange', 'green'])
plt.title('正常市场条件下的投资组合权重')
plt.xlabel('资产')
plt.ylabel('权重')
plt.grid(axis='y')
plt.show()

# 可视化危机情景下的投资组合
plt.figure(figsize=(10, 6))
plt.bar(['资产1', '资产2', '资产3'], crisis_returns, color=['blue', 'orange', 'green'])
plt.title('2008金融危机情景下的资产收益')
plt.xlabel('资产')
plt.ylabel('收益率')
plt.grid(axis='y')
plt.show()

# 可视化相关性趋于1情景下的投资组合
plt.figure(figsize=(10, 6))
plt.bar(['资产1', '资产2', '资产3'], crisis_cov.diagonal(), color=['blue', 'orange', 'green'])
plt.title('相关性趋于1情景下的资产波动率')
plt.xlabel('资产')
plt.ylabel('波动率')
plt.grid(axis='y')
plt.show()
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image