ek-ctp 0.2.0

CTP gateway for the enkeep ek ecosystem: MD/TD session drivers mapping CTP wire rows onto ek-data objects over the ek-event bus (pure-logic core; the CTP SDK ships inside this package and native bridging lands behind the optional `native` feature).
Documentation
# ek-ctp

CTP 期货行情/交易网关(Rust 移植版,`enkeep_trade` 生态)。

对 `enkeep_ctp`(`vnpy` 的 `ctp_gateway` 移植层)行为逐字移植。
**0.1.0**(已发布)为纯逻辑层;**0.2.0**(开发中)分批交付 `native` 桥——
**SDK 已随 crates.io 包发布**(下游安装即得、无需自备),特性与 `build.rs` 见下节。

## 结构

| 模块 | 职责 |
|---|---|
| `wire` | CTP 官方字段的最小**子集**(仅 `DepthMarketData`/`InstrumentField`/`OrderField`/`TradeField`/仓位/资金/插撤单输入) |
| `map` | CTP 码值 ↔ ek-data 类型(状态/方向/开平/单型/交易所/时间戳/价格修整) |
| `driver` | 行情/交易两条**纯状态机**(`MdDriver::step`/`TdDriver::step`),零线程零时钟 |
| `plugin` | `CtpPlugin`:ek-core 宿主插件。出门 9 线值:`eTick/eContract/eOrder/eTrade/ePosition/eAccount/ePositionDone/eContractFinish/eLog`;进门 `eTimer` 与 `eReq.<gw>` 三服务 |
| `native` | (仅 `native` 特性)官方 C++ SDK 的 C ABI 桥;M2 = MD + TD 全面(会话、12 个交易回调、字段映射、传输层实现) |

## 快速用法

纯逻辑驱动层(不带任何网络库,可直接驱动 mock 做测试):

```rust
use ek_ctp::{TdConfig, TdDriver, TdEvent, TdOut, contracts_empty};

let mut td = TdDriver::new("ctp", TdConfig::default(), contracts_empty());
for out in td.step(TdEvent::FrontConnected) {
    // 命令面(TdOut::ReqXxx)交给传输层;数据面(TdOut::Order 等)上总线
}
```

总线级(同 ek-oms):

```rust
let cfg = ek_ctp::CtpConfig { gateway_name: "ctp".into(), ..Default::default() };
host.add_plugin(ek_ctp::CtpPlugin::new(cfg))?;   // 默认构建无传输;动作被吸收
```

## native(0.2.0)

**M2 已落地(MD + TD 全面)**,用 `--features native` 启用(**默认关闭**,纯逻辑消费方与 docs.rs
不触碰 C++ 工具链)。SDK **已随包发布**(D-SDK-SHIP):四份 `ThostFtdc*.h` + win64 导入库/DLL
+ linux SO 都在 `native/sdk/`(来源 `enkeep_ctp` 自带的一套,头与二进制同源,API 版本 **6.7.7**、
DLL 内嵌构建时间戳 `20240607`),压缩后约 5.9MB、在 crates.io 单包 10MB 上限内 ——
下游 `cargo add ek-ctp` 即得,**不需要自备 SDK**。许可边界见 [`NOTICE`]NOTICE:
本包的 MIT **不覆盖**这些上期技术二进制。

```powershell
cargo test --features native        # 46 用例:逻辑 21 + native 侧 25(POD 自检 14 / 传输层 3 / 离线冒烟 8)
powershell -ExecutionPolicy Bypass -File scripts/gate.ps1 -WithNative   # 门禁含 native
```

- **形态**:`src/native/`(`common` 共用工具 + `md`/`td` 两面的 `extern "C"` 声明与 RAII 句柄
  + `pod` 的 CTP↔POD 转换 + `transport` 的 `CtpTransport` 官方实现)
  + 两个 C++ 单元(`native/shim/ek_ctp_shim.{h,cpp}` 会话/回调面、`ek_ctp_pod.{h,cpp}` 字段面,
  **唯一**说 C++ 的地方)
  + 手写 `build.rs`(仅 std,零第三方 crate;特性关闭时**完全 no-op**)。
- **平台**:只支持 **x86_64-pc-windows-msvc**;其他目标在 `build.rs` 里给出明确报错,
  而不是让用户在链接期面对读不懂的符号错误。**Linux/GCC 路径尚未实现**(SDK 的 `.so`
  已随包备好,待补;见 DESIGN D-NATIVE-TARGET)。
- **工具链**:MSVC 环境优先用现成的 `cl.exe`/`INCLUDE`/`LIB`;缺失时经 `vswhere` 自建,
  不要求"VS 开发者命令行"(`EK_CTP_FORCE_VSWHERE=1` 可强制走 vswhere 路径以便自验)。
- **运行期 DLL**:`build.rs` 把 `thost*.dll` 投送到 `target/<profile>/` 与 `deps/`
  —— Windows 只在 exe 所在目录、系统目录、cwd、PATH 里找 DLL,两处都要有。
- **ABI 自检三层**:① 回调表 —— shim 导出 `sizeof`,测试断言等于 Rust 侧 `size_of`
  (MD/TD 各一张表);② 每个事件 POD —— 同样的 `sizeof` 断言;③ 字段映射 ——
  `ek_pod_selftest_*` 在 C++ 里构造带唯一值的 CTP 报文、经真实转换函数后由 Rust
  **逐字段**断言(顺序/类型/映射错位都会被抓住)。C++ 侧每处 SPI 覆盖都带 `override`,
  SDK 换了虚函数签名会**编译失败**,而不是留到运行期崩。
