请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  源码分享 帖子:3128211 新帖:268

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

jjdsad发表于:9 月 2 日 00:00回复(1)

华创证券研报《双重筹码集中的基本面选股策略 》(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

全部回复

0/140

量化课程

    移动端课程