修正动态排名替换目标状态
This commit is contained in:
@@ -799,6 +799,7 @@ pub struct PlatformExprStrategy {
|
|||||||
engine: Engine,
|
engine: Engine,
|
||||||
rebalance_day_counter: usize,
|
rebalance_day_counter: usize,
|
||||||
last_rebalance_date: Option<NaiveDate>,
|
last_rebalance_date: Option<NaiveDate>,
|
||||||
|
last_target_selection: Option<BTreeSet<String>>,
|
||||||
last_trading_ratio: Option<f64>,
|
last_trading_ratio: Option<f64>,
|
||||||
portfolio_drawdown_controller: Option<PlatformPortfolioDrawdownController>,
|
portfolio_drawdown_controller: Option<PlatformPortfolioDrawdownController>,
|
||||||
pending_highlimit_holdings: BTreeSet<String>,
|
pending_highlimit_holdings: BTreeSet<String>,
|
||||||
@@ -882,6 +883,18 @@ impl PlatformExprStrategy {
|
|||||||
defer_execution || projected_after_qty != before_qty
|
defer_execution || projected_after_qty != before_qty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn replacement_reference_symbols(
|
||||||
|
&self,
|
||||||
|
actual_held_symbols: BTreeSet<String>,
|
||||||
|
) -> (&'static str, BTreeSet<String>) {
|
||||||
|
if self.config.daily_replacement_limit > 0 {
|
||||||
|
if let Some(last_target_selection) = &self.last_target_selection {
|
||||||
|
return ("previous_target", last_target_selection.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("actual_holdings", actual_held_symbols)
|
||||||
|
}
|
||||||
|
|
||||||
fn buffered_selection(
|
fn buffered_selection(
|
||||||
ranked_symbols: &[String],
|
ranked_symbols: &[String],
|
||||||
held_symbols: &BTreeSet<String>,
|
held_symbols: &BTreeSet<String>,
|
||||||
@@ -1093,6 +1106,7 @@ impl PlatformExprStrategy {
|
|||||||
engine,
|
engine,
|
||||||
rebalance_day_counter: 0,
|
rebalance_day_counter: 0,
|
||||||
last_rebalance_date: None,
|
last_rebalance_date: None,
|
||||||
|
last_target_selection: None,
|
||||||
last_trading_ratio: None,
|
last_trading_ratio: None,
|
||||||
portfolio_drawdown_controller,
|
portfolio_drawdown_controller,
|
||||||
pending_highlimit_holdings: BTreeSet::new(),
|
pending_highlimit_holdings: BTreeSet::new(),
|
||||||
@@ -9172,6 +9186,8 @@ impl Strategy for PlatformExprStrategy {
|
|||||||
0
|
0
|
||||||
};
|
};
|
||||||
let mut risk_decisions = Vec::new();
|
let mut risk_decisions = Vec::new();
|
||||||
|
let mut replacement_reference_source = "disabled";
|
||||||
|
let mut replacement_reference_count = 0usize;
|
||||||
let stock_list = if self.config.rotation_enabled && !in_skip_window {
|
let stock_list = if self.config.rotation_enabled && !in_skip_window {
|
||||||
let selection_buffer_rank =
|
let selection_buffer_rank =
|
||||||
self.selection_candidate_limit(ctx, &day, selection_limit)?;
|
self.selection_candidate_limit(ctx, &day, selection_limit)?;
|
||||||
@@ -9192,18 +9208,22 @@ impl Strategy for PlatformExprStrategy {
|
|||||||
)?;
|
)?;
|
||||||
selection_notes = notes;
|
selection_notes = notes;
|
||||||
risk_decisions = selection_risk_decisions;
|
risk_decisions = selection_risk_decisions;
|
||||||
let mut held_symbols = ctx
|
let mut actual_held_symbols = ctx
|
||||||
.portfolio
|
.portfolio
|
||||||
.positions()
|
.positions()
|
||||||
.keys()
|
.keys()
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect::<BTreeSet<_>>();
|
.collect::<BTreeSet<_>>();
|
||||||
if persistent_model_lifecycle {
|
if persistent_model_lifecycle {
|
||||||
held_symbols.extend(self.position_entry_dates.keys().cloned());
|
actual_held_symbols.extend(self.position_entry_dates.keys().cloned());
|
||||||
}
|
}
|
||||||
|
let (reference_source, replacement_reference_symbols) =
|
||||||
|
self.replacement_reference_symbols(actual_held_symbols);
|
||||||
|
replacement_reference_source = reference_source;
|
||||||
|
replacement_reference_count = replacement_reference_symbols.len();
|
||||||
let selected = Self::buffered_selection(
|
let selected = Self::buffered_selection(
|
||||||
&ranked_stock_list,
|
&ranked_stock_list,
|
||||||
&held_symbols,
|
&replacement_reference_symbols,
|
||||||
selection_limit,
|
selection_limit,
|
||||||
selection_buffer_rank,
|
selection_buffer_rank,
|
||||||
self.config.daily_replacement_limit,
|
self.config.daily_replacement_limit,
|
||||||
@@ -10738,6 +10758,10 @@ impl Strategy for PlatformExprStrategy {
|
|||||||
self.rebalance_day_counter = self.rebalance_day_counter.saturating_add(1);
|
self.rebalance_day_counter = self.rebalance_day_counter.saturating_add(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if self.config.rotation_enabled && periodic_rebalance {
|
||||||
|
self.last_target_selection =
|
||||||
|
Some(stock_list.iter().take(selection_limit).cloned().collect());
|
||||||
|
}
|
||||||
if self.config.rotation_enabled && trading_ratio.is_finite() {
|
if self.config.rotation_enabled && trading_ratio.is_finite() {
|
||||||
self.last_trading_ratio = Some(trading_ratio);
|
self.last_trading_ratio = Some(trading_ratio);
|
||||||
}
|
}
|
||||||
@@ -10786,13 +10810,15 @@ impl Strategy for PlatformExprStrategy {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
format!(
|
format!(
|
||||||
"selected={} periodic_rebalance={} exits={} projected_positions={} intents={} limit={} decision_date={} signal_rebalance_date_count={} signal_rebalance_match={} selection_market_date={} selection_universe_factor_date={} selection_factor_date={} execution_date={} budget_total={:.2} marked_total={:.2} day_total={:.2}",
|
"selected={} periodic_rebalance={} exits={} projected_positions={} intents={} limit={} replacement_reference={} replacement_reference_count={} decision_date={} signal_rebalance_date_count={} signal_rebalance_match={} selection_market_date={} selection_universe_factor_date={} selection_factor_date={} execution_date={} budget_total={:.2} marked_total={:.2} day_total={:.2}",
|
||||||
stock_list.len(),
|
stock_list.len(),
|
||||||
periodic_rebalance,
|
periodic_rebalance,
|
||||||
exit_symbols.len(),
|
exit_symbols.len(),
|
||||||
projected.positions().len(),
|
projected.positions().len(),
|
||||||
order_intents.len(),
|
order_intents.len(),
|
||||||
selection_limit,
|
selection_limit,
|
||||||
|
replacement_reference_source,
|
||||||
|
replacement_reference_count,
|
||||||
decision_date,
|
decision_date,
|
||||||
self.config.signal_rebalance_dates.len(),
|
self.config.signal_rebalance_dates.len(),
|
||||||
self.config.signal_rebalance_dates.contains(&decision_date),
|
self.config.signal_rebalance_dates.contains(&decision_date),
|
||||||
@@ -31600,6 +31626,58 @@ mod tests {
|
|||||||
assert_eq!(held.difference(&selected_set).count(), 2);
|
assert_eq!(held.difference(&selected_set).count(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn daily_replacement_reference_uses_previous_target_before_lagged_orders_fill() {
|
||||||
|
let mut cfg = PlatformExprStrategyConfig::microcap_rotation();
|
||||||
|
cfg.daily_replacement_limit = 2;
|
||||||
|
let mut strategy = PlatformExprStrategy::new(cfg);
|
||||||
|
let previous_target = ["000001.SZ", "000002.SZ", "000003.SZ", "000004.SZ"]
|
||||||
|
.into_iter()
|
||||||
|
.map(str::to_string)
|
||||||
|
.collect::<BTreeSet<_>>();
|
||||||
|
strategy.last_target_selection = Some(previous_target.clone());
|
||||||
|
|
||||||
|
let (source, reference) = strategy.replacement_reference_symbols(BTreeSet::new());
|
||||||
|
let ranked = [
|
||||||
|
"000005.SZ",
|
||||||
|
"000006.SZ",
|
||||||
|
"000007.SZ",
|
||||||
|
"000008.SZ",
|
||||||
|
"000001.SZ",
|
||||||
|
"000002.SZ",
|
||||||
|
"000003.SZ",
|
||||||
|
"000004.SZ",
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.map(str::to_string)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let selected = PlatformExprStrategy::buffered_selection(&ranked, &reference, 4, 4, 2);
|
||||||
|
|
||||||
|
assert_eq!(source, "previous_target");
|
||||||
|
assert_eq!(reference, previous_target);
|
||||||
|
assert_eq!(
|
||||||
|
selected,
|
||||||
|
vec!["000005.SZ", "000006.SZ", "000001.SZ", "000002.SZ"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn daily_replacement_reference_records_an_explicit_empty_previous_target() {
|
||||||
|
let mut cfg = PlatformExprStrategyConfig::microcap_rotation();
|
||||||
|
cfg.daily_replacement_limit = 2;
|
||||||
|
let mut strategy = PlatformExprStrategy::new(cfg);
|
||||||
|
strategy.last_target_selection = Some(BTreeSet::new());
|
||||||
|
let actual = ["000001.SZ"]
|
||||||
|
.into_iter()
|
||||||
|
.map(str::to_string)
|
||||||
|
.collect::<BTreeSet<_>>();
|
||||||
|
|
||||||
|
let (source, reference) = strategy.replacement_reference_symbols(actual);
|
||||||
|
|
||||||
|
assert_eq!(source, "previous_target");
|
||||||
|
assert!(reference.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn lagged_rebalance_emits_target_value_even_when_projection_is_unchanged() {
|
fn lagged_rebalance_emits_target_value_even_when_projection_is_unchanged() {
|
||||||
assert!(PlatformExprStrategy::should_emit_rebalance_target_value(
|
assert!(PlatformExprStrategy::should_emit_rebalance_target_value(
|
||||||
|
|||||||
Reference in New Issue
Block a user