1. 前言

前面我们介绍了使用机器学习的方法进行因子合成,但是这种方法的适用性仍需斟酌使用。例如机器可能会给某个因子过高的权重,为组合带来风险暴露。本文从因子权重优化出发,基于Python Cvxpy库提供了因子权重优化的一个工具。

2. 常见因子合成方法静态权重:固定的权重加权,例如常见的等权。这种方法非常直观,领导拍脑袋。

动态权重:IC加权,IC_IR加权,最大化IC IR加权。

动态权重的方法在很多卖方的研报都有介绍,像天风,华泰等等,甚至还讨论到IC的计算方式,例如根号市值加权的IC。

假设我们现在有n个因子,其IC的均值向量为

, 因子IC协方差矩阵

(但其实不一定是IC的协方差矩阵),最优权重为

, 我们现在考虑使用最大化IC IR方法进行加权,这是一个优化问题:

这个问题有解析解:

其中

为任意正数,可以用来调整最后的权重之和为1。

3 动态权重的扩展

3.1 最大化IC IR的缺点上一节,我们讨论了最大化IC IR加权方法,这种方法是考虑了因子之间的相关性后最大化合成因子的预测能力的方法。这涉及了因子之间的相关性,一般来说使用IC协方差矩阵

来估计。

使用IC协方差矩阵

来估计存在两个问题,需要一定长度的因子IC序列,假如有n个因子,那么因子的IC序列至少要n+1期。即便如此,计算得到的还不一定是因子IC协方差矩阵的无偏估计。当然这不是本文讨论的重点。

第二节的优化问题,我们实际上使用了因子权重之和为1的约束。本文提供了一个Python编写的工具,使得我们可以灵活添加各种约束来优化因子的权重,更好的辅助领导拍脑袋。相关的Code已经上传至我的github主页,可以私信我领取。

3.2. FWOptimizer介绍模型设定: 考虑到最大化夏普比率这个问题并不满足Cvxpy内置规定的DCP规则,我们转而使用均值方差模型:

简单列了三种约束,包括因子权重之和约束,因子权重上下限约束。例如对于第一个约束,当k=n,且

时,即为因子权重之和为1的约束,通过该约束,我们可以约束某一类因子的权重之和

这个模型存在的问题在于需要给一个先验的

,可以参考的一种方法通过将最大化IC IR方法得到的最优权重代入

使用

作为

的一个近似估计

3.3 举例

3.3.1 与最大化IC IR的比较

import sys

sys.path.append(r'E:\Heat')

import pandas as pd

import numpy as np

from pisces.optimize import FWOptimizer

cov = np.array([[1.60008194e-01, 9.37627712e-02, 1.53377460e-01, -5.49856252e-02],

[9.37627712e-02, 2.00866344e-01, 1.30531255e-02, 6.00987877e-04],

[1.53377460e-01, 1.30531255e-02, 2.08032599e+00, -1.03737730e-01],

[-5.49856252e-02, 6.00987877e-04, -1.03737730e-01, 7.81325575e-01]])

cov = pd.DataFrame(cov, index=['f1', 'f2', 'f3', 'f4'], columns=['f1', 'f2', 'f3', 'f4'])

ic = {'f1': 0.3, 'f2': 0.5, 'f3': 0.7, 'f4': 0.8}

fw_optimizer = FWOptimizer(ic=ic, cov=cov, lam=1)

my_w = fw_optimizer.optimize()

np.round(my_w, 4)

array([-0. , 0.5379, 0.1148, 0.3473])

print('lambda为1时IC IR:')

print(my_w.dot(fw_optimizer.ic.values) / np.sqrt((my_w.dot(fw_optimizer.cov).dot(my_w.T))))

lambda为1时IC IR:

1.506352197341874

max_ic_ir_w = np.linalg.inv(fw_optimizer.cov.values).dot(fw_optimizer.ic.values)

max_ic_ir_w /= max_ic_ir_w.sum()

sigma = np.sqrt((max_ic_ir_w.dot(fw_optimizer.cov).dot(max_ic_ir_w.T)))

max_ic_ir_w

array([0.16082099, 0.50169833, 0.07674268, 0.26073799])

