ek-data 0.2.8

Trading data contracts for the enkeep ek ecosystem: venue enums, zero-dep epoch-nanosecond time, decimal-exact price rounding, vt-symbol convention, bus event dictionary, tick/bar/order/trade/position/account/contract objects and request objects (Python enkeep_trade trader/object.py + constant.py, faithfully ported).
Documentation
//! 一般交易常量枚举。
//!
//! Python 原稿:`enkeep_trade/enkeep_trade/trader/constant.py` 逐名逐值复刻。
//! 线值决策见 crate 文档第 3 条:英文令牌为线上值,`FromStr` 容中文旧值。

// 变体名沿 Python API 用大写下划线形(FUTURES_CONTRACT / MINUTE_5 /
// ETF_INDEX),对齐生态复刻语义;线值才是协议面。
#![allow(non_camel_case_types)]

use std::fmt;
use std::str::FromStr;

macro_rules! wire_enum {
    (
        $(#[$meta:meta])*
        $name:ident,
        display_name = $display:expr,
        { $( $variant:ident = $wire:literal [ $( $alias:literal ),* ] ),* $(,)? }
    ) => {
        $(#[$meta])*
        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
        #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
        pub enum $name {
            $( #[cfg_attr(feature = "serde", serde(rename = $wire))] $variant ),*
        }

        impl $name {
            /// 全部变体(顺序对齐 Python 声明序)
            pub const ALL: &'static [Self] = &[ $( Self::$variant ),* ];

            /// 线上字符串值
            pub fn as_str(&self) -> &'static str {
                match self { $( Self::$variant => $wire ),* }
            }

            /// 中文别名列表(Python 时代线值)
            pub fn chinese_alias(&self) -> &'static [&'static str] {
                match self { $( Self::$variant => &[ $( $alias ),* ] ),* }
            }

            /// 枚举显示名(用于日志与 doc)
            pub const DISPLAY: &'static str = $display;
        }

        impl fmt::Display for $name {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                f.write_str(self.as_str())
            }
        }

        impl FromStr for $name {
            type Err = EnumParseError;
            /// 接受英文线值(大小写敏感,与 Python .value 同形);
            /// 额外容忍中文旧线值别名。
            fn from_str(s: &str) -> Result<Self, Self::Err> {
                $( if s == $wire { return Ok(Self::$variant); } )*
                $(
                    $( if s == $alias { return Ok(Self::$variant); } )*
                )*
                Err(EnumParseError { input: s.to_string(), type_name: $display })
            }
        }
    };
}

/// 枚举解析失败(Python 侧是抛 `ValueError`,Rust 用错误结构表达)。
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EnumParseError {
    /// 输入原文
    pub input: String,
    /// 枚举类型名
    pub type_name: &'static str,
}

impl fmt::Display for EnumParseError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "unknown {} value: {:?}", self.type_name, self.input)
    }
}

impl std::error::Error for EnumParseError {}

wire_enum!(
    /// 订单/成交/持仓方向(`constant.py:8`)。
    Direction, display_name = "Direction",
    {
        LONG = "LONG" ["多"],
        SHORT = "SHORT" ["空"],
        NET = "NET" ["净"]
    }
);

wire_enum!(
    /// 订单/成交的开平属性(`constant.py:18`)。
    Offset, display_name = "Offset",
    {
        NONE = "NONE" [""],
        OPEN = "OPEN" ["开"],
        CLOSE = "CLOSE" ["平"],
        CLOSETODAY = "CLOSETODAY" ["平今"],
        CLOSEYESTERDAY = "CLOSEYESTERDAY" ["平昨"]
    }
);

wire_enum!(
    /// 订单状态(`constant.py:30`);活跃态判定见 [`ACTIVE_STATUSES`]。
    ///
    /// `UNKNOWN`(0.3.0,边界零丢弃):网关收到无法翻译的柜台状态码时填它,
    /// **不上岸为任何已知状态**(替柜台猜"是不是在途"就是在封装层做决策)。
    /// 刻意**不进** [`ACTIVE_STATUSES`]:在途判定是在途/撤单策略的业务决策,
    /// 归 ek-oms;网关只负责如实说"这个状态我不认识"。
    Status, display_name = "Status",
    {
        SUBMITTING = "SUBMITTING" ["提交中"],
        NOTTRADED = "NOTTRADED" ["未成交"],
        PARTTRADED = "PARTTRADED" ["部分成交"],
        ALLTRADED = "ALLTRADED" ["全部成交"],
        CANCELLED = "CANCELLED" ["已撤销"],
        REJECTED = "REJECTED" ["拒单"],
        UNKNOWN = "UNKNOWN" ["未知"]
    }
);

