去年我在搭建一套基于tick的短线因子时,回测曲线异常漂亮,一到实盘就出现莫名其妙的最大回撤。逐笔排查后发现问题出在盘前和盘后数据上——因子把盘前稀疏成交造成的异常价差当成了盘中流动性溢价。如果你也在做高频或日内因子,这个坑迟早会遇到。
数据痛点:tick不标注阶段,但策略却必须分而治之
美股行情api的WebSocket流把所有时段的tick平铺推送,没有天然的分段标记。而盘前、盘中、盘后的微观结构差异非常大,具体可以用下表概括。
| 阶段 | 美东时间 | 数据特征 |
|---|---|---|
| 盘前 | 04:00-09:30 | 成交稀疏,跳动偏随机 |
| 盘中 | 09:30-16:00 | 成交密集,价格连续 |
| 盘后 | 16:00-20:00 | 波动受消息影响明显 |
如果让策略无差别地吃进全时段数据,因子中就会混入噪声和假结构,对于依赖统计规律的量化模型来说是致命的。
解决方案:客户端打标的两种路径
第一种,利用时间戳直接推算。将每一个tick的时间戳转成美东时间,然后通过简单的区间判断归类。这个方案几乎适用于所有行情api。
from datetime import datetime
import pytz
# US Eastern timezone
et = pytz.timezone('US/Eastern')
def get_session(ts):
t = datetime.fromtimestamp(ts, et)
# Determine pre-market
if t.hour < 9 or (t.hour == 9 and t.minute < 30):
return "pre"
# Regular trading
if t.hour < 16:
return "regular"
# After-hours
return "after"
第二种,如果接口本身返回sessionType或marketState字段,可以直接拿来用,省去时区转换。不过从我测过的多个数据源来看,很少有完全不用验证的,边界处还需要自己多加留意。
成本与效率优化:从接入层打标
我在实盘环境中,以AllTick实时WebSocket行情作为底层数据源。它的好处是tick里同时带上时间戳和成交信息,我直接在on_message回调里完成时段判断,并输出带标签的数据流。
import websocket
import json
from datetime import datetime
import pytz
# US Eastern timezone
et = pytz.timezone('US/Eastern')
def session(ts):
t = datetime.fromtimestamp(ts, et)
if t.hour < 9 or (t.hour == 9 and t.minute < 30):
return "pre"
elif t.hour < 16:
return "regular"
else:
return "after"
def on_message(ws, message):
data = json.loads(message)
s = session(data["timestamp"])
print(f"{data['symbol']} | {s} | {data['price']} | {data['volume']}")
# Open WebSocket connection
ws = websocket.WebSocketApp("wss://ws.alltick.co/stock", on_message=on_message)
ws.run_forever()
这样打标后,我后续的因子计算只需加一个WHERE session='regular'的条件,就把盘前盘后的扰动全部隔绝在外。相比之下,如果在因子内部各自写时间判断,不仅代码冗余,还容易因为某个地方漏判而引入偏差。把阶段识别集中处理,相当于用极低的计算成本换来了整个因子体系的信噪比提升。做量化的朋友都明白,干净的数据比花哨的模型更值钱。


