# ek-data —— 设计说明(v0.2.0)
交易平台的**业务数据契约 crate**:全生态(规划中的 ek-paper / ek-book /
回放器 / 对账工具 / xt 适配层)共用的数据对象、类型枚举、件流事件字典、
标识约定与请求对象。**纯 std、零依赖**;`serde` 为 optional feature(default off)。
Python 原稿:`enkeep_trade/enkeep_trade/trader/constant.py`(207 行)与
`trader/object.py`(819 行)。本 crate 是它们的忠实移植,差异都有决策编号。
## 1. 模块布局(维护铁律:一个结构体一个文件)
```
src/
lib.rs reexport 聚合点 + Python#行号 对照总表
constant.rs 10 枚举 + ACTIVE_STATUSES + EnumParseError + Default 取值
time.rs DtNs(i64 纪元纳秒)/ Day / Civil / 双向换算 + 严格校验/解析
price.rs 价格取整族(round/floor/ceil_to + get_digits,Decimal 精确)
symbol.rs vt_symbol 组装(write_vt_symbol 零分配)+ parse_vt_symbol
event.rs EVENT_* 事件字典(前缀型 `eTick.` / 一字面型 `eOrder`)
tick.rs bar.rs 行情对象(Tick 40+ 字段五档全量;Bar 公共字段版)
order.rs trade.rs 委托/成交(Xt 扩展字段全保留)
position.rs account.rs log.rs / contract.rs
subscribe_request.rs / order_request.rs / cancel_request.rs / history_request.rs
serde_impls.rs feature=on 的 JSON 便捷层(to_json/from_json)
tests/
parity.rs ID/线值与 Python 字节级对照(含 CBOT→CBT 等特例)
enums_roundtrip.rs 全枚举 Display/FromStr 双通道回路 + 线值/别名冲突不变量
objects.rs 方法行为用例(限价三层/活跃矩阵/量域四件套/Hinnant 钉死值/时间严格性)
price.rs 价格取整:Decimal 精确性 + half-even 平局 + 退化输入
serde_cfg.rs feature 双路径:serde=on 时 JSON roundtrip + 特例 rename;off 时编译通过
```
规则:新增字段只动一个文件;对外 import 一律走 lib.rs 聚合点;
serde 标注不进结构体文件正文(`#[cfg_attr(...)]` 单行除外),语义层保持纯数据。
## 2. 对象与方法清单(Python → Rust 一对一)
| TickData | gateway_name/symbol/exchange/datetime + trading_day/action_day/update_time/update_millisec + 五档 price/volume×5 + OHLC/limit/pct_change + localtime | `en_symbol` · `reach_limit_up(diff)`(object.py:99)· `reach_limit_down(diff)`(:105)· `reach_limit(diff)`(:111) |
| BarData | 公共字段(symbol/exchange/datetime/interval/volume/turnover/open_interest/OHLC/pct_change) | `en_symbol` |
| OrderData | + type(Python `type`→Rust `order_type`)/direction(Option)/offset/price/volume/traded/status/datetime/reference + Xt:name/status_msg/traded_price | `en_symbol`·`en_orderid`(:180)·`is_active`(:182)·`create_cancel_request`(:188) |
| TradeData | + orderid/tradeid/direction/offset/price/volume/datetime + commission/slippage/trade_pnl + Xt:name/traded_amount | `en_symbol`·`en_orderid`(:230)·`en_tradeid`(:231) |
| PositionData | + direction(必填)/volume/frozen/price/pnl/yd_volume + Xt:can_use_volume/market_value/on_road_volume/avg_price/last_price/profit_rate | `en_symbol`·`en_positionid`(:262-264) |
| AccountData | accountid/balance/frozen | `en_accountid`(:282)·`available`(:281 暂存→方法) |
| LogData | msg/level(缺省 20=INFO) | level 常量(20/30/40)·`info`·`is_warning_or_above` |
| ContractData | asset/instrument_type/size(缺省1)/price_tick(缺省0.01)/product_id/下量四件套/交割年月 + 扩展(open_date/pre_close/涨跌停/股本/停牌态) | `en_symbol` |
| SubscribeRequest | symbol/exchange | `from_vt_symbol`·`en_symbol`·`FromStr` |
| OrderRequest | symbol/exchange/direction/type/volume/price/offset/reference + old_order_id | `en_symbol`·`create_order_data`(:541)·量域四件套(:559-588:min/max/range/beishu) |
| CancelRequest | orderid/symbol/exchange | `en_symbol`·`en_orderid(gateway)` |
| HistoryRequest | symbol/exchange/start/end/interval | `en_symbol` |
| (工具面) | utility.py:275-318 | `round_to`·`floor_to`·`ceil_to`·`get_digits`(+`try_*` 可失败版)·`write_vt_symbol` |
**defer 清单**(共识,后续批次):QuoteData/QuoteRequest(实现 QuoteData 时注意
object.py:657 `create_quote_data` 同样要搬 `reference`)、HedgeContractData/
ContractData2/ProductInfo/HedgeSetting、StockData/SectorData/KlineChartData、
NotifyData/AlertData;BarData 的 pe/pb/float_mv/total_mv 四件套移除待持久层另立。
**每对象零分配辅件**:tick/bar/order/trade/position/contract 均带
`en_symbol_into(&mut String)`(逐 tick 热路径复用缓冲,`en_symbol()` 为其分配封装)。
## 3. 枚举线值决策(Display=英文令牌,FromStr 容中文旧值)
英文令牌即 `ALL/Display/as_str()` 线值;中文别名只进 `FromStr`。
特例(字节级对齐 Python `.value`):`CBOT→"CBT"`、`EUREX→"EUX"`、
`ETF_INDEX→"ETF_index"`。Exchange 全量 50 变体复刻。
上标:`Direction/Offset/Status/Asset/InstrumentType/OrderType/OptionType/
Exchange/Currency/Interval`;`Interval.seconds()` 即 Python `.value` 秒数;
`ACTIVE_STATUSES = [SUBMITTING, NOTTRADED, PARTTRADED]`(object.py:21),
判定真源收敛为 `Status::is_active()`,`OrderData::is_active` 委托到它。
**别名不变量**(0.2.0 加固):每个枚举的线值两两互异、别名两两互异且
不得与任何线值相同——由 `enums_roundtrip.rs` 的冲突检查用例钉死
(据此修掉 `InstrumentType::ETF` 的多余同值别名:Python 的 `.value` 本身
就是 `"ETF"`,没有中文别名)。
Default 取值(供对象 `..Default::default()`,对齐 Python 字段缺省):
Exchange=LOCAL、OrderType=LIMIT、Offset=NONE、Status=SUBMITTING、
Direction=NET、Asset=EQUITY(Position/ContractData 里 Python 必填,
Rust Default 面为中性值)。
## 4. 派生 ID:方法化,不暂存
Python 的 `__post_init__` 暂存字段(en_symbol/en_orderid/en_tradeid/
en_positionid/en_accountid/available)在 Rust 版全部为**方法即时计算**——
字段改动永不脱同步;`en_positionid` 第三段为英文方向令牌(线值决策 3)。
## 5. 时间决定:A 零依赖
`DtNs = i64`(Unix 纪元**纳秒**,UTC 视角)+ `Day = i32`(纪元天)+
`Civil{year,month,day,hour,minute,second,nanos}`。换算走 Hinnant
days_from_civil/civil_from_days(负纳秒用 div_euclid/rem_euclid 的 floor
语义),单测包括 2000-03-01=11017、闰日、纪元前负值。
对齐说明:Python datetime 是本地 naive,故逐字节对齐仅限于**字段语义**
而非时区;时区/本地化展示由使用方(网关层)处理。
**范围与严格性**(0.2.0 加固,见 time.rs 头注):
- `DtNs=i64` 纳秒只覆盖 **1677-09-21 .. 2262-04-11**;超出由
`try_civil_to_nanos` 给 `None`,远日期(IPO 史、交易所史)必须走 `Day` 序数。
- `civil_to_nanos` 现**严格校验**日历(月/日/闰年/时分秒/纳秒)与 i64 范围,
越界即 panic——对齐 Python `datetime` 抛 `ValueError` 的失败面
(0.1.0 会静默产出错误时间戳,属缺陷修正)。
- `parse_civil` 接受网关/回放常见文本:`"YYYY-MM-DD[ HH:MM[:SS[.f{1,9}]]]"`,
亦接受 `T` 分隔与纯日期;与 `format_civil` 构成回路。
## 5.1 价格精度决定:Decimal 语义,不用朴素 f64
Python `utility.py:275-318` 用 `Decimal(str(v))` 运算,故必须复刻**十进制精确**:
| `floor_to(0.3, 0.1)` | `0.2`(0.3/0.1=2.9999…) | `0.3` |
| `round_to(2.5, 1.0)` | `3.0`(half-up) | `2.0`(half-even,Python `round(Decimal)`) |
| `round_to(3500.1, 0.2)` | `3500.0`(17500.4999… 回落) | `3500.4`(17500.5 → 取偶 → 17502) |
实现:从 `Debug` 形态的**最短往返十进制**取数字 → i128 整数比 → half-even/
floor/ceil 整数除法 → 还原。零依赖;退化输入(target=0、非有限、溢出)走
`try_*` 给 `None`、`*_to` 给 `NaN`,绝不静默瞎算。
## 6. serde 决定:optional、default off
`feature=serde` 开启 derive(字段名即 Python 字段名 snake_case)与
serde_impls::{to_json,from_json};枚举 serde 线值同样英文(**中文旧值不做
serde 反序列化**,兼容通道收敛在 `FromStr`)。off 路径连编译都要过
(serde_cfg.rs 用 `#![cfg(feature = "serde")]` 门面)。
## 7. 与生态的边界
- ek-event 0.1.0 / ek-core 0.3.0 **一行不动**;ek-data 运行时**零依赖**
(数据契约先行,插件 crate 再同时依赖 ek-data + ek-core)。
- 0.2.1 测试面借 [`event`] 字典把数据对象放进真实总线;**0.2.2 定稿**:
`[dev-dependencies] ek-event = "0.1.0"` —— **纯版本引用(wire-only)**。
纪律(模块独立):自研 crate 也必须以【已装版本】互引,禁止 path 目录耦合;
dev-deps 不算运行时依赖、不随 lib 发布。
- event.rs 的 `eTick.` 等是**业务事件字典**(ek-event 泛事件队列上的载荷约定),
与 ek-core 的 `ePlugUp/eSrvChg/eReq.*` 属不同命名域,互不重叠。
- 业务日志唯一真载荷:`LogData`(20/30/40)于 `"eLog"`(无点)——ek-core 0.3.0
起宿主零日志,业务日志由插件自造自投,本 crate 是该契约的**唯一定义处**。
- 发布流程同前序:tests→clippy(-D warnings,双 feature)→3 连跑→
commit→tag→push(发布人工执行)。
## 8. 版本历史
- **0.2.2**:dev-dep 改纯版本引用(去 `path`,落地「模块独立——装版互引」纪律);
用例与 0.2.1 持平(62/65 + doctest)。
- **0.2.1**:业务流集成测试批次(`tests/flows_*.rs`,dev-dep 自家 `ek-event`)。
- F1 订阅链 / F2 行情+K线逐合约流(组合键互不串台 + 裸键全市场 + 双投约定
网关腿演示) / F3 下单闸门四连 / F4 委托生命周期(键=en_orderid) /
F5 成交流(en_tradeid= gateway.tradeid) / F6 持仓 / F7 账户 /
F8 合约流(**单投 parity 正证**:字面组合键空收) /
F9 eLog 单形态(宿主离场后的唯一载荷契约) /
F10 时间对象随流(civil 往返 + DTNS_MAX 边界过流) /
F12 负面集(坏载荷静默拒收 / 坏键隔离 / 无效 civil 拒于流前)。
- `tests/common`:`Capture<T>`(类型化捕获)+ `wait_until` + FIFO 排空哨兵
`drained`(负面断言的「窗口已关」证据);全程轮询,无 sleep 断言。
- 用例 46 → 62(default)/ 65(serde)+ README doctest;运行时依赖仍为零。
- **0.2.0**:优化与加固批次。
- 新增 `price` 模块(Decimal 精确取整族)与 `time::parse_civil` /
`try_civil_to_nanos` / `is_valid_civil` / `days_in_month` / `is_leap_year` /
`DTNS_MIN`·`DTNS_MAX`。
- 新增 `Status::is_active()`(活跃判定唯一真源)与 `write_vt_symbol` /
`en_symbol_into`(热路径零分配)。
- 修正:`civil_to_nanos` 越界不再静默产错值;`InstrumentType::ETF` 去掉
与线值同名的多余别名;补 README/CHANGELOG 与 `readme`/`documentation` 元数据;
用例 30 → 42(default)/ 45(serde)。
- **0.1.0**(首个上架版,crates.io 2026-10-09):初始形态,即本文档其余章节。