news 2026/10/16 11:22:59

港股交易系统源码拆解:从拆单算法到新股申购与风控实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
港股交易系统源码拆解:从拆单算法到新股申购与风控实践

刚才整理硬盘里的旧资料,翻出一套去年研究过很久的港股交易系统源码。这套项目的定位很清晰:行情接入、订单管理、大宗交易拆单、新股申购,四个环节串成一套可复用、可二次开发的框架,还附带了全套视频教程。我前后折腾了大半个月,一边看视频一边改代码,踩了不少坑,也补了很多源码里没写透的细节。这篇就把我的拆解思路、关键模块的落地方法,以及文档里不会明说的避坑经验一起梳理出来。

如果你正准备研究港股系统开发,或者想搞明白“一套自动交易框架该有哪些模块”,这篇文章可以帮你省下不少摸黑过河的弯路。我会尽量把每个模块背后的“为什么”讲清楚,同时给出可以直接抄作业的代码和配置思路。

1. 项目整体拆解:这套源码到底解决什么问题

1.1 它服务的真实场景:大单执行与新股批量认购

标题里把“大宗交易”和“新股申购”放在一起,表面看是两件不相关的事,实际都指向同一类用户:管着一定规模资金、需要频繁操作股票账户的机构和工作室。

大宗交易那颗,散户通常觉得交易就是点一下买入卖出,但机构资金完全不是这个玩法。一次买几百万甚至上千万市值的股票,如果直接往盘口砸,冲击成本会非常夸张。所以机构一般找券商对接收单,约定好价格和数量再做批量成交;或者把大单拆散,按时间慢慢消化。这套源码里的大宗交易模块,做的就是后者:拆单执行,并且把拆出来的每一笔委托都放到统一的订单管理里。看了源码之后我更确认,所谓“大宗交易系统源码”,核心不是某个神奇通道,而是拆单、控速、防跟踪这套工程能力。

新股申购则是另一套流程。港股新股认购有现金认购和融资认购两种方式,普通人用券商App点几下就行,但机构要同时打很多只新股,还要算每只票占多少资金、盯死认购截止时间,手动操作非常容易错。源码里的申购模块,本质上是个批量助手——自动生成申购单、计算冻结资金、跟踪中签结果。

1.2 四层架构:这套源码的基本盘

拆开工程来看,它的整体结构不复杂,大致四层:

层级职责对应模块
数据层行情快照、历史K线、交易日历行情客户端、数据仓库
接入层券商交易接口封装、登录鉴权、下单撤单Broker客户端
策略层拆单策略、申购策略、执行逻辑策略引擎
支撑层风控检查、资金计算、日志记录风控模块、财务核算

这个分层几乎是所有交易系统的通用骨架。我拿到任何一套现成源码,第一件事就是看这四层是否完整。如果缺了支撑层,风控和日志都没做,说明它只是毕业论文级的Demo,不能直接拿来跑。

1.3 为什么“源码+视频教程”的组合值得研究

说句实话,现在市面上的源码包和视频课多到泛滥,但大量教程是PPT式念代码,没有任何价值。这套视频我完整看下来,有几个不一样的地方:

  • 每一讲都落在一个可运行的Demo上,不是纯放截图;
  • 会解释接口字段的内涵,比如订单类型、成交状态、交易时段限制;
  • 会演示行情源和券商接口怎么联调、异常怎么抓。

这三点正是新手最容易卡壳的地方。源码给的是“结果”,视频讲的是“过程”,两者配合着来,效率比只看文档高很多。不过也要提醒一句:视频里的接口版本很可能过时,真到了要实盘对接的时候,必须以券商最新的接口文档为准,不要迷信视频里的请求地址和参数格式。

2. 开发前必须吃透的港股底层规则

2.1 交易时段与结算周期:先画日历,再写代码

写交易系统,第一步不是写代码,而是把交易规则刻进脑子里。

