日期不是字符串
价格序列是时间序列:重要的是每个价格发生在何时。从文件读出的日期只是普通字符串,字符串并不知道三月在二月之后,也不知道星期一跟在星期日后面。用 pd.to_datetime 转换并把结果作为索引:DatetimeIndex 保存真正的时间戳,pandas 可以对其排序、切片、比较和拆解。务必对它排序(sort_index()),让最早的观测排在最前。
import pandas as pd
raw_dates = ["2026-03-04", "2026-03-02", "2026-03-03"] # strings, in a messy order
closes = [102.0, 100.0, 101.0]
series = pd.Series(closes, index=pd.to_datetime(raw_dates), name="close").sort_index()
print(series)
# 2026-03-02 100.0
# 2026-03-03 101.0
# 2026-03-04 102.0
# Name: close, dtype: float64
print(isinstance(series.index, pd.DatetimeIndex)) # True生成日历
你常常需要没人提供的日期:用来对比的网格,或回测的各个交易日。pd.date_range(start, periods=n, freq="D") 生成 n 个连续的自然日,pd.bdate_range 跳过周末,给出工作日(它不了解节假日)。freq 参数决定步长:"D" 是一天,"B" 是一个工作日,"h" 是一小时。
# Four calendar days, then six business days (the weekend is skipped)
print(pd.date_range("2026-03-01", periods=4, freq="D").strftime("%a %d %b").tolist())
# ['Sun 01 Mar', 'Mon 02 Mar', 'Tue 03 Mar', 'Wed 04 Mar']
days = pd.bdate_range("2026-02-26", periods=6)
print(days.strftime("%a %d %b").tolist())
# ['Thu 26 Feb', 'Fri 27 Feb', 'Mon 02 Mar', 'Tue 03 Mar', 'Wed 04 Mar', 'Thu 05 Mar']按日期选择
对已排序的 DatetimeIndex,.loc 能理解字符串写成的日期。部分字符串如 "2026-03" 选出整个月,"2026" 选出整年,"2026-03-04" 选出某一天。切片 .loc["2026-03-04":"2026-03-06"] 选出一个范围,并且与按位置切片不同,两端都包含。端点不必出现在数据中:周末的日期只是匹配不到任何行,这正是你说「从月初起的全部」时想要的效果。
prices = pd.Series(
[float(i) for i in range(20)],
index=pd.bdate_range("2026-02-23", periods=20), # Mon 23 Feb ... Fri 20 Mar
)
print(len(prices.loc["2026-03"])) # 15 the business days of March in the data
print(prices.loc["2026-03-04"]) # 7.0 a single day
print(prices.loc["2026-03-04":"2026-03-06"])
# 2026-03-04 7.0
# 2026-03-05 8.0
# 2026-03-06 9.0
# Freq: B, dtype: float64
print(prices.loc["2026-03-01":"2026-03-03"].tolist()) # [5.0, 6.0] Sunday the 1st matches nothing日期的组成部分
DatetimeIndex 把每个日期的组成部分作为属性提供:year、month、day、dayofweek(星期一是 0,星期日是 6)。每个属性都是每行一个值的数组,可以当作掩码或分组键。这样就能不用循环地问「每个星期几各有多少观测?」或「星期一会发生什么?」。记得先对索引排序:对乱序的 DatetimeIndex 切片可能得到出人意料的结果,甚至报错。
# The parts of the dates, one entry per row
print(prices.index.dayofweek[:7].tolist()) # [0, 1, 2, 3, 4, 0, 1] Monday is 0
print(prices.index.month[:7].tolist()) # [2, 2, 2, 2, 2, 3, 3]
# How many observations fall on each weekday?
print(prices.groupby(prices.index.dayofweek).size().to_dict()) # {0: 4, 1: 4, 2: 4, 3: 4, 4: 4}
# What do the Mondays look like?
print(prices[prices.index.dayofweek == 0].tolist()) # [0.0, 5.0, 10.0, 15.0]留意范围的终点不带时间的日期字符串表示午夜。日线数据中
.loc["2026-03-04":"2026-03-06"] 包含 6 日,但日内数据会在 6 日 00:00 就结束,丢掉整天。当数据比一天更细时,请检查结果的最后一个时间戳。pd.to_datetime(字符串)生成 DatetimeIndex;sort_index()让最早的在前;date_range与bdate_range生成日历。.loc["2026-03"]选出一个月,.loc["2026-03-04":"2026-03-06"]选出包含两端的范围。index.year、index.month、index.dayofweek给出每个日期的组成部分,无需循环即可筛选或分组。
下一节:重采样,把日度价格转换成周度、月度甚至更细的K线。
实验:评分内容
- 用
parse_prices构建带日期的价格序列25 分 - 用
prices_in_month选出一个月的数据25 分 - 用
prices_between选出一段日期范围25 分 - 用
count_by_weekday统计每个星期几的观测数25 分