print('最大化IC IR方法解析解得到的IC IR:')

print(max_ic_ir_w.dot(fw_optimizer.ic.values) / np.sqrt((max_ic_ir_w.dot(fw_optimizer.cov).dot(max_ic_ir_w.T))))

最大化IC IR方法解析解得到的IC IR:

1.5488475061977878

lam = max_ic_ir_w.dot(fw_optimizer.ic.values) / sigma / 2 / sigma

lam

2.136537309816752

fw_optimizer = FWOptimizer(ic=ic, cov=cov, lam=2.136)

my_w = fw_optimizer.optimize()

np.round(my_w, 4)

array([0.1607, 0.5018, 0.0768, 0.2608])

print('lambda为2.14时IC IR:')

print(my_w.dot(fw_optimizer.ic.values) / np.sqrt((my_w.dot(fw_optimizer.cov).dot(my_w.T))))

lambda为2.14时IC IR: 1.5488474982752303我们目前只给因子权重添加了权重之和为1以及权重在0与1之间的约束(这是默认的)

当我随便给了一个

的值,如1时,这时候得到的解并不能使IC IR达到最大,然后我通过了求IC IR方法的解析解得到一个经验

为2.14,再代入模型当中,这时候就与解析解得到的基本一致,基本上都达到了1.548

3.3.2 为优化添加约束

fw_optimizer = FWOptimizer(ic=ic, cov=cov, lam=2.136)

fw_optimizer.add_constant('s', cons_type='factor_weight_sum_limit', priority=1, sum_w=0.5, factor_list=['f1', 'f2', 'f3'])

my_w = fw_optimizer.optimize()

np.round(my_w, 4)

array([0.0914, 0.3331, 0.0755, 0.5 ])

print('lambda=2.136:, IR为', my_w.dot(fw_optimizer.ic.values) / np.sqrt((my_w.dot(fw_optimizer.cov).dot(my_w.T))))

lambda=2.136:, IR为 1.358677013278411

fw_optimizer = FWOptimizer(ic=ic, cov=cov, lam=1)

fw_optimizer.add_constant('s', cons_type='factor_weight_sum_limit', priority=1, sum_w=0.5, factor_list=['f1', 'f2', 'f3'])

my_w = fw_optimizer.optimize()

print(my_w.dot(fw_optimizer.ic.values) / np.sqrt((my_w.dot(fw_optimizer.cov).dot(my_w.T))))

1.3690165975340745这里添加做了个实验,当我们不添加约束的情况下,我们将

设置为先验值,得到的IC IR几乎也是最大的

如果我们添加了约束,这个时候先验的

不能保证这时候得到的IC IR是最大的,但可以通过暴力的方式遍历一下,这个先验值应该会与最优的结果类似

3.3.3 实例应用

简单起见,从估值,成长,质量,动量,波动,流动性这几个因子当中,各挑一个,做去极值与中性化处理。分别比较两种因子合成方法。最大化IC IR

最大化IC二次效用,且每个因子权重不得低于10%

import matplotlib.pyplot as plt

import seaborn as sns

from scorpio.utils.data_function import DataAPI

from scorpio.utils.factor_function import FactorUtil

sns.set_style('whitegrid')

def winsorize(df, name):

all_hist = dict()

for date in df['trade_date'].unique():

date_df = (df[df['trade_date'] == date]).set_index('ticker')[name]

tmp = FactorUtil.winsorize(date_df, win_type='percentile', p_value=(0.01, 0.01))

tmp = FactorUtil.winsorize(tmp, win_type='mad', limits=(-3, 3))

tmp = FactorUtil.standardize(tmp, method=0)

tmp = FactorUtil.neutralize(tmp, date, ind='sw', use_style=('size',))

tmp = FactorUtil.standardize(tmp, method=1, date=date, estu='90cap')

all_hist[date] = tmp

all_hist = pd.DataFrame(all_hist).T

return all_hist

def cal_ic(df, ret):

ic = dict()

for date in df.index:

date_signal = df.loc[date, :]

date_f_ret = ret.loc[date, :]

