拆分ST与星号ST风控语义

This commit is contained in:
boris
2026-07-03 09:00:50 +08:00
parent c32926cc34
commit 3bb001c374
17 changed files with 308 additions and 36 deletions
+91 -1
View File
@@ -15,6 +15,8 @@ pub struct ChinaAShareRiskControl;
pub struct StaticRiskRuleConfig {
pub reject_st_selection: bool,
pub reject_st_buy: bool,
pub reject_star_st_selection: bool,
pub reject_star_st_buy: bool,
pub reject_paused_selection: bool,
pub reject_paused_buy: bool,
pub reject_paused_sell: bool,
@@ -45,6 +47,8 @@ impl Default for StaticRiskRuleConfig {
Self {
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,
@@ -346,6 +350,14 @@ impl ChinaAShareRiskControl {
if reject_st && candidate.is_st {
return Some("st");
}
let reject_star_st = match scope {
RiskCheckScope::Selection => config.static_rules.reject_star_st_selection,
RiskCheckScope::Buy => config.static_rules.reject_star_st_buy,
RiskCheckScope::Sell => false,
};
if reject_star_st && candidate.is_star_st {
return Some("star_st");
}
let reject_new_listing = match scope {
RiskCheckScope::Selection => config.static_rules.reject_new_listing_selection,
RiskCheckScope::Buy => config.static_rules.reject_new_listing_buy,
@@ -547,6 +559,7 @@ fn missing_selection_risk_state_rejected(code: &str, config: &FidcRiskControlCon
let fields = missing_risk_state_fields(code);
if fields.is_empty() {
return config.static_rules.reject_st_selection
|| config.static_rules.reject_star_st_selection
|| config.static_rules.reject_paused_selection
|| config.static_rules.reject_inactive_selection
|| config.static_rules.reject_new_listing_selection
@@ -564,6 +577,7 @@ fn missing_buy_risk_state_rejected(code: &str, config: &FidcRiskControlConfig) -
let fields = missing_risk_state_fields(code);
if fields.is_empty() {
return config.static_rules.reject_st_buy
|| config.static_rules.reject_star_st_buy
|| config.static_rules.reject_paused_buy
|| config.static_rules.reject_inactive_buy
|| config.static_rules.reject_new_listing_buy
@@ -603,11 +617,16 @@ fn missing_single_field_rejected(
scope: RiskCheckScope,
) -> bool {
match field {
"is_st" | "is_star_st" | "st" | "star_st" | "star_st_status" => match scope {
"is_st" | "st" => match scope {
RiskCheckScope::Selection => config.static_rules.reject_st_selection,
RiskCheckScope::Buy => config.static_rules.reject_st_buy,
RiskCheckScope::Sell => false,
},
"is_star_st" | "star_st" | "star_st_status" => match scope {
RiskCheckScope::Selection => config.static_rules.reject_star_st_selection,
RiskCheckScope::Buy => config.static_rules.reject_star_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,
@@ -669,6 +688,7 @@ fn missing_single_field_rejected(
_ => match scope {
RiskCheckScope::Selection => {
config.static_rules.reject_st_selection
|| config.static_rules.reject_star_st_selection
|| config.static_rules.reject_paused_selection
|| config.static_rules.reject_inactive_selection
|| config.static_rules.reject_new_listing_selection
@@ -681,6 +701,7 @@ fn missing_single_field_rejected(
}
RiskCheckScope::Buy => {
config.static_rules.reject_st_buy
|| config.static_rules.reject_star_st_buy
|| config.static_rules.reject_paused_buy
|| config.static_rules.reject_inactive_buy
|| config.static_rules.reject_new_listing_buy
@@ -721,6 +742,7 @@ mod tests {
date,
symbol: "002633.SZ".to_string(),
is_st: false,
is_star_st: false,
is_new_listing: false,
is_paused: false,
allow_buy: true,
@@ -844,6 +866,74 @@ mod tests {
assert_eq!(configured_reason, None);
}
#[test]
fn st_and_star_st_filters_are_independent() {
let date = d(2025, 1, 2);
let market = market(date, 6.27, 5.63);
let mut st_candidate = candidate(date);
st_candidate.is_st = true;
st_candidate.allow_sell = true;
let mut star_st_candidate = candidate(date);
star_st_candidate.is_star_st = true;
star_st_candidate.allow_sell = true;
let mut config = FidcRiskControlConfig::default();
config.static_rules.reject_st_selection = false;
config.static_rules.reject_st_buy = false;
config.static_rules.reject_star_st_selection = true;
config.static_rules.reject_star_st_buy = true;
assert_eq!(
ChinaAShareRiskControl::selection_rejection_reason_with_config(
date,
&st_candidate,
&market,
None,
&config,
),
None
);
assert_eq!(
ChinaAShareRiskControl::selection_rejection_reason_with_config(
date,
&star_st_candidate,
&market,
None,
&config,
),
Some("star_st")
);
config.static_rules.reject_st_selection = true;
config.static_rules.reject_st_buy = true;
config.static_rules.reject_star_st_selection = false;
config.static_rules.reject_star_st_buy = false;
assert_eq!(
ChinaAShareRiskControl::buy_rejection_reason_with_config(
date,
&st_candidate,
&market,
None,
6.27,
&config,
),
Some("st")
);
assert_eq!(
ChinaAShareRiskControl::buy_rejection_reason_with_config(
date,
&star_st_candidate,
&market,
None,
6.27,
&config,
),
None
);
}
#[test]
fn configurable_bjse_filter_can_be_disabled() {
let date = d(2025, 1, 2);