你有没有在盘中盯过这样的股票:明明封死了涨停,封单却薄得像层窗户纸,几千手一砸就开;价格刚滑下来,又有资金冲进去抢筹,把它重新拉到板上。如此反复三五次,分时图就像一条被反复折叠的折线。懂行的人管这种情况叫"烂板"。在量化交易圈子里,围绕烂板衍生出来的策略,是一类非常特殊的短线高频交易玩法——它不研究基本面,不看均线金叉死叉,只盯着涨停板被砸开和回封的瞬间,捕捉资金从分歧走向共识的那一段利润。
这篇文章围绕烂板策略,把从底层逻辑、数据准备、因子构造、Python代码实现,到回测陷阱和实盘风控的完整链路讲一遍。我默认看这篇内容的你,已经懂Python基础,接触过pandas和向量化回测,但对涨停板制度下的短线博弈生态还不一定熟悉。内容会比较细,尤其是那些回测曲线不会告诉你的坑,我会重点讲。
1. 烂板策略不是玄学:涨停反复打开的博弈本质
1.1 烂板到底在交易什么:分歧与换手的底层逻辑
先说清楚"烂板"的定义。一只股票盘中触达涨停价,但没能在涨停价上一直封住,中间被大单砸开、价格回落到涨停价以下,之后再被买盘重新拉回涨停——这个过程如果发生两次以上,这只股票当天就是"烂板"。
涨停板制度把单日最大涨幅锁死,所以涨停本身意味着这只股票当天已经走到"最强"的位置。但"最强"并不等于"最一致"。恰恰相反,涨停价的每一次打开,都是多空双方在极限定价位置上的直接碰撞。砸板的人觉得这个价格已经透支了明天的空间,回封的人认为这只是一个中途站。一次打开,就是一次分歧的释放。
反直觉的地方来了:**烂板往往比一字板更健康。**一字板虽然看着最强,但全程没有换手,前一天低位买入的人全部浮盈,第二天一旦打开,这些获利盘的抛压会挤在一起释放,反而容易做出大面。而烂板在当天就把该走的获利盘洗出去了一部分,筹码在涨停价附近完成交换,持仓成本被抬高。次日继续上攻时,抛压相对更小,筹码结构更扎实。
这个策略交易的,其实就是"分歧之后能否转一致"的预期差。量化交易做烂板策略,本质上是把这个行为抽象成可计算的特征,用规则去寻找那些"分歧充分、但共识正在重新形成"的标的。
1.2 烂板不等于弱势:几个经典细分玩法的差异
同样是烂板,性质完全不同。按资金意图和市场位置,可以分出至少三类玩法:
| 细分玩法 | 典型形态 | 核心买点 | 适用环境 |
|---|---|---|---|
| 弱转强 | 昨日烂板或炸板,今日竞价高开且早盘放量走强 | 竞价高开确认后或早盘分时突破时 | 情绪修复期、冰点反抽期 |
| 烂板回封 | 当日反复开板,尾盘放量重新封板 | 尾盘封板时或次日的溢价 | 情绪上升期,赚钱效应较好时 |
| 炸板低吸 | 当日炸板未回封,次日缩量止跌 | 次日分时企稳、不破前日低点时分批进 | 个股有独立逻辑或处于主升中段 |
这三类玩法,风险收益特征差异很大。弱转强是胜率相对高但买点难抓的,要求你必须在竞价或开盘后的极短时间内做出决策;烂板回封是确定性最高但介入时间偏晚的,你看到封板那一刻,其实已经失去了相对最优的成本位置;炸板低吸则是赔率最大但最容易买在半山腰的,赌的是反包。
量化落地时,我建议先选一个细分方向做深。不要指望一套因子同时覆盖三种玩法,它们的持仓周期、止损逻辑和入场时机完全不同。混合在一起,参数优化会让你痛不欲生。
1.3 为什么人工打板高手最终会转向量化实现
烂板策略最早是游资和打板族的"盘口绝活",靠的是盯盘经验。但人工盯盘有几个天然瓶颈:其一,全市场每天触板、炸板的股票经常有几十上百只,一个人能同时盯住5只就不错了;其二,情绪判断高度依赖盯盘者当时的状态,连续亏损后容易动作变形;其三,人工交易的延迟是秒级的,而烂板策略的入场窗口往往是以分钟甚至秒来计算的。
量化实现解决的就是这三件事:覆盖范围从5只扩大到全市场,执行纪律从"人治"变成"代码治",决策延迟从几秒压缩到毫秒级。但这也带来新的问题——数据是否支持分钟级判断、回测是否会自欺欺人、实盘能否应对极端波动。这些我后面逐个展开。
2. 数据是烂板策略的地基:API选型与分钟级数据的坑
2.1 日线数据做不了这个策略,1分钟是最低配置
有人问过我,用tushare的日线接口能不能做烂板策略。答案是能做一个非常粗糙的版本——你只能知道这只股票当日涨停过、也炸板过,但不知道首封时间是几点,不知道炸板炸了几次,更不知道回封发生在尾盘还是午后。而这些恰恰是烂板策略最核心的信息。没有日内数据,策略等于盲人摸象。
对烂板策略来说,1分钟K线是最低配置。首封时间、炸板次数、回封时间、封单变化,这些因子都能从1分钟级别的数据里提取出来。如果资金和存储条件允许,5秒快照或逐笔委托数据会更好,能精确算出封单金额的堆积速度、撤单率、不同价位的挂单分布,但这些属于进阶选项,先不做硬性要求。
2.2 数据API怎么选:研究环境、回测环境、实盘环境是三个问题
很多新手经常问"量化交易用的数据API最好的是什么"。我的回答一贯是:不存在一个API通吃所有场景,你至少要面对三个不同问题——研究环境、回测环境、实盘环境。
研究环境:需要的数据宽、覆盖广、方便做因子分析。我常用akshare和tushare pro配合。akshare免费但接口稳定性一般,适合抓取小批量数据做验证;tushare pro有积分体系,分钟数据和更细的历史数据需要积分门槛,但数据质量相对靠谱。
回测环境:需要历史数据与撮合模型一致。聚宽、米筐这类平台自带完整的历史行情和回测引擎,支持分钟级回测,对新手最友好,不需要自己维护数据库。缺点是策略写完后要迁出到其他环境时,代码耦合度较高。
实盘环境:券商端的使用逻辑和研究端完全不同。ptrade是不少券商提供的量化交易终端,支持Python语言,可以对接实盘交易接口,支持盘中定时运行策略,适合把策略放到真实账户里跑。
我的常规组合是:研究阶段用akshare和tushare做因子验证,回测在聚宽或自建向量化框架里完成,实盘走券商提供的ptrade环境或自己用官方交易接口对接。三者之间做好数据口径的一致性校验,避免出现"研究用前复权、回测用不复权"这种低级事故。
2.3 涨停价与炸板识别的细节,数据预处理里的隐形地雷
很多人以为判断涨停很简单,用收盘价等于涨停价就行。其实远没这么简单,这里我踩过很多次坑。
涨停价的计算规则:主板10%、创业板和科创板20%、ST股5%。计算公式是round(前收盘价 * (1 + 涨跌幅限制), 2),保留两位小数,遵循四舍五入。但注意,不同行情软件对四舍五入的边界处理偶尔会有细微差异,所以在判断涨停时,我建议用一个容差而不是绝对相等:high >= limit_price - 0.001。
炸板的判断逻辑也不复杂:在1分钟K线上,如果某根K线的最高价触及涨停价,但随后某一根K线的收盘价(或最低价)跌落涨停价下方,就算发生了一次炸板。但从"触及涨停"到"重新封板"之间,反复穿越的边界条件要写清楚,否则炸板次数会统计错。
这里有一个关键的预处理问题:判断涨跌停必须使用不复权数据。复权数据会改变历史价格和涨停价的计算基准,导致信号错乱。数据接口默认往往给的是前复权数据,拿到后要先做转换。此外,除权除息日的涨停价会基于新的前收盘计算,如果数据里没有除权除息标记,很容易把正常波动误判为炸板。
3. 把盘口语言翻译成量化因子:烂板策略的信号构造
3.1 首封时间、炸板次数、回封时间:三个核心时间因子
数据准备好之后,第一步是把盘口语言翻译成因子。我按重要性排序,先讲三个时间类因子。
首封时间,即当日第一次触及涨停的时刻。这个因子反映的是资金做多意愿的强烈程度。一般来说,开盘后30分钟内就触及涨停的股票,说明有资金在早盘竞价和开盘抢筹阶段就果断动手,做多意愿较强。但如果一只股票在连续大涨后出现"秒板",反而可能是加速赶顶的信号,次日断板风险极大。所以首封时间要与股票所处的涨幅位置结合来看。
炸板次数,是最能体现分歧程度的因子。炸板次数越多,说明多空双方的拉锯越剧烈,筹码交换越充分。但炸板次数也不是越多越好——反复炸板超过5次还没有彻底封住,说明卖方力量仍然很强,这种板即使尾盘勉强封住,次日也容易继续分歧。通常来说,2到4次炸板的标的,后续走强的概率相对较高。
回封时间,即最后一次封住涨停的时刻。这个因子的意义在于判断资金在尾盘阶段的表态。如果回封发生在早盘,说明多方快速重新掌控局面;如果回封拖到14:30以后,说明全天大部分时间都在拉锯,尾盘才勉强压制住空方。不同回封时间对应的次日预期差异很大,这在实盘中有明显的统计规律。
3.2 封单强度与换手率:让因子具备"量"的维度
时间因子回答的是"什么时候发生",量类因子回答的是"以多大力度发生"。
封单金额占流通市值比例,是我觉得最有效的一个量类因子。收盘时买一涨停价位上挂着的封单金额,除以该股流通市值,得到的比例能直观说明资金的封板决心。比例越高,次日被砸开的概率越低。这个因子尤其适合区分"真封板"和"假封板"——有些股票盘中封单只有几百万,这种板就是纸糊的,一碰就碎。
涨停前的换手率也很关键。它衡量的是资金在涨停之前已经完成了多少筹码交换。换手充分说明浮筹清洗得干净,次日抛压小;换手不足则说明还有很多获利盘没走,次日一旦高开就会有卖压。我一般用开盘到首封时刻之间的成交量,除以流通股本,作为"封板前换手率"。
当日成交额相对近20日的分位数,这个因子用来过滤放量程度。烂板策略本质上需要量能配合,缩量的烂板要么是流动性枯竭,要么是主力自导自演的对倒,都不适合参与。成交额分位在80%以上的标的,参与价值更高。
3.3 市场情绪过滤器:烂板策略的天气系统
同一只烂板股,放在不同的市场环境下,次日的表现可能天差地别。这就是市场情绪过滤器存在的意义。它不直接预测个股涨跌,而是做一道"该不该在今天开仓"的总开关。
我常用的情绪指标有三个:昨日涨停股今日的平均涨幅(衡量打板资金的赚钱效应)、市场连板高度的中位数(衡量市场对高位股的容忍度)、当日盘中曾涨停但最终炸板的个股比例(衡量市场承接力)。这三个指标合成一个"情绪温度",温度高于阈值时才允许开仓,低于阈值则强制空仓或大幅降低仓位。
这个过滤器的价值,在烂板策略上体现得尤其明显。烂板策略本身是逆着"表面弱势"去低吸或打回封,如果市场环境不好,资金接力意愿弱,烂板次日大概率直接低开低走,策略会连续吃面。我实测下来,加上情绪过滤之后,最大回撤能减少30%以上,虽然会错过一部分机会,但对实盘的体验提升是决定性的。
4. Python实战:一个可落地的烂板策略骨架与回测
4.1 数据获取与涨停价计算代码
下面给出一套可以直接改着用的代码骨架。数据获取我以akshare为例,接口可能因版本更新而变化,使用时以官方最新文档为准。
import akshare as ak import pandas as pd import numpy as np def get_1min_data(symbol, date): """ 获取单只股票某一天的1分钟K线 """ df = ak.stock_zh_a_hist_min_em( symbol=symbol, period="1", start_date=f"{date} 09:30:00", end_date=f"{date} 15:00:00" ) if df is None or df.empty: return None df.rename(columns={ "时间": "time", "开盘": "open", "收盘": "close", "最高": "high", "最低": "low", "成交量": "volume" }, inplace=True) df["time"] = pd.to_datetime(df["time"]) return df[["time", "open", "close", "high", "low", "volume"]] def calc_limit_price(prev_close, board_type): """ 计算涨停价 board_type: 'main' 主板 10%, 'gem' 创业板/科创板 20%, 'st' 5% """ if board_type == "main": ratio = 0.10 elif board_type in ("gem", "star"): ratio = 0.20 elif board_type == "st": ratio = 0.05 else: raise ValueError("unknown board type") return round(prev_close * (1 + ratio) + 1e-8, 2)需要注意,akshare返回的时间列默认含日期,我在这里只取了当天的分钟数据。实际做全市场扫描时,你要先去获取一份每日的涨停股票池,再对池中标的逐只拉分钟线,而不是对全市场几千只股票全量拉取,否则接口压力和数据量都吃不消。
4.2 烂板识别与因子计算代码
拿到分钟K线后,核心任务是判断炸板并计算因子。这里给出一个核心函数:
def identify_rotten_board(df, limit_price): """ 从分钟K线中判断烂板,并提取核心因子。 返回dict,包含首封时间、炸板次数、末封时间、尾盘回封标志等。 """ if df is None or len(df) < 10: return None # 触及涨停:最高价在涨停价±0.001容差内 touch_limit = df["high"] >= limit_price - 0.001 # 封住涨停:收盘价在涨停价±0.001容差内 sealed_limit = df["close"] >= limit_price - 0.001 # 找到所有触及涨停的连续片段 touch_idx = np.where(touch_limit)[0] if len(touch_idx) == 0: return None # 按连续区间分组 groups = [] start = touch_idx[0] prev = touch_idx[0] for i in touch_idx[1:]: if i == prev + 1: prev = i else: groups.append((start, prev)) start = i prev = i groups.append((start, prev)) # 首封时间 = 第一个触及涨停片段的起始时间 first_seal_idx = groups[0][0] first_seal_time = df.iloc[first_seal_idx]["time"] # 每个片段结束时是否封住,以及片段之间是否出现收盘价脱离涨停 break_times = 0 for k in range(len(groups) - 1): seg_end = groups[k][1] next_start = groups[k + 1][0] # 如果前一段最后不是封住状态,且两段之间有回落,视为一次炸板 if not sealed_limit.iloc[seg_end] and (next_start - seg_end > 1): break_times += 1 # 末封时间 = 最后一个触及涨停片段的结束时间 last_seal_time = df.iloc[groups[-1][1]]["time"] return { "first_seal_time": first_seal_time, "break_times": break_times, "last_seal_time": last_seal_time, "last_sealed": bool(sealed_limit.iloc[-1]) }这个实现是简化版,但主体逻辑是可靠的。有一点我要特别提醒:"封住"在分钟线上只能近似判断。真正的封板状态应该看涨停价上的委托量(封单),那是tick级数据才有的信息。分钟线用收盘价是否在涨停价上来近似,已经足够提取大部分有效特征。
4.3 交易信号与次日执行的规则设计
因子算完后,就该定义交易信号了。以"烂板回封"和"弱转强"两个方向为例,简单说明信号的合成方式。
当日盘后,全市场扫描得到所有烂板标的,按以下条件过滤:
- 首封时间在10:00之前
- 炸板次数在2到5次之间
- 末次回封时间在14:00之后,且尾盘处于封板状态
- 封单金额/流通市值 > 0.5%
- 当日成交额分位 > 80%
- 排除ST、新股、上市不足60日、停牌股
次日策略按两种情况执行:
- 如果次日竞价高开0%到5%之间,说明市场认可昨天的烂板回封,开盘价附近建仓
- 如果次日竞价低开超过3%,则放弃,避免承接弱势
卖出条件我采用时间止损与价格止损结合:买入后持有不超过3个交易日,若期间最大浮盈达到8%以上,则在分时走弱时止盈;若跌幅超过5%则无条件止损。这个规则不复杂,但执行纪律必须严格。
4.4 回测中必须模拟的"买不进"与"卖不出"
回测框架里最容易被忽略的,是涨跌停制度对成交的约束。烂板策略买入的对象全是涨停过的股票,而涨停股次日经常直接高开甚至一字板——你挂单买不进是常态。如果回测假设"第二天开盘价一定能买到",结果会显著偏乐观。
我推荐的保守做法是:如果次日开盘价直接封涨停(即开盘价等于涨停价),默认本次信号无法成交,直接跳过。如果开盘价高开但未涨停,则假设以开盘价成交,同时考虑一个固定的滑点比例。这样做会牺牲一部分收益,但换来的回测结果与实盘的一致性,远远好于理想化模型。
卖出端同理。如果持仓期间股价触及跌停,不能假设你一定能卖出去,保守处理是等到跌停打开后的价格再卖,或者直接以次日收盘价为准。这些约束加上之后,你会发现原本漂亮的多因子策略,年化收益可能打个七折。这才是真实市场。
5. 回测曲线会骗人:烂板策略的过拟合与成本陷阱
5.1 样本少、参数多,烂板策略天生容易过拟合
烂板策略和趋势策略有一个本质区别:样本量。趋势策略一天能在几千只股票上产生信号,烂板策略一年下来真正有效的信号可能只有几百个。样本少,参数一多,就很容易把回测曲线调得无比漂亮,但每一条"漂亮"都是对历史噪声的精确拟合,而不是对市场规律的捕捉。
我自己的经验是,这类策略的参数越少越好。宁可牺牲一点回测的完美度,也要保证每个参数背后都有一条清晰的经济逻辑支撑。如果一个参数你不能用一句话解释清楚它捕捉了什么市场行为,那它就不该出现在模型里。
做参数敏感性测试也很有必要:把关键参数上下浮动20%,观察回测结果是否大幅恶化。如果稍微一动参数,收益就大幅滑坡,说明策略已经过拟合了。稳健的参数应该在一个合理区间内都能表现出稳定的收益特征。
5.2 幸存者偏差、未来函数和除权日的连环坑
统计上的坑,一个比一个隐蔽。幸存者偏差是最常见的:回测时如果使用当前时点的股票列表去拉历史数据,那些已经退市的股票就不会出现在样本里,但历史上它们确实存在,而且其中有大量是烂板策略的失败案例。正确的做法是使用历史时点的成分股数据快照,这个在tushare和聚宽上都有专门的接口。
未来函数是另一个重灾区。我在早期版本里干过一件蠢事:用当日全天的分钟数据来识别"烂板回封",然后假设当日尾盘封板那一刻就买入,这本身没问题,问题在于我同时把当天的封单金额数据也作为次日的买入依据——但封单金额是收盘后才知道的数据。虽然只差一个交易日,但在高频场景下,这种未来数据会让回测结果失真严重。
还有除权日的问题。前复权数据在除权日会调整历史价格,导致涨停价的计算依据漂移。我建议做因子计算时统一用不复权数据,每次计算涨停价都从当天实际的前收盘价出发,不使用复权后的连续价格序列。
5.3 手续费与冲击成本:高频换手下被低估的杀手
烂板策略的持仓周期通常只有1到3天,年化换手率轻松超过2000%。在这种换手率下,交易成本对收益的侵蚀是致命的。
算一笔账:A股佣金双边按万2.5计算,卖出印花税按万5计算(2023年8月起减半后的水平),再加上滑点,单次完整交易的总成本至少在万12到万25之间。如果按每月换手500%计算,一年下来交易成本就能吃掉总资金的6%到12%。对年化收益目标在20%到40%的策略来说,这已经是非常大的损耗了。
所以回测中手续费和滑点一定要从紧设置。我通常用单边千1.5的手续费加滑点模型做底线压力测试,只有在这个成本假设下仍然盈利的策略,才有上实盘的价值。另外,冲击成本也值得关注,烂板标的通常是中市值股票,买一和卖一的挂单量有限,大资金进场时冲击成本不可忽视。个人资金的容量上限通常在三百万到一千万之间,超过这个区间,信号会自我衰减。
6. 实盘执行才是分水岭:仓位、止损与策略失效监测
6.1 开盘三十秒决定收益,执行速度与滑点控制
烂板策略最关键的执行窗口,在次日开盘的30秒内。竞价一结束,全天最优的介入价格基本就确定了。人工下单往往要经过"看一眼行情、填价格、点确认"这个流程,少说三四秒,遇到网络波动可能十几秒就过去了。量化下单可以把这个过程压缩到毫秒级,但这需要你预先写好交易脚本,并提前把仓位的预算和标的清单准备好。
我建议用限价单而不是市价单。市价单虽然成交快,但在开盘瞬间流动性不足时,很容易吃到极端价。限价单可以设定一个可接受的最高买入价,比如"开盘价上方0.5%以内",挂上去之后让它排队成交。如果价格迅速脱离你的限价区间,说明这波行情比预期强,放弃就是了,没有必要追。
6.2 单票仓位与组合约束:烂板策略不是满仓梭哈
烂板策略的胜率普遍只有30%到45%,比大多数人想象的低得多。它赚钱靠的不是高胜率,而是大赚小亏的盈亏比——对了能赚10%以上,错了只亏4%到5%。在这种胜率分布下,单票梭哈基本等于自杀,因为你根本扛不住连续5次亏损。
我实盘的仓位规则是:单票最大仓位10%,同时持仓不超过5只;单日新开仓总仓位不超过30%;如果情绪过滤器显示市场温度下降,单日新开仓总仓位降档到10%以内。这套规则不是为了博收益,而是为了保证在连续亏损的情况下,账户依然有充分的子弹可以打。
另外,买入前一定要检查标的的流动性。日均成交额低于5000万的股票,即使信号再完美也要放弃。这个约束会过滤掉一部分机会,但能避免你在极端行情中被套在里面出不来。
6.3 策略失效的识别与退出机制
所有短线策略都有生命周期,烂板策略也不例外。市场参与者结构变化、交易制度调整、同质化资金涌入,都会让一个曾经盈利的策略逐渐失效。关键是你要有一套量化的失效监测机制,而不是靠感觉。
我的做法是每月做一次滚动评估:把策略近30天的实盘收益和同期回测收益对比,如果连续两个月的实盘收益比回测收益低20%以上,就要开始排查——是参数失效了,还是市场风格切换了,还是执行环节出了问题。如果连续三个月跑不赢基准模型,不管回测曲线上过去多漂亮,我都会直接停止该策略,而不是抱着侥幸心理继续开仓。
比起在失效的策略上死扛,更务实的做法是同时维护多个低相关性的子策略,比如"烂板回封"和"炸板低吸"各跑一套信号,彼此之间严格独立。当一个策略进入观察期时,另一个还能继续贡献收益,账户整体不至于出现断崖式回撤。
我做烂板策略最大的体会是:这个策略真正的门槛不在回测里,而在实盘中。回测曲线上的每一个参数背后,都是真金白银的盘中决策。如果你没有一套严格的仓位管理、止损纪律和失效退出机制,哪怕模型再先进,也会被连续亏损时的情绪冲动摧毁。建议新手先用模拟盘跑三个月,老老实实记录每一笔信号的执行情况,先把纪律练出来,再考虑上真金白银。