3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
这是一个基于**本杰明·格雷厄姆(Benjamin Graham)**经典价值投资理论设计的选股策略。
格雷厄姆数字是衡量股票每股内在价值的指标。根据格雷厄姆的理论,防御型投资者应寻找市盈率(PE)低于15且市净率(PB)低于1.5的股票。
公式推导如下:
$$15 \times 1.5 = 22.5$$
$$PE \times PB < 22.5$$
由此得出格雷厄姆数字公式:
$$ \text{Graham Number} = \sqrt{22.5 \times \text{每股收益(EPS)} \times \text{每股净资产(BVPS)}} $$
策略逻辑:
# -*- coding: utf-8 -*-
from jqdata import *
import pandas as pd
import numpy as np
def initialize(context):
"""
初始化函数
"""
# 设定基准为沪深300
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 过滤掉order系列API产生的比error级别低的log
log.set_level('order', 'error')
# --- 策略参数设置 ---
g.stock_num = 10 # 持仓股票数量
g.graham_limit = 22.5 # 格雷厄姆乘数上限 (PE * PB < 22.5)
# 设置交易成本(股票佣金万三,印花税千一)
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# 按月运行,每月第一个交易日进行调仓
run_monthly(trade, monthday=1, time='09:30')
def trade(context):
"""
交易主函数
"""
# 1. 获取初始股票池
# 选取全市场股票
all_stocks = list(get_all_securities(['stock']).index)
# 2. 过滤股票
# 剔除停牌、ST、退市、上市不满60天的次新股
check_out_list = filter_basic_stock(all_stocks, context)
# 3. 获取财务数据并计算格雷厄姆指标
# 查询 PE(市盈率) 和 PB(市净率)
# 这里使用 valuation.pe_ratio (PE-TTM) 和 valuation.pb_ratio
q = query(
valuation.code,
valuation.pe_ratio,
valuation.pb_ratio
).filter(
valuation.code.in_(check_out_list),
valuation.pe_ratio > 0, # 剔除亏损股
valuation.pb_ratio > 0
)
df = get_fundamentals(q, date=context.previous_date)
if df is None or len(df) == 0:
log.warn("未获取到财务数据,跳过本次调仓")
return
# 4. 计算格雷厄姆因子 (Graham Factor)
# 逻辑:PE * PB < 22.5 等价于 股价 < 格雷厄姆数字
# 我们计算 PE * PB 值,值越小代表安全边际越高
df['graham_score'] = df['pe_ratio'] * df['pb_ratio']
# 筛选满足格雷厄姆条件的股票 (PE * PB < 22.5)
df = df[df['graham_score'] < g.graham_limit]
# 按照得分从小到大排序 (越小越便宜)
df = df.sort_values(by='graham_score', ascending=True)
# 取前 N 只股票作为目标持仓
target_list = list(df['code'][:g.stock_num])
log.info(f"本月选股数量: {len(target_list)}")
log.info(f"选股列表: {target_list}")
# 5. 执行交易
do_rebalance(context, target_list)
def do_rebalance(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:
# 等权重分配资金
# 注意:这里使用 total_value 可能会包含停牌股票的市值,更严谨的做法是计算可用资金
# 简单起见,这里使用总资产/目标数量
position_value = context.portfolio.total_value / len(target_list)
for stock in target_list:
order_target_value(stock, position_value)
def filter_basic_stock(stock_list, context):
"""
基础过滤器:剔除停牌、ST、次新股
"""
current_data = get_current_data()
return_list = []
for stock in stock_list:
# 剔除停牌
if current_data[stock].paused:
continue
# 剔除ST
if current_data[stock].is_st:
continue
# 剔除上市不满60天的次新股
# 获取股票上市时间
start_date = get_security_info(stock).start_date
days_listed = (context.current_dt.date() - start_date).days
if days_listed < 60:
continue
# 剔除涨跌停(无法买入或卖出,虽然回测引擎可能撮合,但实盘需剔除)
if current_data[stock].last_price >= current_data[stock].high_limit:
continue
if current_data[stock].last_price <= current_data[stock].low_limit:
continue
return_list.append(stock)
return return_list
PE * PB 而不是直接计算格雷厄姆数字?数学上是等价的。
建议回测时间拉长(例如5年以上),因为价值回归通常需要较长的周期。
Q: 为什么 get_fundamentals 只能获取昨天的数据?
A: 为了避免未来函数。在回测中,当天的财务数据(如市值、PE)通常在收盘后才能确定,因此在盘中交易时只能使用前一个交易日的数据。
Q: 代码中的 set_option('use_real_price', True) 是什么意思?
A: 这开启了动态复权模式。这意味着回测引擎会使用真实的股票历史价格进行撮合,并在分红送股时自动调整持仓数量。这是最接近实盘的模拟方式。
Q: 如果选出的股票少于10只怎么办?
A: 代码逻辑是 df[df['graham_score'] < g.graham_limit]。如果市场整体高估,满足条件的股票可能很少。在这种情况下,策略会自动只买入满足条件的股票,剩余资金会保留为现金。这是符合格雷厄姆“宁缺毋滥”的投资原则的。