wire_enum!(
    /// 资产大类(`constant.py:43`);细分形态由 [`InstrumentType`] 正交表达。
    ///
    /// `UNKNOWN`(0.3.0,边界零丢弃):柜台给了无法翻译的产品类码时填它。
    /// **触发条件很窄**:只在用户显式勾选非标准产品类码(如 `'4'` 现货 /
    /// `'5'` EFP / `'I'` MI)时才会出现;默认勾选 `'1'`(期货)时该路径不可达。
    Asset, display_name = "Asset",
    {
        EQUITY = "EQUITY" ["股票"],
        BOND = "BOND" ["债券"],
        FUTURES = "FUTURES" ["期货"],
        OPTION = "OPTION" ["期权"],
        FOREX = "FOREX" ["外汇"],
        UNKNOWN = "UNKNOWN" ["未知"]
    }
);

wire_enum!(
    /// 合约形态(`constant.py:69`);与 [`Asset`] 正交组合。
    InstrumentType, display_name = "InstrumentType",
    {
        STOCK = "STOCK" ["股票"],
        ETF = "ETF" [],
        INDEX = "INDEX" ["指数"],
        FUTURES_CONTRACT = "FUTURES_CONTRACT" ["期货合约"],
        MAIN_CONTRACT = "MAIN_CONTRACT" ["主力合约"],
        CONTINUOUS_CONTRACT = "CONTINUOUS_CONTRACT" ["连续合约"],
        OPTION_CONTRACT = "OPTION_CONTRACT" ["期权合约"],
        BOND = "BOND" ["债券"],
        CONVERTIBLE_BOND = "CONVERTIBLE_BOND" ["可转债"],
        SPREAD = "SPREAD" ["价差组合"],
        HEDGE = "HEDGE" ["对冲组合"]
    }
);

wire_enum!(
    /// 委托类型(`constant.py:100`)。
    ///
    /// `UNKNOWN`(0.3.0,边界零丢弃):`(OrderPriceType, TimeCondition,
    /// VolumeCondition)` 三元组无法翻译时填它,不借 `LIMIT` 顶替
    /// (借用会让界面显示"限价",而真实类型未知 —— 那就是封装层在编故事)。
    OrderType, display_name = "OrderType",
    {
        LIMIT = "LIMIT" ["限价"],
        MARKET = "MARKET" ["市价"],
        STOP = "STOP" [],
        FAK = "FAK" [],
        FOK = "FOK" [],
        RFQ = "RFQ" ["询价"],
        UNKNOWN = "UNKNOWN" ["未知"]
    }
);

wire_enum!(
    /// 期权类型(`constant.py:113`)。
    OptionType, display_name = "OptionType",
    {
        CALL = "CALL" ["看涨期权"],
        PUT = "PUT" ["看跌期权"]
    }
);

wire_enum!(
    /// 币种(`constant.py:184`)。
    Currency, display_name = "Currency",
    {
        USD = "USD" [],
        HKD = "HKD" [],
        CNY = "CNY" [],
        CAD = "CAD" []
    }
);

