ek-ui 0.8.1

ek 生态常驻主界面(egui/eframe):插件即界面——观测页 + 日志页,车头与 UI 同插件
Documentation
//! 0.3.1 Wave D(BD-UI-49):展示面数字与时间格式的唯一口径。
//!
//! 之前 `render.rs` 里价格各自 `format!("{:.1}")` / `format!("{:.0}")`、时间各自 `fmt_dt`,
//! 同一张表里两种精度是常态。Python 基准(`enkeep_trade/widget.py`)的口径是
//! 价格 `round(x, 3)`、时间 `HH:MM:SS.mmm`、日期 `YYYY-MM-DD`,本轮收成一处。
//!
//! **时区口径(先定死再动手,Wave D 测试方案 §0)**:表格时间列 = 北京 +08 墙钟,
//! 与日志同源(`state::beijing_tz` 一份实现两处共用);K 线钟面仍是 UTC 重标定,不动。
//! 判据一律喂固定 epoch,绝不在判据里读当前时间(Wave A 的 t7 栽在墙钟上)。

use chrono::SecondsFormat;

/// 价格:`round(v, 3)`,并且**整数值不带小数点**(Python `round` + `str` 的行为)——
/// 101.0 → "101",3.5 → "3.5",12.345 → "12.345",12.3456 → "12.346"。
pub fn price(v: f64) -> String {
    let r = (v * 1000.0).round() / 1000.0;
    // `{:?}` 对 f64 走 Rust 的最短可还原表示:101.0 → "101.0"(带 .0),
    // 所以自己剥掉尾零,剥到小数点空了就连小数点一起剥。
    let s = format!("{r}");
    if let Some(int_part) = s.strip_suffix(".0") {
        return int_part.to_string();
    }
    if s.contains('.') {
        let trimmed = s.trim_end_matches('0').trim_end_matches('.');
        return trimmed.to_string();
    }
    s
}

/// 数量:整数不带小数(Python 侧成交量按整数显示)。
pub fn volume(v: f64) -> String {
    price(v)
}

/// 涨跌幅:**带符号**、固定两位小数(`+2.50` / `-3.50`)。
///
/// Python 的 `PnlCell` 是 `round(x, 3)` 不带符号、只按符号换色(`widget.py:249-254`);
/// 我们**符号与颜色都要**——同一列里「0 / 正 / 负」只靠颜色分,在 12 列的行情带里读得快会漏。
/// 这一支是 0.3.3 Wave F 收「裸 `format!` 只许住在 format.rs」这条口径(F4 机械扫)时
/// 显式登记的**有意偏离**,不是漏网。
pub fn pct(v: f64) -> String {
    let r = (v * 100.0).round() / 100.0;
    format!("{r:+.2}")
}

/// 最小变动价位(`price_tick`):固定四位小数(`0.0001` / `1.0000`)。
///
/// 不走 `price` 的「剥尾零」口径:这一列的**位数本身就是信息**——`1` 与 `1.0000` 说的是
/// 两种合约(前者按 1 跳、后者按 0.0001 跳)。Python 基准的 `BaseCell` 没有这一列
/// (它的合约面不在 `widget.py` 的 Monitor 清单里),所以这一支是我们自己的口径,
/// 同样显式登记在 F4 的白名单里。
pub fn price_tick(v: f64) -> String {
    format!("{v:.4}")
}

/// 时间列:`HH:MM:SS.mmm`(北京 +08 墙钟)。`None` → "-"(空态与 Python 同口径)。
pub fn time_hms_millis(dt_ns: Option<i64>) -> String {
    dt_ns
        .map(|ns| {
            chrono::DateTime::<chrono::Utc>::from_timestamp_nanos(ns)
                .with_timezone(&crate::state::beijing_tz())
                .to_rfc3339_opts(SecondsFormat::Millis, false)
                .chars()
                .skip(11)
                .take(12)
                .collect()
        })
        .unwrap_or_else(|| "-".to_string())
}

/// 日期列:`YYYY-MM-DD`(北京 +08 墙钟)。`None` → "-"。
pub fn date_ymd(dt_ns: Option<i64>) -> String {
    dt_ns
        .map(|ns| {
            chrono::DateTime::<chrono::Utc>::from_timestamp_nanos(ns)
                .with_timezone(&crate::state::beijing_tz())
                .format("%Y-%m-%d")
                .to_string()
        })
        .unwrap_or_else(|| "-".to_string())
}