diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index 70e08df..2ed498a 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -3543,14 +3543,44 @@ impl PlatformExprStrategy { let stock_volume_ma20 = rolling("volume", 20); let stock_volume_ma60 = rolling("volume", 60); let stock_volume_ma100 = rolling("volume", 100); - let touched_upper_limit = !market.paused - && (market.is_at_upper_limit_price(market.close) - || market.is_at_upper_limit_price(market.open) - || market.is_at_upper_limit_price(market.day_open)); - let touched_lower_limit = !market.paused - && (market.is_at_lower_limit_price(market.close) - || market.is_at_lower_limit_price(market.open) - || market.is_at_lower_limit_price(market.day_open)); + let touched_upper_limit = if intraday_same_day_factor { + !market.paused + && (market.is_at_upper_limit_price(market.close) + || market.is_at_upper_limit_price(market.open) + || market.is_at_upper_limit_price(market.day_open)) + } else if let Some(value) = factor + .extra_factors + .get("touched_upper_limit") + .copied() + .filter(|value| value.is_finite()) + { + value != 0.0 + } else { + !feature_market.paused + && (feature_market.is_at_upper_limit_price(feature_market.high) + || feature_market.is_at_upper_limit_price(feature_market.close) + || feature_market.is_at_upper_limit_price(feature_market.open) + || feature_market.is_at_upper_limit_price(feature_market.day_open)) + }; + let touched_lower_limit = if intraday_same_day_factor { + !market.paused + && (market.is_at_lower_limit_price(market.close) + || market.is_at_lower_limit_price(market.open) + || market.is_at_lower_limit_price(market.day_open)) + } else if let Some(value) = factor + .extra_factors + .get("touched_lower_limit") + .copied() + .filter(|value| value.is_finite()) + { + value != 0.0 + } else { + !feature_market.paused + && (feature_market.is_at_lower_limit_price(feature_market.low) + || feature_market.is_at_lower_limit_price(feature_market.close) + || feature_market.is_at_lower_limit_price(feature_market.open) + || feature_market.is_at_lower_limit_price(feature_market.day_open)) + }; let amount = if intraday_same_day_factor { f64::NAN } else { @@ -15626,7 +15656,7 @@ mod tests { } #[test] - fn platform_stock_state_uses_current_day_limit_status_for_buy_scale() { + fn platform_stock_state_uses_completed_day_high_and_low_for_limit_touch() { let date = d(2025, 3, 14); let symbol = "603813.SH"; let data = DataSet::from_components( @@ -15646,8 +15676,8 @@ mod tests { day_open: 14.82, open: 14.82, high: 15.98, - low: 14.80, - close: 15.98, + low: 13.08, + close: 15.00, last_price: 15.05, bid1: 15.04, ask1: 15.05, @@ -15720,6 +15750,7 @@ mod tests { .expect("stock state"); assert!(stock.touched_upper_limit); + assert!(stock.touched_lower_limit); } #[test] @@ -15860,7 +15891,7 @@ mod tests { assert_eq!(stock.last, 19.06); assert_eq!(stock.market_cap, 8.0); assert_eq!(stock.market_cap_bn, 8.0); - assert!(!stock.touched_upper_limit); + assert!(stock.touched_upper_limit); } #[test] @@ -15883,7 +15914,7 @@ mod tests { timestamp: Some("2025-04-08 10:18:00".to_string()), day_open: 10.0, open: 10.0, - high: 20.0, + high: 22.0, low: 10.0, close: 20.0, last_price: 12.0, @@ -15908,7 +15939,10 @@ mod tests { pe_ttm: 8.0, turnover_ratio: Some(1.0), effective_turnover_ratio: Some(1.0), - extra_factors: BTreeMap::from([("listed_days".to_string(), 263.0)]), + extra_factors: BTreeMap::from([ + ("listed_days".to_string(), 263.0), + ("touched_upper_limit".to_string(), 1.0), + ]), }], vec![CandidateEligibility { date, @@ -15976,6 +16010,7 @@ mod tests { assert!(stock.high.is_nan()); assert!(stock.low.is_nan()); assert!(stock.volume.is_nan()); + assert!(!stock.touched_upper_limit); let day = strategy.day_state(&ctx, date).expect("day state"); let (selected, _, _) = strategy