From 14810708f030983368a45f5f9010d016338b55ae Mon Sep 17 00:00:00 2001 From: boris Date: Sat, 4 Jul 2026 14:12:10 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=80=80=E5=B8=82=E7=94=9F?= =?UTF-8?q?=E6=95=88=E6=97=A5=E7=BB=93=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/fidc-core/src/engine.rs | 2 +- crates/fidc-core/src/instrument.rs | 5 +++ crates/fidc-core/tests/delisting.rs | 54 +++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/crates/fidc-core/src/engine.rs b/crates/fidc-core/src/engine.rs index 76c40a5..d98f306 100644 --- a/crates/fidc-core/src/engine.rs +++ b/crates/fidc-core/src/engine.rs @@ -3319,7 +3319,7 @@ where let Some(instrument) = self.data.instrument(&symbol) else { continue; }; - let should_settle = instrument.is_delisted_before(date) + let should_settle = instrument.is_delisted_on_or_before(date) || (instrument.status.eq_ignore_ascii_case("delisted") && instrument.delisted_at.is_none() && self.data.market(date, &symbol).is_none()); diff --git a/crates/fidc-core/src/instrument.rs b/crates/fidc-core/src/instrument.rs index 700937f..2935c60 100644 --- a/crates/fidc-core/src/instrument.rs +++ b/crates/fidc-core/src/instrument.rs @@ -40,6 +40,11 @@ impl Instrument { .is_some_and(|delisted_at| delisted_at < date) } + pub fn is_delisted_on_or_before(&self, date: NaiveDate) -> bool { + self.delisted_at + .is_some_and(|delisted_at| delisted_at <= date) + } + pub fn is_active_on(&self, date: NaiveDate) -> bool { self.listed_at.is_none_or(|listed_at| listed_at <= date) && !self.is_delisted_before(date) diff --git a/crates/fidc-core/tests/delisting.rs b/crates/fidc-core/tests/delisting.rs index e3cf953..ad221d0 100644 --- a/crates/fidc-core/tests/delisting.rs +++ b/crates/fidc-core/tests/delisting.rs @@ -114,6 +114,29 @@ fn engine_settles_delisted_position_before_missing_market_snapshot_breaks_run() lower_limit: 4.5, price_tick: 0.01, }, + DailyMarketSnapshot { + date: delist_date, + symbol: "000002.SZ".to_string(), + timestamp: Some("2025-01-03 10:18:00".to_string()), + day_open: 5.05, + open: 5.05, + high: 5.15, + low: 5.0, + close: 5.05, + last_price: 5.05, + bid1: 5.04, + ask1: 5.06, + prev_close: 5.0, + volume: 110_000, + minute_volume: 110_000, + bid1_volume: 110_000, + ask1_volume: 110_000, + trading_phase: Some("continuous".to_string()), + paused: false, + upper_limit: 5.5, + lower_limit: 4.5, + price_tick: 0.01, + }, DailyMarketSnapshot { date: date2, symbol: "000002.SZ".to_string(), @@ -159,6 +182,16 @@ fn engine_settles_delisted_position_before_missing_market_snapshot_breaks_run() effective_turnover_ratio: Some(1.0), extra_factors: BTreeMap::new(), }, + DailyFactorSnapshot { + date: delist_date, + symbol: "000002.SZ".to_string(), + market_cap_bn: 30.5, + free_float_cap_bn: 28.5, + pe_ttm: 10.0, + turnover_ratio: Some(1.0), + effective_turnover_ratio: Some(1.0), + extra_factors: BTreeMap::new(), + }, DailyFactorSnapshot { date: date2, symbol: "000002.SZ".to_string(), @@ -197,6 +230,19 @@ fn engine_settles_delisted_position_before_missing_market_snapshot_breaks_run() is_one_yuan: false, risk_level_code: None, }, + CandidateEligibility { + date: delist_date, + symbol: "000002.SZ".to_string(), + is_st: false, + is_star_st: false, + is_new_listing: false, + is_paused: false, + allow_buy: true, + allow_sell: true, + is_kcb: false, + is_one_yuan: false, + risk_level_code: None, + }, CandidateEligibility { date: date2, symbol: "000002.SZ".to_string(), @@ -220,6 +266,14 @@ fn engine_settles_delisted_position_before_missing_market_snapshot_breaks_run() prev_close: 99.0, volume: 1_000_000, }, + BenchmarkSnapshot { + date: delist_date, + benchmark: "000300.SH".to_string(), + open: 100.5, + close: 100.5, + prev_close: 100.0, + volume: 1_050_000, + }, BenchmarkSnapshot { date: date2, benchmark: "000300.SH".to_string(),