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
//! vt_symbol 约定:`"<exchange>.<symbol>"`(如 `CFFEX.IF2506`)。
//!
//! Python 原稿:`trader/utility.py` 的 `extract_vt_symbol`;
//! object.py 各对象 `__post_init__` 组装 `en_symbol` 的同一约定,
//! Rust 版集中于本模块(对象侧调 [`vt_symbol`] 组装)。

use crate::constant::Exchange;
use std::fmt;
use std::str::FromStr;

/// 组装 vt_symbol(exchange 线值 + "." + symbol)。
pub fn vt_symbol(symbol: &str, exchange: Exchange) -> String {
    let mut buf = String::with_capacity(exchange.as_str().len() + 1 + symbol.len());
    write_vt_symbol(&mut buf, symbol, exchange);
    buf
}

/// 零分配组装:把 vt_symbol **追加**到复用缓冲(逐 tick 热路径用,避免每笔一次分配)。
pub fn write_vt_symbol(buf: &mut String, symbol: &str, exchange: Exchange) {
    buf.push_str(exchange.as_str());
    buf.push('.');
    buf.push_str(symbol);
}

/// vt_symbol 解析失败。
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SymbolError {
    /// 不是 `exchange.symbol` 两段形(缺 `.` 或 exchange 段丢失)
    Malformed,
    /// 前段不是已注册交易所线值
    UnknownExchange(String),
}

impl fmt::Display for SymbolError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Malformed => f.write_str("malformed vt_symbol, expect `<exchange>.<symbol>`"),
            Self::UnknownExchange(s) => write!(f, "unknown exchange: {s}"),
        }
    }
}

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

/// 解折 vt_symbol → (symbol, exchange)。
///
/// 语义对齐 `extract_vt_symbol`:**首个** `.` 前为交易所段(symbol 段允许含 `.`)。
pub fn parse_vt_symbol(vt: &str) -> Result<(String, Exchange), SymbolError> {
    let Some((head, tail)) = vt.split_once('.') else {
        return Err(SymbolError::Malformed);
    };
    if head.is_empty() {
        return Err(SymbolError::Malformed);
    }
    Exchange::from_str(head)
        .map(|ex| (tail.to_string(), ex))
        .map_err(|_| SymbolError::UnknownExchange(head.to_string()))
}