神秘代码alpha191的终极打开方式
广义上的量化分析主要有三大领域——量化选股,量化择时和各种套利交易。
在量化选股策略中,多因子策略作为一个主要手段,被各种公募基金和私募基金长期使用。
简单来说,所谓因子就是影响股价波动的因素,将其量化以后用来指导选股。
学过计量经济学的同学应该觉得很好理解——没错,本质上就是所谓的多元回归模型。常见的因子列表如下:
因子包括9类,规模因子,估值因子,成长因子,盈利因子,动量反转因子,交投因子,波动率因子,分析师预测因子。
1规模类因子包括,总市值,流通市值,自由流通市值
2估值类因子包括,市盈率(TTM),市净率,市销率,市现率,企业价值倍数
3成长类因子,营业收入同比增长率、营业利润同比增长率,归属于母公司的近利润同比增长率、经营活动产生的现金流金额同比增长率
4盈利类因子,净资产收益率ROE、总资产报酬率ROA、销售毛利率、销售净利率
5 动量反转因子,前一个月涨跌幅,前2个月涨跌幅、前3个月涨跌幅、前6个月涨跌幅
6交投因子、前一个月日均换手率
7波动因子,前一个月的波动率,前一个月的振幅
8股东因子、户均持股比例、、户均持股比例变化、机构持股比例变化、
9分析师,预测当年净利润增长率、主营业务收入增长率、最近一个月预测净利润上调幅度、最近一个月越策主营业务收入上调幅度,最近一个月上调评级占比
作者:量化小王子
来源:雪球
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
通过对上述因子进行不同的组合,赋予特定的权重,理论上我们可以得到一个使收益率最大化的模型,来指导选股。
如果仔细观察,有没有发现上面9类因子的代理变量特点?没错,大部分代理变量都是静态指标。也就是说,传统上多因子策略是一个偏向于长期价值投资的策略。
那有没有非传统的短期多因子策略呢?有,它就是神秘代码alpha191。
事情要从国泰君安数量化团队的一篇雄文说起——《基于短周期价量特征的多因子选股体系》,非常佩服这几位师傅的毅力和敬业精神,文中通过各种指标,构建出了多达191 个短周期交易型阿尔法因子,并且轻描淡写的表示“这不算什么,很多国外量化资金的因子数量高达成千上万”。
之后,聚宽平台非常给力的引入了alpha191:
通过交易型阿尔法策略的研究,发现在 A 股市场,与传统多因子模型所获取的股票价值阿尔法收益相比,交易型阿尔法收益的空间更大、收益稳定性也更强。
短周期交易型阿尔法体系既是对传统多因子体系的补充,也可以说是全新思路、独立设计的交易体系。在这其中,量化模型不再仅仅是低风险低收益的投资策略,同样也可获得高额的收益回报。
JoinQuant聚宽(专业的量化交易平台)旨在为大家提供更多的投资思路及可使用的数据,因此我们根据国泰君安数量化专题研究报告 - 基于短周期价量特征的多因子选股体系给出了 191 个短周期交易型阿尔法因子,方便大家使用。
其中因子数据则均来自于个股日频率的价格与成交量数据,并且在编写短周期交易型 Alpha191因子时,有对缺失部分和不合理部分的因子公式进行调整。
我们初衷是想为大家提供更多的投资思路及可使用的数据。至于这些因子如何使用能达到策略最佳收益,或者说这些因子是否适用于A股市场等问题,还需要大家自己去研究与钻研。
ahpha191的api文件请点击这里,里面有每个因子具体的计算方法。
本菜鸟用一下午时间,参考聚宽社区大神的一些操作,手撸了一个检测单因子效果的回测策略,具体如下:
from jqdata import *
from jqlib.alpha191 import *
import pandas as pd
import datetime
def initialize(context):
#开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 过滤掉order系列API产生的比error级别低的log
log.set_level('order', 'error')
### 股票相关设定 ###
# 股票类每笔交易时的手续费是:买入时佣金万分之三,卖出时佣金万分之三加千分之一印花税, 每笔交易佣金最低扣5块钱
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
#参数设置部分
g.benchmark = '000001.XSHG'
g.stockpool = '000300.XSHG'
g.max_hold_num = 100 #最大持仓股票
g.days=0 #记录运行天数,用于调仓
g.periods=0 #标记是否为初始化
g.stockcodelist = get_index_stocks(g.stockpool)
#设置基准
set_benchmark(g.benchmark)
#选择策略操作周期
run_daily(main_func, time='open', reference_security = g.benchmark)
#run_weekly(main_func,1,time = 'open', reference_security=g.benchmark)
#run_monthly(main_func,1,time = 'open', reference_security=g.benchmark)
#run_monthly(main_func,11,time = 'open', reference_security=g.benchmark)
## 开盘前运行函数
#==============主体函数==================
def main_func(context):
stocklist = set_feasible_stocks(g.stockcodelist,30,context)
d1 = context.current_dt.date()
d2 = d1 - datetime.timedelta(days = 1)
d3 = d2 - datetime.timedelta(days = 1)
#核心功能,选择你想要的alpha编号001-191
alpss = alpha_001(stocklist, d2).dropna()
alpss.sort(ascending = True)
stock_list = list(alpss.index)
stock_list = stock_list[0:19]
print(d3, stock_list)
g.periods += 1
trade1(context, stock_list)
def set_feasible_stocks(stock_list,days,context):
# 得到是否停牌信息的dataframe,停牌的1,未停牌得0
suspened_info_df = get_price(list(stock_list), start_date=context.current_dt, end_date=context.current_dt, frequency='daily', fields='paused')['paused'].T
# 过滤停牌股票 返回dataframe
unsuspened_index = suspened_info_df.iloc[:,0]<1
# 得到当日未停牌股票的代码list:
unsuspened_stocks = suspened_info_df[unsuspened_index].index
# 进一步,筛选出前days天未曾停牌的股票list:
feasible_stocks = []
current_data = get_current_data()
# print(security_list)
feasible_stocks = [stock for stock in feasible_stocks if not current_data[stock].is_st]
for stock in unsuspened_stocks:
if sum(attribute_history(stock, days, unit='1d',fields=('paused'),skip_paused=False))[0]==0:
feasible_stocks.append(stock)
return feasible_stocks
#交易部分(权重分配
def trade1(context,stock_list):
cash_value_50 = context.portfolio.total_value*0.8
cash_value_30 = context.portfolio.total_value*0.2
#cash_value_20 = context.portfolio.total_value*0.0
stock_list_20 = stock_list[:9]
stock_list_30 = stock_list[10:19]
#stock_list_50 = stock_list[20:]
#卖出不在持仓列表中股票
#调整已有持仓
for stock in context.portfolio.positions.keys():
if stock not in stock_list:
order_target(stock, 0)
trade3(context,stock_list_20,cash_value_50)
trade3(context,stock_list_30,cash_value_30)
#trade3(context,stock_list_50,cash_value_20)
def trade3(context, substocklist, cash):
cash_value = cash*1.0/len(substocklist)
print('每只股票应分仓为:%s'%cash_value)
for stock in substocklist:
if stock in context.portfolio.positions.keys():
#止盈止损,或者也可以一直保持仓位在cash_value,具体看回测结果和因子特点
if context.portfolio.long_positions[stock].value>(1.2*cash_value):
order_target_value(stock, 0) #止盈
elif context.portfolio.long_positions[stock].value<(0.8*cash_value):
order_target_value(stock, 0) #止损
#买入在持仓中股票
else:
order_target_value(stock, cash_value)
不同因子的公式不同,所以回测结果很难预料。
有的可能在大盘下行趋势中好用,但很难抓住快速上涨,比如下面这个。
具体还需要慢慢探索了~
回测示例
最后讲个有趣的事儿。
在我最开始试验的时候,发现一个因子走出了这种收益曲线:
在这里插入图片描述
居然不费吹灰之力破解了神秘代码alpha191?
轻而易举的发现了财富的金矿?
从此走上人生巅峰?
从此走上人生巅峰?
直到我继续尝试下去,才发现大部分因子都走出了类似的收益曲线……
检查了半天才发现问题出在这里:
d1 = context.current_dt.date()
alpss = alpha_001(stocklist, d1).dropna()
看出来了吗?原理在于,由于是短期交易的因子,很多公式里会用当天或前一天的指标进行计算。
但这些指标实际上在收盘以后才会形成,但回测的时候在开盘就用了,所以一些当日超高成长性的股票就会被买入。也就是所谓的
预 知 未 来
笑容逐渐消失默默修改如下:
d1 = context.current_dt.date()
d2 = d1 - datetime.timedelta(days = 1)
d3 = d2 - datetime.timedelta(days = 1)
alpss = alpha_001(stocklist, d2).dropna()
用d1的前一天d2作为参数
就不会出现预知未来的问题了,以此类推d3乃至d4也可以尝试一下。
祝早日找到心仪的因子~