Spaces:
Build error
Build error
File size: 13,176 Bytes
fab9847 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 | """
hedging.py — 对冲决策计算器
==============================
核心功能:
1. 给定企业月度燃油/原料消耗金额
2. 根据当前风险预测(区间+因子+regime)
3. 计算不同对冲比例下的成本-收益矩阵
4. 输出推荐对冲比例和工具建议
对冲逻辑:
- 不对冲:完全暴露在油价波动中
- 部分对冲:锁定一部分成本,保留一部分上行/下行暴露
- 完全对冲:完全锁定成本,放弃上行收益但消除下行风险
- 对冲成本 = 远期升水(contango)+ 期权时间价值(简化为波动率函数)
"""
import numpy as np
from config import INDUSTRIES, INDUSTRY_ZH
# ═══════════════════════════════════════════════════════════
# INDUSTRY COST SENSITIVITY (油价弹性系数,基于公开研究)
# ═══════════════════════════════════════════════════════════
# 油价变动1%对行业成本/利润的影响(弹性系数,文献值+经验校准)
COST_ELASTICITY = {
'Aviation': 0.35, # 航空燃油占运营成本 25-40%
'Logistics': 0.22, # 柴油占物流成本 15-25%
'Chemicals': 0.28, # 原油是石脑油/乙烯原料
'Manufacturing': 0.12, # 能源占制造成本 8-15%
'Upstream_OG': -0.60, # 上游:油价上涨 = 收入增加
}
# 典型企业月度油品相关支出(百万美元,用于示例计算)
TYPICAL_EXPOSURE = {
'Aviation': 50.0, # 大型航司月均燃油 $50M
'Logistics': 15.0, # 大型物流公司 $15M
'Chemicals': 30.0, # 大型化工企业 $30M
'Manufacturing': 8.0, # 中型制造企业 $8M
'Upstream_OG': 80.0, # 油气公司产量对应营收
}
# 对冲成本系数(占名义价值的百分比/月,含远期升水+交易成本)
HEDGE_COST_RATES = {
'futures': 0.002, # 期货锁价:~0.2%/月(远期升水+保证金机会成本)
'put': 0.008, # 看跌期权(保护性):~0.8%/月(时间价值衰减)
'collar': 0.003, # 零成本领:~0.3%/月(放弃部分上行)
}
# ═══════════════════════════════════════════════════════════
# HEDGING DECISION ENGINE
# ═══════════════════════════════════════════════════════════
def compute_hedge_matrix(pred_q10, pred_q50, pred_q90, pred_vol,
risk_level, risk_bias, industry,
monthly_exposure=None):
"""
计算不同对冲比例下的成本-收益矩阵。
Parameters
----------
pred_q10, pred_q50, pred_q90 : float
1M 预测区间(收益率,如 -0.11 表示 -11%)
pred_vol : float
预测波动率
risk_level : str
'Low' / 'Medium' / 'High'
risk_bias : str
'Upward' / 'Balanced' / 'Downward'
industry : str
行业标识
monthly_exposure : float or None
月度油品暴露金额(百万美元),None则使用典型值
Returns
-------
dict with keys:
'recommended_ratio': 推荐对冲比例
'recommended_tool': 推荐工具
'rationale': 推荐理由
'matrix': 对冲比例 × 情景 的成本矩阵
"""
exposure = monthly_exposure or TYPICAL_EXPOSURE.get(industry, 20.0)
elasticity = COST_ELASTICITY.get(industry, 0.20)
is_upstream = industry == 'Upstream_OG'
# 情景定义
scenarios = {
'downside': pred_q10, # 下行风险(10%分位)
'base': pred_q50, # 基准
'upside': pred_q90, # 上行风险(90%分位)
}
# 对冲比例选项
hedge_ratios = [0.0, 0.25, 0.50, 0.75, 1.0]
# 计算矩阵
matrix = []
for ratio in hedge_ratios:
row = {'hedge_ratio': ratio, 'hedge_ratio_pct': f'{ratio*100:.0f}%'}
for scen_name, price_change in scenarios.items():
# 未对冲部分的损益
unhedged_impact = exposure * price_change * elasticity * (1 - ratio)
# 对冲部分:锁定成本,不受价格影响,但有对冲成本
hedge_cost = exposure * ratio * HEDGE_COST_RATES['futures']
# 总净影响 = 未对冲损益 - 对冲成本
net_impact = unhedged_impact - hedge_cost
# 上游油气反向:油价涨=收入增
if is_upstream:
net_impact = -net_impact # 对冲是锁定收入
row[f'{scen_name}_impact'] = round(net_impact, 2)
# VaR: 最大损失
row['worst_case'] = min(row['downside_impact'], row['upside_impact'])
row['best_case'] = max(row['downside_impact'], row['upside_impact'])
row['range'] = round(row['best_case'] - row['worst_case'], 2)
matrix.append(row)
# ── 推荐逻辑 ──
recommended_ratio, recommended_tool, rationale = _recommend(
risk_level, risk_bias, pred_vol, elasticity, is_upstream, pred_q10, pred_q90
)
# ── 各工具成本比较 ──
tool_comparison = []
for tool, rate in HEDGE_COST_RATES.items():
monthly_cost = exposure * recommended_ratio * rate
tool_comparison.append({
'tool': tool,
'tool_zh': {'futures': '期货锁价', 'put': '看跌期权', 'collar': '零成本领'}[tool],
'monthly_cost': round(monthly_cost, 2),
'annualized_cost': round(monthly_cost * 12, 2),
'cost_pct': round(rate * 100, 2),
})
return {
'industry': industry,
'industry_zh': INDUSTRY_ZH.get(industry, industry),
'exposure': exposure,
'elasticity': elasticity,
'recommended_ratio': recommended_ratio,
'recommended_ratio_pct': f'{recommended_ratio*100:.0f}%',
'recommended_tool': recommended_tool,
'rationale': rationale,
'matrix': matrix,
'tool_comparison': tool_comparison,
}
def _recommend(risk_level, risk_bias, pred_vol, elasticity, is_upstream, q10, q90):
"""推荐对冲比例和工具。"""
# 基础比例由风险等级决定
base_ratio = {'Low': 0.25, 'Medium': 0.50, 'High': 0.75}.get(risk_level, 0.50)
# 偏置调整
if is_upstream:
# 上游:下行=收入减少=需要对冲
if risk_bias == 'Downward':
base_ratio += 0.15
elif risk_bias == 'Upward':
base_ratio -= 0.10
else:
# 下游/成本端:上行=成本增加=需要对冲
if risk_bias == 'Upward':
base_ratio += 0.15
elif risk_bias == 'Downward':
base_ratio -= 0.10
# 波动率调整
if pred_vol > 0.08:
base_ratio += 0.10 # 高波动 → 多对冲
# 弹性调整:暴露越大越应该对冲
if abs(elasticity) > 0.30:
base_ratio += 0.05
# 尾部风险调整
tail_risk = abs(q10) if not is_upstream else abs(q90)
if tail_risk > 0.15: # 尾部超过15%
base_ratio += 0.10
base_ratio = max(0.0, min(1.0, round(base_ratio / 0.05) * 0.05)) # 5%步进
# 工具推荐
if risk_level == 'High' and pred_vol > 0.06:
tool = 'collar'
reason = f'高风险+高波动环境,零成本领策略平衡保护与成本'
elif risk_bias == 'Upward' and not is_upstream:
tool = 'futures'
reason = f'上行偏置明显,期货锁价直接锁定成本'
elif risk_bias == 'Downward' and is_upstream:
tool = 'put'
reason = f'下行风险突出,看跌期权保留上行收益空间'
elif pred_vol < 0.04:
tool = 'futures'
reason = f'低波动环境,简单期货锁价成本最低'
else:
tool = 'collar'
reason = f'均衡环境下零成本领提供灵活保护'
risk_zh = {'Low': '低', 'Medium': '中等', 'High': '高'}[risk_level]
bias_zh = {'Upward': '上行', 'Downward': '下行', 'Balanced': '均衡'}[risk_bias]
rationale = (
f"当前风险{risk_zh}、偏置{bias_zh}、预测波动率{pred_vol*100:.1f}%。"
f"建议对冲{base_ratio*100:.0f}%暴露。{reason}。"
)
return base_ratio, tool, rationale
def compute_all_industry_hedges(row):
"""为所有行业计算对冲建议。"""
results = {}
for ind in INDUSTRIES:
results[ind] = compute_hedge_matrix(
pred_q10=row.get('pred_q10_1m', -0.10),
pred_q50=row.get('pred_q50_1m', 0.0),
pred_q90=row.get('pred_q90_1m', 0.10),
pred_vol=row.get('pred_vol', 0.05),
risk_level=row.get('risk_level', 'Medium'),
risk_bias=row.get('risk_bias', 'Balanced'),
industry=ind,
)
return results
# ═══════════════════════════════════════════════════════════
# HEDGING BACKTEST
# ═══════════════════════════════════════════════════════════
def backtest_hedging(results_df, lookback=60):
"""
回测对冲策略:逐月计算 "按推荐比例对冲" vs "完全不对冲" 的累计成本差异。
Parameters
----------
results_df : DataFrame
walk-forward 预测结果(含 risk_level, risk_bias, pred_vol, actual_ret_1m 等)
lookback : int
回测月数(默认 60 个月)
Returns
-------
dict: 各行业的月度时间序列 + 累计节省金额
"""
import pandas as pd
df = results_df.tail(lookback).copy()
backtest = {}
for ind in INDUSTRIES:
exposure = TYPICAL_EXPOSURE.get(ind, 20.0)
elasticity = COST_ELASTICITY.get(ind, 0.20)
is_upstream = ind == 'Upstream_OG'
tool_rate = HEDGE_COST_RATES['futures']
monthly = []
cum_unhedged = 0.0
cum_hedged = 0.0
for _, row in df.iterrows():
actual_ret = row.get('actual_ret_1m', 0)
if np.isnan(actual_ret):
continue
# Determine recommended hedge ratio for this month
rl = row.get('risk_level', 'Medium')
rb = row.get('risk_bias', 'Balanced')
pv = row.get('pred_vol', 0.05)
q10 = row.get('pred_q10_1m', -0.10)
q90 = row.get('pred_q90_1m', 0.10)
ratio, _, _ = _recommend(rl, rb, pv, elasticity, is_upstream, q10, q90)
# Unhedged P&L: full exposure to price change
price_impact = actual_ret * elasticity
if is_upstream:
# Upstream: revenue = price * volume. Price up = good.
unhedged_pnl = exposure * actual_ret # Simplified: revenue change
hedged_pnl = exposure * actual_ret * (1 - ratio) - exposure * ratio * tool_rate
else:
# Downstream: cost = price * consumption. Price up = bad.
unhedged_pnl = -exposure * actual_ret * elasticity
hedged_pnl = -exposure * actual_ret * elasticity * (1 - ratio) - exposure * ratio * tool_rate
cum_unhedged += unhedged_pnl
cum_hedged += hedged_pnl
saving = cum_unhedged - cum_hedged # Positive = hedging saved money
monthly.append({
'date': str(row.get('test_date', '')),
'actual_ret': round(float(actual_ret) * 100, 2),
'hedge_ratio': round(ratio, 2),
'risk_level': rl,
'unhedged_pnl': round(unhedged_pnl, 2),
'hedged_pnl': round(hedged_pnl, 2),
'cum_unhedged': round(cum_unhedged, 2),
'cum_hedged': round(cum_hedged, 2),
'cum_saving': round(saving, 2),
})
# Summary stats
total_saving = cum_unhedged - cum_hedged
# Volatility reduction
unhedged_vol = np.std([m['unhedged_pnl'] for m in monthly]) if monthly else 0
hedged_vol = np.std([m['hedged_pnl'] for m in monthly]) if monthly else 0
vol_reduction = 1 - hedged_vol / unhedged_vol if unhedged_vol > 0 else 0
# Max drawdown
max_dd_unhedged = min(m['cum_unhedged'] for m in monthly) if monthly else 0
max_dd_hedged = min(m['cum_hedged'] for m in monthly) if monthly else 0
backtest[ind] = {
'industry_zh': INDUSTRY_ZH.get(ind, ind),
'months': len(monthly),
'total_saving': round(total_saving, 2),
'vol_reduction': round(vol_reduction * 100, 1),
'max_dd_unhedged': round(max_dd_unhedged, 2),
'max_dd_hedged': round(max_dd_hedged, 2),
'dd_improvement': round(max_dd_hedged - max_dd_unhedged, 2),
'monthly': monthly,
}
return backtest
|