第 3 章 · 03 constant.py 枚举体系与 i18n 本节摘要:本节精读 全部 161 行——VeighNa 的"类型字典"。这里用 9 个 Enum 类把整个交易域的概念固化下来:Direction(方向)、Offset(开平)、Status(状态)、Product(品种)、OrderType(委托类型)、Exchange(交易所)、OptionType(期权类型)、Currency(币种)、Interval(K 线周期)。最巧妙的是它的 i18n 设计——业务枚举的 value 走 可翻译,运行时按 locale 显示中英文;而 Exchange 的 value 故意保留英文缩写字面量(因为要参与 vtsymbol 拼接,需要稳定可读)。
本节摘要:本节精读
vnpy/trader/constant.py全部 161 行——VeighNa 的"类型字典"。这里用 9 个 Enum 类把整个交易域的概念固化下来:Direction(方向)、Offset(开平)、Status(状态)、Product(品种)、OrderType(委托类型)、Exchange(交易所)、OptionType(期权类型)、Currency(币种)、Interval(K 线周期)。最巧妙的是它的 i18n 设计——业务枚举的 value 走_("中文")可翻译,运行时按 locale 显示中英文;而 Exchange 的 value 故意保留英文缩写字面量(因为要参与 vt_symbol 拼接,需要稳定可读)。读完本节,你不仅认识了所有枚举,还理解了 VeighNa "源码以中文为锚、运行时按需切换语言"的国际化套路。
内容来源:原项目源码
vnpy/trader/constant.py(共 161 行),精读并套用体系化模板。
阅读完本节,你应当能够:
from .locale import _ 的 gettext 机制——为什么业务枚举 value 包一层 _()。_() 而保留英文缩写。"1m"/"1h"/"d"/"w"/"tick")的存储意图。文件开头(constant.py:5-7):
5 from enum import Enum 7 from .locale import _
_ 是什么?看 vnpy/trader/locale/__init__.py:
1 import gettext 2 from pathlib import Path 5 localedir: Path = Path(__file__).parent 7 translations = gettext.translation("vnpy", localedir=localedir, fallback=True) 9 _ = translations.gettext
_ 就是 Python 标准 gettext 翻译函数。它的工作机制:
LONG = _("多")。_("多") 在运行时查 locale 目录(vnpy/trader/locale/)下的 .mo 编译字典。"Long"),找不到就 fallback=True 原样返回 "多"。locale/ 目录里实际有 en/LC_MESSAGES/vnpy.po(英文翻译源文件),由 build_hook.py 在打包时编译成 .mo。这是 GNU gettext 的标准套路——.po 是人编辑的文本、.mo 是机器读的二进制。
💡 核心心法:VeighNa 的 i18n 选择"中文为锚"而非英文为锚,因为它是国产框架、默认面向中文用户。源码里看到的
_("多")、_("开")就是中文母语者最熟悉的语义。这种"以母语为锚 + 运行时按 locale 切换"的设计,既保证源码可读,又支持国际化——比单纯写英文枚举值友好得多。
constant.py:10-16:
10 class Direction(Enum): 14 LONG = _("多") 15 SHORT = _("空") 16 NET = _("净")
三个方向,贯穿 OrderData/TradeData/PositionData:
下单时 LONG/SHORT 二选一;持仓查询时,股票一般是 NET,期货可能是双向(LONG 和 SHORT 各一条 PositionData,即"锁仓")。
constant.py:19-27:
19 class Offset(Enum): 23 NONE = "" 24 OPEN = _("开") 25 CLOSE = _("平") 26 CLOSETODAY = _("平今") 27 CLOSEYESTERDAY = _("平昨")
五个开平标志:
⚠️ 重要说明:平今/平昨是国内期货上期所(SHFE)的特色。上期所对当日新开仓与历史持仓采用不同的保证金计算与撮合逻辑,必须显式指明平今还是平昨。其它交易所(中金所 CFFEX、大商所 DCE、郑商所 CZCE)采用"先开先平"自动匹配,统一用 CLOSE 即可。这是国内量化框架必须处理的细节,第 7 章 CTP 网关会详讲。
注意 NONE 的 value 是空字符串 ""(不包 _()),因为空串不需要翻译,且参与字符串拼接时保持空。
constant.py:30-39:
30 class Status(Enum): 34 SUBMITTING = _("提交中") 35 NOTTRADED = _("未成交") 36 PARTTRADED = _("部分成交") 37 ALLTRADED = _("全部成交") 38 CANCELLED = _("已撤销") 39 REJECTED = _("拒单")
六个状态,分两类:
| 类别 | 状态 | 说明 |
|---|---|---|
| 活跃状态(前 3) | SUBMITTING / NOTTRADED / PARTTRADED | 还可能继续成交或被撤 |
| 终结状态(后 3) | ALLTRADED / CANCELLED / REJECTED | 订单生命周期结束 |
回看上一节 object.py:14 的 ACTIVE_STATUSES:
14 ACTIVE_STATUSES = set([Status.SUBMITTING, Status.NOTTRADED, Status.PARTTRADED])
OrderData.is_active() 就是查这前三个状态——一旦进入后三个,订单就"死了",不能再改/撤。OmsEngine(第 4 章)据此维护活动订单缓存,终结订单会被移出。
💡 核心心法:Status 的"活跃/终结"二分法是订单管理系统(OMS)的核心抽象。VeighNa 把它隐含在枚举顺序里(前 3 活跃 + 后 3 终结),再用 ACTIVE_STATUSES 集合显式化。这种"枚举即分类、集合即规则"的写法,让 OMS 逻辑非常清晰。
constant.py:42-58:
42 class Product(Enum): 46 EQUITY = _("股票") 47 FUTURES = _("期货") 48 OPTION = _("期权") 49 INDEX = _("指数") 50 FOREX = _("外汇") 51 SPOT = _("现货") 52 ETF = "ETF" 53 BOND = _("债券") 54 WARRANT = _("权证") 55 SPREAD = _("价差") 56 FUND = _("基金") 57 CFD = "CFD" 58 SWAP = _("互换")
13 种产品类型,覆盖 VeighNa 支持的所有资产类别。注意一个细节:有些 value 不包 _()——ETF、CFD 直接用英文字面量。原因是这些缩写在中文语境里本就通用("ETF 基金""CFD 差价合约"很少翻译),强行中文化反而别扭。这是务实判断——i18n 不是机械地全包 _(),而是该翻的翻、不该翻的保留。
ContractData.product 字段就是这 13 种之一,后续 ContractData 是否填充期权专属字段(option_strike 等)就看 product==OPTION。
constant.py:61-71:
61 class OrderType(Enum): 65 LIMIT = _("限价") 66 MARKET = _("市价") 67 STOP = "STOP" 68 FAK = "FAK" 69 FOK = "FOK" 70 RFQ = _("询价") 71 ETF = "ETF"
七种委托类型:
⚠️ 重要说明:FAK/FOK 是国内期货的精细化委托类型。中金所股指期货、上期所等都支持,用于控制撮合行为——FAK 适合"能成交多少算多少",FOK 适合"要么全成要么不成"。这两类在海外叫 IOC(Immediate-or-Cancel)和 AON(All-or-None),VeighNa 用国内惯用名 FAK/FOK。STOP/FAK/FOK/ETF 都不包
_(),因为它们是国际通用的委托类型缩写,直接保留字面量。
constant.py:82-139:
82 class Exchange(Enum): 86 # Chinese 87 CFFEX = "CFFEX" # China Financial Futures Exchange 中国金融期货交易所 88 SHFE = "SHFE" # Shanghai Futures Exchange 上海期货交易所 89 CZCE = "CZCE" # Zhengzhou Commodity Exchange 郑州商品交易所 90 DCE = "DCE" # Dalian Commodity Exchange 大连商品交易所 91 INE = "INE" # Shanghai International Energy Exchange 上海能源交易所 92 GFEX = "GFEX" # Guangzhou Futures Exchange 广州期货交易所 93 SSE = "SSE" # Shanghai Stock Exchange 上海证券交易所 94 SZSE = "SZSE" # Shenzhen Stock Exchange 深圳证券交易所 95 BSE = "BSE" # Beijing Stock Exchange 北京证券交易所 96 SHHK = "SHHK" # Shanghai-HK Stock Connect 沪港通 97 SZHK = "SZHK" # Shenzhen-HK Stock Connect 深港通 98 SGE = "SGE" # Shanghai Gold Exchange 上海黄金交易所 99 WXE = "WXE" # Wuxi Steel Exchange 无锡不锈钢交易所 100 CFETS = "CFETS" # CFETS Bond Market Maker Trading System 银行间债市做市 101 XBOND = "XBOND" # CFETS X-Bond Anonymous Trading System X-Bond 匿名交易 102 103 # Global 104 SMART = "SMART" # US 智能路由 105 NYSE = "NYSE" # New York Stock Exchange 106 NASDAQ = "NASDAQ" # Nasdaq ... 127 EUREX = "EUX" # Eurex(注意 value 是 EUX 不是 EUREX) ... 138 LOCAL = "LOCAL" # 本地生成的数据 139 GLOBAL = "GLOBAL" # 尚未支持的交易所兜底
这是整个文件最体现"国产框架"的部分。三大组:
CFFEX/SHFE/CZCE/DCE/INE/GFEX 六大期货交易所 + SSE/SZSE/BSE 三大股票所 + SHHK/SZHK 沪深港通 + SGE/WXE 现货所 + CFETS/XBOND 银行间债市。这是国产量化框架的完整覆盖面——海外框架(如 backtrader/zipline)绝不会列这么细的国内市场。
覆盖美股、加拿大、欧洲、亚洲主流交易所,用于对接盈透(IB)等海外券商。注意 EUREX 的 value 是 "EUX"(不是 "EUREX")——这是 IB 的特殊约定,说明 VeighNa 的 Exchange value 在 IB 网关里是直接当 IB 协议字段用的。
💡 核心心法:注意所有 Exchange 的 value 都是英文缩写字面量(如
"SSE"、"CFFEX"),不包_()。为什么?因为 Exchange.value 直接参与 vt_symbol 拼接——回看上一节vt_symbol = f"{symbol}.{exchange.value}",生成的就是"600000.SSE"、"rb2401.SHFE"这种字符串。这个字符串要存数据库、要被网关解析、要可读——它必须稳定且不可翻译。如果包了_(),中文环境下 vt_symbol 会变成"600000.上海证券交易所",既不优雅也破坏了与网关协议的一致性。
所以 Exchange 是 i18n 的例外——它是技术标识符而非显示文本。需要中文显示交易所名时,UI 层会另查一张映射表(而不是用 .value)。
constant.py:74-79:
74 class OptionType(Enum): 78 CALL = _("看涨期权") 79 PUT = _("看跌期权")
期权两种类型——看涨(CALL)与看跌(PUT)。ContractData.option_type 字段用它,决定期权行权方向。
constant.py:142-149:
142 class Currency(Enum): 146 USD = "USD" 147 HKD = "HKD" 148 CNY = "CNY" 149 CAD = "CAD"
四种货币——美元、港币、人民币、加元。value 全是 ISO 4217 货币代码字面量(不包 _()),和 Exchange 同理:货币代码是国际标准符号,不能翻译。VeighNa 主要面向这几个市场的账户,实际用得最多的是 CNY(国内)和 USD(海外券商)。
constant.py:152-160:
152 class Interval(Enum): 156 MINUTE = "1m" 157 HOUR = "1h" 158 DAILY = "d" 159 WEEKLY = "w" 160 TICK = "tick"
五种 K 线周期,value 是字符串简写:
"1m"(分钟)"1h"(小时)"d"(日)"w"(周)"tick"(逐笔)这些简写用于数据库存储和 UI 显示——比存整数(0/1/2/3)可读,比存全名("MINUTE"/"DAILY")简洁。BarData.interval 字段就是它;第 6 章的 BarGenerator 合成 K 线时,产出的 BarData 会带上 interval 标记周期。
⚠️ 重要说明:Interval 的 value 也不包
_()——因为这些简写是技术性符号,中文环境下也用"1m"/"1h"(没人写"分钟线"="1分")。这是"显示文本"与"存储符号"的区分——前者走 i18n,后者保留字面量。
把 9 个枚举放在一起看,VeighNa 的设计意图非常清晰:
所有字段都有 Enum 类型约束,避免魔法字符串。比如 direction: Direction,传 "多" 字符串会过不了类型检查(IDE 提示、运行时 dataclass 校验)。这比裸字符串安全得多。
业务枚举(Direction/Offset/Status/Product/OrderType/OptionType)value 走 _(),运行时按 locale 显示中英文。一份代码两套语言,源码以中文为锚最贴合国产用户。
Exchange 枚举详尽覆盖国内 15 个所(含 SHHK/SZHK 港通、CFETS/XBOND 银行间债市),Offset 支持"平今平昨",这是海外框架都没有的本土化能力。
枚举的 .value 不只是显示文本,还兼任:
"1m")。所以设计时必须权衡:需要 i18n 的走 _(),需要稳定字面量的留原文。VeighNa 的取舍是:业务语义走 _()(可翻译),技术标识保留字面量(稳定)。这条线划得非常清楚。
from .locale import _ 是 gettext 函数,源码以中文为锚,业务枚举 value 走 _() 可运行时翻译。_()。_()),因为要进 vt_symbol 拼接需稳定可读。_()、技术标识留原文。下一节,我们离开 object.py 与 constant.py,进入第 4 章——MainEngine 主引擎。看 VeighNa 怎么用 OmsEngine 把 TickData/OrderData/TradeData/PositionData/AccountData 串成一套完整的订单管理系统,并通过 EventEngine 与各 Gateway 联动。