在这一刻抄底,胜率高达95%
在4月8日,我们发表了一篇名为《1赔10,3月27日我抄底了》的文章,基于坚实的统计数据,说明了为什么当天应该抄底。时间过去了半年,中证1000又为我们提供了两个新的例证。这篇文章我们就来回顾一下。
原理和定义
我们先介绍一下原理。你可能观察到,当发生一段连续下跌时,那么下跌的幅度越大,则反弹的力度就越大,并且越可能发生反弹。这就像弹簧一样,或者像是蹦极 -- 在连续下跌到某个点之后,总会往回弹一点。
但我们需要求出连续下跌到什么程度时,反弹的概率会是多大。这样一来,我们的操作才有更有底气和依据。
首先需要定义什么是连续下跌。实际上,我们可以像前文所述,使用每日涨跌。但在这篇文章里,我们想尝试另一种方法,即通过连续阴线区间的跌幅来定义,这样可以过滤一些假信号。
具体的计算方法如下图:

我们先看图中序号1到序号2这一段。这是10月8号到10月11日的走势。10月8日这一天跳空高开,接着一路下跌直到10月11日,8号开盘买入的人,到11日收盘时,亏损达到15.8%。这个损失,就是我们所说的连续阴线区间的跌幅。
我们再看序号3到序号4这一段。这是11月14日到11月18日的情况,14日开盘买入者共亏损7.5%,连续阴线区间的跌幅就是7.5%。
如果我们使用连续下跌(而不是连续阴线的区间跌幅)来计算最大跌幅,将会是从11月12日起开始计算下跌,但提前一天(11月15日)就可能给出抄底信号,这个信号就有点过早。因为在这区间中,出现了一天的假阳线(13日,下跌但收阳线)。这一天筹码发生交换,假设前一日亏损者把筹码全部倒给了新入场者,那么活跃交易者的持仓成本是下降的,他们还能再扛一阵子。
这是我们这次改进中,使用连续阴线的区间跌幅的原因。但实际上两种定义各有优劣,你可以使用机器学习模型来决定何时使用哪一个。
代码实现
既然模型定义清楚,现在我们就开始实现。这段代码需要用到发现连续阴阳线的一个函数,我们把它定义为find_runs:
def find_runs(x):
"""Find runs of consecutive items in an array.
"""
# ensure array
x = np.asanyarray(x)
if x.ndim != 1:
raise ValueError("only 1D array supported")
n = x.shape[0]
# handle empty array
if n == 0:
return np.array([]), np.array([]), np.array([])
else:
# find run starts
loc_run_start = np.empty(n, dtype=bool)
loc_run_start[0] = True
np.not_equal(x[:-1], x[1:], out=loc_run_start[1:])
run_starts = np.nonzero(loc_run_start)[0]
# find run values
run_values = x[loc_run_start]
# find run lengths
run_lengths = np.diff(np.append(run_starts, n))
return run_values, run_starts, run_lengths
为了让大家都能复现代码,我们使用了akshare来提供数据。这是一个免费、开源的行情数据源。
import akshare as ak
now = datetime.datetime.now().date()
start = now - datetime.timedelta(days=365*4)
start_date = start.strftime("%Y%m%d")
end_date = now.strftime("%Y%m%d")
# 通过akshare获取中证1000日线数据(近1000天)
bars = ak.index_zh_a_hist(symbol="000852", start_date=start_date, end_date=end_date)
bars.rename(columns = {
"日期": "date",
"开盘": "open",
"最高": "high",
"最低": "low",
"收盘": "close",
"成交量":"volume"
}, inplace=True)
bars["date"] = pd.to_datetime(bars["date"])
bars.set_index("date", inplace=True)
bars["flag"] = np.select([bars["close"] > bars["open"],
bars["close"] < bars["open"]],
[1, -1],
0)
v, s, l = find_runs(bars["flag"] == -1)
cum_neg_returns = []
for vi, si, li in zip(v, s, l):
if vi and li > 1:
cum_neg_returns.append((bars.index[si],
bars.index[si + li-1],
bars.close[si + li -1 ]/bars.open[si] - 1))
r = pd.DataFrame(cum_neg_returns, columns=["start", "end", "cnr"])
r.cnr.hist()
从生成的直方图来看,连续跌幅达到7.5%以后的次数就很少了,也就是,连续下跌超过7.5%,还能继续下跌是小概率事件。
我们看一下连续下跌最厉害的10次,分别是什么情况:
r.nsmallest(10, "cnr").sort_values("end", ascending=False)
| start | end | cnr | |
|---|---|---|---|
| 107 | 2024-11-14 | 2024-11-18 | -0.075433 |
| 106 | 2024-10-08 | 2024-10-11 | -0.158511 |
| 89 | 2024-03-21 | 2024-03-27 | -0.071948 |
| 88 | 2024-01-26 | 2024-02-05 | -0.190592 |
| 87 | 2024-01-19 | 2024-01-22 | -0.067846 |
| 78 | 2023-10-16 | 2023-10-23 | -0.074109 |
| 45 | 2022-09-15 | 2022-09-19 | -0.066983 |
| 37 | 2022-04-20 | 2022-04-26 | -0.170335 |
| 34 | 2022-03-14 | 2022-03-15 | -0.066983 |
| 33 | 2022-03-03 | 2022-03-09 | -0.086669 |
我们看到2024年就发生了5次,分别是1月22日、2月5日、3月27日、10月11日和最近的11月18日。其中2月5日和10月11日这两次下跌幅度很大,后面的反弹也就很大,果然是风浪越大鱼越贵!
1月22日这次能出现在表中非常意外,毕竟这一次只下跌了两天。但是,随后也确实出现了一波持续3天的小反弹,涨幅超过6.1%,还比较可观。
底该怎么抄?
在11月8日收盘时,阴线连续下跌幅度为7.54%,盘中跌幅更大,所以,在盘中就出现了连续跌幅达到7.54%的情况。如果此时你决定抄底,成功的概率有多大?
有一个奇怪的pandas函数可以帮我们计算出来:
decline_ratio = -0.075433
r.cnr.le(decline_ratio).mean()
它的奇妙之处在于,le用来找出小于等于decline_ratio的数据,把它们标记为true,其它的标记为false,然后mean用在bool变量上,会求出真值的比例,也就是我们要找的概率!
这个概率是4.63%。如果你在此时抄底,那么还有4.63%的概率,你需要忍受继续下跌,这就是发生在10月9日的情况。按照概率的提示,你大概会在10月9日的盘中杀进来,然后要忍受此后两天的继续下跌,这个跌幅跟你之前看到的差不多(也是7.5%左右)。
不过,好消息是,如果你在10月9日杀进来,你有在10月10日选择小幅盈利出局的权利。如果你在这一天选择出局,把筹码倒给了新入场的人,他们还可以再抗7.5%的跌幅!这就是为什么股谚说多头不死,下跌不止。
如果你不会编程,可以通过下面的表格速查抄底成功概率:
data = []
for loss in np.linspace(-0.06, -0.076, 15):
data.append((loss, 1- r.cnr.le(loss).mean()))
df = pd.DataFrame(data, columns=['最大亏损', '抄底胜率'])
df.style.format("{:.1%}")
| 最大亏损 | 抄底胜率 | |
|---|---|---|
| 0 | -6.0% | 87.0% |
| 1 | -6.1% | 87.0% |
| 2 | -6.2% | 87.0% |
| 3 | -6.3% | 87.0% |
| 4 | -6.5% | 88.0% |
| 5 | -6.6% | 89.8% |
| 6 | -6.7% | 90.7% |
| 7 | -6.8% | 93.5% |
| 8 | -6.9% | 93.5% |
| 9 | -7.0% | 93.5% |
| 10 | -7.1% | 93.5% |
| 11 | -7.3% | 94.4% |
| 12 | -7.4% | 94.4% |
| 13 | -7.5% | 95.4% |
| 14 | -7.6% | 96.3% |
再往后胜率不变(因为数据量少),所以就没有列出了。在实际操作中,可以从-6%之后开始,使用马丁格尔交易法。
tip
我在这里没有使用最高价和最低价。这两个价格的稳定性不如收盘价与开盘价(即成交量少)。但你也可以试试。百闻不如一练。我讨厌读那些看上去很美好,但无法验证的文章。很多时候,读这些文章只是在浪费时间,因为你都不知道哪句是真的,反正都无法验证。
同往常一样,这篇文章同样提供可运行的代码。你只要登录Quantide Research平台,就可以运行本文的代码验证我们作出的结论,然后选择下载本文代码,持续跟踪连续下跌引起的反弹信号。
加入星球,就可以拿到门票。在星球(及Quantide Research平台)里,我们已经发布了可alpha101因子库(可运行)、5个年化超过15%的因子,还有三角形整理检测等代码。未来将继续以更新公众号的频率,持续同步发布笔记相关代码。
如果你不明白这里概率计算的原理,或者想为自己打下坚实的量化基础,可以考虑选修《量化24课》或者《因子分析与机器学习策略》。