ek-data 0.2.3

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
//! flows_time_negative —— F10 时间对象随流 + F12 负面集(坏载荷/坏键/怪癖)。
//!
//! 要点:流上载荷的证据链完整 —— dt 进流、出流仍是同一 nanos 整数;
//! 错形载荷在订阅端被 downcast 静默拒收(与宿主订阅语义一致,不炸 handler)。

mod common;

use common::{Capture, fixture, wait_until};
use ek_data::{
    Civil, DtNs, EVENT_LOG, EVENT_TICK, Exchange, LogData, TickData, format_civil, parse_civil,
    trading_day, try_civil_to_nanos,
};
use ek_event::Event;
use std::sync::Arc;
use std::time::Duration;

fn fx_tick(sym: &str, exchange: Exchange, dt: DtNs) -> TickData {
    TickData {
        gateway_name: "xt".into(),
        symbol: sym.into(),
        exchange,
        datetime: dt,
        last_price: 3520.2,
        ..Default::default()
    }
}

/// F10-1 行情时间随流:parse_civil → 流上载荷 datetime 出流格式化回环 (往返一次成功)。
#[test]
fn f10_civil_roundtrip_over_bus() {
    let t0 = fixture::t0();
    let eng = ek_event::EventEngine::new();
    let cap = Capture::<TickData>::new();
    cap.register_on(&eng, format!("{EVENT_TICK}CFFEX.IF2506"));
    eng.start();

    let tick = fx_tick("IF2506", Exchange::CFFEX, t0);
    eng.put(Event::with_data(
        format!("{EVENT_TICK}CFFEX.IF2506"),
        Arc::new(tick.clone()),
    ))
    .unwrap();

    assert!(wait_until(Duration::from_secs(2), || cap.len() == 1));
    eng.stop();
    let seen = cap.last().unwrap().datetime;
    assert_eq!(seen, t0, "进流出流同一 nanos 整数(零信息损耗)");
    assert_eq!(format_civil(seen), "2024-12-25 09:30:01.500000000");
    assert_eq!(parse_civil(&format_civil(seen)).unwrap(), t0);
    // 交易日:2024-12-25 的 days 计数 > 0(锚点 1970-01-01 = 0)
    assert!(trading_day(seen) > 0);
}

/// F10-2 边界值随流:i64 边界 nanos 在流上完整存活(顺序完整 + 定长不拆)。
#[test]
fn f10_boundary_nanos_survives_bus() {
    let eng = ek_event::EventEngine::new();
    let cap = Capture::<TickData>::new();
    cap.register_on(&eng, format!("{EVENT_TICK}CFFEX.IF2506"));
    eng.start();

    let edge = fx_tick("IF2506", Exchange::CFFEX, ek_data::DTNS_MAX);
    eng.put(Event::with_data(
        format!("{EVENT_TICK}CFFEX.IF2506"),
        Arc::new(edge),
    ))
    .unwrap();

    assert!(wait_until(Duration::from_secs(2), || cap.len() == 1));
    eng.stop();
    assert_eq!(cap.last().unwrap().datetime, ek_data::DTNS_MAX);
    // 上边界的 civil 表示(2262-04-11T23:47:16.854775807)是打包产物,不因流损坏
    let c = ek_data::civil_from_nanos(ek_data::DTNS_MAX);
    assert_eq!(c.year, 2262);
}

/// F12-1 坏载荷静默拒收:同键发 non-Tick 形载荷,订阅端不炸、计数不多。
#[test]
fn f12_1_wrong_payload_silently_ignored() {
    let eng = ek_event::EventEngine::new();
    let cap = Capture::<TickData>::new();
    cap.register_on(&eng, format!("{EVENT_TICK}CFFEX.IF2506"));
    eng.start();

    let tick = fx_tick("IF2506", Exchange::CFFEX, fixture::t0());
    // 键对载荷错:发 LogData / 纯数字 / 空载荷,再发一帧正确载荷
    eng.put(Event::with_data(
        format!("{EVENT_TICK}CFFEX.IF2506"),
        Arc::new(LogData {
            gateway_name: "xt".into(),
            msg: "wrong".into(),
            level: 20,
        }),
    ))
    .unwrap();
    eng.put(Event::with_data(
        format!("{EVENT_TICK}CFFEX.IF2506"),
        Arc::new(42i64),
    ))
    .unwrap();
    eng.put(Event::new(format!("{EVENT_TICK}CFFEX.IF2506")))
        .unwrap();
    eng.put(Event::with_data(
        format!("{EVENT_TICK}CFFEX.IF2506"),
        Arc::new(tick),
    ))
    .unwrap();

    assert!(
        wait_until(Duration::from_secs(2), || cap.len() == 1),
        "只收对形的一帧"
    );
    eng.stop();
}

/// F12-2 坏键不串流:异型/异标的/字面错误键都不进对应订阅(以排空哨兵为界)。
#[test]
fn f12_2_wrong_key_isolated() {
    let eng = ek_event::EventEngine::new();
    let tick_cap = Capture::<TickData>::new();
    tick_cap.register_on(&eng, format!("{EVENT_TICK}CFFEX.IF2506"));
    let log_cap = Capture::<LogData>::new();
    log_cap.register_on(&eng, EVENT_LOG);
    eng.start();

    // tick 载荷发到 eLog 键 / 异标的组合键上都不进 tick 订阅
    let tick = fx_tick("IF2506", Exchange::CFFEX, fixture::t0());
    eng.put(Event::with_data(EVENT_LOG, Arc::new(tick.clone())))
        .unwrap();
    eng.put(Event::with_data(
        format!("{EVENT_TICK}CFFEX.IF2509"),
        Arc::new(tick),
    ))
    .unwrap();
    eng.put(Event::with_data(
        "eBar.CFFEX.IF2506",
        Arc::new("bar-ish".to_string()),
    ))
    .unwrap();
    // 排空哨兵:三帧错误型事件之后到齐 ⇒ 负面窗口已关
    let settle = common::drained(&eng, 1);
    assert!(wait_until(Duration::from_secs(2), || settle.len() == 1));
    eng.stop();

    assert_eq!(tick_cap.len(), 0, "tick 订阅未收任何错键流量");
    assert_eq!(log_cap.len(), 0, "log 订阅不收 tick 载荷");
}

/// F12-3 无效 civil 在构造侧被拒(不会把非法 DtNs 带上流)。
#[test]
fn f12_3_invalid_civil_rejected_before_bus() {
    let bad = Civil {
        year: 2023,
        month: 2,
        day: 29,
        hour: 0,
        minute: 0,
        second: 0,
        nanos: 0,
    };
    assert!(try_civil_to_nanos(bad).is_none(), "平年 2 月 29 日被拒");
    let good = Civil {
        year: 2024,
        month: 2,
        day: 29,
        hour: 0,
        minute: 0,
        second: 0,
        nanos: 0,
    };
    assert!(try_civil_to_nanos(good).is_some(), "闰年 2 月 29 日合法");
}