deep_itm_bull_spread_strategy.py 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248
  1. # 深度实值买购和卖购组合的牛差策略
  2. # 参考文档: Lib/Options/README.md - 策略1
  3. from jqdata import *
  4. import pandas as pd
  5. import numpy as np
  6. import matplotlib.pyplot as plt
  7. import tqdm
  8. from datetime import datetime, timedelta
  9. import warnings
  10. warnings.filterwarnings('ignore')
  11. class DeepITMBullSpreadStrategy:
  12. """深度实值买购和卖购组合的牛差策略"""
  13. def __init__(self, underlying='510300.XSHG', start_date='2024-01-01', end_date='2025-06-30'):
  14. self.underlying = underlying # 标的ETF
  15. self.start_date = start_date
  16. self.end_date = end_date
  17. # 策略参数设置
  18. self.params = {
  19. '一组张数': 30,
  20. '最小权利金': {'沪深300ETF': 0.03, '上证50ETF': 0.05},
  21. '最少开仓日期': 20, # 距离到期日天数
  22. '买购时间价值阈值': 0.0001,
  23. '卖购平仓权利金阈值': 0.0005,
  24. '合约到期移仓日期最大': 7, # 交易日
  25. '合约到期移仓日期最小': 2, # 交易日
  26. '加仓窗口阈值': {'沪深300ETF': 0.2, '上证50ETF': 0.1}
  27. }
  28. # 交易记录
  29. self.positions = [] # 持仓记录
  30. self.trades = [] # 交易记录
  31. self.daily_pnl = [] # 每日损益
  32. # 获取交易日历
  33. self.trade_days = get_trade_days(start_date, end_date)
  34. def get_etf_name(self):
  35. """根据标的代码获取ETF名称"""
  36. if self.underlying == '510300.XSHG':
  37. return '沪深300ETF'
  38. elif self.underlying == '510050.XSHG':
  39. return '上证50ETF'
  40. else:
  41. return '未知ETF'
  42. def get_option_contracts(self, date, contract_type='CO', min_days_to_expire=20):
  43. """获取期权合约信息"""
  44. # 查询期权合约
  45. q = query(opt.OPT_CONTRACT_INFO.code,
  46. opt.OPT_CONTRACT_INFO.trading_code,
  47. opt.OPT_CONTRACT_INFO.name,
  48. opt.OPT_CONTRACT_INFO.exercise_price,
  49. opt.OPT_CONTRACT_INFO.last_trade_date,
  50. opt.OPT_CONTRACT_INFO.list_date
  51. ).filter(
  52. opt.OPT_CONTRACT_INFO.underlying_symbol == self.underlying,
  53. opt.OPT_CONTRACT_INFO.contract_type == contract_type,
  54. opt.OPT_CONTRACT_INFO.list_date <= date,
  55. opt.OPT_CONTRACT_INFO.last_trade_date > date
  56. )
  57. contracts = opt.run_query(q)
  58. print(f" 查询到{len(contracts)}个{contract_type}类型的期权合约")
  59. # 过滤距离到期日至少min_days_to_expire天的合约
  60. valid_contracts = []
  61. for _, contract in contracts.iterrows():
  62. days_to_expire = (pd.to_datetime(contract['last_trade_date']) - pd.to_datetime(date)).days
  63. if days_to_expire >= min_days_to_expire:
  64. valid_contracts.append(contract)
  65. print(f" 过滤后剩余{len(valid_contracts)}个距离到期日至少{min_days_to_expire}天的合约")
  66. return pd.DataFrame(valid_contracts) if valid_contracts else pd.DataFrame()
  67. def get_option_price(self, option_code, date):
  68. """获取期权价格"""
  69. try:
  70. q = query(opt.OPT_DAILY_PRICE.close).filter(
  71. opt.OPT_DAILY_PRICE.code == option_code,
  72. opt.OPT_DAILY_PRICE.date == date
  73. )
  74. result = opt.run_query(q)
  75. return result['close'].iloc[0] if not result.empty else None
  76. except:
  77. return None
  78. def calculate_time_value(self, option_price, intrinsic_value):
  79. """计算时间价值"""
  80. return max(0, option_price - max(0, intrinsic_value))
  81. def select_call_option_to_buy(self, date, etf_price):
  82. """选择深度实值买购期权"""
  83. contracts = self.get_option_contracts(date, 'CO')
  84. if contracts.empty:
  85. print(f" 买购选择失败: 未找到任何认购期权合约")
  86. return None
  87. print(f" 找到{len(contracts)}个认购期权合约")
  88. # 筛选深度实值期权(时间价值 <= 阈值)
  89. suitable_contracts = []
  90. checked_count = 0
  91. for _, contract in contracts.iterrows():
  92. option_price = self.get_option_price(contract['code'], date)
  93. checked_count += 1
  94. if option_price is None:
  95. continue
  96. intrinsic_value = max(0, etf_price - contract['exercise_price'])
  97. time_value = self.calculate_time_value(option_price, intrinsic_value)
  98. if time_value <= self.params['买购时间价值阈值']:
  99. suitable_contracts.append({
  100. 'code': contract['code'],
  101. 'exercise_price': contract['exercise_price'],
  102. 'option_price': option_price,
  103. 'time_value': time_value,
  104. 'last_trade_date': contract['last_trade_date']
  105. })
  106. print(f" 检查了{checked_count}个合约,找到{len(suitable_contracts)}个符合时间价值条件的买购期权")
  107. # 选择时间价值最小的
  108. if suitable_contracts:
  109. return min(suitable_contracts, key=lambda x: x['time_value'])
  110. return None
  111. def select_call_option_to_sell(self, date, etf_price):
  112. """选择平值卖购期权"""
  113. contracts = self.get_option_contracts(date, 'CO')
  114. if contracts.empty:
  115. print(f" 卖购选择失败: 未找到任何认购期权合约")
  116. return None
  117. # 找到最接近平值的期权
  118. etf_name = self.get_etf_name()
  119. min_premium = self.params['最小权利金'].get(etf_name, 0.03)
  120. print(f" 最小权利金要求: {min_premium}")
  121. suitable_contracts = []
  122. checked_count = 0
  123. for _, contract in contracts.iterrows():
  124. option_price = self.get_option_price(contract['code'], date)
  125. checked_count += 1
  126. if option_price is None or option_price < min_premium:
  127. continue
  128. price_diff = abs(contract['exercise_price'] - etf_price)
  129. suitable_contracts.append({
  130. 'code': contract['code'],
  131. 'exercise_price': contract['exercise_price'],
  132. 'option_price': option_price,
  133. 'price_diff': price_diff,
  134. 'last_trade_date': contract['last_trade_date']
  135. })
  136. print(f" 检查了{checked_count}个合约,找到{len(suitable_contracts)}个符合权利金条件的卖购期权")
  137. # 选择最接近平值且权利金较高的
  138. if suitable_contracts:
  139. # 先按价格差排序,再按权利金排序
  140. suitable_contracts.sort(key=lambda x: (x['price_diff'], -x['option_price']))
  141. return suitable_contracts[0]
  142. return None
  143. def open_bull_spread_position(self, date, etf_price, quantity=None):
  144. """开仓牛差组合"""
  145. if quantity is None:
  146. quantity = self.params['一组张数']
  147. print(f"尝试开仓: 日期={date}, ETF价格={etf_price:.4f}")
  148. # 选择买购期权(深度实值)
  149. buy_call = self.select_call_option_to_buy(date, etf_price)
  150. if buy_call is None:
  151. print(f" 失败: 未找到合适的买购期权")
  152. return None
  153. print(f" 找到买购期权: 代码={buy_call['code']}, 行权价={buy_call['exercise_price']:.4f}, 权利金={buy_call['option_price']:.4f}")
  154. # 选择卖购期权(平值)
  155. sell_call = self.select_call_option_to_sell(date, etf_price)
  156. if sell_call is None:
  157. print(f" 失败: 未找到合适的卖购期权")
  158. return None
  159. print(f" 找到卖购期权: 代码={sell_call['code']}, 行权价={sell_call['exercise_price']:.4f}, 权利金={sell_call['option_price']:.4f}")
  160. # 确保买购行权价 < 卖购行权价
  161. if buy_call['exercise_price'] >= sell_call['exercise_price']:
  162. print(f" 失败: 买购行权价({buy_call['exercise_price']:.4f}) >= 卖购行权价({sell_call['exercise_price']:.4f})")
  163. return None
  164. # 计算单张最大盈利和最小盈利
  165. max_profit_per_contract = (sell_call['exercise_price'] - buy_call['exercise_price']
  166. - buy_call['option_price'] + sell_call['option_price'])
  167. min_profit_per_contract = sell_call['option_price']
  168. position = {
  169. 'open_date': date,
  170. 'etf_price': etf_price,
  171. 'buy_call_code': buy_call['code'],
  172. 'buy_call_strike': buy_call['exercise_price'],
  173. 'buy_call_price': buy_call['option_price'],
  174. 'sell_call_code': sell_call['code'],
  175. 'sell_call_strike': sell_call['exercise_price'],
  176. 'sell_call_price': sell_call['option_price'],
  177. 'quantity': quantity,
  178. 'max_profit_per_contract': max_profit_per_contract,
  179. 'min_profit_per_contract': min_profit_per_contract,
  180. 'max_profit_total': max_profit_per_contract * quantity * 10000,
  181. 'min_profit_total': min_profit_per_contract * quantity * 10000,
  182. 'expire_date': sell_call['last_trade_date'],
  183. 'status': 'open',
  184. 'is_additional': False # 是否为加仓
  185. }
  186. self.positions.append(position)
  187. # 记录交易
  188. self.trades.append({
  189. 'date': date,
  190. 'action': 'open_bull_spread',
  191. 'details': position
  192. })
  193. print(f" 成功开仓: 数量={quantity}张, 最大盈利={max_profit_per_contract:.4f}元/张")
  194. return position
  195. def should_close_position(self, position, date, etf_price):
  196. """判断是否应该平仓"""
  197. # 1. 检查是否接近到期
  198. days_to_expire = (pd.to_datetime(position['expire_date']) - pd.to_datetime(date)).days
  199. if days_to_expire <= self.params['合约到期移仓日期最大']:
  200. return True, "approaching_expiry"
  201. # 2. 检查ETF大涨情况(接近最大盈利)
  202. current_max_profit = max(0, min(etf_price - position['buy_call_strike'],
  203. position['sell_call_strike'] - position['buy_call_strike']))
  204. if current_max_profit >= position['max_profit_per_contract'] * 0.9:
  205. return True, "max_profit_reached"
  206. # 3. 检查ETF下跌情况(卖购权利金剩余低于阈值)
  207. sell_call_price = self.get_option_price(position['sell_call_code'], date)
  208. if sell_call_price is not None and sell_call_price <= self.params['卖购平仓权利金阈值']:
  209. return True, "etf_declined"
  210. return False, None
  211. def close_position(self, position, date, reason):
  212. """平仓操作"""
  213. buy_call_price = self.get_option_price(position['buy_call_code'], date)
  214. sell_call_price = self.get_option_price(position['sell_call_code'], date)
  215. if buy_call_price is None or sell_call_price is None:
  216. return None
  217. # 计算平仓收益
  218. buy_call_pnl = (buy_call_price - position['buy_call_price']) * position['quantity'] * 10000
  219. sell_call_pnl = (position['sell_call_price'] - sell_call_price) * position['quantity'] * 10000
  220. total_pnl = buy_call_pnl + sell_call_pnl
  221. # 更新持仓状态
  222. position['status'] = 'closed'
  223. position['close_date'] = date
  224. position['close_reason'] = reason
  225. position['close_buy_call_price'] = buy_call_price
  226. position['close_sell_call_price'] = sell_call_price
  227. position['realized_pnl'] = total_pnl
  228. # 记录交易
  229. self.trades.append({
  230. 'date': date,
  231. 'action': 'close_bull_spread',
  232. 'reason': reason,
  233. 'pnl': total_pnl,
  234. 'details': position
  235. })
  236. return total_pnl
  237. def should_add_position(self, etf_price):
  238. """判断是否应该加仓"""
  239. # 检查是否有开仓的主仓位
  240. main_positions = [p for p in self.positions if p['status'] == 'open' and not p['is_additional']]
  241. if not main_positions:
  242. return False
  243. # 获取最近的主仓位
  244. latest_main = max(main_positions, key=lambda x: x['open_date'])
  245. # 检查价格下跌幅度
  246. etf_name = self.get_etf_name()
  247. threshold = self.params['加仓窗口阈值'].get(etf_name, 0.2)
  248. if latest_main['etf_price'] - etf_price >= threshold:
  249. # 检查是否已经在这个价格水平加过仓
  250. existing_additional = [p for p in self.positions
  251. if p['status'] == 'open' and p['is_additional']
  252. and abs(p['etf_price'] - etf_price) < threshold * 0.5]
  253. if not existing_additional:
  254. return True
  255. return False
  256. def run_backtest(self):
  257. """运行回测"""
  258. print(f"开始回测: {self.start_date} 到 {self.end_date}")
  259. print(f"标的: {self.underlying} ({self.get_etf_name()})")
  260. # 获取ETF价格数据
  261. etf_prices = get_price(self.underlying, self.start_date, self.end_date, fields=['close'])['close']
  262. # 初始化
  263. portfolio_value = []
  264. etf_benchmark = []
  265. with tqdm.tqdm(self.trade_days, desc="回测进度") as pbar:
  266. for date in pbar:
  267. if date not in etf_prices.index:
  268. continue
  269. etf_price = etf_prices[date]
  270. # 检查现有持仓是否需要平仓
  271. open_positions = [p for p in self.positions if p['status'] == 'open']
  272. for position in open_positions:
  273. should_close, reason = self.should_close_position(position, date, etf_price)
  274. if should_close:
  275. self.close_position(position, date, reason)
  276. # 检查是否需要开新仓
  277. open_positions = [p for p in self.positions if p['status'] == 'open']
  278. if not open_positions: # 没有持仓时开仓
  279. self.open_bull_spread_position(date, etf_price)
  280. elif self.should_add_position(etf_price): # 加仓
  281. additional_pos = self.open_bull_spread_position(date, etf_price)
  282. if additional_pos:
  283. additional_pos['is_additional'] = True
  284. # 计算当日组合价值
  285. daily_pnl = self.calculate_daily_pnl(date)
  286. portfolio_value.append(daily_pnl)
  287. # ETF基准收益
  288. if len(etf_benchmark) == 0:
  289. etf_benchmark.append(0)
  290. else:
  291. etf_return = (etf_price / etf_prices[self.trade_days[0]] - 1) * 100000 # 假设初始投资10万
  292. etf_benchmark.append(etf_return)
  293. # 生成回测报告
  294. self.generate_detailed_report()
  295. return portfolio_value, etf_benchmark
  296. def calculate_daily_pnl(self, date):
  297. """计算每日损益"""
  298. total_pnl = 0
  299. for position in self.positions:
  300. if position['status'] == 'closed':
  301. if 'realized_pnl' in position:
  302. total_pnl += position['realized_pnl']
  303. elif position['status'] == 'open':
  304. # 计算未实现损益
  305. buy_call_price = self.get_option_price(position['buy_call_code'], date)
  306. sell_call_price = self.get_option_price(position['sell_call_code'], date)
  307. if buy_call_price is not None and sell_call_price is not None:
  308. buy_call_pnl = (buy_call_price - position['buy_call_price']) * position['quantity'] * 10000
  309. sell_call_pnl = (position['sell_call_price'] - sell_call_price) * position['quantity'] * 10000
  310. total_pnl += buy_call_pnl + sell_call_pnl
  311. return total_pnl
  312. def export_data_to_csv(self, filename_prefix="bull_spread_data"):
  313. """导出数据到CSV文件,用于线下分析"""
  314. # 1. 导出持仓数据
  315. positions_data = []
  316. for pos in self.positions:
  317. positions_data.append({
  318. 'open_date': pos['open_date'],
  319. 'etf_price': pos['etf_price'],
  320. 'buy_call_code': pos['buy_call_code'],
  321. 'buy_call_strike': pos['buy_call_strike'],
  322. 'buy_call_price': pos['buy_call_price'],
  323. 'sell_call_code': pos['sell_call_code'],
  324. 'sell_call_strike': pos['sell_call_strike'],
  325. 'sell_call_price': pos['sell_call_price'],
  326. 'quantity': pos['quantity'],
  327. 'max_profit_total': pos['max_profit_total'],
  328. 'expire_date': pos['expire_date'],
  329. 'status': pos['status'],
  330. 'is_additional': pos['is_additional'],
  331. 'close_date': pos.get('close_date', ''),
  332. 'close_reason': pos.get('close_reason', ''),
  333. 'realized_pnl': pos.get('realized_pnl', 0)
  334. })
  335. positions_df = pd.DataFrame(positions_data)
  336. positions_df.to_csv(f"{filename_prefix}_positions.csv", index=False, encoding='utf-8-sig')
  337. # 2. 导出交易记录
  338. trades_data = []
  339. for trade in self.trades:
  340. trades_data.append({
  341. 'date': trade['date'],
  342. 'action': trade['action'],
  343. 'pnl': trade.get('pnl', 0),
  344. 'reason': trade.get('reason', ''),
  345. 'details': str(trade.get('details', ''))
  346. })
  347. trades_df = pd.DataFrame(trades_data)
  348. trades_df.to_csv(f"{filename_prefix}_trades.csv", index=False, encoding='utf-8-sig')
  349. # 3. 导出每日损益数据
  350. daily_data = []
  351. etf_prices = get_price(self.underlying, self.start_date, self.end_date, fields=['close'])['close']
  352. for date in self.trade_days:
  353. if date not in etf_prices.index:
  354. continue
  355. etf_price = etf_prices[date]
  356. # 计算买购和卖购分别的损益
  357. buy_call_pnl = 0
  358. sell_call_pnl = 0
  359. total_positions = 0
  360. for position in self.positions:
  361. if position['open_date'] <= date and (position['status'] == 'open' or position.get('close_date', date) >= date):
  362. total_positions += 1
  363. if position['status'] == 'closed' and position.get('close_date') == date:
  364. # 已平仓的实现损益
  365. buy_call_pnl += position.get('realized_pnl', 0) / 2 # 简化分配
  366. sell_call_pnl += position.get('realized_pnl', 0) / 2
  367. elif position['status'] == 'open':
  368. # 未实现损益
  369. buy_call_price = self.get_option_price(position['buy_call_code'], date)
  370. sell_call_price = self.get_option_price(position['sell_call_code'], date)
  371. if buy_call_price is not None:
  372. buy_call_pnl += (buy_call_price - position['buy_call_price']) * position['quantity'] * 10000
  373. if sell_call_price is not None:
  374. sell_call_pnl += (position['sell_call_price'] - sell_call_price) * position['quantity'] * 10000
  375. # ETF基准收益
  376. etf_return = (etf_price / etf_prices.iloc[0] - 1) * 100000 if len(etf_prices) > 0 else 0
  377. daily_data.append({
  378. 'date': date,
  379. 'etf_price': etf_price,
  380. 'etf_return': etf_return,
  381. 'buy_call_pnl': buy_call_pnl,
  382. 'sell_call_pnl': sell_call_pnl,
  383. 'total_pnl': buy_call_pnl + sell_call_pnl,
  384. 'total_positions': total_positions
  385. })
  386. daily_df = pd.DataFrame(daily_data)
  387. daily_df.to_csv(f"{filename_prefix}_daily.csv", index=False, encoding='utf-8-sig')
  388. print(f"数据已导出到以下文件:")
  389. print(f"- {filename_prefix}_positions.csv (持仓数据)")
  390. print(f"- {filename_prefix}_trades.csv (交易记录)")
  391. print(f"- {filename_prefix}_daily.csv (每日损益)")
  392. return positions_df, trades_df, daily_df
  393. def generate_detailed_report(self):
  394. """生成详细的分析报告,分别分析买购和卖购收益"""
  395. print("\n" + "="*80)
  396. print("深度实值牛差策略详细回测报告")
  397. print("="*80)
  398. # 获取ETF价格数据
  399. etf_prices = get_price(self.underlying, self.start_date, self.end_date, fields=['close'])['close']
  400. # 计算分项收益
  401. buy_call_returns = []
  402. sell_call_returns = []
  403. combined_returns = []
  404. etf_returns = []
  405. dates = []
  406. initial_etf_price = etf_prices.iloc[0] if len(etf_prices) > 0 else 1
  407. for date in self.trade_days:
  408. if date not in etf_prices.index:
  409. continue
  410. etf_price = etf_prices[date]
  411. dates.append(date)
  412. # 计算买购和卖购分别的损益
  413. buy_call_pnl = 0
  414. sell_call_pnl = 0
  415. for position in self.positions:
  416. if position['open_date'] <= date:
  417. if position['status'] == 'closed':
  418. if 'realized_pnl' in position:
  419. # 简化处理:假设买购和卖购各承担一半损益
  420. buy_call_pnl += position['realized_pnl'] / 2
  421. sell_call_pnl += position['realized_pnl'] / 2
  422. elif position['status'] == 'open':
  423. # 计算未实现损益
  424. buy_call_price = self.get_option_price(position['buy_call_code'], date)
  425. sell_call_price = self.get_option_price(position['sell_call_code'], date)
  426. if buy_call_price is not None:
  427. buy_call_pnl += (buy_call_price - position['buy_call_price']) * position['quantity'] * 10000
  428. if sell_call_price is not None:
  429. sell_call_pnl += (position['sell_call_price'] - sell_call_price) * position['quantity'] * 10000
  430. buy_call_returns.append(buy_call_pnl)
  431. sell_call_returns.append(sell_call_pnl)
  432. combined_returns.append(buy_call_pnl + sell_call_pnl)
  433. # ETF基准收益
  434. etf_return = (etf_price / initial_etf_price - 1) * 100000 # 假设10万本金
  435. etf_returns.append(etf_return)
  436. # 基本统计
  437. total_trades = len([t for t in self.trades if t['action'] == 'close_bull_spread'])
  438. winning_trades = len([t for t in self.trades if t['action'] == 'close_bull_spread' and t['pnl'] > 0])
  439. if total_trades > 0:
  440. win_rate = winning_trades / total_trades * 100
  441. total_pnl = sum([t['pnl'] for t in self.trades if t['action'] == 'close_bull_spread'])
  442. avg_pnl = total_pnl / total_trades
  443. else:
  444. win_rate = 0
  445. total_pnl = 0
  446. avg_pnl = 0
  447. print(f"交易次数: {total_trades}")
  448. print(f"胜率: {win_rate:.2f}%")
  449. print(f"总收益: {total_pnl:.2f}元")
  450. print(f"平均每笔收益: {avg_pnl:.2f}元")
  451. # 分项收益统计和对比分析
  452. if len(buy_call_returns) > 0:
  453. final_buy_call = buy_call_returns[-1]
  454. final_sell_call = sell_call_returns[-1]
  455. final_combined = combined_returns[-1]
  456. final_etf = etf_returns[-1]
  457. print(f"\n=== 分项收益分析 ===")
  458. print(f"买购期权收益: {final_buy_call:.2f}元")
  459. print(f"卖购期权收益: {final_sell_call:.2f}元")
  460. print(f"组合总收益: {final_combined:.2f}元")
  461. print(f"ETF基准收益: {final_etf:.2f}元")
  462. print(f"\n=== 与ETF基准对比分析 ===")
  463. print(f"1. 牛差策略 vs ETF: {final_combined:.2f} vs {final_etf:.2f} = {final_combined - final_etf:+.2f}元")
  464. print(f"2. 买购策略 vs ETF: {final_buy_call:.2f} vs {final_etf:.2f} = {final_buy_call - final_etf:+.2f}元")
  465. print(f"3. 卖购策略 vs ETF: {final_sell_call:.2f} vs {final_etf:.2f} = {final_sell_call - final_etf:+.2f}元")
  466. # 持仓统计
  467. open_positions = len([p for p in self.positions if p['status'] == 'open'])
  468. closed_positions = len([p for p in self.positions if p['status'] == 'closed'])
  469. print(f"\n=== 持仓统计 ===")
  470. print(f"当前持仓: {open_positions}个")
  471. print(f"已平仓位: {closed_positions}个")
  472. # 绘制分项收益曲线
  473. if len(dates) > 0:
  474. self.plot_detailed_performance(dates, buy_call_returns, sell_call_returns, combined_returns, etf_returns)
  475. self.plot_strategy_vs_etf_comparison(dates, buy_call_returns, sell_call_returns, combined_returns, etf_returns)
  476. return {
  477. 'dates': dates,
  478. 'buy_call_returns': buy_call_returns,
  479. 'sell_call_returns': sell_call_returns,
  480. 'combined_returns': combined_returns,
  481. 'etf_returns': etf_returns
  482. }
  483. def plot_detailed_performance(self, dates, buy_call_returns, sell_call_returns, combined_returns, etf_returns):
  484. """绘制详细的策略表现图,分别显示买购和卖购收益"""
  485. # 设置中文字体
  486. plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans']
  487. plt.rcParams['axes.unicode_minus'] = False
  488. plt.figure(figsize=(15, 12))
  489. # 第一个子图:分项收益对比
  490. plt.subplot(3, 1, 1)
  491. plt.plot(dates, buy_call_returns, label='买购期权收益', linewidth=2, color='blue')
  492. plt.plot(dates, sell_call_returns, label='卖购期权收益', linewidth=2, color='red')
  493. plt.plot(dates, combined_returns, label='组合总收益', linewidth=2, color='green')
  494. plt.plot(dates, etf_returns, label=f'{self.get_etf_name()}基准', linewidth=2, color='orange')
  495. plt.axhline(0, color='gray', linestyle='--', alpha=0.5)
  496. plt.title('策略分项收益对比')
  497. plt.ylabel('收益(元)')
  498. plt.legend()
  499. plt.grid(True, alpha=0.3)
  500. # 第二个子图:累计收益率
  501. plt.subplot(3, 1, 2)
  502. if len(combined_returns) > 0 and len(etf_returns) > 0:
  503. combined_cumret = [(x / 100000) * 100 for x in combined_returns] # 转换为百分比
  504. etf_cumret = [(x / 100000) * 100 for x in etf_returns]
  505. plt.plot(dates, combined_cumret, label='策略累计收益率', linewidth=2, color='green')
  506. plt.plot(dates, etf_cumret, label='ETF累计收益率', linewidth=2, color='orange')
  507. plt.axhline(0, color='gray', linestyle='--', alpha=0.5)
  508. plt.title('累计收益率对比')
  509. plt.ylabel('收益率(%)')
  510. plt.legend()
  511. plt.grid(True, alpha=0.3)
  512. # 第三个子图:持仓数量变化
  513. plt.subplot(3, 1, 3)
  514. position_counts = []
  515. for date in dates:
  516. count = len([p for p in self.positions
  517. if p['open_date'] <= date and
  518. (p['status'] == 'open' or p.get('close_date', date) >= date)])
  519. position_counts.append(count)
  520. plt.plot(dates, position_counts, label='持仓数量', linewidth=2, color='purple')
  521. plt.title('持仓数量变化')
  522. plt.ylabel('持仓数量')
  523. plt.xlabel('日期')
  524. plt.legend()
  525. plt.grid(True, alpha=0.3)
  526. plt.tight_layout()
  527. plt.show()
  528. def plot_strategy_vs_etf_comparison(self, dates, buy_call_returns, sell_call_returns, combined_returns, etf_returns):
  529. """绘制三个策略分别与ETF的对比图"""
  530. # 设置中文字体
  531. plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans']
  532. plt.rcParams['axes.unicode_minus'] = False
  533. plt.figure(figsize=(15, 12))
  534. # 第一个子图:牛差策略 vs ETF
  535. plt.subplot(3, 1, 1)
  536. plt.plot(dates, combined_returns, label='牛差策略收益', linewidth=2, color='green')
  537. plt.plot(dates, etf_returns, label='ETF基准收益', linewidth=2, color='orange')
  538. plt.axhline(0, color='gray', linestyle='--', alpha=0.5)
  539. plt.title('牛差策略 vs ETF基准收益对比')
  540. plt.ylabel('收益(元)')
  541. plt.legend()
  542. plt.grid(True, alpha=0.3)
  543. # 第二个子图:买购策略 vs ETF
  544. plt.subplot(3, 1, 2)
  545. plt.plot(dates, buy_call_returns, label='买购期权收益', linewidth=2, color='blue')
  546. plt.plot(dates, etf_returns, label='ETF基准收益', linewidth=2, color='orange')
  547. plt.axhline(0, color='gray', linestyle='--', alpha=0.5)
  548. plt.title('买购期权 vs ETF基准收益对比')
  549. plt.ylabel('收益(元)')
  550. plt.legend()
  551. plt.grid(True, alpha=0.3)
  552. # 第三个子图:卖购策略 vs ETF
  553. plt.subplot(3, 1, 3)
  554. plt.plot(dates, sell_call_returns, label='卖购期权收益', linewidth=2, color='red')
  555. plt.plot(dates, etf_returns, label='ETF基准收益', linewidth=2, color='orange')
  556. plt.axhline(0, color='gray', linestyle='--', alpha=0.5)
  557. plt.title('卖购期权 vs ETF基准收益对比')
  558. plt.ylabel('收益(元)')
  559. plt.xlabel('日期')
  560. plt.legend()
  561. plt.grid(True, alpha=0.3)
  562. plt.tight_layout()
  563. plt.show()
  564. def plot_performance(self, portfolio_value, etf_benchmark):
  565. """绘制策略表现图"""
  566. plt.figure(figsize=(12, 8))
  567. dates = self.trade_days[:len(portfolio_value)]
  568. plt.subplot(2, 1, 1)
  569. plt.plot(dates, portfolio_value, label='牛差策略', linewidth=2)
  570. plt.plot(dates, etf_benchmark, label=f'{self.get_etf_name()}基准', linewidth=2)
  571. plt.title('策略收益对比')
  572. plt.ylabel('收益(元)')
  573. plt.legend()
  574. plt.grid(True, alpha=0.3)
  575. plt.subplot(2, 1, 2)
  576. # 绘制持仓数量变化
  577. position_counts = []
  578. for date in dates:
  579. count = len([p for p in self.positions
  580. if p['open_date'] <= date and
  581. (p['status'] == 'open' or p.get('close_date', date) >= date)])
  582. position_counts.append(count)
  583. plt.plot(dates, position_counts, label='持仓数量', linewidth=2, color='orange')
  584. plt.title('持仓数量变化')
  585. plt.ylabel('持仓数量')
  586. plt.xlabel('日期')
  587. plt.legend()
  588. plt.grid(True, alpha=0.3)
  589. plt.tight_layout()
  590. plt.show()
  591. def print_position_details(self):
  592. """打印持仓详情"""
  593. print("\n" + "="*80)
  594. print("持仓详情")
  595. print("="*80)
  596. for i, pos in enumerate(self.positions):
  597. print(f"\n持仓 {i+1}:")
  598. print(f" 开仓日期: {pos['open_date']}")
  599. print(f" ETF价格: {pos['etf_price']:.4f}")
  600. print(f" 买购: {pos['buy_call_strike']:.2f} @ {pos['buy_call_price']:.4f}")
  601. print(f" 卖购: {pos['sell_call_strike']:.2f} @ {pos['sell_call_price']:.4f}")
  602. print(f" 数量: {pos['quantity']}张")
  603. print(f" 单张最大盈利: {pos['max_profit_per_contract']:.4f}")
  604. print(f" 总最大盈利: {pos['max_profit_total']:.2f}元")
  605. print(f" 状态: {pos['status']}")
  606. print(f" 是否加仓: {'是' if pos['is_additional'] else '否'}")
  607. if pos['status'] == 'closed':
  608. print(f" 平仓日期: {pos['close_date']}")
  609. print(f" 平仓原因: {pos['close_reason']}")
  610. print(f" 实现损益: {pos['realized_pnl']:.2f}元")
  611. class OptionsAnalyzer:
  612. """期权分析工具类"""
  613. @staticmethod
  614. def analyze_options(*options):
  615. """
  616. 统一的期权分析方法
  617. 参数: *options: 一个或多个期权,每个期权格式为 (direction, option_type, premium, strike_price, quantity)
  618. """
  619. if not options:
  620. raise ValueError("请至少提供一个期权")
  621. # 解析期权数据
  622. option_list = []
  623. all_strikes = []
  624. for i, opt in enumerate(options):
  625. if len(opt) != 5:
  626. raise ValueError(f"期权{i+1}格式错误,应为(direction, option_type, premium, strike_price, quantity)")
  627. direction, option_type, premium, strike_price, quantity = opt
  628. option_list.append({
  629. 'direction': direction,
  630. 'option_type': option_type,
  631. 'premium': premium,
  632. 'strike_price': strike_price,
  633. 'quantity': quantity
  634. })
  635. all_strikes.append(strike_price)
  636. # 确定价格分析区间
  637. min_strike = min(all_strikes)
  638. max_strike = max(all_strikes)
  639. price_min = min_strike * 0.7
  640. price_max = max_strike * 1.3
  641. # 生成价格序列
  642. gap = (price_max - price_min) / 1000
  643. prices = np.arange(price_min, price_max + gap, gap)
  644. # 计算每个期权的收益
  645. results = {'price': prices}
  646. for i, opt in enumerate(option_list):
  647. profits = []
  648. for price in prices:
  649. profit = OptionsAnalyzer._calculate_profit(opt, price)
  650. profits.append(profit)
  651. results[f'opt{i+1}'] = profits
  652. # 计算组合收益
  653. if len(option_list) > 1:
  654. combined_profits = []
  655. for j in range(len(prices)):
  656. total = sum(results[f'opt{i+1}'][j] for i in range(len(option_list)))
  657. combined_profits.append(total)
  658. results['combined'] = combined_profits
  659. # 绘制图表
  660. OptionsAnalyzer._plot_results(results, option_list, prices)
  661. # 打印分析报告
  662. OptionsAnalyzer._print_report(results, option_list, prices)
  663. return pd.DataFrame(results)
  664. @staticmethod
  665. def _calculate_profit(option, price):
  666. """计算单个期权在特定价格下的收益"""
  667. direction = option['direction']
  668. option_type = option['option_type']
  669. premium = option['premium']
  670. strike_price = option['strike_price']
  671. quantity = option['quantity']
  672. if direction == 'buy' and option_type == 'call':
  673. # 买入认购
  674. if price > strike_price:
  675. return (price - strike_price - premium) * quantity
  676. else:
  677. return -premium * quantity
  678. elif direction == 'sell' and option_type == 'call':
  679. # 卖出认购
  680. if price > strike_price:
  681. return -(price - strike_price - premium) * quantity
  682. else:
  683. return premium * quantity
  684. elif direction == 'buy' and option_type == 'put':
  685. # 买入认沽
  686. if price < strike_price:
  687. return (strike_price - price - premium) * quantity
  688. else:
  689. return -premium * quantity
  690. elif direction == 'sell' and option_type == 'put':
  691. # 卖出认沽
  692. if price < strike_price:
  693. return -(strike_price - price - premium) * quantity
  694. else:
  695. return premium * quantity
  696. return 0
  697. @staticmethod
  698. def _plot_results(results, option_list, prices):
  699. """绘制分析图表"""
  700. # 设置中文字体
  701. plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans']
  702. plt.rcParams['axes.unicode_minus'] = False
  703. plt.figure(figsize=(14, 10))
  704. colors = ['blue', 'green', 'orange', 'purple', 'brown']
  705. # 绘制单个期权曲线
  706. for i in range(len(option_list)):
  707. opt = option_list[i]
  708. opt_name = f'opt{i+1}'
  709. strategy_name = f"{opt['direction'].upper()} {opt['option_type'].upper()}"
  710. color = colors[i % len(colors)]
  711. plt.plot(prices, results[opt_name], '--', color=color, linewidth=2, alpha=0.7,
  712. label=f'{opt_name}: {strategy_name} (行权价:{opt["strike_price"]})')
  713. # 绘制组合曲线
  714. if 'combined' in results:
  715. plt.plot(prices, results['combined'], 'r-', linewidth=3, label='组合收益')
  716. # 添加零线和行权价线
  717. plt.axhline(0, color='gray', linestyle='-', alpha=0.5)
  718. for opt in option_list:
  719. plt.axvline(opt['strike_price'], color='gray', linestyle='--', alpha=0.3)
  720. # 找到并标注关键点
  721. if 'combined' in results:
  722. OptionsAnalyzer._mark_key_points(results['combined'], prices, '组合')
  723. elif len(option_list) == 1:
  724. OptionsAnalyzer._mark_key_points(results['opt1'], prices, '期权')
  725. plt.xlabel('标的资产价格', fontsize=12)
  726. plt.ylabel('收益/损失', fontsize=12)
  727. if len(option_list) == 1:
  728. opt = option_list[0]
  729. title = f'{opt["direction"].upper()} {opt["option_type"].upper()} 期权分析'
  730. else:
  731. title = f'期权组合分析 ({len(option_list)}个期权)'
  732. plt.title(title, fontsize=14, weight='bold')
  733. plt.grid(True, alpha=0.3)
  734. plt.legend()
  735. plt.tight_layout()
  736. plt.show()
  737. @staticmethod
  738. def _mark_key_points(profits, prices, label_prefix):
  739. """标注关键点:盈亏平衡点、最大收益/损失边界点"""
  740. # 标注盈亏平衡点
  741. for i in range(len(profits) - 1):
  742. if profits[i] * profits[i + 1] <= 0: # 符号改变
  743. # 线性插值找到精确平衡点
  744. p1, profit1 = prices[i], profits[i]
  745. p2, profit2 = prices[i + 1], profits[i + 1]
  746. if profit2 != profit1:
  747. breakeven_price = p1 - profit1 * (p2 - p1) / (profit2 - profit1)
  748. plt.plot(breakeven_price, 0, 'ro', markersize=10)
  749. plt.annotate(f'平衡点: {breakeven_price:.3f}',
  750. xy=(breakeven_price, 0),
  751. xytext=(breakeven_price + (prices.max() - prices.min()) * 0.05, max(profits) * 0.1),
  752. arrowprops=dict(arrowstyle='->', color='red'),
  753. fontsize=11, color='red', weight='bold')
  754. @staticmethod
  755. def _print_report(results, option_list, prices):
  756. """打印分析报告"""
  757. print("=" * 60)
  758. print("期权分析报告")
  759. print("=" * 60)
  760. # 期权基本信息
  761. for i, opt in enumerate(option_list):
  762. print(f"期权{i+1}: {opt['direction'].upper()} {opt['option_type'].upper()}")
  763. print(f" 行权价: {opt['strike_price']}")
  764. print(f" 权利金: {opt['premium']}")
  765. print(f" 数量: {opt['quantity']}手")
  766. # 分析关键指标
  767. if 'combined' in results:
  768. profits = results['combined']
  769. print(f"\n【组合分析】")
  770. else:
  771. profits = results['opt1']
  772. print(f"\n【单期权分析】")
  773. max_profit = max(profits)
  774. min_profit = min(profits)
  775. max_idx = profits.tolist().index(max_profit) if hasattr(profits, 'tolist') else profits.index(max_profit)
  776. min_idx = profits.tolist().index(min_profit) if hasattr(profits, 'tolist') else profits.index(min_profit)
  777. print(f"最大收益: {max_profit:.4f} (标的价格: {prices[max_idx]:.4f})")
  778. print(f"最大损失: {min_profit:.4f} (标的价格: {prices[min_idx]:.4f})")
  779. print(f"一单最大收益: {max_profit * 10000:.2f}元")
  780. print(f"一单最大亏损: {abs(min_profit) * 10000:.2f}元")
  781. # 找盈亏平衡点
  782. breakeven_points = []
  783. for i in range(len(profits) - 1):
  784. if profits[i] * profits[i + 1] <= 0:
  785. p1, profit1 = prices[i], profits[i]
  786. p2, profit2 = prices[i + 1], profits[i + 1]
  787. if profit2 != profit1:
  788. bp = p1 - profit1 * (p2 - p1) / (profit2 - profit1)
  789. breakeven_points.append(bp)
  790. if breakeven_points:
  791. print(f"盈亏平衡点: {[f'{bp:.4f}' for bp in breakeven_points]}")
  792. else:
  793. print("无盈亏平衡点")
  794. print("=" * 60)
  795. def test_opening_logic():
  796. """专门测试开仓逻辑"""
  797. print("="*60)
  798. print("测试开仓逻辑")
  799. print("="*60)
  800. # 创建策略实例
  801. strategy = DeepITMBullSpreadStrategy(
  802. underlying='510300.XSHG', # 300ETF
  803. start_date='2024-01-01',
  804. end_date='2025-06-30'
  805. )
  806. # 获取ETF价格数据
  807. try:
  808. etf_prices = get_price(strategy.underlying, strategy.start_date, strategy.end_date, fields=['close'])['close']
  809. print(f"成功获取ETF价格数据,共{len(etf_prices)}个交易日")
  810. # 测试前几个交易日的开仓逻辑
  811. test_dates = strategy.trade_days[:10] # 只测试前10个交易日
  812. for i, date in enumerate(test_dates):
  813. if date not in etf_prices.index:
  814. continue
  815. etf_price = etf_prices[date]
  816. print(f"\n第{i+1}个交易日测试: {date}, ETF价格: {etf_price:.4f}")
  817. # 尝试开仓
  818. position = strategy.open_bull_spread_position(date, etf_price)
  819. if position:
  820. print(f"✓ 成功开仓!")
  821. print(f" 持仓总数: {len(strategy.positions)}")
  822. break
  823. else:
  824. print(f"✗ 开仓失败")
  825. # 打印最终结果
  826. print(f"\n测试结果:")
  827. print(f"总持仓数: {len(strategy.positions)}")
  828. print(f"总交易数: {len(strategy.trades)}")
  829. if strategy.positions:
  830. print(f"第一个持仓详情:")
  831. pos = strategy.positions[0]
  832. for key, value in pos.items():
  833. print(f" {key}: {value}")
  834. return strategy
  835. except Exception as e:
  836. print(f"测试过程中出现错误: {e}")
  837. import traceback
  838. traceback.print_exc()
  839. return None
  840. def test_strategy():
  841. """测试策略"""
  842. print("开始测试深度实值牛差策略...")
  843. # 创建策略实例
  844. strategy = DeepITMBullSpreadStrategy(
  845. underlying='510300.XSHG', # 300ETF
  846. start_date='2024-01-01',
  847. end_date='2025-06-30'
  848. )
  849. # 运行回测
  850. try:
  851. print("正在运行回测...")
  852. strategy.run_backtest()
  853. print("正在生成详细报告...")
  854. # 生成详细报告(包含买购和卖购分项分析)
  855. detailed_results = strategy.generate_detailed_report()
  856. print("正在导出数据...")
  857. # 导出数据到CSV
  858. positions_df, trades_df, daily_df = strategy.export_data_to_csv("bull_spread_300etf")
  859. print("正在打印持仓详情...")
  860. # 打印详细持仓信息
  861. strategy.print_position_details()
  862. return strategy, detailed_results
  863. except Exception as e:
  864. print(f"回测过程中出现错误: {e}")
  865. import traceback
  866. traceback.print_exc()
  867. return None, None
  868. def analyze_bull_spread_example():
  869. """分析牛差策略示例"""
  870. print("\n" + "="*60)
  871. print("牛差策略期权组合分析示例")
  872. print("="*60)
  873. # 假设当前300ETF价格为4.0
  874. underlying_price = 4.0
  875. # 根据策略逻辑选择期权
  876. buy_strike = underlying_price * 0.90 # 深度实值买购
  877. sell_strike = underlying_price * 1.00 # 平值卖购
  878. # 估算权利金(简化)
  879. buy_premium = 0.44 # 深度实值期权,主要是内在价值
  880. sell_premium = 0.20 # 平值期权,主要是时间价值
  881. print(f"当前标的价格: {underlying_price}")
  882. print(f"买购期权: 行权价 {buy_strike:.2f}, 权利金 {buy_premium:.4f}")
  883. print(f"卖购期权: 行权价 {sell_strike:.2f}, 权利金 {sell_premium:.4f}")
  884. # 使用期权分析工具
  885. analyzer = OptionsAnalyzer()
  886. result = analyzer.analyze_options(
  887. ('buy', 'call', buy_premium, buy_strike, 1),
  888. ('sell', 'call', sell_premium, sell_strike, 1)
  889. )
  890. return result
  891. def compare_with_etf_holding():
  892. """三个策略分别与ETF持有收益对比"""
  893. print("\n" + "="*80)
  894. print("三个策略分别与ETF持有收益对比")
  895. print("="*80)
  896. initial_price = 4.0
  897. initial_investment = 100000
  898. # 牛差策略参数
  899. buy_strike = initial_price * 0.90
  900. sell_strike = initial_price * 1.00
  901. buy_premium = 0.44
  902. sell_premium = 0.20
  903. # 计算可开仓张数
  904. net_premium = buy_premium - sell_premium
  905. contracts = int(initial_investment / (net_premium * 10000))
  906. print(f"初始投资: {initial_investment:,.0f}元")
  907. print(f"初始ETF价格: {initial_price:.2f}")
  908. print(f"牛差组合: 买购{buy_strike:.2f}@{buy_premium:.4f}, 卖购{sell_strike:.2f}@{sell_premium:.4f}")
  909. print(f"可开仓张数: {contracts}张")
  910. # 模拟不同价格变化下的收益
  911. price_changes = np.arange(-20, 21, 5)
  912. results = []
  913. for change in price_changes:
  914. new_price = initial_price * (1 + change / 100)
  915. # ETF持有收益
  916. etf_return = (new_price - initial_price) / initial_price * initial_investment
  917. # 分别计算三个策略的收益
  918. buy_call_value = max(0, new_price - buy_strike)
  919. sell_call_value = max(0, new_price - sell_strike)
  920. # 1. 买购期权收益
  921. buy_call_pnl = (buy_call_value - buy_premium) * contracts * 10000
  922. # 2. 卖购期权收益
  923. sell_call_pnl = (sell_premium - sell_call_value) * contracts * 10000
  924. # 3. 牛差组合收益
  925. bull_spread_return = buy_call_pnl + sell_call_pnl
  926. results.append({
  927. 'price_change': change,
  928. 'new_price': new_price,
  929. 'etf_return': etf_return,
  930. 'buy_call_return': buy_call_pnl,
  931. 'sell_call_return': sell_call_pnl,
  932. 'bull_spread_return': bull_spread_return,
  933. 'bull_vs_etf': bull_spread_return - etf_return,
  934. 'buy_vs_etf': buy_call_pnl - etf_return,
  935. 'sell_vs_etf': sell_call_pnl - etf_return
  936. })
  937. # 创建对比表格
  938. df = pd.DataFrame(results)
  939. print(f"\n=== 详细收益对比表 ===")
  940. print("价格变化(%) | 新价格 | ETF收益 | 买购收益 | 卖购收益 | 牛差收益")
  941. print("-" * 75)
  942. for _, row in df.iterrows():
  943. print(f"{row['price_change']:>8.0f}% | {row['new_price']:>6.2f} | "
  944. f"{row['etf_return']:>7.0f} | {row['buy_call_return']:>8.0f} | "
  945. f"{row['sell_call_return']:>8.0f} | {row['bull_spread_return']:>8.0f}")
  946. print(f"\n=== 与ETF基准的差异对比 ===")
  947. print("价格变化(%) | 牛差-ETF | 买购-ETF | 卖购-ETF")
  948. print("-" * 50)
  949. for _, row in df.iterrows():
  950. print(f"{row['price_change']:>8.0f}% | {row['bull_vs_etf']:>8.0f} | "
  951. f"{row['buy_vs_etf']:>8.0f} | {row['sell_vs_etf']:>8.0f}")
  952. # 绘制三个对比图
  953. # 设置中文字体
  954. plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans']
  955. plt.rcParams['axes.unicode_minus'] = False
  956. plt.figure(figsize=(15, 12))
  957. # 第一个子图:牛差策略 vs ETF
  958. plt.subplot(3, 1, 1)
  959. plt.plot(df['price_change'], df['etf_return'], 'o-', linewidth=2, label='ETF持有', color='orange')
  960. plt.plot(df['price_change'], df['bull_spread_return'], 's-', linewidth=2, label='牛差策略', color='green')
  961. plt.axhline(0, color='gray', linestyle='--', alpha=0.5)
  962. plt.xlabel('价格变化 (%)')
  963. plt.ylabel('收益 (元)')
  964. plt.title('牛差策略 vs ETF持有收益对比')
  965. plt.legend()
  966. plt.grid(True, alpha=0.3)
  967. # 第二个子图:买购策略 vs ETF
  968. plt.subplot(3, 1, 2)
  969. plt.plot(df['price_change'], df['etf_return'], 'o-', linewidth=2, label='ETF持有', color='orange')
  970. plt.plot(df['price_change'], df['buy_call_return'], '^-', linewidth=2, label='买购期权', color='blue')
  971. plt.axhline(0, color='gray', linestyle='--', alpha=0.5)
  972. plt.xlabel('价格变化 (%)')
  973. plt.ylabel('收益 (元)')
  974. plt.title('买购期权 vs ETF持有收益对比')
  975. plt.legend()
  976. plt.grid(True, alpha=0.3)
  977. # 第三个子图:卖购策略 vs ETF
  978. plt.subplot(3, 1, 3)
  979. plt.plot(df['price_change'], df['etf_return'], 'o-', linewidth=2, label='ETF持有', color='orange')
  980. plt.plot(df['price_change'], df['sell_call_return'], 'v-', linewidth=2, label='卖购期权', color='red')
  981. plt.axhline(0, color='gray', linestyle='--', alpha=0.5)
  982. plt.xlabel('价格变化 (%)')
  983. plt.ylabel('收益 (元)')
  984. plt.title('卖购期权 vs ETF持有收益对比')
  985. plt.legend()
  986. plt.grid(True, alpha=0.3)
  987. plt.tight_layout()
  988. plt.show()
  989. return df
  990. if __name__ == "__main__":
  991. print("深度实值牛差策略综合测试工具")
  992. print("="*50)
  993. # 0. 首先测试开仓逻辑
  994. print("\n0. 测试开仓逻辑:")
  995. test_strategy_instance = test_opening_logic()
  996. if test_strategy_instance and len(test_strategy_instance.positions) > 0:
  997. print("✓ 开仓逻辑测试通过,继续完整回测")
  998. # 1. 运行策略回测
  999. print("\n1. 运行策略回测:")
  1000. strategy, detailed_results = test_strategy()
  1001. # 2. 分析期权组合
  1002. print("\n2. 期权组合分析:")
  1003. option_analysis = analyze_bull_spread_example()
  1004. # 3. 与ETF持有对比
  1005. print("\n3. 与ETF持有收益对比:")
  1006. comparison_results = compare_with_etf_holding()
  1007. print(f"\n测试完成!数据已导出到CSV文件,可用于进一步分析。")
  1008. else:
  1009. print("✗ 开仓逻辑测试失败,请检查期权数据和策略参数")
  1010. print("建议检查以下几点:")
  1011. print("1. 期权合约数据是否可用")
  1012. print("2. 策略参数设置是否合理")
  1013. print("3. 时间价值阈值是否过于严格")