港股api实时行情的时间边界处理:从工程化配置到可扩展数据管道

我们在帮专业交易团队和基金公司开发部门做港股行情相关模块时,发现一个非常典型的问题:WebSocket 连接本身并不难,真正反复让开发人员返工的,是“时间”这个看似基础、实则容易踩坑的边界判断。

picture.image

一、为什么时间边界容易成为隐性故障点

港股并不是全天连续交易的市场。如果我们只是把实时行情当成一条条价格、成交量和时间戳的流,而不对“数据时间”和“交易时间”做分层,系统在开市前、午间休市、收市后甚至跨日期时就会出现状态漂移。

例如,有一次我们接收港股 api 推送的 tick 数据,在早上 9:05 收到一条报价,前端却显示为“连续交易中”,导致一个基金客户的内部风控模块触发了错误的预警。排查后发现,代码里只是简单判断“当前时间大于 9:30”就认为开市,完全忽略了 9:00 到 9:30 其实是竞价阶段,也忽略了下午 16:00 之后的时间区间。

这类问题的本质是:我们把时间当成了一个点,而不是一个区间

二、从交易时段定义开始拆分

我们现在的做法是,先把港股一天的时段拆成清晰的配置,而不是散落在业务代码里。下面是我们内部统一维护的时段表:

时段香港时间程序处理
开市前竞价09:00–09:30预开市数据
上午交易09:30–12:00正常交易
午间休市12:00–13:00非连续交易
下午交易13:00–16:00正常交易
收市后16:00 之后非正常交易

这个表看起来简单,但很多团队会忽略“边界”本身。比如下面这种写法:

if current_time >= time(9, 30):
    market_open = True

表面上看似乎合理,实际上下午五点、晚上九点都会被判定成开市。这就是我们后来强制改成区间判断的原因:

def is_market_open(t):
    morning = time(9, 30) <= t < time(12, 0)
    afternoon = time(13, 0) <= t < time(16, 0)
    return morning or afternoon

从工程化角度看,我们更倾向把这类判断抽成独立的时间模块,供实时计算、风控、K 线聚合等多个任务复用,而不是在每个微服务里复制一份。

三、API 时间戳与本地时间的错位问题

我们对接过多个港股 api,返回的数据里时间戳通常是 Unix timestamp,比如:

{
    "symbol": "00700.HK",
    "price": 520.5,
    "timestamp": 1788226200
}

这个数字本身不携带任何时区信息。如果服务器部署在 UTC 或新加坡时区,而我们直接用 datetime.now() 去比较,就会出现同一时刻在不同环境得到不同结果的情况。

我们的统一处理是:所有进入交易时段判断之前的时间,先转换为 Asia/Hong_Kong 时区:

from datetime import datetime
from zoneinfo import ZoneInfo

timestamp = 1788226200
dt = datetime.fromtimestamp(
    timestamp,
    tz=ZoneInfo("Asia/Hong_Kong")
)
print(dt)

这样做的好处非常明显:无论服务跑在哪个地区,只要经过这个转换,后续判断的语义就是一致的。我们在接入 AllTick 的行情接口时,也会沿用这一层时区标准化逻辑,确保数据管道里的时间始终是香港时间。

四、开市前数据不能与连续成交混淆

9:00 到 9:30 这个阶段是“有行情数据”但“市场不连续成交”的阶段。如果我们把它简单标成 market_open=True,对行情展示和策略计算都会产生误导。

我们内部用的状态判断是:

def get_market_status(t):
    if time(9, 0) <= t < time(9, 30):
        return "pre_open"
    if time(9, 30) <= t < time(12, 0):
        return "morning"
    if time(12, 0) <= t < time(13, 0):
        return "break"
    if time(13, 0) <= t < time(16, 0):
        return "afternoon"
    return "closed"

这样前端可以显示“开市前”,量化策略只接受 morningafternoon 的数据,风控模块也能对 pre_open 做单独处理。不同模块各取所需,不会互相干扰。

五、跨日期和交易日历是另一层问题

还有一个容易被忽视的地方:午夜和周末。比如下面这种判断:

if current_time > time(16, 0):
    status = "closed"

在凌晨一点执行时,确实会被判为收市后,这没错。但如果任务同时要判断“当前交易日”,那就不能只看 time,还需要完整的日期信息。

我们的经验是:时间段用 datetime.time,交易日用 datetime.date,两者分开维护。不要在一个变量里既表达时间又表达日期,否则跨午夜时很容易出错。

另外,周一到周五并不等于交易日。香港的公众假期、台风休市等特殊情况都需要独立维护交易日历。生产环境里,交易日历应该是一个可更新的配置,而不是硬编码的星期几判断。

六、实时行情与 K 线的时间边界需要区分

如果我们要从实时 tick 数据聚合 1 分钟 K 线,边界问题会放大。例如 09:29:59 和 09:30:00 只差一秒,但分属两个完全不同的 K 线周期。

我们内部约定使用左闭右开区间:

09:30:00 <= tick_time < 09:31:00

这样每条 tick 只会进入唯一一个 K 线周期,不会出现同一条数据同时被两个周期统计的问题。

七、可扩展的数据管道设计

现在我们的行情处理流程已经固化下来,大致如下:

API实时数据
     ↓
解析 timestamp
     ↓
转换为 Asia/Hong_Kong
     ↓
判断交易日期
     ↓
判断交易时段
     ↓
过滤/分类行情
     ↓
K线聚合或策略计算

这个流程的最大好处是:交易时段、时区、交易日历都做成了可配置项,后续新增市场或调整规则时,改配置即可,不需要动核心逻辑。

我们以前总觉得行情系统里价格才是核心,真正做起来才发现,时间边界处理得不严谨,价格再准确也没用。对专业交易者和基金公司开发部门来说,时间层是行情系统里非常基础、但极其关键的一层。


API Docs:https://alltick.co/apis/en

GitHub:https://github.com/alltick/alltick-realtime-forex-crypto-stock-tick-finance-websocket-api

0
0
0
0
评论
未登录
暂无评论