港股常规交易时段是上午9:30到中午12:00,下午1:00到4:00,另外还有开市前时段(9:00到9:30)和收市竞价时段(16:00到16:10)。系统必须内置这些时间段,否则很容易在非交易时段把委托提交出去,被券商接口直接拒绝,或者更糟——在集合竞价阶段用了连续交易的限价规则,导致报单价格非法。

结算周期是T+2。今天买入的股票,两个交易日后才完成交收;卖出得到的资金也不是马上就能再用。这一点和A股完全不同,资金计算模块如果漏掉T+2约束,本地算出的可用资金和券商账单必然对不上。

我建议工程里单独维护一个交易日历文件,把节假日、半日市(比如除夕前的半天交易)、特殊时间安排都显式列出来。不要用“今天是星期几就开盘”的假设,港股市场偶尔会有非周末休市,用固化的自然日判断迟早会翻车。

2.2 手数与价格精度:两个最容易出Bug的字段

A股有“100股一手”的默认习惯,港股不一样。每只股票上市时都会规定一手是多少股,可能是100、200、500、1000甚至2000股。下单数量必须校验“是一手的整数倍”,否则券商会直接拒绝。源码里维护了一个证券信息表,把股票代码、名称、一手数量、最小价格变动单位放在一起,下单前查表校验。

价格精度同样是个大坑。不同股价区间的最小变动单位不一样,低价股可以0.001港元一跳,高价股可能是0.5港元一跳。如果直接拿浮点数做价格加减,很容易得出非法价位,到了券商那边就是“价格不在有效档位”的报错。但凡涉及金额和价格,统一用Decimal定点数计算,再用配置表维护tick size。

from decimal import Decimal, ROUND_DOWN def floor_price(raw_price): tick = get_tick_size("HKG:STOCK_A") price = Decimal(str(raw_price)) factored = (price / tick).to_integral_value(rounding=ROUND_DOWN) return factored * tick

这一段看起来不起眼,却是实盘时最容易捅娄子的地方。我接手这套源码时,第一轮回测就有3%的单子因为价格精度问题被拒,全改Decimal之后才归零。

2.3 大宗交易模块到底在实现什么

很多人听到“大宗交易”就想到固定价格盘后成交,但港股市场并没有A股那种显式的盘后大宗交易平台。机构大单通常是场外协商,找对手方谈好量价,再通过系统批量申报;或者直接用算法在盘内慢慢消化。这套源码里的大宗交易模块,管的是后面这件事:大单拆小单、控制下单节奏、降低市场冲击。

经典拆单策略主要有三种:

  • TWAP(时间加权):把总量按时间平均拆分,10万股拆成20笔,每笔5000股,隔10分钟下一单,适合流动性相对平均的标的;
  • VWAP(成交量加权):参考历史成交量的时间分布,在成交量大的时段多下单,成交量小的时段少下,实际执行均价更贴近市场平均价;
  • 冰山单:单笔委托只暴露一小部分数量,比如只报500股,隐藏的9500股等成交后自动补上,防止被其他资金盯上。

源码默认实现的是TWAP。我在改造时把VWAP也补了进去,因为港股很多中小票的流动性极度集中,成交量在开盘和尾盘占比很高,纯均分时间根本买不完。

2.4 新股申购流程:不算资金就敢认购,等着爆仓

港股IPO认购流程可以简化成三步:

第一步,公开发售期内发起认购。现金认购买入多少就冻结多少资金;融资认购(孖展)需要计算可融资比例和对应的利息成本。

第二步,申购截止后等待分配。公开发售部分按甲组、乙组进行分配,少量申购的账户中签概率有限,机构一般会拆成多个账户申购来提高中签率。

第三步,未中签部分退款,中签股份在上市前一个交易日划入账户。

对系统来说,真正难的是第一步和第二步:认购前需要估算总成本、校验资金、判断截止时间。不同券商对申购截止时间的处理不一样,有的在发售最后一天中午,有的在下午。源码里把这个时间做成了配置项,而不是写死,这样最靠谱。

