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
//! 价格与小数工具(`utility.py:275-318` 的忠实移植)。
//!
//! Python 版用 `Decimal(str(value))` 做**精确十进制**运算,不能用朴素 f64
//! 替代:经典反例 `floor(0.3 / 0.1)` 在 f64 下是 `2`(0.3/0.1=2.9999…),
//! 而 Python 版给出 `0.3`。本模块以「十进制重表 → i128 整数比」复刻该语义。
//!
//! 十进制来源:Python `str(float)` 与 Rust `Debug for f64` 都是**最短往返**
//! 表示(`0.1` → `"0.1"`,`100.0` → `"100.0"`,`1e-5` → `"1e-5"`),故可从
//! 两者的字符串形态拿到同一组十进制数字。
//!
//! 舍入规则对齐 Python:
//! - [`round_to`] 用 **half-even**(Python `round(Decimal)` 默认 `ROUND_HALF_EVEN`)
//! - [`floor_to`] 向 `-∞`、[`ceil_to`] 向 `+∞`(Python `math.floor/ceil`)
//!
//! value 与 target 各自只解析一次( Mantissa×10^-scale 形式直进整数比);
//! target 的符号折叠进比值分子,保证分母恒正,平局/方向语义统一。

/// Python `str(float)` 的等价形态:最短往返十进制文本。
fn repr_of(value: f64) -> String {
    format!("{value:?}")
}

/// 十进制文本 → `(mantissa, scale)`,表示 `mantissa × 10^-scale`(符号折入 mantissa)。
///
/// 支持 `"123.45"` / `"-0.5"` / `"1e-5"` / `"100.0"`;拒绝非有限值与超范围。
fn parse_decimal(s: &str) -> Option<(i128, u32)> {
    let s = s.trim();
    if s.is_empty() || s.eq_ignore_ascii_case("nan") || s.contains("inf") {
        return None;
    }
    let (neg, body) = match s.strip_prefix('-') {
        Some(rest) => (true, rest),
        None => (false, s.strip_prefix('+').unwrap_or(s)),
    };
    let (mantissa_str, exp) = match body.split_once(['e', 'E']) {
        Some((m, e)) => (m, e.parse::<i32>().ok()?),
        None => (body, 0),
    };
    let (int_str, frac_str) = match mantissa_str.split_once('.') {
        Some((i, f)) => (i, f),
        None => (mantissa_str, ""),
    };
    if int_str.is_empty() && frac_str.is_empty() {
        return None;
    }
    if !int_str.bytes().all(|b| b.is_ascii_digit()) || !frac_str.bytes().all(|b| b.is_ascii_digit())
    {
        return None;
    }
    let digits: String = format!("{int_str}{frac_str}");
    let mut mant = digits.parse::<i128>().ok()?;
    if neg {
        mant = -mant;
    }
    let mut scale = frac_str.len() as i32 - exp;
    if scale < 0 {
        mant = mant.checked_mul(10i128.checked_pow((-scale) as u32)?)?;
        scale = 0;
    }
    Some((mant, scale as u32))
}

/// 十进制操作数对 `(mantissa, scale)`(target 已保证非零)。
type DecParts = (i128, u32);

/// `value / target` 的精确整数比 `(num, den)`(den 恒 > 0;target 符号已折入 num)。
fn ratio_parts(value: DecParts, target: DecParts) -> Option<(i128, i128)> {
    let (mut num, sv) = value;
    let (mut den, st) = target;
    debug_assert!(den != 0);
    if den < 0 {
        num = num.checked_neg()?;
        den = den.checked_neg()?;
    }
    // (mv·10^-sv) / (mt·10^-st) = mv/mt · 10^(st-sv)
    let p = st as i32 - sv as i32;
    if p >= 0 {
        num = num.checked_mul(10i128.checked_pow(p as u32)?)?;
    } else {
        den = den.checked_mul(10i128.checked_pow((-p) as u32)?)?;
    }
    Some((num, den))
}

/// `round_half_even(num / den)`(den > 0)。
fn div_round_half_even(num: i128, den: i128) -> i128 {
    let q = num.div_euclid(den);
    let r = num.rem_euclid(den);
    match (r * 2).cmp(&den) {
        std::cmp::Ordering::Greater => q + 1,
        std::cmp::Ordering::Less => q,
        std::cmp::Ordering::Equal => {
            if q % 2 == 0 {
                q
            } else {
                q + 1
            }
        }
    }
}

/// `floor(num / den)`(den > 0)。
fn div_floor(num: i128, den: i128) -> i128 {
    num.div_euclid(den)
}

/// `ceil(num / den)`(den > 0)。
fn div_ceil(num: i128, den: i128) -> i128 {
    -((-num).div_euclid(den))
}

/// `q × target` 还原为 f64(目标以 `(mantissa, scale)` 表达)。
fn combine(q: i128, target: DecParts) -> Option<f64> {
    let (mt, st) = target;
    let prod = q.checked_mul(mt)?;
    Some(prod as f64 / 10f64.powi(st as i32))
}

fn target_parts(target: f64) -> Option<DecParts> {
    let parts = parse_decimal(&repr_of(target))?;
    (parts.0 != 0).then_some(parts)
}

/// 取整到 `target` 的最近整数倍,平局取**偶**(Python `round_to`,utility.py:275)。
///
/// 例:`round_to(2.5, 1.0) == 2.0`(不是 3.0);`round_to(3.5, 1.0) == 4.0`。
pub fn round_to(value: f64, target: f64) -> f64 {
    try_round_to(value, target).unwrap_or(f64::NAN)
}

/// [`round_to`] 的可失败版本;输入非有限 / target 为 0 / 溢出时返回 `None`。
pub fn try_round_to(value: f64, target: f64) -> Option<f64> {
    let t = target_parts(target)?;
    let v = parse_decimal(&repr_of(value))?;
    let (num, den) = ratio_parts(v, t)?;
    combine(div_round_half_even(num, den), t)
}

/// 向 `-∞` 取整到 `target` 的整数倍(Python `floor_to`,utility.py:285)。
pub fn floor_to(value: f64, target: f64) -> f64 {
    try_floor_to(value, target).unwrap_or(f64::NAN)
}

/// [`floor_to`] 的可失败版本。
pub fn try_floor_to(value: f64, target: f64) -> Option<f64> {
    let t = target_parts(target)?;
    let v = parse_decimal(&repr_of(value))?;
    let (num, den) = ratio_parts(v, t)?;
    combine(div_floor(num, den), t)
}

/// 向 `+∞` 取整到 `target` 的整数倍(Python `ceil_to`,utility.py:295)。
pub fn ceil_to(value: f64, target: f64) -> f64 {
    try_ceil_to(value, target).unwrap_or(f64::NAN)
}

/// [`ceil_to`] 的可失败版本。
pub fn try_ceil_to(value: f64, target: f64) -> Option<f64> {
    let t = target_parts(target)?;
    let v = parse_decimal(&repr_of(value))?;
    let (num, den) = ratio_parts(v, t)?;
    combine(div_ceil(num, den), t)
}

/// 小数位数(Python `get_digits`,utility.py:305)。
///
/// 语义按 Python `str(float)` 计:整数形浮点(`100.0`)算 1 位,
/// 科学计数法(`1e-5`)取指数,`1e16` 之类正指数形算 0 位。
pub fn get_digits(value: f64) -> i32 {
    let s = repr_of(value);
    if let Some((_, buf)) = s.split_once("e-") {
        buf.parse().unwrap_or(0)
    } else if let Some((_, buf)) = s.split_once('.') {
        buf.len() as i32
    } else {
        0
    }
}