ic[date] = pd.concat((date_signal, date_f_ret), axis=1).corr(method='spearman').iloc[-1, 0]

return pd.Series(ic)

month_end_date = DataAPI.get_date_list('20100101', '20200228', 'm')

close = DataAPI.get_stock_quote('20100101', '20200228', ['closePrice'])

forward_1m_ret = close.pivot(index='trade_date', columns='ticker', values='closePrice').reindex(month_end_date).pct_change().shift(-1)

ep = DataAPI.get_factor('ep_ttm', start='20100101', end='20200131', freq='m')

ep = ep[ep['ep_ttm'] > 0]

net_profit_sq_zscore = DataAPI.get_factor('net_profit_sq_zscore', start='20100101', end='20200131', freq='m')

roe = DataAPI.get_factor('roe_ttm_avg', start='20100101', end='20200131', freq='m')

momentum = DataAPI.get_factor('ret_20d', start='20100101', end='20200131', freq='m')

momentum['ret_20d'] = -1 * momentum['ret_20d']

ivol = DataAPI.get_factor('ivol_capm_20d', start='20100101', end='20200131', freq='m')

ivol['ivol_capm_20d'] = -1 * ivol['ivol_capm_20d']

illiq = DataAPI.get_factor('illiq_1m', start='20100101', end='20200131', freq='m')

ep_ = winsorize(ep, 'ep_ttm')

net_profit_sq_zscore_ = winsorize(net_profit_sq_zscore, 'net_profit_sq_zscore')

roe_ = winsorize(roe, 'roe_ttm_avg')

momentum_ = winsorize(momentum, 'ret_20d')

ivol_ = winsorize(ivol, 'ivol_capm_20d')

illiq_ = winsorize(illiq, 'illiq_1m')

ep_ic = cal_ic(ep_, forward_1m_ret)

net_profit_sq_zscore_ic = cal_ic(net_profit_sq_zscore_, forward_1m_ret)

roe_ic = cal_ic(roe_, forward_1m_ret)

momentum_ic = cal_ic(momentum_, forward_1m_ret)

ivol_ic = cal_ic(ivol_, forward_1m_ret)

illiq_ic = cal_ic(illiq_, forward_1m_ret)

ic_all = pd.concat((ep_ic, net_profit_sq_zscore_ic, roe_ic, momentum_ic, ivol_ic, illiq_ic), axis=1).shift(1)

ic_all.columns = ['ep_ttm', 'net_profit_sq_zscore', 'roe_ttm_avg', 'ret_20d', 'ivol_capm_20d', 'illiq_1m']

ic_signal = ic_all.rolling(12).mean().dropna(axis=0, how='all')

w1 = dict()

w2 = dict()

for i, date in enumerate(ic_signal.index):

pre_date = ic_all.index[i]

date_cov = ic_all.loc[pre_date:date, :].cov()

date_ic_signal = ic_signal.loc[date]

date_ic_signal.loc[date_ic_signal < 0] = 0

date_w1 = pd.Series(np.linalg.inv(date_cov).dot(ic_signal.loc[date]), index=date_ic_signal.index)

date_lam = date_w1.dot(date_ic_signal.values) / np.sqrt((date_w1.dot(date_cov).dot(date_w1.T)))

date_w1.loc[date_w1 < 0] = 0

w1[date] = date_w1 / date_w1.sum()

fw_optimizer = FWOptimizer(ic=date_ic_signal, cov=cov, lam=date_lam)

fw_optimizer.add_constant('l', cons_type='weight_limit', priority=1, min_w=0.1, max_w=0.3)

date_w2 = fw_optimizer.optimize()

w2[date] = pd.Series(date_w2, index=date_ic_signal.index)

w1 = pd.DataFrame(w1).T

w2 = pd.DataFrame(w2).T

w1.index = pd.to_datetime(w1.index)

w2.index = pd.to_datetime(w2.index)

ax1 = plt.figure(figsize=(12, 6)).add_subplot(111)

ax1 = w1.plot.area(stacked=True, alpha=0.5, ax=ax1)

t = ax1.set_title('Max IC IR')

ax2 = plt.figure(figsize=(12, 6)).add_subplot(111)

