Skip to main content

ek_data/
constant.rs

1//! 一般交易常量枚举。
2//!
3//! Python 原稿:`enkeep_trade/enkeep_trade/trader/constant.py` 逐名逐值复刻。
4//! 线值决策见 crate 文档第 3 条:英文令牌为线上值,`FromStr` 容中文旧值。
5
6// 变体名沿 Python API 用大写下划线形(FUTURES_CONTRACT / MINUTE_5 /
7// ETF_INDEX),对齐生态复刻语义;线值才是协议面。
8#![allow(non_camel_case_types)]
9
10use std::fmt;
11use std::str::FromStr;
12
13macro_rules! wire_enum {
14    (
15        $(#[$meta:meta])*
16        $name:ident,
17        display_name = $display:expr,
18        { $( $variant:ident = $wire:literal [ $( $alias:literal ),* ] ),* $(,)? }
19    ) => {
20        $(#[$meta])*
21        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22        #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23        pub enum $name {
24            $( #[cfg_attr(feature = "serde", serde(rename = $wire))] $variant ),*
25        }
26
27        impl $name {
28            /// 全部变体(顺序对齐 Python 声明序)
29            pub const ALL: &'static [Self] = &[ $( Self::$variant ),* ];
30
31            /// 线上字符串值
32            pub fn as_str(&self) -> &'static str {
33                match self { $( Self::$variant => $wire ),* }
34            }
35
36            /// 中文别名列表(Python 时代线值)
37            pub fn chinese_alias(&self) -> &'static [&'static str] {
38                match self { $( Self::$variant => &[ $( $alias ),* ] ),* }
39            }
40
41            /// 枚举显示名(用于日志与 doc)
42            pub const DISPLAY: &'static str = $display;
43        }
44
45        impl fmt::Display for $name {
46            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47                f.write_str(self.as_str())
48            }
49        }
50
51        impl FromStr for $name {
52            type Err = EnumParseError;
53            /// 接受英文线值(大小写敏感,与 Python .value 同形);
54            /// 额外容忍中文旧线值别名。
55            fn from_str(s: &str) -> Result<Self, Self::Err> {
56                $( if s == $wire { return Ok(Self::$variant); } )*
57                $(
58                    $( if s == $alias { return Ok(Self::$variant); } )*
59                )*
60                Err(EnumParseError { input: s.to_string(), type_name: $display })
61            }
62        }
63    };
64}
65
66/// 枚举解析失败(Python 侧是抛 `ValueError`,Rust 用错误结构表达)。
67#[derive(Debug, Clone, PartialEq, Eq)]
68pub struct EnumParseError {
69    /// 输入原文
70    pub input: String,
71    /// 枚举类型名
72    pub type_name: &'static str,
73}
74
75impl fmt::Display for EnumParseError {
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        write!(f, "unknown {} value: {:?}", self.type_name, self.input)
78    }
79}
80
81impl std::error::Error for EnumParseError {}
82
83wire_enum!(
84    /// 订单/成交/持仓方向(`constant.py:8`)。
85    Direction, display_name = "Direction",
86    {
87        LONG = "LONG" ["多"],
88        SHORT = "SHORT" ["空"],
89        NET = "NET" ["净"]
90    }
91);
92
93wire_enum!(
94    /// 订单/成交的开平属性(`constant.py:18`)。
95    Offset, display_name = "Offset",
96    {
97        NONE = "NONE" [""],
98        OPEN = "OPEN" ["开"],
99        CLOSE = "CLOSE" ["平"],
100        CLOSETODAY = "CLOSETODAY" ["平今"],
101        CLOSEYESTERDAY = "CLOSEYESTERDAY" ["平昨"]
102    }
103);
104
105wire_enum!(
106    /// 订单状态(`constant.py:30`);活跃态判定见 [`ACTIVE_STATUSES`]。
107    ///
108    /// `UNKNOWN`(0.3.0,边界零丢弃):网关收到无法翻译的柜台状态码时填它,
109    /// **不上岸为任何已知状态**(替柜台猜"是不是在途"就是在封装层做决策)。
110    /// 刻意**不进** [`ACTIVE_STATUSES`]:在途判定是在途/撤单策略的业务决策,
111    /// 归 ek-oms;网关只负责如实说"这个状态我不认识"。
112    Status, display_name = "Status",
113    {
114        SUBMITTING = "SUBMITTING" ["提交中"],
115        NOTTRADED = "NOTTRADED" ["未成交"],
116        PARTTRADED = "PARTTRADED" ["部分成交"],
117        ALLTRADED = "ALLTRADED" ["全部成交"],
118        CANCELLED = "CANCELLED" ["已撤销"],
119        REJECTED = "REJECTED" ["拒单"],
120        UNKNOWN = "UNKNOWN" ["未知"]
121    }
122);
123
124wire_enum!(
125    /// 资产大类(`constant.py:43`);细分形态由 [`InstrumentType`] 正交表达。
126    ///
127    /// `UNKNOWN`(0.3.0,边界零丢弃):柜台给了无法翻译的产品类码时填它。
128    /// **触发条件很窄**:只在用户显式勾选非标准产品类码(如 `'4'` 现货 /
129    /// `'5'` EFP / `'I'` MI)时才会出现;默认勾选 `'1'`(期货)时该路径不可达。
130    Asset, display_name = "Asset",
131    {
132        EQUITY = "EQUITY" ["股票"],
133        BOND = "BOND" ["债券"],
134        FUTURES = "FUTURES" ["期货"],
135        OPTION = "OPTION" ["期权"],
136        FOREX = "FOREX" ["外汇"],
137        UNKNOWN = "UNKNOWN" ["未知"]
138    }
139);
140
141wire_enum!(
142    /// 合约形态(`constant.py:69`);与 [`Asset`] 正交组合。
143    InstrumentType, display_name = "InstrumentType",
144    {
145        STOCK = "STOCK" ["股票"],
146        ETF = "ETF" [],
147        INDEX = "INDEX" ["指数"],
148        FUTURES_CONTRACT = "FUTURES_CONTRACT" ["期货合约"],
149        MAIN_CONTRACT = "MAIN_CONTRACT" ["主力合约"],
150        CONTINUOUS_CONTRACT = "CONTINUOUS_CONTRACT" ["连续合约"],
151        OPTION_CONTRACT = "OPTION_CONTRACT" ["期权合约"],
152        BOND = "BOND" ["债券"],
153        CONVERTIBLE_BOND = "CONVERTIBLE_BOND" ["可转债"],
154        SPREAD = "SPREAD" ["价差组合"],
155        HEDGE = "HEDGE" ["对冲组合"]
156    }
157);
158
159wire_enum!(
160    /// 委托类型(`constant.py:100`)。
161    ///
162    /// `UNKNOWN`(0.3.0,边界零丢弃):`(OrderPriceType, TimeCondition,
163    /// VolumeCondition)` 三元组无法翻译时填它,不借 `LIMIT` 顶替
164    /// (借用会让界面显示"限价",而真实类型未知 —— 那就是封装层在编故事)。
165    OrderType, display_name = "OrderType",
166    {
167        LIMIT = "LIMIT" ["限价"],
168        MARKET = "MARKET" ["市价"],
169        STOP = "STOP" [],
170        FAK = "FAK" [],
171        FOK = "FOK" [],
172        RFQ = "RFQ" ["询价"],
173        UNKNOWN = "UNKNOWN" ["未知"]
174    }
175);
176
177wire_enum!(
178    /// 期权类型(`constant.py:113`)。
179    OptionType, display_name = "OptionType",
180    {
181        CALL = "CALL" ["看涨期权"],
182        PUT = "PUT" ["看跌期权"]
183    }
184);
185
186wire_enum!(
187    /// 币种(`constant.py:184`)。
188    Currency, display_name = "Currency",
189    {
190        USD = "USD" [],
191        HKD = "HKD" [],
192        CNY = "CNY" [],
193        CAD = "CAD" []
194    }
195);
196
197wire_enum!(
198    /// 交易所(`constant.py:122`,全量 50 变体复刻 + 0.2.6 的 `UNKNOWN`)。
199    ///
200    /// 线值沿 Python `.value`:`CBOT` 线值为 `"CBT"`,`EUREX` 线值为 `"EUX"`,
201    /// `ETF_INDEX` 线值为 `"ETF_index"`(大小写敏感)。
202    ///
203    /// **`UNKNOWN` 与 `LOCAL` 语义不同**:`LOCAL` 是「这条数据不来自任何交易所」
204    /// (Python Special Function 的 neutral 面,也是 [`Default`] 取值);
205    /// `UNKNOWN` 是「来自交易所,但我们不认识那个码」(0.3.0 边界零丢弃)。
206    /// 出向遇 `UNKNOWN` 必须**拒单**(不得把它当交易所码发出去) —— 它是「入向译不出」的占位,不是可用的出向取值。
207    Exchange, display_name = "Exchange",
208    {
209        CFFEX = "CFFEX" [],
210        SHFE = "SHFE" [],
211        CZCE = "CZCE" [],
212        DCE = "DCE" [],
213        INE = "INE" [],
214        GFEX = "GFEX" [],
215        SSE = "SSE" [],
216        SZSE = "SZSE" [],
217        BSE = "BSE" [],
218        SHHK = "SHHK" [],
219        SZHK = "SZHK" [],
220        SGE = "SGE" [],
221        WXE = "WXE" [],
222        CFETS = "CFETS" [],
223        XBOND = "XBOND" [],
224        SMART = "SMART" [],
225        NYSE = "NYSE" [],
226        NASDAQ = "NASDAQ" [],
227        ARCA = "ARCA" [],
228        EDGEA = "EDGEA" [],
229        ISLAND = "ISLAND" [],
230        BATS = "BATS" [],
231        IEX = "IEX" [],
232        AMEX = "AMEX" [],
233        TSE = "TSE" [],
234        NYMEX = "NYMEX" [],
235        COMEX = "COMEX" [],
236        GLOBEX = "GLOBEX" [],
237        IDEALPRO = "IDEALPRO" [],
238        CME = "CME" [],
239        ICE = "ICE" [],
240        SEHK = "SEHK" [],
241        HKFE = "HKFE" [],
242        SGX = "SGX" [],
243        CBOT = "CBT" [],
244        CBOE = "CBOE" [],
245        CFE = "CFE" [],
246        DME = "DME" [],
247        EUREX = "EUX" [],
248        APEX = "APEX" [],
249        LME = "LME" [],
250        BMD = "BMD" [],
251        TOCOM = "TOCOM" [],
252        EUNX = "EUNX" [],
253        KRX = "KRX" [],
254        OTC = "OTC" [],
255        IBKRATS = "IBKRATS" [],
256        LOCAL = "LOCAL" [],
257        ETF_INDEX = "ETF_index" [],
258        SW = "SW" [],
259        UNKNOWN = "UNKNOWN" ["未知"]
260    }
261);
262
263wire_enum!(
264    /// K 线间隔(`constant.py:195`);秒值见 [`Interval::seconds`]。
265    Interval, display_name = "Interval",
266    {
267        TICK = "TICK" [],
268        MINUTE = "MINUTE" [],
269        MINUTE_5 = "MINUTE_5" [],
270        MINUTE_15 = "MINUTE_15" [],
271        MINUTE_30 = "MINUTE_30" [],
272        HOUR = "HOUR" [],
273        DAILY = "DAILY" [],
274        WEEKLY = "WEEKLY" []
275    }
276);
277
278impl Interval {
279    /// Python `.value`(秒):TICK=0, MINUTE=60, MINUTE_5=300, MINUTE_15=900,
280    /// MINUTE_30=1800, HOUR=3600, DAILY=86400, WEEKLY=604800(`constant.py:200-207`)。
281    pub fn seconds(&self) -> i64 {
282        match self {
283            Self::TICK => 0,
284            Self::MINUTE => 60,
285            Self::MINUTE_5 => 300,
286            Self::MINUTE_15 => 900,
287            Self::MINUTE_30 => 1800,
288            Self::HOUR => 3600,
289            Self::DAILY => 86400,
290            Self::WEEKLY => 604800,
291        }
292    }
293}
294
295/// 订单处于活跃态的状态集合(`object.py:21`)。
296pub const ACTIVE_STATUSES: [Status; 3] =
297    [Status::SUBMITTING, Status::NOTTRADED, Status::PARTTRADED];
298
299impl Status {
300    /// 是否活跃态(`object.py:182` 判定的唯一真源;`OrderData::is_active` 委托到此)。
301    pub fn is_active(&self) -> bool {
302        ACTIVE_STATUSES.contains(self)
303    }
304}
305
306// —— Default 取值(与 Python 字段缺省一一对齐;供对象结构体 `..Default::default()`)——
307
308/// Python `.value` 特例枚举的 neutral 面:`LOCAL`(本地数据,Special Function)。
309impl Default for Exchange {
310    fn default() -> Self {
311        Self::LOCAL
312    }
313}
314
315/// 对齐 `OrderType.LIMIT`(`object.py:162` 缺省)。
316impl Default for OrderType {
317    fn default() -> Self {
318        Self::LIMIT
319    }
320}
321
322/// 对齐 `Offset.NONE`(`object.py:164` 缺省)。
323impl Default for Offset {
324    fn default() -> Self {
325        Self::NONE
326    }
327}
328
329/// 对齐 `Status.SUBMITTING`(`object.py:168` 缺省)。
330impl Default for Status {
331    fn default() -> Self {
332        Self::SUBMITTING
333    }
334}
335
336/// Python Direction 必填无缺省;Rust `Default` 取 neutral 的 NET(Python 无法表达空方向)。
337impl Default for Direction {
338    fn default() -> Self {
339        Self::NET
340    }
341}
342
343/// Python Asset 必填;Rust `Default` 取 EQUITY(首类)。
344impl Default for Asset {
345    fn default() -> Self {
346        Self::EQUITY
347    }
348}