第一次接美股数据时漏掉盘前盘后,系统上线 3 天后被用户问"为什么开盘前是空的"------这是绝大多数工程师首次接入美股数据的同款坑。
美股一天有 4 个交易时段,但大部分人只接了正盘那 6.5 小时,等于每天有 8.5 小时是瞎的。
| 时段 | 美东时间 | 特点 |
|---|---|---|
| 盘前 | 04:00--09:30 | 流动性最薄,机构试盘窗口 |
| 正盘 | 09:30--16:00 | 常规交易,流动性最好 |
| 盘后 | 16:00--20:00 | 财报集中发布,信息密度最高 |
| 夜盘 | 20:00--次日 04:00 | 部分券商支持,覆盖标的有限 |
问题不只是"少接了几个时段"。盘前盘后的订单簿深度只有正盘的几分之一,但财报和宏观数据偏偏集中在这两个窗口发布。策略代码如果不知道当前处于哪个时段,它无法判断是否应该执行------盘前流动性不足时本该暂停的趋势策略,可能因为没有时段判断逻辑而在错误的时间下单。
yfinance 能拿到盘前盘后数据吗?
能,但有前提:必须加 prepost=True 参数。不加这个参数,history() 只返回正盘数据------这是新手最容易漏的配置项。
python
import yfinance as yf
ticker = yf.Ticker("AAPL")
# 关键:prepost=True 才能拿到盘前盘后数据
df = ticker.history(period="5d", interval="1m", prepost=True)
能拿到数据,但 yfinance 本身有 3 个限制,在生产环境会逐渐暴露。
坑 1:yfinance 不区分时段,需要自己映射
history() 返回的 DataFrame 没有"当前是盘前还是盘后"的标记,只有时间戳和 OHLCV。你需要自己根据时间戳映射时段。
美东时间对应关系:
hhmm 格式
400 → 盘前开始(4:00 AM ET)
930 → 正盘开始(9:30 AM ET)
1600 → 盘后开始(4:00 PM ET)
2000 → 夜盘开始(8:00 PM ET)
映射函数:
python
from datetime import datetime
from zoneinfo import ZoneInfo # Python 3.9+ 内置,无需额外安装
EASTERN = ZoneInfo("America/New_York")
# 时段定义:(开始时间 hhmm, 结束时间 hhmm, 时段名称)
# 注意夜盘跨午夜,拆成两段处理
US_SESSIONS = [
(400, 930, "盘前"),
(930, 1600, "盘中"),
(1600, 2000, "盘后"),
(2000, 2400, "夜盘"), # 夜盘前半:20:00--00:00
(0, 400, "夜盘"), # 夜盘后半:00:00--04:00(跨午夜)
]
def get_current_session() -> str:
"""返回当前美股时段名称,休市返回 '休市'"""
now = datetime.now(EASTERN)
t = now.hour * 100 + now.minute
for begin, end, name in US_SESSIONS:
if begin > end:
# 跨午夜时段:begin=2000, end=400 这种情况不存在,
# 已拆成两段,这里只是保险
if t >= begin or t < end:
return name
else:
if begin <= t < end:
return name
return "休市"
# 给 DataFrame 打时段标签
def label_sessions(df):
"""给 yfinance 返回的 DataFrame 打时段标签"""
# 转换为美东时间
df_et = df.copy()
if df_et.index.tzinfo is None:
df_et.index = df_et.index.tz_localize("UTC")
df_et.index = df_et.index.tz_convert(EASTERN)
def row_session(ts):
t = ts.hour * 100 + ts.minute
for begin, end, name in US_SESSIONS:
if begin <= t < end:
return name
return "休市"
df_et["session"] = df_et.index.map(row_session)
return df_et
坑 2:时区陷阱------夏令时会让你的时段判断偏移 1 小时
yfinance 返回的时间戳是 UTC,服务器如果跑在 UTC+8,直接用本地时间判断美东时段会差 12-13 小时(夏令时差 12 小时,冬令时差 13 小时)。
不要自己算偏移量,用 zoneinfo 让系统自动处理:
python
import yfinance as yf
from zoneinfo import ZoneInfo
EASTERN = ZoneInfo("America/New_York")
def get_premarket_data(symbol: str = "AAPL", days: int = 5):
"""拉取近期数据,筛选盘前时段(04:00--09:30 ET)"""
ticker = yf.Ticker(symbol)
df = ticker.history(period=f"{days + 2}d", interval="1m", prepost=True)
if df.empty:
print(f"未获取到数据(标的:{symbol},检查代码或网络)")
return df
# 转为美东时间再过滤,自动处理夏令时
df_et = df.tz_convert(EASTERN)
# 筛选盘前:04:00 ≤ time < 09:30
# 注意:between_time 默认左闭右闭,需要用 inclusive 参数控制
premarket = df_et.between_time("04:00", "09:30", inclusive="left")
return premarket
if __name__ == "__main__":
print(f"当前时段:{get_current_session()}")
data = get_premarket_data("AAPL", days=5)
print(f"\n近 5 日盘前数据(共 {len(data)} 条):")
if not data.empty:
print(data[["Open", "High", "Low", "Close", "Volume"]].tail(5))
这里有个细节 :between_time("04:00", "09:30") 默认是左闭右闭,会包含 09:30 的数据------正盘第一分钟会混进来。用 inclusive="left" 改成左闭右开,只取 04:00 ≤ time < 09:30 的数据。
坑 3:港股、A 股需要换库,不是换参数
yfinance 的覆盖范围以美股为主。如果策略同时跑港股和 A 股:
- 港股标的在 yfinance 里格式是
0700.HK,可以拉到数据,但时段标记逻辑要重写一套(港股有午休 12:00--13:00) - A 股目前 yfinance 支持较差,通常用 akshare 或 tushare 替代,接口风格完全不同
这意味着同时监控多个市场时,你需要维护多套数据拉取逻辑。每个市场的时段规则不同:美股 4 个时段、港股含午休、A 股含集合竞价------这层差异如果放在业务逻辑里处理,代码会越来越难维护。
生产环境的 3 个优化方向
1. 时段表缓存
时段定义不会分钟级变化,不要每次判断都重新计算。把时段表缓存在内存里,只在日期切换或交易日历更新时刷新。
2. 按时段切换订阅列表
盘前流动性不足,没必要订所有标的:
python
def get_symbols_by_session(session: str) -> list:
if session == "盘前":
return CORE_SYMBOLS[:50] # 核心 50 只
elif session == "盘中":
return FULL_SYMBOLS # 全量
else:
return CORE_SYMBOLS[:20] # 盘后/夜盘精简
3. 注意 yfinance 的限频
yfinance 是基于 Yahoo Finance 的非官方接口,频繁调用会触发限频(HTTP 429)。生产环境建议加重试逻辑和请求间隔控制。
有了美股 API ≠ 有了盘前数据。时段部分往往是文档里被跳过的章节------不是因为不重要,是因为正盘能跑通的代码已经够让人忙的了。等真的踩过漏数据的坑再回来补,通常要多花 2-3 天。
你的策略跑在哪个时段上?有没有因为没接盘前数据错过信号?评论区聊聊。