use ek_data::constant::*;
use ek_data::time::*;
use ek_data::*;
fn tick(limit_up: f64, limit_down: f64) -> TickData {
TickData {
limit_up,
limit_down,
..TickData::default()
}
}
#[test]
fn tick_reach_limit_up() {
let mut t = tick(10.0, 8.0);
t.bid_price_1 = 10.0;
assert!(t.reach_limit_up(0.0));
assert!(t.reach_limit_up(0.5));
t.bid_price_1 = 9.4;
assert!(!t.reach_limit_up(0.0));
assert!(t.reach_limit_up(0.7));
}
#[test]
fn tick_reach_limit_down() {
let mut t = tick(100.0, 92.0);
t.ask_price_1 = 92.0;
assert!(t.reach_limit_down(0.0));
assert!(t.reach_limit_down(0.5));
t.ask_price_1 = 92.6;
assert!(!t.reach_limit_down(0.0));
assert!(t.reach_limit_down(0.7));
}
#[test]
fn tick_reach_limit_combined() {
let mut t = tick(10.0, 8.0);
t.bid_price_1 = 10.0;
assert!(t.reach_limit(0.0));
t.bid_price_1 = 0.0;
t.ask_price_1 = 8.0;
assert!(t.reach_limit(0.0));
t.ask_price_1 = 9.0;
assert!(!t.reach_limit(0.0));
}
fn order(status: Status) -> OrderData {
OrderData {
gateway_name: "xt".into(),
symbol: "IF2506".into(),
exchange: Exchange::CFFEX,
orderid: "1".into(),
status,
..OrderData::default()
}
}
#[test]
fn order_is_active_matrix() {
assert!(order(Status::SUBMITTING).is_active());
assert!(order(Status::NOTTRADED).is_active());
assert!(order(Status::PARTTRADED).is_active());
assert!(!order(Status::ALLTRADED).is_active());
assert!(!order(Status::CANCELLED).is_active());
assert!(!order(Status::REJECTED).is_active());
}
#[test]
fn order_create_cancel_request_copies_fields() {
let req = order(Status::NOTTRADED).create_cancel_request();
assert_eq!(req.orderid, "1");
assert_eq!(req.symbol, "IF2506");
assert_eq!(req.exchange, Exchange::CFFEX);
}
#[test]
fn account_available_is_balance_minus_frozen() {
let a = AccountData {
balance: 1000.0,
frozen: 300.0,
..AccountData::default()
};
assert_eq!(a.available(), 700.0);
let zero = AccountData::default();
assert_eq!(zero.available(), 0.0);
}
fn req_volume(v: f64) -> OrderRequest {
OrderRequest {
volume: v,
..OrderRequest::default()
}
}
#[test]
fn request_min_volume_check() {
assert!(!req_volume(0.99).is_min_volume_ok(1.0));
assert!(req_volume(1.0).is_min_volume_ok(1.0));
assert!(req_volume(5.0).is_min_volume_ok(1.0));
}
#[test]
fn request_max_volume_check() {
assert!(req_volume(1000.0).is_max_volume_ok(1000.0));
assert!(!req_volume(1000.1).is_max_volume_ok(1000.0));
assert!(!req_volume(1001.0).is_max_volume_ok(1000.0));
}
#[test]
fn request_volume_range_check() {
assert!(!req_volume(0.0).is_volume_range_ok(1.0, 1000.0));
assert!(req_volume(1.0).is_volume_range_ok(1.0, 1000.0));
assert!(req_volume(1000.0).is_volume_range_ok(1.0, 1000.0));
assert!(!req_volume(1000.5).is_volume_range_ok(1.0, 1000.0));
}
#[test]
fn request_volume_beishu_check() {
assert!(req_volume(10.0).is_volume_beishu_ok(5.0));
assert!(!req_volume(10.5).is_volume_beishu_ok(5.0));
assert!(req_volume(3.0).is_volume_beishu_ok(1.0));
assert!(req_volume(0.0).is_volume_beishu_ok(5.0));
assert!(!req_volume(10.0).is_volume_beishu_ok(0.0));
}
#[test]
fn time_civil_roundtrip_known_days() {
assert_eq!(days_from_civil(1970, 1, 1), 0);
assert_eq!(days_from_civil(1970, 1, 2), 1);
assert_eq!(days_from_civil(2000, 3, 1), 11017);
assert_eq!(civil_from_days(0), (1970, 1, 1));
assert_eq!(civil_from_days(11017), (2000, 3, 1));
let leap = days_from_civil(2024, 2, 29);
let (y, m, d) = civil_from_days(leap);
assert_eq!((y, m, d), (2024, 2, 29));
let next = days_from_civil(2024, 3, 1);
assert_eq!(next - leap, 1);
}
#[test]
fn time_nanos_roundtrip_and_negative() {
let c = Civil { year: 2024, month: 12, day: 25, hour: 9, minute: 30, second: 1, nanos: 123 };
let t = civil_to_nanos(c);
assert_eq!(civil_from_nanos(t), c);
assert_eq!(civil_to_nanos(Civil { year: 1970, month: 1, day: 1, hour: 0, minute: 0, second: 0, nanos: 0 }), 0);
let neg = -86_400 * NANOS_PER_SEC - 1;
let c_neg = civil_from_nanos(neg);
assert_eq!((c_neg.year, c_neg.month, c_neg.day), (1969, 12, 30));
assert_eq!(
(c_neg.hour, c_neg.minute, c_neg.second, c_neg.nanos),
(23, 59, 59, 999_999_999)
);
let last_of_m1 = civil_to_nanos(Civil { year: 1969, month: 12, day: 31, hour: 23, minute: 59, second: 59, nanos: 999_999_999 });
assert_eq!(trading_day(last_of_m1), -1);
}
#[test]
fn time_from_system_time_and_now() {
let epoch = std::time::UNIX_EPOCH;
assert_eq!(from_system_time(epoch), 0);
assert!(now_nanos() > 1_600_000_000_000_000_000); let _ = format_civil(civil_to_nanos(Civil { year: 2024, month: 1, day: 6, hour: 8, minute: 3, second: 9, nanos: 12 }));
}
#[test]
fn vt_symbol_roundtrip_and_errors() {
let vt = vt_symbol("IF2506", Exchange::CFFEX);
assert_eq!(vt, "CFFEX.IF2506");
let (sym, ex) = parse_vt_symbol(&vt).unwrap();
assert_eq!((sym.as_str(), ex), ("IF2506", Exchange::CFFEX));
let (sym2, ex2) = parse_vt_symbol("SZSE.000300.IDX").unwrap();
assert_eq!(sym2, "000300.IDX");
assert_eq!(ex2, Exchange::SZSE);
assert_eq!(parse_vt_symbol("NO_DOT"), Err(ek_data::SymbolError::Malformed));
assert_eq!(parse_vt_symbol(".NOHEAD"), Err(ek_data::SymbolError::Malformed));
assert_eq!(
parse_vt_symbol("FAKE.IF2506"),
Err(ek_data::SymbolError::UnknownExchange("FAKE".into()))
);
}
#[test]
fn contract_defaults_match_python() {
let c = ContractData::default();
assert_eq!(c.size, 1.0); assert_eq!(c.price_tick, 0.01); assert!(c.instrument_type.is_none());
assert!(c.product_id.is_none());
}
#[test]
fn log_data_default_level_is_info_20() {
let l = LogData { msg: "hello".into(), ..LogData::default() };
assert_eq!(l.level, 20); assert!(!l.is_warning_or_above());
assert!(LogData { level: 30, ..l.clone() }.is_warning_or_above());
assert_eq!(LEVEL_INFO, 20);
assert_eq!(LEVEL_WARNING, 30);
assert_eq!(LEVEL_ERROR, 40);
}