3. 核心模块实现:订单、拆单、申购、风控

3.1 工程结构与基础配置

先看目录设计。一套好维护的交易系统,工程结构要能让你在三分钟内定位任何一块逻辑:

project/ config/ config.yaml securities.csv core/ broker.py market.py order.py risk.py strategies/ twap.py vwap.py ipo.py utils/ datetime_utils.py decimal_utils.py logger.py main.py

config.yaml里至少要包含以下配置项:

broker: endpoint: "https://api.example-broker.com" account_id: "your_account" auth_file: "credentials.json" market: data_source: "websocket" symbols: ["HKG:STOCK_A", "HKG:STOCK_B"] risk: max_single_order_value: 1000000 max_position_ratio: 0.2 max_ipo_sub_amount: 500000

这里有一个必须强调的实操细节:认证信息、API密钥、证书文件要独立放一个文件,并且加入.gitignore。很多人买了源码之后直接把密钥写进配置文件,再顺手推到Git仓库,等于把账户权限挂在公网上。

3.2 订单状态机:系统最值得写好的地方

订单从创建到结束要经过多个状态,源码里简化后如下:

CREATED -> SUBMITTED -> PARTIALLY_FILLED -> FILLED -> PENDING_CANCEL -> CANCELLED -> REJECTED

每次收到交易所回报或券商回调,都走一遍状态迁移:

class OrderStateMachine: valid_transitions = { "CREATED": {"SUBMITTED"}, "SUBMITTED": {"PARTIALLY_FILLED", "FILLED", "PENDING_CANCEL", "REJECTED"}, "PARTIALLY_FILLED": {"FILLED", "PENDING_CANCEL", "REJECTED"}, "PENDING_CANCEL": {"CANCELLED"}, } def transition(self, old_state, new_state): if new_state not in self.valid_transitions.get(old_state, set()): raise InvalidOrderStateError( f"非法状态迁移: {old_state} -> {new_state}" )

代码本身不难,但它决定了整套系统的可靠性。网络延迟、接口重试、回报丢失都是常态,如果状态机写得随意,非常容易出现撤单撤不掉、订单实际成交但本地标记为已取消这类致命问题。

我在这套源码基础上加了一个“回报去重”的机制:每条成交流水都用券商侧的回执编号做主键,重复收到的回报只更新一次。原因很朴素,实盘环境中WebSocket断线重连会造成重复推送,去重逻辑是刚需。

3.3 大宗交易拆单策略实现

TWAP策略在源码里的实现长这样:

from datetime import datetime, timedelta class TWAPExecutor: def __init__(self, symbol, total_quantity, duration_minutes, interval_minutes): self.symbol = symbol self.total_quantity = total_quantity self.duration_minutes = duration_minutes self.interval_minutes = interval_minutes self.executed = 0 def generate_slices(self): slices = [] remaining = self.total_quantity slot_time = datetime.now() plan_count = self.duration_minutes // self.interval_minutes base_qty = self.total_quantity // plan_count while remaining > 0: qty = min(base_qty, remaining) slices.append({"time": slot_time, "quantity": qty}) remaining -= qty slot_time += timedelta(minutes=self.interval_minutes) return slices

注意三个细节:

  • 最后一片必须用剩余总量兜底,否则所有切片加起来小于委托总量,策略会“莫名少买”;
  • 拆出来的单不能直接提交,要再过一遍风控模块,因为单笔金额上限是动态的,可能随着持仓变化变小;
  • 如果某一笔因为行情剧烈波动没有成交,不要闷头继续下一片,应该暂停整个策略并告警。源码保留了这个保守设计,我举双手支持。

VWAP的改造思路也不复杂:对目标股票取过去20个交易日的分钟成交量分布,把总数量按“该时段的成交量占比”加权分配。核心代码就多了一个权重映射:

