refactor(execution): 改为固定仓位分配逻辑
- 原逻辑: 按实际持仓数量等权(选出2只时权重50%) - 新逻辑: 按select_num固定等权(选出2只时权重33.3%+现金33.3%) - 缺失仓位用现金替代,收益为0 - 交易成本按固定仓位比例计算 - 目的: 保持稳定风险敞口,避免仓位不足时波动放大
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user