wire_enum!(
    /// 交易所(`constant.py:122`,全量 50 变体复刻 + 0.2.6 的 `UNKNOWN`)。
    ///
    /// 线值沿 Python `.value`:`CBOT` 线值为 `"CBT"`,`EUREX` 线值为 `"EUX"`,
    /// `ETF_INDEX` 线值为 `"ETF_index"`(大小写敏感)。
    ///
    /// **`UNKNOWN` 与 `LOCAL` 语义不同**:`LOCAL` 是「这条数据不来自任何交易所」
    /// (Python Special Function 的 neutral 面,也是 [`Default`] 取值);
    /// `UNKNOWN` 是「来自交易所,但我们不认识那个码」(0.3.0 边界零丢弃)。
    /// 出向遇 `UNKNOWN` 必须**拒单**(不得把它当交易所码发出去) —— 它是「入向译不出」的占位,不是可用的出向取值。
    Exchange, display_name = "Exchange",
    {
        CFFEX = "CFFEX" [],
        SHFE = "SHFE" [],
        CZCE = "CZCE" [],
        DCE = "DCE" [],
        INE = "INE" [],
        GFEX = "GFEX" [],
        SSE = "SSE" [],
        SZSE = "SZSE" [],
        BSE = "BSE" [],
        SHHK = "SHHK" [],
        SZHK = "SZHK" [],
        SGE = "SGE" [],
        WXE = "WXE" [],
        CFETS = "CFETS" [],
        XBOND = "XBOND" [],
        SMART = "SMART" [],
        NYSE = "NYSE" [],
        NASDAQ = "NASDAQ" [],
        ARCA = "ARCA" [],
        EDGEA = "EDGEA" [],
        ISLAND = "ISLAND" [],
        BATS = "BATS" [],
        IEX = "IEX" [],
        AMEX = "AMEX" [],
        TSE = "TSE" [],
        NYMEX = "NYMEX" [],
        COMEX = "COMEX" [],
        GLOBEX = "GLOBEX" [],
        IDEALPRO = "IDEALPRO" [],
        CME = "CME" [],
        ICE = "ICE" [],
        SEHK = "SEHK" [],
        HKFE = "HKFE" [],
        SGX = "SGX" [],
        CBOT = "CBT" [],
        CBOE = "CBOE" [],
        CFE = "CFE" [],
        DME = "DME" [],
        EUREX = "EUX" [],
        APEX = "APEX" [],
        LME = "LME" [],
        BMD = "BMD" [],
        TOCOM = "TOCOM" [],
        EUNX = "EUNX" [],
        KRX = "KRX" [],
        OTC = "OTC" [],
        IBKRATS = "IBKRATS" [],
        LOCAL = "LOCAL" [],
        ETF_INDEX = "ETF_index" [],
        SW = "SW" [],
        UNKNOWN = "UNKNOWN" ["未知"]
    }
);

wire_enum!(
    /// K 线间隔(`constant.py:195`);秒值见 [`Interval::seconds`]。
    Interval, display_name = "Interval",
    {
        TICK = "TICK" [],
        MINUTE = "MINUTE" [],
        MINUTE_5 = "MINUTE_5" [],
        MINUTE_15 = "MINUTE_15" [],
        MINUTE_30 = "MINUTE_30" [],
        HOUR = "HOUR" [],
        DAILY = "DAILY" [],
        WEEKLY = "WEEKLY" []
    }
);

impl Interval {
    /// Python `.value`(秒):TICK=0, MINUTE=60, MINUTE_5=300, MINUTE_15=900,
    /// MINUTE_30=1800, HOUR=3600, DAILY=86400, WEEKLY=604800(`constant.py:200-207`)。
    pub fn seconds(&self) -> i64 {
        match self {
            Self::TICK => 0,
            Self::MINUTE => 60,
            Self::MINUTE_5 => 300,
            Self::MINUTE_15 => 900,
            Self::MINUTE_30 => 1800,
            Self::HOUR => 3600,
            Self::DAILY => 86400,
            Self::WEEKLY => 604800,
        }
    }
}

/// 订单处于活跃态的状态集合(`object.py:21`)。
pub const ACTIVE_STATUSES: [Status; 3] =
    [Status::SUBMITTING, Status::NOTTRADED, Status::PARTTRADED];

impl Status {
    /// 是否活跃态(`object.py:182` 判定的唯一真源;`OrderData::is_active` 委托到此)。
    pub fn is_active(&self) -> bool {
        ACTIVE_STATUSES.contains(self)
    }
}

// —— Default 取值(与 Python 字段缺省一一对齐;供对象结构体 `..Default::default()`)——

/// Python `.value` 特例枚举的 neutral 面:`LOCAL`(本地数据,Special Function)。
impl Default for Exchange {
    fn default() -> Self {
        Self::LOCAL
    }
}

/// 对齐 `OrderType.LIMIT`(`object.py:162` 缺省)。
impl Default for OrderType {
    fn default() -> Self {
        Self::LIMIT
    }
}

/// 对齐 `Offset.NONE`(`object.py:164` 缺省)。
impl Default for Offset {
    fn default() -> Self {
        Self::NONE
    }
}

/// 对齐 `Status.SUBMITTING`(`object.py:168` 缺省)。
impl Default for Status {
    fn default() -> Self {
        Self::SUBMITTING
    }
}

/// Python Direction 必填无缺省;Rust `Default` 取 neutral 的 NET(Python 无法表达空方向)。
impl Default for Direction {
    fn default() -> Self {
        Self::NET
    }
}

/// Python Asset 必填;Rust `Default` 取 EQUITY(首类)。
impl Default for Asset {
    fn default() -> Self {
        Self::EQUITY
    }
}