繁簡切換您正在訪問的是FX168財經網,本網站所提供的內容及信息均遵守中華人民共和國香港特別行政區當地法律法規。

FX168财经网>人物频道>帖子

F_score计算函数及简单策略应用分享

作者/jjdsad 2019-09-02 00:00 0 来源: FX168财经网人物频道

华创证券研报《双重筹码集中的基本面选股策略 》(2019年8月20日)介绍了F-score。

根据该研报,F-score是Piotroski于2000年提出,探讨了是否简单地使用财务报表信息来做基本面分析,可以明显地改善低估值策略,有效地规避低估值陷阱。

F-score从盈利能力、偿债能力与资本结构、运营能力三个方面提出 9 个指标来衡量公司的基本面状况, 华创证券结合 A 股情况做了细微地修改,主要是将年度改成了 TTM,如下表所示:

Img

可以看出这 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)
1

经营活动产生的现金流量净额比总资产¶

统计口径:经营活动产生的现金流量净额_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

资产收益率变化¶

统计口径:当期资产收益率 - 上一期资产收益率

计分标准:大于零为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

应计收益率¶

统计口径:经营活动产生的现金流量净额比总资产 -资产收益率

计分标准:大于零为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

长期负债率变化¶

统计口径:当期长期负债率 - 上一期长期负债率

计分标准:大于零为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

流动比率变化¶

统计口径:当期流动比率 - 上一期流动比率

计分标准:大于零为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

股票是否增发¶

统计口径:最近一年股票是否增发

计分标准:是为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)
1

毛利率变化¶

统计口径:当期毛利率_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)
0

资产周转率变化¶

统计口径:当期资产周转率_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)
1

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)
6
#以上证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
125.922972202 秒
600000.XSHG    5
600016.XSHG    4
600019.XSHG    5
600028.XSHG    6
600029.XSHG    4
600030.XSHG    5
600031.XSHG    9
600036.XSHG    5
600048.XSHG    8
600050.XSHG    8
600104.XSHG    5
600196.XSHG    7
600276.XSHG    6
600309.XSHG    5
600340.XSHG    5
600519.XSHG    7
600585.XSHG    7
600690.XSHG    5
600703.XSHG    5
600837.XSHG    5
600887.XSHG    7
601066.XSHG    7
601088.XSHG    6
601111.XSHG    5
601138.XSHG    6
601166.XSHG    5
601186.XSHG    6
601211.XSHG    5
601229.XSHG    7
601288.XSHG    6
601318.XSHG    7
601319.XSHG    6
601328.XSHG    5
601336.XSHG    7
601390.XSHG    4
601398.XSHG    6
601601.XSHG    6
601628.XSHG    5
601668.XSHG    3
601688.XSHG    4
601766.XSHG    5
601800.XSHG    4
601818.XSHG    5
601857.XSHG    8
601888.XSHG    8
601939.XSHG    6
601988.XSHG    5
601989.XSHG    3
603259.XSHG    5
603993.XSHG    5
Name: stock, dtype: int64
#筛选F_socre 大于等于7的股票
df_stock = df_stock[df_stock>=7]
df_stock 
600031.XSHG    9
600048.XSHG    8
600050.XSHG    8
600196.XSHG    7
600519.XSHG    7
600585.XSHG    7
600887.XSHG    7
601066.XSHG    7
601229.XSHG    7
601318.XSHG    7
601336.XSHG    7
601857.XSHG    8
601888.XSHG    8
Name: stock, dtype: int64
分享到:
举报财经168客户端下载

全部回复

0/140

投稿 您想发表你的观点和看法?

更多人气分析师

  • 张亦巧

    人气2144文章4145粉丝45

    暂无个人简介信息

  • 梁孟梵

    人气2152文章3177粉丝39

    qq:2294906466 了解群指导添加微信mfmacd

  • 指导老师

    人气1856文章4423粉丝52

    暂无个人简介信息

  • 李冉晴

    人气2296文章3821粉丝34

    李冉晴,专业现贷实盘分析师。

  • 刘钥钥1

    人气2016文章3119粉丝34

    专业从事现货黄金、现货白银模似实盘操作分析指导

  • 张迎妤

    人气1896文章3305粉丝34

    个人专注于行情技术分析,消息面解读剖析,给予您第一时间方向...

  • 金泰铬J

    人气2320文章3925粉丝51

    投资问答解咨询金泰铬V/信tgtg67即可获取每日的实时资讯、行情...

  • 金算盘

    人气2696文章7761粉丝125

    高级分析师,混过名校,厮杀于股市和期货、证券市场多年,专注...

  • 金帝财神

    人气4728文章8329粉丝118

    本文由资深分析师金帝财神微信:934295330,指导黄金,白银,...