3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
这是一个基于乔尔·格林布拉特(Joel Greenblatt)在《股市稳赚》(The Little Book That Beats the Market)中提出的**神奇公式(Magic Formula)**选股策略的 Python 实现代码。
神奇公式的核心思想是**“用便宜的价格买入好公司”**。它通过两个指标对股票进行排名:
EBIT / (净流动资本 + 净固定资产),在 A 股回测中,我们可以直接使用聚宽提供的 indicator.roic (投资资本回报率) 作为替代。EBIT / 企业价值 (EV),在 A 股回测中,我们可以使用市盈率的倒数(即 1 / PE)或者直接使用 valuation.pe_ratio (市盈率) 进行反向排序(PE 越低越好)。选股逻辑:
# -*- coding: utf-8 -*-
import pandas as pd
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')
# 设定股票交易手续费:买入万三,卖出万三加千一印花税,最低5元
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# 设定全局变量
g.stock_num = 30 # 持仓股票数量
# 设定定期运行:每月第一个交易日进行调仓
run_monthly(rebalance, 1, time='09:30')
def rebalance(context):
"""
调仓函数
"""
# 1. 获取股票池
# 获取所有A股
all_stocks = list(get_all_securities(['stock']).index)
# 过滤掉 ST, *ST, 退市, 停牌的股票
current_data = get_current_data()
all_stocks = [stock for stock in all_stocks
if not current_data[stock].is_st
and not current_data[stock].paused
and '退' not in current_data[stock].name]
# 2. 获取财务数据 (ROIC 和 PE)
# 查询 valuation.pe_ratio (市盈率) 和 indicator.roic (投资资本回报率)
# 过滤掉 PE < 0 (亏损) 的股票
q = query(
valuation.code,
valuation.pe_ratio,
indicator.roic
).filter(
valuation.code.in_(all_stocks),
valuation.pe_ratio > 0
)
df = get_fundamentals(q)
if df is None or len(df) == 0:
log.warn("未获取到财务数据,跳过本次调仓")
return
# 3. 计算排名
# 对 PE 进行升序排名 (PE越低越好,排名越靠前)
df['rank_pe'] = df['pe_ratio'].rank(ascending=True)
# 对 ROIC 进行降序排名 (ROIC越高越好,排名越靠前)
df['rank_roic'] = df['roic'].rank(ascending=False)
# 计算总分 (排名之和)
df['total_score'] = df['rank_pe'] + df['rank_roic']
# 4. 选股
# 按总分升序排列,取前 g.stock_num 只股票
df = df.sort_values('total_score', ascending=True)
target_list = df.head(g.stock_num)['code'].tolist()
# 5. 执行交易
trade(context, target_list)
def trade(context, target_list):
"""
交易执行函数
"""
# 获取当前持仓
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:
# 等权重分配资金
# 注意:这里简单处理,用总资产除以目标数量。实际交易中可能需要预留现金或考虑卖出后的可用资金。
position_value = context.portfolio.total_value / len(target_list)
for stock in target_list:
order_target_value(stock, position_value)
def before_trading_start(context):
"""
盘前运行函数
"""
pass
def after_trading_end(context):
"""
盘后运行函数
"""
pass
数据获取 (get_fundamentals):
valuation.pe_ratio 代表估值因子。神奇公式原版使用的是 EBIT/EV,但在 A 股量化中,PE 是最接近且数据质量最稳定的替代指标。indicator.roic 代表质量因子。这直接对应了资本回报率。pe_ratio > 0,排除了亏损企业,这符合神奇公式寻找“好公司”的初衷。排名逻辑 (rank):
df['pe_ratio'].rank(ascending=True): PE 越小,排名数值越小(例如 PE 最低的排名为 1)。df['roic'].rank(ascending=False): ROIC 越大,排名数值越小(例如 ROIC 最高的排名为 1)。total_score: 两者相加,分数越低,代表综合性价比越高。调仓频率:
run_monthly 设置为每月调仓。虽然格林布拉特建议一年调仓一次,但在 A 股市场波动较大的环境下,月度或季度调仓通常能获得更好的平滑收益。过滤逻辑:
Q: 为什么不完全按照原书使用 EBIT/EV 和 EBIT/(净流动资本+净固定资产)?
A: 在聚宽等量化平台上,直接获取准确的 EV (企业价值) 和净流动资本的历史数据比较复杂,且容易受到财报公布时间滞后的影响。使用 PE 和 ROIC 是业界公认的在 A 股行之有效的简化替代方案,逻辑一致且数据更健壮。
Q: 这个策略适合什么资金规模?
A: 神奇公式通常选取的是基本面较好的股票,流动性尚可。代码中设置了持仓 30 只股票,适合中小资金规模。如果资金量非常大,建议增加持仓数量或增加流动性过滤条件。
Q: 回测时应该注意什么?
A: 建议开启 set_option('use_real_price', True) 以使用真实价格复权。此外,财务数据(get_fundamentals)在回测中会自动匹配当时能看到的最新财报,避免了未来函数,但在分析结果时要注意财报发布的季节性效应。