🚀 AI 一键生成 joinquant 策略代码

JoinQuant 平台如何构建 EV/EBITDA 价值选股策略?

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

问题描述

请帮我写一个基于 EV/EBITDA 的价值选股策略

解决方案

这是一个基于 EV/EBITDA(企业价值倍数) 的价值选股策略。

策略原理

EV/EBITDA 是一个广泛使用的价值投资指标,相比于市盈率(PE),它更能排除资本结构(债务杠杆)、税收政策和折旧摊销政策的影响,从而更纯粹地反映企业的估值水平。

  • EV (企业价值) = 市值 + 净债务 = 市值 + 总负债 - 现金及现金等价物
  • EBITDA (息税折旧摊销前利润) = 净利润 + 所得税 + 利息费用 + 折旧 + 摊销
  • 选股逻辑:EV/EBITDA 数值越低,代表企业被低估的可能性越大。本策略将在沪深300成分股中,选取 EV/EBITDA 最小的股票进行持仓。

策略实现细节

  1. 股票池:沪深300成分股。
  2. 过滤条件:剔除停牌、ST股、EBITDA为负(亏损或经营状况极差)的股票。
  3. 调仓频率:按月调仓(每月第一个交易日)。
  4. 资金管理:等权重买入排名前 20 的股票。

策略代码

# -*- coding: utf-8 -*-
from jqdata import *

def initialize(context):
    """
    初始化函数,设定基准、手续费、滑点等
    """
    # 设定沪深300作为基准
    set_benchmark('000300.XSHG')
    # 开启动态复权模式(真实价格)
    set_option('use_real_price', True)
    # 过滤掉order系列API产生的比error级别低的log
    log.set_level('order', 'error')
    
    # 设定股票交易手续费:买入万三,卖出万三加千一印花税
    set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
    
    # 设定全局变量:持仓数量
    g.stock_num = 20 
    
    # 设定按月运行,每月第一个交易日09:30运行
    run_monthly(rebalance, monthday=1, time='09:30')

def rebalance(context):
    """
    调仓函数
    """
    # 1. 获取股票池:沪深300
    stocks = get_index_stocks('000300.XSHG')
    
    # 2. 过滤 ST 和 停牌 的股票
    current_data = get_current_data()
    stocks = [s for s in stocks if not current_data[s].is_st and not current_data[s].paused]
    
    # 3. 获取财务数据
    # 我们需要计算 EV 和 EBITDA
    # EV = 市值 + 总负债 - 货币资金(现金及现金等价物)
    # EBITDA = 净利润 + 所得税 + 财务费用(近似利息) + 折旧 + 摊销
    
    q = query(
        valuation.code,
        valuation.market_cap,  # 总市值 (单位:亿元)
        balance.total_liability, # 总负债 (单位:元)
        balance.cash_equivalents, # 货币资金/现金及现金等价物 (单位:元)
        income.net_profit,       # 净利润 (单位:元)
        income.income_tax_expense, # 所得税费用 (单位:元)
        income.financial_expense,  # 财务费用 (单位:元)
        cash_flow.depreciation_of_fixed_assets, # 固定资产折旧 (单位:元)
        cash_flow.amortization_of_intangible_assets, # 无形资产摊销 (单位:元)
        cash_flow.amortization_of_long_term_deferred_expenses # 长期待摊费用摊销 (单位:元)
    ).filter(
        valuation.code.in_(stocks)
    )
    
    df = get_fundamentals(q)
    
    # 数据清洗:填充空值为0
    df.fillna(0, inplace=True)
    
    # 4. 计算指标
    # 注意:valuation.market_cap 单位是亿元,需要转换为元 (* 10^8)
    df['EV'] = df['market_cap'] * 1e8 + df['total_liability'] - df['cash_equivalents']
    
    df['EBITDA'] = df['net_profit'] + df['income_tax_expense'] + df['financial_expense'] + \
                   df['depreciation_of_fixed_assets'] + df['amortization_of_intangible_assets'] + \
                   df['amortization_of_long_term_deferred_expenses']
    
    # 过滤掉 EBITDA <= 0 的公司(避免估值无意义或亏损严重)
    df = df[df['EBITDA'] > 0]
    
    # 计算 EV/EBITDA
    df['EV_EBITDA'] = df['EV'] / df['EBITDA']
    
    # 5. 选股
    # 按照 EV/EBITDA 从小到大排序
    df = df.sort_values('EV_EBITDA', ascending=True)
    
    # 取前 g.stock_num 只股票
    target_list = df['code'][:g.stock_num].tolist()
    
    # 6. 执行交易
    # 获取当前持仓
    current_holdings = list(context.portfolio.positions.keys())
    
    # 卖出不在目标列表中的股票
    for stock in current_holdings:
        if stock not in target_list:
            order_target_value(stock, 0)
    
    # 买入目标列表中的股票
    if len(target_list) > 0:
        # 将资金等分
        # 注意:这里使用 total_value (总资产) 来计算每只股票的目标持仓,保持仓位平衡
        target_value = context.portfolio.total_value / len(target_list)
        
        for stock in target_list:
            order_target_value(stock, target_value)
            
    log.info("调仓完成,当前持有股票数量: %d" % len(target_list))

常见问题 Q&A

Q1: 为什么使用 get_fundamentals 而不是直接调用因子库?
A: 虽然聚宽因子库(jqfactor)提供了现成的因子,但使用 get_fundamentals 从原始财务报表计算可以让策略逻辑更加透明,且方便您根据需要微调公式(例如调整 EV 的计算方式,是否包含少数股东权益等)。此外,get_fundamentals 在回测中会自动处理“未来函数”问题,确保只获取当时已发布的财报数据。

Q2: 代码中 valuation.market_cap * 1e8 是什么意思?
A: 聚宽数据库中,valuation 表里的 market_cap(市值)单位是亿元,而 balanceincome 表里的财务数据单位是。为了保证公式 EV = 市值 + 负债 - 现金 的量级一致,必须将市值乘以 $10^8$ 转换为元。

Q3: 为什么过滤掉 EBITDA <= 0 的股票?
A: 当 EBITDA 为负时,说明企业经营现金流压力较大或处于亏损状态,此时计算出的 EV/EBITDA 比率为负数,在数学排序上虽然“小”,但并不代表“低估”,而是代表“差”。因此在价值选股中通常会剔除这类股票。

Q4: 策略可以如何优化?
A:

  1. 行业中性化:EV/EBITDA 在重资产行业(如公用事业、制造)和轻资产行业(如科技、服务)之间差异很大。可以考虑在每个行业内分别选股,而不是全市场混排。
  2. 增加质量因子:低估值可能意味着“价值陷阱”。可以结合 ROE(净资产收益率)或 盈利增长率 来筛选既便宜又好的公司。
  3. 止损机制:加入个股止损或大盘风控(如大盘跌破20日均线空仓)。