收紧风控策略别名冲突校验
This commit is contained in:
@@ -364,6 +364,55 @@ fn parse_policy_bool(canonical: &str, value: Value) -> Result<bool, String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn policy_value_semantically_equals(canonical: &str, left: &Value, right: &Value) -> bool {
|
||||||
|
if left == right {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if let (Some(left_number), Some(right_number)) =
|
||||||
|
(policy_value_as_number(left), policy_value_as_number(right))
|
||||||
|
{
|
||||||
|
if canonical == "volumePercent" {
|
||||||
|
return (normalize_percent_ratio_for_alias_compare(left_number)
|
||||||
|
- normalize_percent_ratio_for_alias_compare(right_number))
|
||||||
|
.abs()
|
||||||
|
< 1e-12;
|
||||||
|
}
|
||||||
|
return (left_number - right_number).abs() < 1e-12;
|
||||||
|
}
|
||||||
|
if let (Some(left_text), Some(right_text)) = (left.as_str(), right.as_str()) {
|
||||||
|
return left_text.trim() == right_text.trim();
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_percent_ratio_for_alias_compare(value: f64) -> f64 {
|
||||||
|
if value > 1.0 { value / 100.0 } else { value }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn policy_value_as_number(value: &Value) -> Option<f64> {
|
||||||
|
match value {
|
||||||
|
Value::Number(number) => number.as_f64(),
|
||||||
|
Value::String(text) => text.trim().parse::<f64>().ok(),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reject_conflicting_policy_values(canonical: &str, values: &[Value]) -> Result<(), String> {
|
||||||
|
let Some(first) = values.first() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
if values
|
||||||
|
.iter()
|
||||||
|
.skip(1)
|
||||||
|
.any(|value| !policy_value_semantically_equals(canonical, first, value))
|
||||||
|
{
|
||||||
|
return Err(format!(
|
||||||
|
"riskPolicy.{canonical} has conflicting alias values"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn push_blacklist_symbols_from_str(
|
fn push_blacklist_symbols_from_str(
|
||||||
raw: &str,
|
raw: &str,
|
||||||
symbols: &mut Vec<Value>,
|
symbols: &mut Vec<Value>,
|
||||||
@@ -384,17 +433,25 @@ fn normalize_risk_policy_object_aliases(
|
|||||||
for (canonical, aliases) in RISK_POLICY_BOOL_ALIAS_GROUPS {
|
for (canonical, aliases) in RISK_POLICY_BOOL_ALIAS_GROUPS {
|
||||||
let values = remove_policy_alias_values(object, canonical, aliases);
|
let values = remove_policy_alias_values(object, canonical, aliases);
|
||||||
if !values.is_empty() {
|
if !values.is_empty() {
|
||||||
let mut merged = false;
|
let mut parsed_values = Vec::with_capacity(values.len());
|
||||||
for value in values {
|
for value in values {
|
||||||
merged |= parse_policy_bool(canonical, value)?;
|
parsed_values.push(parse_policy_bool(canonical, value)?);
|
||||||
}
|
}
|
||||||
object.insert((*canonical).to_string(), Value::Bool(merged));
|
let first = parsed_values[0];
|
||||||
|
if parsed_values.iter().any(|value| *value != first) {
|
||||||
|
return Err(format!(
|
||||||
|
"riskPolicy.{canonical} has conflicting alias values"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
object.insert((*canonical).to_string(), Value::Bool(first));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (canonical, aliases) in RISK_POLICY_VALUE_ALIAS_GROUPS {
|
for (canonical, aliases) in RISK_POLICY_VALUE_ALIAS_GROUPS {
|
||||||
let values = remove_policy_alias_values(object, canonical, aliases);
|
let values = remove_policy_alias_values(object, canonical, aliases);
|
||||||
if let Some(value) = values.into_iter().next() {
|
if !values.is_empty() {
|
||||||
|
reject_conflicting_policy_values(canonical, &values)?;
|
||||||
|
let value = values.into_iter().next().expect("non-empty values");
|
||||||
object.insert((*canonical).to_string(), value);
|
object.insert((*canonical).to_string(), value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2423,6 +2480,63 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn accepts_equivalent_risk_policy_alias_values() {
|
||||||
|
let spec = serde_json::json!({
|
||||||
|
"engineConfig": {
|
||||||
|
"riskPolicy": {
|
||||||
|
"rejectStBuy": false,
|
||||||
|
"reject_st_buy": 0,
|
||||||
|
"volumePercent": 0.25,
|
||||||
|
"volume_percent": 25,
|
||||||
|
"minimumCommission": 5,
|
||||||
|
"minimum_commission": "5"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let cfg = platform_expr_config_from_value("", "", &spec).expect("config");
|
||||||
|
|
||||||
|
assert!(!cfg.risk_config.static_rules.reject_st_buy);
|
||||||
|
assert!((cfg.risk_config.trading_constraints.volume_percent - 0.25).abs() < 1e-12);
|
||||||
|
assert_eq!(cfg.risk_config.trading_constraints.minimum_commission, 5.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_conflicting_risk_policy_alias_values() {
|
||||||
|
let bool_conflict = serde_json::json!({
|
||||||
|
"engineConfig": {
|
||||||
|
"riskPolicy": {
|
||||||
|
"rejectStBuy": false,
|
||||||
|
"reject_st_buy": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let bool_error = platform_expr_config_from_value("", "", &bool_conflict)
|
||||||
|
.expect_err("conflicting bool aliases must fail");
|
||||||
|
assert!(
|
||||||
|
bool_error
|
||||||
|
.to_string()
|
||||||
|
.contains("riskPolicy.rejectStBuy has conflicting alias values")
|
||||||
|
);
|
||||||
|
|
||||||
|
let value_conflict = serde_json::json!({
|
||||||
|
"engineConfig": {
|
||||||
|
"riskPolicy": {
|
||||||
|
"minimumCommission": 5,
|
||||||
|
"minimum_commission": 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let value_error = platform_expr_config_from_value("", "", &value_conflict)
|
||||||
|
.expect_err("conflicting value aliases must fail");
|
||||||
|
assert!(
|
||||||
|
value_error
|
||||||
|
.to_string()
|
||||||
|
.contains("riskPolicy.minimumCommission has conflicting alias values")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parses_execution_slippage_overrides_into_platform_config() {
|
fn parses_execution_slippage_overrides_into_platform_config() {
|
||||||
let spec = serde_json::json!({
|
let spec = serde_json::json!({
|
||||||
|
|||||||
Reference in New Issue
Block a user