diff --git a/crates/fidc-core/src/data.rs b/crates/fidc-core/src/data.rs index 42dacf4..493b507 100644 --- a/crates/fidc-core/src/data.rs +++ b/crates/fidc-core/src/data.rs @@ -448,49 +448,12 @@ pub struct EligibleUniverseSnapshot { pub free_float_cap_bn: f64, } -pub fn decision_adjusted_cap_bn( - factor_date: NaiveDate, - raw_cap_bn: f64, - market: &DailyMarketSnapshot, -) -> f64 { - if !raw_cap_bn.is_finite() || raw_cap_bn <= 0.0 { - return f64::NAN; - } - if factor_date != market.date { - return raw_cap_bn; - } - if !market.close.is_finite() - || market.close <= 0.0 - || !market.prev_close.is_finite() - || market.prev_close <= 0.0 - { - return f64::NAN; - } - raw_cap_bn * market.prev_close / market.close +pub fn decision_market_cap_bn(factor: &DailyFactorSnapshot) -> f64 { + factor.market_cap_bn } -fn factor_market_cap_is_decision_adjusted(factor: &DailyFactorSnapshot) -> bool { - factor - .extra_factors - .get("__market_cap_decision_adjusted") - .is_some_and(|value| value.is_finite() && *value > 0.0) -} - -pub fn decision_market_cap_bn(factor: &DailyFactorSnapshot, market: &DailyMarketSnapshot) -> f64 { - if factor_market_cap_is_decision_adjusted(factor) { - return factor.market_cap_bn; - } - decision_adjusted_cap_bn(factor.date, factor.market_cap_bn, market) -} - -pub fn decision_free_float_cap_bn( - factor: &DailyFactorSnapshot, - market: &DailyMarketSnapshot, -) -> f64 { - if factor_market_cap_is_decision_adjusted(factor) { - return factor.free_float_cap_bn; - } - decision_adjusted_cap_bn(factor.date, factor.free_float_cap_bn, market) +pub fn decision_free_float_cap_bn(factor: &DailyFactorSnapshot) -> f64 { + factor.free_float_cap_bn } #[derive(Debug, Clone)] @@ -3048,20 +3011,21 @@ fn build_fundamental_universe_for_date( return rows; }; for factor in factors { - let Some(market) = market_by_date + if market_by_date .get(&date) .and_then(|rows| find_arc_by_symbol(rows, &factor.symbol, |row| row.symbol.as_str())) - else { + .is_none() + { continue; - }; - let market_cap_bn = decision_market_cap_bn(factor, market); + } + let market_cap_bn = decision_market_cap_bn(factor); if market_cap_bn <= 0.0 || !market_cap_bn.is_finite() { continue; } rows.push(EligibleUniverseSnapshot { symbol: factor.symbol.clone(), market_cap_bn, - free_float_cap_bn: decision_free_float_cap_bn(factor, market), + free_float_cap_bn: decision_free_float_cap_bn(factor), }); } rows.sort_by(|left, right| { @@ -3136,11 +3100,11 @@ fn build_eligible_universe_for_date_from_factors( { continue; } - let market_cap_bn = decision_market_cap_bn(factor, market); + let market_cap_bn = decision_market_cap_bn(factor); if market_cap_bn <= 0.0 || !market_cap_bn.is_finite() { continue; } - let free_float_cap_bn = decision_free_float_cap_bn(factor, market); + let free_float_cap_bn = decision_free_float_cap_bn(factor); rows.push(EligibleUniverseSnapshot { symbol: factor.symbol.clone(), market_cap_bn, @@ -3444,11 +3408,11 @@ mod tests { let rows = data.eligible_universe_on(date); assert_eq!(rows.len(), 2); - assert_eq!(rows[0].symbol, "000001.SZ"); - assert!((rows[0].market_cap_bn - 6.0).abs() < 1e-9); - assert!((rows[0].free_float_cap_bn - 2.0).abs() < 1e-9); - assert_eq!(rows[1].symbol, "000002.SZ"); - assert!((rows[1].market_cap_bn - 10.0).abs() < 1e-9); + assert_eq!(rows[0].symbol, "000002.SZ"); + assert!((rows[0].market_cap_bn - 10.0).abs() < 1e-9); + assert_eq!(rows[1].symbol, "000001.SZ"); + assert!((rows[1].market_cap_bn - 12.0).abs() < 1e-9); + assert!((rows[1].free_float_cap_bn - 4.0).abs() < 1e-9); } #[test] @@ -3621,33 +3585,8 @@ mod tests { } #[test] - fn decision_market_cap_keeps_pre_adjusted_factor() { + fn decision_market_cap_uses_factor_date_snapshot_without_price_reconstruction() { let date = NaiveDate::parse_from_str("2025-01-06", "%Y-%m-%d").unwrap(); - let market = DailyMarketSnapshot { - date, - symbol: "000001.SZ".to_string(), - timestamp: Some("2025-01-06 10:18:00".to_string()), - day_open: 10.0, - open: 10.0, - high: 20.0, - low: 10.0, - close: 20.0, - last_price: 10.0, - bid1: 10.0, - ask1: 10.0, - prev_close: 10.0, - volume: 100_000, - minute_volume: 1_000, - bid1_volume: 1_000, - ask1_volume: 1_000, - trading_phase: Some("continuous".to_string()), - paused: false, - upper_limit: 11.0, - lower_limit: 9.0, - price_tick: 0.01, - }; - let mut extra_factors = BTreeMap::new(); - extra_factors.insert("__market_cap_decision_adjusted".to_string(), 1.0); let factor = DailyFactorSnapshot { date, symbol: "000001.SZ".to_string(), @@ -3656,11 +3595,11 @@ mod tests { pe_ttm: 10.0, turnover_ratio: Some(1.0), effective_turnover_ratio: Some(1.0), - extra_factors, + extra_factors: BTreeMap::new(), }; - assert!((decision_market_cap_bn(&factor, &market) - 12.0).abs() < 1e-9); - assert!((decision_free_float_cap_bn(&factor, &market) - 4.0).abs() < 1e-9); + assert!((decision_market_cap_bn(&factor) - 12.0).abs() < 1e-9); + assert!((decision_free_float_cap_bn(&factor) - 4.0).abs() < 1e-9); } #[test] diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index b034754..1fad2c3 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -3123,8 +3123,8 @@ impl PlatformExprStrategy { } else { factor.extra_factors.get("amount").copied().unwrap_or(0.0) }; - let market_cap_bn = decision_market_cap_bn(factor, market); - let free_float_cap_bn = decision_free_float_cap_bn(factor, market); + let market_cap_bn = decision_market_cap_bn(factor); + let free_float_cap_bn = decision_free_float_cap_bn(factor); let market_cap = Self::market_cap_storage_to_strategy_unit(market_cap_bn); let free_float_cap = Self::market_cap_storage_to_strategy_unit(free_float_cap_bn); let expression_high = if intraday_same_day_factor { @@ -6888,11 +6888,11 @@ impl PlatformExprStrategy { if !self.stock_passes_universe_exclude(candidate, market) { continue; } - let market_cap_bn = decision_market_cap_bn(factor, market); + let market_cap_bn = decision_market_cap_bn(factor); if market_cap_bn <= 0.0 || !market_cap_bn.is_finite() { continue; } - let free_float_cap_bn = decision_free_float_cap_bn(factor, market); + let free_float_cap_bn = decision_free_float_cap_bn(factor); rows.push(EligibleUniverseSnapshot { symbol: factor.symbol.clone(), market_cap_bn, @@ -14683,16 +14683,16 @@ mod tests { let universe = strategy.selectable_universe_on(&ctx, date, date); assert_eq!(universe.len(), 1); - assert!((universe[0].market_cap_bn - 9.0).abs() < 1e-9); - assert!((universe[0].free_float_cap_bn - 4.5).abs() < 1e-9); + assert!((universe[0].market_cap_bn - 18.0).abs() < 1e-9); + assert!((universe[0].free_float_cap_bn - 9.0).abs() < 1e-9); let stock = strategy .stock_state_with_factor_date(&ctx, date, date, symbol) .expect("stock state"); - assert!((stock.market_cap - 9.0).abs() < 1e-9); - assert!((stock.market_cap_bn - 9.0).abs() < 1e-9); - assert!((stock.free_float_cap - 4.5).abs() < 1e-9); - assert!((stock.free_float_cap_bn - 4.5).abs() < 1e-9); + assert!((stock.market_cap - 18.0).abs() < 1e-9); + assert!((stock.market_cap_bn - 18.0).abs() < 1e-9); + assert!((stock.free_float_cap - 9.0).abs() < 1e-9); + assert!((stock.free_float_cap_bn - 9.0).abs() < 1e-9); assert_eq!(stock.close, 10.0); assert!(stock.high.is_nan()); assert!(stock.low.is_nan()); @@ -14702,7 +14702,7 @@ mod tests { let (selected, _, _) = strategy .select_symbols(&ctx, date, date, date, &day, 10.0, 20.0, 1) .expect("selection"); - assert!(selected.is_empty()); + assert_eq!(selected, vec![symbol.to_string()]); } #[test] @@ -25484,8 +25484,9 @@ mod tests { .order_intents .iter() .filter_map(|intent| match intent { - OrderIntent::Value { value, reason, .. } - if reason == "periodic_rebalance_buy" => Some(*value), + OrderIntent::Value { value, reason, .. } if reason == "periodic_rebalance_buy" => { + Some(*value) + } _ => None, }) .collect::>();