def generate_vwap_slices(self, volume_profile): total_volume = sum(volume_profile.values()) for slot, volume in volume_profile.items(): weight = volume / total_volume qty = int(self.total_quantity * weight) slices.append({"time": slot, "quantity": qty})

实际跑的时候,TWAP适合大盘蓝筹,VWAP适合日内成交量分布极不均匀的中小盘票,两者没有绝对优劣。

3.4 新股申购模块实现

申购模块要做三件事:计算成本、生成申购单、跟踪状态。成本计算最容易被低估,因为融资申购的利息会拖累真实收益。

from decimal import Decimal class IPOSubscription: def __init__(self, symbol, price, lots, financing_ratio=0.0): self.symbol = symbol self.price = Decimal(str(price)) self.lots = lots self.financing_ratio = Decimal(str(financing_ratio)) def required_fund(self): lot_size = get_lot_size(self.symbol) total_shares = lot_size * self.lots total_cost = total_shares * self.price cash_needed = total_cost * (Decimal("1") - self.financing_ratio) interest = self.estimate_interest(total_cost) return total_cost, cash_needed, interest def estimate_interest(self, total_cost): # 融资比例为 M,年化利率为 r,占用天数为 d financed = total_cost * self.financing_ratio return financed * Decimal("0.038") * Decimal("4") / Decimal("365")

融资申购的利息按天计算,且要等到中签结果出来后才真正扣收。如果系统只把“申购总额”当作冻结资金校验,而忽略利息估算,账户资金曲线和真实账单会差出不少。

申购单生成之后,要监控两个时间点:券商申购截止时间和中签公布时间。合理做法是把这两个时间做成配置,每天开盘前扫描一次,如果快到截止时间还有未提交的申购计划,自动触发提醒或提交。

3.5 资金风控校验:先保命,再谈收益

风控模块把“能不能做”变成规则,在下单前强制执行。我保留并强化了源码里的三个基础规则:

class RiskManager: def __init__(self, cash_balance, positions): self.cash_balance = Decimal(str(cash_balance)) self.positions = positions def can_place_order(self, symbol, quantity, price): order_value = Decimal(str(price)) * quantity if order_value > Decimal("1000000"): return False, "单笔金额超过风控上限" if self.cash_balance < order_value: return False, "可用资金不足" symbol_position = self.positions.get(symbol, 0) max_position_value = Decimal("5000000") if order_value + symbol_position > max_position_value: return False, "单票持仓超过上限" return True, "ok"

风控规则不是越多越好,但至少要有三条:单笔金额上限、单票持仓上限、账户总资金占用上限。而且这些参数必须放在配置文件里,可以随时调整。把任何一条规则写死在代码里,后面调整都要改代码重新部署,完全没有灵活性。

3.6 日志与监控:系统靠不靠谱,看这里就知道

很多人拿到源码先看策略逻辑,我反而先看日志模块。一套交易系统的日志必须记录三类信息:关键事件、数据快照、异常堆栈。

  • 关键事件:下单、撤单、成交、拒绝,记录动作发生时间、订单ID、参数内容和操作人;
  • 数据快照:每次行情推送时,记录用到的盘口数据、最新价、成交量,事后才能回放;
  • 异常堆栈:任何接口异常、数据断流、状态迁移失败,都要记录完整堆栈和上下文。

源码自带的日志比较简单,我改造时给每一条日志加上了trace_id,从策略触发到下单、回报、风控检查全链路串起来。遇到问题的时候,用trace_id一条命令拉出整条链路,排查效率提升非常大。

4. 常见问题排查与实盘避坑实录

4.1 高频问题速查表

问题现象典型原因解决办法
下单被拒,提示“非交易时段”本地时间与交易所时间不一致统一用交易所时间戳,配置交易日历
回报回执丢失,订单卡在SUBMITTED没有补偿查询机制定时轮询订单状态,配合幂等处理
委托数量不是一手整数倍没有维护每只股票的手数证券信息表显式维护lot size
价格精度错误,出现非法价位用float做价格计算价格与金额统一用Decimal
行情断流后策略不暂停缺少心跳检测连续N次无行情,自动挂起策略
申购截止时间算错把配售截止误当券商截止截止时间配置化并提前告警

