问题描述
解决方案
这是一个基于**流动比率(Current Ratio)和速动比率(Quick Ratio)**的财务选股策略。
策略原理
该策略的核心逻辑是寻找短期偿债能力强、财务状况稳健的公司。
- 流动比率 (Current Ratio):计算公式为
流动资产 / 流动负债。它衡量企业偿还短期债务的能力。一般认为该比率在 2:1 左右比较理想。 - 速动比率 (Quick Ratio):计算公式为
(流动资产 - 存货) / 流动负债。它比流动比率更严格,剔除了变现能力较差的存货。一般认为该比率在 1:1 左右比较理想。
策略逻辑
- 股票池:选取沪深300指数成分股(
000300.SS),保证流动性和基本面质量。 - 选股条件:
- 流动比率 > 2.0
- 速动比率 > 1.0
- 根据流动比率从大到小排序,选取前 10 只股票。
- 调仓频率:每月第一个交易日进行调仓。
- 交易逻辑:
- 卖出不在新选出的股票列表中的持仓。
- 买入新选出的股票,按资金等权重分配。
PTrade 策略代码
import pandas as pd
def initialize(context):
"""
策略初始化函数,只在开始时运行一次
"""
# 设置基准为沪深300
set_benchmark('000300.SS')
# 设置手续费:股票买入万分之三,卖出万分之三加千分之一印花税
set_commission(commission_ratio=0.0003, min_commission=5.0, type='STOCK')
# 设置滑点:0.2%
set_slippage(slippage=0.002)
# 定义全局变量
g.index_security = '000300.SS' # 股票池:沪深300
g.stock_num = 10 # 持仓数量
g.min_current_ratio = 2.0 # 最小流动比率
g.min_quick_ratio = 1.0 # 最小速动比率
# 设置按月定时运行,每月第一个交易日的09:35执行选股和调仓
run_daily(rebalance, time='09:35')
def rebalance(context):
"""
调仓函数:每月运行一次
"""
# 判断是否为每月的第一个交易日
# 获取当前日期
current_date = context.blotter.current_dt.date()
# 获取当月所有交易日
month_days = get_trade_days(start_date=current_date.strftime("%Y-%m-01"), end_date=current_date.strftime("%Y-%m-%d"))
# 如果当前日期不是当月第一个交易日,则跳过
if len(month_days) > 0 and current_date != month_days[0]:
return
log.info("开始进行月度调仓...")
# 1. 获取股票池(沪深300成分股)
security_list = get_index_stocks(g.index_security)
if not security_list:
log.warning("获取股票池失败")
return
# 2. 获取财务数据
# 表名:debt_paying_ability (偿债能力表)
# 字段:current_ratio (流动比率), quick_ratio (速动比率)
# 注意:get_fundamentals 查询大量股票时建议分批或确保不超时,此处演示直接查询
q = get_fundamentals(
security=security_list,
table='debt_paying_ability',
fields=['current_ratio', 'quick_ratio'],
date=None, # 默认取回测当日的最新可用数据
report_types=None # 默认取最近一期报告
)
if q is None or q.empty:
log.warning("未获取到财务数据")
return
# 3. 数据清洗与筛选
# 去除空值
df = q.dropna()
# 筛选条件:流动比率 > 2 且 速动比率 > 1
condition = (df['current_ratio'] > g.min_current_ratio) & (df['quick_ratio'] > g.min_quick_ratio)
filtered_df = df[condition]
# 4. 排序与截取
# 按照流动比率降序排列,取前 g.stock_num 只
# 注意:sort_values在pandas旧版本中参数可能不同,这里使用兼容写法
sorted_df = filtered_df.sort_values(by='current_ratio', ascending=False)
target_list = sorted_df.head(g.stock_num).index.tolist()
log.info("本月选出股票: %s" % target_list)
# 5. 执行交易
adjust_position(context, target_list)
def adjust_position(context, target_list):
"""
执行具体的买卖操作
"""
# 获取当前持仓
current_positions = list(context.portfolio.positions.keys())
# 1. 卖出不在目标列表中的股票
for stock in current_positions:
if stock not in target_list:
# 检查是否停牌,停牌无法卖出
if check_stock_tradable(stock):
order_target_value(stock, 0)
log.info("卖出: %s" % stock)
# 如果没有选出股票,则清仓后返回
if not target_list:
return
# 2. 买入目标列表中的股票
# 计算每只股票的目标资金(等权重)
# 使用当前总资产计算,包括现金和持仓市值
total_value = context.portfolio.portfolio_value
position_value = total_value / len(target_list)
for stock in target_list:
if check_stock_tradable(stock):
order_target_value(stock, position_value)
log.info("买入/调仓: %s, 目标市值: %.2f" % (stock, position_value))
def check_stock_tradable(stock):
"""
检查股票是否可以交易(非停牌、非退市、非ST)
这里简单检查停牌状态
"""
# 获取快照数据
snapshot = get_snapshot(stock)
if not snapshot:
return False
# 检查交易状态
# 这里的判断逻辑依赖于 get_snapshot 返回的 trade_status
# 常见不可交易状态:HALT(暂停), SUSP(停盘)
status = snapshot.get(stock, {}).get('trade_status')
if status in ['HALT', 'SUSP', 'STOPT', 'DELISTED']:
return False
return True
def handle_data(context, data):
"""
盘中运行函数,本策略主要逻辑在 run_daily 中,此处留空
"""
pass
代码关键点解析
-
get_fundamentals函数:- 这是获取财务数据的核心 API。
table='debt_paying_ability':指定查询偿债能力表。fields=['current_ratio', 'quick_ratio']:指定需要的两个指标。date=None:在回测模式下,不传 date 默认获取回测当前日期之前已发布的最新财报数据,避免了“未来函数”的问题(即不会用到当时还没发布的财报)。
-
run_daily与日期判断:- 策略使用
run_daily每天运行rebalance函数。 - 在
rebalance内部,通过get_trade_days获取当月第一个交易日,并与context.blotter.current_dt进行比对,实现了月度调仓的功能。这样可以避免因财务数据更新缓慢而导致的每日无效计算和过度交易。
- 策略使用
-
order_target_value:- 使用目标市值下单,系统会自动计算需要买入或卖出的股数,非常适合等权重策略。
-
数据清洗:
- 使用了
dropna()去除数据缺失的股票,确保计算逻辑的稳健性。
- 使用了
常见问题 (Q&A)
Q1: 为什么选择沪深300作为股票池?
A1: 沪深300成分股通常是市值大、流动性好的蓝筹股。对于基于基本面(如偿债能力)的策略,这些公司的数据质量相对较高,且不容易出现因流动性不足导致的无法成交问题。您可以根据需要将 g.index_security 修改为 000905.SS (中证500) 或使用 get_Ashares() 获取全市场股票。
Q2: 财务数据有滞后性吗?
A2: 是的,财务数据是按季度发布的。PTrade 的 get_fundamentals 接口在回测时会严格按照财报的**公告日期(publ_date)**来提供数据。例如,一季报可能在4月底才发布,在4月1日时调用接口,获取到的可能还是去年的年报数据。这是符合真实交易场景的。
Q3: 如何优化这个策略?
A3:
- 增加过滤条件:仅看偿债能力可能选出经营停滞但现金多的公司,可以增加
roe(净资产收益率) 或net_profit_grow_rate(净利润增长率) 来确保公司具有成长性。 - 行业中性化:不同行业的流动比率标准不同(例如零售业和制造业),可以在行业内部进行排名选股。
- 止损机制:在
handle_data中加入基于价格的技术止损逻辑。