补充缺失风控状态拒绝
This commit is contained in:
@@ -300,6 +300,9 @@ impl ChinaAShareRiskControl {
|
|||||||
{
|
{
|
||||||
return Some(reason);
|
return Some(reason);
|
||||||
}
|
}
|
||||||
|
if Self::missing_risk_state_rejected(candidate, config, scope) {
|
||||||
|
return Some("missing_risk_state");
|
||||||
|
}
|
||||||
let reject_paused = match scope {
|
let reject_paused = match scope {
|
||||||
RiskCheckScope::Selection => config.static_rules.reject_paused_selection,
|
RiskCheckScope::Selection => config.static_rules.reject_paused_selection,
|
||||||
RiskCheckScope::Buy => config.static_rules.reject_paused_buy,
|
RiskCheckScope::Buy => config.static_rules.reject_paused_buy,
|
||||||
@@ -343,6 +346,45 @@ impl ChinaAShareRiskControl {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn missing_risk_state_rejected(
|
||||||
|
candidate: &CandidateEligibility,
|
||||||
|
config: &FidcRiskControlConfig,
|
||||||
|
scope: RiskCheckScope,
|
||||||
|
) -> bool {
|
||||||
|
let Some(code) = candidate
|
||||||
|
.risk_level_code
|
||||||
|
.as_deref()
|
||||||
|
.map(str::trim)
|
||||||
|
.filter(|value| value.starts_with("missing_risk_state"))
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if code.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
match scope {
|
||||||
|
RiskCheckScope::Selection => {
|
||||||
|
config.static_rules.reject_st_selection
|
||||||
|
|| config.static_rules.reject_paused_selection
|
||||||
|
|| config.static_rules.reject_inactive_selection
|
||||||
|
|| config.static_rules.reject_new_listing_selection
|
||||||
|
|| config.static_rules.reject_kcb_selection
|
||||||
|
|| config.static_rules.reject_one_yuan_selection
|
||||||
|
|| config.static_rules.respect_allow_buy_sell
|
||||||
|
}
|
||||||
|
RiskCheckScope::Buy => {
|
||||||
|
config.static_rules.reject_st_buy
|
||||||
|
|| config.static_rules.reject_paused_buy
|
||||||
|
|| config.static_rules.reject_inactive_buy
|
||||||
|
|| config.static_rules.reject_new_listing_buy
|
||||||
|
|| config.static_rules.reject_kcb_buy
|
||||||
|
|| config.static_rules.reject_one_yuan_buy
|
||||||
|
|| config.static_rules.respect_allow_buy_sell
|
||||||
|
}
|
||||||
|
RiskCheckScope::Sell => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn buy_rejection_reason(
|
pub fn buy_rejection_reason(
|
||||||
date: NaiveDate,
|
date: NaiveDate,
|
||||||
candidate: &CandidateEligibility,
|
candidate: &CandidateEligibility,
|
||||||
@@ -598,6 +640,64 @@ mod tests {
|
|||||||
assert_eq!(configured_reason, None);
|
assert_eq!(configured_reason, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_risk_state_rejects_selection_and_buy_when_static_filters_enabled() {
|
||||||
|
let date = d(2025, 1, 2);
|
||||||
|
let mut candidate = candidate(date);
|
||||||
|
candidate.risk_level_code = Some("missing_risk_state:is_st,paused,allow_buy".to_string());
|
||||||
|
let market = market(date, 6.27, 5.63);
|
||||||
|
|
||||||
|
let selection_reason =
|
||||||
|
ChinaAShareRiskControl::selection_rejection_reason(date, &candidate, &market, None);
|
||||||
|
let buy_reason =
|
||||||
|
ChinaAShareRiskControl::buy_rejection_reason(date, &candidate, &market, None, 6.27);
|
||||||
|
let sell_reason = ChinaAShareRiskControl::sell_rejection_reason(
|
||||||
|
date,
|
||||||
|
&candidate,
|
||||||
|
&market,
|
||||||
|
None,
|
||||||
|
Some(&position(d(2024, 12, 31))),
|
||||||
|
6.27,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(selection_reason, Some("missing_risk_state"));
|
||||||
|
assert_eq!(buy_reason, Some("missing_risk_state"));
|
||||||
|
assert_eq!(sell_reason, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_risk_state_can_be_relaxed_by_disabling_static_filters() {
|
||||||
|
let date = d(2025, 1, 2);
|
||||||
|
let mut candidate = candidate(date);
|
||||||
|
candidate.allow_sell = true;
|
||||||
|
candidate.risk_level_code = Some("missing_risk_state:is_st,paused,allow_buy".to_string());
|
||||||
|
let market = market(date, 6.27, 5.63);
|
||||||
|
let mut config = FidcRiskControlConfig::default();
|
||||||
|
config.static_rules.reject_st_selection = false;
|
||||||
|
config.static_rules.reject_st_buy = false;
|
||||||
|
config.static_rules.reject_paused_selection = false;
|
||||||
|
config.static_rules.reject_paused_buy = false;
|
||||||
|
config.static_rules.reject_inactive_selection = false;
|
||||||
|
config.static_rules.reject_inactive_buy = false;
|
||||||
|
config.static_rules.reject_new_listing_selection = false;
|
||||||
|
config.static_rules.reject_new_listing_buy = false;
|
||||||
|
config.static_rules.reject_kcb_selection = false;
|
||||||
|
config.static_rules.reject_kcb_buy = false;
|
||||||
|
config.static_rules.reject_one_yuan_selection = false;
|
||||||
|
config.static_rules.reject_one_yuan_buy = false;
|
||||||
|
config.static_rules.respect_allow_buy_sell = false;
|
||||||
|
|
||||||
|
let selection_reason = ChinaAShareRiskControl::selection_rejection_reason_with_config(
|
||||||
|
date, &candidate, &market, None, &config,
|
||||||
|
);
|
||||||
|
let buy_reason = ChinaAShareRiskControl::buy_rejection_reason_with_config(
|
||||||
|
date, &candidate, &market, None, 6.27, &config,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(selection_reason, None);
|
||||||
|
assert_eq!(buy_reason, None);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn configurable_upper_limit_buy_filter_can_be_disabled() {
|
fn configurable_upper_limit_buy_filter_can_be_disabled() {
|
||||||
let date = d(2025, 1, 2);
|
let date = d(2025, 1, 2);
|
||||||
|
|||||||
Reference in New Issue
Block a user