这些表里的坑,基本覆盖了实盘前六个月会遇到的七成问题。源码本身没有把这些全部解决,很多是靠我在调试中一点一点补上的。

4.2 深度复盘:重复下单是怎么发生的

最容易出大问题的场景,是网络超时后的重复下单。订单实际已经到了券商服务器,但本地没有收到确认,如果直接重发,就会变成一笔策略跑两次,轻则多扣手续费,重则仓位翻倍直接突破风控线。

解决办法是给每笔委托生成唯一的客户端订单号(client_order_id)。券商接口一般支持以它作为幂等键,本地重试时沿用同一个订单号,而不是每次重试都生成新的。源码里这个设计做得不够稳,我单独写了一个order_key生成器,把策略ID、股票代码、拆分序号、时间戳合并起来,保证同一笔业务重试多次仍然使用同一个幂等键。

每次真实故障复盘,最后都会回到一个问题:能不能幂等。下单要幂等,撤单要幂等,状态查询也要幂等。这个原则应该写进系统设计的第一页。

4.3 实盘前必须完成的检查清单

我这里列一份自己实盘前用的清单,每一条都真实踩过:

  • 先用模拟盘或券商沙箱连续跑一个完整交易周,中间包含一个非交易日;
  • 用不超过1万港币的小资金跑真实行情,验证下单、撤单、回报全链路;
  • 手工模拟三种故障场景:网络断开、进程崩溃、券商接口拒单;
  • 检查日志里能否完整还原一笔订单从创建到成交的调用链;
  • 核对账户资金曲线,确保每一笔冻结、释放、手续费都和券商账单一致。

我现在的验收标准只有一句话:是否对得上券商账单。系统内部算得再完美,只要对不上账单,等于没做对。

5. 视频教程的正确食用方式与二次开发建议

5.1 建议学习路径

这套视频教程有十几讲,不建议一口气看完。我是按四轮来刷的,效果比线性看课好很多:

第一轮:只看环境搭建和行情接入,把所有依赖装好,确认行情能稳定打印出来。这一轮不用碰策略,先把地基打牢。

第二轮:带着订单模块做,尝试不看源码默写状态机和下单流程。写不出来就再看一遍视频,直到能独立写完。

第三轮:把拆单和申购模块的代码读通,画出调用链图,标注每个方法的输入输出。

第四轮:自己做一个小改动,比如把TWAP的参数从5分钟改成3分钟,观察日志变化。能自己改出日志差异,才算真正理解。

5.2 源码审查重点

拿到任何一套源码,我都会先做一轮安全与结构审查:

  • 检查依赖版本是否锁死,特别是认证协议相关的老旧依赖,可能存在兼容性问题;
  • 确认没有把密钥、口令提交进代码库;
  • 看订单状态处理是否覆盖了拒绝、部分成交、撤单失败三大分支;
  • 看交易日历是否可配置,而不是用自然日简单判断;
  • 重点看异常模块是不是只是“print”了事,有没有做告警和补偿。

如果这五条里面有三条不过关,不建议往里面填自己的策略代码,维护成本会高到爆炸。

5.3 后续扩展的几个方向

这套源码的扩展空间其实很大,我列几个自己正在做的方向:

  • 多券商接口冗余:行情和交易都做故障切换,避免单一通道断掉导致策略裸奔;
  • 自适应拆单:根据盘口深度和成交量动态调整每一笔的暴露量,而不是死板固定间隔;
  • 多标的批量认购:申购模块扩展成批量处理,自动分配各账户资金,输出汇总报表;
  • 盘后对账模块:以上市公司公告、券商成交文件为准,自动核对系统内记录。

