Files
fidc-backtest-engine/crates/fidc-core/tests/core_rules.rs
2026-04-06 23:56:37 -07:00

104 lines
2.7 KiB
Rust

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,
}
}
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"));
}