ek_ui/format.rs
1//! 0.3.1 Wave D(BD-UI-49):展示面数字与时间格式的唯一口径。
2//!
3//! 之前 `render.rs` 里价格各自 `format!("{:.1}")` / `format!("{:.0}")`、时间各自 `fmt_dt`,
4//! 同一张表里两种精度是常态。Python 基准(`enkeep_trade/widget.py`)的口径是
5//! 价格 `round(x, 3)`、时间 `HH:MM:SS.mmm`、日期 `YYYY-MM-DD`,本轮收成一处。
6//!
7//! **时区口径(先定死再动手,Wave D 测试方案 §0)**:表格时间列 = 北京 +08 墙钟,
8//! 与日志同源(`state::beijing_tz` 一份实现两处共用);K 线钟面仍是 UTC 重标定,不动。
9//! 判据一律喂固定 epoch,绝不在判据里读当前时间(Wave A 的 t7 栽在墙钟上)。
10
11use chrono::SecondsFormat;
12
13/// 价格:`round(v, 3)`,并且**整数值不带小数点**(Python `round` + `str` 的行为)——
14/// 101.0 → "101",3.5 → "3.5",12.345 → "12.345",12.3456 → "12.346"。
15pub fn price(v: f64) -> String {
16 let r = (v * 1000.0).round() / 1000.0;
17 // `{:?}` 对 f64 走 Rust 的最短可还原表示:101.0 → "101.0"(带 .0),
18 // 所以自己剥掉尾零,剥到小数点空了就连小数点一起剥。
19 let s = format!("{r}");
20 if let Some(int_part) = s.strip_suffix(".0") {
21 return int_part.to_string();
22 }
23 if s.contains('.') {
24 let trimmed = s.trim_end_matches('0').trim_end_matches('.');
25 return trimmed.to_string();
26 }
27 s
28}
29
30/// 数量:整数不带小数(Python 侧成交量按整数显示)。
31pub fn volume(v: f64) -> String {
32 price(v)
33}
34
35/// 涨跌幅:**带符号**、固定两位小数(`+2.50` / `-3.50`)。
36///
37/// Python 的 `PnlCell` 是 `round(x, 3)` 不带符号、只按符号换色(`widget.py:249-254`);
38/// 我们**符号与颜色都要**——同一列里「0 / 正 / 负」只靠颜色分,在 12 列的行情带里读得快会漏。
39/// 这一支是 0.3.3 Wave F 收「裸 `format!` 只许住在 format.rs」这条口径(F4 机械扫)时
40/// 显式登记的**有意偏离**,不是漏网。
41pub fn pct(v: f64) -> String {
42 let r = (v * 100.0).round() / 100.0;
43 format!("{r:+.2}")
44}
45
46/// 最小变动价位(`price_tick`):固定四位小数(`0.0001` / `1.0000`)。
47///
48/// 不走 `price` 的「剥尾零」口径:这一列的**位数本身就是信息**——`1` 与 `1.0000` 说的是
49/// 两种合约(前者按 1 跳、后者按 0.0001 跳)。Python 基准的 `BaseCell` 没有这一列
50/// (它的合约面不在 `widget.py` 的 Monitor 清单里),所以这一支是我们自己的口径,
51/// 同样显式登记在 F4 的白名单里。
52pub fn price_tick(v: f64) -> String {
53 format!("{v:.4}")
54}
55
56/// 时间列:`HH:MM:SS.mmm`(北京 +08 墙钟)。`None` → "-"(空态与 Python 同口径)。
57pub fn time_hms_millis(dt_ns: Option<i64>) -> String {
58 dt_ns
59 .map(|ns| {
60 chrono::DateTime::<chrono::Utc>::from_timestamp_nanos(ns)
61 .with_timezone(&crate::state::beijing_tz())
62 .to_rfc3339_opts(SecondsFormat::Millis, false)
63 .chars()
64 .skip(11)
65 .take(12)
66 .collect()
67 })
68 .unwrap_or_else(|| "-".to_string())
69}
70
71/// 日期列:`YYYY-MM-DD`(北京 +08 墙钟)。`None` → "-"。
72pub fn date_ymd(dt_ns: Option<i64>) -> String {
73 dt_ns
74 .map(|ns| {
75 chrono::DateTime::<chrono::Utc>::from_timestamp_nanos(ns)
76 .with_timezone(&crate::state::beijing_tz())
77 .format("%Y-%m-%d")
78 .to_string()
79 })
80 .unwrap_or_else(|| "-".to_string())
81}