1. 识别做市业务中的市场风险敞口
任务说明
本案例要求从做市交易记录中识别并量化市场风险敞口。你将处理一份包含多资产类别(股票、期权、债券)的做市持仓数据,计算三类核心风险因子敞口:价格敞口(Delta)、波动率敞口(Vega)和利率敞口(Rho),并按资产类别汇总分类。完成后的输出将直接用于后续案例的限额监控与对冲决策。
环境与依赖
本案例使用 Python 3.9+,依赖 numpy 和 pandas。若需复现期权定价计算,请安装 scipy(用于正态分布CDF/PDF)。无需其他外部数据源,所有输入数据在代码中内联定义。
核心实现
以下代码完成从原始持仓记录到风险敞口汇总的完整链路。关键设计:期权敞口使用Black-Scholes解析公式计算,股票和债券敞口按线性近似处理。债券利率敞口以修正久期近似,股票价格敞口即市值本身。
import numpy as np
import pandas as pd
from scipy.stats import norm
# ---------- 输入数据:做市持仓记录 ----------
# 字段:instrument, asset_class, position, underlying_price,
# strike, maturity_years, volatility, risk_free_rate, coupon_rate
positions = pd.DataFrame([
# 股票持仓(价格敞口 = 市值)
{"instrument": "AAPL", "asset_class": "equity", "position": 5000,
"underlying_price": 175.0, "strike": np.nan, "maturity_years": np.nan,
"volatility": np.nan, "risk_free_rate": 0.03, "coupon_rate": np.nan},
{"instrument": "MSFT", "asset_class": "equity", "position": -2000,
"underlying_price": 330.0, "strike": np.nan, "maturity_years": np.nan,
"volatility": np.nan, "risk_free_rate": 0.03, "coupon_rate": np.nan},
# 期权持仓(需要计算 Delta / Vega / Rho)
{"instrument": "AAPL_OPT_C", "asset_class": "option", "position": 100,
"underlying_price": 175.0, "strike": 170.0, "maturity_years": 0.5,
"volatility": 0.25, "risk_free_rate": 0.03, "coupon_rate": np.nan},
{"instrument": "MSFT_OPT_P", "asset_class": "option", "position": -50,
"underlying_price": 330.0, "strike": 340.0, "maturity_years": 0.25,
"volatility": 0.30, "risk_free_rate": 0.03, "coupon_rate": np.nan},
# 债券持仓(利率敞口 = 修正久期 × 市值 × (-1))
{"instrument": "US_T_2Y", "asset_class": "bond", "position": 1000000,
"underlying_price": 99.5, "strike": np.nan, "maturity_years": 2.0,
"volatility": np.nan, "risk_free_rate": 0.03, "coupon_rate": 0.025},
{"instrument": "US_T_10Y", "asset_class": "bond", "position": -500000,
"underlying_price": 98.0, "strike": np.nan, "maturity_years": 10.0,
"volatility": np.nan, "risk_free_rate": 0.03, "coupon_rate": 0.035},
])
# ---------- Black-Scholes 希腊字母计算 ----------
def bs_greeks(S, K, T, r, sigma, option_type):
"""返回 (delta, vega, rho),vega 为每单位标的变动1%的敞口"""
if T <= 0 or sigma <= 0:
return 0.0, 0.0, 0.0
d1 = (np.log(S / K) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
d2 = d1 - sigma * np.sqrt(T)
if option_type == "call":
delta = norm.cdf(d1)
rho = K * T * np.exp(-r * T) * norm.cdf(d2) / 100 # 每1%利率变动
else: # put
delta = norm.cdf(d1) - 1
rho = -K * T * np.exp(-r * T) * norm.cdf(-d2) / 100
vega = S * np.sqrt(T) * norm.pdf(d1) / 100 # 每1%波动率变动
return delta, vega, rho
# ---------- 逐笔计算敞口 ----------
results = []
for _, row in positions.iterrows():
if row["asset_class"] == "equity":
# 价格敞口 = 持仓 × 价格(市值)
price_exp = row["position"] * row["underlying_price"]
vol_exp, rate_exp = 0.0, 0.0
elif row["asset_class"] == "option":
# 判断期权类型(从instrument名称末尾C/P)
opt_type = "call" if row["instrument"].endswith("C") else "put"
delta, vega, rho = bs_greeks(
row["underlying_price"], row["strike"], row["maturity_years"],
row["risk_free_rate"], row["volatility"], opt_type
)
# 合约乘数假设为100(标准美股期权)
contract_mult = 100
price_exp = row["position"] * contract_mult * delta * row["underlying_price"]
vol_exp = row["position"] * contract_mult * vega
rate_exp = row["position"] * contract_mult * rho
else: # bond
# 修正久期近似:D_mod ≈ maturity / (1 + coupon_rate)
mod_duration = row["maturity_years"] / (1 + row["coupon_rate"])
bond_value = row["position"] * row["underlying_price"] / 100 # 价格按面值百分比
price_exp = bond_value # 价格敞口 = 市值
rate_exp = -mod_duration * bond_value / 100 # 每1%利率变动
vol_exp = 0.0
results.append({
"instrument": row["instrument"],
"asset_class": row["asset_class"],
"price_exposure": round(price_exp, 2),
"vol_exposure": round(vol_exp, 2),
"rate_exposure": round(rate_exp, 2),
})
exposure_df = pd.DataFrame(results)
# ---------- 按资产类别汇总 ----------
summary = exposure_df.groupby("asset_class")[["price_exposure", "vol_exposure", "rate_exposure"]].sum()
summary.loc["total"] = summary.sum()
# ---------- 输出 ----------
print("=== 逐笔敞口明细 ===")
print(exposure_df.to_string(index=False))
print("\n=== 按资产类别汇总 ===")
print(summary.round(2).to_string())
运行方式
将上述代码保存为 exposure_identification.py,在安装依赖后执行:
pip install numpy pandas scipy
python exposure_identification.py
验证方法与判定标准
运行后应观察到以下特征,满足即判定通过:
- 逐笔明细:AAPL股票价格敞口为 5000×175 = 875,000;MSFT为 -660,000;期权行有非零的Delta/Vega/Rho值;债券行利率敞口为负(多头)或正(空头)。
- 汇总行:
total行的三类敞口等于各资产类别之和,且数值与手工计算一致(例如AAPL期权Call的Delta敞口 = 100×100×N(d1)×175,其中N(d1)≈0.62,约1,085,000)。 - 符号正确性:空头持仓(负position)产生负敞口,多头为正;看跌期权Delta为负,价格敞口为负。
若输出中上述数值匹配、无NaN且汇总正确,则案例实现通过。
常见问题
- 期权合约乘数:本案例假设100股/合约,若实际市场不同需调整
contract_mult。 - 债券价格单位:输入价格99.5表示面值的99.5%,计算市值时已除以100换算。
- Rho单位:代码中除以100表示每1%利率变动(而非1bp),与Vega的每1%波动率口径一致。