fix(risk-engine): 修复多个风控引擎问题
- ScopeIsGlobal 类型由 bool 改为 int 以兼容前端传输 0/1 - 修复 QueryApplicationList 中 Status 投影 EF Core enum 转换失败 - 新增 Scope 字段查询支持(逗号分隔集合精确匹配) - 新增变量列表 DataType 筛选 - 修复审计日志日期查询范围(EndDate 纳入整天数据) - 统一 Application 审计日志 OperationType 前缀(APP_xxx) - 审计日志 OperationDetail 枚举值映射为中文
This commit is contained in:
@@ -13,7 +13,7 @@ namespace YLErp.DBModels
|
||||
public string ScopeClientIds { get; set; }
|
||||
public string ScopeUnderlyingTypes { get; set; }
|
||||
public string ScopeTradeTypes { get; set; }
|
||||
public bool ScopeIsGlobal { get; set; }
|
||||
public int ScopeIsGlobal { get; set; }
|
||||
public int Version { get; set; } = 1;
|
||||
public int? UpdateOptId { get; set; }
|
||||
public string UpdateOptName { get; set; }
|
||||
|
||||
@@ -50,7 +50,7 @@ INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr
|
||||
'挂钩标的到期日早于合约到期日时禁止交易',
|
||||
JSON_ARRAY(JSON_OBJECT(
|
||||
'VariableId', 22,
|
||||
'Operator', '<', 'ThresholdType', 'Variable',
|
||||
'Operator', '早于', 'ThresholdType', 'Variable',
|
||||
'ThresholdVariableId', 3
|
||||
)),
|
||||
'market.UnderlyingMaturityDate < trade.ExerciseDate',
|
||||
@@ -87,7 +87,8 @@ INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr
|
||||
'保证金利率不在配置区间内(默认2%~5%)时触发审批',
|
||||
JSON_ARRAY(JSON_OBJECT(
|
||||
'VariableId', 12,
|
||||
'Operator', '不介于', 'ThresholdType', 'Fixed', 'Value', JSON_ARRAY(2, 5)
|
||||
'Operator', '不介于', 'ThresholdType', 'Fixed', 'Value', JSON_ARRAY(2, 5),
|
||||
'IncludeLowerBound', true, 'IncludeUpperBound', true
|
||||
)),
|
||||
'!(client_marginrate.InitMarginRebateRate >= 2 && client_marginrate.InitMarginRebateRate <= 5)',
|
||||
1, 1, 0, 'SYSTEM', NOW());
|
||||
@@ -112,7 +113,7 @@ INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr
|
||||
'合约起息日早于当前日期时触发审批',
|
||||
JSON_ARRAY(JSON_OBJECT(
|
||||
'VariableId', 2,
|
||||
'Operator', '<', 'ThresholdType', 'Variable',
|
||||
'Operator', '早于', 'ThresholdType', 'Variable',
|
||||
'ThresholdVariableId', 21
|
||||
)),
|
||||
'trade.StartDate < sys.CurrentDate',
|
||||
|
||||
@@ -304,12 +304,19 @@ namespace YLErp.Modules.RiskEngine
|
||||
{
|
||||
return op?.Trim() switch
|
||||
{
|
||||
// 数值型 C# 符号
|
||||
"=" or "==" => "==",
|
||||
"!=" or "≠" => "!=",
|
||||
">" => ">",
|
||||
"<" => "<",
|
||||
">=" => ">=",
|
||||
"<=" => "<=",
|
||||
// 日期型中文语义 → C# 符号
|
||||
"早于" => "<",
|
||||
"晚于" => ">",
|
||||
"等于" => "==",
|
||||
"不早于" => ">=",
|
||||
"不晚于" => "<=",
|
||||
_ => throw new ArgumentException($"不支持的操作符:{op}")
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,6 +11,6 @@ namespace YLErp.Modules.RiskEngine.Dto
|
||||
public string ScopeClientIds { get; set; }
|
||||
public string ScopeUnderlyingTypes { get; set; }
|
||||
public string ScopeTradeTypes { get; set; }
|
||||
public bool ScopeIsGlobal { get; set; }
|
||||
public int ScopeIsGlobal { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,5 +9,9 @@ namespace YLErp.Modules.RiskEngine.Dto
|
||||
public RiskRuleStatus? Status { get; set; }
|
||||
public RiskControlStrategy? Strategy { get; set; }
|
||||
public string TriggerPoint { get; set; }
|
||||
public string ScopeClientIds { get; set; }
|
||||
public string ScopeUnderlyingTypes { get; set; }
|
||||
public string ScopeTradeTypes { get; set; }
|
||||
public string ScopeAssetBookIds { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace YLErp.Modules.RiskEngine.Dto
|
||||
public class QueryRiskVariableReq : BaseSearchReq
|
||||
{
|
||||
public RiskVariableCategory? Category { get; set; }
|
||||
public RiskVariableDataType? DataType { get; set; }
|
||||
public string VariableName { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace YLErp.Modules.RiskEngine.Dto
|
||||
public string ScopeClientIds { get; set; }
|
||||
public string ScopeUnderlyingTypes { get; set; }
|
||||
public string ScopeTradeTypes { get; set; }
|
||||
public bool ScopeIsGlobal { get; set; }
|
||||
public int ScopeIsGlobal { get; set; }
|
||||
public int Version { get; set; }
|
||||
public string OptName { get; set; }
|
||||
public DateTime OptDate { get; set; }
|
||||
|
||||
@@ -7,14 +7,14 @@ namespace YLErp.Modules.RiskEngine.Dto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string RuleIds { get; set; }
|
||||
public RiskRuleStatus Status { get; set; }
|
||||
public int Status { get; set; }
|
||||
public RiskControlStrategy ControlStrategy { get; set; }
|
||||
public string TriggerPoints { get; set; }
|
||||
public string ScopeAssetBookIds { get; set; }
|
||||
public string ScopeClientIds { get; set; }
|
||||
public string ScopeUnderlyingTypes { get; set; }
|
||||
public string ScopeTradeTypes { get; set; }
|
||||
public bool ScopeIsGlobal { get; set; }
|
||||
public int ScopeIsGlobal { get; set; }
|
||||
public int Version { get; set; }
|
||||
public string OptName { get; set; }
|
||||
public DateTime OptDate { get; set; }
|
||||
|
||||
@@ -7,5 +7,8 @@ namespace YLErp.Modules.RiskEngine.Dto
|
||||
public string ThresholdType { get; set; }
|
||||
public object Value { get; set; }
|
||||
public long? ThresholdVariableId { get; set; }
|
||||
// 介于/不介于 的边界包含控制,默认 true
|
||||
public bool IncludeLowerBound { get; set; } = true;
|
||||
public bool IncludeUpperBound { get; set; } = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace YLErp.Modules.RiskEngine.Dto
|
||||
public string ScopeClientIds { get; set; }
|
||||
public string ScopeUnderlyingTypes { get; set; }
|
||||
public string ScopeTradeTypes { get; set; }
|
||||
public bool ScopeIsGlobal { get; set; }
|
||||
public int ScopeIsGlobal { get; set; }
|
||||
public int Version { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,7 +436,7 @@ namespace YLErp.Modules.RiskEngine
|
||||
return false;
|
||||
}
|
||||
|
||||
if (application.ScopeIsGlobal)
|
||||
if (application.ScopeIsGlobal == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace YLErp.Modules.RiskEngine
|
||||
/// true 表示对所有交易都适用,此时通常忽略其他 Scope 字段;
|
||||
/// false 表示只在指定范围内适用,需要结合下方各 Scope 字段进行过滤。
|
||||
/// </summary>
|
||||
public bool ScopeIsGlobal { get; set; }
|
||||
public int ScopeIsGlobal { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 适用的资产簿记账户范围。
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using BaseOUDAL;
|
||||
using Newtonsoft.Json;
|
||||
using Qdp.Foundation.Utilities;
|
||||
using System.Linq.Expressions;
|
||||
using YLErp.BLL;
|
||||
using YLErp.DBModels;
|
||||
using YLErp.Model;
|
||||
@@ -202,7 +203,7 @@ namespace YLErp.Modules.RiskEngine
|
||||
private static readonly Dictionary<RiskVariableDataType, HashSet<string>> ValidOperatorsByType = new Dictionary<RiskVariableDataType, HashSet<string>>
|
||||
{
|
||||
{ RiskVariableDataType.Numeric, new HashSet<string> { ">", "<", ">=", "<=", "=", "≠", "介于", "不介于" } },
|
||||
{ RiskVariableDataType.Date, new HashSet<string> { "<", ">", "=", ">=", "<=", "介于", "不介于" } },
|
||||
{ RiskVariableDataType.Date, new HashSet<string> { "早于", "晚于", "等于", "不早于", "不晚于", "介于", "不介于" } },
|
||||
{ RiskVariableDataType.Boolean, new HashSet<string> { "是", "否" } }
|
||||
};
|
||||
|
||||
@@ -291,15 +292,35 @@ namespace YLErp.Modules.RiskEngine
|
||||
{
|
||||
if (cond.Value == null)
|
||||
throw new ServiceException($"{condLabel}:固定阈值不能为空");
|
||||
if (variableDef.DataType == RiskVariableDataType.Numeric)
|
||||
|
||||
if (cond.Operator == "介于" || cond.Operator == "不介于")
|
||||
{
|
||||
if (!decimal.TryParse(cond.Value.ToString(), out _))
|
||||
throw new ServiceException($"{condLabel}:数值型变量的阈值必须为数字");
|
||||
// 介于/不介于:Value 必须为双元素数组 [下限, 上限]
|
||||
if (!(cond.Value is Newtonsoft.Json.Linq.JArray arr && arr.Count == 2))
|
||||
throw new ServiceException($"{condLabel}:操作符 '{cond.Operator}' 的阈值必须为双元素数组 [下限, 上限]");
|
||||
if (variableDef.DataType == RiskVariableDataType.Numeric)
|
||||
{
|
||||
if (!decimal.TryParse(arr[0].ToString(), out _) || !decimal.TryParse(arr[1].ToString(), out _))
|
||||
throw new ServiceException($"{condLabel}:数值型介于/不介于的下限和上限必须为数字");
|
||||
}
|
||||
if (variableDef.DataType == RiskVariableDataType.Date)
|
||||
{
|
||||
if (!DateTime.TryParse(arr[0].ToString(), out _) || !DateTime.TryParse(arr[1].ToString(), out _))
|
||||
throw new ServiceException($"{condLabel}:日期型介于/不介于的起始和结束日期必须为合法日期");
|
||||
}
|
||||
}
|
||||
if (variableDef.DataType == RiskVariableDataType.Date)
|
||||
else
|
||||
{
|
||||
if (!DateTime.TryParse(cond.Value.ToString(), out _))
|
||||
throw new ServiceException($"{condLabel}:日期型变量的阈值必须为合法日期");
|
||||
if (variableDef.DataType == RiskVariableDataType.Numeric)
|
||||
{
|
||||
if (!decimal.TryParse(cond.Value.ToString(), out _))
|
||||
throw new ServiceException($"{condLabel}:数值型变量的阈值必须为数字");
|
||||
}
|
||||
if (variableDef.DataType == RiskVariableDataType.Date)
|
||||
{
|
||||
if (!DateTime.TryParse(cond.Value.ToString(), out _))
|
||||
throw new ServiceException($"{condLabel}:日期型变量的阈值必须为合法日期");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (cond.ThresholdType == "Variable")
|
||||
@@ -356,7 +377,7 @@ namespace YLErp.Modules.RiskEngine
|
||||
|
||||
private void ValidateScopeFields(CreateRiskApplicationReq req)
|
||||
{
|
||||
if (req.ScopeIsGlobal)
|
||||
if (req.ScopeIsGlobal == 1)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(req.ScopeAssetBookIds) ||
|
||||
!string.IsNullOrEmpty(req.ScopeClientIds) ||
|
||||
@@ -513,6 +534,36 @@ namespace YLErp.Modules.RiskEngine
|
||||
return string.Join(", ", names);
|
||||
}
|
||||
|
||||
private static string GetStrategyName(RiskControlStrategy strategy)
|
||||
{
|
||||
return strategy switch
|
||||
{
|
||||
RiskControlStrategy.Block => "阻断",
|
||||
RiskControlStrategy.Approval => "审批",
|
||||
RiskControlStrategy.ShowTip => "提醒",
|
||||
_ => strategy.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
private static readonly Dictionary<string, string> TriggerPointCnMap = new Dictionary<string, string>
|
||||
{
|
||||
["BOOK_CONFIRM"] = "交易录入确认",
|
||||
["CLOSE_REVIEW"] = "平仓审核",
|
||||
["UPLOAD_CONFIRMATION"] = "上传确认书",
|
||||
["EVENT_TRIGGER"] = "事件触发",
|
||||
["FUND_PAYMENT"] = "资金支付"
|
||||
};
|
||||
|
||||
private static string MapTriggerPointsToChinese(string triggerPoints)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(triggerPoints))
|
||||
return "";
|
||||
|
||||
var parts = triggerPoints.Split(',');
|
||||
var translated = parts.Select(p => TriggerPointCnMap.TryGetValue(p.Trim(), out var cn) ? cn : p.Trim());
|
||||
return string.Join(", ", translated);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Rule Management
|
||||
@@ -813,7 +864,7 @@ namespace YLErp.Modules.RiskEngine
|
||||
{
|
||||
Id = app.id,
|
||||
RuleIds = app.RuleIds,
|
||||
Status = app.Status,
|
||||
Status = (int)app.Status,
|
||||
ControlStrategy = app.ControlStrategy,
|
||||
TriggerPoints = app.TriggerPoints,
|
||||
ScopeAssetBookIds = app.ScopeAssetBookIds,
|
||||
@@ -878,12 +929,84 @@ namespace YLErp.Modules.RiskEngine
|
||||
&& r.RuleName.Contains(req.RuleName)));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(req.ScopeClientIds))
|
||||
{
|
||||
var ids = req.ScopeClientIds.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||
Expression<Func<glms_risk_rule_application, bool>> scopePredicate = null;
|
||||
foreach (var id in ids)
|
||||
{
|
||||
var idCopy = id;
|
||||
Expression<Func<glms_risk_rule_application, bool>> expr = a =>
|
||||
a.ScopeClientIds == idCopy ||
|
||||
a.ScopeClientIds.StartsWith(idCopy + ",") ||
|
||||
a.ScopeClientIds.EndsWith("," + idCopy) ||
|
||||
a.ScopeClientIds.Contains("," + idCopy + ",");
|
||||
scopePredicate = scopePredicate == null ? expr : scopePredicate.Or(expr);
|
||||
}
|
||||
if (scopePredicate != null)
|
||||
query = query.Where(scopePredicate);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(req.ScopeUnderlyingTypes))
|
||||
{
|
||||
var types = req.ScopeUnderlyingTypes.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||
Expression<Func<glms_risk_rule_application, bool>> scopePredicate = null;
|
||||
foreach (var type in types)
|
||||
{
|
||||
var typeCopy = type;
|
||||
Expression<Func<glms_risk_rule_application, bool>> expr = a =>
|
||||
a.ScopeUnderlyingTypes == typeCopy ||
|
||||
a.ScopeUnderlyingTypes.StartsWith(typeCopy + ",") ||
|
||||
a.ScopeUnderlyingTypes.EndsWith("," + typeCopy) ||
|
||||
a.ScopeUnderlyingTypes.Contains("," + typeCopy + ",");
|
||||
scopePredicate = scopePredicate == null ? expr : scopePredicate.Or(expr);
|
||||
}
|
||||
if (scopePredicate != null)
|
||||
query = query.Where(scopePredicate);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(req.ScopeTradeTypes))
|
||||
{
|
||||
var types = req.ScopeTradeTypes.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||
Expression<Func<glms_risk_rule_application, bool>> scopePredicate = null;
|
||||
foreach (var type in types)
|
||||
{
|
||||
var typeCopy = type;
|
||||
Expression<Func<glms_risk_rule_application, bool>> expr = a =>
|
||||
a.ScopeTradeTypes == typeCopy ||
|
||||
a.ScopeTradeTypes.StartsWith(typeCopy + ",") ||
|
||||
a.ScopeTradeTypes.EndsWith("," + typeCopy) ||
|
||||
a.ScopeTradeTypes.Contains("," + typeCopy + ",");
|
||||
scopePredicate = scopePredicate == null ? expr : scopePredicate.Or(expr);
|
||||
}
|
||||
if (scopePredicate != null)
|
||||
query = query.Where(scopePredicate);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(req.ScopeAssetBookIds))
|
||||
{
|
||||
var ids = req.ScopeAssetBookIds.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||
Expression<Func<glms_risk_rule_application, bool>> scopePredicate = null;
|
||||
foreach (var id in ids)
|
||||
{
|
||||
var idCopy = id;
|
||||
Expression<Func<glms_risk_rule_application, bool>> expr = a =>
|
||||
a.ScopeAssetBookIds == idCopy ||
|
||||
a.ScopeAssetBookIds.StartsWith(idCopy + ",") ||
|
||||
a.ScopeAssetBookIds.EndsWith("," + idCopy) ||
|
||||
a.ScopeAssetBookIds.Contains("," + idCopy + ",");
|
||||
scopePredicate = scopePredicate == null ? expr : scopePredicate.Or(expr);
|
||||
}
|
||||
if (scopePredicate != null)
|
||||
query = query.Where(scopePredicate);
|
||||
}
|
||||
|
||||
var pagedResult = query.OrderByDescending(a => a.UpdateDate)
|
||||
.Select(a => new RiskApplicationListItem
|
||||
{
|
||||
Id = a.id,
|
||||
RuleIds = a.RuleIds,
|
||||
Status = a.Status,
|
||||
Status = (int)a.Status,
|
||||
ControlStrategy = a.ControlStrategy,
|
||||
TriggerPoints = a.TriggerPoints,
|
||||
ScopeAssetBookIds = a.ScopeAssetBookIds,
|
||||
@@ -962,7 +1085,7 @@ namespace YLErp.Modules.RiskEngine
|
||||
DbContext.SaveChanges();
|
||||
|
||||
WriteAuditLog("APP_CREATE", "APPLICATION", entity.id, ResolveRuleNames(req.RuleIds),
|
||||
$"创建应用配置:策略={req.ControlStrategy}, 触发时点={req.TriggerPoints}",
|
||||
$"创建应用配置:策略={GetStrategyName(req.ControlStrategy)}, 触发时点={MapTriggerPointsToChinese(req.TriggerPoints)}",
|
||||
snapshotData: JsonConvert.SerializeObject(new { Version = entity.Version }));
|
||||
DbContext.SaveChanges();
|
||||
|
||||
@@ -1016,7 +1139,7 @@ namespace YLErp.Modules.RiskEngine
|
||||
app.UpdateDate = DateTime.Now;
|
||||
|
||||
WriteAuditLog("APP_UPDATE", "APPLICATION", applicationId, ResolveRuleNames(app.RuleIds),
|
||||
$"修改应用配置:策略={req.ControlStrategy}, 触发时点={req.TriggerPoints}");
|
||||
$"修改应用配置:策略={GetStrategyName(req.ControlStrategy)}, 触发时点={MapTriggerPointsToChinese(req.TriggerPoints)}");
|
||||
DbContext.SaveChanges();
|
||||
|
||||
TryRefreshCache();
|
||||
@@ -1034,7 +1157,7 @@ namespace YLErp.Modules.RiskEngine
|
||||
app.UpdateOptName = UserName;
|
||||
app.UpdateDate = DateTime.Now;
|
||||
|
||||
WriteAuditLog("APPLICATION_DELETE", "APPLICATION", applicationId, ResolveRuleNames(app.RuleIds), "删除应用配置");
|
||||
WriteAuditLog("APP_DELETE", "APPLICATION", applicationId, ResolveRuleNames(app.RuleIds), "删除应用配置");
|
||||
DbContext.SaveChanges();
|
||||
|
||||
TryRefreshCache();
|
||||
@@ -1127,7 +1250,7 @@ namespace YLErp.Modules.RiskEngine
|
||||
app.UpdateOptName = UserName;
|
||||
app.UpdateDate = DateTime.Now;
|
||||
|
||||
WriteAuditLog("APPLICATION_BATCH_DISABLE", "APPLICATION", app.id, ResolveRuleNames(app.RuleIds), "批量停用应用配置");
|
||||
WriteAuditLog("APP_BATCH_DISABLE", "APPLICATION", app.id, ResolveRuleNames(app.RuleIds), "批量停用应用配置");
|
||||
}
|
||||
|
||||
DbContext.SaveChanges();
|
||||
@@ -1154,6 +1277,11 @@ namespace YLErp.Modules.RiskEngine
|
||||
query = query.Where(v => v.Category == req.Category.Value);
|
||||
}
|
||||
|
||||
if (req.DataType.HasValue)
|
||||
{
|
||||
query = query.Where(v => v.DataType == req.DataType.Value);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(req.VariableName))
|
||||
{
|
||||
query = query.Where(v => v.VariableName.Contains(req.VariableName));
|
||||
@@ -1365,12 +1493,13 @@ namespace YLErp.Modules.RiskEngine
|
||||
|
||||
if (req.StartDate.HasValue)
|
||||
{
|
||||
query = query.Where(l => l.OptDate >= req.StartDate.Value);
|
||||
query = query.Where(l => l.OptDate >= req.StartDate.Value.Date);
|
||||
}
|
||||
|
||||
if (req.EndDate.HasValue)
|
||||
{
|
||||
query = query.Where(l => l.OptDate <= req.EndDate.Value);
|
||||
var end = req.EndDate.Value.Date.AddDays(1);
|
||||
query = query.Where(l => l.OptDate < end);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(req.Keyword))
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
{Name:"资金监控",Rights:["风险控制-资金监控"],Url:"client/clientRiskMonitor"},
|
||||
{Name:"市场风险",Rights:["风险控制-市场风险"],Url:"risk/RiskExposureReport"},
|
||||
{Name:"限额监控",Rights:["风险控制-限额监控"],Url:"risk/quotaMonitor"},
|
||||
{Name:"异常交易监控",Rights:["风险控制-异常交易监控"],Url:"v3//risk/risk-engine-config"},
|
||||
{Name:"白名单券池",Rights:["风险控制-白名单券池"],Url:"v3/data/underlying-pool"},
|
||||
{Name:"日终持仓风险",Rights:["风险控制-日终持仓风险"],Url:"trade/EodPositionRisks"},
|
||||
{Name:"日终持仓风险_互换",Rights:["风险控制-日终持仓风险_互换"],Url:"swaptrade2/EodPositionRisks"},
|
||||
|
||||
Reference in New Issue
Block a user