细化缺失风控事实开关判断
This commit is contained in:
@@ -363,65 +363,9 @@ impl ChinaAShareRiskControl {
|
||||
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 => {
|
||||
let fields = missing_risk_state_fields(code);
|
||||
if fields.is_empty() {
|
||||
return config.static_rules.reject_paused_sell
|
||||
|| config.static_rules.reject_inactive_sell
|
||||
|| config.static_rules.respect_allow_buy_sell
|
||||
|| config.static_rules.reject_lower_limit_sell;
|
||||
}
|
||||
(config.static_rules.reject_paused_sell
|
||||
&& fields.iter().any(|field| {
|
||||
matches!(
|
||||
field.as_str(),
|
||||
"paused" | "is_paused" | "suspended" | "is_suspended"
|
||||
)
|
||||
}))
|
||||
|| (config.static_rules.reject_inactive_sell
|
||||
&& fields.iter().any(|field| {
|
||||
matches!(
|
||||
field.as_str(),
|
||||
"active_status"
|
||||
| "is_active"
|
||||
| "inactive_status"
|
||||
| "is_inactive"
|
||||
| "is_delisted"
|
||||
| "listed_date"
|
||||
| "delisted_date"
|
||||
)
|
||||
}))
|
||||
|| (config.static_rules.respect_allow_buy_sell
|
||||
&& fields.iter().any(|field| field == "allow_sell"))
|
||||
|| (config.static_rules.reject_lower_limit_sell
|
||||
&& fields.iter().any(|field| {
|
||||
matches!(
|
||||
field.as_str(),
|
||||
"lower_limit"
|
||||
| "lower_limit_price"
|
||||
| "low_limit"
|
||||
| "low_limit_price"
|
||||
)
|
||||
}))
|
||||
}
|
||||
RiskCheckScope::Selection => missing_selection_risk_state_rejected(code, config),
|
||||
RiskCheckScope::Buy => missing_buy_risk_state_rejected(code, config),
|
||||
RiskCheckScope::Sell => missing_sell_risk_state_rejected(code, config),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,6 +495,149 @@ fn missing_risk_state_fields(code: &str) -> Vec<String> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn missing_selection_risk_state_rejected(code: &str, config: &FidcRiskControlConfig) -> bool {
|
||||
let fields = missing_risk_state_fields(code);
|
||||
if fields.is_empty() {
|
||||
return 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.reject_upper_limit_selection
|
||||
|| config.static_rules.reject_lower_limit_selection
|
||||
|| config.static_rules.respect_allow_buy_sell;
|
||||
}
|
||||
missing_field_rejected(&fields, config, RiskCheckScope::Selection)
|
||||
}
|
||||
|
||||
fn missing_buy_risk_state_rejected(code: &str, config: &FidcRiskControlConfig) -> bool {
|
||||
let fields = missing_risk_state_fields(code);
|
||||
if fields.is_empty() {
|
||||
return 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.reject_upper_limit_buy
|
||||
|| config.static_rules.respect_allow_buy_sell;
|
||||
}
|
||||
missing_field_rejected(&fields, config, RiskCheckScope::Buy)
|
||||
}
|
||||
|
||||
fn missing_sell_risk_state_rejected(code: &str, config: &FidcRiskControlConfig) -> bool {
|
||||
let fields = missing_risk_state_fields(code);
|
||||
if fields.is_empty() {
|
||||
return config.static_rules.reject_paused_sell
|
||||
|| config.static_rules.reject_inactive_sell
|
||||
|| config.static_rules.respect_allow_buy_sell
|
||||
|| config.static_rules.reject_lower_limit_sell;
|
||||
}
|
||||
missing_field_rejected(&fields, config, RiskCheckScope::Sell)
|
||||
}
|
||||
|
||||
fn missing_field_rejected(
|
||||
fields: &[String],
|
||||
config: &FidcRiskControlConfig,
|
||||
scope: RiskCheckScope,
|
||||
) -> bool {
|
||||
fields
|
||||
.iter()
|
||||
.any(|field| missing_single_field_rejected(field, config, scope))
|
||||
}
|
||||
|
||||
fn missing_single_field_rejected(
|
||||
field: &str,
|
||||
config: &FidcRiskControlConfig,
|
||||
scope: RiskCheckScope,
|
||||
) -> bool {
|
||||
match field {
|
||||
"is_st" | "is_star_st" | "st" | "star_st" | "star_st_status" => match scope {
|
||||
RiskCheckScope::Selection => config.static_rules.reject_st_selection,
|
||||
RiskCheckScope::Buy => config.static_rules.reject_st_buy,
|
||||
RiskCheckScope::Sell => false,
|
||||
},
|
||||
"paused" | "is_paused" | "suspended" | "is_suspended" => match scope {
|
||||
RiskCheckScope::Selection => config.static_rules.reject_paused_selection,
|
||||
RiskCheckScope::Buy => config.static_rules.reject_paused_buy,
|
||||
RiskCheckScope::Sell => config.static_rules.reject_paused_sell,
|
||||
},
|
||||
"active_status" | "status" | "instrument_status" | "is_active" | "inactive_status"
|
||||
| "is_inactive" | "is_delisted" | "listed_date" | "delisted_date" => match scope {
|
||||
RiskCheckScope::Selection => config.static_rules.reject_inactive_selection,
|
||||
RiskCheckScope::Buy => config.static_rules.reject_inactive_buy,
|
||||
RiskCheckScope::Sell => config.static_rules.reject_inactive_sell,
|
||||
},
|
||||
"listed_days" | "listing_days" | "days_since_listing" => match scope {
|
||||
RiskCheckScope::Selection => config.static_rules.reject_new_listing_selection,
|
||||
RiskCheckScope::Buy => config.static_rules.reject_new_listing_buy,
|
||||
RiskCheckScope::Sell => false,
|
||||
},
|
||||
"is_kcb" | "kcb" | "board" | "market_board" => match scope {
|
||||
RiskCheckScope::Selection => config.static_rules.reject_kcb_selection,
|
||||
RiskCheckScope::Buy => config.static_rules.reject_kcb_buy,
|
||||
RiskCheckScope::Sell => false,
|
||||
},
|
||||
"is_one_yuan" | "one_yuan" => match scope {
|
||||
RiskCheckScope::Selection => config.static_rules.reject_one_yuan_selection,
|
||||
RiskCheckScope::Buy => config.static_rules.reject_one_yuan_buy,
|
||||
RiskCheckScope::Sell => false,
|
||||
},
|
||||
"allow_buy" => match scope {
|
||||
RiskCheckScope::Selection | RiskCheckScope::Buy => {
|
||||
config.static_rules.respect_allow_buy_sell
|
||||
}
|
||||
RiskCheckScope::Sell => false,
|
||||
},
|
||||
"allow_sell" => match scope {
|
||||
RiskCheckScope::Selection | RiskCheckScope::Sell => {
|
||||
config.static_rules.respect_allow_buy_sell
|
||||
}
|
||||
RiskCheckScope::Buy => false,
|
||||
},
|
||||
"upper_limit" | "upper_limit_price" | "high_limit" | "high_limit_price" => match scope {
|
||||
RiskCheckScope::Selection => config.static_rules.reject_upper_limit_selection,
|
||||
RiskCheckScope::Buy => config.static_rules.reject_upper_limit_buy,
|
||||
RiskCheckScope::Sell => false,
|
||||
},
|
||||
"lower_limit" | "lower_limit_price" | "low_limit" | "low_limit_price" => match scope {
|
||||
RiskCheckScope::Selection => config.static_rules.reject_lower_limit_selection,
|
||||
RiskCheckScope::Buy => false,
|
||||
RiskCheckScope::Sell => config.static_rules.reject_lower_limit_sell,
|
||||
},
|
||||
_ => 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.reject_upper_limit_selection
|
||||
|| config.static_rules.reject_lower_limit_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.reject_upper_limit_buy
|
||||
|| config.static_rules.respect_allow_buy_sell
|
||||
}
|
||||
RiskCheckScope::Sell => {
|
||||
config.static_rules.reject_paused_sell
|
||||
|| config.static_rules.reject_inactive_sell
|
||||
|| config.static_rules.reject_lower_limit_sell
|
||||
|| config.static_rules.respect_allow_buy_sell
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum RiskCheckScope {
|
||||
@@ -772,6 +859,47 @@ mod tests {
|
||||
assert_eq!(reason, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_risk_state_respects_field_specific_selection_switches() {
|
||||
let date = d(2025, 1, 2);
|
||||
let mut candidate = candidate(date);
|
||||
candidate.allow_sell = true;
|
||||
candidate.risk_level_code =
|
||||
Some("missing_risk_state:upper_limit_price,lower_limit_price".to_string());
|
||||
let market = market(date, 6.27, 5.63);
|
||||
let mut config = FidcRiskControlConfig::default();
|
||||
config.static_rules.reject_upper_limit_selection = false;
|
||||
|
||||
let upper_disabled_reason = ChinaAShareRiskControl::selection_rejection_reason_with_config(
|
||||
date, &candidate, &market, None, &config,
|
||||
);
|
||||
|
||||
config.static_rules.reject_lower_limit_selection = false;
|
||||
let both_disabled_reason = ChinaAShareRiskControl::selection_rejection_reason_with_config(
|
||||
date, &candidate, &market, None, &config,
|
||||
);
|
||||
|
||||
assert_eq!(upper_disabled_reason, Some("missing_risk_state"));
|
||||
assert_eq!(both_disabled_reason, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_risk_state_respects_field_specific_buy_switches() {
|
||||
let date = d(2025, 1, 2);
|
||||
let mut candidate = candidate(date);
|
||||
candidate.risk_level_code =
|
||||
Some("missing_risk_state:upper_limit_price,lower_limit_price".to_string());
|
||||
let market = market(date, 6.27, 5.63);
|
||||
let mut config = FidcRiskControlConfig::default();
|
||||
config.static_rules.reject_upper_limit_buy = false;
|
||||
|
||||
let buy_reason = ChinaAShareRiskControl::buy_rejection_reason_with_config(
|
||||
date, &candidate, &market, None, 6.27, &config,
|
||||
);
|
||||
|
||||
assert_eq!(buy_reason, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_risk_state_can_be_relaxed_by_disabling_static_filters() {
|
||||
let date = d(2025, 1, 2);
|
||||
|
||||
Reference in New Issue
Block a user