修正模型排名缺失诊断

This commit is contained in:
boris
2026-07-22 09:13:42 +08:00
parent 4132793219
commit ca9732ecb2
+74 -3
View File
@@ -8040,6 +8040,38 @@ impl PlatformExprStrategy {
}
let rank_value = self.rank_value(ctx, day, &candidate, &stock)?;
if !rank_value.is_finite() {
// Model-score artifacts intentionally contain only the PIT-eligible
// ranked universe. Do not report a missing score for a symbol that
// the same signal-day business filter would reject before ranking.
if !ctx.is_lagged_execution()
&& let Some(reason) = self.stock_selection_limit_rejection_reason(&stock)
{
if diagnostics.len() < 12 {
diagnostics.push(format!("{} rejected by {}", candidate.symbol, reason));
}
continue;
}
if !self.stock_passes_expr(ctx, day, &stock)? {
if diagnostics.len() < 12 {
diagnostics.push(format!("{} rejected by stock_expr", candidate.symbol));
}
continue;
}
if self.config.stop_take_reference_price_mode
== PlatformStopTakeReferencePriceMode::SignalDayPostAdjustedClose
&& ctx
.data
.market_latest_back_adjusted_close(date, &candidate.symbol)
.is_none()
{
if diagnostics.len() < 12 {
diagnostics.push(format!(
"{} rejected by missing signal-day post-adjusted close",
candidate.symbol
));
}
continue;
}
missing_rank_count += 1;
if missing_rank_examples.len() < 5 {
missing_rank_examples.push(candidate.symbol.clone());
@@ -8754,6 +8786,18 @@ impl PlatformExprStrategy {
}
let rank_value = self.rank_value(ctx, day, &candidate, &stock)?;
if !rank_value.is_finite() {
// The score universe is already filtered by the signal-day
// candidate contract. Missing rank is actionable only when the
// daily portion of that same contract still admits the symbol.
if !self.stock_passes_quote_plan_filter(ctx, day, &stock, quote_usage)? {
if diagnostics.len() < 12 {
diagnostics.push(format!(
"{} quote_plan rejected by stock_expr",
candidate.symbol
));
}
continue;
}
missing_rank_count += 1;
if missing_rank_examples.len() < 5 {
missing_rank_examples.push(candidate.symbol.clone());
@@ -31687,7 +31731,11 @@ mod tests {
pe_ttm: 8.0,
turnover_ratio: Some(1.0),
effective_turnover_ratio: Some(1.0),
extra_factors: BTreeMap::new(),
extra_factors: if *symbol == "000002.SZ" {
BTreeMap::new()
} else {
BTreeMap::from([("model_score".to_string(), 4.0 - index as f64)])
},
})
.collect(),
symbols
@@ -31736,14 +31784,16 @@ mod tests {
};
let mut cfg = PlatformExprStrategyConfig::microcap_rotation();
cfg.signal_symbol = "000001.SZ".to_string();
cfg.max_positions = 2;
cfg.max_positions = 3;
cfg.benchmark_short_ma_days = 1;
cfg.benchmark_long_ma_days = 1;
cfg.market_cap_lower_expr = "0".to_string();
cfg.market_cap_upper_expr = "100".to_string();
cfg.selection_limit_expr = "2".to_string();
cfg.selection_limit_expr = "3".to_string();
cfg.stock_filter_expr =
"prev_close > 5.0 && !at_upper_limit && !at_lower_limit".to_string();
cfg.rank_by = "model_score".to_string();
cfg.rank_desc = true;
cfg.aiquant_transaction_cost = true;
cfg.intraday_execution_time = Some(NaiveTime::from_hms_opt(10, 18, 0).unwrap());
let strategy = PlatformExprStrategy::new(cfg);
@@ -31770,6 +31820,27 @@ mod tests {
],
"quote plan may prefetch a superset; final selection/order risk is checked in the decision path"
);
assert!(
plan.diagnostics
.iter()
.all(|item| !item.contains("rank_field_unavailable")),
"a symbol rejected by the daily candidate filter must not be reported as missing model score"
);
let day = strategy.day_state(&ctx, date).expect("day state");
let (selected, diagnostics, _) = strategy
.select_symbols(&ctx, date, date, date, &day, 0.0, 100.0, 3)
.expect("selection");
assert_eq!(
selected,
vec!["000003.SZ".to_string(), "000004.SZ".to_string()]
);
assert!(
diagnostics
.iter()
.all(|item| !item.contains("rank_field_unavailable")),
"the final selector must apply the same missing-score classification"
);
}
#[test]