diff --git a/crates/fidc-core/src/data.rs b/crates/fidc-core/src/data.rs index bc72f70..3618cbf 100644 --- a/crates/fidc-core/src/data.rs +++ b/crates/fidc-core/src/data.rs @@ -2888,6 +2888,11 @@ fn factor_numeric_value(snapshot: &DailyFactorSnapshot, field: &str) -> Option { Some(snapshot.free_float_cap_bn) } + "free_float_cap_or_market_cap" => Some( + (snapshot.free_float_cap_bn.is_finite() && snapshot.free_float_cap_bn > 0.0) + .then_some(snapshot.free_float_cap_bn) + .unwrap_or(snapshot.market_cap_bn), + ), "pe_ttm" => Some(snapshot.pe_ttm), "turnover_ratio" => snapshot.turnover_ratio, "effective_turnover_ratio" => snapshot.effective_turnover_ratio, diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index 16342d6..518674d 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -3837,12 +3837,10 @@ impl PlatformExprStrategy { allow_sell: candidate.allow_sell, touched_upper_limit, touched_lower_limit, - listed_days: factor - .extra_factors - .get("listed_days") - .copied() - .filter(|value| value.is_finite() && *value >= 0.0) - .map(|value| value.floor() as i64) + listed_days: instrument + .and_then(|item| item.listed_at) + .map(|listed_at| factor_date.signed_duration_since(listed_at).num_days()) + .filter(|days| *days >= 0) .unwrap_or(-1), stock_ma_short, stock_ma_mid, @@ -4242,6 +4240,11 @@ impl PlatformExprStrategy { scope.push_required("day_factors", day_factors); } if let Some(stock) = stock { + let free_float_cap_or_market_cap = if stock.free_float_cap > 0.0 { + stock.free_float_cap + } else { + stock.market_cap + }; let at_upper_limit = Self::price_is_at_or_above_upper_limit( stock.last, stock.upper_limit, @@ -4258,6 +4261,7 @@ impl PlatformExprStrategy { scope.push("free_float_cap", stock.free_float_cap); scope.push("free_float_cap_bn", stock.free_float_cap_bn); scope.push("free_float_market_cap", stock.free_float_cap); + scope.push("free_float_cap_or_market_cap", free_float_cap_or_market_cap); scope.push("pe_ttm", stock.pe_ttm); scope.push("volume", stock.volume); scope.push("minute_volume", stock.minute_volume); @@ -4362,6 +4366,10 @@ impl PlatformExprStrategy { "free_float_market_cap".into(), Dynamic::from(stock.free_float_cap), ); + factors.insert( + "free_float_cap_or_market_cap".into(), + Dynamic::from(free_float_cap_or_market_cap), + ); factors.insert("pe_ttm".into(), Dynamic::from(stock.pe_ttm)); factors.insert("volume".into(), Dynamic::from(stock.volume)); factors.insert("minute_volume".into(), Dynamic::from(stock.minute_volume)); @@ -7941,6 +7949,11 @@ impl PlatformExprStrategy { "market_cap_bn" => Some(stock.market_cap_bn), "free_float_cap" | "free_float_market_cap" => Some(stock.free_float_cap), "free_float_cap_bn" => Some(stock.free_float_cap_bn), + "free_float_cap_or_market_cap" => Some(if stock.free_float_cap > 0.0 { + stock.free_float_cap + } else { + stock.market_cap + }), "pe_ttm" => Some(stock.pe_ttm), "volume" => Some(stock.volume), "minute_volume" | "intraday_volume" => Some(stock.minute_volume as f64), @@ -8584,6 +8597,7 @@ impl PlatformExprStrategy { | "free_float_cap" | "free_float_market_cap" | "free_float_cap_bn" + | "free_float_cap_or_market_cap" | "pe_ttm" | "volume" | "minute_volume" @@ -16187,7 +16201,7 @@ mod tests { name: symbol.to_string(), board: "SZ".to_string(), round_lot: 100, - listed_at: Some(d(2020, 1, 1)), + listed_at: Some(date - chrono::Duration::days(263)), delisted_at: None, status: "active".to_string(), }], @@ -16222,10 +16236,7 @@ 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), - ("touched_upper_limit".to_string(), 1.0), - ]), + extra_factors: BTreeMap::from([("touched_upper_limit".to_string(), 1.0)]), }], vec![CandidateEligibility { date,