共享固定数值因子字段名

This commit is contained in:
boris
2026-08-25 20:25:05 +08:00
parent fac5078dbf
commit 01cffb947c
4 changed files with 73 additions and 73 deletions
+14 -12
View File
@@ -161,6 +161,8 @@ impl DailyMarketSnapshot {
} }
} }
pub type NumericFactorMap = BTreeMap<Cow<'static, str>, f64>;
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DailyFactorSnapshot { pub struct DailyFactorSnapshot {
#[serde(with = "date_format")] #[serde(with = "date_format")]
@@ -172,7 +174,7 @@ pub struct DailyFactorSnapshot {
pub turnover_ratio: Option<f64>, pub turnover_ratio: Option<f64>,
pub effective_turnover_ratio: Option<f64>, pub effective_turnover_ratio: Option<f64>,
#[serde(default)] #[serde(default)]
pub extra_factors: BTreeMap<String, f64>, pub extra_factors: NumericFactorMap,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -3264,11 +3266,15 @@ fn normalize_factor_snapshots(factors: Vec<DailyFactorSnapshot>) -> Vec<DailyFac
.extra_factors .extra_factors
.into_iter() .into_iter()
.filter_map(|(field, value)| { .filter_map(|(field, value)| {
let normalized = normalize_field(&field); let trimmed = field.as_ref().trim().trim_matches('"').trim_matches('\'');
if normalized.is_empty() || !value.is_finite() { if trimmed.is_empty() || !value.is_finite() {
None None
} else if trimmed == field.as_ref()
&& trimmed.bytes().all(|byte| !byte.is_ascii_uppercase())
{
Some((field, value))
} else { } else {
Some((normalized, value)) Some((Cow::Owned(trimmed.to_ascii_lowercase()), value))
} }
}) })
.collect(); .collect();
@@ -3962,10 +3968,9 @@ mod tests {
.map(|(index, date)| { .map(|(index, date)| {
let mut extra_factors = BTreeMap::new(); let mut extra_factors = BTreeMap::new();
if let Some(values) = availability { if let Some(values) = availability {
extra_factors extra_factors.insert("source_daily_volume_available".into(), values[index]);
.insert("source_daily_volume_available".to_string(), values[index]);
if values[index] >= 0.5 { if values[index] >= 0.5 {
extra_factors.insert("daily_volume".to_string(), volumes[index] as f64); extra_factors.insert("daily_volume".into(), volumes[index] as f64);
} }
} }
DailyFactorSnapshot { DailyFactorSnapshot {
@@ -4140,10 +4145,7 @@ mod tests {
pe_ttm: 10.0, pe_ttm: 10.0,
turnover_ratio: None, turnover_ratio: None,
effective_turnover_ratio: None, effective_turnover_ratio: None,
extra_factors: BTreeMap::from([( extra_factors: BTreeMap::from([("adjustment_factor_backward1".into(), factor)]),
"adjustment_factor_backward1".to_string(),
factor,
)]),
}) })
.collect(), .collect(),
Vec::new(), Vec::new(),
@@ -4243,7 +4245,7 @@ mod tests {
extra_factors: if *date == dates[3] { extra_factors: if *date == dates[3] {
BTreeMap::new() BTreeMap::new()
} else { } else {
BTreeMap::from([("adjustment_factor_backward1".to_string(), 1.0)]) BTreeMap::from([("adjustment_factor_backward1".into(), 1.0)])
}, },
}) })
.collect(), .collect(),
+2 -2
View File
@@ -31,8 +31,8 @@ pub use data::{
BenchmarkSnapshot, CandidateEligibility, CorporateAction, DailyFactorSnapshot, BenchmarkSnapshot, CandidateEligibility, CorporateAction, DailyFactorSnapshot,
DailyMarketSnapshot, DailySnapshotBundle, DataSet, DataSetError, DividendRecord, DailyMarketSnapshot, DailySnapshotBundle, DataSet, DataSetError, DividendRecord,
EligibleUniverseSnapshot, FactorTextValue, FactorValue, IntradayExecutionQuote, EligibleUniverseSnapshot, FactorTextValue, FactorValue, IntradayExecutionQuote,
IntradayOrderBookDepthLevel, PriceBar, PriceField, SecuritiesMarginRecord, SplitRecord, IntradayOrderBookDepthLevel, NumericFactorMap, PriceBar, PriceField, SecuritiesMarginRecord,
YieldCurvePoint, SplitRecord, YieldCurvePoint,
}; };
pub use engine::{ pub use engine::{
AnalyzerMonthlyReturnRow, AnalyzerPositionRow, AnalyzerReport, AnalyzerRiskSummary, AnalyzerMonthlyReturnRow, AnalyzerPositionRow, AnalyzerReport, AnalyzerRiskSummary,
+37 -39
View File
@@ -3669,7 +3669,7 @@ impl PlatformExprStrategy {
ctx.data ctx.data
.factor_snapshots_on(date) .factor_snapshots_on(date)
.into_iter() .into_iter()
.flat_map(|row| row.extra_factors.keys().cloned()) .flat_map(|row| row.extra_factors.keys().map(|key| key.to_string()))
.collect() .collect()
} else { } else {
BTreeSet::new() BTreeSet::new()
@@ -3993,7 +3993,11 @@ impl PlatformExprStrategy {
}; };
let extra_factors = if self.stock_extra_factors_required { let extra_factors = if self.stock_extra_factors_required {
factor.extra_factors.clone() factor
.extra_factors
.iter()
.map(|(field, value)| (field.to_string(), *value))
.collect()
} else { } else {
BTreeMap::new() BTreeMap::new()
}; };
@@ -13686,7 +13690,7 @@ mod tests {
pe_ttm: 8.0, pe_ttm: 8.0,
turnover_ratio: Some(1.0), turnover_ratio: Some(1.0),
effective_turnover_ratio: Some(1.0), effective_turnover_ratio: Some(1.0),
extra_factors: BTreeMap::from([("model_score".to_string(), 2.0)]), extra_factors: BTreeMap::from([("model_score".into(), 2.0)]),
}, },
], ],
symbols symbols
@@ -16173,10 +16177,7 @@ mod tests {
pe_ttm: 8.0, pe_ttm: 8.0,
turnover_ratio: Some(3.0), turnover_ratio: Some(3.0),
effective_turnover_ratio: Some(3.0), effective_turnover_ratio: Some(3.0),
extra_factors: BTreeMap::from([( extra_factors: BTreeMap::from([("adjustment_factor_backward1".into(), 1.0)]),
"adjustment_factor_backward1".to_string(),
1.0,
)]),
}) })
.collect(), .collect(),
vec![CandidateEligibility { vec![CandidateEligibility {
@@ -17125,7 +17126,7 @@ mod tests {
pe_ttm: 8.0, pe_ttm: 8.0,
turnover_ratio: Some(1.2), turnover_ratio: Some(1.2),
effective_turnover_ratio: Some(1.2), effective_turnover_ratio: Some(1.2),
extra_factors: BTreeMap::from([("touched_upper_limit".to_string(), 1.0)]), extra_factors: BTreeMap::from([("touched_upper_limit".into(), 1.0)]),
}, },
DailyFactorSnapshot { DailyFactorSnapshot {
date, date,
@@ -17239,7 +17240,7 @@ mod tests {
pe_ttm: 8.0, pe_ttm: 8.0,
turnover_ratio: Some(1.0), turnover_ratio: Some(1.0),
effective_turnover_ratio: Some(1.0), effective_turnover_ratio: Some(1.0),
extra_factors: BTreeMap::from([("touched_upper_limit".to_string(), 1.0)]), extra_factors: BTreeMap::from([("touched_upper_limit".into(), 1.0)]),
}], }],
vec![CandidateEligibility { vec![CandidateEligibility {
date, date,
@@ -17976,7 +17977,7 @@ mod tests {
pe_ttm: 8.0, pe_ttm: 8.0,
turnover_ratio: Some(1.0), turnover_ratio: Some(1.0),
effective_turnover_ratio: Some(1.0), effective_turnover_ratio: Some(1.0),
extra_factors: BTreeMap::from([("adjustment_factor_backward1".to_string(), 1.0)]), extra_factors: BTreeMap::from([("adjustment_factor_backward1".into(), 1.0)]),
}) })
.collect(); .collect();
let benchmark_rows: Vec<BenchmarkSnapshot> = market_rows let benchmark_rows: Vec<BenchmarkSnapshot> = market_rows
@@ -20825,7 +20826,7 @@ mod tests {
pe_ttm: 8.0, pe_ttm: 8.0,
turnover_ratio: Some(1.0), turnover_ratio: Some(1.0),
effective_turnover_ratio: Some(1.0), effective_turnover_ratio: Some(1.0),
extra_factors: BTreeMap::from([("amount".to_string(), 30_000_000.0)]), extra_factors: BTreeMap::from([("amount".into(), 30_000_000.0)]),
}, },
DailyFactorSnapshot { DailyFactorSnapshot {
date: decision_date, date: decision_date,
@@ -22054,17 +22055,17 @@ mod tests {
turnover_ratio: Some(22.0), turnover_ratio: Some(22.0),
effective_turnover_ratio: Some(18.0), effective_turnover_ratio: Some(18.0),
extra_factors: BTreeMap::from([ extra_factors: BTreeMap::from([
("custom_alpha".to_string(), 7.0), ("custom_alpha".into(), 7.0),
("margin_all".to_string(), 1.0), ("margin_all".into(), 1.0),
("yield_curve_1y".to_string(), 0.02), ("yield_curve_1y".into(), 0.02),
("total_shares".to_string(), 123.0), ("total_shares".into(), 123.0),
("stock_connect_north_bound".to_string(), 1.0), ("stock_connect_north_bound".into(), 1.0),
("industry_citics_l1".to_string(), 10.0), ("industry_citics_l1".into(), 10.0),
("fundamental_net_profit".to_string(), 99.0), ("fundamental_net_profit".into(), 99.0),
("financial_revenue".to_string(), 188.0), ("financial_revenue".into(), 188.0),
("pit_financial_eps".to_string(), 0.88), ("pit_financial_eps".into(), 0.88),
("current_performance_roe".to_string(), 12.0), ("current_performance_roe".into(), 12.0),
("amount".to_string(), 12_345_678.0), ("amount".into(), 12_345_678.0),
]), ]),
}], }],
vec![CandidateEligibility { vec![CandidateEligibility {
@@ -22835,8 +22836,8 @@ mod tests {
turnover_ratio: Some(1.0), turnover_ratio: Some(1.0),
effective_turnover_ratio: Some(1.0), effective_turnover_ratio: Some(1.0),
extra_factors: BTreeMap::from([ extra_factors: BTreeMap::from([
("Mixed_Factor".to_string(), index as f64 + 5.0), ("Mixed_Factor".into(), index as f64 + 5.0),
("adjustment_factor_backward1".to_string(), 1.0), ("adjustment_factor_backward1".into(), 1.0),
]), ]),
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@@ -22989,7 +22990,7 @@ mod tests {
pe_ttm: 8.0, pe_ttm: 8.0,
turnover_ratio: Some(1.0), turnover_ratio: Some(1.0),
effective_turnover_ratio: Some(1.0), effective_turnover_ratio: Some(1.0),
extra_factors: BTreeMap::from([("adjustment_factor_backward1".to_string(), 1.0)]), extra_factors: BTreeMap::from([("adjustment_factor_backward1".into(), 1.0)]),
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let candidate_rows = dates let candidate_rows = dates
@@ -25352,7 +25353,7 @@ mod tests {
pe_ttm: 8.0, pe_ttm: 8.0,
turnover_ratio: Some(1.0), turnover_ratio: Some(1.0),
effective_turnover_ratio: Some(1.0), effective_turnover_ratio: Some(1.0),
extra_factors: BTreeMap::from([("adjustment_factor_backward1".to_string(), 1.0)]), extra_factors: BTreeMap::from([("adjustment_factor_backward1".into(), 1.0)]),
}) })
.chain(symbols.iter().map(|symbol| DailyFactorSnapshot { .chain(symbols.iter().map(|symbol| DailyFactorSnapshot {
date, date,
@@ -25368,7 +25369,7 @@ mod tests {
pe_ttm: 8.0, pe_ttm: 8.0,
turnover_ratio: Some(1.0), turnover_ratio: Some(1.0),
effective_turnover_ratio: Some(1.0), effective_turnover_ratio: Some(1.0),
extra_factors: BTreeMap::from([("adjustment_factor_backward1".to_string(), 1.0)]), extra_factors: BTreeMap::from([("adjustment_factor_backward1".into(), 1.0)]),
})) }))
.collect(), .collect(),
symbols symbols
@@ -26020,7 +26021,7 @@ mod tests {
pe_ttm: 8.0, pe_ttm: 8.0,
turnover_ratio: Some(1.0), turnover_ratio: Some(1.0),
effective_turnover_ratio: Some(1.0), effective_turnover_ratio: Some(1.0),
extra_factors: BTreeMap::from([("adjustment_factor_backward1".to_string(), 1.0)]), extra_factors: BTreeMap::from([("adjustment_factor_backward1".into(), 1.0)]),
}; };
let data = DataSet::from_components( let data = DataSet::from_components(
vec![Instrument { vec![Instrument {
@@ -29884,7 +29885,7 @@ mod tests {
let date = dates[5]; let date = dates[5];
let symbol = "300001.SZ"; let symbol = "300001.SZ";
let mut extra_factors = BTreeMap::new(); let mut extra_factors = BTreeMap::new();
extra_factors.insert("adjustment_factor_backward1".to_string(), 1.0); extra_factors.insert("adjustment_factor_backward1".into(), 1.0);
let data = DataSet::from_components( let data = DataSet::from_components(
vec![Instrument { vec![Instrument {
symbol: symbol.to_string(), symbol: symbol.to_string(),
@@ -29934,7 +29935,7 @@ mod tests {
extra_factors: if factor_date == date { extra_factors: if factor_date == date {
extra_factors.clone() extra_factors.clone()
} else { } else {
BTreeMap::from([("adjustment_factor_backward1".to_string(), 1.0)]) BTreeMap::from([("adjustment_factor_backward1".into(), 1.0)])
}, },
}) })
.collect(), .collect(),
@@ -30088,9 +30089,9 @@ mod tests {
turnover_ratio: None, turnover_ratio: None,
effective_turnover_ratio: None, effective_turnover_ratio: None,
extra_factors: BTreeMap::from([ extra_factors: BTreeMap::from([
("adjustment_factor_backward1".to_string(), 1.0), ("adjustment_factor_backward1".into(), 1.0),
("ma5_current_back_adjusted_close".to_string(), current_close), ("ma5_current_back_adjusted_close".into(), current_close),
("avg_volume5_current".to_string(), current_volume), ("avg_volume5_current".into(), current_volume),
]), ]),
}, },
) )
@@ -30239,7 +30240,7 @@ mod tests {
pe_ttm: 0.0, pe_ttm: 0.0,
turnover_ratio: None, turnover_ratio: None,
effective_turnover_ratio: None, effective_turnover_ratio: None,
extra_factors: BTreeMap::from([("ma5_prev_close".to_string(), 10.0)]), extra_factors: BTreeMap::from([("ma5_prev_close".into(), 10.0)]),
}], }],
vec![CandidateEligibility { vec![CandidateEligibility {
date: current, date: current,
@@ -32539,10 +32540,7 @@ let target_exposure = csi_ready ? dynamic_exposure : 0.0;
pe_ttm: 8.0, pe_ttm: 8.0,
turnover_ratio: Some(22.0), turnover_ratio: Some(22.0),
effective_turnover_ratio: Some(18.0), effective_turnover_ratio: Some(18.0),
extra_factors: BTreeMap::from([( extra_factors: BTreeMap::from([("adjustment_factor_backward1".into(), 1.0)]),
"adjustment_factor_backward1".to_string(),
1.0,
)]),
}) })
.collect(), .collect(),
dates dates
@@ -32755,7 +32753,7 @@ let target_exposure = csi_ready ? dynamic_exposure : 0.0;
extra_factors: if *symbol == "000002.SZ" { extra_factors: if *symbol == "000002.SZ" {
BTreeMap::new() BTreeMap::new()
} else { } else {
BTreeMap::from([("model_score".to_string(), 4.0 - index as f64)]) BTreeMap::from([("model_score".into(), 4.0 - index as f64)])
}, },
}) })
.collect(), .collect(),
+20 -20
View File
@@ -10,11 +10,11 @@ use fidc_core::{
ChinaEquityRuleHooks, DailyFactorSnapshot, DailyMarketSnapshot, DataSet, ExecutionQuoteRequest, ChinaEquityRuleHooks, DailyFactorSnapshot, DailyMarketSnapshot, DataSet, ExecutionQuoteRequest,
FuturesAccountState, FuturesCommissionType, FuturesContractSpec, FuturesDirection, FuturesAccountState, FuturesCommissionType, FuturesContractSpec, FuturesDirection,
FuturesOrderIntent, FuturesPositionEffect, FuturesTradingParameter, FuturesValidationConfig, FuturesOrderIntent, FuturesPositionEffect, FuturesTradingParameter, FuturesValidationConfig,
Instrument, IntradayExecutionQuote, IntradayOrderBookDepthLevel, MatchingType, OpenOrderView, Instrument, IntradayExecutionQuote, IntradayOrderBookDepthLevel, MatchingType,
OrderIntent, OrderSide, OrderStatus, PlatformExprStrategy, PlatformExprStrategyConfig, NumericFactorMap, OpenOrderView, OrderIntent, OrderSide, OrderStatus, PlatformExprStrategy,
PlatformTradeAction, PortfolioState, PriceField, ProcessEvent, ProcessEventBus, PlatformExprStrategyConfig, PlatformTradeAction, PortfolioState, PriceField, ProcessEvent,
ProcessEventKind, ScheduleRule, ScheduleStage, ScheduleTimeRule, Strategy, StrategyContext, ProcessEventBus, ProcessEventKind, ScheduleRule, ScheduleStage, ScheduleTimeRule, Strategy,
StrategyDecision, StrategyContext, StrategyDecision,
}; };
fn d(year: i32, month: u32, day: u32) -> NaiveDate { fn d(year: i32, month: u32, day: u32) -> NaiveDate {
@@ -133,7 +133,7 @@ fn market_row(date: NaiveDate, symbol: &str, open: f64, close: f64) -> DailyMark
fn factor_row( fn factor_row(
date: NaiveDate, date: NaiveDate,
symbol: &str, symbol: &str,
extra_factors: BTreeMap<String, f64>, extra_factors: NumericFactorMap,
) -> DailyFactorSnapshot { ) -> DailyFactorSnapshot {
DailyFactorSnapshot { DailyFactorSnapshot {
date, date,
@@ -209,26 +209,26 @@ fn two_day_futures_data() -> DataSet {
d1, d1,
"000001.SZ", "000001.SZ",
BTreeMap::from([ BTreeMap::from([
("custom_alpha".to_string(), 7.0), ("custom_alpha".into(), 7.0),
("margin_all".to_string(), 1.0), ("margin_all".into(), 1.0),
("yield_curve_1y".to_string(), 0.02), ("yield_curve_1y".into(), 0.02),
("total_shares".to_string(), 123.0), ("total_shares".into(), 123.0),
("stock_connect_north_bound".to_string(), 1.0), ("stock_connect_north_bound".into(), 1.0),
("industry_citics_l1".to_string(), 10.0), ("industry_citics_l1".into(), 10.0),
("fundamental_net_profit".to_string(), 99.0), ("fundamental_net_profit".into(), 99.0),
]), ]),
), ),
factor_row( factor_row(
d2, d2,
"000001.SZ", "000001.SZ",
BTreeMap::from([ BTreeMap::from([
("custom_alpha".to_string(), 8.0), ("custom_alpha".into(), 8.0),
("margin_all".to_string(), 1.0), ("margin_all".into(), 1.0),
("yield_curve_1y".to_string(), 0.021), ("yield_curve_1y".into(), 0.021),
("total_shares".to_string(), 124.0), ("total_shares".into(), 124.0),
("stock_connect_north_bound".to_string(), 1.0), ("stock_connect_north_bound".into(), 1.0),
("industry_citics_l1".to_string(), 10.0), ("industry_citics_l1".into(), 10.0),
("fundamental_net_profit".to_string(), 101.0), ("fundamental_net_profit".into(), 101.0),
]), ]),
), ),
], ],