Files
sanguo_quant_live/strategies/factors-dynamic-weight-timing-20260327/utils/dynamic_weight.py
T
2026-04-02 08:55:07 +08:00

121 lines
3.8 KiB
Python

"""
动态加权模块
根据因子近期IC(信息系数)动态调整权重
IC越高,因子效果越好,权重越大
"""
import pandas as pd
import numpy as np
from typing import Dict, List, Optional
from scipy.stats import spearmanr
class DynamicWeightAdjuster:
"""
动态权重调整器
根据每期因子IC计算新权重
"""
def __init__(
self,
factor_names: List[str],
window_size: int = 12, # 观测窗口,月频就是12个月
min_ic: float = -0.05,
base_weight: float = 0.05 # 基础权重,保证每个因子都有一定暴露
):
"""
参数:
factor_names: 因子名称列表
window_size: 计算IC的滚动窗口大小(期数,月频调仓就是多少个月)
min_ic: 最小IC,如果IC小于这个值,会被降低权重
base_weight: 每个因子的基础权重,避免权重为0
"""
self.factor_names = factor_names
self.window_size = window_size
self.min_ic = min_ic
self.base_weight = base_weight
# 保存历史IC记录
self.ic_history: Dict[str, List[float]] = {name: [] for name in factor_names}
def update_ic(
self,
factor_scores: pd.Series,
forward_returns: pd.Series
) -> Dict[str, float]:
"""
更新一期IC数据
参数:
factor_scores: 当期因子得分(横截面)
forward_returns: 下期收益率(要预测的目标)
返回:
当期IC字典
"""
# 合并去掉NaN
df = pd.concat([factor_scores, forward_returns], axis=1).dropna()
current_ic = {}
for name in self.factor_names:
if name in df.columns:
# 计算Spearman秩相关系数作为IC
ic, _ = spearmanr(df[name], df[forward_returns.name])
current_ic[name] = ic
self.ic_history[name].append(ic)
# 保持窗口大小
if len(self.ic_history[name]) > self.window_size:
self.ic_history[name].pop(0)
return current_ic
def calculate_weights(self) -> Dict[str, float]:
"""
根据历史IC计算新权重
IC越高,权重越大
"""
# 计算每个因子平均IC
avg_ic = {}
for name in self.factor_names:
ics = self.ic_history[name]
if len(ics) > 0:
avg_ic[name] = np.mean(ics)
else:
avg_ic[name] = 0
# IC转权重:IC越大权重越大,IC为负降低权重
weights = {}
for name in self.factor_names:
ic = avg_ic[name]
# 如果IC小于最小值,只保留基础权重
if ic < self.min_ic:
weights[name] = self.base_weight
else:
# IC越大权重越大,加上基础权重保证至少有base
weights[name] = self.base_weight + max(0, ic)
# 归一化到总和为1
total = sum(weights.values())
if total > 0:
weights = {k: v / total for k, v in weights.items()}
else:
# 如果都不好,等权重
n = len(self.factor_names)
weights = {k: 1.0 / n for k in self.factor_names}
return weights
def get_ic_history(self) -> Dict[str, List[float]]:
"""获取历史IC记录"""
return self.ic_history.copy()
def get_avg_ic(self) -> Dict[str, float]:
"""获取滚动平均IC"""
avg_ic = {}
for name, ics in self.ic_history.items():
if len(ics) > 0:
avg_ic[name] = np.mean(ics)
else:
avg_ic[name] = 0
return avg_ic