w2.plot.area(stacked=True, alpha=0.5, ax=ax2)

t = ax2.set_title('Optimize IC IR with constraint')

ep_.index = pd.to_datetime(ep_.index)

net_profit_sq_zscore_.index = pd.to_datetime(net_profit_sq_zscore_.index)

roe_.index = pd.to_datetime(roe_.index)

momentum_.index = pd.to_datetime(momentum_.index)

ivol_.index = pd.to_datetime(ivol_.index)

illiq_.index = pd.to_datetime(illiq_.index)

alpha1 = ep_.multiply(w1['ep_ttm'], axis=0).add(net_profit_sq_zscore_.multiply(w1['net_profit_sq_zscore'], axis=0))\

.add(roe_.multiply(w1['roe_ttm_avg'], axis=0))\

.add(momentum_.multiply(w1['ret_20d'], axis=0))\

.add(ivol_.multiply(w1['ivol_capm_20d'], axis=0))\

.add(illiq_.multiply(w1['illiq_1m'], axis=0))

alpha1 = (alpha1.dropna(axis=0, how='all')).unstack().reset_index()

alpha1.columns = ['ticker', 'trade_date', 'alpha1']

alpha1['trade_date'] = alpha1['trade_date'].dt.strftime('%Y%m%d')

alpha2 = ep_.multiply(w2['ep_ttm'], axis=0).add(net_profit_sq_zscore_.multiply(w2['net_profit_sq_zscore'], axis=0))\

.add(roe_.multiply(w2['roe_ttm_avg'], axis=0))\

.add(momentum_.multiply(w2['ret_20d'], axis=0))\

.add(ivol_.multiply(w2['ivol_capm_20d'], axis=0))\

.add(illiq_.multiply(w2['illiq_1m'], axis=0))

alpha2 = (alpha2.dropna(axis=0, how='all')).unstack().reset_index()

alpha2.columns = ['ticker', 'trade_date', 'alpha2']

alpha2['trade_date'] = alpha2['trade_date'].dt.strftime('%Y%m%d')

alpha1.to_csv(r'./alpha1.csv')

alpha2.to_csv(r'./alpha2.csv')这里发现,最大化IC IR方法可能会得到负的因子权重,这里选择当期不配该因子

我们绘制了两者不同方法得到的因子权重的历史走势,可以发现,最大化IC IR方法得到的因子权重并不是那么稳定,而优化方法提供的工具更多,相对来说可操作性更强

使用单因子分析工具,大概可以得到如下一些有趣的结果:从Spearman IC的角度来看,似乎优化的方法更好一些,t统计量更大。同时从因子历史值的分布的历史走势来看,优化方法更加稳定,这得益于我们约束的设定。

4. 总结做了一个辅助领导拍脑袋的工具,简单测试下来貌似比IC IR要更好一些。当然本文细扣起来讲涉及到很多东西,后面有机会再一一展开

熬了两天夜,放弃了脱单的时间,但也挺充实的,保持初心,贵在坚持!

