""" 期权盒式套利 (Box Spread Arbitrage)。 盒式套利利用不同执行价格的看涨和看跌期权构建合成远期合约, 当合成远期价格偏离理论值时产生套利机会。 盒式套利组合: Long Box: 买入低执行价合成远期 + 卖出高执行价合成远期 Short Box: 卖出低执行价合成远期 + 买入高执行价合成远期 无风险收益: 收益 = K_high - K_low (到期时确定) 成本 = 期权权利金差额 利润 = (K_high - K_low) - 成本 适用场景: - 利率套利 (盒式价格反映隐含利率) - 边界情况套利 """ from dataclasses import dataclass from datetime import datetime, timezone from typing import Dict, List, Optional import numpy as np def _time_to_expiry_years(expiry: datetime) -> float: """Return time-to-expiry in years using full timestamp precision.""" if expiry.tzinfo is None: expiry = expiry.replace(tzinfo=timezone.utc) now = datetime.now(timezone.utc) return max(0.0, (expiry - now).total_seconds() / (365.0 * 24 * 3600)) def _select_box_trade_side( *, net_premium: float, payoff: float, fees: float ) -> Optional[tuple[str, float, float, float]]: long_cost = net_premium + fees long_profit = payoff - long_cost short_profit = (net_premium - fees) - payoff if long_profit <= 0 and short_profit <= 0: return None if short_profit > long_profit: return ( "short_box", float(short_profit), float(-net_premium + fees), float(max(payoff, 1e-12)), ) return ( "long_box", float(long_profit), float(long_cost), float(max(long_cost, 1e-12)), ) def _annualized_box_return( *, profit: float, net_cost: float, capital_base: float, time_to_expiry: float ) -> float: if net_cost <= 0: return float("inf") return float(profit / capital_base / time_to_expiry) def _iter_strike_pairs(strikes: List[float]): for index, low_k in enumerate(strikes): for high_k in strikes[index + 1:]: yield low_k, high_k def _box_leg_prices( option_chain: Dict[float, Dict[str, float]], *, low_k: float, high_k: float, ) -> Optional[Dict[str, float]]: low_chain = option_chain.get(low_k, {}) high_chain = option_chain.get(high_k, {}) prices = { "call_low": low_chain.get("call", 0), "put_low": low_chain.get("put", 0), "call_high": high_chain.get("call", 0), "put_high": high_chain.get("put", 0), } if any(price == 0 for price in prices.values()): return None return prices @dataclass class BoxSpread: """盒式套利组合。""" low_strike: float high_strike: float expiry: datetime # 低执行价合成远期: 买入看涨 + 卖出看跌 long_call_low: float # 买入低执行价看涨 short_put_low: float # 卖出低执行价看跌 # 高执行价合成远期: 卖出看涨 + 买入看跌 short_call_high: float # 卖出高执行价看涨 long_put_high: float # 买入高执行价看跌 @property def net_premium(self) -> float: """净权利金支出 (成本)。""" return (self.long_call_low - self.short_put_low - self.short_call_high + self.long_put_high) @property def box_value_at_expiry(self) -> float: """到期时盒式组合价值 (确定值)。""" return self.high_strike - self.low_strike @property def profit(self) -> float: """套利利润。""" return self.box_value_at_expiry - self.net_premium @property def implied_rate(self) -> float: """隐含的无风险利率。""" spread_width = self.high_strike - self.low_strike if spread_width <= 0: return 0.0 if self.net_premium <= 0: return 0.0 T = _time_to_expiry_years(self.expiry) if T <= 0: return 0.0 # Box no-arbitrage relation: premium = (K_high - K_low) * exp(-rT) ratio = self.net_premium / spread_width if ratio <= 0: return 0.0 r = -np.log(ratio) / T return float(r) @dataclass class BoxOpportunity: """盒式套利机会。""" underlying: str expiry: datetime low_strike: float high_strike: float net_cost: float payoff: float profit: float implied_rate: float annualized_return: float box_type: str = "long_box" liquidity_score: float = 1.0 class OptionBoxArbitrage: """ 期权盒式套利策略。 识别盒式价格偏离理论值的套利机会。 """ def __init__( self, risk_free_rate: float = 0.05, min_annualized_return: float = 0.02, # 2% 最小年化 transaction_cost_per_leg: float = 0.001, # 每条腿成本 min_leg_liquidity: float = 1.0, ): self.risk_free_rate = risk_free_rate self.min_annualized_return = min_annualized_return self.transaction_cost = transaction_cost_per_leg self.min_leg_liquidity = min_leg_liquidity def build_box( self, low_strike: float, high_strike: float, expiry: datetime, option_prices: Dict[str, float] ) -> Optional[BoxSpread]: """构建盒式套利组合,要求四腿期权价格齐备且执行价有效。""" required = ['call_low', 'put_low', 'call_high', 'put_high'] if not all(k in option_prices for k in required): return None if low_strike <= 0 or high_strike <= 0 or low_strike >= high_strike: return None if any(not np.isfinite(option_prices[k]) or option_prices[k] < 0 for k in required): return None return BoxSpread( low_strike=low_strike, high_strike=high_strike, expiry=expiry, long_call_low=option_prices['call_low'], short_put_low=option_prices['put_low'], short_call_high=option_prices['call_high'], long_put_high=option_prices['put_high'] ) def find_arbitrage( self, box: BoxSpread, underlying: str = "", liquidity_score: float = 1.0 ) -> Optional[BoxOpportunity]: """检查盒式组合是否存在套利机会。""" fees = 4 * self.transaction_cost payoff = box.box_value_at_expiry selected = _select_box_trade_side(net_premium=box.net_premium, payoff=payoff, fees=fees) if selected is None: return None box_type, profit, net_cost, capital_base = selected if (T := _time_to_expiry_years(box.expiry)) <= 0: return None annualized_return = _annualized_box_return( profit=profit, net_cost=net_cost, capital_base=capital_base, time_to_expiry=T ) if annualized_return < self.min_annualized_return: return None return BoxOpportunity( underlying=underlying, expiry=box.expiry, low_strike=box.low_strike, high_strike=box.high_strike, net_cost=net_cost, payoff=payoff, profit=profit, implied_rate=box.implied_rate, annualized_return=annualized_return, box_type=box_type, liquidity_score=liquidity_score ) def _liquidity_score(self, option_chain: Dict[float, Dict[str, float]], low_k: float, high_k: float) -> float: """Score leg liquidity using provided call/put sizes.""" low = option_chain.get(low_k, {}) high = option_chain.get(high_k, {}) legs = [ float(low.get("call_size", low.get("size", 0.0))), float(low.get("put_size", low.get("size", 0.0))), float(high.get("call_size", high.get("size", 0.0))), float(high.get("put_size", high.get("size", 0.0))), ] if any(v <= 0 for v in legs): return 0.0 return float(min(1.0, min(legs) / max(self.min_leg_liquidity, 1e-12))) def scan_strikes( self, strikes: List[float], expiry: datetime, option_chain: Dict[float, Dict[str, float]], underlying: str = "" ) -> List[BoxOpportunity]: """扫描执行价组合并返回可交易盒式套利机会。""" opportunities = [] for low_k, high_k in _iter_strike_pairs(strikes): prices = _box_leg_prices(option_chain, low_k=low_k, high_k=high_k) if prices is None: continue box = self.build_box(low_k, high_k, expiry, prices) if box is None: continue liquidity_score = self._liquidity_score(option_chain, low_k, high_k) if liquidity_score <= 0: continue opp = self.find_arbitrage(box, underlying, liquidity_score=liquidity_score) if opp is not None: opportunities.append(opp) opportunities.sort(key=lambda x: x.annualized_return, reverse=True) return opportunities def calculate_boundaries(self, box: BoxSpread) -> Dict[str, float]: """ 计算盒式套利的边界条件。 根据无套利原理: - 盒式价格应在 [0, K_high - K_low] 之间 - 盒式价格反映隐含利率 """ max_value = box.high_strike - box.low_strike min_value = 0 # 考虑利率的边界 T = _time_to_expiry_years(box.expiry) # 理论价格 (无套利) theoretical_price = max_value * np.exp(-self.risk_free_rate * T) return { 'min_price': min_value, 'max_price': max_value, 'theoretical_price': theoretical_price, 'actual_price': box.net_premium, 'deviation': box.net_premium - theoretical_price, 'arbitrage_free_range': ( theoretical_price * 0.99, theoretical_price * 1.01 ) }