按有序市值流提前停止选股

This commit is contained in:
boris
2026-08-29 07:49:43 +08:00
parent 362d85773a
commit e793a5fcc1
+84 -27
View File
@@ -9228,6 +9228,47 @@ impl PlatformExprStrategy {
) )
} }
fn selection_candidate_passes_filters(
&self,
ctx: &StrategyContext<'_>,
date: NaiveDate,
day: &DayExpressionState,
candidate: &EligibleUniverseSnapshot,
stock: &StockExpressionState,
diagnostics: &mut Vec<String>,
) -> Result<bool, BacktestError> {
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));
}
return Ok(false);
}
if !self.stock_passes_expr(ctx, day, stock)? {
if diagnostics.len() < 12 {
diagnostics.push(format!("{} rejected by stock_expr", candidate.symbol));
}
return Ok(false);
}
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
));
}
return Ok(false);
}
Ok(true)
}
fn can_sell_position(&self, ctx: &StrategyContext<'_>, date: NaiveDate, symbol: &str) -> bool { fn can_sell_position(&self, ctx: &StrategyContext<'_>, date: NaiveDate, symbol: &str) -> bool {
self.can_sell_position_at_time(ctx, date, symbol, None) self.can_sell_position_at_time(ctx, date, symbol, None)
} }
@@ -9373,6 +9414,41 @@ impl PlatformExprStrategy {
universe_factor_date, universe_factor_date,
5, 5,
); );
// The universe is already stably ordered by market cap. When the
// strategy asks for that exact ascending order and does not need a
// complete ranking for replacement limiting, select directly from the
// ordered stream instead of materializing a second candidate vector.
if self.rank_reuses_market_cap_order() && self.config.daily_replacement_limit == 0 {
let mut selected = Vec::with_capacity(limit.min(universe.len()));
for candidate in universe {
let stock = self.selection_stock_state_with_factor_date(
ctx,
date,
stock_factor_date,
&candidate.symbol,
)?;
let field_value = self.selection_field_value(&candidate, &stock);
if !field_value.is_finite() || field_value < band_low || field_value > band_high {
continue;
}
if self.selection_candidate_passes_filters(
ctx,
date,
day,
&candidate,
&stock,
&mut diagnostics,
)? {
selected.push(candidate.symbol);
if selected.len() >= limit {
break;
}
}
}
return Ok((selected, diagnostics, risk_decisions));
}
let mut candidates = Vec::new(); let mut candidates = Vec::new();
let mut missing_rank_count = 0usize; let mut missing_rank_count = 0usize;
let mut missing_rank_examples = Vec::new(); let mut missing_rank_examples = Vec::new();
@@ -9461,33 +9537,14 @@ impl PlatformExprStrategy {
let mut selected = Vec::new(); let mut selected = Vec::new();
for (candidate, stock, _) in candidates { for (candidate, stock, _) in candidates {
if !ctx.is_lagged_execution() if !self.selection_candidate_passes_filters(
&& let Some(reason) = self.stock_selection_limit_rejection_reason(&stock) ctx,
{ date,
if diagnostics.len() < 12 { day,
diagnostics.push(format!("{} rejected by {}", candidate.symbol, reason)); &candidate,
} &stock,
continue; &mut diagnostics,
} )? {
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; continue;
} }
selected.push(candidate.symbol.clone()); selected.push(candidate.symbol.clone());