- **线程模型**:SPI 回调跑在 CTP 自己的线程上,**不引入 ingress 线程** —— 回调直接步进
  插件(驱动状态自锁在 `Mutex` 里,`PluginContext` 可跨线程 `put`),与原稿"回调里直接
  上抛"同构(见 DESIGN D-NATIVE-THREAD)。
- **连接触发在插件内**:宿主 `start()` → `on_start` → `CtpPlugin::connect()`(先 TD 后 MD);
  也可显式调用。`CtpPlugin::with_native(cfg, flow_root)` 一次完成"传输层 + 回调接线"装配
  (回调入口用 `Weak` 回指插件,避免循环引用)。
- **离线可验**:native 用例完全不触网(编译 → 链接 → DLL 加载 → 创建/装配/析构 + 版本串
  + POD 逐字段映射);真连网登录(SimNow)属 M3,且需本机能直达前置地址。
- **`shim`/`build.rs`/`.ps1` 正文一律 ASCII**:CTP 头是 GBK,ASCII 源码 + 不给 MSVC 传
  `/utf-8`,它才按系统码页正确解码头文件(见 DESIGN D-NATIVE-ENC)。
- 发布前跑 `scripts/check-package.ps1`:双向断言包内**必须含** SDK、**不得含**私密配置。

## 本地测试配置(SimNow)

账户与前置地址放在**本机私密文件**里(已被 `.gitignore` 忽略,永不提交/推送):

```powershell
Copy-Item config/simnow.example.env config/simnow.local.env   # 然后填入自己的账号
```

SimNow 现行两套前置(**2026-10 实测口径**,来源:openctp 模拟环境监控 <http://www.openctp.cn/simenv.html>):

| 套 | 交易前置 | 行情前置 | 服务时间 |
|---|---|---|---|
| 第一套(仿真,与实盘时段一致,**优先**) | `tcp://182.254.243.31:30001`(备用 `:30002` / `:30003`) | `tcp://182.254.243.31:30011`(备用 `:30012` / `:30013`) | 与实盘生产时段一致(日盘 / 夜盘) |
| 第二套(7x24) | `tcp://182.254.243.31:40001` | `tcp://182.254.243.31:40011` | 全天候(周末与节假日另有维护窗口) |

> **2026-10 地址变更**:SimNow 前置已从 `180.168.146.187`(电信)/ `218.202.237.33`(移动)
> 迁到 `182.254.243.31`。老地址的失效形态很好认 —— 电信线路 **TCP 超时**(包被丢),
> 移动线路**主动拒绝**(RST,端口已关)。地址会随 SimNow 运维变动:连不上时先查
> openctp 监控页,再改 `config/simnow.local.env`(本仓库已按 2026-10 口径更新)。

选择规则(脚本自动裁决):**交易时段**(北京时区,日盘 08:55–15:15 / 夜盘 20:55–02:35)→ 第一套;其余时间 → 第二套。两套都通时优先第一套。

```powershell
powershell -ExecutionPolicy Bypass -File scripts/simnow-fronts.ps1          # 看当前该用哪套(含理由)
powershell -ExecutionPolicy Bypass -File scripts/simnow-fronts.ps1 -Probe   # 先实测 TCP 连通性再选
powershell -ExecutionPolicy Bypass -File scripts/simnow-fronts.ps1 -Env     # 输出 EK_CTP_TD_ADDR / EK_CTP_MD_ADDR 赋值行
powershell -ExecutionPolicy Bypass -File scripts/simnow-fronts.ps1 -Force 724
```

脚本正文**只含 ASCII**(输出为英文):Windows PowerShell 5.1 对无 BOM 的 `.ps1` 按 ANSI 解码,
中文串会乱码并炸解析;配置文件是 `KEY=VALUE`,读取时显式按 UTF-8,故其中文注释安全。

排查:`-Probe` 前置全部 `down` 时先分清两种形态 —— **超时**(包被丢:本机/网络策略或对端下线)
与**主动拒绝 RST**(对端可达但端口已关:地址过时)。SimNow 前置是裸 TCP(不是 80/443),
可用 `Test-NetConnection 182.254.243.31 -Port 40001` 或 `telnet 182.254.243.31 40001` 复核;
本机网络本身是否通,用 `Test-NetConnection www.baidu.com -Port 443` 对照。

登录字段:BrokerID `9999`、AppID `simnow_client_test`、AuthCode `0000000000000000`(16 个 0)。
`CtpConfig.td_auth_code` 非空即走认证分支(与 `enkeep_ctp` 原稿 448-451 的分支一致)。
SimNow 仅提供快照行情(无分笔),周末与节假日环境关闭。

### 真连端到端联调

```powershell
$env:EK_CTP_FRONT_SET = 'live'                    # live = 仿真那套 / 724 = 7x24
cargo run --features native --example live_simnow # 真 SDK、真前置,非 mock
```

`examples/live_simnow.rs` 按原稿启动顺序跑完整业务面(启动链 → 结算确认 → 合约查询 →
订阅行情并挑"当前真在交易"的标的 → 可成交价下单/成交 → 挂单/撤单 → 拒单探针 ×2 →
资金/持仓),逐步判定 + 完整时间线 + Markdown 报告落盘 `target/ctp-live-report.md`。

**2026-10-15 实测**:7 步全 PASS(真实成交 / 撤单 / 拒单 / 资金 / 持仓),详见
[LIVE-TEST.md](LIVE-TEST.md) —— 该轮还抓出并修掉两个时间戳真 bug(决策见 DESIGN `D-TZ`)。

## 生态依赖(expected)

- `ek-core` `0.3.0`(宿主/服务)
- `ek-data` `0.2.2`(数据类型)
- `ek-event` `0.1.0`(总线形态)