121 lines
3.8 KiB
Python
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
|