修复FIDC风控别名归一化
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::collections::{BTreeMap, BTreeSet, HashSet};
|
||||
|
||||
use chrono::{NaiveDate, NaiveTime};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -244,6 +244,170 @@ pub struct StrategyRiskPolicySpec {
|
||||
pub stamp_tax_change_date: Option<String>,
|
||||
}
|
||||
|
||||
const RISK_POLICY_BOOL_ALIAS_GROUPS: &[(&str, &[&str])] = &[
|
||||
(
|
||||
"rejectStSelection",
|
||||
&[
|
||||
"reject_st_selection",
|
||||
"rejectStarStSelection",
|
||||
"reject_star_st_selection",
|
||||
],
|
||||
),
|
||||
(
|
||||
"rejectStBuy",
|
||||
&["reject_st_buy", "rejectStarStBuy", "reject_star_st_buy"],
|
||||
),
|
||||
("rejectPausedSelection", &["reject_paused_selection"]),
|
||||
("rejectPausedBuy", &["reject_paused_buy"]),
|
||||
("rejectPausedSell", &["reject_paused_sell"]),
|
||||
("rejectInactiveSelection", &["reject_inactive_selection"]),
|
||||
("rejectInactiveBuy", &["reject_inactive_buy"]),
|
||||
("rejectInactiveSell", &["reject_inactive_sell"]),
|
||||
(
|
||||
"rejectNewListingSelection",
|
||||
&["reject_new_listing_selection"],
|
||||
),
|
||||
("rejectNewListingBuy", &["reject_new_listing_buy"]),
|
||||
("rejectKcbSelection", &["reject_kcb_selection"]),
|
||||
("rejectKcbBuy", &["reject_kcb_buy"]),
|
||||
("rejectOneYuanSelection", &["reject_one_yuan_selection"]),
|
||||
("rejectOneYuanBuy", &["reject_one_yuan_buy"]),
|
||||
("respectAllowBuySell", &["respect_allow_buy_sell"]),
|
||||
(
|
||||
"rejectUpperLimitSelection",
|
||||
&["reject_upper_limit_selection"],
|
||||
),
|
||||
(
|
||||
"rejectLowerLimitSelection",
|
||||
&["reject_lower_limit_selection"],
|
||||
),
|
||||
("rejectUpperLimitBuy", &["reject_upper_limit_buy"]),
|
||||
("rejectLowerLimitSell", &["reject_lower_limit_sell"]),
|
||||
(
|
||||
"forbidSameDayRebuyAfterSell",
|
||||
&["forbid_same_day_rebuy_after_sell"],
|
||||
),
|
||||
("blacklistEnabled", &["blacklist_enabled"]),
|
||||
("volumeLimitEnabled", &["volume_limit_enabled"]),
|
||||
("liquidityLimitEnabled", &["liquidity_limit_enabled"]),
|
||||
];
|
||||
|
||||
const RISK_POLICY_VALUE_ALIAS_GROUPS: &[(&str, &[&str])] = &[
|
||||
("volumePercent", &["volume_percent"]),
|
||||
("commissionRate", &["commission_rate"]),
|
||||
(
|
||||
"minimumCommission",
|
||||
&["minimum_commission", "minCommission"],
|
||||
),
|
||||
(
|
||||
"stampTaxRateBeforeChange",
|
||||
&["stamp_tax_rate_before_change"],
|
||||
),
|
||||
("stampTaxRateAfterChange", &["stamp_tax_rate_after_change"]),
|
||||
("stampTaxChangeDate", &["stamp_tax_change_date"]),
|
||||
];
|
||||
|
||||
fn remove_policy_alias_values(
|
||||
object: &mut serde_json::Map<String, Value>,
|
||||
canonical: &str,
|
||||
aliases: &[&str],
|
||||
) -> Vec<Value> {
|
||||
let mut values = Vec::new();
|
||||
if let Some(value) = object.remove(canonical).filter(|value| !value.is_null()) {
|
||||
values.push(value);
|
||||
}
|
||||
for alias in aliases {
|
||||
if let Some(value) = object.remove(*alias).filter(|value| !value.is_null()) {
|
||||
values.push(value);
|
||||
}
|
||||
}
|
||||
values
|
||||
}
|
||||
|
||||
fn normalize_risk_policy_object_aliases(
|
||||
object: &mut serde_json::Map<String, Value>,
|
||||
) -> Result<(), String> {
|
||||
for (canonical, aliases) in RISK_POLICY_BOOL_ALIAS_GROUPS {
|
||||
let values = remove_policy_alias_values(object, canonical, aliases);
|
||||
if !values.is_empty() {
|
||||
let mut merged = false;
|
||||
for value in values {
|
||||
let Some(value) = value.as_bool() else {
|
||||
return Err(format!("riskPolicy.{canonical} must be a boolean"));
|
||||
};
|
||||
merged |= value;
|
||||
}
|
||||
object.insert((*canonical).to_string(), Value::Bool(merged));
|
||||
}
|
||||
}
|
||||
|
||||
for (canonical, aliases) in RISK_POLICY_VALUE_ALIAS_GROUPS {
|
||||
let values = remove_policy_alias_values(object, canonical, aliases);
|
||||
if let Some(value) = values.into_iter().next() {
|
||||
object.insert((*canonical).to_string(), value);
|
||||
}
|
||||
}
|
||||
|
||||
let blacklist_values = remove_policy_alias_values(
|
||||
object,
|
||||
"blacklistedSymbols",
|
||||
&["blacklisted_symbols", "blacklist"],
|
||||
);
|
||||
let mut symbols = Vec::<Value>::new();
|
||||
let mut seen = HashSet::<String>::new();
|
||||
for value in blacklist_values {
|
||||
match value {
|
||||
Value::Array(items) => {
|
||||
for item in items {
|
||||
let Some(symbol) = item.as_str() else {
|
||||
return Err(
|
||||
"riskPolicy.blacklistedSymbols must contain only strings".to_string()
|
||||
);
|
||||
};
|
||||
let key = symbol.trim().to_string();
|
||||
if key.is_empty() || !seen.insert(key.clone()) {
|
||||
continue;
|
||||
}
|
||||
symbols.push(Value::String(key));
|
||||
}
|
||||
}
|
||||
Value::String(item) => {
|
||||
let key = item.trim().to_string();
|
||||
if !key.is_empty() && seen.insert(key.clone()) {
|
||||
symbols.push(Value::String(key));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(
|
||||
"riskPolicy.blacklistedSymbols must be a string or string array".to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if !symbols.is_empty() {
|
||||
object.insert("blacklistedSymbols".to_string(), Value::Array(symbols));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn normalize_risk_policy_aliases_in_value(value: &mut Value) -> Result<(), String> {
|
||||
match value {
|
||||
Value::Object(object) => {
|
||||
normalize_risk_policy_object_aliases(object)?;
|
||||
for child in object.values_mut() {
|
||||
normalize_risk_policy_aliases_in_value(child)?;
|
||||
}
|
||||
}
|
||||
Value::Array(items) => {
|
||||
for item in items {
|
||||
normalize_risk_policy_aliases_in_value(item)?;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DynamicRangeConfig {
|
||||
@@ -471,7 +635,9 @@ pub fn platform_expr_config_from_value(
|
||||
return platform_expr_config_from_spec(strategy_id, signal_symbol, None)
|
||||
.map_err(platform_config_error);
|
||||
}
|
||||
let spec = serde_json::from_value::<StrategyRuntimeSpec>(value.clone())?;
|
||||
let mut value = value.clone();
|
||||
normalize_risk_policy_aliases_in_value(&mut value).map_err(platform_config_error)?;
|
||||
let spec = serde_json::from_value::<StrategyRuntimeSpec>(value)?;
|
||||
platform_expr_config_from_spec(strategy_id, signal_symbol, Some(&spec))
|
||||
.map_err(platform_config_error)
|
||||
}
|
||||
@@ -2031,11 +2197,14 @@ mod tests {
|
||||
let spec = serde_json::json!({
|
||||
"engine_config": {
|
||||
"risk_policy": {
|
||||
"reject_star_st_selection": false,
|
||||
"reject_star_st_buy": false,
|
||||
"reject_st_selection": false,
|
||||
"rejectStarStSelection": true,
|
||||
"reject_st_buy": false,
|
||||
"rejectStarStBuy": true,
|
||||
"reject_paused_selection": false,
|
||||
"reject_kcb_buy": false,
|
||||
"blacklisted_symbols": ["000001.SZ"],
|
||||
"blacklist": ["000002.SZ", "000001.SZ"],
|
||||
"volume_limit_enabled": true,
|
||||
"volume_percent": 25,
|
||||
"minimum_commission": 6.0,
|
||||
@@ -2052,8 +2221,8 @@ mod tests {
|
||||
|
||||
let cfg = platform_expr_config_from_value("", "", &spec).expect("config");
|
||||
|
||||
assert!(!cfg.risk_config.static_rules.reject_st_selection);
|
||||
assert!(!cfg.risk_config.static_rules.reject_st_buy);
|
||||
assert!(cfg.risk_config.static_rules.reject_st_selection);
|
||||
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!(
|
||||
@@ -2062,6 +2231,12 @@ mod tests {
|
||||
.blacklisted_symbols
|
||||
.contains("000001.SZ")
|
||||
);
|
||||
assert!(
|
||||
cfg.risk_config
|
||||
.static_rules
|
||||
.blacklisted_symbols
|
||||
.contains("000002.SZ")
|
||||
);
|
||||
assert!((cfg.risk_config.trading_constraints.volume_percent - 0.25).abs() < 1e-12);
|
||||
assert_eq!(cfg.risk_config.trading_constraints.minimum_commission, 6.0);
|
||||
assert_eq!(cfg.commission_rate, Some(0.0003));
|
||||
|
||||
Reference in New Issue
Block a user