一句话结论:ES 日盘的日 K 线,任意一天之后第二天收阳的概率是 55.0%;连涨 2 天以上之后是 54.6%,几乎一样,“涨了还会涨”在日线上不成立。
连跌 2 天以上之后,第二天收阳 57.4%,比平时高 2.4 个百分点。有一点反弹的倾向,但差距在误差范围之内,还不能说它一定存在。
作用:不要因为已经连涨或连跌了几天,就觉得明天一定继续或者一定反转;单看连续几根,日线和周线都接近抛硬币,只是整体偏多(因为 ES 长期在涨)。
看到连涨三天,很多人会觉得”该回调了”;看到连跌三天,又觉得”该反弹了”。也有人反过来,觉得涨了还会涨。
我用 ES 2010–2026 年的数据,只问一个纯统计的问题:连续 N 根阳线(阴线)之后,下一根收阳的概率是多少?不看指标,不看位置,只看 K 线的颜色。日线和周线都算,K 线分日盘和全天(24 小时)两种。
2010–2026 年 · 连跌 2 根以上之后,下一根收阳
57.4%
样本 712 次 · 不看前面 55.0% · 连涨 2 根以上之后 54.6%
连续 N 根之后,下一根收阳的概率横轴是连续几根。圆点是比例,竖线是 95% 的区间(样本越少越长);虚线是不看前面、任意一根之后收阳的比例。样本不到 20 次的画成浅色。
怎么读这张图:竖线是 95% 区间,意思是按这么多样本,真实的比例大概率落在这根竖线的范围里,样本越少竖线越长。只有整根竖线都在虚线的同一边,才能说这种情况和平时不一样;竖线跨过虚线,差别就可能只是运气。浅色的点样本不到 20 次,比例很容易大起大落,基本不用看。
阳线 = 收盘 > 开盘,阴线 = 收盘 < 开盘,十字线不算。日盘 K 线是 09:30 开、16:15 收;全天 K 线是前一天 18:00 开、当天 17:00 收(和 TradingView 上 ES1! 的日线一样)。鼠标移到图上(手机上点一下)看具体数字。
连涨之后:和平时差不多
日盘日 K 线,3787 次里:
- 任意一天之后,第二天收阳 55.0%。这是”底数”:ES 这 16 年大部分时间在涨,所以阳线本来就比阴线多。
- 连涨 1 天之后 53.2%,连涨 2 天 55.5%,3 天 53.6%,4 天 53.2%,5 天 58.0%……都在 55% 上下晃。
结论:在日线上,“已经连涨了几天”不能帮你判断明天。
连跌之后:稍微更容易反弹,但不显著
- 连跌 1 天之后,第二天收阳 55.7%;连跌 2 天 58.6%,3 天 58.3%。
- 连跌 2 天以上合在一起:712 次里收阳 57.4%,比底数 55.0% 高 2.4 个百分点。
- 但 712 次的 95% 区间大约是 53.8%–61.0%,把 55% 包在里面。也就是说,这个差距用 16 年的数据还不能排除是运气。
- 连跌 4 天以上的样本更少(4 天 67 次,5 天 32 次),比例反而掉到 50% 左右,图上的区间也很长。
所以”连跌之后容易反弹”在数据里有一点影子,但不够确定。这 16 年大部分是牛市(底数就有 55%),跌几天之后被买回来本来就常见;要确认它,需要更长的数据,最好包括 2000–2010 年那样的熊市。
全天 K 线和周线
- 全天 K 线(前一天 18:00 到当天 17:00):底数 55.7%,连涨 2 天以上之后 55.7%,连跌 2 天以上之后 58.3%。结论和日盘一样。
- 周线(日盘):任意一周之后,下一周收阳 58.9%。连跌 2 周以上之后 63.8%,连涨 2 周以上之后 59.7%。连跌 3 周之后 69.7%,但只有 33 次,区间很宽(53%–83% 左右)。
- 周线的样本只有 845 次,连续 4 周以上的组合大部分不到 20 次,在图上画成了浅色,只能看个大概。
定义和复现
连续阳线 / 阴线之后
- 数据:清洗后的 ES 1 分钟 K 线,2010-06 到 2026-09,清洗方法见这篇。
- 日盘 K 线:09:30 开,16:15 收。全天 K 线:前一天 18:00 开,当天 17:00 收,和 TradingView 上 ES1! 的日线一样。
- 周线:同一周(周一到周五)第一天的开盘到最后一天的收盘。
- 阳线 / 阴线:收盘 > 开盘是阳线,收盘 < 开盘是阴线;相等是十字线(日盘日线 16 年里只有 29 根)。
- 连续根数:阳线 +1、+2、+3……,阴线 −1、−2、−3……;方向一变从 ±1 重新数,十字线归零。
- 统计:每个”连续 N 根”之后,看下一根收阳的比例;下一根是十字线的不算。连续 8 根以上并成一组。
- 中间缺一天怎么办:清洗时删掉了半日市和换合约的那几天。日线的”连续”按股市交易日算,上一个交易日被删掉了,就从头数,这一对也不统计。
- 95% 区间:用 Wilson 方法算,意思是”按这么多样本,真实比例大概率在这个范围里”。
我有一份清洗过的 ES 期货 1 分钟 K 线 CSV(Databento 的 ES.c.0 连续合约,列有 ts_event、open、close,时间是 UTC,半日市和换合约那几天已经删掉)。请帮我写 Python 代码,统计“连续 N 根阳线(阴线)之后,下一根收阳的概率”。规则如下: 1. 时间先转成美东时间(US/Eastern),自动处理夏令时。做两种日 K 线: - 日盘:09:30 那根 1 分钟的开盘价开,16:14 那根的收盘价收(09:30–16:15)。 - 全天:前一天 18:00 开,当天 17:00 收(18:00 以后的 1 分钟 K 线算下一个交易日),和 TradingView 上 ES1! 的日线一样。只保留有日盘的交易日,数据第一天的夜盘不完整,去掉。 2. 周线:把同一周(周一到周五)的日 K 线合起来,开盘价 = 第一天的开盘,收盘价 = 最后一天的收盘。 3. 阳线:收盘 > 开盘;阴线:收盘 < 开盘;相等是十字线。 4. 连续根数:阳线记 +1、+2、+3……,阴线记 −1、−2、−3……;方向一变就从 ±1 重新数,十字线归零。 5. 日线的“连续”按股市交易日算:用纽交所日历(pandas 自带的节假日规则,再加上 2012-10-29、2012-10-30、2018-12-05、2025-01-09 临时休市)找上一个交易日,如果它不在数据里(被清洗掉了),就从头数,这一对也不统计。周线要求两周是相邻的两周。 6. 统计:在每个“连续 N 根”之后,看下一根是不是阳线;下一根是十字线的不算。连续 8 根以上并成一组。 7. 日盘 / 全天、日线 / 周线四种组合,各打印:总次数、不看前面时下一根收阳的比例、连涨 2 根以上和连跌 2 根以上之后下一根收阳的比例,以及连涨 1–8+ 根、连跌 1–8+ 根每一组的次数和下一根收阳的比例。每个比例都附上 95% 区间(用 Wilson 方法算)。 请每一步加中文注释,说明为什么这样做。
# 连续阳线 / 阴线之后:连涨(连跌)N 根以后,下一根收阳的概率
# 输入:清洗后的 ES 1 分钟 K 线(Databento ES.c.0,时间是 UTC)
import numpy as np
import pandas as pd
from pandas.tseries.holiday import (
AbstractHolidayCalendar, Holiday, GoodFriday, USMartinLutherKingJr, USPresidentsDay,
USMemorialDay, USLaborDay, USThanksgivingDay, nearest_workday, sunday_to_monday,
)
class NYSE(AbstractHolidayCalendar): # 纽交所全天休市日
rules = [
Holiday("元旦", month=1, day=1, observance=sunday_to_monday),
USMartinLutherKingJr, USPresidentsDay, GoodFriday, USMemorialDay,
Holiday("六月节", month=6, day=19, start_date="2022-01-01", observance=nearest_workday),
Holiday("独立日", month=7, day=4, observance=nearest_workday),
USLaborDay, USThanksgivingDay,
Holiday("圣诞节", month=12, day=25, observance=nearest_workday),
]
CLOSED = pd.to_datetime(["2012-10-29", "2012-10-30", "2018-12-05", "2025-01-09"]) # 临时休市
df = pd.read_csv("ES-1min_标准版.csv", usecols=["ts_event", "open", "close"])
t = pd.to_datetime(df.ts_event, utc=True).dt.tz_convert("US/Eastern").dt.tz_localize(None)
m = t.dt.hour * 60 + t.dt.minute
# 日盘 K 线:09:30 开、16:15 收
rth = df[(m >= 570) & (m < 975)].groupby(t.dt.normalize()).agg(o=("open", "first"), c=("close", "last"))
# 全天 K 线:前一天 18:00 开、当天 17:00 收(18:00 以后的算下一个交易日)
full = df.groupby((t + pd.Timedelta(hours=6)).dt.normalize()).agg(o=("open", "first"), c=("close", "last"))
full = full.loc[rth.index[1:]] # 只留有日盘的日子;数据第一天的夜盘不完整,去掉
# 日线的“连续”按股市交易日算:上一个交易日被清洗掉了,就从头数
cal = pd.bdate_range(rth.index.min() - pd.Timedelta(days=10), rth.index.max())
cal = cal[~cal.isin(NYSE().holidays(cal.min(), cal.max()).union(CLOSED))]
prev = pd.Series(cal[:-1], index=cal[1:])
def wilson(k, n, z=1.96):
"""比例的 95% 区间(Wilson 方法),样本少时比“比例 ± 2 倍标准误”更靠谱"""
p = k / n
c = (p + z * z / (2 * n)) / (1 + z * z / n)
h = z * np.sqrt(p * (1 - p) / n + z * z / (4 * n * n)) / (1 + z * z / n)
return f"{100 * (c - h):.1f}%–{100 * (c + h):.1f}%"
def study(bars, linked, name):
d = np.sign(bars.c.values - bars.o.values).astype(int) # 阳线 +1,阴线 −1,十字线 0
streak = np.zeros(len(d), int) # 连涨 +1、+2……,连跌 −1、−2……
for i, x in enumerate(d):
same = i > 0 and linked[i] and x != 0 and np.sign(streak[i - 1]) == x
streak[i] = streak[i - 1] + x if same else x
rows = pd.DataFrame({"连续": streak[:-1], "下一根收阳": d[1:] > 0})[(d[1:] != 0) & linked[1:]] # 下一根是十字线、或者不挨着的,不算
rows["组"] = rows["连续"].clip(-8, 8) # 连续 8 根以上并成一组
print(f"== {name}:{len(rows)} 次,不看前面下一根收阳 {100 * rows['下一根收阳'].mean():.1f}%")
for lab, g in [("连涨 2 根以上", rows[rows["连续"] >= 2]), ("连跌 2 根以上", rows[rows["连续"] <= -2])]:
print(f"{lab}:{len(g)} 次,下一根收阳 {100 * g['下一根收阳'].mean():.1f}%(95% 区间 {wilson(g['下一根收阳'].sum(), len(g))})")
tab = rows[rows["组"] != 0].groupby("组")["下一根收阳"].agg(["size", "mean", "sum"])
tab["95% 区间"] = [wilson(k, n) for k, n in zip(tab["sum"], tab["size"])]
tab.index = [f"连{'涨' if k > 0 else '跌'} {abs(k)}{'+' if abs(k) == 8 else ''} 根" for k in tab.index]
print(tab.drop(columns="sum").rename(columns={"size": "次数", "mean": "下一根收阳"}).round(3).to_string())
for bars, name in [(rth, "日线 · 日盘"), (full, "日线 · 全天")]:
study(bars, np.r_[False, prev.reindex(bars.index).values[1:] == bars.index.values[:-1]], name)
wk = bars.index - pd.to_timedelta(bars.index.dayofweek, unit="D") # 这周的周一
w = bars.groupby(wk).agg(o=("o", "first"), c=("c", "last"))
study(w, np.r_[False, np.diff(w.index.values) == np.timedelta64(7, "D")], name.replace("日线", "周线"))局限
- 只看收阳还是收阴,不看涨跌多少:连跌 3 天之后第二天收了一根小阳线,也算”反弹”。
- 样本不够大:连续 4 根以上的组合,日线只有几十次,周线只有个位数到十几次,比例会大起大落。
- 16 年大部分是牛市:底数 55% 本身就偏多。换成 2000–2010 年那样的熊市,结论可能不一样。