| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248 |
- # 深度实值买购和卖购组合的牛差策略
- # 参考文档: 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. 时间价值阈值是否过于严格")
|