每一条扩展都意味着大量的边界情况,但也正是这些补充,让一套通用源码慢慢变成自己的武器。

最后再分享一点个人体会。源码和视频再怎么完整,也只是给你一套模板,真正值钱的是你在调试过程中建立的判断力。我当初把这套系统跑起来之后,第一件事不是急着谈收益,而是连续盯了一整周日志,把每一笔异常都做了注释。等到你能不看文档,就能说出某个状态在什么情况下出现、某种报错大概是哪一层抛出来的,这才说明你真正掌握了这套东西。

如果让我重新选一次,我依然会觉得这套“大宗交易+新股申购”源码值得深入研究,但我不会再犯一个错误:拿到手就套默认配置上实盘。所有参数,尤其是时间窗口、单笔限额、拆单间隔,都必须在回测和模拟环境里用自己的标的多验证几遍。程序化交易里,风控不是让你赚钱的,而是让你活着等到赚钱的那天。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 11:22:45

KUKA SIM PRO机器人仿真实战:坐标系、KRL编程与节拍验证避坑手册

简介&#xff1a;工业机器人项目交付前&#xff0c;离线编程与仿真验证是降低调试风险的关键环节。通过建立与真实控制器同源的运动学模型&#xff0c;工程师可以在虚拟环境中完成工作单元搭建、路径示教与干涉检查。坐标系映射、工具TCP标定及外部轴配置直接影响仿真与实机的一…

作者头像 李华
网站建设 2026/10/16 11:21:09

YARN命令与Rest API实战:集群资源调度诊断与自动化运维

1. 项目概述&#xff1a;YARN不是“ yarn”毛线&#xff0c;而是集群资源调度的中枢神经很多人第一次看到YARN&#xff0c;下意识念成“yarn”&#xff08;毛线&#xff09;&#xff0c;结果在技术讨论里被问&#xff1a;“你打算用毛线编个调度器出来&#xff1f;”——其实YA…

作者头像 李华
网站建设 2026/10/16 11:21:07

C++ Socket底层原理与高并发实战指南

1. 项目概述&#xff1a;为什么“C SOCKET面试题”至今仍是硬通货&#xff1f;在某高校计算机系的嵌入式系统实验室里&#xff0c;我带过三届实习生&#xff0c;每次安排网络编程模块前&#xff0c;都会先扔出一份手写的“C SOCKET基础问答清单”。不是为了考倒谁&#xff0c;而…

作者头像 李华
网站建设 2026/10/16 11:21:00

PyTorch实战:从动态图原理到训练部署的全链路经验

1. 为什么绕了一圈&#xff0c;最后还是回到PyTorch 我是做深度学习方向落地的&#xff0c;这几年框架换来换去&#xff0c;TensorFlow用过、PaddlePaddle也试过、JAX也尝过鲜&#xff0c;但真正到了要交项目、出结果的时候&#xff0c;我的主力工具始终是PyTorch。这倒不是说什…

作者头像 李华
网站建设 2026/10/16 11:19:02

JPG批量转npy加速图像数据加载:完整方案与踩坑实录

做图像训练的人&#xff0c;应该都有过这种体会&#xff1a;数据文件明明不大&#xff0c;但每次训练前光是把图片读进来就等得人心烦。我最近处理一个项目时&#xff0c;文件夹里堆了上万张JPG&#xff0c;每轮迭代都要重新读取、解码、缩放&#xff0c;数据加载的时间比模型计…

作者头像 李华
网站建设 2026/10/16 11:18:12

Gradle 7.2-all.zip 全量包下载与源码级调试实战指南

简介&#xff1a;本资源为Gradle 7.2版本的完整离线发行包&#xff08;gradle-7.2-all.zip&#xff09;&#xff0c;专为Android开发者、Java工程构建人员及Android Studio用户设计&#xff0c;旨在解决国内网络环境下在线下载Gradle缓慢、超时或失败导致的项目编译卡顿问题。压…

作者头像 李华