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    Status, display_name = "Status",
108    {
109        SUBMITTING = "SUBMITTING" ["提交中"],
110        NOTTRADED = "NOTTRADED" ["未成交"],
111        PARTTRADED = "PARTTRADED" ["部分成交"],
112        ALLTRADED = "ALLTRADED" ["全部成交"],
113        CANCELLED = "CANCELLED" ["已撤销"],
114        REJECTED = "REJECTED" ["拒单"]
115    }
116);
117
118wire_enum!(
119    /// 资产大类(`constant.py:43`);细分形态由 [`InstrumentType`] 正交表达。
120    Asset, display_name = "Asset",
121    {
122        EQUITY = "EQUITY" ["股票"],
123        BOND = "BOND" ["债券"],
124        FUTURES = "FUTURES" ["期货"],
125        OPTION = "OPTION" ["期权"],
126        FOREX = "FOREX" ["外汇"]
127    }
128);
129
130wire_enum!(
131    /// 合约形态(`constant.py:69`);与 [`Asset`] 正交组合。
132    InstrumentType, display_name = "InstrumentType",
133    {
134        STOCK = "STOCK" ["股票"],
135        ETF = "ETF" [],
136        INDEX = "INDEX" ["指数"],
137        FUTURES_CONTRACT = "FUTURES_CONTRACT" ["期货合约"],
138        MAIN_CONTRACT = "MAIN_CONTRACT" ["主力合约"],
139        CONTINUOUS_CONTRACT = "CONTINUOUS_CONTRACT" ["连续合约"],
140        OPTION_CONTRACT = "OPTION_CONTRACT" ["期权合约"],
141        BOND = "BOND" ["债券"],
142        CONVERTIBLE_BOND = "CONVERTIBLE_BOND" ["可转债"],
143        SPREAD = "SPREAD" ["价差组合"],
144        HEDGE = "HEDGE" ["对冲组合"]
145    }
146);
147
148wire_enum!(
149    /// 委托类型(`constant.py:100`)。
150    OrderType, display_name = "OrderType",
151    {
152        LIMIT = "LIMIT" ["限价"],
153        MARKET = "MARKET" ["市价"],
154        STOP = "STOP" [],
155        FAK = "FAK" [],
156        FOK = "FOK" [],
157        RFQ = "RFQ" ["询价"]
158    }
159);
160
161wire_enum!(
162    /// 期权类型(`constant.py:113`)。
163    OptionType, display_name = "OptionType",
164    {
165        CALL = "CALL" ["看涨期权"],
166        PUT = "PUT" ["看跌期权"]
167    }
168);
169
170wire_enum!(
171    /// 币种(`constant.py:184`)。
172    Currency, display_name = "Currency",
173    {
174        USD = "USD" [],
175        HKD = "HKD" [],
176        CNY = "CNY" [],
177        CAD = "CAD" []
178    }
179);
180
181wire_enum!(
182    /// 交易所(`constant.py:122`,全量 50 变体复刻)。
183    ///
184    /// 线值沿 Python `.value`:`CBOT` 线值为 `"CBT"`,`EUREX` 线值为 `"EUX"`,
185    /// `ETF_INDEX` 线值为 `"ETF_index"`(大小写敏感)。
186    Exchange, display_name = "Exchange",
187    {
188        CFFEX = "CFFEX" [],
189        SHFE = "SHFE" [],
190        CZCE = "CZCE" [],
191        DCE = "DCE" [],
192        INE = "INE" [],
193        GFEX = "GFEX" [],
194        SSE = "SSE" [],
195        SZSE = "SZSE" [],
196        BSE = "BSE" [],
197        SHHK = "SHHK" [],
198        SZHK = "SZHK" [],
199        SGE = "SGE" [],
200        WXE = "WXE" [],
201        CFETS = "CFETS" [],
202        XBOND = "XBOND" [],
203        SMART = "SMART" [],
204        NYSE = "NYSE" [],
205        NASDAQ = "NASDAQ" [],
206        ARCA = "ARCA" [],
207        EDGEA = "EDGEA" [],
208        ISLAND = "ISLAND" [],
209        BATS = "BATS" [],
210        IEX = "IEX" [],
211        AMEX = "AMEX" [],
212        TSE = "TSE" [],
213        NYMEX = "NYMEX" [],
214        COMEX = "COMEX" [],
215        GLOBEX = "GLOBEX" [],
216        IDEALPRO = "IDEALPRO" [],
217        CME = "CME" [],
218        ICE = "ICE" [],
219        SEHK = "SEHK" [],
220        HKFE = "HKFE" [],
221        SGX = "SGX" [],
222        CBOT = "CBT" [],
223        CBOE = "CBOE" [],
224        CFE = "CFE" [],
225        DME = "DME" [],
226        EUREX = "EUX" [],
227        APEX = "APEX" [],
228        LME = "LME" [],
229        BMD = "BMD" [],
230        TOCOM = "TOCOM" [],
231        EUNX = "EUNX" [],
232        KRX = "KRX" [],
233        OTC = "OTC" [],
234        IBKRATS = "IBKRATS" [],
235        LOCAL = "LOCAL" [],
236        ETF_INDEX = "ETF_index" [],
237        SW = "SW" []
238    }
239);
240
241wire_enum!(
242    /// K 线间隔(`constant.py:195`);秒值见 [`Interval::seconds`]。
243    Interval, display_name = "Interval",
244    {
245        TICK = "TICK" [],
246        MINUTE = "MINUTE" [],
247        MINUTE_5 = "MINUTE_5" [],
248        MINUTE_15 = "MINUTE_15" [],
249        MINUTE_30 = "MINUTE_30" [],
250        HOUR = "HOUR" [],
251        DAILY = "DAILY" [],
252        WEEKLY = "WEEKLY" []
253    }
254);
255
256impl Interval {
257    /// Python `.value`(秒):TICK=0, MINUTE=60, MINUTE_5=300, MINUTE_15=900,
258    /// MINUTE_30=1800, HOUR=3600, DAILY=86400, WEEKLY=604800(`constant.py:200-207`)。
259    pub fn seconds(&self) -> i64 {
260        match self {
261            Self::TICK => 0,
262            Self::MINUTE => 60,
263            Self::MINUTE_5 => 300,
264            Self::MINUTE_15 => 900,
265            Self::MINUTE_30 => 1800,
266            Self::HOUR => 3600,
267            Self::DAILY => 86400,
268            Self::WEEKLY => 604800,
269        }
270    }
271}
272
273/// 订单处于活跃态的状态集合(`object.py:21`)。
274pub const ACTIVE_STATUSES: [Status; 3] =
275    [Status::SUBMITTING, Status::NOTTRADED, Status::PARTTRADED];
276
277impl Status {
278    /// 是否活跃态(`object.py:182` 判定的唯一真源;`OrderData::is_active` 委托到此)。
279    pub fn is_active(&self) -> bool {
280        ACTIVE_STATUSES.contains(self)
281    }
282}
283
284// —— Default 取值(与 Python 字段缺省一一对齐;供对象结构体 `..Default::default()`)——
285
286/// Python `.value` 特例枚举的 neutral 面:`LOCAL`(本地数据,Special Function)。
287impl Default for Exchange {
288    fn default() -> Self {
289        Self::LOCAL
290    }
291}
292
293/// 对齐 `OrderType.LIMIT`(`object.py:162` 缺省)。
294impl Default for OrderType {
295    fn default() -> Self {
296        Self::LIMIT
297    }
298}
299
300/// 对齐 `Offset.NONE`(`object.py:164` 缺省)。
301impl Default for Offset {
302    fn default() -> Self {
303        Self::NONE
304    }
305}
306
307/// 对齐 `Status.SUBMITTING`(`object.py:168` 缺省)。
308impl Default for Status {
309    fn default() -> Self {
310        Self::SUBMITTING
311    }
312}
313
314/// Python Direction 必填无缺省;Rust `Default` 取 neutral 的 NET(Python 无法表达空方向)。
315impl Default for Direction {
316    fn default() -> Self {
317        Self::NET
318    }
319}
320
321/// Python Asset 必填;Rust `Default` 取 EQUITY(首类)。
322impl Default for Asset {
323    fn default() -> Self {
324        Self::EQUITY
325    }
326}