开启左侧

飓风图代码

发表于:昨天 15:25 20
  1. import numpy as np
  2. import matplotlib.pyplot as plt

  3. # ============================================================
  4. # 1. 蒙特卡洛模拟函数(备用)
  5. # ============================================================
  6. def monte_carlo_simulation(S0, mu, sigma, T, n_sim=1000, n_steps=252):
  7.     """
  8.     几何布朗运动路径模拟。
  9.     返回形状 (n_sim, n_steps+1) 的价格矩阵。
  10.     """
  11.     dt = T / n_steps
  12.     drift = (mu - 0.5 * sigma**2) * dt
  13.     vol = sigma * np.sqrt(dt)
  14.     Z = np.random.standard_normal((n_sim, n_steps))
  15.     log_returns = drift + vol * Z
  16.     log_cumsum = np.cumsum(log_returns, axis=1)
  17.     paths = np.zeros((n_sim, n_steps + 1))
  18.     paths[:, 0] = S0
  19.     paths[:, 1:] = S0 * np.exp(log_cumsum)
  20.     return paths

  21. # ============================================================
  22. # 2. 目标函数(可替换为任何标量输出)
  23. # ============================================================
  24. def gbm_expected_final_price(params: dict) -> float:
  25.     """
  26.     解析计算 GBM 期末价格的数学期望:E[S_T] = S0 * exp(mu * T)
  27.     参数:
  28.         params (dict): 包含 'S0', 'mu', 'sigma', 'T' 键的字典。
  29.     返回:
  30.         float: 期望期末价格。
  31.     """
  32.     return params['S0'] * np.exp(params['mu'] * params['T'])

  33. # ---------- 如需模拟版本,取消下面注释即可 ----------
  34. # def gbm_mean_simulation(params: dict, n_sim=2000, n_steps=252) -> float:
  35. #     paths = monte_carlo_simulation(params['S0'], params['mu'], params['sigma'],
  36. #                                    params['T'], n_sim, n_steps)
  37. #     return paths[:, -1].mean()

  38. # ============================================================
  39. # 3. 飓风图绘制函数
  40. # ============================================================
  41. def plot_tornado(
  42.     base_params: dict,
  43.     param_ranges: dict,
  44.     target_func,
  45.     target_label: str = 'Expected Final Price',
  46.     figsize=None
  47. ):
  48.     """
  49.     绘制飓风图,展示各参数独立变化时目标函数相对于基准的变化幅度。

  50.     参数:
  51.         base_params : dict
  52.             基准参数值,如 {'S0':100, 'mu':0.05, 'sigma':0.2, 'T':1.0}
  53.         param_ranges : dict
  54.             参数变化比例范围,值为 (低值比例, 高值比例)。
  55.             例如 {'S0': (-0.1, 0.1)} 表示 S0 在基准的 ±10% 之间变动。
  56.         target_func : callable
  57.             接收参数字典,返回标量数值。
  58.         target_label : str
  59.             目标函数的名称,用于轴标签。
  60.         figsize : tuple, optional
  61.             图形大小,默认自适应。

  62.     返回:
  63.         fig, ax : matplotlib 对象。
  64.     """
  65.     # 计算基准情景输出
  66.     base_val = target_func(base_params)

  67.     # 收集每个参数的低/高输出变化量
  68.     data = []
  69.     for param, (low_pct, high_pct) in param_ranges.items():
  70.         # 低情景
  71.         low_params = base_params.copy()
  72.         low_params[param] = base_params[param] * (1 + low_pct)
  73.         low_val = target_func(low_params)
  74.         low_delta = low_val - base_val

  75.         # 高情景
  76.         high_params = base_params.copy()
  77.         high_params[param] = base_params[param] * (1 + high_pct)
  78.         high_val = target_func(high_params)
  79.         high_delta = high_val - base_val

  80.         data.append((param, low_delta, high_delta))

  81.     # 按最大影响宽度排序(降序)
  82.     data.sort(key=lambda x: max(abs(x[1]), abs(x[2])), reverse=True)

  83.     # 绘图
  84.     if figsize is None:
  85.         figsize = (max(8, len(data)*1.5), max(4, len(data)*0.5))
  86.     fig, ax = plt.subplots(figsize=figsize)
  87.     y_idx = np.arange(len(data))

  88.     for i, (param, low_d, high_d) in enumerate(data):
  89.         ax.barh(i, high_d - low_d, left=low_d, height=0.5,
  90.                 color='steelblue', alpha=0.8, edgecolor='black')

  91.     # 基准线
  92.     ax.axvline(x=0, color='black', linewidth=1)
  93.     ax.set_yticks(y_idx)
  94.     ax.set_yticklabels([d[0] for d in data])
  95.     ax.set_xlabel(f'Change in {target_label}')
  96.     ax.set_title(f'Sensitivity Tornado Chart\nBase value: {base_val:.2f}')
  97.     ax.grid(axis='x', linestyle=':', alpha=0.6)
  98.     ax.invert_yaxis()  # 最宽的放在最上面

  99.     fig.tight_layout()
  100.     return fig, ax

  101. # ============================================================
  102. # 4. 主程序:示例运行
  103. # ============================================================
  104. if __name__ == '__main__':
  105.     # 基准参数
  106.     base = {
  107.         'S0': 100.0,    # 初始价格
  108.         'mu': 0.05,     # 预期收益率 5%
  109.         'sigma': 0.2,   # 波动率 20%
  110.         'T': 1.0        # 期限 1 年
  111.     }

  112.     # 各参数的变化范围(相对于基准的比例)
  113.     ranges = {
  114.         'S0':    (-0.10, 0.10),   # S0 在 90 ~ 110
  115.         'mu':    (-0.50, 0.50),   # mu 在 2.5% ~ 7.5% (相对变化 ±50%)
  116.         'sigma': (-0.20, 0.20),   # sigma 在 0.16 ~ 0.24
  117.         'T':     (-0.20, 0.20),   # T 在 0.8 ~ 1.2 年
  118.     }

  119.     # 绘制飓风图(使用快速解析期望)
  120.     # 若需要基于模拟的期末价格均值,将 target_func 换为 gbm_mean_simulation 即可
  121.     fig, ax = plot_tornado(base, ranges, gbm_expected_final_price,
  122.                            target_label='Expected Final Price')
  123.     plt.show()
复制代码


收藏
送赞
分享
567567.png

发表回复