问题描述
解决方案
在PTrade中,企业自由现金流(Enterprise Free Cash Flow, FCFF)是一个非常重要的基本面指标,用于衡量公司在支付了运营费用和资本支出后,能够分配给所有资本提供者(包括股东和债权人)的现金流。
以下是获取该数据的方法以及一个基于**企业自由现金流收益率(FCFF Yield)**的选股策略实现。
1. 如何获取企业自由现金流数据
在PTrade的API中,企业自由现金流数据位于财务数据接口 get_fundamentals 的 eps(每股指标)表中。
- 表名 (
table):'eps' - 字段名 (
fields):'enterprise_fcf_ps'(每股企业自由现金流)
获取方式示例:
# 获取股票列表
stocks = ['600519.SS', '000858.SZ']
# 获取每股企业自由现金流
df = get_fundamentals(stocks, 'eps', ['enterprise_fcf_ps'], date='20230601')
2. 策略逻辑:高企业自由现金流收益率选股
单纯比较“每股企业自由现金流”绝对值意义不大,因为股价不同。通常我们使用企业自由现金流收益率 (FCFF Yield) 进行横向对比。
公式:
$$ \text{FCFF Yield} = \frac{\text{每股企业自由现金流 (enterprise_fcf_ps)}}{\text{股价 (Price)}} $$
策略步骤:
- 股票池:选取沪深300成分股。
- 调仓周期:每月调仓一次。
- 数据获取:获取成分股的
enterprise_fcf_ps和当前收盘价。 - 因子计算:计算 FCFF Yield。
- 排序与筛选:剔除FCFF为负的公司,按收益率从高到低排序,选取前10只股票。
- 交易执行:卖出不在持仓列表的股票,买入目标股票(等权分配)。
3. 策略代码实现
以下是完整的策略代码,可以直接在PTrade回测环境中运行。
import numpy as np
import pandas as pd
def initialize(context):
"""
初始化函数,设置策略参数和运行计划
"""
# 设置回测基准为沪深300
set_benchmark('000300.SS')
# 设置手续费(股票万分之三)
set_commission(commission_ratio=0.0003, min_commission=5.0, type="STOCK")
# 设置滑点
set_slippage(slippage=0.002)
# 定义全局变量:持仓数量
g.stock_num = 10
# 定义全局变量:股票池代码(沪深300)
g.index_code = '000300.SS'
# 设置按月运行,每月第一个交易日进行调仓
run_daily(context, rebalance, time='09:35')
def rebalance(context):
"""
调仓主函数
"""
# 1. 判断是否为月初(这里简化处理,每20个交易日或者通过日期判断,此处演示简单逻辑:每月运行)
# 为了演示方便,这里每次run_daily都检查,实际策略建议配合日期判断
# 获取当前日期
current_date = context.blotter.current_dt
# 获取上一个月
previous_date = context.previous_date
# 如果当前月份和上一个交易日月份相同,则不调仓(即只在月初调仓)
if current_date.month == previous_date.month:
return
log.info("开始进行月度调仓: %s" % current_date)
# 2. 获取股票池(沪深300成分股)
# 注意:get_index_stocks 建议在盘前或盘中调用,不要在 initialize 中调用
stocks = get_index_stocks(g.index_code)
if not stocks:
log.warning("未获取到指数成分股")
return
# 3. 获取基本面数据:每股企业自由现金流
# date参数传入当前回测日期,获取当时已发布的最新财报数据
q_date = current_date.strftime('%Y%m%d')
# 查询 eps 表中的 enterprise_fcf_ps 字段
fund_df = get_fundamentals(stocks, 'eps', ['enterprise_fcf_ps'], date=q_date)
if fund_df is None or fund_df.empty:
log.warning("未获取到财务数据")
return
# 4. 获取当前股价(使用前一日收盘价计算收益率)
# get_history 返回的是 DataFrame,我们需要提取收盘价
price_df = get_history(1, '1d', 'close', stocks, fq='pre', include=False)
# 5. 数据处理与因子计算
# 提取收盘价 Series
# 注意:get_history 返回结构可能因版本不同略有差异,这里做兼容处理
if 'code' in price_df.columns:
# Python 3.11+ 风格
close_series = price_df.set_index('code')['close']
else:
# Python 3.5 风格 (Panel 或 列名为代码的DF)
# 如果是DataFrame且列名为股票代码,取最后一行
close_series = price_df.iloc[-1]
# 将财务数据和价格数据合并
# fund_df 的索引通常是股票代码
df_merge = pd.DataFrame()
df_merge['fcf_ps'] = fund_df['enterprise_fcf_ps']
df_merge['price'] = close_series
# 去除空值(停牌或无数据)
df_merge = df_merge.dropna()
# 剔除股价为0或负数的情况(极少见,防错)
df_merge = df_merge[df_merge['price'] > 0]
# 计算企业自由现金流收益率 (FCFF Yield)
df_merge['fcf_yield'] = df_merge['fcf_ps'] / df_merge['price']
# 剔除 FCFF 为负的公司(只投资产生正向自由现金流的公司)
df_merge = df_merge[df_merge['fcf_ps'] > 0]
# 6. 排序选股
# 按收益率降序排列
df_merge = df_merge.sort_values(by='fcf_yield', ascending=False)
# 取前 N 只股票
target_list = df_merge.index[:g.stock_num].tolist()
log.info("本期选中股票: %s" % target_list)
# 7. 交易执行
trade_execution(context, target_list)
def trade_execution(context, target_list):
"""
执行具体的买卖操作
"""
# 获取当前持仓
current_positions = list(context.portfolio.positions.keys())
# 卖出不在目标列表中的股票
for stock in current_positions:
if stock not in target_list:
order_target_value(stock, 0)
log.info("卖出: %s" % stock)
# 如果目标列表为空,直接返回
if not target_list:
return
# 买入目标股票
# 简单的等权分配资金
position_count = len(target_list)
# 获取当前可用资金 + 持仓市值(估算总资产,用于分配)
# 注意:这里简单使用 portfolio_value,实际交易中可能需要考虑预留资金
total_value = context.portfolio.portfolio_value
value_per_stock = total_value / position_count
for stock in target_list:
order_target_value(stock, value_per_stock)
log.info("买入/调仓: %s, 目标市值: %.2f" % (stock, value_per_stock))
def handle_data(context, data):
"""
盘中运行函数(本策略主要逻辑在 run_daily 中,此处留空或做风控)
"""
pass
4. 代码关键点解析
-
get_fundamentals(..., 'eps', ['enterprise_fcf_ps'], ...):- 这是核心API调用。
eps表存储了每股数据,enterprise_fcf_ps即每股企业自由现金流。 - 使用
date参数可以确保获取的是回测当时已公开的最新财报数据,避免“未来函数”。
- 这是核心API调用。
-
数据对齐:
- 财务数据和行情数据(股价)必须对齐。代码中通过
pd.DataFrame将两者合并,并使用dropna()剔除停牌或数据缺失的股票。
- 财务数据和行情数据(股价)必须对齐。代码中通过
-
FCFF Yield 计算:
- 代码计算了
fcf_ps / price。这是一个估值指标,类似于市盈率的倒数(Earnings Yield),但使用的是更难造假的现金流数据。数值越高,代表股票越“便宜”且现金流越好。
- 代码计算了
-
兼容性:
- 代码考虑了PTrade不同Python环境对
get_history返回格式的微小差异,使用了较为通用的处理方式。
- 代码考虑了PTrade不同Python环境对
Q&A
Q1: 为什么使用“每股”数据而不是总额?
A1: 在PTrade的 eps 表中直接提供了每股数据,结合股价计算收益率(Yield)非常方便。如果使用总额(Total Enterprise Free Cash Flow),则需要除以总市值(Total Market Cap)来计算收益率,结果是一样的,但直接用每股数据计算步骤更少。
Q2: 企业自由现金流为负说明什么?
A2: 说明企业经营产生的现金不足以覆盖其资本支出(如购买厂房设备)。这在初创期或高速扩张期的公司很常见,但对于成熟期选股策略,通常倾向于剔除此类公司以降低风险。因此代码中包含了 df_merge[df_merge['fcf_ps'] > 0] 的过滤条件。
Q3: 这个策略适合什么类型的市场风格?
A3: 这是一个典型的价值风格策略。它倾向于选择现金流充沛且估值较低的股票,通常在市场震荡或价值回归的行情中表现较好,但在成长股爆发的行情中可能会跑输指数。