|
@@ -0,0 +1,1248 @@
|
|
|
|
|
+# 深度实值买购和卖购组合的牛差策略
|
|
|
|
|
+# 参考文档: Lib/Options/README.md - 策略1
|
|
|
|
|
+
|
|
|
|
|
+from jqdata import *
|
|
|
|
|
+import pandas as pd
|
|
|
|
|
+import numpy as np
|
|
|
|
|
+import matplotlib.pyplot as plt
|
|
|
|
|
+import tqdm
|
|
|
|
|
+from datetime import datetime, timedelta
|
|
|
|
|
+import warnings
|
|
|
|
|
+warnings.filterwarnings('ignore')
|
|
|
|
|
+
|
|
|
|
|
+class DeepITMBullSpreadStrategy:
|
|
|
|
|
+ """深度实值买购和卖购组合的牛差策略"""
|
|
|
|
|
+
|
|
|
|
|
+ def __init__(self, underlying='510300.XSHG', start_date='2024-01-01', end_date='2025-06-30'):
|
|
|
|
|
+ self.underlying = underlying # 标的ETF
|
|
|
|
|
+ self.start_date = start_date
|
|
|
|
|
+ self.end_date = end_date
|
|
|
|
|
+
|
|
|
|
|
+ # 策略参数设置
|
|
|
|
|
+ self.params = {
|
|
|
|
|
+ '一组张数': 30,
|
|
|
|
|
+ '最小权利金': {'沪深300ETF': 0.03, '上证50ETF': 0.05},
|
|
|
|
|
+ '最少开仓日期': 20, # 距离到期日天数
|
|
|
|
|
+ '买购时间价值阈值': 0.0001,
|
|
|
|
|
+ '卖购平仓权利金阈值': 0.0005,
|
|
|
|
|
+ '合约到期移仓日期最大': 7, # 交易日
|
|
|
|
|
+ '合约到期移仓日期最小': 2, # 交易日
|
|
|
|
|
+ '加仓窗口阈值': {'沪深300ETF': 0.2, '上证50ETF': 0.1}
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ # 交易记录
|
|
|
|
|
+ self.positions = [] # 持仓记录
|
|
|
|
|
+ self.trades = [] # 交易记录
|
|
|
|
|
+ self.daily_pnl = [] # 每日损益
|
|
|
|
|
+
|
|
|
|
|
+ # 获取交易日历
|
|
|
|
|
+ self.trade_days = get_trade_days(start_date, end_date)
|
|
|
|
|
+
|
|
|
|
|
+ def get_etf_name(self):
|
|
|
|
|
+ """根据标的代码获取ETF名称"""
|
|
|
|
|
+ if self.underlying == '510300.XSHG':
|
|
|
|
|
+ return '沪深300ETF'
|
|
|
|
|
+ elif self.underlying == '510050.XSHG':
|
|
|
|
|
+ return '上证50ETF'
|
|
|
|
|
+ else:
|
|
|
|
|
+ return '未知ETF'
|
|
|
|
|
+
|
|
|
|
|
+ def get_option_contracts(self, date, contract_type='CO', min_days_to_expire=20):
|
|
|
|
|
+ """获取期权合约信息"""
|
|
|
|
|
+ # 查询期权合约
|
|
|
|
|
+ q = query(opt.OPT_CONTRACT_INFO.code,
|
|
|
|
|
+ opt.OPT_CONTRACT_INFO.trading_code,
|
|
|
|
|
+ opt.OPT_CONTRACT_INFO.name,
|
|
|
|
|
+ opt.OPT_CONTRACT_INFO.exercise_price,
|
|
|
|
|
+ opt.OPT_CONTRACT_INFO.last_trade_date,
|
|
|
|
|
+ opt.OPT_CONTRACT_INFO.list_date
|
|
|
|
|
+ ).filter(
|
|
|
|
|
+ opt.OPT_CONTRACT_INFO.underlying_symbol == self.underlying,
|
|
|
|
|
+ opt.OPT_CONTRACT_INFO.contract_type == contract_type,
|
|
|
|
|
+ opt.OPT_CONTRACT_INFO.list_date <= date,
|
|
|
|
|
+ opt.OPT_CONTRACT_INFO.last_trade_date > date
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ contracts = opt.run_query(q)
|
|
|
|
|
+ print(f" 查询到{len(contracts)}个{contract_type}类型的期权合约")
|
|
|
|
|
+
|
|
|
|
|
+ # 过滤距离到期日至少min_days_to_expire天的合约
|
|
|
|
|
+ valid_contracts = []
|
|
|
|
|
+ for _, contract in contracts.iterrows():
|
|
|
|
|
+ days_to_expire = (pd.to_datetime(contract['last_trade_date']) - pd.to_datetime(date)).days
|
|
|
|
|
+ if days_to_expire >= min_days_to_expire:
|
|
|
|
|
+ valid_contracts.append(contract)
|
|
|
|
|
+
|
|
|
|
|
+ print(f" 过滤后剩余{len(valid_contracts)}个距离到期日至少{min_days_to_expire}天的合约")
|
|
|
|
|
+ return pd.DataFrame(valid_contracts) if valid_contracts else pd.DataFrame()
|
|
|
|
|
+
|
|
|
|
|
+ def get_option_price(self, option_code, date):
|
|
|
|
|
+ """获取期权价格"""
|
|
|
|
|
+ try:
|
|
|
|
|
+ q = query(opt.OPT_DAILY_PRICE.close).filter(
|
|
|
|
|
+ opt.OPT_DAILY_PRICE.code == option_code,
|
|
|
|
|
+ opt.OPT_DAILY_PRICE.date == date
|
|
|
|
|
+ )
|
|
|
|
|
+ result = opt.run_query(q)
|
|
|
|
|
+ return result['close'].iloc[0] if not result.empty else None
|
|
|
|
|
+ except:
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+ def calculate_time_value(self, option_price, intrinsic_value):
|
|
|
|
|
+ """计算时间价值"""
|
|
|
|
|
+ return max(0, option_price - max(0, intrinsic_value))
|
|
|
|
|
+
|
|
|
|
|
+ def select_call_option_to_buy(self, date, etf_price):
|
|
|
|
|
+ """选择深度实值买购期权"""
|
|
|
|
|
+ contracts = self.get_option_contracts(date, 'CO')
|
|
|
|
|
+ if contracts.empty:
|
|
|
|
|
+ print(f" 买购选择失败: 未找到任何认购期权合约")
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+ print(f" 找到{len(contracts)}个认购期权合约")
|
|
|
|
|
+
|
|
|
|
|
+ # 筛选深度实值期权(时间价值 <= 阈值)
|
|
|
|
|
+ suitable_contracts = []
|
|
|
|
|
+ checked_count = 0
|
|
|
|
|
+ for _, contract in contracts.iterrows():
|
|
|
|
|
+ option_price = self.get_option_price(contract['code'], date)
|
|
|
|
|
+ checked_count += 1
|
|
|
|
|
+ if option_price is None:
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ intrinsic_value = max(0, etf_price - contract['exercise_price'])
|
|
|
|
|
+ time_value = self.calculate_time_value(option_price, intrinsic_value)
|
|
|
|
|
+
|
|
|
|
|
+ if time_value <= self.params['买购时间价值阈值']:
|
|
|
|
|
+ suitable_contracts.append({
|
|
|
|
|
+ 'code': contract['code'],
|
|
|
|
|
+ 'exercise_price': contract['exercise_price'],
|
|
|
|
|
+ 'option_price': option_price,
|
|
|
|
|
+ 'time_value': time_value,
|
|
|
|
|
+ 'last_trade_date': contract['last_trade_date']
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ print(f" 检查了{checked_count}个合约,找到{len(suitable_contracts)}个符合时间价值条件的买购期权")
|
|
|
|
|
+
|
|
|
|
|
+ # 选择时间价值最小的
|
|
|
|
|
+ if suitable_contracts:
|
|
|
|
|
+ return min(suitable_contracts, key=lambda x: x['time_value'])
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+ def select_call_option_to_sell(self, date, etf_price):
|
|
|
|
|
+ """选择平值卖购期权"""
|
|
|
|
|
+ contracts = self.get_option_contracts(date, 'CO')
|
|
|
|
|
+ if contracts.empty:
|
|
|
|
|
+ print(f" 卖购选择失败: 未找到任何认购期权合约")
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+ # 找到最接近平值的期权
|
|
|
|
|
+ etf_name = self.get_etf_name()
|
|
|
|
|
+ min_premium = self.params['最小权利金'].get(etf_name, 0.03)
|
|
|
|
|
+ print(f" 最小权利金要求: {min_premium}")
|
|
|
|
|
+
|
|
|
|
|
+ suitable_contracts = []
|
|
|
|
|
+ checked_count = 0
|
|
|
|
|
+ for _, contract in contracts.iterrows():
|
|
|
|
|
+ option_price = self.get_option_price(contract['code'], date)
|
|
|
|
|
+ checked_count += 1
|
|
|
|
|
+ if option_price is None or option_price < min_premium:
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ price_diff = abs(contract['exercise_price'] - etf_price)
|
|
|
|
|
+ suitable_contracts.append({
|
|
|
|
|
+ 'code': contract['code'],
|
|
|
|
|
+ 'exercise_price': contract['exercise_price'],
|
|
|
|
|
+ 'option_price': option_price,
|
|
|
|
|
+ 'price_diff': price_diff,
|
|
|
|
|
+ 'last_trade_date': contract['last_trade_date']
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ print(f" 检查了{checked_count}个合约,找到{len(suitable_contracts)}个符合权利金条件的卖购期权")
|
|
|
|
|
+
|
|
|
|
|
+ # 选择最接近平值且权利金较高的
|
|
|
|
|
+ if suitable_contracts:
|
|
|
|
|
+ # 先按价格差排序,再按权利金排序
|
|
|
|
|
+ suitable_contracts.sort(key=lambda x: (x['price_diff'], -x['option_price']))
|
|
|
|
|
+ return suitable_contracts[0]
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+ def open_bull_spread_position(self, date, etf_price, quantity=None):
|
|
|
|
|
+ """开仓牛差组合"""
|
|
|
|
|
+ if quantity is None:
|
|
|
|
|
+ quantity = self.params['一组张数']
|
|
|
|
|
+
|
|
|
|
|
+ print(f"尝试开仓: 日期={date}, ETF价格={etf_price:.4f}")
|
|
|
|
|
+
|
|
|
|
|
+ # 选择买购期权(深度实值)
|
|
|
|
|
+ buy_call = self.select_call_option_to_buy(date, etf_price)
|
|
|
|
|
+ if buy_call is None:
|
|
|
|
|
+ print(f" 失败: 未找到合适的买购期权")
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+ print(f" 找到买购期权: 代码={buy_call['code']}, 行权价={buy_call['exercise_price']:.4f}, 权利金={buy_call['option_price']:.4f}")
|
|
|
|
|
+
|
|
|
|
|
+ # 选择卖购期权(平值)
|
|
|
|
|
+ sell_call = self.select_call_option_to_sell(date, etf_price)
|
|
|
|
|
+ if sell_call is None:
|
|
|
|
|
+ print(f" 失败: 未找到合适的卖购期权")
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+ print(f" 找到卖购期权: 代码={sell_call['code']}, 行权价={sell_call['exercise_price']:.4f}, 权利金={sell_call['option_price']:.4f}")
|
|
|
|
|
+
|
|
|
|
|
+ # 确保买购行权价 < 卖购行权价
|
|
|
|
|
+ if buy_call['exercise_price'] >= sell_call['exercise_price']:
|
|
|
|
|
+ print(f" 失败: 买购行权价({buy_call['exercise_price']:.4f}) >= 卖购行权价({sell_call['exercise_price']:.4f})")
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+ # 计算单张最大盈利和最小盈利
|
|
|
|
|
+ max_profit_per_contract = (sell_call['exercise_price'] - buy_call['exercise_price']
|
|
|
|
|
+ - buy_call['option_price'] + sell_call['option_price'])
|
|
|
|
|
+ min_profit_per_contract = sell_call['option_price']
|
|
|
|
|
+
|
|
|
|
|
+ position = {
|
|
|
|
|
+ 'open_date': date,
|
|
|
|
|
+ 'etf_price': etf_price,
|
|
|
|
|
+ 'buy_call_code': buy_call['code'],
|
|
|
|
|
+ 'buy_call_strike': buy_call['exercise_price'],
|
|
|
|
|
+ 'buy_call_price': buy_call['option_price'],
|
|
|
|
|
+ 'sell_call_code': sell_call['code'],
|
|
|
|
|
+ 'sell_call_strike': sell_call['exercise_price'],
|
|
|
|
|
+ 'sell_call_price': sell_call['option_price'],
|
|
|
|
|
+ 'quantity': quantity,
|
|
|
|
|
+ 'max_profit_per_contract': max_profit_per_contract,
|
|
|
|
|
+ 'min_profit_per_contract': min_profit_per_contract,
|
|
|
|
|
+ 'max_profit_total': max_profit_per_contract * quantity * 10000,
|
|
|
|
|
+ 'min_profit_total': min_profit_per_contract * quantity * 10000,
|
|
|
|
|
+ 'expire_date': sell_call['last_trade_date'],
|
|
|
|
|
+ 'status': 'open',
|
|
|
|
|
+ 'is_additional': False # 是否为加仓
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ self.positions.append(position)
|
|
|
|
|
+
|
|
|
|
|
+ # 记录交易
|
|
|
|
|
+ self.trades.append({
|
|
|
|
|
+ 'date': date,
|
|
|
|
|
+ 'action': 'open_bull_spread',
|
|
|
|
|
+ 'details': position
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ print(f" 成功开仓: 数量={quantity}张, 最大盈利={max_profit_per_contract:.4f}元/张")
|
|
|
|
|
+
|
|
|
|
|
+ return position
|
|
|
|
|
+
|
|
|
|
|
+ def should_close_position(self, position, date, etf_price):
|
|
|
|
|
+ """判断是否应该平仓"""
|
|
|
|
|
+ # 1. 检查是否接近到期
|
|
|
|
|
+ days_to_expire = (pd.to_datetime(position['expire_date']) - pd.to_datetime(date)).days
|
|
|
|
|
+ if days_to_expire <= self.params['合约到期移仓日期最大']:
|
|
|
|
|
+ return True, "approaching_expiry"
|
|
|
|
|
+
|
|
|
|
|
+ # 2. 检查ETF大涨情况(接近最大盈利)
|
|
|
|
|
+ current_max_profit = max(0, min(etf_price - position['buy_call_strike'],
|
|
|
|
|
+ position['sell_call_strike'] - position['buy_call_strike']))
|
|
|
|
|
+ if current_max_profit >= position['max_profit_per_contract'] * 0.9:
|
|
|
|
|
+ return True, "max_profit_reached"
|
|
|
|
|
+
|
|
|
|
|
+ # 3. 检查ETF下跌情况(卖购权利金剩余低于阈值)
|
|
|
|
|
+ sell_call_price = self.get_option_price(position['sell_call_code'], date)
|
|
|
|
|
+ if sell_call_price is not None and sell_call_price <= self.params['卖购平仓权利金阈值']:
|
|
|
|
|
+ return True, "etf_declined"
|
|
|
|
|
+
|
|
|
|
|
+ return False, None
|
|
|
|
|
+
|
|
|
|
|
+ def close_position(self, position, date, reason):
|
|
|
|
|
+ """平仓操作"""
|
|
|
|
|
+ buy_call_price = self.get_option_price(position['buy_call_code'], date)
|
|
|
|
|
+ sell_call_price = self.get_option_price(position['sell_call_code'], date)
|
|
|
|
|
+
|
|
|
|
|
+ if buy_call_price is None or sell_call_price is None:
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+ # 计算平仓收益
|
|
|
|
|
+ buy_call_pnl = (buy_call_price - position['buy_call_price']) * position['quantity'] * 10000
|
|
|
|
|
+ sell_call_pnl = (position['sell_call_price'] - sell_call_price) * position['quantity'] * 10000
|
|
|
|
|
+ total_pnl = buy_call_pnl + sell_call_pnl
|
|
|
|
|
+
|
|
|
|
|
+ # 更新持仓状态
|
|
|
|
|
+ position['status'] = 'closed'
|
|
|
|
|
+ position['close_date'] = date
|
|
|
|
|
+ position['close_reason'] = reason
|
|
|
|
|
+ position['close_buy_call_price'] = buy_call_price
|
|
|
|
|
+ position['close_sell_call_price'] = sell_call_price
|
|
|
|
|
+ position['realized_pnl'] = total_pnl
|
|
|
|
|
+
|
|
|
|
|
+ # 记录交易
|
|
|
|
|
+ self.trades.append({
|
|
|
|
|
+ 'date': date,
|
|
|
|
|
+ 'action': 'close_bull_spread',
|
|
|
|
|
+ 'reason': reason,
|
|
|
|
|
+ 'pnl': total_pnl,
|
|
|
|
|
+ 'details': position
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ return total_pnl
|
|
|
|
|
+
|
|
|
|
|
+ def should_add_position(self, etf_price):
|
|
|
|
|
+ """判断是否应该加仓"""
|
|
|
|
|
+ # 检查是否有开仓的主仓位
|
|
|
|
|
+ main_positions = [p for p in self.positions if p['status'] == 'open' and not p['is_additional']]
|
|
|
|
|
+ if not main_positions:
|
|
|
|
|
+ return False
|
|
|
|
|
+
|
|
|
|
|
+ # 获取最近的主仓位
|
|
|
|
|
+ latest_main = max(main_positions, key=lambda x: x['open_date'])
|
|
|
|
|
+
|
|
|
|
|
+ # 检查价格下跌幅度
|
|
|
|
|
+ etf_name = self.get_etf_name()
|
|
|
|
|
+ threshold = self.params['加仓窗口阈值'].get(etf_name, 0.2)
|
|
|
|
|
+
|
|
|
|
|
+ if latest_main['etf_price'] - etf_price >= threshold:
|
|
|
|
|
+ # 检查是否已经在这个价格水平加过仓
|
|
|
|
|
+ existing_additional = [p for p in self.positions
|
|
|
|
|
+ if p['status'] == 'open' and p['is_additional']
|
|
|
|
|
+ and abs(p['etf_price'] - etf_price) < threshold * 0.5]
|
|
|
|
|
+ if not existing_additional:
|
|
|
|
|
+ return True
|
|
|
|
|
+
|
|
|
|
|
+ return False
|
|
|
|
|
+
|
|
|
|
|
+ def run_backtest(self):
|
|
|
|
|
+ """运行回测"""
|
|
|
|
|
+ print(f"开始回测: {self.start_date} 到 {self.end_date}")
|
|
|
|
|
+ print(f"标的: {self.underlying} ({self.get_etf_name()})")
|
|
|
|
|
+
|
|
|
|
|
+ # 获取ETF价格数据
|
|
|
|
|
+ etf_prices = get_price(self.underlying, self.start_date, self.end_date, fields=['close'])['close']
|
|
|
|
|
+
|
|
|
|
|
+ # 初始化
|
|
|
|
|
+ portfolio_value = []
|
|
|
|
|
+ etf_benchmark = []
|
|
|
|
|
+
|
|
|
|
|
+ with tqdm.tqdm(self.trade_days, desc="回测进度") as pbar:
|
|
|
|
|
+ for date in pbar:
|
|
|
|
|
+ if date not in etf_prices.index:
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ etf_price = etf_prices[date]
|
|
|
|
|
+
|
|
|
|
|
+ # 检查现有持仓是否需要平仓
|
|
|
|
|
+ open_positions = [p for p in self.positions if p['status'] == 'open']
|
|
|
|
|
+ for position in open_positions:
|
|
|
|
|
+ should_close, reason = self.should_close_position(position, date, etf_price)
|
|
|
|
|
+ if should_close:
|
|
|
|
|
+ self.close_position(position, date, reason)
|
|
|
|
|
+
|
|
|
|
|
+ # 检查是否需要开新仓
|
|
|
|
|
+ open_positions = [p for p in self.positions if p['status'] == 'open']
|
|
|
|
|
+ if not open_positions: # 没有持仓时开仓
|
|
|
|
|
+ self.open_bull_spread_position(date, etf_price)
|
|
|
|
|
+ elif self.should_add_position(etf_price): # 加仓
|
|
|
|
|
+ additional_pos = self.open_bull_spread_position(date, etf_price)
|
|
|
|
|
+ if additional_pos:
|
|
|
|
|
+ additional_pos['is_additional'] = True
|
|
|
|
|
+
|
|
|
|
|
+ # 计算当日组合价值
|
|
|
|
|
+ daily_pnl = self.calculate_daily_pnl(date)
|
|
|
|
|
+ portfolio_value.append(daily_pnl)
|
|
|
|
|
+
|
|
|
|
|
+ # ETF基准收益
|
|
|
|
|
+ if len(etf_benchmark) == 0:
|
|
|
|
|
+ etf_benchmark.append(0)
|
|
|
|
|
+ else:
|
|
|
|
|
+ etf_return = (etf_price / etf_prices[self.trade_days[0]] - 1) * 100000 # 假设初始投资10万
|
|
|
|
|
+ etf_benchmark.append(etf_return)
|
|
|
|
|
+
|
|
|
|
|
+ # 生成回测报告
|
|
|
|
|
+ self.generate_detailed_report()
|
|
|
|
|
+
|
|
|
|
|
+ return portfolio_value, etf_benchmark
|
|
|
|
|
+
|
|
|
|
|
+ def calculate_daily_pnl(self, date):
|
|
|
|
|
+ """计算每日损益"""
|
|
|
|
|
+ total_pnl = 0
|
|
|
|
|
+
|
|
|
|
|
+ for position in self.positions:
|
|
|
|
|
+ if position['status'] == 'closed':
|
|
|
|
|
+ if 'realized_pnl' in position:
|
|
|
|
|
+ total_pnl += position['realized_pnl']
|
|
|
|
|
+ elif position['status'] == 'open':
|
|
|
|
|
+ # 计算未实现损益
|
|
|
|
|
+ buy_call_price = self.get_option_price(position['buy_call_code'], date)
|
|
|
|
|
+ sell_call_price = self.get_option_price(position['sell_call_code'], date)
|
|
|
|
|
+
|
|
|
|
|
+ if buy_call_price is not None and sell_call_price is not None:
|
|
|
|
|
+ buy_call_pnl = (buy_call_price - position['buy_call_price']) * position['quantity'] * 10000
|
|
|
|
|
+ sell_call_pnl = (position['sell_call_price'] - sell_call_price) * position['quantity'] * 10000
|
|
|
|
|
+ total_pnl += buy_call_pnl + sell_call_pnl
|
|
|
|
|
+
|
|
|
|
|
+ return total_pnl
|
|
|
|
|
+
|
|
|
|
|
+ def export_data_to_csv(self, filename_prefix="bull_spread_data"):
|
|
|
|
|
+ """导出数据到CSV文件,用于线下分析"""
|
|
|
|
|
+
|
|
|
|
|
+ # 1. 导出持仓数据
|
|
|
|
|
+ positions_data = []
|
|
|
|
|
+ for pos in self.positions:
|
|
|
|
|
+ positions_data.append({
|
|
|
|
|
+ 'open_date': pos['open_date'],
|
|
|
|
|
+ 'etf_price': pos['etf_price'],
|
|
|
|
|
+ 'buy_call_code': pos['buy_call_code'],
|
|
|
|
|
+ 'buy_call_strike': pos['buy_call_strike'],
|
|
|
|
|
+ 'buy_call_price': pos['buy_call_price'],
|
|
|
|
|
+ 'sell_call_code': pos['sell_call_code'],
|
|
|
|
|
+ 'sell_call_strike': pos['sell_call_strike'],
|
|
|
|
|
+ 'sell_call_price': pos['sell_call_price'],
|
|
|
|
|
+ 'quantity': pos['quantity'],
|
|
|
|
|
+ 'max_profit_total': pos['max_profit_total'],
|
|
|
|
|
+ 'expire_date': pos['expire_date'],
|
|
|
|
|
+ 'status': pos['status'],
|
|
|
|
|
+ 'is_additional': pos['is_additional'],
|
|
|
|
|
+ 'close_date': pos.get('close_date', ''),
|
|
|
|
|
+ 'close_reason': pos.get('close_reason', ''),
|
|
|
|
|
+ 'realized_pnl': pos.get('realized_pnl', 0)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ positions_df = pd.DataFrame(positions_data)
|
|
|
|
|
+ positions_df.to_csv(f"{filename_prefix}_positions.csv", index=False, encoding='utf-8-sig')
|
|
|
|
|
+
|
|
|
|
|
+ # 2. 导出交易记录
|
|
|
|
|
+ trades_data = []
|
|
|
|
|
+ for trade in self.trades:
|
|
|
|
|
+ trades_data.append({
|
|
|
|
|
+ 'date': trade['date'],
|
|
|
|
|
+ 'action': trade['action'],
|
|
|
|
|
+ 'pnl': trade.get('pnl', 0),
|
|
|
|
|
+ 'reason': trade.get('reason', ''),
|
|
|
|
|
+ 'details': str(trade.get('details', ''))
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ trades_df = pd.DataFrame(trades_data)
|
|
|
|
|
+ trades_df.to_csv(f"{filename_prefix}_trades.csv", index=False, encoding='utf-8-sig')
|
|
|
|
|
+
|
|
|
|
|
+ # 3. 导出每日损益数据
|
|
|
|
|
+ daily_data = []
|
|
|
|
|
+ etf_prices = get_price(self.underlying, self.start_date, self.end_date, fields=['close'])['close']
|
|
|
|
|
+
|
|
|
|
|
+ for date in self.trade_days:
|
|
|
|
|
+ if date not in etf_prices.index:
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ etf_price = etf_prices[date]
|
|
|
|
|
+
|
|
|
|
|
+ # 计算买购和卖购分别的损益
|
|
|
|
|
+ buy_call_pnl = 0
|
|
|
|
|
+ sell_call_pnl = 0
|
|
|
|
|
+ total_positions = 0
|
|
|
|
|
+
|
|
|
|
|
+ for position in self.positions:
|
|
|
|
|
+ if position['open_date'] <= date and (position['status'] == 'open' or position.get('close_date', date) >= date):
|
|
|
|
|
+ total_positions += 1
|
|
|
|
|
+
|
|
|
|
|
+ if position['status'] == 'closed' and position.get('close_date') == date:
|
|
|
|
|
+ # 已平仓的实现损益
|
|
|
|
|
+ buy_call_pnl += position.get('realized_pnl', 0) / 2 # 简化分配
|
|
|
|
|
+ sell_call_pnl += position.get('realized_pnl', 0) / 2
|
|
|
|
|
+ elif position['status'] == 'open':
|
|
|
|
|
+ # 未实现损益
|
|
|
|
|
+ buy_call_price = self.get_option_price(position['buy_call_code'], date)
|
|
|
|
|
+ sell_call_price = self.get_option_price(position['sell_call_code'], date)
|
|
|
|
|
+
|
|
|
|
|
+ if buy_call_price is not None:
|
|
|
|
|
+ buy_call_pnl += (buy_call_price - position['buy_call_price']) * position['quantity'] * 10000
|
|
|
|
|
+
|
|
|
|
|
+ if sell_call_price is not None:
|
|
|
|
|
+ sell_call_pnl += (position['sell_call_price'] - sell_call_price) * position['quantity'] * 10000
|
|
|
|
|
+
|
|
|
|
|
+ # ETF基准收益
|
|
|
|
|
+ etf_return = (etf_price / etf_prices.iloc[0] - 1) * 100000 if len(etf_prices) > 0 else 0
|
|
|
|
|
+
|
|
|
|
|
+ daily_data.append({
|
|
|
|
|
+ 'date': date,
|
|
|
|
|
+ 'etf_price': etf_price,
|
|
|
|
|
+ 'etf_return': etf_return,
|
|
|
|
|
+ 'buy_call_pnl': buy_call_pnl,
|
|
|
|
|
+ 'sell_call_pnl': sell_call_pnl,
|
|
|
|
|
+ 'total_pnl': buy_call_pnl + sell_call_pnl,
|
|
|
|
|
+ 'total_positions': total_positions
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ daily_df = pd.DataFrame(daily_data)
|
|
|
|
|
+ daily_df.to_csv(f"{filename_prefix}_daily.csv", index=False, encoding='utf-8-sig')
|
|
|
|
|
+
|
|
|
|
|
+ print(f"数据已导出到以下文件:")
|
|
|
|
|
+ print(f"- {filename_prefix}_positions.csv (持仓数据)")
|
|
|
|
|
+ print(f"- {filename_prefix}_trades.csv (交易记录)")
|
|
|
|
|
+ print(f"- {filename_prefix}_daily.csv (每日损益)")
|
|
|
|
|
+
|
|
|
|
|
+ return positions_df, trades_df, daily_df
|
|
|
|
|
+
|
|
|
|
|
+ def generate_detailed_report(self):
|
|
|
|
|
+ """生成详细的分析报告,分别分析买购和卖购收益"""
|
|
|
|
|
+ print("\n" + "="*80)
|
|
|
|
|
+ print("深度实值牛差策略详细回测报告")
|
|
|
|
|
+ print("="*80)
|
|
|
|
|
+
|
|
|
|
|
+ # 获取ETF价格数据
|
|
|
|
|
+ etf_prices = get_price(self.underlying, self.start_date, self.end_date, fields=['close'])['close']
|
|
|
|
|
+
|
|
|
|
|
+ # 计算分项收益
|
|
|
|
|
+ buy_call_returns = []
|
|
|
|
|
+ sell_call_returns = []
|
|
|
|
|
+ combined_returns = []
|
|
|
|
|
+ etf_returns = []
|
|
|
|
|
+ dates = []
|
|
|
|
|
+
|
|
|
|
|
+ initial_etf_price = etf_prices.iloc[0] if len(etf_prices) > 0 else 1
|
|
|
|
|
+
|
|
|
|
|
+ for date in self.trade_days:
|
|
|
|
|
+ if date not in etf_prices.index:
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ etf_price = etf_prices[date]
|
|
|
|
|
+ dates.append(date)
|
|
|
|
|
+
|
|
|
|
|
+ # 计算买购和卖购分别的损益
|
|
|
|
|
+ buy_call_pnl = 0
|
|
|
|
|
+ sell_call_pnl = 0
|
|
|
|
|
+
|
|
|
|
|
+ for position in self.positions:
|
|
|
|
|
+ if position['open_date'] <= date:
|
|
|
|
|
+ if position['status'] == 'closed':
|
|
|
|
|
+ if 'realized_pnl' in position:
|
|
|
|
|
+ # 简化处理:假设买购和卖购各承担一半损益
|
|
|
|
|
+ buy_call_pnl += position['realized_pnl'] / 2
|
|
|
|
|
+ sell_call_pnl += position['realized_pnl'] / 2
|
|
|
|
|
+ elif position['status'] == 'open':
|
|
|
|
|
+ # 计算未实现损益
|
|
|
|
|
+ buy_call_price = self.get_option_price(position['buy_call_code'], date)
|
|
|
|
|
+ sell_call_price = self.get_option_price(position['sell_call_code'], date)
|
|
|
|
|
+
|
|
|
|
|
+ if buy_call_price is not None:
|
|
|
|
|
+ buy_call_pnl += (buy_call_price - position['buy_call_price']) * position['quantity'] * 10000
|
|
|
|
|
+
|
|
|
|
|
+ if sell_call_price is not None:
|
|
|
|
|
+ sell_call_pnl += (position['sell_call_price'] - sell_call_price) * position['quantity'] * 10000
|
|
|
|
|
+
|
|
|
|
|
+ buy_call_returns.append(buy_call_pnl)
|
|
|
|
|
+ sell_call_returns.append(sell_call_pnl)
|
|
|
|
|
+ combined_returns.append(buy_call_pnl + sell_call_pnl)
|
|
|
|
|
+
|
|
|
|
|
+ # ETF基准收益
|
|
|
|
|
+ etf_return = (etf_price / initial_etf_price - 1) * 100000 # 假设10万本金
|
|
|
|
|
+ etf_returns.append(etf_return)
|
|
|
|
|
+
|
|
|
|
|
+ # 基本统计
|
|
|
|
|
+ total_trades = len([t for t in self.trades if t['action'] == 'close_bull_spread'])
|
|
|
|
|
+ winning_trades = len([t for t in self.trades if t['action'] == 'close_bull_spread' and t['pnl'] > 0])
|
|
|
|
|
+
|
|
|
|
|
+ if total_trades > 0:
|
|
|
|
|
+ win_rate = winning_trades / total_trades * 100
|
|
|
|
|
+ total_pnl = sum([t['pnl'] for t in self.trades if t['action'] == 'close_bull_spread'])
|
|
|
|
|
+ avg_pnl = total_pnl / total_trades
|
|
|
|
|
+ else:
|
|
|
|
|
+ win_rate = 0
|
|
|
|
|
+ total_pnl = 0
|
|
|
|
|
+ avg_pnl = 0
|
|
|
|
|
+
|
|
|
|
|
+ print(f"交易次数: {total_trades}")
|
|
|
|
|
+ print(f"胜率: {win_rate:.2f}%")
|
|
|
|
|
+ print(f"总收益: {total_pnl:.2f}元")
|
|
|
|
|
+ print(f"平均每笔收益: {avg_pnl:.2f}元")
|
|
|
|
|
+
|
|
|
|
|
+ # 分项收益统计和对比分析
|
|
|
|
|
+ if len(buy_call_returns) > 0:
|
|
|
|
|
+ final_buy_call = buy_call_returns[-1]
|
|
|
|
|
+ final_sell_call = sell_call_returns[-1]
|
|
|
|
|
+ final_combined = combined_returns[-1]
|
|
|
|
|
+ final_etf = etf_returns[-1]
|
|
|
|
|
+
|
|
|
|
|
+ print(f"\n=== 分项收益分析 ===")
|
|
|
|
|
+ print(f"买购期权收益: {final_buy_call:.2f}元")
|
|
|
|
|
+ print(f"卖购期权收益: {final_sell_call:.2f}元")
|
|
|
|
|
+ print(f"组合总收益: {final_combined:.2f}元")
|
|
|
|
|
+ print(f"ETF基准收益: {final_etf:.2f}元")
|
|
|
|
|
+
|
|
|
|
|
+ print(f"\n=== 与ETF基准对比分析 ===")
|
|
|
|
|
+ print(f"1. 牛差策略 vs ETF: {final_combined:.2f} vs {final_etf:.2f} = {final_combined - final_etf:+.2f}元")
|
|
|
|
|
+ print(f"2. 买购策略 vs ETF: {final_buy_call:.2f} vs {final_etf:.2f} = {final_buy_call - final_etf:+.2f}元")
|
|
|
|
|
+ print(f"3. 卖购策略 vs ETF: {final_sell_call:.2f} vs {final_etf:.2f} = {final_sell_call - final_etf:+.2f}元")
|
|
|
|
|
+
|
|
|
|
|
+ # 持仓统计
|
|
|
|
|
+ open_positions = len([p for p in self.positions if p['status'] == 'open'])
|
|
|
|
|
+ closed_positions = len([p for p in self.positions if p['status'] == 'closed'])
|
|
|
|
|
+ print(f"\n=== 持仓统计 ===")
|
|
|
|
|
+ print(f"当前持仓: {open_positions}个")
|
|
|
|
|
+ print(f"已平仓位: {closed_positions}个")
|
|
|
|
|
+
|
|
|
|
|
+ # 绘制分项收益曲线
|
|
|
|
|
+ if len(dates) > 0:
|
|
|
|
|
+ self.plot_detailed_performance(dates, buy_call_returns, sell_call_returns, combined_returns, etf_returns)
|
|
|
|
|
+ self.plot_strategy_vs_etf_comparison(dates, buy_call_returns, sell_call_returns, combined_returns, etf_returns)
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ 'dates': dates,
|
|
|
|
|
+ 'buy_call_returns': buy_call_returns,
|
|
|
|
|
+ 'sell_call_returns': sell_call_returns,
|
|
|
|
|
+ 'combined_returns': combined_returns,
|
|
|
|
|
+ 'etf_returns': etf_returns
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ def plot_detailed_performance(self, dates, buy_call_returns, sell_call_returns, combined_returns, etf_returns):
|
|
|
|
|
+ """绘制详细的策略表现图,分别显示买购和卖购收益"""
|
|
|
|
|
+ # 设置中文字体
|
|
|
|
|
+ plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans']
|
|
|
|
|
+ plt.rcParams['axes.unicode_minus'] = False
|
|
|
|
|
+
|
|
|
|
|
+ plt.figure(figsize=(15, 12))
|
|
|
|
|
+
|
|
|
|
|
+ # 第一个子图:分项收益对比
|
|
|
|
|
+ plt.subplot(3, 1, 1)
|
|
|
|
|
+ plt.plot(dates, buy_call_returns, label='买购期权收益', linewidth=2, color='blue')
|
|
|
|
|
+ plt.plot(dates, sell_call_returns, label='卖购期权收益', linewidth=2, color='red')
|
|
|
|
|
+ plt.plot(dates, combined_returns, label='组合总收益', linewidth=2, color='green')
|
|
|
|
|
+ plt.plot(dates, etf_returns, label=f'{self.get_etf_name()}基准', linewidth=2, color='orange')
|
|
|
|
|
+ plt.axhline(0, color='gray', linestyle='--', alpha=0.5)
|
|
|
|
|
+ plt.title('策略分项收益对比')
|
|
|
|
|
+ plt.ylabel('收益(元)')
|
|
|
|
|
+ plt.legend()
|
|
|
|
|
+ plt.grid(True, alpha=0.3)
|
|
|
|
|
+
|
|
|
|
|
+ # 第二个子图:累计收益率
|
|
|
|
|
+ plt.subplot(3, 1, 2)
|
|
|
|
|
+ if len(combined_returns) > 0 and len(etf_returns) > 0:
|
|
|
|
|
+ combined_cumret = [(x / 100000) * 100 for x in combined_returns] # 转换为百分比
|
|
|
|
|
+ etf_cumret = [(x / 100000) * 100 for x in etf_returns]
|
|
|
|
|
+
|
|
|
|
|
+ plt.plot(dates, combined_cumret, label='策略累计收益率', linewidth=2, color='green')
|
|
|
|
|
+ plt.plot(dates, etf_cumret, label='ETF累计收益率', linewidth=2, color='orange')
|
|
|
|
|
+ plt.axhline(0, color='gray', linestyle='--', alpha=0.5)
|
|
|
|
|
+ plt.title('累计收益率对比')
|
|
|
|
|
+ plt.ylabel('收益率(%)')
|
|
|
|
|
+ plt.legend()
|
|
|
|
|
+ plt.grid(True, alpha=0.3)
|
|
|
|
|
+
|
|
|
|
|
+ # 第三个子图:持仓数量变化
|
|
|
|
|
+ plt.subplot(3, 1, 3)
|
|
|
|
|
+ position_counts = []
|
|
|
|
|
+ for date in dates:
|
|
|
|
|
+ count = len([p for p in self.positions
|
|
|
|
|
+ if p['open_date'] <= date and
|
|
|
|
|
+ (p['status'] == 'open' or p.get('close_date', date) >= date)])
|
|
|
|
|
+ position_counts.append(count)
|
|
|
|
|
+
|
|
|
|
|
+ plt.plot(dates, position_counts, label='持仓数量', linewidth=2, color='purple')
|
|
|
|
|
+ plt.title('持仓数量变化')
|
|
|
|
|
+ plt.ylabel('持仓数量')
|
|
|
|
|
+ plt.xlabel('日期')
|
|
|
|
|
+ plt.legend()
|
|
|
|
|
+ plt.grid(True, alpha=0.3)
|
|
|
|
|
+
|
|
|
|
|
+ plt.tight_layout()
|
|
|
|
|
+ plt.show()
|
|
|
|
|
+
|
|
|
|
|
+ def plot_strategy_vs_etf_comparison(self, dates, buy_call_returns, sell_call_returns, combined_returns, etf_returns):
|
|
|
|
|
+ """绘制三个策略分别与ETF的对比图"""
|
|
|
|
|
+ # 设置中文字体
|
|
|
|
|
+ plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans']
|
|
|
|
|
+ plt.rcParams['axes.unicode_minus'] = False
|
|
|
|
|
+
|
|
|
|
|
+ plt.figure(figsize=(15, 12))
|
|
|
|
|
+
|
|
|
|
|
+ # 第一个子图:牛差策略 vs ETF
|
|
|
|
|
+ plt.subplot(3, 1, 1)
|
|
|
|
|
+ plt.plot(dates, combined_returns, label='牛差策略收益', linewidth=2, color='green')
|
|
|
|
|
+ plt.plot(dates, etf_returns, label='ETF基准收益', linewidth=2, color='orange')
|
|
|
|
|
+ plt.axhline(0, color='gray', linestyle='--', alpha=0.5)
|
|
|
|
|
+ plt.title('牛差策略 vs ETF基准收益对比')
|
|
|
|
|
+ plt.ylabel('收益(元)')
|
|
|
|
|
+ plt.legend()
|
|
|
|
|
+ plt.grid(True, alpha=0.3)
|
|
|
|
|
+
|
|
|
|
|
+ # 第二个子图:买购策略 vs ETF
|
|
|
|
|
+ plt.subplot(3, 1, 2)
|
|
|
|
|
+ plt.plot(dates, buy_call_returns, label='买购期权收益', linewidth=2, color='blue')
|
|
|
|
|
+ plt.plot(dates, etf_returns, label='ETF基准收益', linewidth=2, color='orange')
|
|
|
|
|
+ plt.axhline(0, color='gray', linestyle='--', alpha=0.5)
|
|
|
|
|
+ plt.title('买购期权 vs ETF基准收益对比')
|
|
|
|
|
+ plt.ylabel('收益(元)')
|
|
|
|
|
+ plt.legend()
|
|
|
|
|
+ plt.grid(True, alpha=0.3)
|
|
|
|
|
+
|
|
|
|
|
+ # 第三个子图:卖购策略 vs ETF
|
|
|
|
|
+ plt.subplot(3, 1, 3)
|
|
|
|
|
+ plt.plot(dates, sell_call_returns, label='卖购期权收益', linewidth=2, color='red')
|
|
|
|
|
+ plt.plot(dates, etf_returns, label='ETF基准收益', linewidth=2, color='orange')
|
|
|
|
|
+ plt.axhline(0, color='gray', linestyle='--', alpha=0.5)
|
|
|
|
|
+ plt.title('卖购期权 vs ETF基准收益对比')
|
|
|
|
|
+ plt.ylabel('收益(元)')
|
|
|
|
|
+ plt.xlabel('日期')
|
|
|
|
|
+ plt.legend()
|
|
|
|
|
+ plt.grid(True, alpha=0.3)
|
|
|
|
|
+
|
|
|
|
|
+ plt.tight_layout()
|
|
|
|
|
+ plt.show()
|
|
|
|
|
+
|
|
|
|
|
+ def plot_performance(self, portfolio_value, etf_benchmark):
|
|
|
|
|
+ """绘制策略表现图"""
|
|
|
|
|
+ plt.figure(figsize=(12, 8))
|
|
|
|
|
+
|
|
|
|
|
+ dates = self.trade_days[:len(portfolio_value)]
|
|
|
|
|
+
|
|
|
|
|
+ plt.subplot(2, 1, 1)
|
|
|
|
|
+ plt.plot(dates, portfolio_value, label='牛差策略', linewidth=2)
|
|
|
|
|
+ plt.plot(dates, etf_benchmark, label=f'{self.get_etf_name()}基准', linewidth=2)
|
|
|
|
|
+ plt.title('策略收益对比')
|
|
|
|
|
+ plt.ylabel('收益(元)')
|
|
|
|
|
+ plt.legend()
|
|
|
|
|
+ plt.grid(True, alpha=0.3)
|
|
|
|
|
+
|
|
|
|
|
+ plt.subplot(2, 1, 2)
|
|
|
|
|
+ # 绘制持仓数量变化
|
|
|
|
|
+ position_counts = []
|
|
|
|
|
+ for date in dates:
|
|
|
|
|
+ count = len([p for p in self.positions
|
|
|
|
|
+ if p['open_date'] <= date and
|
|
|
|
|
+ (p['status'] == 'open' or p.get('close_date', date) >= date)])
|
|
|
|
|
+ position_counts.append(count)
|
|
|
|
|
+
|
|
|
|
|
+ plt.plot(dates, position_counts, label='持仓数量', linewidth=2, color='orange')
|
|
|
|
|
+ plt.title('持仓数量变化')
|
|
|
|
|
+ plt.ylabel('持仓数量')
|
|
|
|
|
+ plt.xlabel('日期')
|
|
|
|
|
+ plt.legend()
|
|
|
|
|
+ plt.grid(True, alpha=0.3)
|
|
|
|
|
+
|
|
|
|
|
+ plt.tight_layout()
|
|
|
|
|
+ plt.show()
|
|
|
|
|
+
|
|
|
|
|
+ def print_position_details(self):
|
|
|
|
|
+ """打印持仓详情"""
|
|
|
|
|
+ print("\n" + "="*80)
|
|
|
|
|
+ print("持仓详情")
|
|
|
|
|
+ print("="*80)
|
|
|
|
|
+
|
|
|
|
|
+ for i, pos in enumerate(self.positions):
|
|
|
|
|
+ print(f"\n持仓 {i+1}:")
|
|
|
|
|
+ print(f" 开仓日期: {pos['open_date']}")
|
|
|
|
|
+ print(f" ETF价格: {pos['etf_price']:.4f}")
|
|
|
|
|
+ print(f" 买购: {pos['buy_call_strike']:.2f} @ {pos['buy_call_price']:.4f}")
|
|
|
|
|
+ print(f" 卖购: {pos['sell_call_strike']:.2f} @ {pos['sell_call_price']:.4f}")
|
|
|
|
|
+ print(f" 数量: {pos['quantity']}张")
|
|
|
|
|
+ print(f" 单张最大盈利: {pos['max_profit_per_contract']:.4f}")
|
|
|
|
|
+ print(f" 总最大盈利: {pos['max_profit_total']:.2f}元")
|
|
|
|
|
+ print(f" 状态: {pos['status']}")
|
|
|
|
|
+ print(f" 是否加仓: {'是' if pos['is_additional'] else '否'}")
|
|
|
|
|
+
|
|
|
|
|
+ if pos['status'] == 'closed':
|
|
|
|
|
+ print(f" 平仓日期: {pos['close_date']}")
|
|
|
|
|
+ print(f" 平仓原因: {pos['close_reason']}")
|
|
|
|
|
+ print(f" 实现损益: {pos['realized_pnl']:.2f}元")
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class OptionsAnalyzer:
|
|
|
|
|
+ """期权分析工具类"""
|
|
|
|
|
+
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def analyze_options(*options):
|
|
|
|
|
+ """
|
|
|
|
|
+ 统一的期权分析方法
|
|
|
|
|
+ 参数: *options: 一个或多个期权,每个期权格式为 (direction, option_type, premium, strike_price, quantity)
|
|
|
|
|
+ """
|
|
|
|
|
+ if not options:
|
|
|
|
|
+ raise ValueError("请至少提供一个期权")
|
|
|
|
|
+
|
|
|
|
|
+ # 解析期权数据
|
|
|
|
|
+ option_list = []
|
|
|
|
|
+ all_strikes = []
|
|
|
|
|
+
|
|
|
|
|
+ for i, opt in enumerate(options):
|
|
|
|
|
+ if len(opt) != 5:
|
|
|
|
|
+ raise ValueError(f"期权{i+1}格式错误,应为(direction, option_type, premium, strike_price, quantity)")
|
|
|
|
|
+
|
|
|
|
|
+ direction, option_type, premium, strike_price, quantity = opt
|
|
|
|
|
+ option_list.append({
|
|
|
|
|
+ 'direction': direction,
|
|
|
|
|
+ 'option_type': option_type,
|
|
|
|
|
+ 'premium': premium,
|
|
|
|
|
+ 'strike_price': strike_price,
|
|
|
|
|
+ 'quantity': quantity
|
|
|
|
|
+ })
|
|
|
|
|
+ all_strikes.append(strike_price)
|
|
|
|
|
+
|
|
|
|
|
+ # 确定价格分析区间
|
|
|
|
|
+ min_strike = min(all_strikes)
|
|
|
|
|
+ max_strike = max(all_strikes)
|
|
|
|
|
+ price_min = min_strike * 0.7
|
|
|
|
|
+ price_max = max_strike * 1.3
|
|
|
|
|
+
|
|
|
|
|
+ # 生成价格序列
|
|
|
|
|
+ gap = (price_max - price_min) / 1000
|
|
|
|
|
+ prices = np.arange(price_min, price_max + gap, gap)
|
|
|
|
|
+
|
|
|
|
|
+ # 计算每个期权的收益
|
|
|
|
|
+ results = {'price': prices}
|
|
|
|
|
+
|
|
|
|
|
+ for i, opt in enumerate(option_list):
|
|
|
|
|
+ profits = []
|
|
|
|
|
+ for price in prices:
|
|
|
|
|
+ profit = OptionsAnalyzer._calculate_profit(opt, price)
|
|
|
|
|
+ profits.append(profit)
|
|
|
|
|
+ results[f'opt{i+1}'] = profits
|
|
|
|
|
+
|
|
|
|
|
+ # 计算组合收益
|
|
|
|
|
+ if len(option_list) > 1:
|
|
|
|
|
+ combined_profits = []
|
|
|
|
|
+ for j in range(len(prices)):
|
|
|
|
|
+ total = sum(results[f'opt{i+1}'][j] for i in range(len(option_list)))
|
|
|
|
|
+ combined_profits.append(total)
|
|
|
|
|
+ results['combined'] = combined_profits
|
|
|
|
|
+
|
|
|
|
|
+ # 绘制图表
|
|
|
|
|
+ OptionsAnalyzer._plot_results(results, option_list, prices)
|
|
|
|
|
+
|
|
|
|
|
+ # 打印分析报告
|
|
|
|
|
+ OptionsAnalyzer._print_report(results, option_list, prices)
|
|
|
|
|
+
|
|
|
|
|
+ return pd.DataFrame(results)
|
|
|
|
|
+
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def _calculate_profit(option, price):
|
|
|
|
|
+ """计算单个期权在特定价格下的收益"""
|
|
|
|
|
+ direction = option['direction']
|
|
|
|
|
+ option_type = option['option_type']
|
|
|
|
|
+ premium = option['premium']
|
|
|
|
|
+ strike_price = option['strike_price']
|
|
|
|
|
+ quantity = option['quantity']
|
|
|
|
|
+
|
|
|
|
|
+ if direction == 'buy' and option_type == 'call':
|
|
|
|
|
+ # 买入认购
|
|
|
|
|
+ if price > strike_price:
|
|
|
|
|
+ return (price - strike_price - premium) * quantity
|
|
|
|
|
+ else:
|
|
|
|
|
+ return -premium * quantity
|
|
|
|
|
+
|
|
|
|
|
+ elif direction == 'sell' and option_type == 'call':
|
|
|
|
|
+ # 卖出认购
|
|
|
|
|
+ if price > strike_price:
|
|
|
|
|
+ return -(price - strike_price - premium) * quantity
|
|
|
|
|
+ else:
|
|
|
|
|
+ return premium * quantity
|
|
|
|
|
+
|
|
|
|
|
+ elif direction == 'buy' and option_type == 'put':
|
|
|
|
|
+ # 买入认沽
|
|
|
|
|
+ if price < strike_price:
|
|
|
|
|
+ return (strike_price - price - premium) * quantity
|
|
|
|
|
+ else:
|
|
|
|
|
+ return -premium * quantity
|
|
|
|
|
+
|
|
|
|
|
+ elif direction == 'sell' and option_type == 'put':
|
|
|
|
|
+ # 卖出认沽
|
|
|
|
|
+ if price < strike_price:
|
|
|
|
|
+ return -(strike_price - price - premium) * quantity
|
|
|
|
|
+ else:
|
|
|
|
|
+ return premium * quantity
|
|
|
|
|
+
|
|
|
|
|
+ return 0
|
|
|
|
|
+
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def _plot_results(results, option_list, prices):
|
|
|
|
|
+ """绘制分析图表"""
|
|
|
|
|
+ # 设置中文字体
|
|
|
|
|
+ plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans']
|
|
|
|
|
+ plt.rcParams['axes.unicode_minus'] = False
|
|
|
|
|
+
|
|
|
|
|
+ plt.figure(figsize=(14, 10))
|
|
|
|
|
+
|
|
|
|
|
+ colors = ['blue', 'green', 'orange', 'purple', 'brown']
|
|
|
|
|
+
|
|
|
|
|
+ # 绘制单个期权曲线
|
|
|
|
|
+ for i in range(len(option_list)):
|
|
|
|
|
+ opt = option_list[i]
|
|
|
|
|
+ opt_name = f'opt{i+1}'
|
|
|
|
|
+ strategy_name = f"{opt['direction'].upper()} {opt['option_type'].upper()}"
|
|
|
|
|
+ color = colors[i % len(colors)]
|
|
|
|
|
+
|
|
|
|
|
+ plt.plot(prices, results[opt_name], '--', color=color, linewidth=2, alpha=0.7,
|
|
|
|
|
+ label=f'{opt_name}: {strategy_name} (行权价:{opt["strike_price"]})')
|
|
|
|
|
+
|
|
|
|
|
+ # 绘制组合曲线
|
|
|
|
|
+ if 'combined' in results:
|
|
|
|
|
+ plt.plot(prices, results['combined'], 'r-', linewidth=3, label='组合收益')
|
|
|
|
|
+
|
|
|
|
|
+ # 添加零线和行权价线
|
|
|
|
|
+ plt.axhline(0, color='gray', linestyle='-', alpha=0.5)
|
|
|
|
|
+ for opt in option_list:
|
|
|
|
|
+ plt.axvline(opt['strike_price'], color='gray', linestyle='--', alpha=0.3)
|
|
|
|
|
+
|
|
|
|
|
+ # 找到并标注关键点
|
|
|
|
|
+ if 'combined' in results:
|
|
|
|
|
+ OptionsAnalyzer._mark_key_points(results['combined'], prices, '组合')
|
|
|
|
|
+ elif len(option_list) == 1:
|
|
|
|
|
+ OptionsAnalyzer._mark_key_points(results['opt1'], prices, '期权')
|
|
|
|
|
+
|
|
|
|
|
+ plt.xlabel('标的资产价格', fontsize=12)
|
|
|
|
|
+ plt.ylabel('收益/损失', fontsize=12)
|
|
|
|
|
+
|
|
|
|
|
+ if len(option_list) == 1:
|
|
|
|
|
+ opt = option_list[0]
|
|
|
|
|
+ title = f'{opt["direction"].upper()} {opt["option_type"].upper()} 期权分析'
|
|
|
|
|
+ else:
|
|
|
|
|
+ title = f'期权组合分析 ({len(option_list)}个期权)'
|
|
|
|
|
+
|
|
|
|
|
+ plt.title(title, fontsize=14, weight='bold')
|
|
|
|
|
+ plt.grid(True, alpha=0.3)
|
|
|
|
|
+ plt.legend()
|
|
|
|
|
+ plt.tight_layout()
|
|
|
|
|
+ plt.show()
|
|
|
|
|
+
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def _mark_key_points(profits, prices, label_prefix):
|
|
|
|
|
+ """标注关键点:盈亏平衡点、最大收益/损失边界点"""
|
|
|
|
|
+ # 标注盈亏平衡点
|
|
|
|
|
+ for i in range(len(profits) - 1):
|
|
|
|
|
+ if profits[i] * profits[i + 1] <= 0: # 符号改变
|
|
|
|
|
+ # 线性插值找到精确平衡点
|
|
|
|
|
+ p1, profit1 = prices[i], profits[i]
|
|
|
|
|
+ p2, profit2 = prices[i + 1], profits[i + 1]
|
|
|
|
|
+ if profit2 != profit1:
|
|
|
|
|
+ breakeven_price = p1 - profit1 * (p2 - p1) / (profit2 - profit1)
|
|
|
|
|
+ plt.plot(breakeven_price, 0, 'ro', markersize=10)
|
|
|
|
|
+ plt.annotate(f'平衡点: {breakeven_price:.3f}',
|
|
|
|
|
+ xy=(breakeven_price, 0),
|
|
|
|
|
+ xytext=(breakeven_price + (prices.max() - prices.min()) * 0.05, max(profits) * 0.1),
|
|
|
|
|
+ arrowprops=dict(arrowstyle='->', color='red'),
|
|
|
|
|
+ fontsize=11, color='red', weight='bold')
|
|
|
|
|
+
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def _print_report(results, option_list, prices):
|
|
|
|
|
+ """打印分析报告"""
|
|
|
|
|
+ print("=" * 60)
|
|
|
|
|
+ print("期权分析报告")
|
|
|
|
|
+ print("=" * 60)
|
|
|
|
|
+
|
|
|
|
|
+ # 期权基本信息
|
|
|
|
|
+ for i, opt in enumerate(option_list):
|
|
|
|
|
+ print(f"期权{i+1}: {opt['direction'].upper()} {opt['option_type'].upper()}")
|
|
|
|
|
+ print(f" 行权价: {opt['strike_price']}")
|
|
|
|
|
+ print(f" 权利金: {opt['premium']}")
|
|
|
|
|
+ print(f" 数量: {opt['quantity']}手")
|
|
|
|
|
+
|
|
|
|
|
+ # 分析关键指标
|
|
|
|
|
+ if 'combined' in results:
|
|
|
|
|
+ profits = results['combined']
|
|
|
|
|
+ print(f"\n【组合分析】")
|
|
|
|
|
+ else:
|
|
|
|
|
+ profits = results['opt1']
|
|
|
|
|
+ print(f"\n【单期权分析】")
|
|
|
|
|
+
|
|
|
|
|
+ max_profit = max(profits)
|
|
|
|
|
+ min_profit = min(profits)
|
|
|
|
|
+ max_idx = profits.tolist().index(max_profit) if hasattr(profits, 'tolist') else profits.index(max_profit)
|
|
|
|
|
+ min_idx = profits.tolist().index(min_profit) if hasattr(profits, 'tolist') else profits.index(min_profit)
|
|
|
|
|
+
|
|
|
|
|
+ print(f"最大收益: {max_profit:.4f} (标的价格: {prices[max_idx]:.4f})")
|
|
|
|
|
+ print(f"最大损失: {min_profit:.4f} (标的价格: {prices[min_idx]:.4f})")
|
|
|
|
|
+ print(f"一单最大收益: {max_profit * 10000:.2f}元")
|
|
|
|
|
+ print(f"一单最大亏损: {abs(min_profit) * 10000:.2f}元")
|
|
|
|
|
+
|
|
|
|
|
+ # 找盈亏平衡点
|
|
|
|
|
+ breakeven_points = []
|
|
|
|
|
+ for i in range(len(profits) - 1):
|
|
|
|
|
+ if profits[i] * profits[i + 1] <= 0:
|
|
|
|
|
+ p1, profit1 = prices[i], profits[i]
|
|
|
|
|
+ p2, profit2 = prices[i + 1], profits[i + 1]
|
|
|
|
|
+ if profit2 != profit1:
|
|
|
|
|
+ bp = p1 - profit1 * (p2 - p1) / (profit2 - profit1)
|
|
|
|
|
+ breakeven_points.append(bp)
|
|
|
|
|
+
|
|
|
|
|
+ if breakeven_points:
|
|
|
|
|
+ print(f"盈亏平衡点: {[f'{bp:.4f}' for bp in breakeven_points]}")
|
|
|
|
|
+ else:
|
|
|
|
|
+ print("无盈亏平衡点")
|
|
|
|
|
+
|
|
|
|
|
+ print("=" * 60)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_opening_logic():
|
|
|
|
|
+ """专门测试开仓逻辑"""
|
|
|
|
|
+ print("="*60)
|
|
|
|
|
+ print("测试开仓逻辑")
|
|
|
|
|
+ print("="*60)
|
|
|
|
|
+
|
|
|
|
|
+ # 创建策略实例
|
|
|
|
|
+ strategy = DeepITMBullSpreadStrategy(
|
|
|
|
|
+ underlying='510300.XSHG', # 300ETF
|
|
|
|
|
+ start_date='2024-01-01',
|
|
|
|
|
+ end_date='2025-06-30'
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ # 获取ETF价格数据
|
|
|
|
|
+ try:
|
|
|
|
|
+ etf_prices = get_price(strategy.underlying, strategy.start_date, strategy.end_date, fields=['close'])['close']
|
|
|
|
|
+ print(f"成功获取ETF价格数据,共{len(etf_prices)}个交易日")
|
|
|
|
|
+
|
|
|
|
|
+ # 测试前几个交易日的开仓逻辑
|
|
|
|
|
+ test_dates = strategy.trade_days[:10] # 只测试前10个交易日
|
|
|
|
|
+
|
|
|
|
|
+ for i, date in enumerate(test_dates):
|
|
|
|
|
+ if date not in etf_prices.index:
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ etf_price = etf_prices[date]
|
|
|
|
|
+ print(f"\n第{i+1}个交易日测试: {date}, ETF价格: {etf_price:.4f}")
|
|
|
|
|
+
|
|
|
|
|
+ # 尝试开仓
|
|
|
|
|
+ position = strategy.open_bull_spread_position(date, etf_price)
|
|
|
|
|
+
|
|
|
|
|
+ if position:
|
|
|
|
|
+ print(f"✓ 成功开仓!")
|
|
|
|
|
+ print(f" 持仓总数: {len(strategy.positions)}")
|
|
|
|
|
+ break
|
|
|
|
|
+ else:
|
|
|
|
|
+ print(f"✗ 开仓失败")
|
|
|
|
|
+
|
|
|
|
|
+ # 打印最终结果
|
|
|
|
|
+ print(f"\n测试结果:")
|
|
|
|
|
+ print(f"总持仓数: {len(strategy.positions)}")
|
|
|
|
|
+ print(f"总交易数: {len(strategy.trades)}")
|
|
|
|
|
+
|
|
|
|
|
+ if strategy.positions:
|
|
|
|
|
+ print(f"第一个持仓详情:")
|
|
|
|
|
+ pos = strategy.positions[0]
|
|
|
|
|
+ for key, value in pos.items():
|
|
|
|
|
+ print(f" {key}: {value}")
|
|
|
|
|
+
|
|
|
|
|
+ return strategy
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ print(f"测试过程中出现错误: {e}")
|
|
|
|
|
+ import traceback
|
|
|
|
|
+ traceback.print_exc()
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_strategy():
|
|
|
|
|
+ """测试策略"""
|
|
|
|
|
+ print("开始测试深度实值牛差策略...")
|
|
|
|
|
+
|
|
|
|
|
+ # 创建策略实例
|
|
|
|
|
+ strategy = DeepITMBullSpreadStrategy(
|
|
|
|
|
+ underlying='510300.XSHG', # 300ETF
|
|
|
|
|
+ start_date='2024-01-01',
|
|
|
|
|
+ end_date='2025-06-30'
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ # 运行回测
|
|
|
|
|
+ try:
|
|
|
|
|
+ print("正在运行回测...")
|
|
|
|
|
+ strategy.run_backtest()
|
|
|
|
|
+
|
|
|
|
|
+ print("正在生成详细报告...")
|
|
|
|
|
+ # 生成详细报告(包含买购和卖购分项分析)
|
|
|
|
|
+ detailed_results = strategy.generate_detailed_report()
|
|
|
|
|
+
|
|
|
|
|
+ print("正在导出数据...")
|
|
|
|
|
+ # 导出数据到CSV
|
|
|
|
|
+ positions_df, trades_df, daily_df = strategy.export_data_to_csv("bull_spread_300etf")
|
|
|
|
|
+
|
|
|
|
|
+ print("正在打印持仓详情...")
|
|
|
|
|
+ # 打印详细持仓信息
|
|
|
|
|
+ strategy.print_position_details()
|
|
|
|
|
+
|
|
|
|
|
+ return strategy, detailed_results
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ print(f"回测过程中出现错误: {e}")
|
|
|
|
|
+ import traceback
|
|
|
|
|
+ traceback.print_exc()
|
|
|
|
|
+ return None, None
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def analyze_bull_spread_example():
|
|
|
|
|
+ """分析牛差策略示例"""
|
|
|
|
|
+ print("\n" + "="*60)
|
|
|
|
|
+ print("牛差策略期权组合分析示例")
|
|
|
|
|
+ print("="*60)
|
|
|
|
|
+
|
|
|
|
|
+ # 假设当前300ETF价格为4.0
|
|
|
|
|
+ underlying_price = 4.0
|
|
|
|
|
+
|
|
|
|
|
+ # 根据策略逻辑选择期权
|
|
|
|
|
+ buy_strike = underlying_price * 0.90 # 深度实值买购
|
|
|
|
|
+ sell_strike = underlying_price * 1.00 # 平值卖购
|
|
|
|
|
+
|
|
|
|
|
+ # 估算权利金(简化)
|
|
|
|
|
+ buy_premium = 0.44 # 深度实值期权,主要是内在价值
|
|
|
|
|
+ sell_premium = 0.20 # 平值期权,主要是时间价值
|
|
|
|
|
+
|
|
|
|
|
+ print(f"当前标的价格: {underlying_price}")
|
|
|
|
|
+ print(f"买购期权: 行权价 {buy_strike:.2f}, 权利金 {buy_premium:.4f}")
|
|
|
|
|
+ print(f"卖购期权: 行权价 {sell_strike:.2f}, 权利金 {sell_premium:.4f}")
|
|
|
|
|
+
|
|
|
|
|
+ # 使用期权分析工具
|
|
|
|
|
+ analyzer = OptionsAnalyzer()
|
|
|
|
|
+ result = analyzer.analyze_options(
|
|
|
|
|
+ ('buy', 'call', buy_premium, buy_strike, 1),
|
|
|
|
|
+ ('sell', 'call', sell_premium, sell_strike, 1)
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ return result
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def compare_with_etf_holding():
|
|
|
|
|
+ """三个策略分别与ETF持有收益对比"""
|
|
|
|
|
+ print("\n" + "="*80)
|
|
|
|
|
+ print("三个策略分别与ETF持有收益对比")
|
|
|
|
|
+ print("="*80)
|
|
|
|
|
+
|
|
|
|
|
+ initial_price = 4.0
|
|
|
|
|
+ initial_investment = 100000
|
|
|
|
|
+
|
|
|
|
|
+ # 牛差策略参数
|
|
|
|
|
+ buy_strike = initial_price * 0.90
|
|
|
|
|
+ sell_strike = initial_price * 1.00
|
|
|
|
|
+ buy_premium = 0.44
|
|
|
|
|
+ sell_premium = 0.20
|
|
|
|
|
+
|
|
|
|
|
+ # 计算可开仓张数
|
|
|
|
|
+ net_premium = buy_premium - sell_premium
|
|
|
|
|
+ contracts = int(initial_investment / (net_premium * 10000))
|
|
|
|
|
+
|
|
|
|
|
+ print(f"初始投资: {initial_investment:,.0f}元")
|
|
|
|
|
+ print(f"初始ETF价格: {initial_price:.2f}")
|
|
|
|
|
+ print(f"牛差组合: 买购{buy_strike:.2f}@{buy_premium:.4f}, 卖购{sell_strike:.2f}@{sell_premium:.4f}")
|
|
|
|
|
+ print(f"可开仓张数: {contracts}张")
|
|
|
|
|
+
|
|
|
|
|
+ # 模拟不同价格变化下的收益
|
|
|
|
|
+ price_changes = np.arange(-20, 21, 5)
|
|
|
|
|
+ results = []
|
|
|
|
|
+
|
|
|
|
|
+ for change in price_changes:
|
|
|
|
|
+ new_price = initial_price * (1 + change / 100)
|
|
|
|
|
+
|
|
|
|
|
+ # ETF持有收益
|
|
|
|
|
+ etf_return = (new_price - initial_price) / initial_price * initial_investment
|
|
|
|
|
+
|
|
|
|
|
+ # 分别计算三个策略的收益
|
|
|
|
|
+ buy_call_value = max(0, new_price - buy_strike)
|
|
|
|
|
+ sell_call_value = max(0, new_price - sell_strike)
|
|
|
|
|
+
|
|
|
|
|
+ # 1. 买购期权收益
|
|
|
|
|
+ buy_call_pnl = (buy_call_value - buy_premium) * contracts * 10000
|
|
|
|
|
+
|
|
|
|
|
+ # 2. 卖购期权收益
|
|
|
|
|
+ sell_call_pnl = (sell_premium - sell_call_value) * contracts * 10000
|
|
|
|
|
+
|
|
|
|
|
+ # 3. 牛差组合收益
|
|
|
|
|
+ bull_spread_return = buy_call_pnl + sell_call_pnl
|
|
|
|
|
+
|
|
|
|
|
+ results.append({
|
|
|
|
|
+ 'price_change': change,
|
|
|
|
|
+ 'new_price': new_price,
|
|
|
|
|
+ 'etf_return': etf_return,
|
|
|
|
|
+ 'buy_call_return': buy_call_pnl,
|
|
|
|
|
+ 'sell_call_return': sell_call_pnl,
|
|
|
|
|
+ 'bull_spread_return': bull_spread_return,
|
|
|
|
|
+ 'bull_vs_etf': bull_spread_return - etf_return,
|
|
|
|
|
+ 'buy_vs_etf': buy_call_pnl - etf_return,
|
|
|
|
|
+ 'sell_vs_etf': sell_call_pnl - etf_return
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ # 创建对比表格
|
|
|
|
|
+ df = pd.DataFrame(results)
|
|
|
|
|
+
|
|
|
|
|
+ print(f"\n=== 详细收益对比表 ===")
|
|
|
|
|
+ print("价格变化(%) | 新价格 | ETF收益 | 买购收益 | 卖购收益 | 牛差收益")
|
|
|
|
|
+ print("-" * 75)
|
|
|
|
|
+
|
|
|
|
|
+ for _, row in df.iterrows():
|
|
|
|
|
+ print(f"{row['price_change']:>8.0f}% | {row['new_price']:>6.2f} | "
|
|
|
|
|
+ f"{row['etf_return']:>7.0f} | {row['buy_call_return']:>8.0f} | "
|
|
|
|
|
+ f"{row['sell_call_return']:>8.0f} | {row['bull_spread_return']:>8.0f}")
|
|
|
|
|
+
|
|
|
|
|
+ print(f"\n=== 与ETF基准的差异对比 ===")
|
|
|
|
|
+ print("价格变化(%) | 牛差-ETF | 买购-ETF | 卖购-ETF")
|
|
|
|
|
+ print("-" * 50)
|
|
|
|
|
+
|
|
|
|
|
+ for _, row in df.iterrows():
|
|
|
|
|
+ print(f"{row['price_change']:>8.0f}% | {row['bull_vs_etf']:>8.0f} | "
|
|
|
|
|
+ f"{row['buy_vs_etf']:>8.0f} | {row['sell_vs_etf']:>8.0f}")
|
|
|
|
|
+
|
|
|
|
|
+ # 绘制三个对比图
|
|
|
|
|
+ # 设置中文字体
|
|
|
|
|
+ plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans']
|
|
|
|
|
+ plt.rcParams['axes.unicode_minus'] = False
|
|
|
|
|
+
|
|
|
|
|
+ plt.figure(figsize=(15, 12))
|
|
|
|
|
+
|
|
|
|
|
+ # 第一个子图:牛差策略 vs ETF
|
|
|
|
|
+ plt.subplot(3, 1, 1)
|
|
|
|
|
+ plt.plot(df['price_change'], df['etf_return'], 'o-', linewidth=2, label='ETF持有', color='orange')
|
|
|
|
|
+ plt.plot(df['price_change'], df['bull_spread_return'], 's-', linewidth=2, label='牛差策略', color='green')
|
|
|
|
|
+ plt.axhline(0, color='gray', linestyle='--', alpha=0.5)
|
|
|
|
|
+ plt.xlabel('价格变化 (%)')
|
|
|
|
|
+ plt.ylabel('收益 (元)')
|
|
|
|
|
+ plt.title('牛差策略 vs ETF持有收益对比')
|
|
|
|
|
+ plt.legend()
|
|
|
|
|
+ plt.grid(True, alpha=0.3)
|
|
|
|
|
+
|
|
|
|
|
+ # 第二个子图:买购策略 vs ETF
|
|
|
|
|
+ plt.subplot(3, 1, 2)
|
|
|
|
|
+ plt.plot(df['price_change'], df['etf_return'], 'o-', linewidth=2, label='ETF持有', color='orange')
|
|
|
|
|
+ plt.plot(df['price_change'], df['buy_call_return'], '^-', linewidth=2, label='买购期权', color='blue')
|
|
|
|
|
+ plt.axhline(0, color='gray', linestyle='--', alpha=0.5)
|
|
|
|
|
+ plt.xlabel('价格变化 (%)')
|
|
|
|
|
+ plt.ylabel('收益 (元)')
|
|
|
|
|
+ plt.title('买购期权 vs ETF持有收益对比')
|
|
|
|
|
+ plt.legend()
|
|
|
|
|
+ plt.grid(True, alpha=0.3)
|
|
|
|
|
+
|
|
|
|
|
+ # 第三个子图:卖购策略 vs ETF
|
|
|
|
|
+ plt.subplot(3, 1, 3)
|
|
|
|
|
+ plt.plot(df['price_change'], df['etf_return'], 'o-', linewidth=2, label='ETF持有', color='orange')
|
|
|
|
|
+ plt.plot(df['price_change'], df['sell_call_return'], 'v-', linewidth=2, label='卖购期权', color='red')
|
|
|
|
|
+ plt.axhline(0, color='gray', linestyle='--', alpha=0.5)
|
|
|
|
|
+ plt.xlabel('价格变化 (%)')
|
|
|
|
|
+ plt.ylabel('收益 (元)')
|
|
|
|
|
+ plt.title('卖购期权 vs ETF持有收益对比')
|
|
|
|
|
+ plt.legend()
|
|
|
|
|
+ plt.grid(True, alpha=0.3)
|
|
|
|
|
+
|
|
|
|
|
+ plt.tight_layout()
|
|
|
|
|
+ plt.show()
|
|
|
|
|
+
|
|
|
|
|
+ return df
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+if __name__ == "__main__":
|
|
|
|
|
+ print("深度实值牛差策略综合测试工具")
|
|
|
|
|
+ print("="*50)
|
|
|
|
|
+
|
|
|
|
|
+ # 0. 首先测试开仓逻辑
|
|
|
|
|
+ print("\n0. 测试开仓逻辑:")
|
|
|
|
|
+ test_strategy_instance = test_opening_logic()
|
|
|
|
|
+
|
|
|
|
|
+ if test_strategy_instance and len(test_strategy_instance.positions) > 0:
|
|
|
|
|
+ print("✓ 开仓逻辑测试通过,继续完整回测")
|
|
|
|
|
+
|
|
|
|
|
+ # 1. 运行策略回测
|
|
|
|
|
+ print("\n1. 运行策略回测:")
|
|
|
|
|
+ strategy, detailed_results = test_strategy()
|
|
|
|
|
+
|
|
|
|
|
+ # 2. 分析期权组合
|
|
|
|
|
+ print("\n2. 期权组合分析:")
|
|
|
|
|
+ option_analysis = analyze_bull_spread_example()
|
|
|
|
|
+
|
|
|
|
|
+ # 3. 与ETF持有对比
|
|
|
|
|
+ print("\n3. 与ETF持有收益对比:")
|
|
|
|
|
+ comparison_results = compare_with_etf_holding()
|
|
|
|
|
+
|
|
|
|
|
+ print(f"\n测试完成!数据已导出到CSV文件,可用于进一步分析。")
|
|
|
|
|
+ else:
|
|
|
|
|
+ print("✗ 开仓逻辑测试失败,请检查期权数据和策略参数")
|
|
|
|
|
+ print("建议检查以下几点:")
|
|
|
|
|
+ print("1. 期权合约数据是否可用")
|
|
|
|
|
+ print("2. 策略参数设置是否合理")
|
|
|
|
|
+ print("3. 时间价值阈值是否过于严格")
|