一句话结论:一周五天,涨跌方向的差别不大。所有日子日盘收阳 54.6%,只有周五高一些(58.1%),而且 2010–2017 和 2018–2026 两段都是这样。
波幅的差别更明显,也更稳定:周一日内波幅的中位数是 0.83%,最小;周四是 0.96%,最大。两段数据都是这个顺序。
Al Brooks 讲过的”周末、月末资金倾向买、容易涨”:周五对得上;但每个月最后一个交易日反而更容易跌,日盘收阳只有 47.6%。
作用:不要靠星期几判断方向;但可以用它调整预期:周一的目标别定得太远,周四的止损留宽一点。
交易圈里有很多关于星期几的说法:“周一容易反转”、“周五收盘前多头会平仓”、“周四容易跌”。我把 ES 16 年的交易日按星期几分开,看三件事:收阳的比例、涨跌幅的平均数和中位数、日内波幅。
2010–2026 年 · 样本 3924 天 · 所有日子收阳(上涨)54.6% · 日内波幅中位数 0.90%
收阳的比例日盘收盘高于日盘开盘。圆点是比例,竖线是 95% 的区间;虚线是所有日子(54.6%)。
怎么读这张图:竖线是 95% 区间,意思是按这么多样本,真实的比例大概率落在这根竖线的范围里,样本越少竖线越长。只有整根竖线都在虚线的同一边,才能说这一天和平时不一样;竖线跨过虚线,差别就可能只是运气。
平均涨跌和中位数涨跌平均数会被少数大涨大跌的日子拉偏,中位数是"一般的一天"。
日内波幅(日盘最高 − 日盘最低) ÷ 开盘价。柱子是中位数,圆点是平均。
星期几按日历算,周一放假的那一周,周二还是算周二。收盘对收盘要求昨天是上一个股市交易日、而且是同一个合约。鼠标移到图上(手机上点一下)看这一天的全部数字。
方向:只有周五稍微突出
| 星期 | 天数 | 日盘收阳 | 收盘对收盘上涨 |
|---|---|---|---|
| 所有日子 | 3924 | 54.6% | 55.2% |
| 周一 | 698 | 55.7% | 57.9% |
| 周二 | 838 | 53.1% | 52.7% |
| 周三 | 833 | 52.2% | 56.1% |
| 周四 | 818 | 54.4% | 54.3% |
| 周五 | 737 | 58.1% | 55.7% |
日盘收阳:日盘收盘高于日盘开盘。收盘对收盘上涨:今天收盘高于昨天收盘,包括隔夜的涨跌。
- 周五日盘最常收阳:58.1%,95% 区间是 54.5%–61.6%,下沿刚好碰到所有日子的 54.6%。2010–2017 年是 57.5%,2018–2026 年是 58.6%,两段都排第一。这是方向上唯一一个前后都站得住的差别。
- 周二这几年变弱了:2018–2026 年,周二收盘对收盘上涨只有 49.3%,是唯一一个不到一半的;但 2010–2017 年是 56.6%,五天里最高。前后不一致,更像是这几年的特点,不能当成规律。
- 周一收盘对收盘上涨最多(57.9%),但它包括了周末的跳空;只看周一日盘,收阳是 55.7%,和平时差不多。
- “周四容易跌”在 ES 上看不出来:周四收盘对收盘上涨 54.3%,和所有日子的 55.2% 差不多。
Al Brooks 说的周末、月末效应
Al Brooks 在 2024 年 3 月的直播里讲过季末的”橱窗效应”(window dressing):机构每个季度要交报告,都想让报告里显示自己持有表现好的股票,所以季末会买进,市场容易一路涨到月底。这个说法常被延伸到每周五和每个月底:资金经理都想让自己管理的账户在结算时好看一点,大家都倾向买;买的人越多,价格越容易涨,涨了又吸引更多人买。所以周末、月末、季末收阳的概率应该偏高一点。
用 ES 的数据核对一下:
| 天数 | 日盘收阳 | 收盘对收盘上涨 | |
|---|---|---|---|
| 所有日子 | 3924 | 54.6% | 55.2% |
| 周五 | 737 | 58.1% | 55.7% |
| 月末 | 187 | 47.6% | 45.4% |
| 季末 | 64 | 51.6% | 54.7% |
月末:每个月最后一个股市交易日;季末:3、6、9、12 月的月末。这一天被清洗掉的月份不算。
- 周五对得上:日盘收阳 58.1%,一周里最高,前后两段都成立。
- 月末最后一天反而更容易跌:日盘收阳只有 47.6%,收盘对收盘上涨 45.4%。收盘对收盘的 95% 区间是 38.4%–52.6%,整根都在平时的 55.2% 下面,不太像是运气。
- 季末看不出差别:只有 64 天,比例和平时差不多,区间也很宽。
所以在 ES 上,“周末偏多”站得住,“月末偏多”不成立,至少最后一个交易日不是这样。一个可能的原因(没有验证):Al 说的是季末前一两周的整体倾向,不是最后一天;月底最后一天还有大资金的再平衡,股票涨多了要卖掉一部分,可能把买盘抵消甚至反过来。Al 自己在同一段直播里也说过,日历规律都不强,靠它交易赚不了多少钱。
平均和中位数不一样
图里第二张同时画了平均涨跌和中位数涨跌,有几天差得不少。比如收盘对收盘,周一的平均只有 +0.04%,中位数却是 +0.08%,比周二、周四都高。
原因是少数大跌的日子把平均数拉低了:16 年里收盘对收盘跌得最多的 5 天,有 3 天是周一(2020-03-16 −10.45%、2020-03-09 −7.25%、2011-08-08 −7.16%),都是周末出了大消息、周一集中反应。
所以看”一般的一天”要看中位数;平均数更容易被几天极端行情带偏。
波幅:周一最小,周四最大
| 星期 | 全部 | 2010–2017 | 2018–2026 |
|---|---|---|---|
| 周一 | 0.83% | 0.75% | 0.88% |
| 周二 | 0.89% | 0.84% | 0.94% |
| 周三 | 0.93% | 0.85% | 0.98% |
| 周四 | 0.96% | 0.89% | 1.04% |
| 周五 | 0.89% | 0.79% | 1.00% |
数字是日内波幅的中位数:(日盘最高 − 日盘最低) ÷ 开盘价。
- 两段数据都是周一最小、周四最大。周四的波幅比周一大约宽 16%:按 6000 点算,一般的周一日盘走 50 点左右,一般的周四走 58 点左右。
- 可能的原因(没有验证):周一的消息很多已经在周日晚上的夜盘里消化了,日盘没有那么多新东西;周四每周早上 8:30 都有初请失业金人数,很多重要数据和央行会议也常排在周三、周四。
定义和复现
星期几效应
- 数据:清洗后的 ES 1 分钟 K 线,2010-06 到 2026-09,共 3924 个交易日,清洗方法见这篇。
- 日盘:美东 09:30–16:15。开盘价是 09:30 那根 1 分钟 K 线的开盘价,收盘价是 16:14 那根的收盘价。
- 日盘收阳:日盘收盘 > 日盘开盘。
- 收盘对收盘:(今天收盘 − 昨天收盘) ÷ 昨天收盘。昨天 = 上一个股市交易日,规则和突破昨日高低点那篇一样;昨天被清洗掉了、或者两天不是同一个合约的,这一天不算。
- 日内波幅:(日盘最高 − 日盘最低) ÷ 开盘价,和日内波幅那篇一样。
- 星期几:按日历算。周一放假的那一周,周二还是算周二。
- 月末 / 季末:月末是每个月最后一个股市交易日(按纽交所日历),这一天被清洗掉的月份不算;季末是 3、6、9、12 月的月末。
- 95% 区间:用 Wilson 方法算,意思是”按这么多样本,真实比例大概率在这个范围里”。
我有一份清洗过的 ES 期货 1 分钟 K 线 CSV(Databento 的 ES.c.0 连续合约,列有 ts_event、instrument_id、open、high、low、close,时间是 UTC)。请帮我写 Python 代码,统计“周一到周五,ES 日盘的涨跌和波幅有没有差别”。规则如下: 1. 时间先转成美东时间(US/Eastern),自动处理夏令时。只看日盘 09:30–16:15,也就是 1 分钟 K 线从 09:30 到 16:14。开盘价 = 09:30 那根的开盘价,收盘价 = 16:14 那根的收盘价,最高 / 最低价 = 日盘所有 K 线的最高 / 最低。 2. 每天算三个数: - 日盘涨跌 % = (收盘 − 开盘) ÷ 开盘 × 100,大于 0 算收阳; - 收盘对收盘涨跌 % = (今天收盘 − 昨天收盘) ÷ 昨天收盘 × 100。昨天 = 今天之前最近的一个股市交易日,用纽交所日历判断(pandas 自带的节假日规则,再加上 2012-10-29、2012-10-30、2018-12-05、2025-01-09 临时休市);如果这个昨天不在数据里,或者两天的 instrument_id 不同,这一天的收盘对收盘不算; - 日内波幅 % = (日盘最高 − 日盘最低) ÷ 开盘 × 100。 3. 星期几按日历算(周一放假的那一周,周二还是算周二)。 4. 按星期几分组,打印每组的天数、日盘收阳比例和它的 95% 区间(Wilson 方法)、收盘对收盘上涨比例、收盘对收盘涨跌的平均数和中位数、波幅的中位数。 5. 再把数据分成 2010–2017 和 2018–2026 两段,各打印一遍同样的表,看结论是不是两段都成立。 6. 再看周五、月末、季末:月末 = 每个月最后一个股市交易日(按纽交所日历;这一天不在数据里的月份不算),季末 = 3、6、9、12 月的月末。分别打印天数、日盘收阳比例和收盘对收盘上涨比例,以及各自的 95% 区间,和所有日子比较。最后列出收盘对收盘跌得最多的 5 天。 请每一步加中文注释,说明为什么这样做。
# 星期几效应:周一到周五,ES 日盘收阳的比例、涨跌幅、日内波幅有没有差别?
# 输入:清洗后的 ES 1 分钟 K 线(Databento ES.c.0,时间是 UTC,含 instrument_id 列)
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", "instrument_id", "open", "high", "low", "close"])
t = pd.to_datetime(df.ts_event, utc=True).dt.tz_convert("US/Eastern").dt.tz_localize(None)
hm = t.dt.hour * 100 + t.dt.minute
rth = df[(hm >= 930) & (hm < 1615)].assign(date=t.dt.normalize()) # 日盘 09:30–16:15
day = rth.groupby("date").agg(o=("open", "first"), h=("high", "max"), l=("low", "min"), c=("close", "last"),
contract=("instrument_id", "first"))
# 收盘对收盘:昨天 = 上一个股市交易日;昨天被清洗掉了、或者两天不是同一个合约,这一天不算
cal = pd.bdate_range(day.index.min() - pd.Timedelta(days=10), day.index.max())
cal = cal[~cal.isin(NYSE().holidays(cal.min(), cal.max()).union(CLOSED))]
prev = pd.Series(cal[:-1], index=cal[1:]).reindex(day.index)
linked = (prev.values[1:] == day.index.values[:-1]) & (day.contract.values[1:] == day.contract.values[:-1])
day["日盘"] = (day.c - day.o) / day.o * 100 # 日盘涨跌 %
day["收盘对收盘"] = np.r_[np.nan, np.where(linked, (day.c.values[1:] / day.c.values[:-1] - 1) * 100, np.nan)]
day["波幅"] = (day.h - day.l) / day.o * 100 # 日内波幅 %
day["星期"] = day.index.dayofweek # 0 = 周一 … 4 = 周五
def wilson(k, n, z=1.96):
"""收阳比例的 95% 区间(Wilson 方法)"""
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 table(d):
g = d.groupby("星期")
tab = pd.DataFrame({
"天数": g.size(),
"日盘收阳%": g["日盘"].apply(lambda s: 100 * (s > 0).mean()),
"95%区间": g["日盘"].apply(lambda s: wilson((s > 0).sum(), len(s))),
"收盘对收盘上涨%": g["收盘对收盘"].apply(lambda s: 100 * (s.dropna() > 0).mean()),
"收盘对收盘平均%": g["收盘对收盘"].mean(),
"收盘对收盘中位数%": g["收盘对收盘"].median(),
"波幅中位数%": g["波幅"].median(),
}).round(3)
tab.index = ["周一", "周二", "周三", "周四", "周五"]
return tab
print(f"样本 {len(day)} 天;日盘收阳 {100 * (day['日盘'] > 0).mean():.1f}%,"
f"收盘对收盘上涨 {100 * (day['收盘对收盘'].dropna() > 0).mean():.1f}%,波幅中位数 {day['波幅'].median():.2f}%")
print(table(day).to_string())
for a, b in [(2010, 2017), (2018, 2026)]: # 前后两半分开看,检查是不是稳定
print(f"\n{a}–{b}")
print(table(day[(day.index.year >= a) & (day.index.year <= b)]).to_string())
# 周末、月末、季末:月末 = 每个月最后一个股市交易日(这一天被清洗掉的月份不算),季末 = 3、6、9、12 月的月末
last_day = pd.Series(cal, index=cal).groupby([cal.year, cal.month]).max()
day["月末"] = day.index.isin(last_day.values)
day["季末"] = day["月末"] & day.index.month.isin([3, 6, 9, 12])
print()
for name, m in [("所有日子", day.index == day.index), ("周五", day["星期"] == 4), ("月末", day["月末"]), ("季末", day["季末"])]:
s = day[m]
cc = s["收盘对收盘"].dropna()
print(f"{name}:{len(s)} 天,日盘收阳 {100 * (s['日盘'] > 0).mean():.1f}%(95% 区间 {wilson((s['日盘'] > 0).sum(), len(s))}),"
f"收盘对收盘上涨 {100 * (cc > 0).mean():.1f}%(95% 区间 {wilson((cc > 0).sum(), len(cc))})")
print("\n收盘对收盘跌得最多的 5 天:")
print(day["收盘对收盘"].nsmallest(5).round(2).to_string())局限
- 方向上的差别都很小:除了周五,其他几天和平均值的差距都在误差范围以内,16 年的样本说明不了更多。
- 周一和周五的样本少:马丁·路德·金纪念日、总统日、阵亡将士纪念日、劳动节都在周一,耶稣受难日在周五,感恩节后的周五半日市也删掉了;清洗时每季度换合约,删掉的是交割日那个周五和之后的周一。所以周一只有 698 天、周五 737 天,周二到周四都在 810 天以上。
- 没有区分消息日:FOMC、CPI、非农这些日子和普通日子混在一起;波幅的差别可能主要来自这些日子排在哪一天。
- 只看日盘:周一的差别有一部分发生在周日晚上的夜盘里,这里没有单独看。