Spaces:
Build error
Build error
| """ | |
| 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 | |