From 444dc0e75134f9ff00b99221c2f9c98a730f2a1f Mon Sep 17 00:00:00 2001 From: aszerW Date: Sat, 16 May 2026 00:18:19 +0800 Subject: [PATCH] =?UTF-8?q?refactor(execution):=20=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E5=9B=BA=E5=AE=9A=E4=BB=93=E4=BD=8D=E5=88=86=E9=85=8D=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 原逻辑: 按实际持仓数量等权(选出2只时权重50%) - 新逻辑: 按select_num固定等权(选出2只时权重33.3%+现金33.3%) - 缺失仓位用现金替代,收益为0 - 交易成本按固定仓位比例计算 - 目的: 保持稳定风险敞口,避免仓位不足时波动放大 --- framework/execution/__init__.py | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/framework/execution/__init__.py b/framework/execution/__init__.py index 700248e..47958ba 100644 --- a/framework/execution/__init__.py +++ b/framework/execution/__init__.py @@ -228,17 +228,29 @@ class BacktestExecutor(Executor): result['策略日收益率'] = result.apply(calc_return, axis=1) else: - # 多标的策略(等权组合) + # 多标的策略(固定仓位分配) + # 核心逻辑:按select_num固定分配仓位,缺失标的用现金替代 + # 例如:select_num=3,选出2只标的 → 权重=1/3+1/3,现金权重=1/3(收益为0) def calc_multi_return(row): codes = [c for c in row[signal_col].split(',') if c] if not codes: + # 空仓:全部现金,收益为0 return 0.0 - returns = [] + + # 固定仓位权重:每只标的权重 = 1 / select_num + unit_weight = 1.0 / self.select_num + + # 计算实际持仓收益(缺失标的用现金替代,收益为0) + total_return = 0.0 for c in codes: ret = data.loc[row.name, f'日收益率_{c}'] if f'日收益率_{c}' in data.columns else None if ret is not None and pd.notna(ret): - returns.append(ret) - return np.mean(returns) if returns else 0.0 + total_return += ret * unit_weight + # 如果数据缺失,视为现金(收益为0,不累加) + + # 缺失标的的仓位自动变成现金(收益为0) + # 总收益 = sum(实际持仓收益) + 0 * (缺失仓位) + return total_return result['策略日收益率'] = result.apply(calc_multi_return, axis=1) @@ -256,7 +268,9 @@ class BacktestExecutor(Executor): changed = (signals[signal_col] != prev_signal) & prev_signal.notna() result.loc[changed, '策略日收益率'] -= self.trade_cost else: - # 多标的策略:按换手率比例扣除成本 + # 多标的策略:按固定仓位比例扣除成本 + # 核心逻辑:每只标的权重固定为1/select_num + # 换手率 = (调出数量 + 调入数量) / select_num turnover_list = [] for curr, prev in zip(signals[signal_col], prev_signal): if pd.isna(prev) or curr == prev: @@ -264,8 +278,13 @@ class BacktestExecutor(Executor): else: old = set(prev.split(',')) new = set(curr.split(',')) - swapped = len(old - new) - turnover = swapped / len(old) if old else 0.0 + # 调出的标的数量(这些仓位需要卖出) + exit_count = len(old - new) + # 调入的标的数量(这些仓位需要买入) + enter_count = len(new - old) + # 换手率 = (卖出 + 买入) / select_num + # 每次调仓涉及的仓位比例 + turnover = (exit_count + enter_count) / self.select_num turnover_list.append(turnover) result['换手率'] = turnover_list