use chrono::NaiveDate; use fidc_core::cost::CostModel; use fidc_core::rules::EquityRuleHooks; use fidc_core::{ CandidateEligibility, ChinaAShareCostModel, ChinaEquityRuleHooks, DailyMarketSnapshot, OrderSide, Position, }; fn d(year: i32, month: u32, day: u32) -> NaiveDate { NaiveDate::from_ymd_opt(year, month, day).expect("valid date") } fn candidate() -> CandidateEligibility { CandidateEligibility { date: d(2024, 1, 3), symbol: "000001.SZ".to_string(), is_st: false, is_new_listing: false, is_paused: false, allow_buy: true, allow_sell: true, is_kcb: false, is_one_yuan: false, } } fn snapshot(open: f64, upper_limit: f64, lower_limit: f64) -> DailyMarketSnapshot { DailyMarketSnapshot { date: d(2024, 1, 3), symbol: "000001.SZ".to_string(), open, high: open, low: open, close: open, prev_close: 10.0, volume: 1_000_000, paused: false, upper_limit, lower_limit, } } #[test] fn china_cost_model_applies_minimum_commission_and_stamp_tax() { let model = ChinaAShareCostModel::default(); let buy = model.calculate(OrderSide::Buy, 1_000.0); assert!((buy.commission - 5.0).abs() < 1e-9); assert_eq!(buy.stamp_tax, 0.0); let sell = model.calculate(OrderSide::Sell, 100_000.0); assert!((sell.commission - 30.0).abs() < 1e-9); assert!((sell.stamp_tax - 100.0).abs() < 1e-9); } #[test] fn china_rule_hooks_block_same_day_sell_under_t_plus_one() { let hooks = ChinaEquityRuleHooks; let mut position = Position::new("000001.SZ"); let trade_date = d(2024, 1, 3); position.buy(trade_date, 1_000, 10.0); let check = hooks.can_sell( trade_date, &snapshot(10.1, 11.0, 9.0), &candidate(), &position, ); assert!(!check.allowed); assert!(check .reason .as_deref() .unwrap_or_default() .contains("t+1")); } #[test] fn china_rule_hooks_block_buy_at_limit_up_and_sell_at_limit_down() { let hooks = ChinaEquityRuleHooks; let candidate = candidate(); let mut position = Position::new("000001.SZ"); position.buy(d(2024, 1, 2), 1_000, 10.0); let buy_check = hooks.can_buy(d(2024, 1, 3), &snapshot(11.0, 11.0, 9.0), &candidate); assert!(!buy_check.allowed); assert!(buy_check .reason .as_deref() .unwrap_or_default() .contains("upper limit")); let sell_check = hooks.can_sell(d(2024, 1, 3), &snapshot(9.0, 11.0, 9.0), &candidate, &position); assert!(!sell_check.allowed); assert!(sell_check .reason .as_deref() .unwrap_or_default() .contains("lower limit")); }