python 因子分析 权重计算_Python与量化多因子——因子权重优化相关推荐

  1. python利用近似公式计算π_python如何利用公式计算π

    python利用公式计算π的方法:首先导入数学模块及时间模块:然后计算Pi精确到小数点后几位数,代码为[print('n{:=^70}'.format('计算开始'))]:最后完成计算,代码为[pri ...

  2. python均线选股_GitHub - pkujf/Wind_Python: 量化开发 多因子选股模型

    多因子选股模型 multifactorial_model文件夹中是目前最完整的. 版本1和2是初级的实现.对数据库进行了多次操作,更多的是考虑功能,性能问题暂时作为次要的. 版本3是改进版本,对原始数 ...

  3. python选股票进阶_Python进阶量化交易专栏场外篇27-股票数据的除权和复权

    欢迎大家订阅<教你用 Python 进阶量化交易>专栏!为了能够提供给大家更轻松的学习过程,笔者在专栏内容之外已陆续推出一些手记来辅助同学们学习本专栏内容,目前推出的扩展篇链接如下: 在行 ...

  4. python股票编程入门_Python股票量化投资-3.python基础

    Python股票量化投资-1.开发环境部署 Python股票量化投资-2.量化投资介绍 继续开始今天的内容,主要介绍 PyCharm的开发使用[这IDE对JAVA人员来说不陌生] Python的语法推 ...

  5. python历史性分布计算代码_量化交易中,如何使用Python计算「筹码分布」指标【附代码】 [量化小讲堂-64]...

    引言: 邢不行的系列帖子"量化小讲堂",通过实际案例教初学者使用python进行量化投资,了解行业研究方向,希望能对大家有帮助. [历史文章汇总] 请点击此处 这是邢不行第  64 ...

  6. python应用于期货_Python期货量化交易基础教程(17)

    16.14.异步任务: 16.14.1.使用协程任务: 函数create_task()用来创建协程任务,并将任务加入事件循环以实现异步并发. wait_update()不能用在协程中,若在协程中等待业 ...

  7. python中怎么计算_python中的加减乘除运算

    python中的加减乘除运算,是学习python入门的基础,是以后进行数学计算的关键部分.包括运算表达式的写法,运算规则,加减乘除,求余,求商等等. 工具/原料 python 电脑 方法/步骤 1 1 ...

  8. 自学python编程基础科学计算_Python基础与科学计算常用方法

    Python基础与科学计算常用方法 本文使用的是Jupyter Notebook,Python3.你可以将代码直接复制到Jupyter Notebook中运行,以便更好的学习. 导入所需要的头文件 i ...

  9. python tus股票数据分析_python 股票量化分析(一)---基于Tushare平台的个股历史行情获取...

    在浩瀚如烟的股票中,如何将好股选择出来?长线.中线.短线个股如何筛选?国内外政治.经济.地缘博弈基本面对股市的影响有多大,怎样将其纳入股票选择买卖的决策因子?公司财报.未来规划.公司所处行业发展前景怎 ...

  10. python编程工时计算_Python编程题:两个日期间的天数统计(附代码)

    编程题一:计算两个日期之间的天数 题目如下: 题目一 解题思路: 将两个日期格式化成time模块中的标准时间结构struct_time,缺少值用0补齐利用time模块函数分别计算两个日期到时间戳(指格 ...

最新文章

  1. GitHub基本使用
  2. linux固定分辨率,如何锁定分辨率
  3. 【BZOJ3156】防御准备,斜率优化DP
  4. 【java】浅析JDK中ServiceLoader的源码
  5. ❤️《大前端—模块化》
  6. python编程入门与案例详解-Pythony运维入门之Socket网络编程详解
  7. Linux_shell 学习
  8. [转载] 全本张广泰——第十九回 御赐黄马褂 奉旨喜还乡
  9. python ndimage_Python ndimage.zoom方法代码示例
  10. 21考研数学二和英语二复习规划
  11. 毕设-基于JavaWeb体育竞赛管理系统
  12. 华为hs8145v5刷机包_华为hs8145v5刷机包_华为P40 Pro(ELSAN00/TN00)官方11.0.0.155固件卡刷包强刷包救砖包...
  13. java中peek是什么意思,在Java流中,PEEK真的只用于调试吗?
  14. 一个进度条,类似DNF的血条
  15. Matlab命令行乱码问题
  16. CSS首字母下沉怎么设置?
  17. linux操作系统 以下哪个命令,Linux认证考试基础试题及答案
  18. 推荐9个适合Python开发的IDE。
  19. MIPS、CPI、MFLOPS
  20. .chm文件是啥,以及打开方法(我电脑win7)

热门文章

  1. 快速实现安卓输入法!
  2. 【概念】区块链中账本是什么?通用区块链平台账本概念介绍,一个谁都能看懂的账本概念
  3. 使用tcpdump抓取DNS包
  4. BUGS 小胡的学习日志
  5. 台式电脑出厂编号怎么查_出厂编号的查询方法
  6. 使用命令行查看Windows系统激活信息
  7. JavaScript重写alert方法
  8. permissions is only granted to system apps
  9. 「现场参会攻略」| 2018AI in China之智能制造数据驱动产业变革高峰论坛
  10. PIC16F877A与Proteus仿真-4位7段数码管驱动