跳转至

在这一刻抄底,胜率高达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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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来提供数据。这是一个免费、开源的行情数据源。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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次,分别是什么情况:

1
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函数可以帮我们计算出来:

1
2
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%的跌幅!这就是为什么股谚说多头不死,下跌不止。

如果你不会编程,可以通过下面的表格速查抄底成功概率:

1
2
3
4
5
6
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课》或者《因子分析与机器学习策略》。