fix: accept scoped blacklist runtime context
This commit is contained in:
@@ -336,6 +336,15 @@ pub struct StrategyRiskPolicySpec {
|
||||
alias = "blacklist"
|
||||
)]
|
||||
pub blacklisted_symbols: Vec<String>,
|
||||
/// Account- and strategy-scoped blacklist facts are injected by the
|
||||
/// trading platform before runtime execution. They are typed here so the
|
||||
/// shared contract validator accepts the context without silently
|
||||
/// discarding malformed values; the trading risk layer applies the
|
||||
/// account/strategy match with the actual execution identity.
|
||||
#[serde(default, alias = "accountBlacklistedInstruments")]
|
||||
pub account_blacklisted_instruments: BTreeMap<String, BTreeSet<String>>,
|
||||
#[serde(default, alias = "strategyBlacklistedInstruments")]
|
||||
pub strategy_blacklisted_instruments: BTreeMap<String, BTreeSet<String>>,
|
||||
#[serde(
|
||||
default,
|
||||
alias = "volume_limit_enabled",
|
||||
@@ -675,8 +684,14 @@ const STRATEGY_ALIAS_GROUPS: &[(&str, &[&str])] = &[
|
||||
("matchingType", &["matching_type"]),
|
||||
("slippageModel", &["slippage_model"]),
|
||||
("slippageValue", &["slippage_value"]),
|
||||
("slippageImpactCoefficient", &["slippage_impact_coefficient"]),
|
||||
("slippageVolatilityCoefficient", &["slippage_volatility_coefficient"]),
|
||||
(
|
||||
"slippageImpactCoefficient",
|
||||
&["slippage_impact_coefficient"],
|
||||
),
|
||||
(
|
||||
"slippageVolatilityCoefficient",
|
||||
&["slippage_volatility_coefficient"],
|
||||
),
|
||||
(
|
||||
"slippageMaxValue",
|
||||
&["slippage_max_value", "slippage_max_rate"],
|
||||
@@ -688,7 +703,10 @@ const STRATEGY_ALIAS_GROUPS: &[(&str, &[&str])] = &[
|
||||
),
|
||||
("transferFeeRate", &["transfer_fee_rate", "transferFeeRate"]),
|
||||
("stampTaxRate", &["stamp_tax_rate"]),
|
||||
("stampTaxRateBeforeChange", &["stamp_tax_rate_before_change"]),
|
||||
(
|
||||
"stampTaxRateBeforeChange",
|
||||
&["stamp_tax_rate_before_change"],
|
||||
),
|
||||
("stampTaxRateAfterChange", &["stamp_tax_rate_after_change"]),
|
||||
("stampTaxChangeDate", &["stamp_tax_change_date"]),
|
||||
("volumeLimit", &["volume_limit"]),
|
||||
@@ -1080,6 +1098,10 @@ pub fn validate_strategy_risk_policy_fields(value: &Value) -> Result<(), String>
|
||||
"blacklistedInstruments",
|
||||
"blacklisted_instruments",
|
||||
"blacklist",
|
||||
"accountBlacklistedInstruments",
|
||||
"account_blacklisted_instruments",
|
||||
"strategyBlacklistedInstruments",
|
||||
"strategy_blacklisted_instruments",
|
||||
// Legacy execution aliases are accepted by StrategyExecutionSpec and
|
||||
// normalized into the same shared switches.
|
||||
"volumeLimit",
|
||||
@@ -1295,18 +1317,12 @@ fn apply_risk_policy_overrides(
|
||||
let Some(policy) = policy else {
|
||||
return Ok(());
|
||||
};
|
||||
let max_order_quantity = valid_positive_limit(
|
||||
policy.max_order_quantity,
|
||||
"riskPolicy.maxOrderQuantity",
|
||||
)?;
|
||||
let max_order_notional = valid_positive_limit(
|
||||
policy.max_order_notional,
|
||||
"riskPolicy.maxOrderNotional",
|
||||
)?;
|
||||
let max_symbol_position = valid_positive_limit(
|
||||
policy.max_symbol_position,
|
||||
"riskPolicy.maxSymbolPosition",
|
||||
)?;
|
||||
let max_order_quantity =
|
||||
valid_positive_limit(policy.max_order_quantity, "riskPolicy.maxOrderQuantity")?;
|
||||
let max_order_notional =
|
||||
valid_positive_limit(policy.max_order_notional, "riskPolicy.maxOrderNotional")?;
|
||||
let max_symbol_position =
|
||||
valid_positive_limit(policy.max_symbol_position, "riskPolicy.maxSymbolPosition")?;
|
||||
if let Some(value) = max_order_quantity {
|
||||
cfg.risk_config.trading_constraints.max_order_quantity = value;
|
||||
}
|
||||
@@ -3433,9 +3449,18 @@ mod tests {
|
||||
);
|
||||
assert!(cfg.risk_config.trading_constraints.volume_limit_enabled);
|
||||
assert!(cfg.risk_config.trading_constraints.liquidity_limit_enabled);
|
||||
assert_eq!(cfg.risk_config.trading_constraints.max_order_quantity, 8000.0);
|
||||
assert_eq!(cfg.risk_config.trading_constraints.max_order_notional, 2_000_000.0);
|
||||
assert_eq!(cfg.risk_config.trading_constraints.max_symbol_position, 12_000.0);
|
||||
assert_eq!(
|
||||
cfg.risk_config.trading_constraints.max_order_quantity,
|
||||
8000.0
|
||||
);
|
||||
assert_eq!(
|
||||
cfg.risk_config.trading_constraints.max_order_notional,
|
||||
2_000_000.0
|
||||
);
|
||||
assert_eq!(
|
||||
cfg.risk_config.trading_constraints.max_symbol_position,
|
||||
12_000.0
|
||||
);
|
||||
assert!((cfg.risk_config.trading_constraints.volume_percent - 0.25).abs() < 1e-12);
|
||||
assert_eq!(
|
||||
cfg.risk_config
|
||||
@@ -3647,6 +3672,25 @@ mod tests {
|
||||
assert_eq!(cfg.risk_config.trading_constraints.minimum_commission, 5.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepts_scoped_blacklist_context_in_runtime_risk_policy() {
|
||||
let spec = serde_json::json!({
|
||||
"execution": {
|
||||
"riskPolicy": {
|
||||
"account_blacklisted_instruments": {
|
||||
"2075773": ["000001.SZ"]
|
||||
},
|
||||
"strategyBlacklistedInstruments": {
|
||||
"live-gt-2075773-20260829": ["600000.SH"]
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
platform_expr_config_from_value("105", "932000.CSI", &spec)
|
||||
.expect("scoped blacklist context is part of the shared runtime contract");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_conflicting_risk_policy_alias_values() {
|
||||
let bool_conflict = serde_json::json!({
|
||||
|
||||
Reference in New Issue
Block a user