🚀 AI 一键生成 ptrade 策略代码

PTrade 选股指南:基于企业自由现金流量 (FCF) 的价值策略

ptrade | 发布时间: 2025/12/23
以下内容由 EasyQuant 生成。

问题描述

如何获取并使用企业自由现金流进行选股?

解决方案

在PTrade中,企业自由现金流(Enterprise Free Cash Flow, FCFF)是一个非常重要的基本面指标,用于衡量公司在支付了运营费用和资本支出后,能够分配给所有资本提供者(包括股东和债权人)的现金流。

以下是获取该数据的方法以及一个基于**企业自由现金流收益率(FCFF Yield)**的选股策略实现。

1. 如何获取企业自由现金流数据

在PTrade的API中,企业自由现金流数据位于财务数据接口 get_fundamentalseps(每股指标)表中。

  • 表名 (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)}} $$

策略步骤:

  1. 股票池:选取沪深300成分股。
  2. 调仓周期:每月调仓一次。
  3. 数据获取:获取成分股的 enterprise_fcf_ps 和当前收盘价。
  4. 因子计算:计算 FCFF Yield。
  5. 排序与筛选:剔除FCFF为负的公司,按收益率从高到低排序,选取前10只股票。
  6. 交易执行:卖出不在持仓列表的股票,买入目标股票(等权分配)。

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. 代码关键点解析

  1. get_fundamentals(..., 'eps', ['enterprise_fcf_ps'], ...):

    • 这是核心API调用。eps 表存储了每股数据,enterprise_fcf_ps 即每股企业自由现金流。
    • 使用 date 参数可以确保获取的是回测当时已公开的最新财报数据,避免“未来函数”。
  2. 数据对齐:

    • 财务数据和行情数据(股价)必须对齐。代码中通过 pd.DataFrame 将两者合并,并使用 dropna() 剔除停牌或数据缺失的股票。
  3. FCFF Yield 计算:

    • 代码计算了 fcf_ps / price。这是一个估值指标,类似于市盈率的倒数(Earnings Yield),但使用的是更难造假的现金流数据。数值越高,代表股票越“便宜”且现金流越好。
  4. 兼容性:

    • 代码考虑了PTrade不同Python环境对 get_history 返回格式的微小差异,使用了较为通用的处理方式。

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: 这是一个典型的价值风格策略。它倾向于选择现金流充沛且估值较低的股票,通常在市场震荡或价值回归的行情中表现较好,但在成长股爆发的行情中可能会跑输指数。