ek-data 0.2.2

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 `utility.py:275-318` **精确十进制**同面。
//!
//! 关键点:Python 版走 `Decimal(str(v))`,故 `floor_to(0.3, 0.1) == 0.3`
//! 而非 f64 直觉的 `0.2`;`round_to` 的平局取偶(`round_to(2.5, 1.0) == 2.0`)。
//! 这些正是网关/策略把价格对齐到最小变动价位时的真实现场。

use ek_data::{ceil_to, floor_to, get_digits, round_to, try_round_to};

/// half-even 平局:2.5→2、3.5→4、-2.5→-2(Python `round(Decimal)` 默认)。
#[test]
fn round_to_uses_half_even_like_python() {
    assert_eq!(round_to(2.5, 1.0), 2.0);
    assert_eq!(round_to(3.5, 1.0), 4.0);
    assert_eq!(round_to(-2.5, 1.0), -2.0);
    assert_eq!(round_to(-3.5, 1.0), -4.0);
    assert_eq!(round_to(0.5, 1.0), 0.0);
    assert_eq!(round_to(1.5, 1.0), 2.0);
}

/// 十进制精确性:朴素 f64 会错的组合必须给 Python 的答案。
#[test]
fn decimal_exactness_cases() {
    // 0.3/0.1 在 f64 下是 2.9999999999999996 → 朴素 floor 给 0.2
    assert_eq!(floor_to(0.3, 0.1), 0.3);
    assert_eq!(ceil_to(0.3, 0.1), 0.3);
    assert_eq!(round_to(0.3, 0.1), 0.3);

    // 3500.3 / 0.2 = 17501.5 → half-even → 17502 → 3500.4
    assert_eq!(round_to(3500.3, 0.2), 3500.4);

    // 期货真实现场:IF 最小变动 0.2
    assert_eq!(floor_to(3500.35, 0.2), 3500.2);
    assert_eq!(ceil_to(3500.35, 0.2), 3500.4);
    // 平局例子:3500.1 / 0.2 = 17500.5 → half-even 取偶 → 17500 → 3500.0
    // (若实现是 half-up / f64 round 就会给 3500.2)
    assert_eq!(round_to(3500.1, 0.2), 3500.0);
    assert_eq!(round_to(3500.3, 0.2), 3500.4);
}

/// floor/ceil 方向性与整数倍恒等。
#[test]
fn floor_ceil_directions() {
    assert_eq!(floor_to(1.9, 1.0), 1.0);
    assert_eq!(ceil_to(1.1, 1.0), 2.0);
    assert_eq!(floor_to(-1.1, 1.0), -2.0);
    assert_eq!(ceil_to(-1.9, 1.0), -1.0);
    // 已在栅格上 → 恒等
    assert_eq!(round_to(10.0, 0.01), 10.0);
    assert_eq!(floor_to(3500.2, 0.2), 3500.2);
    assert_eq!(ceil_to(-0.4, 0.2), -0.4);
}

/// get_digits 的 Python `str(float)` 口径。
#[test]
fn get_digits_matches_python_repr() {
    assert_eq!(get_digits(0.001), 3);
    assert_eq!(get_digits(0.1), 1);
    assert_eq!(get_digits(100.0), 1); // str(100.0) == "100.0"
    assert_eq!(get_digits(0.0), 1); // str(0.0) == "0.0"
    assert_eq!(get_digits(1e-5), 5); // "1e-05"
    assert_eq!(get_digits(1e16), 0); // "1e+16" 既无 "." 也非 "e-"
    assert_eq!(get_digits(-3.25), 2);
}

/// 退化输入:target=0 / 非有限 → `try_*` 给 None,`*_to` 给 NaN(不静默瞎算)。
#[test]
fn degenerate_inputs_are_explicit() {
    assert!(try_round_to(1.0, 0.0).is_none());
    assert!(round_to(1.0, 0.0).is_nan());
    assert!(floor_to(f64::NAN, 1.0).is_nan());
    assert!(ceil_to(f64::INFINITY, 1.0).is_nan());
}

/// target 为负:Python 同面 —— 比值先取整,再乘回负 target。
#[test]
fn negative_target_matches_python() {
    // round(2.5/-1) = -2(half-even)→ -2 * -1 = 2.0
    assert_eq!(round_to(2.5, -1.0), 2.0);
    // floor(1.9/-1) = floor(-1.9) = -2 → 2.0
    assert_eq!(floor_to(1.9, -1.0), 2.0);
    // ceil(1.9/-1) = ceil(-1.9) = -1 → 1.0
    assert_eq!(ceil_to(1.9, -1.0), 1.0);
    // 双负:-2.5 / -1 = 2.5 → half-even → 2 → 2 * -1 = -2.0
    assert_eq!(round_to(-2.5, -1.0), -2.0);
    // round(-17501.5) = -17502 → *(-0.2) = 3500.4
    assert_eq!(round_to(3500.3, -0.2), 3500.4);
}