修复延迟执行选股风控语义
This commit is contained in:
@@ -2506,6 +2506,10 @@ impl DataSet {
|
|||||||
.unwrap_or(&[])
|
.unwrap_or(&[])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn fundamental_universe_on(&self, date: NaiveDate) -> Vec<EligibleUniverseSnapshot> {
|
||||||
|
build_fundamental_universe_for_date(date, &self.factor_by_date, &self.market_by_date)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn eligible_universe_on_with_risk_config(
|
pub fn eligible_universe_on_with_risk_config(
|
||||||
&self,
|
&self,
|
||||||
date: NaiveDate,
|
date: NaiveDate,
|
||||||
@@ -3051,6 +3055,41 @@ fn build_eligible_universe(
|
|||||||
per_date
|
per_date
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_fundamental_universe_for_date(
|
||||||
|
date: NaiveDate,
|
||||||
|
factor_by_date: &BTreeMap<NaiveDate, Vec<Arc<DailyFactorSnapshot>>>,
|
||||||
|
market_by_date: &BTreeMap<NaiveDate, Vec<Arc<DailyMarketSnapshot>>>,
|
||||||
|
) -> Vec<EligibleUniverseSnapshot> {
|
||||||
|
let mut rows = Vec::new();
|
||||||
|
let Some(factors) = factor_by_date.get(&date) else {
|
||||||
|
return rows;
|
||||||
|
};
|
||||||
|
for factor in factors {
|
||||||
|
let Some(market) = market_by_date
|
||||||
|
.get(&date)
|
||||||
|
.and_then(|rows| find_arc_by_symbol(rows, &factor.symbol, |row| row.symbol.as_str()))
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let market_cap_bn = decision_market_cap_bn(factor, market);
|
||||||
|
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),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
rows.sort_by(|left, right| {
|
||||||
|
left.market_cap_bn
|
||||||
|
.partial_cmp(&right.market_cap_bn)
|
||||||
|
.unwrap_or(std::cmp::Ordering::Equal)
|
||||||
|
.then_with(|| left.symbol.cmp(&right.symbol))
|
||||||
|
});
|
||||||
|
rows
|
||||||
|
}
|
||||||
|
|
||||||
fn build_eligible_universe_for_date(
|
fn build_eligible_universe_for_date(
|
||||||
date: NaiveDate,
|
date: NaiveDate,
|
||||||
factor_by_date: &BTreeMap<NaiveDate, Vec<Arc<DailyFactorSnapshot>>>,
|
factor_by_date: &BTreeMap<NaiveDate, Vec<Arc<DailyFactorSnapshot>>>,
|
||||||
|
|||||||
@@ -156,6 +156,10 @@ pub struct StrategyContext<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl StrategyContext<'_> {
|
impl StrategyContext<'_> {
|
||||||
|
pub fn is_lagged_execution(&self) -> bool {
|
||||||
|
self.execution_date != self.decision_date
|
||||||
|
}
|
||||||
|
|
||||||
pub fn current_datetime(&self) -> Option<NaiveDateTime> {
|
pub fn current_datetime(&self) -> Option<NaiveDateTime> {
|
||||||
self.active_datetime
|
self.active_datetime
|
||||||
}
|
}
|
||||||
@@ -525,6 +529,20 @@ impl StrategyContext<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn fundamental_universe_on(
|
||||||
|
&self,
|
||||||
|
date: NaiveDate,
|
||||||
|
) -> Vec<crate::data::EligibleUniverseSnapshot> {
|
||||||
|
let eligible = self.data.fundamental_universe_on(date);
|
||||||
|
match self.dynamic_universe {
|
||||||
|
Some(symbols) if !symbols.is_empty() => eligible
|
||||||
|
.into_iter()
|
||||||
|
.filter(|row| symbols.contains(&row.symbol))
|
||||||
|
.collect(),
|
||||||
|
_ => eligible,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn current_snapshot(&self, symbol: &str) -> Option<&DailyMarketSnapshot> {
|
pub fn current_snapshot(&self, symbol: &str) -> Option<&DailyMarketSnapshot> {
|
||||||
self.data.market(self.execution_date, symbol)
|
self.data.market(self.execution_date, symbol)
|
||||||
}
|
}
|
||||||
@@ -1373,14 +1391,14 @@ impl Strategy for CnSmallCapRotationStrategy {
|
|||||||
date: ctx.decision_date,
|
date: ctx.decision_date,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if self.config.in_skip_window(ctx.execution_date) {
|
if self.config.in_skip_window(ctx.decision_date) {
|
||||||
self.last_gross_exposure = Some(0.0);
|
self.last_gross_exposure = Some(0.0);
|
||||||
return Ok(StrategyDecision {
|
return Ok(StrategyDecision {
|
||||||
rebalance: true,
|
rebalance: true,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: ctx.portfolio.positions().keys().cloned().collect(),
|
exit_symbols: ctx.portfolio.positions().keys().cloned().collect(),
|
||||||
order_intents: Vec::new(),
|
order_intents: Vec::new(),
|
||||||
notes: vec![format!("skip-window active on {}", ctx.execution_date)],
|
notes: vec![format!("skip-window active on {}", ctx.decision_date)],
|
||||||
diagnostics: vec![
|
diagnostics: vec![
|
||||||
"seasonal stop window approximated at daily granularity".to_string(),
|
"seasonal stop window approximated at daily granularity".to_string(),
|
||||||
"run_daily(10:17/10:18) mapped to T-1 decision and T open execution"
|
"run_daily(10:17/10:18) mapped to T-1 decision and T open execution"
|
||||||
@@ -1454,6 +1472,7 @@ impl Strategy for CnSmallCapRotationStrategy {
|
|||||||
data: ctx.data,
|
data: ctx.data,
|
||||||
dynamic_universe: ctx.dynamic_universe,
|
dynamic_universe: ctx.dynamic_universe,
|
||||||
risk_config: Some(&self.config.risk_config),
|
risk_config: Some(&self.config.risk_config),
|
||||||
|
defer_selection_risk: ctx.is_lagged_execution(),
|
||||||
});
|
});
|
||||||
let before_ma_count = selected_before_ma.len();
|
let before_ma_count = selected_before_ma.len();
|
||||||
let mut ma_rejects = Vec::new();
|
let mut ma_rejects = Vec::new();
|
||||||
@@ -2421,7 +2440,11 @@ impl OmniMicroCapStrategy {
|
|||||||
&self,
|
&self,
|
||||||
ctx: &StrategyContext<'_>,
|
ctx: &StrategyContext<'_>,
|
||||||
date: NaiveDate,
|
date: NaiveDate,
|
||||||
|
defer_selection_risk: bool,
|
||||||
) -> Vec<FidcRiskDecisionAudit> {
|
) -> Vec<FidcRiskDecisionAudit> {
|
||||||
|
if defer_selection_risk {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
let mut decisions = Vec::new();
|
let mut decisions = Vec::new();
|
||||||
for factor in ctx.data.factor_snapshots_on(date) {
|
for factor in ctx.data.factor_snapshots_on(date) {
|
||||||
if ctx.has_dynamic_universe() && !ctx.dynamic_universe_contains(&factor.symbol) {
|
if ctx.has_dynamic_universe() && !ctx.dynamic_universe_contains(&factor.symbol) {
|
||||||
@@ -2481,8 +2504,13 @@ impl OmniMicroCapStrategy {
|
|||||||
date: NaiveDate,
|
date: NaiveDate,
|
||||||
band_low: f64,
|
band_low: f64,
|
||||||
band_high: f64,
|
band_high: f64,
|
||||||
|
defer_selection_risk: bool,
|
||||||
) -> Result<(Vec<String>, Vec<String>), BacktestError> {
|
) -> Result<(Vec<String>, Vec<String>), BacktestError> {
|
||||||
let universe = ctx.eligible_universe_on_with_risk_config(date, &self.config.risk_config);
|
let universe = if defer_selection_risk {
|
||||||
|
ctx.fundamental_universe_on(date)
|
||||||
|
} else {
|
||||||
|
ctx.eligible_universe_on_with_risk_config(date, &self.config.risk_config)
|
||||||
|
};
|
||||||
let mut diagnostics = Vec::new();
|
let mut diagnostics = Vec::new();
|
||||||
let mut selected = Vec::new();
|
let mut selected = Vec::new();
|
||||||
let start = lower_bound_eligible(&universe, band_low);
|
let start = lower_bound_eligible(&universe, band_low);
|
||||||
@@ -2491,7 +2519,13 @@ impl OmniMicroCapStrategy {
|
|||||||
if candidate.market_cap_bn > band_high {
|
if candidate.market_cap_bn > band_high {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if let Some(reason) = self.buy_rejection_reason(ctx, date, &candidate.symbol)? {
|
let rejection = if defer_selection_risk {
|
||||||
|
(!self.stock_passes_ma_filter(ctx, date, &candidate.symbol))
|
||||||
|
.then_some("ma_filter".to_string())
|
||||||
|
} else {
|
||||||
|
self.buy_rejection_reason(ctx, date, &candidate.symbol)?
|
||||||
|
};
|
||||||
|
if let Some(reason) = rejection {
|
||||||
if diagnostics.len() < 12 {
|
if diagnostics.len() < 12 {
|
||||||
diagnostics.push(format!("{} rejected by {}", candidate.symbol, reason));
|
diagnostics.push(format!("{} rejected by {}", candidate.symbol, reason));
|
||||||
}
|
}
|
||||||
@@ -2514,8 +2548,10 @@ impl Strategy for OmniMicroCapStrategy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn on_day(&mut self, ctx: &StrategyContext<'_>) -> Result<StrategyDecision, BacktestError> {
|
fn on_day(&mut self, ctx: &StrategyContext<'_>) -> Result<StrategyDecision, BacktestError> {
|
||||||
let date = ctx.execution_date;
|
let signal_date = ctx.decision_date;
|
||||||
if self.config.in_skip_window(date) {
|
let execution_date = ctx.execution_date;
|
||||||
|
let defer_selection_risk = ctx.is_lagged_execution();
|
||||||
|
if self.config.in_skip_window(signal_date) {
|
||||||
return Ok(StrategyDecision {
|
return Ok(StrategyDecision {
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
@@ -2531,14 +2567,14 @@ impl Strategy for OmniMicroCapStrategy {
|
|||||||
reason: "seasonal_stop_window".to_string(),
|
reason: "seasonal_stop_window".to_string(),
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
notes: vec![format!("seasonal stop window on {}", date)],
|
notes: vec![format!("seasonal stop window on {}", signal_date)],
|
||||||
diagnostics: vec!["platform-native skip window forced all cash".to_string()],
|
diagnostics: vec!["platform-native skip window forced all cash".to_string()],
|
||||||
risk_decisions: Vec::new(),
|
risk_decisions: Vec::new(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let (index_level, prev_index_level, ma_short, ma_long, trading_ratio) =
|
let (index_level, prev_index_level, ma_short, ma_long, trading_ratio) =
|
||||||
match self.trading_ratio(ctx, date) {
|
match self.trading_ratio(ctx, signal_date) {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(BacktestError::Execution(message))
|
Err(BacktestError::Execution(message))
|
||||||
if message.contains("insufficient benchmark") =>
|
if message.contains("insufficient benchmark") =>
|
||||||
@@ -2559,9 +2595,11 @@ impl Strategy for OmniMicroCapStrategy {
|
|||||||
};
|
};
|
||||||
// 使用前一交易日的指数价格计算市值区间(模拟实盘场景)
|
// 使用前一交易日的指数价格计算市值区间(模拟实盘场景)
|
||||||
let (band_low, band_high) = self.market_cap_band(prev_index_level);
|
let (band_low, band_high) = self.market_cap_band(prev_index_level);
|
||||||
let (stock_list, selection_notes) = self.select_symbols(ctx, date, band_low, band_high)?;
|
let (stock_list, selection_notes) =
|
||||||
let risk_decisions = self.selection_risk_decisions(ctx, date);
|
self.select_symbols(ctx, signal_date, band_low, band_high, defer_selection_risk)?;
|
||||||
|
let risk_decisions = self.selection_risk_decisions(ctx, signal_date, defer_selection_risk);
|
||||||
let periodic_rebalance = ctx.decision_index % self.config.refresh_rate == 0;
|
let periodic_rebalance = ctx.decision_index % self.config.refresh_rate == 0;
|
||||||
|
let projection_date = signal_date;
|
||||||
let mut projected = ctx.portfolio.clone();
|
let mut projected = ctx.portfolio.clone();
|
||||||
let mut projected_execution_state = ProjectedExecutionState::default();
|
let mut projected_execution_state = ProjectedExecutionState::default();
|
||||||
let mut order_intents = Vec::new();
|
let mut order_intents = Vec::new();
|
||||||
@@ -2571,18 +2609,21 @@ impl Strategy for OmniMicroCapStrategy {
|
|||||||
if position.quantity == 0 || position.average_cost <= 0.0 {
|
if position.quantity == 0 || position.average_cost <= 0.0 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let Some(current_price) = ctx.data.price(date, &position.symbol, PriceField::Last)
|
let Some(current_price) =
|
||||||
|
ctx.data
|
||||||
|
.price(signal_date, &position.symbol, PriceField::Last)
|
||||||
else {
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Some(market) = ctx.data.market(date, &position.symbol) else {
|
let Some(market) = ctx.data.market(signal_date, &position.symbol) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let stop_hit = current_price
|
let stop_hit = current_price
|
||||||
<= position.average_cost * self.config.stop_loss_ratio
|
<= position.average_cost * self.config.stop_loss_ratio
|
||||||
+ self.stop_loss_tolerance(market);
|
+ self.stop_loss_tolerance(market);
|
||||||
let profit_hit = current_price / position.average_cost > self.config.take_profit_ratio;
|
let profit_hit = current_price / position.average_cost > self.config.take_profit_ratio;
|
||||||
let can_sell = self.can_sell_position(ctx, date, &position.symbol);
|
let can_sell = defer_selection_risk
|
||||||
|
|| self.can_sell_position(ctx, execution_date, &position.symbol);
|
||||||
let at_upper_limit = market.is_at_upper_limit_price(current_price);
|
let at_upper_limit = market.is_at_upper_limit_price(current_price);
|
||||||
if stop_hit || (profit_hit && !at_upper_limit) {
|
if stop_hit || (profit_hit && !at_upper_limit) {
|
||||||
let sell_reason = if stop_hit {
|
let sell_reason = if stop_hit {
|
||||||
@@ -2600,7 +2641,7 @@ impl Strategy for OmniMicroCapStrategy {
|
|||||||
self.project_target_zero(
|
self.project_target_zero(
|
||||||
ctx,
|
ctx,
|
||||||
&mut projected,
|
&mut projected,
|
||||||
date,
|
projection_date,
|
||||||
&position.symbol,
|
&position.symbol,
|
||||||
sell_reason,
|
sell_reason,
|
||||||
&mut projected_execution_state,
|
&mut projected_execution_state,
|
||||||
@@ -2618,7 +2659,11 @@ impl Strategy for OmniMicroCapStrategy {
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if self.buy_rejection_reason(ctx, date, symbol)?.is_some() {
|
if !defer_selection_risk
|
||||||
|
&& self
|
||||||
|
.buy_rejection_reason(ctx, execution_date, symbol)?
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
order_intents.push(OrderIntent::Value {
|
order_intents.push(OrderIntent::Value {
|
||||||
@@ -2629,7 +2674,7 @@ impl Strategy for OmniMicroCapStrategy {
|
|||||||
self.project_order_value(
|
self.project_order_value(
|
||||||
ctx,
|
ctx,
|
||||||
&mut projected,
|
&mut projected,
|
||||||
date,
|
projection_date,
|
||||||
symbol,
|
symbol,
|
||||||
replacement_cash,
|
replacement_cash,
|
||||||
&format!("replacement_after_{}", sell_reason),
|
&format!("replacement_after_{}", sell_reason),
|
||||||
@@ -2652,7 +2697,7 @@ impl Strategy for OmniMicroCapStrategy {
|
|||||||
if stock_list.iter().any(|candidate| candidate == symbol) {
|
if stock_list.iter().any(|candidate| candidate == symbol) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if !self.can_sell_position(ctx, date, symbol) {
|
if !defer_selection_risk && !self.can_sell_position(ctx, execution_date, symbol) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
order_intents.push(OrderIntent::TargetValue {
|
order_intents.push(OrderIntent::TargetValue {
|
||||||
@@ -2663,7 +2708,7 @@ impl Strategy for OmniMicroCapStrategy {
|
|||||||
self.project_target_zero(
|
self.project_target_zero(
|
||||||
ctx,
|
ctx,
|
||||||
&mut projected,
|
&mut projected,
|
||||||
date,
|
projection_date,
|
||||||
symbol,
|
symbol,
|
||||||
"periodic_rebalance_sell",
|
"periodic_rebalance_sell",
|
||||||
&mut projected_execution_state,
|
&mut projected_execution_state,
|
||||||
@@ -2680,7 +2725,11 @@ impl Strategy for OmniMicroCapStrategy {
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if self.buy_rejection_reason(ctx, date, symbol)?.is_some() {
|
if !defer_selection_risk
|
||||||
|
&& self
|
||||||
|
.buy_rejection_reason(ctx, execution_date, symbol)?
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
order_intents.push(OrderIntent::Value {
|
order_intents.push(OrderIntent::Value {
|
||||||
@@ -2691,7 +2740,7 @@ impl Strategy for OmniMicroCapStrategy {
|
|||||||
self.project_order_value(
|
self.project_order_value(
|
||||||
ctx,
|
ctx,
|
||||||
&mut projected,
|
&mut projected,
|
||||||
date,
|
projection_date,
|
||||||
symbol,
|
symbol,
|
||||||
fixed_buy_cash,
|
fixed_buy_cash,
|
||||||
"periodic_rebalance_buy",
|
"periodic_rebalance_buy",
|
||||||
@@ -2713,7 +2762,7 @@ impl Strategy for OmniMicroCapStrategy {
|
|||||||
projected.positions().len(),
|
projected.positions().len(),
|
||||||
order_intents.len()
|
order_intents.len()
|
||||||
),
|
),
|
||||||
"platform schedule 10:17/10:18 approximated as same-day decision with snapshot last_price signals and bid1/ask1 side-aware execution".to_string(),
|
"platform schedule signal uses decision_date data; broker applies execution_date price and risk checks".to_string(),
|
||||||
];
|
];
|
||||||
if std::env::var("FIDC_BT_DEBUG_POSITION_ORDER")
|
if std::env::var("FIDC_BT_DEBUG_POSITION_ORDER")
|
||||||
.map(|value| value == "1")
|
.map(|value| value == "1")
|
||||||
@@ -2907,10 +2956,11 @@ mod tests {
|
|||||||
default_cfg.stock_long_ma_days = 3;
|
default_cfg.stock_long_ma_days = 3;
|
||||||
let default_strategy = OmniMicroCapStrategy::new(default_cfg.clone());
|
let default_strategy = OmniMicroCapStrategy::new(default_cfg.clone());
|
||||||
let (default_selected, _) = default_strategy
|
let (default_selected, _) = default_strategy
|
||||||
.select_symbols(&ctx, dates[2], 0.0, 100.0)
|
.select_symbols(&ctx, dates[2], 0.0, 100.0, false)
|
||||||
.expect("default selection");
|
.expect("default selection");
|
||||||
assert!(default_selected.is_empty());
|
assert!(default_selected.is_empty());
|
||||||
let default_risk_decisions = default_strategy.selection_risk_decisions(&ctx, dates[2]);
|
let default_risk_decisions =
|
||||||
|
default_strategy.selection_risk_decisions(&ctx, dates[2], false);
|
||||||
assert_eq!(default_risk_decisions.len(), 1);
|
assert_eq!(default_risk_decisions.len(), 1);
|
||||||
assert_eq!(default_risk_decisions[0].symbol, symbol);
|
assert_eq!(default_risk_decisions[0].symbol, symbol);
|
||||||
assert_eq!(default_risk_decisions[0].rule_code, "kcb");
|
assert_eq!(default_risk_decisions[0].rule_code, "kcb");
|
||||||
@@ -2925,11 +2975,11 @@ mod tests {
|
|||||||
cfg.risk_config.static_rules.reject_kcb_buy = false;
|
cfg.risk_config.static_rules.reject_kcb_buy = false;
|
||||||
let configured_strategy = OmniMicroCapStrategy::new(cfg);
|
let configured_strategy = OmniMicroCapStrategy::new(cfg);
|
||||||
let (selected, _) = configured_strategy
|
let (selected, _) = configured_strategy
|
||||||
.select_symbols(&ctx, dates[2], 0.0, 100.0)
|
.select_symbols(&ctx, dates[2], 0.0, 100.0, false)
|
||||||
.expect("configured selection");
|
.expect("configured selection");
|
||||||
assert!(
|
assert!(
|
||||||
configured_strategy
|
configured_strategy
|
||||||
.selection_risk_decisions(&ctx, dates[2])
|
.selection_risk_decisions(&ctx, dates[2], false)
|
||||||
.is_empty()
|
.is_empty()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -50,15 +50,20 @@ pub struct SelectionContext<'a> {
|
|||||||
pub data: &'a DataSet,
|
pub data: &'a DataSet,
|
||||||
pub dynamic_universe: Option<&'a BTreeSet<String>>,
|
pub dynamic_universe: Option<&'a BTreeSet<String>>,
|
||||||
pub risk_config: Option<&'a FidcRiskControlConfig>,
|
pub risk_config: Option<&'a FidcRiskControlConfig>,
|
||||||
|
pub defer_selection_risk: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SelectionContext<'_> {
|
impl SelectionContext<'_> {
|
||||||
fn eligible_universe(&self) -> Vec<EligibleUniverseSnapshot> {
|
fn eligible_universe(&self) -> Vec<EligibleUniverseSnapshot> {
|
||||||
let eligible = match self.risk_config {
|
let eligible = if self.defer_selection_risk {
|
||||||
|
self.data.fundamental_universe_on(self.decision_date)
|
||||||
|
} else {
|
||||||
|
match self.risk_config {
|
||||||
Some(risk_config) => self
|
Some(risk_config) => self
|
||||||
.data
|
.data
|
||||||
.eligible_universe_on_with_risk_config(self.decision_date, risk_config),
|
.eligible_universe_on_with_risk_config(self.decision_date, risk_config),
|
||||||
None => self.data.eligible_universe_on(self.decision_date).to_vec(),
|
None => self.data.eligible_universe_on(self.decision_date).to_vec(),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
match self.dynamic_universe {
|
match self.dynamic_universe {
|
||||||
Some(symbols) if !symbols.is_empty() => eligible
|
Some(symbols) if !symbols.is_empty() => eligible
|
||||||
@@ -70,6 +75,9 @@ impl SelectionContext<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn selection_risk_decisions(&self) -> Vec<FidcRiskDecisionAudit> {
|
fn selection_risk_decisions(&self) -> Vec<FidcRiskDecisionAudit> {
|
||||||
|
if self.defer_selection_risk {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
let default_risk_config;
|
let default_risk_config;
|
||||||
let risk_config = match self.risk_config {
|
let risk_config = match self.risk_config {
|
||||||
Some(value) => value,
|
Some(value) => value,
|
||||||
@@ -403,6 +411,7 @@ mod tests {
|
|||||||
data: &data,
|
data: &data,
|
||||||
dynamic_universe: None,
|
dynamic_universe: None,
|
||||||
risk_config: Some(&FidcRiskControlConfig::default()),
|
risk_config: Some(&FidcRiskControlConfig::default()),
|
||||||
|
defer_selection_risk: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
let rules = diagnostics
|
let rules = diagnostics
|
||||||
|
|||||||
Reference in New Issue
Block a user