华创证券研报《双重筹码集中的基本面选股策略 》(2019年8月20日)介绍了F-score。
根据该研报,F-score是Piotroski于2000年提出,探讨了是否简单地使用财务报表信息来做基本面分析,可以明显地改善低估值策略,有效地规避低估值陷阱。
F-score从盈利能力、偿债能力与资本结构、运营能力三个方面提出 9 个指标来衡量公司的基本面状况, 华创证券结合 A 股情况做了细微地修改,主要是将年度改成了 TTM,如下表所示:
可以看出这 9 个指标均为 0-1 变量,将这 9 个指标加起来即为 F-score。Piotroski(2000)依据得分将得分在 0 至 3 之间的股票设置为 Low 组,将得分在 4 至 6 之前的股票设置为 Middle 组,将得分在 7 至 9 之间的股票设置为 High 组。
根据研报回测结果,Middle 组 和 High 组选择的股票基本面更好,大幅度回撤的概率更低。研报指出,F-score 具备一定的收益区分能力,高 F-score 组明显的跑赢低 F-score 组,但研报并不把 F-score 当作一个收益预测因子,而更多的是风控手段。
我根据研报给出的F_score计算方式,把计算各个指标的函数代码分享出来,供大家用于作为筛选基本面良好的股票。(感谢@JoinQuant-PM,此处使用了其共享的获取一段时期财务数据的函数。)
同时还给大家分享了融合F_score基本面选股的经典小市值策略(目前看来对小市值策略收益的改善效果不理想),希望对大家有启发作用。策略思路是:选择F_score大于等于5且市值最小的5只股票持有,每10天调仓。
对于F_score 如何应用于策略中,希望能够得到论坛各位大神指点!谢谢!
# 导入函数库
from jqfactor import get_factor_values
import pandas as pd
import numpy as np
from jqdata import finance
import datetime
import time
stock ='000651.XSHE' #以格力电器为例
index = '000016.XSHG'#上证50指数
def get_fundamentals_sum_mean_value(security='000001.XSHE', search=income.basic_eps, count=5, frequency='quarter'):
'''
输入:
security: 要查询股票的代码
search: 要查询的字段,详情参考 API: get_fundamentals-查询财务数据
count: 单位时间长度,表示返回前几多少期的季报或者年报
frequency: 获取数据类型,'quarter'为季报,'year'为年报
输出:
sum_num: 总值
mean_num: 平均值
df: 一段时间内季报或者年报的 DataFrame
注:
对于年报数据, 我们目前只有 现金流表 和 利润表, 当查询其他表时, 会返回该年份最后一个季报的数据"
'''
import pandas as pd
def get_quarter(month):
if month in (1,2,3):
return 1
elif month in (4,5,6):
return 2
elif month in (7,8,9):
return 3
elif month in (10,11,12):
return 4
# 查询条件
q = query(
income.code,
income.statDate,
search,
).filter(
income.code.in_([security])
)
# 获取最近一次报表发布的日期
statDate_num = get_fundamentals(q)['statDate'][0]
# 获取最近一次报表发布所属的年份
year = datetime.datetime.strptime(statDate_num, "%Y-%m-%d").year
# 获取最近一次报表发布所属的月份
month = datetime.datetime.strptime(statDate_num, "%Y-%m-%d").month
# 获取最近一次报表发布日期所属的季度
qt = get_quarter(month)
# 获取季度列表
qt_list = range(qt,0,-1)+range(4,0,-1)*(count/4+1)
qt_list = qt_list[:count]
# 查询时间列表
data_list = []
# 获取查询的名称
name = str(search).split('.')[-1]
# 列名称
colums_list = []
# 获取拼接后的 DataFrame
if frequency == 'quarter':
for num in range(len(qt_list)):
s = str(year)+'q'+str(qt_list[num])
data_list.append(s)
colums_list.append(name + '_' + s)
if qt_list[num] == 1:
year -= 1
# 拼接列表
df = get_fundamentals(q, statDate = data_list[0])
df.set_index(df.code.values, inplace=True)
for t in data_list[1:]:
d = get_fundamentals(q, statDate = t)
d.set_index(d.code.values, inplace=True)
df = pd.concat([df[name],d[name]],axis = 1)
elif frequency == 'year':
for num in range(count):
year -= 1
s = str(year)
data_list.append(s)
colums_list.append(name + '_' + s)
# 拼接列表
df = get_fundamentals(q, statDate = data_list[0])
df.set_index(df.code.values, inplace=True)
for t in data_list[1:]:
d = get_fundamentals(q, statDate = t)
d.set_index(d.code.values, inplace=True)
df = pd.concat([df[name],d[name]],axis = 1)
else:
print "请输入正确的 frequency 参数,'quarter'为季报,'year'为年报. \n对于年报数据, 我们目前只有 现金流表 和 利润表, \n当查询其他表时, 会返回该年份最后一个季报的数据"
return
# 设置列名称
df.columns = colums_list[:len(df.iloc[0])]
# 计算总值
sum_num = sum(df.iloc[0])
# 计算平均值
mean_num = mean(df.iloc[0])
# 返回结果
return sum_num, mean_num, df
资产收益率¶
统计口径:扣非净利润_TTM/总资产_AVG
计分标准:大于零为1,否则为0
def ROA_TTM_score(stock):
#输入:股票代码
#输出:如果资产收益率大于零为1,否则为0
if get_fundamentals_sum_mean_value(security=stock, search=indicator.adjusted_profit, count=4, frequency='quarter')[0]/get_fundamentals_sum_mean_value(security=stock, search=balance.total_assets, count=4, frequency='quarter')[1] > 0:
return 1
else:
return 0
ROA_TTM_score(stock)
经营活动产生的现金流量净额比总资产¶
统计口径:经营活动产生的现金流量净额_TTM/总资产_AVG
计分标准:大于零为1,否则为0
def OCFOA_TTM_score(stock):
#输入:股票代码
#输出:如果经营活动产生的现金流量净额比总资产大于零为1,否则为0
if get_fundamentals_sum_mean_value(security=stock, search=cash_flow.net_operate_cash_flow, count=4, frequency='quarter')[0]/get_fundamentals_sum_mean_value(security=stock, search=balance.total_assets, count=4, frequency='quarter')[1] > 0:
return 1
else:
return 0
OCFOA_TTM_score(stock)
资产收益率变化¶
统计口径:当期资产收益率 - 上一期资产收益率
计分标准:大于零为1,否则为0
def ROA_TTM_change_score(stock):
#输入:股票代码
#输出:如果资产收益率大于零为1,否则为0
if get_fundamentals_sum_mean_value(security=stock, search=indicator.adjusted_profit, count=4, frequency='quarter')[0]/get_fundamentals_sum_mean_value(security=stock, search=balance.total_assets, count=4, frequency='quarter')[1]- (get_fundamentals_sum_mean_value(security=stock, search=indicator.adjusted_profit, count=8, frequency='quarter')[0] - get_fundamentals_sum_mean_value(security=stock, search=indicator.adjusted_profit, count=4, frequency='quarter')[0])/ ((get_fundamentals_sum_mean_value(security=stock, search=balance.total_assets, count=8, frequency='quarter')[0] - get_fundamentals_sum_mean_value(security=stock, search=balance.total_assets, count=4, frequency='quarter')[0])/4) > 0:
return 1
else:
return 0
ROA_TTM_change_score(stock)
应计收益率¶
统计口径:经营活动产生的现金流量净额比总资产 -资产收益率
计分标准:大于零为1,否则为
def OCFOA_TTM_ROA_TTM_score(stock):
#输入:股票代码
#输出:如果应计收益率大于零为1,否则为0
if (get_fundamentals_sum_mean_value(security=stock, search=cash_flow.net_operate_cash_flow, count=4, frequency='quarter')[0]/get_fundamentals_sum_mean_value(security=stock, search=balance.total_assets, count=4, frequency='quarter')[1]) - (get_fundamentals_sum_mean_value(security=stock, search=indicator.adjusted_profit, count=4, frequency='quarter')[0]/get_fundamentals_sum_mean_value(security=stock, search=balance.total_assets, count=4, frequency='quarter')[1]) > 0:
return 1
else:
return 0
OCFOA_TTM_ROA_TTM_score(stock)
长期负债率变化¶
统计口径:当期长期负债率 - 上一期长期负债率
计分标准:大于零为0,否则为1
def Long_term_debt_ratio_score(stock):
#输入:股票代码
#输出:如果长期负债率变化大于零为0,否则为1
if get_fundamentals_sum_mean_value(security=stock, search = balance.total_non_current_liability, count=2, frequency='quarter')[2].iloc[0,0]/get_fundamentals_sum_mean_value(security=stock, search=balance.total_assets, count=2, frequency='quarter')[2].iloc[0,0] - get_fundamentals_sum_mean_value(security=stock, search = balance.total_non_current_liability, count=2, frequency='quarter')[2].iloc[0,1]/get_fundamentals_sum_mean_value(security=stock, search=balance.total_assets, count=2, frequency='quarter')[2].iloc[0,1] > 0:
return 0
else:
return 1
Long_term_debt_ratio_score(stock)
流动比率变化¶
统计口径:当期流动比率 - 上一期流动比率
计分标准:大于零为1,否则为0
def Current_ratio_score(stock):
#输入:股票代码
#输出:如果流动比率变化大于零为1,否则为0
if get_fundamentals_sum_mean_value(security=stock, search = balance.total_current_assets, count=2, frequency='quarter')[2].iloc[0,0]/get_fundamentals_sum_mean_value(security=stock, search=balance.total_current_liability, count=2, frequency='quarter')[2].iloc[0,0] - get_fundamentals_sum_mean_value(security=stock, search = balance.total_current_assets, count=2, frequency='quarter')[2].iloc[0,1]/get_fundamentals_sum_mean_value(security=stock, search=balance.total_current_liability, count=2, frequency='quarter')[2].iloc[0,1] > 0:
return 1
else:
return 0
Current_ratio_score(stock)
股票是否增发¶
统计口径:最近一年股票是否增发
计分标准:是为0,否则为1
def SPO_score(stock):
#输入:股票代码
#输出:如果最近一年做了股票增发为0,否则为1
date = (datetime.datetime.now()+ datetime.timedelta(days=-365)).strftime('%Y-%m-%d') #获取一年前的日期,回测时要修改
df_temp = finance.run_query(query(finance.STK_CAPITAL_CHANGE).filter(finance.STK_CAPITAL_CHANGE.code==stock,finance.STK_CAPITAL_CHANGE.pub_date>date,finance.STK_CAPITAL_CHANGE.change_reason_id == 306004).limit(10))
if df_temp.empty :
return 1
else:
return 0
SPO_score(stock)
毛利率变化¶
统计口径:当期毛利率_TTM - 上一期毛利率_TTM
计分标准:大于零为1,否则为0
def DEGM_score(stock):
#输入:股票代码
#输出:如果大于零为1,否则为0
date = (datetime.datetime.now()+ datetime.timedelta(days=-1))#获取前一天的日期,回测时要修改
if get_factor_values(securities =stock , factors = ['DEGM'],end_date = date,count = 2 )['DEGM'].iloc[-1,0] > 0 :
return 1
else:
return 0
DEGM_score(stock)
资产周转率变化¶
统计口径:当期资产周转率_TTM - 上一期资产周转率_TTM
计分标准:大于零为1,否则为0
def Total_asset_turnover_rate_score(stock):
#输入:股票代码
#输出:如果资产周转率变化大于零为1,否则为0
if get_fundamentals_sum_mean_value(security=stock, search=income.operating_revenue, count=4, frequency='quarter')[0]/get_fundamentals_sum_mean_value(security=stock, search=balance.total_assets, count=4, frequency='quarter')[1]- (get_fundamentals_sum_mean_value(security=stock, search=income.operating_revenue, count=8, frequency='quarter')[0] - get_fundamentals_sum_mean_value(security=stock, search=income.operating_revenue, count=4, frequency='quarter')[0])/ ((get_fundamentals_sum_mean_value(security=stock, search=balance.total_assets, count=8, frequency='quarter')[0] - get_fundamentals_sum_mean_value(security=stock, search=balance.total_assets, count=4, frequency='quarter')[0])/4) > 0:
return 1
else:
return 0
Total_asset_turnover_rate_score(stock)
F-score¶
统计口径:这9个指标均为0-1变量,将这9个指标加起来即为F-Score。
分组方式:Piotroski(2000)依据得分将得分在0至3之间的股票设置为Low组,将得分在4至6之前的股票设置为Middle组,将得分在7至9之间的股票设置为High组
def F_score(stock):
return ROA_TTM_score(stock) + OCFOA_TTM_score(stock) + ROA_TTM_change_score(stock) + OCFOA_TTM_ROA_TTM_score(stock) + Long_term_debt_ratio_score(stock) + Current_ratio_score(stock) + SPO_score(stock) + DEGM_score(stock) + Total_asset_turnover_rate_score(stock)
F_score(stock)
#以上证50成分股为例,批量计算股票的F_score
s = time.time()
stock_list = get_index_stocks(index)
df_stock =pd.DataFrame(stock_list,columns=['stock'],index = stock_list)
df_stock = df_stock['stock'].map(F_score)
e = time.time()
print e-s,'秒' #展示计算时长
df_stock
#筛选F_socre 大于等于7的股票
df_stock = df_stock[df_stock>=7]
df_stock