修复FIDC选股阶段风控语义
This commit is contained in:
@@ -2426,18 +2426,26 @@ where
|
|||||||
} else {
|
} else {
|
||||||
ChinaAShareRiskControl::sell_check_price(snapshot, self.execution_price_field)
|
ChinaAShareRiskControl::sell_check_price(snapshot, self.execution_price_field)
|
||||||
};
|
};
|
||||||
let mut risk_config = self.risk_config.clone();
|
let adjusted_candidate;
|
||||||
if self.aiquant_execution_rules {
|
let candidate_for_check = if self.aiquant_execution_rules
|
||||||
risk_config.static_rules.respect_allow_buy_sell = false;
|
&& self.aiquant_sell_allow_flag_is_stale_lower_limit(candidate, snapshot, check_price)
|
||||||
}
|
{
|
||||||
|
adjusted_candidate = crate::data::CandidateEligibility {
|
||||||
|
allow_sell: true,
|
||||||
|
..candidate.clone()
|
||||||
|
};
|
||||||
|
&adjusted_candidate
|
||||||
|
} else {
|
||||||
|
candidate
|
||||||
|
};
|
||||||
if let Some(reason) = ChinaAShareRiskControl::sell_rejection_reason_with_config(
|
if let Some(reason) = ChinaAShareRiskControl::sell_rejection_reason_with_config(
|
||||||
date,
|
date,
|
||||||
candidate,
|
candidate_for_check,
|
||||||
snapshot,
|
snapshot,
|
||||||
instrument,
|
instrument,
|
||||||
Some(position),
|
Some(position),
|
||||||
check_price,
|
check_price,
|
||||||
&risk_config,
|
&self.risk_config,
|
||||||
) {
|
) {
|
||||||
return RuleCheck::reject(reason);
|
return RuleCheck::reject(reason);
|
||||||
}
|
}
|
||||||
@@ -2453,6 +2461,21 @@ where
|
|||||||
RuleCheck::allow()
|
RuleCheck::allow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn aiquant_sell_allow_flag_is_stale_lower_limit(
|
||||||
|
&self,
|
||||||
|
candidate: &crate::data::CandidateEligibility,
|
||||||
|
snapshot: &crate::data::DailyMarketSnapshot,
|
||||||
|
check_price: f64,
|
||||||
|
) -> bool {
|
||||||
|
if candidate.allow_sell || candidate.is_paused || snapshot.paused {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let snapshot_check_price =
|
||||||
|
ChinaAShareRiskControl::sell_check_price(snapshot, self.execution_price_field);
|
||||||
|
snapshot.is_at_lower_limit_price(snapshot_check_price)
|
||||||
|
&& !snapshot.is_at_lower_limit_price(check_price)
|
||||||
|
}
|
||||||
|
|
||||||
fn minimum_target_quantity(
|
fn minimum_target_quantity(
|
||||||
&self,
|
&self,
|
||||||
date: NaiveDate,
|
date: NaiveDate,
|
||||||
@@ -5283,7 +5306,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.volume_limit {
|
if self.volume_limit {
|
||||||
let raw_limit = ((available_market_volume as f64) * self.volume_percent).round() as i64
|
let raw_limit = ((available_market_volume as f64) * self.volume_percent).floor() as i64
|
||||||
- consumed_turnover as i64;
|
- consumed_turnover as i64;
|
||||||
if raw_limit <= 0 {
|
if raw_limit <= 0 {
|
||||||
return Err(volume_limit_reason.to_string());
|
return Err(volume_limit_reason.to_string());
|
||||||
@@ -6537,6 +6560,27 @@ mod tests {
|
|||||||
assert_eq!(fillable, Ok(5_000));
|
assert_eq!(fillable, Ok(5_000));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn volume_limit_uses_floor_for_odd_lot_sell() {
|
||||||
|
let mut snapshot = limit_test_snapshot();
|
||||||
|
snapshot.minute_volume = 0;
|
||||||
|
snapshot.volume = 3;
|
||||||
|
let broker = BrokerSimulator::new_with_execution_price(
|
||||||
|
ChinaAShareCostModel::default(),
|
||||||
|
ChinaEquityRuleHooks,
|
||||||
|
PriceField::Close,
|
||||||
|
)
|
||||||
|
.with_matching_type(MatchingType::CurrentBarClose)
|
||||||
|
.with_volume_limit(true)
|
||||||
|
.with_volume_percent(0.5)
|
||||||
|
.with_liquidity_limit(false);
|
||||||
|
|
||||||
|
let fillable =
|
||||||
|
broker.market_fillable_quantity(&snapshot, OrderSide::Sell, 10, 100, 100, 0, true);
|
||||||
|
|
||||||
|
assert_eq!(fillable, Ok(1));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn current_bar_close_volume_limit_rejects_daily_zero_volume() {
|
fn current_bar_close_volume_limit_rejects_daily_zero_volume() {
|
||||||
let mut snapshot = limit_test_snapshot();
|
let mut snapshot = limit_test_snapshot();
|
||||||
@@ -7427,4 +7471,99 @@ mod tests {
|
|||||||
report.order_events
|
report.order_events
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn aiquant_sell_rule_still_blocks_real_allow_sell_false() {
|
||||||
|
let date = chrono::NaiveDate::from_ymd_opt(2024, 4, 17).expect("valid date");
|
||||||
|
let prev_date = chrono::NaiveDate::from_ymd_opt(2024, 4, 16).expect("valid date");
|
||||||
|
let symbol = "000001.SZ";
|
||||||
|
let broker = BrokerSimulator::new_with_execution_price(
|
||||||
|
ChinaAShareCostModel::default(),
|
||||||
|
ChinaEquityRuleHooks,
|
||||||
|
PriceField::Last,
|
||||||
|
)
|
||||||
|
.with_aiquant_execution_rules(true)
|
||||||
|
.with_intraday_execution_start_time(date.and_hms_opt(10, 18, 0).unwrap().time())
|
||||||
|
.with_volume_limit(false)
|
||||||
|
.with_liquidity_limit(false)
|
||||||
|
.with_inactive_limit(false)
|
||||||
|
.with_slippage_model(SlippageModel::None);
|
||||||
|
|
||||||
|
let mut snapshot = limit_test_snapshot();
|
||||||
|
snapshot.date = date;
|
||||||
|
snapshot.timestamp = Some("2024-04-17 10:18:00".to_string());
|
||||||
|
snapshot.day_open = 10.0;
|
||||||
|
snapshot.open = 10.0;
|
||||||
|
snapshot.high = 10.2;
|
||||||
|
snapshot.low = 9.8;
|
||||||
|
snapshot.close = 10.0;
|
||||||
|
snapshot.last_price = 10.0;
|
||||||
|
snapshot.bid1 = 10.0;
|
||||||
|
snapshot.ask1 = 10.0;
|
||||||
|
snapshot.prev_close = 10.0;
|
||||||
|
snapshot.upper_limit = 11.0;
|
||||||
|
snapshot.lower_limit = 9.0;
|
||||||
|
let mut candidate = limit_test_candidate(true, false);
|
||||||
|
candidate.date = date;
|
||||||
|
let mut quote = limit_test_quote(10.0, 9.99, 10.0);
|
||||||
|
quote.date = date;
|
||||||
|
quote.timestamp = date.and_hms_opt(10, 18, 0).unwrap();
|
||||||
|
quote.last_price = 10.0;
|
||||||
|
quote.bid1 = 9.99;
|
||||||
|
quote.ask1 = 10.0;
|
||||||
|
|
||||||
|
let data = DataSet::from_components_with_actions_and_quotes(
|
||||||
|
vec![limit_test_instrument()],
|
||||||
|
vec![snapshot],
|
||||||
|
Vec::new(),
|
||||||
|
vec![candidate],
|
||||||
|
vec![BenchmarkSnapshot {
|
||||||
|
date,
|
||||||
|
benchmark: "000852.SH".to_string(),
|
||||||
|
open: 1000.0,
|
||||||
|
close: 1000.0,
|
||||||
|
prev_close: 1000.0,
|
||||||
|
volume: 1_000_000,
|
||||||
|
}],
|
||||||
|
Vec::new(),
|
||||||
|
vec![quote],
|
||||||
|
)
|
||||||
|
.expect("valid dataset");
|
||||||
|
let mut portfolio = PortfolioState::new(100.0);
|
||||||
|
portfolio.position_mut(symbol).buy(prev_date, 7_200, 8.48);
|
||||||
|
let mut report = BrokerExecutionReport::default();
|
||||||
|
|
||||||
|
broker
|
||||||
|
.process_target_value(
|
||||||
|
date,
|
||||||
|
&mut portfolio,
|
||||||
|
&data,
|
||||||
|
symbol,
|
||||||
|
0.0,
|
||||||
|
"stop_loss_exit",
|
||||||
|
&mut BTreeMap::new(),
|
||||||
|
&mut BTreeMap::new(),
|
||||||
|
&mut None,
|
||||||
|
&mut BTreeMap::new(),
|
||||||
|
&mut report,
|
||||||
|
)
|
||||||
|
.expect("target sell processed");
|
||||||
|
|
||||||
|
assert!(report.fill_events.is_empty());
|
||||||
|
assert_eq!(
|
||||||
|
portfolio.position(symbol).map(|position| position.quantity),
|
||||||
|
Some(7_200)
|
||||||
|
);
|
||||||
|
let rejected_order = report
|
||||||
|
.order_events
|
||||||
|
.iter()
|
||||||
|
.find(|event| event.side == OrderSide::Sell)
|
||||||
|
.expect("sell order event");
|
||||||
|
assert_eq!(rejected_order.status, OrderStatus::Canceled);
|
||||||
|
assert!(
|
||||||
|
rejected_order.reason.contains("sell_disabled"),
|
||||||
|
"{}",
|
||||||
|
rejected_order.reason
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3516,8 +3516,13 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.expect("dataset");
|
.expect("dataset");
|
||||||
|
|
||||||
assert!(data.eligible_universe_on(date).is_empty());
|
assert_eq!(data.eligible_universe_on(date).len(), 1);
|
||||||
let mut risk_config = FidcRiskControlConfig::default();
|
let mut risk_config = FidcRiskControlConfig::default();
|
||||||
|
risk_config.static_rules.reject_kcb_selection = true;
|
||||||
|
assert!(
|
||||||
|
data.eligible_universe_on_with_risk_config(date, &risk_config)
|
||||||
|
.is_empty()
|
||||||
|
);
|
||||||
risk_config.static_rules.reject_kcb_selection = false;
|
risk_config.static_rules.reject_kcb_selection = false;
|
||||||
let rows = data.eligible_universe_on_with_risk_config(date, &risk_config);
|
let rows = data.eligible_universe_on_with_risk_config(date, &risk_config);
|
||||||
|
|
||||||
|
|||||||
@@ -5002,9 +5002,22 @@ mod tests {
|
|||||||
.map(|snapshot| snapshot.close),
|
.map(|snapshot| snapshot.close),
|
||||||
Some(11.5)
|
Some(11.5)
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
dataset
|
||||||
|
.eligible_universe_on(first)
|
||||||
|
.iter()
|
||||||
|
.map(|row| row.symbol.clone())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
vec![SYMBOL.to_string()],
|
||||||
|
"raw DataSet helper should not apply default selection risk"
|
||||||
|
);
|
||||||
|
let mut selection_risk_config = FidcRiskControlConfig::default();
|
||||||
|
selection_risk_config.static_rules.reject_paused_selection = true;
|
||||||
assert!(
|
assert!(
|
||||||
dataset.eligible_universe_on(first).is_empty(),
|
dataset
|
||||||
"raw DataSet helper remains a risk-filtered universe"
|
.eligible_universe_on_with_risk_config(first, &selection_risk_config)
|
||||||
|
.is_empty(),
|
||||||
|
"explicit raw DataSet selection risk can still filter the universe"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
manual_ctx
|
manual_ctx
|
||||||
@@ -5017,7 +5030,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
manual_ctx
|
manual_ctx
|
||||||
.eligible_universe_on_with_risk_config(first, &FidcRiskControlConfig::default())
|
.eligible_universe_on_with_risk_config(first, &selection_risk_config)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|row| row.symbol)
|
.map(|row| row.symbol)
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
|
|||||||
@@ -6019,7 +6019,7 @@ impl PlatformExprStrategy {
|
|||||||
ctx: &StrategyContext<'_>,
|
ctx: &StrategyContext<'_>,
|
||||||
date: NaiveDate,
|
date: NaiveDate,
|
||||||
factor_date: NaiveDate,
|
factor_date: NaiveDate,
|
||||||
defer_limit_state_selection_risk: bool,
|
defer_all_selection_risk: bool,
|
||||||
) -> Vec<EligibleUniverseSnapshot> {
|
) -> Vec<EligibleUniverseSnapshot> {
|
||||||
let mut rows = Vec::new();
|
let mut rows = Vec::new();
|
||||||
for factor in ctx.data.factor_snapshots_on(factor_date) {
|
for factor in ctx.data.factor_snapshots_on(factor_date) {
|
||||||
@@ -6035,12 +6035,10 @@ impl PlatformExprStrategy {
|
|||||||
let Some(market) = ctx.data.market(date, &factor.symbol) else {
|
let Some(market) = ctx.data.market(date, &factor.symbol) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if let Some(reason) =
|
if let Some(_reason) =
|
||||||
self.selection_risk_rejection_reason(ctx, date, &factor.symbol, candidate, market)
|
self.selection_risk_rejection_reason(ctx, date, &factor.symbol, candidate, market)
|
||||||
{
|
{
|
||||||
if !(defer_limit_state_selection_risk
|
if !defer_all_selection_risk {
|
||||||
&& matches!(reason, "upper_limit" | "lower_limit"))
|
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6081,7 +6079,7 @@ impl PlatformExprStrategy {
|
|||||||
ctx: &StrategyContext<'_>,
|
ctx: &StrategyContext<'_>,
|
||||||
date: NaiveDate,
|
date: NaiveDate,
|
||||||
factor_date: NaiveDate,
|
factor_date: NaiveDate,
|
||||||
defer_limit_state_selection_risk: bool,
|
_defer_all_selection_risk: bool,
|
||||||
) -> Vec<FidcRiskDecisionAudit> {
|
) -> Vec<FidcRiskDecisionAudit> {
|
||||||
let mut decisions = Vec::new();
|
let mut decisions = Vec::new();
|
||||||
for factor in ctx.data.factor_snapshots_on(factor_date) {
|
for factor in ctx.data.factor_snapshots_on(factor_date) {
|
||||||
@@ -6101,11 +6099,6 @@ impl PlatformExprStrategy {
|
|||||||
ctx.data.instrument(&factor.symbol),
|
ctx.data.instrument(&factor.symbol),
|
||||||
&self.config.risk_config,
|
&self.config.risk_config,
|
||||||
) {
|
) {
|
||||||
if defer_limit_state_selection_risk
|
|
||||||
&& matches!(decision.rule_code.as_str(), "upper_limit" | "lower_limit")
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
decisions.push(decision);
|
decisions.push(decision);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6464,13 +6457,13 @@ impl PlatformExprStrategy {
|
|||||||
band_high: f64,
|
band_high: f64,
|
||||||
limit: usize,
|
limit: usize,
|
||||||
) -> Result<(Vec<String>, Vec<String>, Vec<FidcRiskDecisionAudit>), BacktestError> {
|
) -> Result<(Vec<String>, Vec<String>, Vec<FidcRiskDecisionAudit>), BacktestError> {
|
||||||
let defer_limit_state_selection_risk = ctx.is_lagged_execution();
|
let defer_all_selection_risk = ctx.is_lagged_execution();
|
||||||
let universe = if defer_limit_state_selection_risk {
|
let universe = if defer_all_selection_risk {
|
||||||
self.selectable_universe_on_with_options(ctx, date, universe_factor_date, true)
|
self.selectable_universe_on_with_options(ctx, date, universe_factor_date, true)
|
||||||
} else {
|
} else {
|
||||||
self.selectable_universe_on(ctx, date, universe_factor_date)
|
self.selectable_universe_on(ctx, date, universe_factor_date)
|
||||||
};
|
};
|
||||||
let risk_decisions = if defer_limit_state_selection_risk {
|
let risk_decisions = if defer_all_selection_risk {
|
||||||
self.selection_risk_decisions_with_options(ctx, date, universe_factor_date, true)
|
self.selection_risk_decisions_with_options(ctx, date, universe_factor_date, true)
|
||||||
} else {
|
} else {
|
||||||
self.selection_risk_decisions(ctx, date, universe_factor_date)
|
self.selection_risk_decisions(ctx, date, universe_factor_date)
|
||||||
@@ -8950,8 +8943,22 @@ mod tests {
|
|||||||
|
|
||||||
let default_strategy =
|
let default_strategy =
|
||||||
PlatformExprStrategy::new(PlatformExprStrategyConfig::microcap_rotation());
|
PlatformExprStrategy::new(PlatformExprStrategyConfig::microcap_rotation());
|
||||||
|
assert_eq!(
|
||||||
|
default_strategy
|
||||||
|
.selectable_universe_on(&ctx, date, date)
|
||||||
|
.len(),
|
||||||
|
1
|
||||||
|
);
|
||||||
assert!(
|
assert!(
|
||||||
default_strategy
|
default_strategy
|
||||||
|
.selection_risk_decisions(&ctx, date, date)
|
||||||
|
.is_empty()
|
||||||
|
);
|
||||||
|
let mut selection_cfg = PlatformExprStrategyConfig::microcap_rotation();
|
||||||
|
selection_cfg.risk_config.static_rules.reject_kcb_selection = true;
|
||||||
|
let selection_strategy = PlatformExprStrategy::new(selection_cfg);
|
||||||
|
assert!(
|
||||||
|
selection_strategy
|
||||||
.selectable_universe_on(&ctx, date, date)
|
.selectable_universe_on(&ctx, date, date)
|
||||||
.is_empty()
|
.is_empty()
|
||||||
);
|
);
|
||||||
@@ -8962,11 +8969,13 @@ mod tests {
|
|||||||
date,
|
date,
|
||||||
5,
|
5,
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(risk_diagnostics.is_empty(), "{risk_diagnostics:?}");
|
||||||
risk_diagnostics
|
let risk_decisions = selection_strategy.selection_risk_decisions(&ctx, date, date);
|
||||||
.iter()
|
let risk_diagnostics = PlatformExprStrategy::selection_risk_decision_diagnostics(
|
||||||
.any(|item| item.contains("risk_decisions selection_total=1 by_rule=kcb:1")),
|
&risk_decisions,
|
||||||
"{risk_diagnostics:?}"
|
date,
|
||||||
|
date,
|
||||||
|
5,
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
risk_diagnostics
|
risk_diagnostics
|
||||||
@@ -9118,12 +9127,41 @@ mod tests {
|
|||||||
|
|
||||||
let default_strategy =
|
let default_strategy =
|
||||||
PlatformExprStrategy::new(PlatformExprStrategyConfig::microcap_rotation());
|
PlatformExprStrategy::new(PlatformExprStrategyConfig::microcap_rotation());
|
||||||
|
assert_eq!(
|
||||||
|
default_strategy
|
||||||
|
.selectable_universe_on(&ctx, date, date)
|
||||||
|
.len(),
|
||||||
|
symbols.len()
|
||||||
|
);
|
||||||
assert!(
|
assert!(
|
||||||
default_strategy
|
default_strategy
|
||||||
|
.selection_risk_decisions(&ctx, date, date)
|
||||||
|
.is_empty()
|
||||||
|
);
|
||||||
|
let mut strict_cfg = PlatformExprStrategyConfig::microcap_rotation();
|
||||||
|
strict_cfg
|
||||||
|
.risk_config
|
||||||
|
.static_rules
|
||||||
|
.reject_upper_limit_selection = true;
|
||||||
|
strict_cfg
|
||||||
|
.risk_config
|
||||||
|
.static_rules
|
||||||
|
.reject_lower_limit_selection = true;
|
||||||
|
strict_cfg
|
||||||
|
.risk_config
|
||||||
|
.static_rules
|
||||||
|
.reject_new_listing_selection = true;
|
||||||
|
strict_cfg
|
||||||
|
.risk_config
|
||||||
|
.static_rules
|
||||||
|
.reject_inactive_selection = true;
|
||||||
|
let strict_strategy = PlatformExprStrategy::new(strict_cfg);
|
||||||
|
assert!(
|
||||||
|
strict_strategy
|
||||||
.selectable_universe_on(&ctx, date, date)
|
.selectable_universe_on(&ctx, date, date)
|
||||||
.is_empty()
|
.is_empty()
|
||||||
);
|
);
|
||||||
let rejected_rules = default_strategy
|
let rejected_rules = strict_strategy
|
||||||
.selection_risk_decisions(&ctx, date, date)
|
.selection_risk_decisions(&ctx, date, date)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|decision| (decision.symbol, decision.rule_code))
|
.map(|decision| (decision.symbol, decision.rule_code))
|
||||||
@@ -9254,10 +9292,11 @@ mod tests {
|
|||||||
|
|
||||||
let default_strategy =
|
let default_strategy =
|
||||||
PlatformExprStrategy::new(PlatformExprStrategyConfig::microcap_rotation());
|
PlatformExprStrategy::new(PlatformExprStrategyConfig::microcap_rotation());
|
||||||
assert!(
|
assert_eq!(
|
||||||
default_strategy
|
default_strategy
|
||||||
.selectable_universe_on(&ctx, date, date)
|
.selectable_universe_on(&ctx, date, date)
|
||||||
.is_empty()
|
.len(),
|
||||||
|
1
|
||||||
);
|
);
|
||||||
let default_stock = default_strategy
|
let default_stock = default_strategy
|
||||||
.stock_state(&ctx, date, symbol)
|
.stock_state(&ctx, date, symbol)
|
||||||
@@ -15239,6 +15278,8 @@ mod tests {
|
|||||||
cfg.selection_limit_expr = "1".to_string();
|
cfg.selection_limit_expr = "1".to_string();
|
||||||
cfg.stock_filter_expr = "close > 0".to_string();
|
cfg.stock_filter_expr = "close > 0".to_string();
|
||||||
cfg.exposure_expr = "1.0".to_string();
|
cfg.exposure_expr = "1.0".to_string();
|
||||||
|
cfg.risk_config.static_rules.reject_st_selection = true;
|
||||||
|
cfg.risk_config.static_rules.reject_paused_selection = true;
|
||||||
let mut strategy = PlatformExprStrategy::new(cfg);
|
let mut strategy = PlatformExprStrategy::new(cfg);
|
||||||
|
|
||||||
let decision = strategy.on_day(&ctx).expect("platform decision");
|
let decision = strategy.on_day(&ctx).expect("platform decision");
|
||||||
@@ -15247,7 +15288,7 @@ mod tests {
|
|||||||
decision
|
decision
|
||||||
.diagnostics
|
.diagnostics
|
||||||
.iter()
|
.iter()
|
||||||
.any(|item| item == "selected_symbols="),
|
.any(|item| item == "selected_symbols=600462.SH"),
|
||||||
"{:?}",
|
"{:?}",
|
||||||
decision.diagnostics
|
decision.diagnostics
|
||||||
);
|
);
|
||||||
@@ -15260,11 +15301,11 @@ mod tests {
|
|||||||
decision.diagnostics
|
decision.diagnostics
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
decision.order_intents.iter().all(|intent| !matches!(
|
decision.order_intents.iter().any(|intent| matches!(
|
||||||
intent,
|
intent,
|
||||||
OrderIntent::Shares { symbol, quantity, .. }
|
OrderIntent::Shares { symbol, quantity, .. }
|
||||||
if symbol == candidate_symbol && *quantity > 0
|
if symbol == candidate_symbol && *quantity > 0
|
||||||
) && !matches!(
|
) || matches!(
|
||||||
intent,
|
intent,
|
||||||
OrderIntent::Value { symbol, value, .. }
|
OrderIntent::Value { symbol, value, .. }
|
||||||
if symbol == candidate_symbol && *value > 0.0
|
if symbol == candidate_symbol && *value > 0.0
|
||||||
@@ -15695,7 +15736,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn platform_strategy_baseline_risk_filter_cannot_be_disabled_by_empty_exclude() {
|
fn platform_strategy_empty_exclude_keeps_buy_risk_without_selection_filtering() {
|
||||||
let date = d(2024, 12, 31);
|
let date = d(2024, 12, 31);
|
||||||
let symbol = "002494.SZ";
|
let symbol = "002494.SZ";
|
||||||
let data = DataSet::from_components(
|
let data = DataSet::from_components(
|
||||||
@@ -15796,7 +15837,8 @@ mod tests {
|
|||||||
let selected = strategy.selectable_universe_on(&ctx, date, date);
|
let selected = strategy.selectable_universe_on(&ctx, date, date);
|
||||||
|
|
||||||
assert_eq!(rejection.as_deref(), Some("paused"));
|
assert_eq!(rejection.as_deref(), Some("paused"));
|
||||||
assert!(selected.is_empty());
|
assert_eq!(selected.len(), 1);
|
||||||
|
assert_eq!(selected[0].symbol, symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sample_calendar() -> TradingCalendar {
|
fn sample_calendar() -> TradingCalendar {
|
||||||
|
|||||||
@@ -45,27 +45,27 @@ pub struct StaticRiskRuleConfig {
|
|||||||
impl Default for StaticRiskRuleConfig {
|
impl Default for StaticRiskRuleConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
reject_st_selection: true,
|
reject_st_selection: false,
|
||||||
reject_st_buy: true,
|
reject_st_buy: true,
|
||||||
reject_star_st_selection: true,
|
reject_star_st_selection: false,
|
||||||
reject_star_st_buy: true,
|
reject_star_st_buy: true,
|
||||||
reject_paused_selection: true,
|
reject_paused_selection: false,
|
||||||
reject_paused_buy: true,
|
reject_paused_buy: true,
|
||||||
reject_paused_sell: true,
|
reject_paused_sell: true,
|
||||||
reject_inactive_selection: true,
|
reject_inactive_selection: false,
|
||||||
reject_inactive_buy: true,
|
reject_inactive_buy: true,
|
||||||
reject_inactive_sell: true,
|
reject_inactive_sell: true,
|
||||||
reject_new_listing_selection: true,
|
reject_new_listing_selection: false,
|
||||||
reject_new_listing_buy: true,
|
reject_new_listing_buy: true,
|
||||||
reject_kcb_selection: true,
|
reject_kcb_selection: false,
|
||||||
reject_kcb_buy: true,
|
reject_kcb_buy: true,
|
||||||
reject_bjse_selection: true,
|
reject_bjse_selection: false,
|
||||||
reject_bjse_buy: true,
|
reject_bjse_buy: true,
|
||||||
reject_one_yuan_selection: true,
|
reject_one_yuan_selection: false,
|
||||||
reject_one_yuan_buy: true,
|
reject_one_yuan_buy: true,
|
||||||
respect_allow_buy_sell: true,
|
respect_allow_buy_sell: true,
|
||||||
reject_upper_limit_selection: true,
|
reject_upper_limit_selection: false,
|
||||||
reject_lower_limit_selection: true,
|
reject_lower_limit_selection: false,
|
||||||
reject_upper_limit_buy: true,
|
reject_upper_limit_buy: true,
|
||||||
reject_lower_limit_sell: true,
|
reject_lower_limit_sell: true,
|
||||||
forbid_same_day_rebuy_after_sell: true,
|
forbid_same_day_rebuy_after_sell: true,
|
||||||
@@ -255,11 +255,6 @@ impl ChinaAShareRiskControl {
|
|||||||
) {
|
) {
|
||||||
return Some(reason);
|
return Some(reason);
|
||||||
}
|
}
|
||||||
if config.static_rules.respect_allow_buy_sell
|
|
||||||
&& (!candidate.allow_buy || !candidate.allow_sell)
|
|
||||||
{
|
|
||||||
return Some("trade_disabled");
|
|
||||||
}
|
|
||||||
let selection_price = market.price(PriceField::Last);
|
let selection_price = market.price(PriceField::Last);
|
||||||
if config.static_rules.reject_upper_limit_selection
|
if config.static_rules.reject_upper_limit_selection
|
||||||
&& market.is_at_upper_limit_price(selection_price)
|
&& market.is_at_upper_limit_price(selection_price)
|
||||||
@@ -719,7 +714,6 @@ fn missing_single_field_rejected(
|
|||||||
|| config.static_rules.reject_one_yuan_selection
|
|| config.static_rules.reject_one_yuan_selection
|
||||||
|| config.static_rules.reject_upper_limit_selection
|
|| config.static_rules.reject_upper_limit_selection
|
||||||
|| config.static_rules.reject_lower_limit_selection
|
|| config.static_rules.reject_lower_limit_selection
|
||||||
|| config.static_rules.respect_allow_buy_sell
|
|
||||||
}
|
}
|
||||||
RiskCheckScope::Buy => {
|
RiskCheckScope::Buy => {
|
||||||
config.static_rules.reject_st_buy
|
config.static_rules.reject_st_buy
|
||||||
@@ -893,13 +887,19 @@ mod tests {
|
|||||||
let default_reason =
|
let default_reason =
|
||||||
ChinaAShareRiskControl::selection_rejection_reason(date, &candidate, &market, None);
|
ChinaAShareRiskControl::selection_rejection_reason(date, &candidate, &market, None);
|
||||||
let mut config = FidcRiskControlConfig::default();
|
let mut config = FidcRiskControlConfig::default();
|
||||||
|
config.static_rules.reject_kcb_selection = true;
|
||||||
|
let enabled_selection_reason =
|
||||||
|
ChinaAShareRiskControl::selection_rejection_reason_with_config(
|
||||||
|
date, &candidate, &market, None, &config,
|
||||||
|
);
|
||||||
config.static_rules.reject_kcb_selection = false;
|
config.static_rules.reject_kcb_selection = false;
|
||||||
config.static_rules.reject_kcb_buy = false;
|
config.static_rules.reject_kcb_buy = false;
|
||||||
let configured_reason = ChinaAShareRiskControl::buy_rejection_reason_with_config(
|
let configured_reason = ChinaAShareRiskControl::buy_rejection_reason_with_config(
|
||||||
date, &candidate, &market, None, 6.27, &config,
|
date, &candidate, &market, None, 6.27, &config,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(default_reason, Some("kcb"));
|
assert_eq!(default_reason, None);
|
||||||
|
assert_eq!(enabled_selection_reason, Some("kcb"));
|
||||||
assert_eq!(configured_reason, None);
|
assert_eq!(configured_reason, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1008,6 +1008,10 @@ mod tests {
|
|||||||
let default_buy =
|
let default_buy =
|
||||||
ChinaAShareRiskControl::buy_rejection_reason(date, &candidate, &market, None, 6.27);
|
ChinaAShareRiskControl::buy_rejection_reason(date, &candidate, &market, None, 6.27);
|
||||||
let mut config = FidcRiskControlConfig::default();
|
let mut config = FidcRiskControlConfig::default();
|
||||||
|
config.static_rules.reject_bjse_selection = true;
|
||||||
|
let enabled_selection = ChinaAShareRiskControl::selection_rejection_reason_with_config(
|
||||||
|
date, &candidate, &market, None, &config,
|
||||||
|
);
|
||||||
config.static_rules.reject_bjse_selection = false;
|
config.static_rules.reject_bjse_selection = false;
|
||||||
config.static_rules.reject_bjse_buy = false;
|
config.static_rules.reject_bjse_buy = false;
|
||||||
let configured_selection = ChinaAShareRiskControl::selection_rejection_reason_with_config(
|
let configured_selection = ChinaAShareRiskControl::selection_rejection_reason_with_config(
|
||||||
@@ -1017,8 +1021,9 @@ mod tests {
|
|||||||
date, &candidate, &market, None, 6.27, &config,
|
date, &candidate, &market, None, 6.27, &config,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(default_selection, Some("bjse"));
|
assert_eq!(default_selection, None);
|
||||||
assert_eq!(default_buy, Some("bjse"));
|
assert_eq!(default_buy, Some("bjse"));
|
||||||
|
assert_eq!(enabled_selection, Some("bjse"));
|
||||||
assert_eq!(configured_selection, None);
|
assert_eq!(configured_selection, None);
|
||||||
assert_eq!(configured_buy, None);
|
assert_eq!(configured_buy, None);
|
||||||
}
|
}
|
||||||
@@ -1030,13 +1035,11 @@ mod tests {
|
|||||||
candidate.symbol = "688506.SH".to_string();
|
candidate.symbol = "688506.SH".to_string();
|
||||||
candidate.risk_level_code = Some("missing_risk_state".to_string());
|
candidate.risk_level_code = Some("missing_risk_state".to_string());
|
||||||
let market = market(date, 6.27, 5.63);
|
let market = market(date, 6.27, 5.63);
|
||||||
|
let mut config = FidcRiskControlConfig::default();
|
||||||
|
config.static_rules.reject_kcb_selection = true;
|
||||||
|
|
||||||
let decision = ChinaAShareRiskControl::selection_rejection_decision_with_config(
|
let decision = ChinaAShareRiskControl::selection_rejection_decision_with_config(
|
||||||
date,
|
date, &candidate, &market, None, &config,
|
||||||
&candidate,
|
|
||||||
&market,
|
|
||||||
None,
|
|
||||||
&FidcRiskControlConfig::default(),
|
|
||||||
)
|
)
|
||||||
.expect("kcb selection rejection");
|
.expect("kcb selection rejection");
|
||||||
|
|
||||||
@@ -1051,9 +1054,13 @@ mod tests {
|
|||||||
candidate.allow_sell = true;
|
candidate.allow_sell = true;
|
||||||
candidate.risk_level_code = Some("inactive_or_delisted".to_string());
|
candidate.risk_level_code = Some("inactive_or_delisted".to_string());
|
||||||
let market = market(date, 6.27, 5.63);
|
let market = market(date, 6.27, 5.63);
|
||||||
|
let mut config = FidcRiskControlConfig::default();
|
||||||
|
config.static_rules.reject_inactive_selection = true;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ChinaAShareRiskControl::selection_rejection_reason(date, &candidate, &market, None),
|
ChinaAShareRiskControl::selection_rejection_reason_with_config(
|
||||||
|
date, &candidate, &market, None, &config
|
||||||
|
),
|
||||||
Some("inactive_or_delisted")
|
Some("inactive_or_delisted")
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1189,6 +1196,8 @@ mod tests {
|
|||||||
Some("missing_risk_state:upper_limit_price,lower_limit_price".to_string());
|
Some("missing_risk_state:upper_limit_price,lower_limit_price".to_string());
|
||||||
let market = market(date, 6.27, 5.63);
|
let market = market(date, 6.27, 5.63);
|
||||||
let mut config = FidcRiskControlConfig::default();
|
let mut config = FidcRiskControlConfig::default();
|
||||||
|
config.static_rules.reject_upper_limit_selection = true;
|
||||||
|
config.static_rules.reject_lower_limit_selection = true;
|
||||||
config.static_rules.reject_upper_limit_selection = false;
|
config.static_rules.reject_upper_limit_selection = false;
|
||||||
|
|
||||||
let upper_disabled_reason = ChinaAShareRiskControl::selection_rejection_reason_with_config(
|
let upper_disabled_reason = ChinaAShareRiskControl::selection_rejection_reason_with_config(
|
||||||
@@ -1282,12 +1291,17 @@ mod tests {
|
|||||||
let default_reason =
|
let default_reason =
|
||||||
ChinaAShareRiskControl::selection_rejection_reason(date, &candidate, &market, None);
|
ChinaAShareRiskControl::selection_rejection_reason(date, &candidate, &market, None);
|
||||||
let mut config = FidcRiskControlConfig::default();
|
let mut config = FidcRiskControlConfig::default();
|
||||||
|
config.static_rules.reject_upper_limit_selection = true;
|
||||||
|
let enabled_reason = ChinaAShareRiskControl::selection_rejection_reason_with_config(
|
||||||
|
date, &candidate, &market, None, &config,
|
||||||
|
);
|
||||||
config.static_rules.reject_upper_limit_selection = false;
|
config.static_rules.reject_upper_limit_selection = false;
|
||||||
let configured_reason = ChinaAShareRiskControl::selection_rejection_reason_with_config(
|
let configured_reason = ChinaAShareRiskControl::selection_rejection_reason_with_config(
|
||||||
date, &candidate, &market, None, &config,
|
date, &candidate, &market, None, &config,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(default_reason, Some("upper_limit"));
|
assert_eq!(default_reason, None);
|
||||||
|
assert_eq!(enabled_reason, Some("upper_limit"));
|
||||||
assert_eq!(configured_reason, None);
|
assert_eq!(configured_reason, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1300,12 +1314,17 @@ mod tests {
|
|||||||
let default_reason =
|
let default_reason =
|
||||||
ChinaAShareRiskControl::selection_rejection_reason(date, &candidate, &market, None);
|
ChinaAShareRiskControl::selection_rejection_reason(date, &candidate, &market, None);
|
||||||
let mut config = FidcRiskControlConfig::default();
|
let mut config = FidcRiskControlConfig::default();
|
||||||
|
config.static_rules.reject_lower_limit_selection = true;
|
||||||
|
let enabled_reason = ChinaAShareRiskControl::selection_rejection_reason_with_config(
|
||||||
|
date, &candidate, &market, None, &config,
|
||||||
|
);
|
||||||
config.static_rules.reject_lower_limit_selection = false;
|
config.static_rules.reject_lower_limit_selection = false;
|
||||||
let configured_reason = ChinaAShareRiskControl::selection_rejection_reason_with_config(
|
let configured_reason = ChinaAShareRiskControl::selection_rejection_reason_with_config(
|
||||||
date, &candidate, &market, None, &config,
|
date, &candidate, &market, None, &config,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(default_reason, Some("lower_limit"));
|
assert_eq!(default_reason, None);
|
||||||
|
assert_eq!(enabled_reason, Some("lower_limit"));
|
||||||
assert_eq!(configured_reason, None);
|
assert_eq!(configured_reason, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2978,30 +2978,25 @@ mod tests {
|
|||||||
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)
|
||||||
.expect("default selection");
|
.expect("default selection");
|
||||||
assert!(default_selected.is_empty());
|
assert_eq!(default_selected, vec![symbol.to_string()]);
|
||||||
let default_risk_decisions = default_strategy.selection_risk_decisions(&ctx, dates[2]);
|
let default_risk_decisions = default_strategy.selection_risk_decisions(&ctx, dates[2]);
|
||||||
assert_eq!(default_risk_decisions.len(), 1);
|
assert!(default_risk_decisions.is_empty());
|
||||||
assert_eq!(default_risk_decisions[0].symbol, symbol);
|
|
||||||
assert_eq!(default_risk_decisions[0].rule_code, "kcb");
|
let mut cfg = default_cfg;
|
||||||
|
cfg.risk_config.static_rules.reject_kcb_selection = true;
|
||||||
|
let strict_strategy = OmniMicroCapStrategy::new(cfg);
|
||||||
|
let (selected, _) = strict_strategy
|
||||||
|
.select_symbols(&ctx, dates[2], 0.0, 100.0)
|
||||||
|
.expect("strict selection");
|
||||||
|
assert!(selected.is_empty());
|
||||||
|
let strict_risk_decisions = strict_strategy.selection_risk_decisions(&ctx, dates[2]);
|
||||||
|
assert_eq!(strict_risk_decisions.len(), 1);
|
||||||
|
assert_eq!(strict_risk_decisions[0].symbol, symbol);
|
||||||
|
assert_eq!(strict_risk_decisions[0].rule_code, "kcb");
|
||||||
assert!(
|
assert!(
|
||||||
default_risk_decisions[0]
|
strict_risk_decisions[0]
|
||||||
.diagnostic_line()
|
.diagnostic_line()
|
||||||
.starts_with("risk_decision=")
|
.starts_with("risk_decision=")
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut cfg = default_cfg;
|
|
||||||
cfg.risk_config.static_rules.reject_kcb_selection = false;
|
|
||||||
cfg.risk_config.static_rules.reject_kcb_buy = false;
|
|
||||||
let configured_strategy = OmniMicroCapStrategy::new(cfg);
|
|
||||||
let (selected, _) = configured_strategy
|
|
||||||
.select_symbols(&ctx, dates[2], 0.0, 100.0)
|
|
||||||
.expect("configured selection");
|
|
||||||
assert!(
|
|
||||||
configured_strategy
|
|
||||||
.selection_risk_decisions(&ctx, dates[2])
|
|
||||||
.is_empty()
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(selected, vec![symbol.to_string()]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,8 +119,8 @@ pub struct StrategyAiOptimizeRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_THREE_YEAR_RETURN_TARGET_PROMPT: &str = "默认收益目标:用户没有明确指定更高收益阈值时,三年回测区间策略总收益 >= 150% 即视为满足收益目标;达到该阈值后可以继续优化夏普、回撤、换手和稳定性,但不得把已达标策略判为失败或为了追更高收益破坏无未来数据、持仓数量和同条件对账合同。";
|
const DEFAULT_THREE_YEAR_RETURN_TARGET_PROMPT: &str = "默认收益目标:用户没有明确指定更高收益阈值时,三年回测区间策略总收益 >= 150% 即视为满足收益目标;达到该阈值后可以继续优化夏普、回撤、换手和稳定性,但不得把已达标策略判为失败或为了追更高收益破坏无未来数据、持仓数量和同条件对账合同。";
|
||||||
const DEFAULT_RISK_POLICY_DSL_PROMPT: &str = "reject_st_selection=true、reject_st_buy=true、reject_star_st_selection=true、reject_star_st_buy=true、reject_paused_selection=true、reject_paused_buy=true、reject_paused_sell=true、reject_inactive_selection=true、reject_inactive_buy=true、reject_inactive_sell=true、reject_new_listing_selection=true、reject_new_listing_buy=true、reject_kcb_selection=true、reject_kcb_buy=true、reject_bjse_selection=true、reject_bjse_buy=true、reject_one_yuan_selection=true、reject_one_yuan_buy=true、respect_allow_buy_sell=true、reject_upper_limit_selection=true、reject_lower_limit_selection=true、reject_upper_limit_buy=true、reject_lower_limit_sell=true、forbid_same_day_rebuy_after_sell=true、blacklist_enabled=true、volume_limit_enabled=true、liquidity_limit_enabled=true、volume_percent=0.25、commission_rate=0.0003、minimum_commission=5、stamp_tax_rate_before_change=0.001、stamp_tax_rate_after_change=0.0005、stamp_tax_change_date=\"2023-08-28\"";
|
const DEFAULT_RISK_POLICY_DSL_PROMPT: &str = "reject_st_selection=false、reject_st_buy=true、reject_star_st_selection=false、reject_star_st_buy=true、reject_paused_selection=false、reject_paused_buy=true、reject_paused_sell=true、reject_inactive_selection=false、reject_inactive_buy=true、reject_inactive_sell=true、reject_new_listing_selection=false、reject_new_listing_buy=true、reject_kcb_selection=false、reject_kcb_buy=true、reject_bjse_selection=false、reject_bjse_buy=true、reject_one_yuan_selection=false、reject_one_yuan_buy=true、respect_allow_buy_sell=true、reject_upper_limit_selection=false、reject_lower_limit_selection=false、reject_upper_limit_buy=true、reject_lower_limit_sell=true、forbid_same_day_rebuy_after_sell=true、blacklist_enabled=true、volume_limit_enabled=true、liquidity_limit_enabled=true、volume_percent=0.25、commission_rate=0.0003、minimum_commission=5、stamp_tax_rate_before_change=0.001、stamp_tax_rate_after_change=0.0005、stamp_tax_change_date=\"2023-08-28\"";
|
||||||
const DEFAULT_RISK_POLICY_DSL_CODE: &str = "reject_st_selection=true, reject_st_buy=true, reject_star_st_selection=true, reject_star_st_buy=true, reject_paused_selection=true, reject_paused_buy=true, reject_paused_sell=true, reject_inactive_selection=true, reject_inactive_buy=true, reject_inactive_sell=true, reject_new_listing_selection=true, reject_new_listing_buy=true, reject_kcb_selection=true, reject_kcb_buy=true, reject_bjse_selection=true, reject_bjse_buy=true, reject_one_yuan_selection=true, reject_one_yuan_buy=true, respect_allow_buy_sell=true, reject_upper_limit_selection=true, reject_lower_limit_selection=true, reject_upper_limit_buy=true, reject_lower_limit_sell=true, forbid_same_day_rebuy_after_sell=true, blacklist_enabled=true, volume_limit_enabled=true, liquidity_limit_enabled=true, volume_percent=0.25, commission_rate=0.0003, minimum_commission=5, stamp_tax_rate_before_change=0.001, stamp_tax_rate_after_change=0.0005, stamp_tax_change_date=\"2023-08-28\"";
|
const DEFAULT_RISK_POLICY_DSL_CODE: &str = "reject_st_selection=false, reject_st_buy=true, reject_star_st_selection=false, reject_star_st_buy=true, reject_paused_selection=false, reject_paused_buy=true, reject_paused_sell=true, reject_inactive_selection=false, reject_inactive_buy=true, reject_inactive_sell=true, reject_new_listing_selection=false, reject_new_listing_buy=true, reject_kcb_selection=false, reject_kcb_buy=true, reject_bjse_selection=false, reject_bjse_buy=true, reject_one_yuan_selection=false, reject_one_yuan_buy=true, respect_allow_buy_sell=true, reject_upper_limit_selection=false, reject_lower_limit_selection=false, reject_upper_limit_buy=true, reject_lower_limit_sell=true, forbid_same_day_rebuy_after_sell=true, blacklist_enabled=true, volume_limit_enabled=true, liquidity_limit_enabled=true, volume_percent=0.25, commission_rate=0.0003, minimum_commission=5, stamp_tax_rate_before_change=0.001, stamp_tax_rate_after_change=0.0005, stamp_tax_change_date=\"2023-08-28\"";
|
||||||
|
|
||||||
pub fn built_in_strategy_manual() -> StrategyAiManual {
|
pub fn built_in_strategy_manual() -> StrategyAiManual {
|
||||||
StrategyAiManual {
|
StrategyAiManual {
|
||||||
|
|||||||
@@ -396,13 +396,16 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let selector = DynamicMarketCapBandSelector::new(2000.0, 7.0, 10.0, 0.0, 10, 0.0, 0.0, 0.0);
|
let selector = DynamicMarketCapBandSelector::new(2000.0, 7.0, 10.0, 0.0, 10, 0.0, 0.0, 0.0);
|
||||||
|
let mut risk_config = FidcRiskControlConfig::default();
|
||||||
|
risk_config.static_rules.reject_st_selection = true;
|
||||||
|
risk_config.static_rules.reject_kcb_selection = true;
|
||||||
let (_selected, diagnostics) = selector.select_with_diagnostics(&SelectionContext {
|
let (_selected, diagnostics) = selector.select_with_diagnostics(&SelectionContext {
|
||||||
decision_date: d(),
|
decision_date: d(),
|
||||||
benchmark: &benchmark(),
|
benchmark: &benchmark(),
|
||||||
reference_level: 2000.0,
|
reference_level: 2000.0,
|
||||||
data: &data,
|
data: &data,
|
||||||
dynamic_universe: None,
|
dynamic_universe: None,
|
||||||
risk_config: Some(&FidcRiskControlConfig::default()),
|
risk_config: Some(&risk_config),
|
||||||
});
|
});
|
||||||
|
|
||||||
let rules = diagnostics
|
let rules = diagnostics
|
||||||
@@ -450,13 +453,16 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let selector = DynamicMarketCapBandSelector::new(2000.0, 7.0, 10.0, 0.0, 10, 0.0, 0.0, 0.0);
|
let selector = DynamicMarketCapBandSelector::new(2000.0, 7.0, 10.0, 0.0, 10, 0.0, 0.0, 0.0);
|
||||||
|
let mut risk_config = FidcRiskControlConfig::default();
|
||||||
|
risk_config.static_rules.reject_st_selection = true;
|
||||||
|
risk_config.static_rules.reject_kcb_selection = true;
|
||||||
let (selected, diagnostics) = selector.select_with_diagnostics(&SelectionContext {
|
let (selected, diagnostics) = selector.select_with_diagnostics(&SelectionContext {
|
||||||
decision_date: d(),
|
decision_date: d(),
|
||||||
benchmark: &benchmark(),
|
benchmark: &benchmark(),
|
||||||
reference_level: 2000.0,
|
reference_level: 2000.0,
|
||||||
data: &data,
|
data: &data,
|
||||||
dynamic_universe: None,
|
dynamic_universe: None,
|
||||||
risk_config: Some(&FidcRiskControlConfig::default()),
|
risk_config: Some(&risk_config),
|
||||||
});
|
});
|
||||||
|
|
||||||
let selected_symbols = selected
|
let selected_symbols = selected
|
||||||
|
|||||||
Reference in New Issue
Block a user