
凌晨三点盯盘盯到眼皮打架美股那边一家公司突然闪崩同一时间它的港股代码纹丝不动——因为港股要等到上午九点半才开盘。这中间的四个多小时就是跨时区套利最有想象力的窗口期。我最早接触“特价股票”这个概念并不是什么高深理论就是发现同一家公司的股票在不同的交易市场价格经常出现肉眼可见的偏离。同一个资产两个市场定价不一样这本身就是一种错价。而跨时区套利系统做的就是把这套错价识别、计算、执行的过程全部自动化让我不用天天熬夜盯盘也能捕捉机会。这篇文章我想把整套系统的搭建思路和实操细节完整讲一遍包括数据怎么落地、时区怎么统一、信号怎么计算、风险怎么控制以及在真实运行中踩过哪些坑。适合对量化交易感兴趣的技术开发者和具备一定交易经验的投资者参考不涉及任何推荐股票的内容纯技术拆解。1. 跨时区套利的底层逻辑与可行性判断1.1 特价股票的机会到底从哪来很多人第一次听到“特价股票”第一反应是打折促销那种特价其实不完全对。在我的理解里跨时区场景下的特价股核心是“短暂定价失效”带来的折价机会。具体来说有三个来源。第一种是同股不同价。比如某家公司同时在美股和港股上市理论上同一份资产的价值应该一样但受制于两地投资者的风险偏好、资金成本、流动性差异股价天然会存在偏差。当这种偏差大到超过交易成本时就出现了套利空间。第二种是事件驱动的错杀。比如财报发布、重大产品发布、突发舆情这些事件发生在一个市场交易时间段而另一个市场处于闭市状态。闭市中的市场没有办法第一时间消化消息等到它重新开盘时价格才会剧烈跳动。这个“还没反应”的时间差就是利润的来源。第三种是流动性稀薄导致的定价失真。跨时区的早盘和尾盘阶段参与者少订单簿薄几十手的卖单就能把价格砸下去几个百分点这种失真在正常交易时段很难出现。这三种机会有一个共同点它们都不是长期存在的存在的时间窗口短则几分钟长也不过几个交易日。如果没有系统化工具人力基本追不上这种机会。1.2 为什么套利空间没有被瞬间抹平有人肯定会问现在做跨市场交易的资金这么多程序化交易也很成熟为什么还有套利空间存在我的理解是跨时区套利的门槛远比同一个市场内部的套利高得多。首先它要求交易者同时具备多个市场的交易权限和资金进出能力这一条就过滤掉大部分散户。其次多市场之间的数据同步本身就存在延迟看到信号到执行下单之间的路径很长不是普通交易软件能搞定的。第三跨市场套利还要承担汇率波动和隔夜风险这会让很多机构资金的预期收益算不过来账。说白了这个套利空间之所以存在是因为大多数参与者没有能力或者没有意愿去赚这个钱。它要求的基础设施壁垒、技术壁垒和资金壁垒天然筛掉了大部分竞争者。1.3 做这套系统前必须想清楚的四个问题在动工之前我建议你先冷静评估一下否则做到一半容易想放弃。资金量是否够覆盖双边成本。套利不是零风险收益每次交易都要付佣金、滑点、可能的汇率兑换成本如果单笔套利空间只有0.5%但双边摩擦成本占掉0.8%这笔交易做一单亏一单。低于一定资金量收益根本覆盖不了成本。数据源是否稳定可靠。跨时区行情数据需要覆盖至少两个市场的历史行情和实时行情数据源的质量直接决定信号准不准。很多数据供应商对海外市场的覆盖是有延迟的这一点只能买服务商的企业级接口不能省。自己的空闲时间是否能覆盖盯盘需求。虽然是自动化系统但系统的初始化、测试、异常处理都需要人工介入。特别是刚开始跑的前几个月我基本每天都要在开盘时段守着看系统日志。是否接受合理的回撤预期。套利系统不是印钞机它的特点是单次收益不高、胜率相对稳定但一旦遇到极端行情比如说汇率跳空、市场熔断回撤也会很可观。心态上顶不住的话很难坚持下去。这些都是动手之前需要想清楚的问题。如果条件不具备建议先做模拟盘后面我会讲怎么做模拟验证。2. 系统整体架构与核心模块设计2.1 我选择的系统架构思路整套系统的架构我一开始就想清楚了一个原则尽可能用轻量级方案不追求大而全的平台化设计。因为跨时区套利系统最核心的能力是响应速度和准确性而不是功能的丰富程度。整体上分为四层数据接入层、信号计算层、风控决策层、执行模拟层。数据接入层负责从行情源拉取多个市场的历史和实时数据做清洗对齐统一成内部标准格式。信号计算层根据预设的价差算法实时计算套利信号的强度并输出具体标的、方向、开仓和平仓条件。风控决策层对信号做二次确认检查持仓风险、汇率风险、单笔金额上限决定这个信号是否真的执行。执行模拟层对接模拟交易接口记录每一笔模拟单的下单成交情况为后续升级实盘做准备。在选型上数据存储用的是SQLite加Parquet的组合。历史数据量不大SQLite足够应对日常查询实时高频的行情快照则用Parquet做按日分区的列存储查询和分析效率更高。计算层用Python节点调度用Cron和Supervisor做进程管理整个过程没有引入重量级的消息队列或者分布式框架因为这套系统的实时数据吞吐量其实并不高没必要上复杂技术栈。2.2 数据接入与多市场统一时区是基建的地基做跨时区系统最容易被忽略但又最致命的问题是时区。美股是东部时间港股是北京时间A股也是北京时间但夏令时冬令时还会切换。如果数据落地时没有统一时区标准后面的所有计算都会出问题。我的做法是从接入层开始就把所有时间字段统一转换成UTC时间存储。为什么用UTC因为UTC不随季节变化没有夏令时的问题所有市场的开收盘时间都可以通过固定的UTC偏移量推算出来。这里有一个细节值得提醒夏令时切换的那两周美股的本地交易时间不变但UTC偏移会从UTC-5变成UTC-4。如果系统里硬编码了“美东09:30开盘”对应的UTC时间那春秋两季各会出错一次。我的处理方式是维护一个交易日历表每次启动时加载每个市场当天的开收盘UTC时间不靠程序推算直接用配置驱动。数据清洗方面要特别注意除权和停牌。除权日当天历史价格会出现跳空。停牌期间价格不更新如果直接用缺失值填充会把停牌前的价格当作持续可交易价格在信号计算时出现严重的假信号。我是在清洗阶段给每只标的状态打标记停牌期间不参与信号计算。2.3 信号计算层的核心算法与触发条件信号计算是整个系统的大脑。我的核心算法并不复杂主要就两类折溢价回归和均值回归触发。折溢价回归的前提是找到同一公司的跨市场对应标的比如某公司既有美股ADR又有港股正股。通过汇率将两边价格转换为同一个货币单位后实时计算折溢价率。假设港股价格为HKD 100对应美股ADR价格为USD 25.6当前USD/HKD汇率是7.8则ADR对应的港股价格就是25.6乘以7.8等于199.68和港股实际价格100相比出现了接近一倍的差异。这个极端例子说明如果没有考虑到ADR的换股比例这里就会计算出离谱的错误信号。真实的计算要用已发行的ADR数量对应的普通股股数做换算这个数据在公司公告里能查到。折溢价率的计算公式为折溢价率等于美股端换算价格减去港股端价格除以港股端价格。当折溢价率的绝对值超过某个阈值比如历史百分位95%以上同时订单簿深度满足最小成交要求时系统会生成开仓信号方向是买入低估一端、卖出高估一端。平仓条件是折溢价率回归到均值附近同时设置硬止损线。均值回归触发其实是辅助策略主要用在做事件驱动的机会判断上。计算标的价格与N周期移动均线的偏离度Z-score当Z-score超过2.0或者低于负2.0时提示该标的存在超买或超卖。这个策略在跨时区的盘前盘后阶段表现特别明显很多时候美股收盘后欧股市场继续交易相关信息已经在欧股价格里体现但美股还没重新开盘Z-score会直接飙到3以上。这时候系统会记录下来等待美股开盘后观察联动效应。2.4 风控模块的权重比信号模块更高做个简单的套利系统不难难的是把风险控制做好。我把风控模块放在信号模块后面做二次过滤它决定了一个信号是否允许被执行。我的风控规则有几个硬性指标。单标的持仓上限不超过总资金的15%这个比例用来防黑天鹅。单笔交易的最大亏损容忍度是1.5%超过就强制止损不商量。跨市场总持仓的净敞口必须保持在正负3%以内避免因为方向判断错误导致整个组合暴露在系统性风险里。另外汇率波动保护是用远期锁汇逻辑做模拟对冲虽然现在还没有接实盘但这个模块已经在系统里预留了。很多人一开始做套利系统会陷入一个误区把所有精力都放在信号策略上觉得策略越复杂赚得越多到最后面临极端回撤时才发现风控模块的缺位才是最大的风险源。3. 实操过程与核心环节实现细节3.1 数据落地结构设计数据落地是整个项目里最基础的部分但偏偏大部分教程不爱讲。这里把我现在的数据结构直接分享出来基本可以直接照搬。标的基本信息表包含代码、市场、名称、货币单位、对应跨市场标的代码、折算比例、是否启用等字段。行情K线表包含标的代码、市场、时间戳UTC、开盘价、最高价、最低价、收盘价、成交量。日线、小时线、分钟线分不同的表存避免互相干扰。交易日历表这是最容易忘记的包含市场代号、日期、开盘点UTC时间、收盘UTC时间、状态交易中、休市。每年更新一次或者通过数据服务商直接拉取。汇率历史表用于记录汇率的分钟级变化。跨市场套利中汇率是不可忽略的变量我遇到过折算后价差为1.2%但汇率波动吃掉0.8%的情况所以在数据结构里汇率要和行情数据一样保留完整的历史记录。信号日志表记录每一次信号的生成时间、标的、信号类型、触发指标值、当时的价差数据以及是否被风控拦截。这张表是整个系统的黑匣子所有问题排查都靠它。我在上线初期给自己定了一个规矩任何一个信号被生成不管最终有没有执行都要记录完整上下文方便回放。3.2 行情采集与跨时区处理的关键代码行情采集的逻辑并不复杂核心就两个点一是对每个市场独立维护一个采集任务互不干扰二是所有行情数据在写入数据库之前必须完成时区转换和自然时间对齐。这里用Python写一个简化的采集与清洗示例演示如何处理时区问题。from datetime import datetime, timezone import pandas as pd # 示例原始行情时间为美东时间字符串 raw_time_us 2024-05-15 14:30:00 # 美东夏令时偏移为UTC-4 EDT_OFFSET -4 us_eastern timezone(timedelta(hoursEDT_OFFSET)) dt_utc datetime.strptime(raw_time_us, %Y-%m-%d %H:%M:%S).replace(tzinfous_eastern).astimezone(timezone.utc) print(转换后的UTC时间:, dt_utc.isoformat()) # 对齐到分钟级K线 ts_minute dt_utc.replace(second0, microsecond0)注意这里的时区偏移不能写死。夏令时和冬令时要通过一个配置表动态读取我在实际项目里维护了一个常量表每年自动更新。如果你的行情源返回的时间本来就带时区信息那就直接用astimezone(timezone.utc)转换不要手动拼偏移量。采集进程的调度用Cron实现配置大致是这样# 每5秒采集一次美股主连行情 */5 * * * * * cd /opt/trade-engine python3 collector.py --market US /var/log/trade-engine/us_collector.log 21 # 每1分钟同步一次汇率数据 */1 * * * * * cd /opt/trade-engine python3 collector.py --market FX /var/log/trade-engine/fx_collector.log 213.3 特价信号的识别与计算实现信号计算的实现我这里展示一段折溢价套利的核心逻辑。我先声明一下所有参数都基于模拟环境实盘需要根据你的资金量和风险偏好重新调整。假设标的是ADR与港股配对收集实时的美股价格和港股价格。美股代码为US.ABC港股为HK.12341份ADR等于1股普通股没有换股比例问题折算比例直接取1。def calculate_spread(usd_price, hkd_price, fx_rate, ratio1.0): # usd_price: ADR实时价格单位美元 # hkd_price: 港股实时价格单位港元 # fx_rate: USD/HKD汇率 # ratio: 每份ADR对应的普通股股数 usd_equivalent_hkd usd_price * fx_rate * ratio spread (usd_equivalent_hkd - hkd_price) / hkd_price return spread # 假设当前行情 usd_price 25.60 hkd_price 199.68 fx_rate 7.80 spread calculate_spread(usd_price, hkd_price, fx_rate) print(f当前折溢价率: {spread:.4%}) # 当折溢价率超过2%时触发开仓信号 if spread 0.02: print(信号美股高估港股低估考虑买入港股并卖出美股ADR) elif spread -0.02: print(信号美股低估港股高估考虑买入美股ADR并卖出港股)看这个例子如果USD价格25.6、汇率7.8换算港股价格199.68此时HKD价格199.68换算后没有价差spread为0说明两边定价一致不触发信号。真实系统需要动态计算并且把手续费、滑点预先折算进阈值里。对于均值回归辅助策略Z-score计算是这样的def z_score(prices, window20): ma prices.rolling(windowwindow).mean() std prices.rolling(windowwindow).std() return (prices - ma) / std # 最近20个K线收盘价 prices pd.Series([100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119]) z z_score(prices).iloc[-1] print(f当前Z-score: {z:.2f})当z大于2时系统认为价格偏离过大触发事件提醒。这个指标在跨时区场景里有一个比较明显的特性由于市场闭市期间价格固定不动重新开盘时均值快速的拉扯会让Z-score瞬间变化很大如果直接拿它做信号很容易被假突破骗到。我的经验是Z-score信号至少要配合成交量确认比如开盘前15分钟的成交量要高于过去5日同时段均值的1.5倍才认可。3.4 模拟盘的搭建与参数配置建议模拟盘是整个系统最值得花时间的部分。不建议一上来就接实盘原因很简单实盘环境里一个BUG导致的亏损可能直接让你对这个系统失去信心。我搭模拟盘的方式是用历史数据回放加实时延迟模拟。每天收盘后把当天的分钟行情数据存下来然后交易日结束后在本地模拟第二天开盘后的行情流动每次信号触发后按照当时的盘口深度模拟成交用随机数模拟滑点每笔交易记录滑点成本。模拟盘的核心参数建议这样配置初始模拟资金100万港元单笔最大占用资金20%折溢价套利开仓阈值1.8%平仓阈值0.3%硬止损1.2%均值回归策略只在目标市场开盘后30分钟内生效避免闭市跳空汇率对冲方式为模拟远期按隔夜掉期点计算成本。模拟周期建议至少跑6个月覆盖至少一次财报季、一次市场恐慌期和一个完整的夏令时切换周期。没有经过完整周期验证的系统上线实盘纯粹是赌运气。4. 常见问题与排查技巧实录4.1 数据延迟导致假信号的问题跨时区系统的行情数据延迟问题比想象的严重得多。我一开始用的是免费行情源美股延迟大概有15分钟。有一次系统在14:45生成一个强烈的买入信号等到我手动核对时发现那个价格已经是15分钟前的旧报价真实价格早就变了。排查方式是行情接入后先做延迟测试办法是在交易活跃时段对同一只股票同时请求本地行情源的原始时间戳和服务器接收时间戳两者相减得出网络链路延迟和行情源延迟。如果延迟超过5秒这套数据源就只能用于离线分析不能用于实时信号。这个坑对新手来说是致命的。系统的策略再好数据滞后就相当于拿昨天的地图找今天的路。4.2 跨时区夏令时切换的隐蔽故障前面提到夏令时切换会导致UTC偏移变化实际出现过的情况是春季切换后的第一个交易日系统的美股开盘中判断信号全部失效。原因是系统内硬编码了美东时间的偏移量为UTC-5但实际市场已经在夏令时UTC-4运行导致所有时间戳偏移了一个小时系统以为市场还没开盘实际市场已经交易了半小时等系统反应过来时机会早已过去。解决方式是在交易日历表里直接维护每个市场一年内的开收盘UTC时间每天开盘前早晨5点重新加载配置不依赖程序里的时区推导逻辑。4.3 汇率波动吞噬价差利润的教训这个案例对我触动很大。模拟盘上线第三个月系统捕捉到一个折溢价0.9%的套利机会但当天美元兑港元汇率在交易时段内波动了0.5%如果执行开仓后汇率朝不利方向移动整个套利的利润空间瞬间被压缩到几乎没有。从那次之后我的策略里增加了汇率保护逻辑当折算汇率过去5分钟的波动超过0.1%时系统暂停跨市场套利信号等待汇率稳定后再恢复。这个规则可以用一句话概括跨市场套利赚的是价差的钱绝不能在汇率波动剧烈的时候扛风险。4.4 常见问题速查表现象可能原因排查方式解决办法信号生成频率过高行情数据未对齐时区查看信号日志里的时间戳是否乱序统一改用UTC存储重新清洗历史数据折溢价持续为负未考虑换股比例核对ADR与正股的兑换比例从公司公告中获取准确比例开盘后30分钟内频繁触发假信号闭市跳空导致Z-score失真对比信号时间与成交量数据增加成交量确认条件模拟盘长期无信号开仓阈值设得过高检查历史回放中的价差分布根据历史分位数重新设定阈值回测收益为正但模拟盘亏损未计算滑点和手续费检查每笔交易的摩擦成本将滑点预设为2到5个基点4.5 风控与心态上的三点心得第一这套系统最大的价值不是让你抓住每一次机会而是让你在大多数时间什么都不做。等待是常态系统大部分时间都在监测真正下单的次数并不多。第二回测数据里看起来完美的参数实盘环境中都会被现实打败所以保留不低于20%的冗余安全边际。第三无论系统多么自动化我建议每天至少手动复盘一次当天的所有信号、成交、拦截记录都要过一遍很多隐患都是在复盘里发现的而不是运行中发现的。5. 扩展方向与后续演进思路这套跨时区套利系统跑通之后我发现它能扩展的方向还挺多。最直接的是增加标的覆盖范围目前主要集中在港美股联动后续可以加入日股、欧股和新加坡市场的对应标的覆盖面越大资产池越丰富套利机会越多。另一个方向是把同一套架构迁移到ETF与指数期货的跨市场套利上。这类标的的流动性更好数据质量更高摩擦成本相对可控策略逻辑也类似核心还是折溢价回归。还有一块值得探索的是盘前盘后的延伸交易时段。目前很多券商已经支持美股盘前和盘后交易虽然流动性差但特价股票的波动机会恰恰在流动性最差的时候出现。如果风控端做好仓位限制这个时间段有机会捕捉到比其他时段更高赔率的信号。做这个项目最大的感受是跨时区套利系统的技术难度并不在于算法有多复杂而在于把数据、时区、信号、风控、执行这五个环节无缝咬合在一起。每一步单独拿出来都不难但连成一条高效链路靠的是大量的细节打磨和异常处理。我踩过的那些坑总结成一句话就是无论系统多智能最终一定是细节决定胜败。