diff --git a/crates/fidc-core/src/rules.rs b/crates/fidc-core/src/rules.rs index 9e2707f..2e9730a 100644 --- a/crates/fidc-core/src/rules.rs +++ b/crates/fidc-core/src/rules.rs @@ -53,7 +53,11 @@ impl ChinaEquityRuleHooks { } fn at_lower_limit(snapshot: &DailyMarketSnapshot, price_field: PriceField) -> bool { - snapshot.is_at_lower_limit_price(snapshot.sell_price(price_field)) + let check_price = match price_field { + PriceField::Last => snapshot.price(PriceField::Last), + _ => snapshot.sell_price(price_field), + }; + snapshot.is_at_lower_limit_price(check_price) } } diff --git a/crates/fidc-core/src/strategy.rs b/crates/fidc-core/src/strategy.rs index 56ec39d..7a0084b 100644 --- a/crates/fidc-core/src/strategy.rs +++ b/crates/fidc-core/src/strategy.rs @@ -1050,10 +1050,11 @@ impl JqMicroCapStrategy { let Ok(candidate) = ctx.data.require_candidate(date, symbol) else { return false; }; + let lower_limit_check_price = market.price(PriceField::Last); !(market.paused || candidate.is_paused || !candidate.allow_sell - || market.is_at_lower_limit_price(market.sell_price(PriceField::Last))) + || market.is_at_lower_limit_price(lower_limit_check_price)) } fn buy_rejection_reason( diff --git a/crates/fidc-core/tests/core_rules.rs b/crates/fidc-core/tests/core_rules.rs index 7ff30d5..9e8aab5 100644 --- a/crates/fidc-core/tests/core_rules.rs +++ b/crates/fidc-core/tests/core_rules.rs @@ -148,3 +148,38 @@ fn china_rule_hooks_use_tick_size_tolerance_for_price_limits() { ); assert!(!sell_check.allowed); } + +#[test] +fn china_rule_hooks_allow_sell_when_last_price_is_above_lower_limit() { + let hooks = ChinaEquityRuleHooks; + let candidate = candidate(); + let mut position = Position::new("000001.SZ"); + position.buy(d(2024, 4, 3), 1_000, 2.89); + + let snapshot = DailyMarketSnapshot { + date: d(2024, 4, 7), + symbol: "000001.SZ".to_string(), + timestamp: Some("2024-04-07 10:18:00".to_string()), + day_open: 2.53, + open: 2.53, + high: 2.53, + low: 2.52, + close: 2.53, + last_price: 2.53, + bid1: 2.52, + ask1: 2.53, + prev_close: 2.80, + volume: 1_000_000, + tick_volume: 100_000, + bid1_volume: 50_000, + ask1_volume: 50_000, + trading_phase: Some("continuous".to_string()), + paused: false, + upper_limit: 3.08, + lower_limit: 2.52, + price_tick: 0.01, + }; + + let sell_check = hooks.can_sell(d(2024, 4, 7), &snapshot, &candidate, &position, PriceField::Last); + assert!(sell_check.allowed, "sell should be allowed when snapshot last is above lower limit"); +}