revert(execution): 恢复动态权重仓位分配逻辑

- 恢复原逻辑: 按实际持仓数量等权分配
- 选出2只时每只权重50%,选出1只时权重100%
- 收益计算恢复为 np.mean(returns)
- 交易成本恢复为 swapped/len(old)
- 固定仓位逻辑记录在 docs/experiments/仓位分配逻辑修改分析.md
This commit is contained in:
2026-05-16 00:34:12 +08:00
parent e0d6f81ea1
commit 2c1689089d

View File

@@ -228,29 +228,18 @@ class BacktestExecutor(Executor):
result['策略日收益率'] = result.apply(calc_return, axis=1)
else:
# 多标的策略(固定仓位分配
# 核心逻辑按select_num固定分配仓位缺失标的用现金替代
# 例如select_num=3选出2只标的 → 权重=1/3+1/3现金权重=1/3收益为0
# 多标的策略(等权组合
# 按实际持仓数量等权分配选出2只时每只50%选出1只时100%
def calc_multi_return(row):
codes = [c for c in row[signal_col].split(',') if c]
if not codes:
# 空仓全部现金收益为0
return 0.0
# 固定仓位权重:每只标的权重 = 1 / select_num
unit_weight = 1.0 / self.select_num
# 计算实际持仓收益缺失标的用现金替代收益为0
total_return = 0.0
returns = []
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):
total_return += ret * unit_weight
# 如果数据缺失视为现金收益为0不累加
# 缺失标的的仓位自动变成现金收益为0
# 总收益 = sum(实际持仓收益) + 0 * (缺失仓位)
return total_return
returns.append(ret)
return np.mean(returns) if returns else 0.0
result['策略日收益率'] = result.apply(calc_multi_return, axis=1)
@@ -268,9 +257,7 @@ 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:
@@ -278,13 +265,8 @@ class BacktestExecutor(Executor):
else:
old = set(prev.split(','))
new = set(curr.split(','))
# 调出的标的数量(这些仓位需要卖出)
exit_count = len(old - new)
# 调入的标的数量(这些仓位需要买入)
enter_count = len(new - old)
# 换手率 = (卖出 + 买入) / select_num
# 每次调仓涉及的仓位比例
turnover = (exit_count + enter_count) / self.select_num
swapped = len(old - new)
turnover = swapped / len(old) if old else 0.0
turnover_list.append(turnover)
result['换手率'] = turnover_list