简介:一份基于布林通道指标与KD随机摆动指标的股票池量化策略分析文档,适合具备一定量化基础的投资者作为策略设计与回测参考。文档从动态市盈率、每股收益、净利润环比增长率、净资产收益率、流动资产与流动负债差额、经营活动现金净流量六大指标出发构建股票池,并对江山股份、天赐材料、盛新锂能、中矿资源等个股进行基本面分析;随后利用布林与KD指标制定买入卖出择时规则,通过Python实现代码并完成回测参数设置,最终从收益情况、成交情况、夏普比率、索提诺比率、最大回撤等维度解读回测结果。资源包内为一个docx文档,大小约391KB,内容涵盖研究思路、代码注释、回测设置与结果分析,便于直接阅读与二次整理。目前已有326人学习下载,适合希望系统梳理选股、择时与回测流程的读者。
1. 量化策略拆解:拿一份能直接跑出 256% 收益的 BOLL+KD 选股方案
量化策略文档里最容易被低估的,是策略逻辑的自洽性。这篇基于布林指标(BOLL)和 KD 指标的股票池分析,拿到手第一眼感觉是常规双指标组合,但拆完才发现它把「选哪些股票、何时买入、何时卖出」三个环节全部用代码和回测固定了下来,而不是停留在指标科普。文档完整记录了从动态市盈率、每股收益、净利润环比、ROE、偿债能力、现金流六个财务维度筛出 4 只股票的过程,给出了 BOLL 通道与 KD 金叉死叉叠加的精确触发条件,并在聚宽平台跑出 2019 至 2022 年 256.66% 的策略收益。适合已经熟悉聚宽或同类回测框架基础操作、想找一套可复现的选股-择时-回测闭环的读者参考。如果完全没有量化基础,建议先补平台 API 再上手。
2. 股票池构建:六个维度财务指标与四只标的的筛选逻辑
2.1 为什么是这六个维度,而不是只看 PE 和 ROE
选股池是整个策略的地基,原文选股逻辑并不复杂,但维度覆盖比较完整:动态市盈率对应价值,每股收益对应获利能力,净利润环比增长率对应成长性,净资产收益率对应质量,流动资产与流动负债差额对应偿债能力,经营活动现金净流量对应现金流状况。这六类指标实际上是在回答六个不同的问题:公司贵不贵、赚不赚钱、还在不在增长、赚钱质量高不高、会不会爆雷、利润有没有真金白银支撑。
把这些条件组合起来看,筛选标准就变得清晰了:动态市盈率 10 到 40 之间,排除了亏损股和估值过热的标的;每股收益大于 0.3,保证有真实的税后利润;净利润环比增长大于 30%,这是成长性的硬门槛;ROE 落在 15% 到 40% 之间,既要求盈利能力突出,又排除了 ROE 异常高的可疑标的;流动资产大于流动负债是基本的偿债安全线;经营活动现金流入大于流出则避免「有利润没现金」的纸面富贵。这套条件的筛选结果数量不会太多,但每一只都经过多重财务约束,组合本身具备一定的基本面安全垫。
财务指标 | 类型 | 筛选条件 | 筛选意图 动态市盈率 | 价值类 | 10 < PE < 40 | 排除亏损与高估值 每股收益 | 获利能力 | EPS > 0.3 | 保证税后利润为正且可观 净利润环比增长率 | 成长性 | 增长 > 30% | 要求业绩处于扩张期 净资产收益率 | 质量类 | 15% < ROE < 40% | 盈利质量高且不异常 流动资产与流动负债差额 | 偿债能力 | 流动资产 > 流动负债 | 避免短期偿债风险 经营活动现金净流量 | 现金流状况 | 现金流入 > 现金流出 | 排除纸面利润
2.2 选股代码与数据口径:get_fundamentals 的边界
原文的选股代码基于聚宽的 get_fundamentals 实现,这段代码可以直接抄进 notebook 跑一遍。需要注意一个细节:原文 query 里没有显式引入 balance 表,但 filter 里用了 balance.total_current_assets,实际运行时需要把 balance 也加进 query,否则会报字段找不到的错误。修正后的代码如下:
# 导入聚宽函数库 from jqdata import * # 获取满足财务条件的所有股票列表 stocks_data = get_fundamentals( query( valuation.code, # 股票代码 valuation.pe_ratio, # 动态市盈率 valuation.market_cap, # 总市值 indicator.eps, # 每股收益 indicator.roe, # 净资产收益率 indicator.inc_net_profit_annual, # 净利润环比增长率 income.net_profit, # 净利润 cash_flow.subtotal_operate_cash_inflow, # 经营活动现金流入 cash_flow.subtotal_operate_cash_outflow, # 经营活动现金流出 balance.total_current_assets, # 流动资产 balance.total_current_liability # 流动负债 ).filter( valuation.pe_ratio > 10, # 动态市盈率下限 valuation.pe_ratio < 40, # 动态市盈率上限 indicator.eps > 0.3, # 每股收益大于0.3元 indicator.inc_net_profit_annual > 0.3, # 净利润环比增长超过30% indicator.roe > 15, # ROE下限 indicator.roe < 40, # ROE上限 balance.total_current_assets > balance.total_current_liability, # 流动资产大于流动负债 cash_flow.subtotal_operate_cash_inflow > cash_flow.subtotal_operate_cash_outflow # 经营现金流入大于流出 ), date=None # 取最新披露数据 )逻辑说明:query 部分把 valuation、indicator、income、cash_flow、balance 五张表需要的字段都列了出来,filter 部分用七个条件依次收紧。这里的 date=None 表示取系统当前可用的最新财务数据,在实际回测中这个写法需要特别注意,后面避坑章节会专门展开。
参数说明:pe_ratio 的 10 到 40 边界是为了避开亏损股和热门高估值股;inc_net_profit_annual 的 0.3 是 30% 的环比增速,原文用 0.3 而不是 30,是聚宽接口的默认小数格式;roe 上限设为 40 主要是过滤掉财务数据异常的公司。如果想把池子放宽,可以先把净利润环比降到 0.2 看看候选池数量变化,但不建议低于 0.15,否则选出来的股票成长属性会明显减弱。
2.3 从候选池到四只股票:基本面如何落地
经过上述财务条件筛选后,原文最终确定的四只股票是江山股份、天赐材料、盛新锂能和中矿资源。这个结果不是随机挑选,四只都落在当时的高景气赛道:江山股份是农药龙头,受益于粮食安全带来的农药需求提升;天赐材料是锂电池电解液龙头,拥有从上游原材料到电解液成品的一体化产业链;盛新锂能主营锂产品,2021 年归母净利润同比增长 3030.29%;中矿资源从事稀有轻金属原料,2019 到 2021 年营收增速持续走高。四只股票分属农化制品、电池化学品、能源金属、小金属行业,表面看行业分散,实际上都围绕新能源和资源安全两条主线。
股票代码 | 名称 | 所属行业 | 核心选入逻辑 600389.XSHG | 江山股份 | 农化制品-农药 | 除草剂杀虫剂营收占比 60%,盈利增速明显 002709.XSHE | 天赐材料 | 电池-电池化学品 | 电解液龙头,一体化产业链布局 002240.XSHE | 盛新锂能 | 能源金属-锂 | 锂产品生产,2021 年归母净利同比增长 3030% 002738.XSHE | 中矿资源 | 小金属-其他小金属 | 海外固体矿产勘查领先,与盛新锂能合作
这里要提醒一句:四只股票是 2019 到 2022 年时间窗口下的选股结果,行业景气判断有明确的时间背景。现在照着这个思路复现时,不能直接照抄标的,而是应该跑一遍同样的财务筛选,再结合当下的行业景气度人工复核一遍,这才是这套逻辑的正确用法。
2.4 组合画像:55.29% 预期收益背后的高波动
选股完成后,原文用 Python 计算了四只股票近三年的对数年化收益率和波动率,组合的期望收益率是 55.29%,年化波动率 57%。单看收益很诱人,但波动率也是同类策略里偏高的水平,这说明该策略本质上是在用高波动换高收益,并非稳赚型。
从原文给出的相关性数据看,四只股票的相关系数大多在 0.85 到 0.96 之间,呈高度正相关。这意味着所谓「组合分散化」的效果有限,四只股票基本是同涨同跌,一旦行业逻辑被证伪,组合回撤会非常剧烈。原文在投资组合设置里用 80% 资金交易、留 20% 现金做风险管理,就是对这种高波动的对冲手段。做回测参数设置时,这个 80/20 的资金分配是重要前提,不能随意改成全仓。
3. 择时规则:BOLL 通道定趋势方向,KD 指标定进出场时机
3.1 BOLL 指标的通道逻辑与买卖法则
BOLL 指标由三条线组成:中轨是 20 周期移动平均线,上轨是移动平均线加上 2 倍标准差,下轨是移动平均线减去 2 倍标准差。它的核心思想是价格通常运行在通道之内,上轨构成压力,下轨构成支撑,通道宽窄则反映当前波动率的大小。原文的用法是:股价向上突破中轨线时买入建仓,股价上升穿越上轨线时卖出平仓。前者的逻辑是价格从弱势区域回到均值上方,趋势可能由空转多;后者的逻辑是价格冲出上轨后回档概率加大,属于超买离场信号。
在代码里对应的是 Bollinger_Bands 函数,参数取 timeperiod=20、nbdevup=2、nbdevdn=2,也就是标准的 20 日均线和上下 2 倍标准差。这里有一个容易被忽略的细节:该函数返回的 upperband、middleband、lowerband 是字典,需要用股票代码作为 key 去取值,比如 middleband[security],不能直接把整个字典拿来跟 current_price 做比较。
3.2 KD 指标的超买超卖与金叉死叉
KD 指标属于超买超卖型指标,核心是衡量当前收盘价在过去一段时间价格区间中的相对位置。聚宽接口里调用 KD 函数返回 K 值和 D 值,原文参数取 N=18、M1=3、M2=3,也就是说计算周期是 18 日,K 值是 RSV 的 3 日移动平均,D 值是 K 值的 3 日移动平均。相比常见的 9 日参数,18 日周期更慢,K 线和 D 线的交叉信号会更少,但每次信号的有效性相对更高,适合低频的股票池轮动策略。
KD 指标的标准用法是:K 值在 20 附近向上穿过 D 值时形成金叉,买入进场;K 值在 80 附近向下穿过 D 值时形成死叉,卖出离场。金叉发生在超卖区域,说明下跌动能衰竭,反弹概率加大;死叉发生在超买区域,说明上涨动能减弱,回调风险增加。原文在此基础上加了两个边界条件:买入时要求 K 值大于等于 15,卖出时要求 K 值小于等于 80。这个设计比教科书条件更宽松,目的是让信号在强势行情中更容易触发买入、在超买区域仍然保留卖出机会。
3.3 双指标叠加:买入卖出条件的完整触发逻辑
单一指标在震荡行情里经常发出假信号,BOLL 和 KD 组合的目的是用趋势方向过滤掉一部分无效交叉。完整买入条件是三选一的「与」关系:当前股价向上突破中轨线;K 值大于等于 15;同时 K 线上穿 D 线形成金叉。这三个条件同时满足时,用账户可用现金的 80% 买入。这里的关键是 BOLL 中轨负责确认趋势方向,KD 金叉负责确认动能转折点,两者叠加能过滤掉价格仍在低位但 KD 已经钝化的假信号。
卖出条件同样严格:当前股价上升穿越上轨线;K 值小于等于 80;同时 K 线下穿 D 线形成死叉。注意原文卖出用的是 order_target(security, 0),意思是把该股票仓位全部清掉,属于一次性的止盈止损操作,不是分批减仓。这套双指标逻辑的好处是信号不频繁,持仓周期长,交易成本低;坏处是遇到单边大涨行情时,卖出信号迟迟不来,回撤会偏大,这一点在后文的风险分析中会看到具体数据。
4. 聚宽回测实现:策略代码逐段拆解与参数设定
4.1 initialize 初始化:股票池、基准与手续费
策略代码可以整体放进聚宽平台的「我的策略」中运行。initialize 函数负责回测开始前的初始化工作,包括定义股票池、设置基准指数、开启真实价格复权、设定交易费用。股票池直接用列表写死四只股票代码,这是回测可复现的关键一步,不建议在 initialize 里每次动态选股,否则回测结果会混入选股逻辑本身的误差。
# 定义初始化函数 def initialize(context): # 定义股票池:天赐材料、盛新锂能、中矿资源、江山股份 g.stocks = ['002709.XSHE', '002240.XSHE', '002738.XSHE', '600389.XSHG'] # 设置基准参数:以沪深300指数作为比较基准 set_benchmark('000300.XSHG') # 设置动态复权,使用真实价格计算收益 set_option('use_real_price', True) # 设置股票交易手续费:印花税0.1%,佣金0.03%,最低佣金5元 set_order_cost( OrderCost( close_tax=0.001, # 卖出时印花税,单向征收 open_commission=0.0003, # 买入佣金 close_commission=0.0003, # 卖出佣金 min_commission=5 # 单笔最低佣金5元 ), type='stock' ) # 每个交易日运行 trade 函数 run_daily(trade, 'every_bar')逻辑说明:set_benchmark 将沪深 300 指数作为基准,后续回测报告里所有超额收益、贝塔系数都是相对它计算的;use_real_price 设为 True 表示按真实成交价模拟,而不是用前复权或后复权价格,更贴近实际交易;OrderCost 里的 close_tax 只在卖出时收取。手续费参数对回测结果影响很大,如果费率设得太低,回测收益里会混入不切实际的利润。
4.2 trade 主循环:每日轮询与买卖条件判定
trade 函数是策略的核心,每个交易日对股票池中的四只股票依次做判断。先取最近 30 个交易日的收盘价,再计算 BOLL 和 KD 指标,然后逐条检查买入和卖出条件。需要注意,attribute_history 返回的是 DataFrame,取最后一根 K 线收盘价用 close_data['close'][-1],这个索引方式在聚宽里很常见。
# 定义每日交易函数 def trade(context): # 循环遍历股票池中的每只股票 for security in g.stocks: # 获取最近30个交易日的收盘价,用于计算当前价格 close_data = attribute_history(security, 30, '1d', ['close']) # 计算布林通道指标,返回上轨、中轨、下轨三个值 upperband, middleband, lowerband = Bollinger_Bands( security, check_date=context.current_dt, # 当前回测日期 timeperiod=20, # 中轨周期20日 nbdevup=2, # 上轨偏差2倍标准差 nbdevdn=2 # 下轨偏差2倍标准差 ) # 取得最新收盘价作为当前价格 current_price = close_data['close'][-1] # 计算KD指标,返回K值和D值 K1, D1 = KD( security, check_date=context.current_dt, N=18, # 计算周期18日 M1=3, # K值平滑天数 M2=3 # D值平滑天数 ) # 获取账户当前可用现金 cash = context.portfolio.available_cash # 买入条件:价格突破中轨 + K值在低位 + 金叉 + 有现金 if current_price > middleband[security] and K1[security] >= 15 and K1[security] > D1[security] and (cash > 0): # 输出买入日志 log.info("买入 %s" % (security)) # 使用 80% 可用资金买入 order_value(security, cash * 0.8) # 卖出条件:价格穿越上轨 + K值未极端超买 + 死叉 + 有可卖持仓 elif current_price > upperband[security] and K1[security] <= 80 and K1[security] < D1[security] and context.portfolio.positions[security].closeable_amount > 0: # 输出卖出日志 log.info('卖出 %s' % (security)) # 清仓该股票 order_target(security, 0) # 每日交易结束日志 log.info('一天交易完成')逻辑说明:买入用了 order_value(security, cash * 0.8),第二个参数是买入金额而非股数,这样资金利用率更直接;卖出用 order_target(security, 0) 把持仓降到 0,实现全部平仓。这里有一个关键的仓位逻辑:cash > 0判断的是可用现金,而 order_value 用的是可用现金的 80%,也就是说即使账户里只有少量现金,也会全部用于买入,这符合原文「80% 资金交易、20% 资金做风险管理」的设计。
参数说明:Bollinger_Bands 的 timeperiod=20 是主流默认值,nbdevup 和 nbdevdn 都取 2,意味着通道宽度是 2 倍标准差。KD 的 N=18 比标准 9 日参数更慢,交叉信号更少,适合这种低频轮动策略。如果想提高交易频率,可以把 N 改成 9,但要做好信号变多、手续费增加的准备。
4.3 回测参数怎么设:资金、时间、基准与交易费用
回测参数的设置直接决定结果是否可信。原文给出的回测参数是:初始资金 20 万元,回测时间 2019 年 1 月 1 日到 2022 年 5 月 1 日,基准沪深 300 指数,交易费用包括印花税 0.1% 单向征收和佣金 0.03% 双向征收,最低佣金 5 元。这些参数和上文 initialize 里的设置一一对应,可以整理成一张参数表直接照抄。
回测参数 | 设置值 | 说明 初始资金 | 200000 元 | 20 万元本金 回测时间 | 2019-01-01 至 2022-05-01 | 约 3 年零 4 个月 回测基准 | 000300.XSHG | 沪深 300 指数 印花税 | 0.1% 单向 | 仅在卖出时收取 佣金 | 0.03% 双向 | 买入卖出均收取,单笔最低 5 元
这里要特别说明最低佣金 5 元的影响。20 万资金按 80% 买入一笔约 16 万元,佣金约 48 元,不会触发最低 5 元限制;但如果资金量降到 1 万元以下,单笔佣金不足 5 元时按 5 元收取,实际费率会显著上升。小资金跑这套策略时要注意这一点,回测收益和实盘会有偏差。
5. 回测避坑:五个容易翻车的细节
5.1 选股数据时点没卡住,用了未来函数
现象:回测结果异常高,年化收益超过 80%,但盯着净值曲线发现收益集中在某几个交易日突然跳升。原因:get_fundamentals 里 date=None 表示取「当前」最新财务数据,在回测环境中如果当天复权信息更新滞后,会把未来披露的财务数据用到历史交易判断里,形成未来函数。解决:在回测代码中把 date 参数显式设为 context.previous_date,或者用 get_fundamentals 的季度披露日期做对齐,确保选股只使用当时已经公开的数据。
5.2 query 里漏掉了 balance 表,代码直接报错
现象:跑选股代码时提示字段不存在,或者 FieldNotFound 类型错误。原因:原文的 query 里只写了 valuation、indicator、income、cash_flow 的字段,但 filter 条件里用了 balance.total_current_assets 和 balance.total_current_liability,balance 表没有被引入 query。解决:把所有涉及的表都显式加进 query,包括 balance 表,确保每个字段都有对应的表来源,这一点在聚宽接口中特别重要。
5.3 Bollinger_Bands 返回的是字典,不是数值
现象:直接拿 upperband 和 current_price 做比较时报类型错误,或者结果始终为 False。原因:Bollinger_Bands 在传入单只股票时返回的仍是以股票代码为 key 的字典,不能直接把字典和 float 比较。解决:统一用 upperband[security]、middleband[security]、lowerband[security] 取值,再参与条件判断。KD 指标同理,K1[security] 和 D1[security] 才是实际数值。
5.4 卖出判断用 closeable_amount,避免把当日买入误判为可卖
现象:盘中买入当天就被卖出,产生大量虚假交易和手续费。原因:如果判断条件写的是 positions[security].total_amount,会把当天买入但 T+1 不可卖的持仓也算进去。解决:使用 closeable_amount 判断可卖数量,这是聚宽里专门表示当日可卖持仓的字段。原文的卖出条件正是用了 closeable_amount > 0,这个细节值得保留。
5.5 该策略是低频策略,别拿高频标准去衡量
现象:回测报告里交易记录极少,三年半只有几笔成交,容易让人误以为策略失效。原因:BOLL 和 KD 双指标叠加后信号触发条件非常苛刻,大量时间处于持仓不动或空仓观察状态。解决:用低频策略的预期去评估,重点关注长期收益、最大回撤和持仓周期,而不是日均交易次数。成交频率低本身是优势,代表交易成本控制得好,但前提是每一笔持仓的逻辑足够扎实。
6. 学会读回测报告:从 256.66% 收益里看出策略的真实性格
6.1 收益端:钱是哪段时间赚的
回测报告显示策略总收益 256.66%,年化 48.28%,但收益并不是均匀分布的。原文明确指出主要收益来自 2021 年 5 月至 2022 年 5 月这段区间,也就是新能源板块整体走强的阶段。这说明策略的 alpha 很大程度来自股票池本身的高景气度,BOLL 和 KD 择时起到的作用是让组合在大涨阶段始终在场、在下跌初期及时离场,而不是凭空创造收益。
6.2 风险端:五个指标一起看
夏普比率 1.097,意味着每承担一单位总风险能换回约 1.1 单位超额回报;索提诺比率 1.693,说明下行风险控制比总风险控制更好;贝塔 0.934,组合波动略大于基准但基本同步;波动率 40.4%,低于选股时预期的 57%,说明择时确实平滑了部分波动;最大回撤 38.56%,发生在 2021 年 9 月到 2022 年 1 月,正好对应大盘高位回调阶段。五个指标合在一起,这套策略的画像就是:高收益、高波动、但下行风险控制优于普通满仓持有。
6.3 一个验证过拟合的小技巧
把手里的回测报告看明白之后,我一般会再多做一步验证:把回测起点从 2019 年 1 月挪到 2020 年 1 月,再把股票池里的四只股票换成同板块其他标的,比如把天赐材料换成多氟多、把盛新锂能换成赣锋锂业,重新跑一遍回测。如果收益和回撤数据发生剧烈变化,说明策略的收益高度依赖特定标的和特定时间段,属于过拟合;如果收益结构基本相似,只是数值略有浮动,说明策略逻辑本身有稳定性。从那以后我每次拿到回测报告都强制走一遍这套检查顺序——先看收益来自哪个时间段,再看最大回撤发生在什么时候,最后手动换掉股票池做敏感性测试,希望帮到你。
本文还有配套的精品资源,点击获取