|
@@ -34,8 +34,8 @@ class DeepITMBullSpreadStrategy:
|
|
|
'min_days_to_expiry': 15, # 最少开仓日期(距离到期日)
|
|
'min_days_to_expiry': 15, # 最少开仓日期(距离到期日)
|
|
|
'call_time_value_threshold': 0.015, # 买购时间价值阈值(README中为0.015)
|
|
'call_time_value_threshold': 0.015, # 买购时间价值阈值(README中为0.015)
|
|
|
'put_close_premium_threshold': 0.005, # 卖购平仓权利金阈值(README中为50单位,转换为0.005)
|
|
'put_close_premium_threshold': 0.005, # 卖购平仓权利金阈值(README中为50单位,转换为0.005)
|
|
|
- 'max_days_before_expiry': 7, # 合约到期移仓日期最大(交易日)
|
|
|
|
|
- 'min_days_before_expiry': 2, # 合约到期移仓日期最小(交易日)
|
|
|
|
|
|
|
+ 'max_days_before_expiry': 3, # 合约到期移仓日期最大(交易日)
|
|
|
|
|
+ 'min_days_before_expiry': 1, # 合约到期移仓日期最小(交易日)
|
|
|
'add_position_threshold': {'510300': 0.2, '510050': 0.1, '159915': 0.15}, # 加仓窗口阈值
|
|
'add_position_threshold': {'510300': 0.2, '510050': 0.1, '159915': 0.15}, # 加仓窗口阈值
|
|
|
'max_add_positions': 2, # 加仓次数上限
|
|
'max_add_positions': 2, # 加仓次数上限
|
|
|
'max_profit_close_threshold': 0.83
|
|
'max_profit_close_threshold': 0.83
|
|
@@ -62,12 +62,12 @@ class DeepITMBullSpreadStrategy:
|
|
|
self.month_split = list(self.trade_days.resample('M', label='left').mean().index) + [pd.to_datetime(end_date)]
|
|
self.month_split = list(self.trade_days.resample('M', label='left').mean().index) + [pd.to_datetime(end_date)]
|
|
|
|
|
|
|
|
# 调试输出:显示月份分割点
|
|
# 调试输出:显示月份分割点
|
|
|
- print(f"Month split 计算结果 ({len(self.month_split)}个分割点):")
|
|
|
|
|
|
|
+ # print(f"Month split 计算结果 ({len(self.month_split)}个分割点):")
|
|
|
for i, split_date in enumerate(self.month_split):
|
|
for i, split_date in enumerate(self.month_split):
|
|
|
- print(f" 索引{i}: {split_date.strftime('%Y-%m-%d')}")
|
|
|
|
|
|
|
+ # print(f" 索引{i}: {split_date.strftime('%Y-%m-%d')}")
|
|
|
if i < len(self.month_split) - 1:
|
|
if i < len(self.month_split) - 1:
|
|
|
next_split = self.month_split[i + 1]
|
|
next_split = self.month_split[i + 1]
|
|
|
- print(f" 月份{i}覆盖范围: {split_date.strftime('%Y-%m-%d')} 到 {next_split.strftime('%Y-%m-%d')}")
|
|
|
|
|
|
|
+ # print(f" 月份{i}覆盖范围: {split_date.strftime('%Y-%m-%d')} 到 {next_split.strftime('%Y-%m-%d')}")
|
|
|
|
|
|
|
|
# 用于存储前一日ETF价格,计算当日涨幅
|
|
# 用于存储前一日ETF价格,计算当日涨幅
|
|
|
self.previous_etf_price = None
|
|
self.previous_etf_price = None
|
|
@@ -116,7 +116,7 @@ class DeepITMBullSpreadStrategy:
|
|
|
query_date = trade_date.date() if hasattr(trade_date, 'date') else trade_date
|
|
query_date = trade_date.date() if hasattr(trade_date, 'date') else trade_date
|
|
|
current_year = query_date.year
|
|
current_year = query_date.year
|
|
|
current_month = query_date.month
|
|
current_month = query_date.month
|
|
|
- print(f" 期权筛选调试: month_idx={month_idx}, query_date={query_date}, 当前年月={current_year}-{current_month:02d}")
|
|
|
|
|
|
|
+ # print(f" 期权筛选调试: month_idx={month_idx}, query_date={query_date}, 当前年月={current_year}-{current_month:02d}")
|
|
|
|
|
|
|
|
# 计算目标月份
|
|
# 计算目标月份
|
|
|
if month_idx == 0:
|
|
if month_idx == 0:
|
|
@@ -145,7 +145,7 @@ class DeepITMBullSpreadStrategy:
|
|
|
else:
|
|
else:
|
|
|
end_date = pd.to_datetime(f'{target_year}-{target_month + 1:02d}-01').date() - pd.Timedelta(days=1)
|
|
end_date = pd.to_datetime(f'{target_year}-{target_month + 1:02d}-01').date() - pd.Timedelta(days=1)
|
|
|
|
|
|
|
|
- print(f" 月份索引{month_idx}({target_year}-{target_month:02d})期权筛选范围: {start_date} 到 {end_date}")
|
|
|
|
|
|
|
+ # print(f" 月份索引{month_idx}({target_year}-{target_month:02d})期权筛选范围: {start_date} 到 {end_date}")
|
|
|
|
|
|
|
|
q_contract_info = query(
|
|
q_contract_info = query(
|
|
|
opt.OPT_CONTRACT_INFO.code,
|
|
opt.OPT_CONTRACT_INFO.code,
|
|
@@ -175,17 +175,18 @@ class DeepITMBullSpreadStrategy:
|
|
|
|
|
|
|
|
def get_monthly_option_candidates(self, trade_date, month_idx, silent=False):
|
|
def get_monthly_option_candidates(self, trade_date, month_idx, silent=False):
|
|
|
"""获取指定月份的所有认购期权候选信息
|
|
"""获取指定月份的所有认购期权候选信息
|
|
|
- 返回: (contract_info, month_info) - contract_info为期权列表,month_info为月份信息
|
|
|
|
|
|
|
+ 返回: (contract_info, month_info, failure_reason) - contract_info为期权列表,month_info为月份信息,failure_reason为失败原因(成功时为None)
|
|
|
"""
|
|
"""
|
|
|
- if not silent:
|
|
|
|
|
- print(f"{trade_date.strftime('%Y-%m-%d')} 获取月份索引 {month_idx} 的认购期权候选信息")
|
|
|
|
|
|
|
+ # if not silent:
|
|
|
|
|
+ # print(f"{trade_date.strftime('%Y-%m-%d')} 获取月份索引 {month_idx} 的认购期权候选信息")
|
|
|
|
|
|
|
|
# 获取期权合约信息
|
|
# 获取期权合约信息
|
|
|
contract_info = self.get_option_contracts(trade_date, month_idx, 'CO')
|
|
contract_info = self.get_option_contracts(trade_date, month_idx, 'CO')
|
|
|
|
|
|
|
|
if contract_info.empty:
|
|
if contract_info.empty:
|
|
|
|
|
+ failure_reason = "无可用认购期权合约"
|
|
|
if not silent:
|
|
if not silent:
|
|
|
- print(f" 月份索引 {month_idx} 无可用认购期权合约")
|
|
|
|
|
|
|
+ print(f" 月份索引 {month_idx} {failure_reason}")
|
|
|
# 增加调试信息,显示查询的时间范围
|
|
# 增加调试信息,显示查询的时间范围
|
|
|
if month_idx < len(self.month_split) - 1:
|
|
if month_idx < len(self.month_split) - 1:
|
|
|
start_date = self.month_split[month_idx].date() if hasattr(self.month_split[month_idx], 'date') else self.month_split[month_idx]
|
|
start_date = self.month_split[month_idx].date() if hasattr(self.month_split[month_idx], 'date') else self.month_split[month_idx]
|
|
@@ -194,7 +195,7 @@ class DeepITMBullSpreadStrategy:
|
|
|
print(f" 查询日期: {trade_date}")
|
|
print(f" 查询日期: {trade_date}")
|
|
|
print(f" 标的代码: {self.get_underlying_code()}")
|
|
print(f" 标的代码: {self.get_underlying_code()}")
|
|
|
print(f" month_split总长度: {len(self.month_split)}")
|
|
print(f" month_split总长度: {len(self.month_split)}")
|
|
|
- return None, None
|
|
|
|
|
|
|
+ return None, None, failure_reason
|
|
|
|
|
|
|
|
# 获取月份信息
|
|
# 获取月份信息
|
|
|
underlying_code = self.get_underlying_code()
|
|
underlying_code = self.get_underlying_code()
|
|
@@ -204,8 +205,13 @@ class DeepITMBullSpreadStrategy:
|
|
|
query_date = trade_date.date() if hasattr(trade_date, 'date') else trade_date
|
|
query_date = trade_date.date() if hasattr(trade_date, 'date') else trade_date
|
|
|
candidates = []
|
|
candidates = []
|
|
|
|
|
|
|
|
- if not silent:
|
|
|
|
|
- print(f" 查询到 {len(contract_info)} 个认购期权合约,开始获取价格信息:")
|
|
|
|
|
|
|
+ # 统计失败原因
|
|
|
|
|
+ expiry_rejected_count = 0
|
|
|
|
|
+ price_missing_count = 0
|
|
|
|
|
+ price_error_count = 0
|
|
|
|
|
+
|
|
|
|
|
+ # if not silent:
|
|
|
|
|
+ # print(f" 查询到 {len(contract_info)} 个认购期权合约,开始获取价格信息:")
|
|
|
|
|
|
|
|
for idx, contract in contract_info.iterrows():
|
|
for idx, contract in contract_info.iterrows():
|
|
|
# 检查到期日
|
|
# 检查到期日
|
|
@@ -214,6 +220,7 @@ class DeepITMBullSpreadStrategy:
|
|
|
days_to_expiry = len(get_trade_days(trade_date_obj, expiry_date.date())) - 1
|
|
days_to_expiry = len(get_trade_days(trade_date_obj, expiry_date.date())) - 1
|
|
|
|
|
|
|
|
if days_to_expiry < min_days_to_expiry:
|
|
if days_to_expiry < min_days_to_expiry:
|
|
|
|
|
+ expiry_rejected_count += 1
|
|
|
# if not silent:
|
|
# if not silent:
|
|
|
# print(f" 行权价 {contract['exercise_price']:.3f}: 到期时间不足 ({days_to_expiry} < {min_days_to_expiry})")
|
|
# print(f" 行权价 {contract['exercise_price']:.3f}: 到期时间不足 ({days_to_expiry} < {min_days_to_expiry})")
|
|
|
continue
|
|
continue
|
|
@@ -226,6 +233,7 @@ class DeepITMBullSpreadStrategy:
|
|
|
)
|
|
)
|
|
|
price_result = opt.run_query(q_price)
|
|
price_result = opt.run_query(q_price)
|
|
|
if price_result.empty:
|
|
if price_result.empty:
|
|
|
|
|
+ price_missing_count += 1
|
|
|
if not silent:
|
|
if not silent:
|
|
|
print(f" 行权价 {contract['exercise_price']:.3f}: 无价格数据")
|
|
print(f" 行权价 {contract['exercise_price']:.3f}: 无价格数据")
|
|
|
continue
|
|
continue
|
|
@@ -246,14 +254,30 @@ class DeepITMBullSpreadStrategy:
|
|
|
# print(f" 行权价 {contract['exercise_price']:.3f}: 期权价格 {option_price:.4f}, 剩余天数 {days_to_expiry}")
|
|
# print(f" 行权价 {contract['exercise_price']:.3f}: 期权价格 {option_price:.4f}, 剩余天数 {days_to_expiry}")
|
|
|
|
|
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
|
|
+ price_error_count += 1
|
|
|
if not silent:
|
|
if not silent:
|
|
|
print(f" 行权价 {contract['exercise_price']:.3f}: 价格查询失败 ({str(e)})")
|
|
print(f" 行权价 {contract['exercise_price']:.3f}: 价格查询失败 ({str(e)})")
|
|
|
continue
|
|
continue
|
|
|
|
|
|
|
|
if not candidates:
|
|
if not candidates:
|
|
|
- if not silent:
|
|
|
|
|
- print(f" 月份索引 {month_idx} 无符合基本条件的期权候选")
|
|
|
|
|
- return None, None
|
|
|
|
|
|
|
+ # 构建详细的失败原因
|
|
|
|
|
+ total_contracts = len(contract_info)
|
|
|
|
|
+ failure_parts = []
|
|
|
|
|
+ if expiry_rejected_count > 0:
|
|
|
|
|
+ failure_parts.append(f"到期时间不足{expiry_rejected_count}个")
|
|
|
|
|
+ if price_missing_count > 0:
|
|
|
|
|
+ failure_parts.append(f"无价格数据{price_missing_count}个")
|
|
|
|
|
+ if price_error_count > 0:
|
|
|
|
|
+ failure_parts.append(f"价格查询失败{price_error_count}个")
|
|
|
|
|
+
|
|
|
|
|
+ if failure_parts:
|
|
|
|
|
+ failure_reason = f"共{total_contracts}个期权合约,但均不符合条件:" + "、".join(failure_parts)
|
|
|
|
|
+ else:
|
|
|
|
|
+ failure_reason = f"共{total_contracts}个期权合约,但均不符合基本条件"
|
|
|
|
|
+
|
|
|
|
|
+ # if not silent:
|
|
|
|
|
+ # print(f" 月份索引 {month_idx} 无符合基本条件的期权候选")
|
|
|
|
|
+ return None, None, failure_reason
|
|
|
|
|
|
|
|
month_info = {
|
|
month_info = {
|
|
|
'month_idx': month_idx,
|
|
'month_idx': month_idx,
|
|
@@ -266,7 +290,7 @@ class DeepITMBullSpreadStrategy:
|
|
|
print(f" 成功获取 {len(candidates)} 个有效期权候选")
|
|
print(f" 成功获取 {len(candidates)} 个有效期权候选")
|
|
|
# print(f" 月份索引 {month_idx} 的认购期权候选信息: {candidates}")
|
|
# print(f" 月份索引 {month_idx} 的认购期权候选信息: {candidates}")
|
|
|
|
|
|
|
|
- return candidates, month_info
|
|
|
|
|
|
|
+ return candidates, month_info, None
|
|
|
|
|
|
|
|
def select_sell_call_from_candidates(self, candidates, etf_price, min_premium, silent=False):
|
|
def select_sell_call_from_candidates(self, candidates, etf_price, min_premium, silent=False):
|
|
|
"""从候选期权中选择卖购期权(虚值期权)
|
|
"""从候选期权中选择卖购期权(虚值期权)
|
|
@@ -398,9 +422,9 @@ class DeepITMBullSpreadStrategy:
|
|
|
print(f" 权利金阈值: {min_premium:.4f},时间价值阈值: {time_value_threshold:.4f}")
|
|
print(f" 权利金阈值: {min_premium:.4f},时间价值阈值: {time_value_threshold:.4f}")
|
|
|
|
|
|
|
|
# 1. 获取月份期权候选
|
|
# 1. 获取月份期权候选
|
|
|
- candidates, month_info = self.get_monthly_option_candidates(trade_date, month_idx, silent)
|
|
|
|
|
|
|
+ candidates, month_info, failure_reason = self.get_monthly_option_candidates(trade_date, month_idx, silent)
|
|
|
if not candidates:
|
|
if not candidates:
|
|
|
- reason = f"{month_type}无可用期权候选"
|
|
|
|
|
|
|
+ reason = f"{month_type}期权候选获取失败:{failure_reason}"
|
|
|
if not silent:
|
|
if not silent:
|
|
|
print(f" {month_type}牛差策略失败:{reason}")
|
|
print(f" {month_type}牛差策略失败:{reason}")
|
|
|
return None, None, reason
|
|
return None, None, reason
|
|
@@ -521,9 +545,6 @@ class DeepITMBullSpreadStrategy:
|
|
|
def try_bull_spread_strategy(self, trade_date, etf_price, month_idx, position_type='main', silent=False, save_to_csv=True):
|
|
def try_bull_spread_strategy(self, trade_date, etf_price, month_idx, position_type='main', silent=False, save_to_csv=True):
|
|
|
"""尝试牛差策略:深度实值买购+卖购期权"""
|
|
"""尝试牛差策略:深度实值买购+卖购期权"""
|
|
|
|
|
|
|
|
- if not silent:
|
|
|
|
|
- print(f"{trade_date.strftime('%Y-%m-%d')} 开始尝试牛差策略")
|
|
|
|
|
-
|
|
|
|
|
# 1. 先尝试当月牛差策略
|
|
# 1. 先尝试当月牛差策略
|
|
|
buy_call, sell_call, reason = self.try_bull_spread_for_month(trade_date, etf_price, month_idx, is_current_month=True, silent=silent)
|
|
buy_call, sell_call, reason = self.try_bull_spread_for_month(trade_date, etf_price, month_idx, is_current_month=True, silent=silent)
|
|
|
|
|
|
|
@@ -588,6 +609,10 @@ class DeepITMBullSpreadStrategy:
|
|
|
if trade_date >= month_date:
|
|
if trade_date >= month_date:
|
|
|
month_idx = i
|
|
month_idx = i
|
|
|
|
|
|
|
|
|
|
+ # 先输出开始尝试的日志,确保顺序正确
|
|
|
|
|
+ if not silent:
|
|
|
|
|
+ print(f"{trade_date.strftime('%Y-%m-%d')} 开始尝试牛差策略")
|
|
|
|
|
+
|
|
|
# 尝试牛差策略(深度实值买购+卖购)
|
|
# 尝试牛差策略(深度实值买购+卖购)
|
|
|
result, reason = self.try_bull_spread_strategy(trade_date, etf_price, month_idx, position_type, silent, save_to_csv)
|
|
result, reason = self.try_bull_spread_strategy(trade_date, etf_price, month_idx, position_type, silent, save_to_csv)
|
|
|
if result is not None:
|
|
if result is not None:
|
|
@@ -1239,9 +1264,9 @@ class StrategyConfig:
|
|
|
def __init__(self):
|
|
def __init__(self):
|
|
|
# ETF标的配置字典
|
|
# ETF标的配置字典
|
|
|
self.etf_symbols = {
|
|
self.etf_symbols = {
|
|
|
- '50ETF': '510050.XSHG' # 上证50ETF
|
|
|
|
|
- # '300ETF': '510300.XSHG', # 沪深300ETF
|
|
|
|
|
- # '创业板ETF': '159915.XSHE' # 创业板ETF
|
|
|
|
|
|
|
+ '50ETF': '510050.XSHG', # 上证50ETF
|
|
|
|
|
+ '300ETF': '510300.XSHG', # 沪深300ETF
|
|
|
|
|
+ '创业板ETF': '159915.XSHE' # 创业板ETF
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
# 时间范围配置
|
|
# 时间范围配置
|
|
@@ -1892,8 +1917,8 @@ class MultiUnderlyingBullSpreadManager:
|
|
|
|
|
|
|
|
print(f" {etf_code}: 开仓主仓位({strategy_desc}),ETF价格: {etf_price:.4f}, 合约数量: {contract_size}张, {profit_desc}")
|
|
print(f" {etf_code}: 开仓主仓位({strategy_desc}),ETF价格: {etf_price:.4f}, 合约数量: {contract_size}张, {profit_desc}")
|
|
|
daily_has_activity = True
|
|
daily_has_activity = True
|
|
|
- else:
|
|
|
|
|
- print(f" {etf_code}: 开仓失败,无法找到合适的期权组合,ETF价格: {etf_price:.4f}")
|
|
|
|
|
|
|
+ # else:
|
|
|
|
|
+ # print(f" {etf_code}: 开仓失败,无法找到合适的期权组合,ETF价格: {etf_price:.4f}")
|
|
|
|
|
|
|
|
# 检查是否需要加仓
|
|
# 检查是否需要加仓
|
|
|
elif strategy.should_add_position(trade_date, etf_price):
|
|
elif strategy.should_add_position(trade_date, etf_price):
|
|
@@ -1994,7 +2019,7 @@ def run_deep_itm_bull_spread_example():
|
|
|
# 可以自定义配置
|
|
# 可以自定义配置
|
|
|
config.time_config = {
|
|
config.time_config = {
|
|
|
'start_date': '2025-06-15',
|
|
'start_date': '2025-06-15',
|
|
|
- 'end_date': '2025-08-15'
|
|
|
|
|
|
|
+ 'end_date': '2025-08-25'
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
config.capital_config.update({
|
|
config.capital_config.update({
|