越南市场量化交易入门:VN30与HOSE API接入实战指南

发布时间:2026/10/5 2:46:26
越南市场量化交易入门:VN30与HOSE API接入实战指南 如果你准备在2026年做越南市场的量化交易或行情监控第一关绝对是VN30和HOSE的API接入。VN30是胡志明交易所HOSE的核心股票指数做越南市场策略的人绕不开它。我最早接越南行情的时候网上资料少得可怜官方文档又大多只有越南语和英语光是认证和WebSocket断线就折腾了大半个月。这篇东西就围绕VN30和HOSE的API接口把从注册、认证、行情到交易下单一整条链路讲清楚适合有编程基础、想快速上手越南证券接口的开发者。文章不会去推荐任何具体投资标的也不替任何人做交易决策纯粹是技术接入层面的事。后面所有代码示例都以某券商开放平台的常见接口风格为例字段名可能和你的平台不完全一致但核心逻辑是通用的。你只要理解了这套流程换任何一家数据源都能很快适配。1. 先弄清楚你在接什么VN30、HOSE与API生态1.1 VN30不是一只股票是一篮子股票我第一次看到VN30这个代码时下意识以为它是一只股票后来才发现它是一个指数。VN30全称VN30 Index由胡志明证券交易所HOSE编制筛选的是HOSE中市值规模大、流动性好的前30只股票地位有点像A股的上证50。这也意味着当你面对VN30相关的API接口时看到的不是一个单独的证券代码而是一组证券指数点位、30只成分股、每只股票的权重最少三个维度都要有。很多刚接触越南市场的开发者只拉一个指数价格就开始做分析结果跟实际盘面对不上。原因很简单指数点位是依赖成分股权重计算出来的各家数据源对权重的更新频率也不一样有的按日更新有的按周更新。你要做的第一件事不是急着调行情接口而是先确认API文档里把VN30当作一个可订阅的指数标的还当成一个成分股列表的查询条件。这两种设计对应的接口路径完全不同搞错了后面全乱。还有一个容易踩的坑VN30成分股是定期调整的每年会有几次审核和替换。如果你本地缓存了旧的成分股列表又没有做同步时间一长你的行情程序和交易脚本就会覆盖到已经剔除的股票。建议在对接API第一天就设计一个每日收盘后的同步任务把最新成分股列表和权重拉下来放到数据库或配置文件里后续所有程序都从统一缓存读取。1.2 行情和交易数据从哪来三类数据源怎么选越南的证券数据API不像美股市场那么统一没有像Polygon、IEX这种一家通吃的聚合商而是分散在交易所、券商和第三方数据公司手里。做个人项目或小团队量化我建议先搞清三类数据源的区别再决定接入哪家。数据源类型典型代表优势劣势交易所官方网关HOSE官方数据服务权威性最高实时性强接入门槛高费用贵流程复杂券商开放平台SSI、VNDIRECT等行情交易一体有模拟盘数据覆盖面可能被阉割第三方数据商FiinTrade等数据整理好适合回测实时性弱价格偏高券商开放平台是大多数个人开发者和小团队的首选。越南一些主流券商都提供面向程序化交易的REST和WebSocket接口注册开发者账号后可以申请模拟盘完全免费。行情和订单接口在同一个系统里不需要自己拼接两套身份认证开发效率高很多。缺点是有时候K线历史长度有限或者某些财务字段不开放需要额外找其他数据源补齐。第三方数据商我一般只用来做研究分析和回测准备工作。他们会把原始行情做复权、清洗、对齐省掉很多脏活。但实时性通常比券商接口差有的延迟几分钟日内高频策略基本用不了。交易所直连适合机构级自建系统个人开发者的资源和预算都很容易被门槛卡住。我的建议很直接先用券商模拟盘打通全流程确认系统架构没问题再根据实际需要决定要不要上更高成本的实时数据源。2. 认证与连接API Key、签名和限流规则2.1 API Key的获取与存储别在代码里裸奔不管哪家平台接入流程第一步都是在开发者门户注册应用然后获得一对API Key和API Secret。有的新平台还会额外给你clientId、accountId甚至要求绑定IP白名单。这个过程本身不难但很多人在第一步就埋下了401的隐患。我在调试日志里经常看到unexpected status 401 unauthorized: incorrect api key provided。这种错误表面上是API Key不对实际原因五花八门有人从控制台复制Key时把遮挡星号一并复制进去了有人把测试环境的Key填进了生产环境地址还有人把环境变量名写错程序压根没读到Key。排查401的时候先不要怀疑接口写错先从Key本身查起。正确的做法是把API Key和Secret存到环境变量或本地的.env文件里代码中统一用os.environ.get()读取。这样既不会因为硬编码泄露密钥也能在环境切换时不用改代码。注意Secret是签名用的绝对不能出现在前端页面、日志或任何能被别人看到的地方。有些平台支持设置IP白名单本地开发时如果把IP配错也会变成401或403。2.2 请求签名与时间戳理解HMAC-SHA256怎么拼越南主流的券商开放平台很少允许直接用API Key裸调接口绝大多数都要做请求签名。签名过程听起来高级其实就是把请求的关键信息拼成一个字符串再用Secret算一个HMAC-SHA256值放进请求头里。我习惯用一个生活类比来解释你要寄一个快递快递单上既要写收件人又要贴一个防伪码快递公司只有拿自己的密码本核对了这个防伪码才会确认包裹是你寄的。签名规则各家细节不同但基本都包含几要素请求方法、请求路径、时间戳、请求体或查询参数。以常见的签名方式为例拼接逻辑大概是时间戳、换行、请求方法、换行、路径、换行、参数然后使用HMAC-SHA256加密最后把签名摘要放进X-Signature头里。下面这段Python代码是我封装好的通用签名逻辑import os import time import hmac import hashlib import requests API_BASE https://api.example.com/v1 def build_headers(method: str, path: str, params: dict None): api_key os.environ[VN30_API_KEY] api_secret os.environ[VN30_API_SECRET] timestamp str(int(time.time() * 1000)) # 参数需要先按字典序排序拼成 query string query if params: query .join( f{k}{v} for k, v in sorted(params.items()) ) message f{timestamp}\n{method}\n{path}\n{query} signature hmac.new( api_secret.encode(utf-8), message.encode(utf-8), hashlib.sha256, ).hexdigest() return { X-Api-Key: api_key, X-Timestamp: timestamp, X-Signature: signature, } params {symbol: VIC, interval: 1D} resp requests.get( f{API_BASE}/market/ohlc, paramsparams, headersbuild_headers(GET, /market/ohlc, params), ) print(resp.json())要注意几点。第一不同平台对签名字符串里的“参数”定义不同有的是只签路径和Query有的是连请求体一起签。第二Query参数必须按字典序排序否则签名结果不一致。第三本地机器时间一定要做NTP同步很多平台会校验时间戳允许的偏移窗口通常是5分钟以内。你要是手动改过系统时间哪怕改回当前时间签名也会间歇性失败因为系统时间戳发生过跳变。2.3 限流、并发与重试别把免费额度当无限量越南券商的API限流策略普遍比美股平台保守。有的按每秒请求数限制有的按每分钟积分或调用量配额特别是行情接口官方文档里写着“免费额度”的超了之后要么直接拒绝要么把响应速度拖慢。我接过的平台里有一个免费档只允许每秒2次REST请求写循环拉30只成分股都要小心触发限流。我建议在客户端做三层防护。第一层在代码层用threading.Semaphore限制最大并发数第二层在循环里设置最小请求间隔比如每只股票之间至少隔200毫秒第三层对HTTP 429和503响应使用指数退避重试。退避策略别一上来就sleep很久我常用初始500毫秒每次翻倍最大不超过10秒最多尝试4次。如果4次还不行就放弃当前批次等到下一个调度周期再重试。这里要特别提醒重试只适合幂等接口比如查询行情、查询订单状态。对于下单接口盲目重试可能导致重复下单。后文讲到交易接口时会专门说幂等键的问题。3. 行情数据接口开发实录一步步打通VN30数据链路3.1 获取VN30成分股列表是一切行情任务的地基无论你是要做指数仪表盘还是套利策略第一步永远是先拿到VN30的成分股列表。这类接口通常命名为/index/constituents或/stock/list传入indexVN30就能拿到股票代码列表和权重信息。响应体常见结构是这样的{ data: [ { symbol: VIC, companyName: Vingroup JSC, weight: 12.35, totalShares: 3824000000, freeFloatShares: 2210000000 }, { symbol: HPG, companyName: Hoa Phat Group JSC, weight: 10.02, totalShares: 5847000000, freeFloatShares: 3800000000 } ], lastUpdated: 2026-01-15 17:00:00 }要注意重点不是把列表打印出来而是建立一份本地映射表。字段里symbol是越南股票代码通常是三个或四个英文字母weight有的平台返回百分比数值比如12.35代表12.35%有的平台返回小数0.1235。如果你不做换算后面算指数跟踪误差会差整整100倍。我踩过一个更隐蔽的坑有些接口返回的freeFloatShares是流通股数有些返回的是可交易股数两者可能因为股东锁定期出现差异影响自由流通量权重。做组合展示可以凑合做风控就必须搞明白。建议把原始响应完整落库别只挑几个字段存万一后面要核对细节你不需要回头重新拉历史。3.2 拉取实时行情与K线注意单位和批量参数成分股列表到位后下一步就是拉行情。实时快照接口一般是/market/quote返回最新价、涨跌额、涨跌幅、成交额、买卖盘五档。K线接口一般是/market/ohlc需要传symbol、interval和limit。常见周期有1m、5m、15m、1h、1D和全球主流交易平台的习惯基本一致。这里有个容易踩的坑越南股票计价货币是越南盾不同API对价格的表示方式完全不统一。有的平台返回整数比如150500表示每股150500越南盾有的平台为了省存储返回150.5单位却是千盾。一定要看字段说明里的unit不能直接拿来算乖离率或均线。同样成交量字段也有两种可能要么是股数要么是手数一手等于100股。做资金管理和仓位计算时必须有一个统一的换算层不能拿裸字段到处用。批量请求也需要提前确认。大部分平台支持symbolsVIC,HPG,SSI这种逗号分隔一次返回多只股票的行情避免每次只拉一只的低效循环。如果只能用单只股票接口拉那就用线程池并发然后严格控制QPS。下面是批量拉取K线的示例封装from concurrent.futures import ThreadPoolExecutor SYMBOLS [VIC, HPG, SSI, MSN] def fetch_ohlc(symbol: str): params {symbol: symbol, interval: 1D, limit: 500} resp requests.get( f{API_BASE}/market/ohlc, paramsparams, headersbuild_headers(GET, /market/ohlc, params), ) resp.raise_for_status() return symbol, resp.json()[data] with ThreadPoolExecutor(max_workers4) as executor: results list(executor.map(fetch_ohlc, SYMBOLS)) for symbol, klines in results: print(symbol, len(klines), klines[-1])线程数不建议开太高最多4到6个就够了。越南券商的行情服务一般不是为高频并发设计的线程开多了只会更快触发限流响应反而更慢。3.3 WebSocket推送别再傻傻轮询了实时监控和程序化交易如果用REST接口轮询很快就会撞上限流而且延迟也下不来。越南主流平台基本都支持WebSocket行情推送但各家实现风格差别很大。有些平台用标准的JSON订阅消息有些则用自定义二进制帧还有的要求先登录再订阅。接入前第一件事不是复制代码而是先把文档里的“订阅-推送”流程图看明白。以最常见的JSON格式为例连接后要发送一个订阅消息指定action、channel、symbols和subscription_id。之后服务端会持续推送快照或增量更新。下面是简化版本import json import websocket WS_URL wss://api.example.com/ws def on_message(ws, message): data json.loads(message) if data.get(event) snapshot: print(快照:, data[data]) def on_error(ws, error): print(连接异常:, error) ws websocket.WebSocketApp( WS_URL, on_messageon_message, on_erroron_error, ) def on_open(ws): subscribe_msg { action: subscribe, channel: market.snapshot, symbols: [VIC, HPG, SSI, MSN], subscription_id: vn30_demo_001, } ws.send(json.dumps(subscribe_msg)) ws.on_open on_open ws.run_forever(ping_interval20, ping_timeout10)WebSocket最让人头疼的是断线。网络波动、服务端超时、代理服务器回收空闲连接都会导致连接断开。日志里最常见的就是connection dropped (econnreset)这通常不代表代码写错了而是连接不够“活跃”。解决思路有三个定时发送心跳ping一般20到30秒一次维护一份订阅列表断线重连后自动重新订阅把消息解析放到独立线程避免UI或主循环被阻塞。做到了这三点WebSocket连接基本能稳定跑一整天。4. 交易接口与订单管理实战4.1 下单前先熟悉撮合逻辑别拿A股习惯套用很多人从A股程序化转过来下意识觉得下单逻辑哪里都差不多接完越南券商的API才发现订单状态机和交易时间差别很大。越南股市的交易时间通常是工作日9:00到15:00中间有午休部分平台还区分连续竞价和集合竞价时段。你需要先确认自己的下单逻辑是否适配委托时段否则在午休窗口提交订单很可能直接返回“交易所拒绝”。订单类型也要认真看文档。常见的有限价单LO、市价单MP、止盈止损单STOP还有一些平台支持开盘价集合竞价单ATO和收盘价集合竞价单ATC。我建议在代码里用枚举把这些类型定义清楚不要用裸字符串到处传。因为下单接口的类型字段经常是缩写LO和ATO可能分别代表限价单和开盘竞价单一旦填错撮合逻辑完全不一样。另外越南市场目前存在T1或相关的交收限制当天买入的股票不能立刻卖出这个规则和美股差别很大。API层面不一定强制拦截但实际清算时会限制可卖数量。如果你做日内策略必须提前在业务逻辑里判断“可卖持仓”不能只看总持仓就直接下单。4.2 下单、撤单与持仓查询把订单状态机做对交易接口的编写比行情接口更小心因为涉及资金和持仓。下单接口一般长这样POST /trade/order请求体包含symbol、quantity、price、side、orderType、accountId。越南一些券商平台要求传accountId而不是portfolioId这两个ID在开户流程里是不同概念搞混了下单会失败。def place_order(symbol: str, quantity: int, price: float, side: str): payload { symbol: symbol, quantity: quantity, price: price, side: side, # BUY / SELL orderType: LO, # 限价单 accountId: os.environ[VN30_ACCOUNT_ID], } resp requests.post( f{API_BASE}/trade/order, jsonpayload, headersbuild_headers(POST, /trade/order, payload), ) resp.raise_for_status() order resp.json()[data] print(orderId:, order[orderId]) return order def cancel_order(order_id: str): resp requests.delete( f{API_BASE}/trade/order/{order_id}, headersbuild_headers(DELETE, f/trade/order/{order_id}, ), ) return resp.status_code 200这里要重点记住两个事情。第一下单接口务必实现幂等键机制。很多越南券商的API支持在请求头里传X-Idempotency-Key你的客户端生成一个UUID失败重试时保持同一个Key。这样即使发生了超时服务端也能识别这是同一笔订单避免重复下单。第二订单状态码各家数字含义不同但基本都有已报、部成、全成、已撤、废单。不要假定特定数字一定要把状态码枚举映射放在独立模块里并写单元测试。查持仓接口通常返回symbol、totalQuantity、availableQuantity、averagePrice和unrealizedPnl。其中availableQuantity才是可卖数量totalQuantity是包含冻结和锁定部分的总量。做仓位管理时一律用availableQuantity否则会撞上卖不出去的尴尬。4.3 模拟盘的真实价值是验证你的订单状态机越南券商的开发者平台通常提供模拟盘有些叫Paper Trading。很多人觉得模拟盘就是给策略做回测用的其实最大价值是验证你的接入代码。订单状态机处理得好不好、网络断线后会不会漏单、部分成交后如何计算剩余数量这些问题在模拟盘里都能暴露得很彻底。我自己的经验是跑通模拟盘并不是简单地下一个订单然后等成交就完事。一定要做压力场景测试。比如下一个限价单故意挂得远一点订单进入Pending状态这时再撤单看看会不会出现竞态问题再比如模拟一次网络断开断线重连后查询订单确认没有重下。把这些边界情况都测完再切换到真盘你会轻松很多。不要把模拟盘当成摆设它是一面免费的照妖镜。5. 接口调试常见问题与避坑指南5.1 401 Unauthorized按这个顺序排查在我接过的所有统一交易接口里401出现频率最高。日志里常见unexpected status 401 unauthorized: incorrect api key provided但真实的错误根因往往不是Key本身。排查顺序我总结成了下面这张表现象常见原因处理办法incorrect api key复制了遮挡符、环境变量未加载、Key已过期用print(len(api_key))先核对长度去控制台重新复制invalid timestamp本机时间偏差、手动修改过系统时间开启NTP同步signature mismatch签名字符串拼接顺序错、Query未排序对照文档逐字核对拼接规则forbidden / missing scope接口权限未申请、域名与Key环境不匹配检查权限申请状态和域名环境ip not allowedIP白名单未配置在开发者后台加入当前出口IP有一个问题容易被忽略测试环境和生产环境往往各有一套API Key你在测试控制台拿到的Key去请求生产域名一样会报incorrect api key。看到401先看请求的域名再检查Key归属环境顺序不能乱。5.2 限流、超时与断线处理的正确姿势行情REST接口返回429或503时普通指数退避就能解决。但交易接口遇到429要格外小心如果请求已经到达服务器但响应超时你并不能确定订单是否已经被受理。这时候唯一的保护伞就是幂等键。没有幂等键的情况下宁可先去查订单列表也不要在超时后立刻重新发单。WebSocket连接断开的处理前面说过我在这里再补充一个细节很多断线不是服务端主动断的而是防火墙或负载均衡器在空闲一段时间后清理了连接。所以即便你没有收到服务端下发的任何错误消息只要超过一定时间没有收到任何推送就应该主动触发一次心跳检测。如果连续两次心跳没有收到pong不要犹豫立刻断开重建连接然后重新订阅。这样做的目的不是解决偶然的网络抖动而是保证程序在长跑中不会悄悄变成“死连接”。5.3 越南行情字段的隐藏坑单位、小数位和时区越南行情接口的坑主要集中在三个方面单位、小数位和时区。先说单位价格和成交量的单位我们前面提过价格可能是越南盾或千盾成交量可能是股数或手数。这个不统一不是文档故意坑你而是每家平台为了自己存储便利做的取舍。你必须在数据管道入口就做标准化转换而不是在策略代码里到处除以100。再说小数位。股价通常两位小数VN30指数点位可能是五位小数权重字段可能返回百分数或小数。有个快速验证方法拿当天交易所官网的公开数据进行交叉比对算一遍确认字段转换逻辑没错。时区问题很隐蔽越南时间比UTC快7个小时和北京时间完全一样但有些API后端直接返回UTC时间有些返回带时区偏移的ISO字符串。K线按日切分时如果时区转换出错每天的开盘时间会偏一小时导致日K线的上下影线有偏差。还有一个我踩过的坑是除权除息。越南公司分红配股比较频繁很多API的原始价格不会自动复权。你做回测时直接用原始价格碰到除权日会出现价格断裂。第三方数据商一般会提供复权因子券商原始接口就要自己处理。这个属于数据加工层面的事但最好在架构设计阶段就把复权逻辑留好位置不然后面改起来很痛。6. 个人经验总结与建议我刚开始接越南行情时最大的错觉是觉得API文档写得清清爽爽照着调就完了。实际接完会发现真正费时间的是那些文档里不会写的字段歧义和网络边界。如果你也准备走这条路我建议把整套接入流程拆成三步先用REST把K线和快照跑通再切WebSocket做实时监控最后才上模拟交易。每一步都留出足够的调试时间别指望一个周末全搞定。最后再分享一个小技巧把越南股票代码和公司名做一张映射表存本地而不是每次都查接口。VN30成分股不算太多但权重会变每天收盘后同步一次就够了。这样既省接口调用量也方便对接后续的新闻或基本面数据。希望这篇指南能帮你少走点弯路。