补齐FIDC北交所风控开关
This commit is contained in:
@@ -216,6 +216,10 @@ pub struct StrategyRiskPolicySpec {
|
||||
pub reject_kcb_selection: Option<bool>,
|
||||
#[serde(default, alias = "reject_kcb_buy")]
|
||||
pub reject_kcb_buy: Option<bool>,
|
||||
#[serde(default, alias = "reject_bjse_selection")]
|
||||
pub reject_bjse_selection: Option<bool>,
|
||||
#[serde(default, alias = "reject_bjse_buy")]
|
||||
pub reject_bjse_buy: Option<bool>,
|
||||
#[serde(default, alias = "reject_one_yuan_selection")]
|
||||
pub reject_one_yuan_selection: Option<bool>,
|
||||
#[serde(default, alias = "reject_one_yuan_buy")]
|
||||
@@ -285,6 +289,8 @@ const RISK_POLICY_BOOL_ALIAS_GROUPS: &[(&str, &[&str])] = &[
|
||||
("rejectNewListingBuy", &["reject_new_listing_buy"]),
|
||||
("rejectKcbSelection", &["reject_kcb_selection"]),
|
||||
("rejectKcbBuy", &["reject_kcb_buy"]),
|
||||
("rejectBjseSelection", &["reject_bjse_selection"]),
|
||||
("rejectBjseBuy", &["reject_bjse_buy"]),
|
||||
("rejectOneYuanSelection", &["reject_one_yuan_selection"]),
|
||||
("rejectOneYuanBuy", &["reject_one_yuan_buy"]),
|
||||
("respectAllowBuySell", &["respect_allow_buy_sell"]),
|
||||
@@ -794,6 +800,12 @@ fn apply_risk_policy_overrides(
|
||||
if let Some(value) = policy.reject_kcb_buy {
|
||||
static_rules.reject_kcb_buy = value;
|
||||
}
|
||||
if let Some(value) = policy.reject_bjse_selection {
|
||||
static_rules.reject_bjse_selection = value;
|
||||
}
|
||||
if let Some(value) = policy.reject_bjse_buy {
|
||||
static_rules.reject_bjse_buy = value;
|
||||
}
|
||||
if let Some(value) = policy.reject_one_yuan_selection {
|
||||
static_rules.reject_one_yuan_selection = value;
|
||||
}
|
||||
@@ -2244,6 +2256,8 @@ mod tests {
|
||||
"rejectStarStBuy": true,
|
||||
"reject_paused_selection": false,
|
||||
"reject_kcb_buy": false,
|
||||
"rejectBjseSelection": false,
|
||||
"reject_bjse_buy": false,
|
||||
"blacklisted_symbols": ["000001.SZ"],
|
||||
"blacklist": ["000002.SZ", "000001.SZ"],
|
||||
"volume_limit_enabled": true,
|
||||
@@ -2266,6 +2280,8 @@ mod tests {
|
||||
assert!(cfg.risk_config.static_rules.reject_st_buy);
|
||||
assert!(!cfg.risk_config.static_rules.reject_paused_selection);
|
||||
assert!(!cfg.risk_config.static_rules.reject_kcb_buy);
|
||||
assert!(!cfg.risk_config.static_rules.reject_bjse_selection);
|
||||
assert!(!cfg.risk_config.static_rules.reject_bjse_buy);
|
||||
assert!(
|
||||
cfg.risk_config
|
||||
.static_rules
|
||||
|
||||
@@ -25,6 +25,8 @@ pub struct StaticRiskRuleConfig {
|
||||
pub reject_new_listing_buy: bool,
|
||||
pub reject_kcb_selection: bool,
|
||||
pub reject_kcb_buy: bool,
|
||||
pub reject_bjse_selection: bool,
|
||||
pub reject_bjse_buy: bool,
|
||||
pub reject_one_yuan_selection: bool,
|
||||
pub reject_one_yuan_buy: bool,
|
||||
pub respect_allow_buy_sell: bool,
|
||||
@@ -53,6 +55,8 @@ impl Default for StaticRiskRuleConfig {
|
||||
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,
|
||||
@@ -358,6 +362,14 @@ impl ChinaAShareRiskControl {
|
||||
if reject_kcb && (candidate.is_kcb || symbol_is_kcb(&candidate.symbol)) {
|
||||
return Some("kcb");
|
||||
}
|
||||
let reject_bjse = match scope {
|
||||
RiskCheckScope::Selection => config.static_rules.reject_bjse_selection,
|
||||
RiskCheckScope::Buy => config.static_rules.reject_bjse_buy,
|
||||
RiskCheckScope::Sell => false,
|
||||
};
|
||||
if reject_bjse && symbol_is_bjse(&candidate.symbol) {
|
||||
return Some("bjse");
|
||||
}
|
||||
let reject_one_yuan = match scope {
|
||||
RiskCheckScope::Selection => config.static_rules.reject_one_yuan_selection,
|
||||
RiskCheckScope::Buy => config.static_rules.reject_one_yuan_buy,
|
||||
@@ -514,6 +526,11 @@ fn symbol_is_kcb(symbol: &str) -> bool {
|
||||
(normalized.starts_with("688") || normalized.starts_with("689")) && normalized.ends_with(".SH")
|
||||
}
|
||||
|
||||
fn symbol_is_bjse(symbol: &str) -> bool {
|
||||
let normalized = symbol.trim().to_ascii_uppercase();
|
||||
normalized.ends_with(".BJ") || normalized.ends_with(".BSE") || normalized.ends_with(".BE")
|
||||
}
|
||||
|
||||
fn missing_risk_state_fields(code: &str) -> Vec<String> {
|
||||
let Some((_, fields)) = code.split_once(':') else {
|
||||
return Vec::new();
|
||||
@@ -534,6 +551,7 @@ fn missing_selection_risk_state_rejected(code: &str, config: &FidcRiskControlCon
|
||||
|| config.static_rules.reject_inactive_selection
|
||||
|| config.static_rules.reject_new_listing_selection
|
||||
|| config.static_rules.reject_kcb_selection
|
||||
|| config.static_rules.reject_bjse_selection
|
||||
|| config.static_rules.reject_one_yuan_selection
|
||||
|| config.static_rules.reject_upper_limit_selection
|
||||
|| config.static_rules.reject_lower_limit_selection
|
||||
@@ -550,6 +568,7 @@ fn missing_buy_risk_state_rejected(code: &str, config: &FidcRiskControlConfig) -
|
||||
|| config.static_rules.reject_inactive_buy
|
||||
|| config.static_rules.reject_new_listing_buy
|
||||
|| config.static_rules.reject_kcb_buy
|
||||
|| config.static_rules.reject_bjse_buy
|
||||
|| config.static_rules.reject_one_yuan_buy
|
||||
|| config.static_rules.reject_upper_limit_buy
|
||||
|| config.static_rules.respect_allow_buy_sell;
|
||||
@@ -606,8 +625,18 @@ fn missing_single_field_rejected(
|
||||
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::Selection => {
|
||||
config.static_rules.reject_kcb_selection
|
||||
|| config.static_rules.reject_bjse_selection
|
||||
}
|
||||
RiskCheckScope::Buy => {
|
||||
config.static_rules.reject_kcb_buy || config.static_rules.reject_bjse_buy
|
||||
}
|
||||
RiskCheckScope::Sell => false,
|
||||
},
|
||||
"is_bjse" | "bjse" | "bse" | "beijing_exchange" | "north_exchange" => match scope {
|
||||
RiskCheckScope::Selection => config.static_rules.reject_bjse_selection,
|
||||
RiskCheckScope::Buy => config.static_rules.reject_bjse_buy,
|
||||
RiskCheckScope::Sell => false,
|
||||
},
|
||||
"is_one_yuan" | "one_yuan" => match scope {
|
||||
@@ -644,6 +673,7 @@ fn missing_single_field_rejected(
|
||||
|| config.static_rules.reject_inactive_selection
|
||||
|| config.static_rules.reject_new_listing_selection
|
||||
|| config.static_rules.reject_kcb_selection
|
||||
|| config.static_rules.reject_bjse_selection
|
||||
|| config.static_rules.reject_one_yuan_selection
|
||||
|| config.static_rules.reject_upper_limit_selection
|
||||
|| config.static_rules.reject_lower_limit_selection
|
||||
@@ -655,6 +685,7 @@ fn missing_single_field_rejected(
|
||||
|| config.static_rules.reject_inactive_buy
|
||||
|| config.static_rules.reject_new_listing_buy
|
||||
|| config.static_rules.reject_kcb_buy
|
||||
|| config.static_rules.reject_bjse_buy
|
||||
|| config.static_rules.reject_one_yuan_buy
|
||||
|| config.static_rules.reject_upper_limit_buy
|
||||
|| config.static_rules.respect_allow_buy_sell
|
||||
@@ -813,6 +844,33 @@ mod tests {
|
||||
assert_eq!(configured_reason, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn configurable_bjse_filter_can_be_disabled() {
|
||||
let date = d(2025, 1, 2);
|
||||
let mut candidate = candidate(date);
|
||||
candidate.symbol = "430047.BJ".to_string();
|
||||
candidate.allow_sell = true;
|
||||
let market = market(date, 6.27, 5.63);
|
||||
let default_selection =
|
||||
ChinaAShareRiskControl::selection_rejection_reason(date, &candidate, &market, None);
|
||||
let default_buy =
|
||||
ChinaAShareRiskControl::buy_rejection_reason(date, &candidate, &market, None, 6.27);
|
||||
let mut config = FidcRiskControlConfig::default();
|
||||
config.static_rules.reject_bjse_selection = false;
|
||||
config.static_rules.reject_bjse_buy = false;
|
||||
let configured_selection = ChinaAShareRiskControl::selection_rejection_reason_with_config(
|
||||
date, &candidate, &market, None, &config,
|
||||
);
|
||||
let configured_buy = ChinaAShareRiskControl::buy_rejection_reason_with_config(
|
||||
date, &candidate, &market, None, 6.27, &config,
|
||||
);
|
||||
|
||||
assert_eq!(default_selection, Some("bjse"));
|
||||
assert_eq!(default_buy, Some("bjse"));
|
||||
assert_eq!(configured_selection, None);
|
||||
assert_eq!(configured_buy, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concrete_kcb_reason_wins_over_generic_missing_risk_state() {
|
||||
let date = d(2025, 1, 2);
|
||||
@@ -994,6 +1052,8 @@ mod tests {
|
||||
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_bjse_selection = false;
|
||||
config.static_rules.reject_bjse_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;
|
||||
|
||||
Reference in New Issue
Block a user