feat:增加日历与价格偏离辅助函数,修复日志过长无法落库的问题,修复老风控特批、新风控不批时,前端展示为提示框,不是细节框的问题。
This commit is contained in:
@@ -359,6 +359,8 @@ namespace YLErp.Modules.RiskEngine
|
||||
var options = ScriptOptions.Default
|
||||
.WithReferences(
|
||||
typeof(RiskContext).Assembly,
|
||||
typeof(RiskCalendarHelper).Assembly,
|
||||
typeof(RiskMarketDeviationHelper).Assembly,
|
||||
typeof(YLContext).Assembly,
|
||||
typeof(YLErp.DBModels.trade).Assembly,
|
||||
typeof(QdpCalendarHelper).Assembly,
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using YLErp.BLL;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// 风控规则专用日历辅助类。
|
||||
/// 当前主要用于债券类规则按银行间日历确认“上一收盘日”,避免简单按估值表倒序取最近日期导致口径偏差。
|
||||
/// </summary>
|
||||
public static class RiskCalendarHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取指定日期的上一银行间交易日。
|
||||
/// </summary>
|
||||
/// <param name="dbContext">当前风控执行使用的数据库上下文。</param>
|
||||
/// <param name="date">当前交易日或业务基准日。</param>
|
||||
/// <returns>严格早于入参日期的上一银行间交易日。</returns>
|
||||
/// <exception cref="ArgumentNullException">数据库上下文为空。</exception>
|
||||
/// <exception cref="Exception">缺少银行间日历、日历内容异常或在保护范围内找不到上一交易日。</exception>
|
||||
public static DateTime GetPreviousInterbankTradingDay(YLContext dbContext, DateTime date)
|
||||
{
|
||||
if (dbContext == null)
|
||||
throw new ArgumentNullException(nameof(dbContext));
|
||||
|
||||
var holidayCache = new Dictionary<int, HashSet<string>>();
|
||||
var currentDate = date.Date.AddDays(-1);
|
||||
|
||||
// 最多向前查 370 天,既覆盖跨年和长假场景,也避免日历配置异常时出现无限循环。
|
||||
for (var i = 0; i < 370; i++)
|
||||
{
|
||||
var holidays = GetInterbankHolidays(dbContext, currentDate.Year, holidayCache);
|
||||
var currentDateText = currentDate.ToString("yyyy,MM,dd", CultureInfo.InvariantCulture);
|
||||
|
||||
// calendar.HolidayJson 存的是非交易日;不在非交易日集合内,即认为是银行间交易日。
|
||||
if (!holidays.Contains(currentDateText))
|
||||
return currentDate;
|
||||
|
||||
currentDate = currentDate.AddDays(-1);
|
||||
}
|
||||
|
||||
throw new Exception($"未找到{date:yyyy-MM-dd}的上一银行间交易日");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定年份的银行间非交易日集合。
|
||||
/// </summary>
|
||||
/// <param name="dbContext">当前风控执行使用的数据库上下文。</param>
|
||||
/// <param name="year">日历年份。</param>
|
||||
/// <param name="holidayCache">单次查询过程内的年份级缓存,跨年查找时避免重复读取同一年日历。</param>
|
||||
/// <returns>格式为 yyyy,MM,dd 的非交易日集合。</returns>
|
||||
private static HashSet<string> GetInterbankHolidays(YLContext dbContext, int year, Dictionary<int, HashSet<string>> holidayCache)
|
||||
{
|
||||
if (holidayCache.TryGetValue(year, out var holidays))
|
||||
return holidays;
|
||||
|
||||
// 同一年可能存在多种市场日历;规则 12 明确使用 Country=IB 的银行间日历。
|
||||
var calendar = dbContext.calendar
|
||||
.Where(c => c.Year == year && (c.ValidState == null || c.ValidState != ConsGlobal.InValid))
|
||||
.ToList()
|
||||
.FirstOrDefault(c => string.Equals(c.Country, "IB", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (calendar == null)
|
||||
throw new Exception($"未找到{year}年银行间日历");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(calendar.HolidayJson))
|
||||
throw new Exception($"{year}年银行间日历HolidayJson为空");
|
||||
|
||||
List<string> holidayList;
|
||||
try
|
||||
{
|
||||
holidayList = JsonConvert.DeserializeObject<List<string>>(calendar.HolidayJson);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"{year}年银行间日历HolidayJson解析失败", ex);
|
||||
}
|
||||
|
||||
holidays = new HashSet<string>(holidayList ?? new List<string>());
|
||||
holidayCache[year] = holidays;
|
||||
return holidays;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using YLErp.BLL;
|
||||
|
||||
namespace YLErp.Modules.RiskEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// 风控行情偏离类变量辅助方法,统一封装债券中债估值偏离和非债券行情价格偏离的取数、计算和命中明细生成逻辑。
|
||||
/// </summary>
|
||||
public static class RiskMarketDeviationHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 计算当前交易所有浮动支付端的债券类净价偏离值,返回最大偏离值用于规则比较。
|
||||
/// </summary>
|
||||
/// <param name="dbContext">数据库上下文。</param>
|
||||
/// <param name="tradeId">当前交易ID。</param>
|
||||
/// <returns>包含最大净价偏离值和逐笔偏离明细的变量返回值。</returns>
|
||||
public static RiskVariableValueDetail GetBondNetPriceDeviation(YLContext dbContext, int tradeId)
|
||||
{
|
||||
return GetBondValuationDeviation(
|
||||
dbContext,
|
||||
tradeId,
|
||||
"债券类净价偏离",
|
||||
"期初交割净价",
|
||||
"中债估值净价",
|
||||
p => p.PosiNetNoFeePrice,
|
||||
v => v.net_price);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算当前交易所有浮动支付端的债券类收益率偏离值,返回最大偏离值用于规则比较。
|
||||
/// </summary>
|
||||
/// <param name="dbContext">数据库上下文。</param>
|
||||
/// <param name="tradeId">当前交易ID。</param>
|
||||
/// <returns>包含最大收益率偏离值和逐笔偏离明细的变量返回值。</returns>
|
||||
public static RiskVariableValueDetail GetBondYieldDeviation(YLContext dbContext, int tradeId)
|
||||
{
|
||||
return GetBondValuationDeviation(
|
||||
dbContext,
|
||||
tradeId,
|
||||
"债券类收益率偏离",
|
||||
"期初成交收益率",
|
||||
"中债估值收益率",
|
||||
p => p.InitYtm,
|
||||
v => v.yield);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算当前交易所有浮动支付端的非债券类价格偏离值,返回最大偏离值用于规则比较。
|
||||
/// </summary>
|
||||
/// <param name="dbContext">数据库上下文。</param>
|
||||
/// <param name="tradeId">当前交易ID。</param>
|
||||
/// <returns>包含最大价格偏离值和逐笔偏离明细的变量返回值。</returns>
|
||||
public static RiskVariableValueDetail GetNonBondPriceDeviation(YLContext dbContext, int tradeId)
|
||||
{
|
||||
if (dbContext == null)
|
||||
throw new ArgumentNullException(nameof(dbContext));
|
||||
|
||||
var tradeDate = GetTradeDate(dbContext, tradeId);
|
||||
var floatingPositions = GetFloatingPaymentPositions(dbContext, tradeId)
|
||||
.Select(p => new
|
||||
{
|
||||
p.id,
|
||||
p.UnderlyingCode,
|
||||
p.PosiGrossPrice
|
||||
})
|
||||
.ToList();
|
||||
|
||||
if (!floatingPositions.Any())
|
||||
throw new Exception("浮动支付端记录不存在");
|
||||
|
||||
var underlyingCodes = floatingPositions
|
||||
.Select(p => p.UnderlyingCode)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
// 非债券类取交易日前最近一条行情,不使用银行间日历,也不要求行情日等于上一银行间交易日。
|
||||
var eodRows = dbContext.eod_commodity_future_price
|
||||
.Where(e => underlyingCodes.Contains(e.UnderlyingCode)
|
||||
&& e.ValueDate < tradeDate)
|
||||
.Select(e => new
|
||||
{
|
||||
e.id,
|
||||
e.UnderlyingCode,
|
||||
e.ValueDate,
|
||||
e.ClosePrice
|
||||
})
|
||||
.ToList();
|
||||
|
||||
// 先按标的批量查出行情,再在内存中分组取最近日,避免每条浮动支付端单独访问数据库。
|
||||
var eodByUnderlyingCode = eodRows
|
||||
.GroupBy(e => e.UnderlyingCode)
|
||||
.ToDictionary(
|
||||
g => g.Key,
|
||||
g => g.OrderByDescending(e => e.ValueDate).ThenBy(e => e.id).First());
|
||||
|
||||
var valuationItems = floatingPositions
|
||||
.Select(p => new
|
||||
{
|
||||
Position = p,
|
||||
Eod = eodByUnderlyingCode.ContainsKey(p.UnderlyingCode) ? eodByUnderlyingCode[p.UnderlyingCode] : null
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var missingEodItems = valuationItems
|
||||
.Where(x => x.Eod == null)
|
||||
.Select(x => $"记录ID {x.Position.id},标的{x.Position.UnderlyingCode}")
|
||||
.ToList();
|
||||
|
||||
if (missingEodItems.Any())
|
||||
throw new Exception($"未找到交易日前行情收盘价:" + string.Join(";", missingEodItems));
|
||||
|
||||
var diffItems = valuationItems
|
||||
.Select(x => new
|
||||
{
|
||||
PositionId = x.Position.id,
|
||||
UnderlyingCode = x.Position.UnderlyingCode,
|
||||
PositionPrice = x.Position.PosiGrossPrice * 100m,
|
||||
MarketDate = x.Eod.ValueDate,
|
||||
MarketPrice = Convert.ToDecimal(x.Eod.ClosePrice),
|
||||
DiffAbs = Math.Abs(x.Position.PosiGrossPrice * 100m - Convert.ToDecimal(x.Eod.ClosePrice))
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return BuildDeviationDetail(
|
||||
diffItems.Select(x => new DeviationItem
|
||||
{
|
||||
PositionId = x.PositionId,
|
||||
UnderlyingCode = x.UnderlyingCode,
|
||||
PositionValue = x.PositionPrice,
|
||||
MarketDate = x.MarketDate,
|
||||
MarketValue = x.MarketPrice,
|
||||
DiffAbs = x.DiffAbs
|
||||
}).ToList(),
|
||||
"非债券类价格偏离",
|
||||
"期初标的价格",
|
||||
"上一行情收盘价");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 债券类中债估值偏离的公共计算入口,净价偏离和收益率偏离仅通过字段选择器区分取值字段。
|
||||
/// </summary>
|
||||
/// <param name="dbContext">数据库上下文。</param>
|
||||
/// <param name="tradeId">当前交易ID。</param>
|
||||
/// <param name="deviationName">偏离规则名称,用于生成命中明细。</param>
|
||||
/// <param name="positionValueName">交易侧取值名称,用于生成命中明细。</param>
|
||||
/// <param name="marketValueName">市场估值取值名称,用于生成命中明细。</param>
|
||||
/// <param name="positionValueSelector">交易侧字段选择器。</param>
|
||||
/// <param name="valuationValueSelector">中债估值字段选择器。</param>
|
||||
/// <returns>包含最大偏离值和逐笔偏离明细的变量返回值。</returns>
|
||||
private static RiskVariableValueDetail GetBondValuationDeviation(
|
||||
YLContext dbContext,
|
||||
int tradeId,
|
||||
string deviationName,
|
||||
string positionValueName,
|
||||
string marketValueName,
|
||||
Func<YLErp.DBModels.swap_position, decimal?> positionValueSelector,
|
||||
Func<YLErp.DBModels.ChinaBondValuation, decimal?> valuationValueSelector)
|
||||
{
|
||||
if (dbContext == null)
|
||||
throw new ArgumentNullException(nameof(dbContext));
|
||||
|
||||
var tradeDate = GetTradeDate(dbContext, tradeId);
|
||||
var previousTradingDay = RiskCalendarHelper.GetPreviousInterbankTradingDay(dbContext, tradeDate);
|
||||
var nextTradingDate = previousTradingDay.AddDays(1);
|
||||
var floatingPositions = GetFloatingPaymentPositions(dbContext, tradeId).ToList();
|
||||
|
||||
if (!floatingPositions.Any())
|
||||
throw new Exception("浮动支付端记录不存在");
|
||||
|
||||
var positionItems = floatingPositions
|
||||
.Select(p => new
|
||||
{
|
||||
p.id,
|
||||
p.UnderlyingCode,
|
||||
PositionValue = positionValueSelector(p)
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var missingPositionValueIds = positionItems
|
||||
.Where(p => !p.PositionValue.HasValue)
|
||||
.Select(p => p.id.ToString())
|
||||
.ToList();
|
||||
|
||||
if (missingPositionValueIds.Any())
|
||||
throw new Exception($"浮动支付端{positionValueName}为空,记录ID:" + string.Join("、", missingPositionValueIds));
|
||||
|
||||
var underlyingCodes = positionItems
|
||||
.Select(p => p.UnderlyingCode)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
// 债券类必须严格匹配上一银行间交易日当天的中债估值,不能简单取交易日前最近估值日。
|
||||
var valuationRows = dbContext.china_bond_valuation
|
||||
.Where(v => underlyingCodes.Contains(v.bond_id)
|
||||
&& v.valuation_date >= previousTradingDay
|
||||
&& v.valuation_date < nextTradingDate)
|
||||
.ToList()
|
||||
.Select(v => new
|
||||
{
|
||||
v.id,
|
||||
v.bond_id,
|
||||
v.valuation_date,
|
||||
v.credibility,
|
||||
ValuationValue = valuationValueSelector(v)
|
||||
})
|
||||
.Where(v => v.ValuationValue.HasValue)
|
||||
.ToList();
|
||||
|
||||
// 同一标的同一估值日可能有多条来源,按可信度优先,ID兜底稳定排序。
|
||||
var valuationByBondId = valuationRows
|
||||
.GroupBy(v => v.bond_id)
|
||||
.ToDictionary(
|
||||
g => g.Key,
|
||||
g => g.OrderBy(v => v.credibility).ThenBy(v => v.id).First());
|
||||
|
||||
var valuationItems = positionItems
|
||||
.Select(p => new
|
||||
{
|
||||
Position = p,
|
||||
Valuation = valuationByBondId.ContainsKey(p.UnderlyingCode) ? valuationByBondId[p.UnderlyingCode] : null
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var missingValuationItems = valuationItems
|
||||
.Where(x => x.Valuation == null)
|
||||
.Select(x => $"记录ID {x.Position.id},标的{x.Position.UnderlyingCode}")
|
||||
.ToList();
|
||||
|
||||
if (missingValuationItems.Any())
|
||||
throw new Exception($"未找到上一银行间交易日{previousTradingDay:yyyy-MM-dd}的{marketValueName}:" + string.Join(";", missingValuationItems));
|
||||
|
||||
var diffItems = valuationItems
|
||||
.Select(x => new DeviationItem
|
||||
{
|
||||
PositionId = x.Position.id,
|
||||
UnderlyingCode = x.Position.UnderlyingCode,
|
||||
PositionValue = x.Position.PositionValue.Value * 100m,
|
||||
MarketDate = x.Valuation.valuation_date,
|
||||
MarketValue = x.Valuation.ValuationValue.Value,
|
||||
DiffAbs = Math.Abs(x.Position.PositionValue.Value * 100m - x.Valuation.ValuationValue.Value)
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return BuildDeviationDetail(diffItems, deviationName, positionValueName, marketValueName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前交易的交易日,所有行情偏离规则都以交易日作为市场数据取数基准。
|
||||
/// </summary>
|
||||
/// <param name="dbContext">数据库上下文。</param>
|
||||
/// <param name="tradeId">当前交易ID。</param>
|
||||
/// <returns>交易日日期部分。</returns>
|
||||
private static DateTime GetTradeDate(YLContext dbContext, int tradeId)
|
||||
{
|
||||
var tradeDate = dbContext.trade
|
||||
.Where(t => t.id == tradeId)
|
||||
.Select(t => t.TradeDate)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (!tradeDate.HasValue)
|
||||
throw new Exception("交易日为空");
|
||||
|
||||
return tradeDate.Value.Date;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前交易下全部浮动支付端记录,行情偏离类规则需要遍历同一TradeId下所有浮动支付端。
|
||||
/// </summary>
|
||||
/// <param name="dbContext">数据库上下文。</param>
|
||||
/// <param name="tradeId">当前交易ID。</param>
|
||||
/// <returns>浮动支付端记录查询对象。</returns>
|
||||
private static IQueryable<YLErp.DBModels.swap_position> GetFloatingPaymentPositions(YLContext dbContext, int tradeId)
|
||||
{
|
||||
return dbContext.swap_position
|
||||
.Where(p => p.SwapTradeId == tradeId
|
||||
&& p.IsInitial
|
||||
&& !p.Invalid
|
||||
&& p.PosiDirection == 2
|
||||
&& !string.IsNullOrEmpty(p.UnderlyingCode));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 统一生成行情偏离类变量返回值,变量值取最大偏离值,命中说明保留逐笔偏离明细。
|
||||
/// </summary>
|
||||
/// <param name="diffItems">逐笔偏离结果。</param>
|
||||
/// <param name="deviationName">偏离规则名称。</param>
|
||||
/// <param name="positionValueName">交易侧取值名称。</param>
|
||||
/// <param name="marketValueName">市场侧取值名称。</param>
|
||||
/// <returns>包含最大偏离值和逐笔偏离明细的变量返回值。</returns>
|
||||
private static RiskVariableValueDetail BuildDeviationDetail(
|
||||
List<DeviationItem> diffItems,
|
||||
string deviationName,
|
||||
string positionValueName,
|
||||
string marketValueName)
|
||||
{
|
||||
var maxDiffItem = diffItems
|
||||
.OrderByDescending(x => x.DiffAbs)
|
||||
.ThenBy(x => x.PositionId)
|
||||
.First();
|
||||
|
||||
var deviatedItems = diffItems
|
||||
.Where(x => x.DiffAbs > 0m)
|
||||
.OrderByDescending(x => x.DiffAbs)
|
||||
.ThenBy(x => x.PositionId)
|
||||
.Select(x => $"记录ID {x.PositionId},标的{x.UnderlyingCode}:{positionValueName}{FormatDecimal(x.PositionValue)},{x.MarketDate:yyyy-MM-dd}{marketValueName}{FormatDecimal(x.MarketValue)},偏离{FormatDecimal(x.DiffAbs)}")
|
||||
.ToList();
|
||||
|
||||
string diffMessage = deviatedItems.Any()
|
||||
? $"存在{deviationName}的浮动支付端记录:" + string.Join(";", deviatedItems)
|
||||
: $"未发现{deviationName}记录";
|
||||
|
||||
return new RiskVariableValueDetail(maxDiffItem.DiffAbs, diffMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格式化风控命中说明中的数值,避免展示过长小数。
|
||||
/// </summary>
|
||||
/// <param name="value">待格式化数值。</param>
|
||||
/// <returns>最多9位小数的展示文本。</returns>
|
||||
private static string FormatDecimal(decimal value)
|
||||
{
|
||||
return value.ToString("0.#########");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 行情偏离计算的中间结果模型,用于把债券和非债券两类计算结果统一交给明细构建逻辑。
|
||||
/// </summary>
|
||||
private class DeviationItem
|
||||
{
|
||||
public long PositionId { get; set; }
|
||||
public string UnderlyingCode { get; set; }
|
||||
public decimal PositionValue { get; set; }
|
||||
public DateTime MarketDate { get; set; }
|
||||
public decimal MarketValue { get; set; }
|
||||
public decimal DiffAbs { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -576,6 +576,12 @@ namespace YLErp.Modules.RiskEngine
|
||||
return dateValue.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
|
||||
if (value is bool boolValue)
|
||||
return boolValue ? "是" : "否";
|
||||
if (value is decimal decimalValue)
|
||||
return decimalValue.ToString("0.#########", CultureInfo.InvariantCulture);
|
||||
if (value is double doubleValue)
|
||||
return doubleValue.ToString("0.#########", CultureInfo.InvariantCulture);
|
||||
if (value is float floatValue)
|
||||
return floatValue.ToString("0.#########", CultureInfo.InvariantCulture);
|
||||
return Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
|
||||
@@ -1546,6 +1546,28 @@ WHERE t.id = @TradeId;
|
||||
开仓名义本金 > 100000000
|
||||
```
|
||||
|
||||
变量 Roslyn 示例:变量名为“开仓名义本金”,DataType 为 Numeric;规则前端配置“开仓名义本金 > 阈值”。`OriginalStockEqvNotional` 在实体模型中为 `double?`,脚本中转为 `decimal` 后参与数值比较。
|
||||
|
||||
```csharp
|
||||
double? openingNotionalRaw = DbContext.trade
|
||||
.Where(t => t.id == TradeId)
|
||||
.Select(t => t.OriginalStockEqvNotional)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (!openingNotionalRaw.HasValue)
|
||||
{
|
||||
return new RiskVariableValueDetail(
|
||||
0m,
|
||||
"开仓名义本金为空,本规则不命中");
|
||||
}
|
||||
|
||||
decimal openingNotional = (decimal)openingNotionalRaw.Value;
|
||||
|
||||
return new RiskVariableValueDetail(
|
||||
openingNotional,
|
||||
$"开仓名义本金为{openingNotional}");
|
||||
```
|
||||
|
||||
规则字段口径:
|
||||
|
||||
```text
|
||||
@@ -1613,14 +1635,22 @@ WHERE t.id = @TradeId;
|
||||
变量 Roslyn 示例:变量名为“保证金支付比例”,DataType 为 Numeric;规则前端仍配置“保证金支付比例 > 0.5”。字段类型需为 decimal / decimal?,否则需要字段层调整或显式类型转换。
|
||||
|
||||
```csharp
|
||||
decimal marginPaymentAmount = DbContext.swap_position
|
||||
var marginPaymentItems = DbContext.swap_position
|
||||
.Where(p => p.SwapTradeId == TradeId
|
||||
&& p.IsInitial
|
||||
&& !p.Invalid
|
||||
&& (p.InterestMode == 5 || p.InterestMode == 6)
|
||||
&& p.InterestDirection == 2)
|
||||
.Select(p => p.InterestPrincipalFix)
|
||||
.Sum();
|
||||
.Select(p => new
|
||||
{
|
||||
p.id,
|
||||
p.InterestPrincipalFix,
|
||||
p.InterestRateDefault,
|
||||
p.HappenDate
|
||||
})
|
||||
.ToList();
|
||||
|
||||
decimal marginPaymentAmount = marginPaymentItems.Sum(p => p.InterestPrincipalFix);
|
||||
|
||||
double? openingNotionalRaw = DbContext.trade
|
||||
.Where(t => t.id == TradeId)
|
||||
@@ -1637,9 +1667,19 @@ if (openingNotional == 0m)
|
||||
|
||||
decimal marginPaymentRatio = marginPaymentAmount / openingNotional;
|
||||
|
||||
var marginPaymentDetails = marginPaymentItems
|
||||
.OrderBy(p => p.HappenDate)
|
||||
.ThenBy(p => p.id)
|
||||
.Select(p => $"保证金记录ID为{p.id},支付金额为{p.InterestPrincipalFix},返息率为{p.InterestRateDefault},发生日期为{p.HappenDate}")
|
||||
.ToList();
|
||||
|
||||
string marginPaymentMessage = marginPaymentDetails.Any()
|
||||
? string.Join(";", marginPaymentDetails)
|
||||
: "未查询到保证金支付记录";
|
||||
|
||||
return new RiskVariableValueDetail(
|
||||
marginPaymentRatio,
|
||||
$"保证金支付金额为{marginPaymentAmount},开仓名义本金为{openingNotional}");
|
||||
$"保证金支付总金额为{marginPaymentAmount},开仓名义本金为{openingNotional},{marginPaymentMessage}");
|
||||
```
|
||||
|
||||
规则字段口径:
|
||||
@@ -1747,28 +1787,44 @@ ORDER BY
|
||||
ABS((预付金返息率 - 1) * 100) > 阈值
|
||||
```
|
||||
|
||||
变量 Roslyn 示例:变量名为“保证金利率偏离”,DataType 为 Numeric;规则前端仍配置“保证金利率偏离 > 阈值”。`InterestRateDefault` 实体字段类型为 decimal,可直接参与 decimal 计算。
|
||||
变量 Roslyn 示例:变量名为“保证金利率偏离”,DataType 为 Numeric;规则前端仍配置“保证金利率偏离 > 阈值”。脚本会查询当前 TradeId 下所有符合口径的保证金记录,变量值返回最大偏离值用于判断,命中说明列出所有存在偏离的记录。`InterestRateDefault` 实体字段类型为 decimal,可直接参与 decimal 计算。
|
||||
|
||||
```csharp
|
||||
var marginPosition = DbContext.swap_position
|
||||
var marginRateItems = DbContext.swap_position
|
||||
.Where(p => p.SwapTradeId == TradeId
|
||||
&& p.IsInitial
|
||||
&& !p.Invalid
|
||||
&& (p.InterestMode == 5 || p.InterestMode == 6))
|
||||
.OrderBy(p => p.HappenDate)
|
||||
.ThenBy(p => p.id)
|
||||
.FirstOrDefault();
|
||||
.Select(p => new
|
||||
{
|
||||
p.id,
|
||||
p.InterestRateDefault,
|
||||
InterestRateDeviation = Math.Abs((p.InterestRateDefault - 1m) * 100m)
|
||||
})
|
||||
.ToList();
|
||||
|
||||
if (marginPosition == null)
|
||||
if (!marginRateItems.Any())
|
||||
throw new Exception("预付金记录不存在");
|
||||
|
||||
decimal interestRateRawValue = marginPosition.InterestRateDefault;
|
||||
decimal interestRatePercentValue = interestRateRawValue * 100m;
|
||||
decimal interestRateDeviation = Math.Abs((interestRateRawValue - 1m) * 100m);
|
||||
var maxDeviationItem = marginRateItems
|
||||
.OrderByDescending(p => p.InterestRateDeviation)
|
||||
.ThenBy(p => p.id)
|
||||
.First();
|
||||
|
||||
var deviatedItems = marginRateItems
|
||||
.Where(p => p.InterestRateDeviation > 0m)
|
||||
.OrderByDescending(p => p.InterestRateDeviation)
|
||||
.ThenBy(p => p.id)
|
||||
.Select(p => $"记录ID {p.id}:返息率{(p.InterestRateDefault * 100m).ToString("0.#########")}%,偏离{p.InterestRateDeviation.ToString("0.#########")}%")
|
||||
.ToList();
|
||||
|
||||
string deviationMessage = deviatedItems.Any()
|
||||
? "存在偏离的预付金记录:" + string.Join(";", deviatedItems)
|
||||
: "未发现保证金利率偏离记录";
|
||||
|
||||
return new RiskVariableValueDetail(
|
||||
interestRateDeviation,
|
||||
$"预付金返息率原值为{interestRateRawValue},页面百分比口径为{interestRatePercentValue},保证金利率偏离为{interestRateDeviation}");
|
||||
maxDeviationItem.InterestRateDeviation,
|
||||
deviationMessage);
|
||||
```
|
||||
|
||||
规则字段口径:
|
||||
@@ -1878,14 +1934,22 @@ ORDER BY
|
||||
变量 Roslyn 示例:变量名为“保证金收取比例”,DataType 为 Numeric;规则前端仍配置“保证金收取比例 < 0.2”。字段类型需为 decimal / decimal?,否则需要字段层调整或显式类型转换。
|
||||
|
||||
```csharp
|
||||
decimal marginReceiveAmount = DbContext.swap_position
|
||||
var marginReceiveItems = DbContext.swap_position
|
||||
.Where(p => p.SwapTradeId == TradeId
|
||||
&& p.IsInitial
|
||||
&& !p.Invalid
|
||||
&& (p.InterestMode == 5 || p.InterestMode == 6)
|
||||
&& p.InterestDirection == 1)
|
||||
.Select(p => p.InterestPrincipalFix)
|
||||
.Sum();
|
||||
.Select(p => new
|
||||
{
|
||||
p.id,
|
||||
p.InterestPrincipalFix,
|
||||
p.InterestRateDefault,
|
||||
p.HappenDate
|
||||
})
|
||||
.ToList();
|
||||
|
||||
decimal marginReceiveAmount = marginReceiveItems.Sum(p => p.InterestPrincipalFix);
|
||||
|
||||
double? openingNotionalRaw = DbContext.trade
|
||||
.Where(t => t.id == TradeId)
|
||||
@@ -1902,9 +1966,19 @@ if (openingNotional == 0m)
|
||||
|
||||
decimal marginReceiveRatio = marginReceiveAmount / openingNotional;
|
||||
|
||||
var marginReceiveDetails = marginReceiveItems
|
||||
.OrderBy(p => p.HappenDate)
|
||||
.ThenBy(p => p.id)
|
||||
.Select(p => $"保证金记录ID为{p.id},收取金额为{p.InterestPrincipalFix},返息率为{p.InterestRateDefault},发生日期为{p.HappenDate}")
|
||||
.ToList();
|
||||
|
||||
string marginReceiveMessage = marginReceiveDetails.Any()
|
||||
? string.Join(";", marginReceiveDetails)
|
||||
: "未查询到保证金收取记录";
|
||||
|
||||
return new RiskVariableValueDetail(
|
||||
marginReceiveRatio,
|
||||
$"保证金收取金额为{marginReceiveAmount},开仓名义本金为{openingNotional}");
|
||||
$"保证金收取总金额为{marginReceiveAmount},开仓名义本金为{openingNotional},{marginReceiveMessage}");
|
||||
```
|
||||
|
||||
规则字段口径:
|
||||
@@ -2011,6 +2085,36 @@ ORDER BY
|
||||
起息日 < 当前日期
|
||||
```
|
||||
|
||||
变量 Roslyn 示例:拆成两个 Date 类型变量,规则前端配置“起息日 < 今日”,右侧阈值类型选择变量“今日”。
|
||||
|
||||
变量 1:变量名为“起息日”,DataType 为 Date。为保持原公式 `StartDate.HasValue && StartDate.Value.Date < DateTime.Today` 的语义,起息日为空时返回当前日期,使本规则不命中,避免空值被当作执行异常。
|
||||
|
||||
```csharp
|
||||
DateTime? startDate = DbContext.trade
|
||||
.Where(t => t.id == TradeId)
|
||||
.Select(t => t.StartDate)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (!startDate.HasValue)
|
||||
{
|
||||
return new RiskVariableValueDetail(
|
||||
DateTime.Today,
|
||||
"起息日为空,本规则不命中");
|
||||
}
|
||||
|
||||
return new RiskVariableValueDetail(
|
||||
startDate.Value.Date,
|
||||
$"起息日为{startDate.Value.Date:yyyy-MM-dd}");
|
||||
```
|
||||
|
||||
变量 2:变量名为“今日”,DataType 为 Date。
|
||||
|
||||
```csharp
|
||||
return new RiskVariableValueDetail(
|
||||
DateTime.Today,
|
||||
$"当前日期为{DateTime.Today:yyyy-MM-dd}");
|
||||
```
|
||||
|
||||
规则字段口径:
|
||||
|
||||
```text
|
||||
@@ -2340,6 +2444,68 @@ WHERE t.id = @TradeId;
|
||||
(到期日.Date - 起始日.Date).Days + 是否算头 - 是否不算尾 > 阈值
|
||||
```
|
||||
|
||||
变量 Roslyn 示例:变量名为“合约期限天数”,DataType 为 Numeric;规则前端配置“合约期限天数 > 阈值”。变量内部统一读取起始日、到期日和计息方式,并返回实际合约期限天数。
|
||||
|
||||
```csharp
|
||||
var tradeInfo = DbContext.trade
|
||||
.Where(t => t.id == TradeId)
|
||||
.Select(t => new
|
||||
{
|
||||
t.StartDate,
|
||||
t.ExerciseDate
|
||||
})
|
||||
.FirstOrDefault();
|
||||
|
||||
if (tradeInfo == null)
|
||||
throw new Exception("交易不存在");
|
||||
|
||||
if (!tradeInfo.StartDate.HasValue || !tradeInfo.ExerciseDate.HasValue)
|
||||
{
|
||||
return new RiskVariableValueDetail(
|
||||
0m,
|
||||
$"起始日或到期日为空,起始日为{tradeInfo.StartDate?.ToString("yyyy-MM-dd") ?? "空"},到期日为{tradeInfo.ExerciseDate?.ToString("yyyy-MM-dd") ?? "空"},本规则不命中");
|
||||
}
|
||||
|
||||
var tradeExtend = DbContext.trade_extend
|
||||
.Where(e => e.TradeId == TradeId)
|
||||
.FirstOrDefault();
|
||||
|
||||
string interestCalcModeRaw = tradeExtend?.ExtendObj?.InterestCalcMode;
|
||||
string interestCalcMode = string.IsNullOrWhiteSpace(interestCalcModeRaw) ? "11" : interestCalcModeRaw;
|
||||
|
||||
if (interestCalcMode.Length != 2
|
||||
|| (interestCalcMode[0] != '0' && interestCalcMode[0] != '1')
|
||||
|| (interestCalcMode[1] != '0' && interestCalcMode[1] != '1'))
|
||||
{
|
||||
throw new Exception($"计息方式不合法:{interestCalcMode}");
|
||||
}
|
||||
|
||||
DateTime startDate = tradeInfo.StartDate.Value.Date;
|
||||
DateTime exerciseDate = tradeInfo.ExerciseDate.Value.Date;
|
||||
int baseNaturalDays = (exerciseDate - startDate).Days;
|
||||
int calcFirstDays = interestCalcMode.StartsWith("1") ? 1 : 0;
|
||||
int notCalcLastDays = interestCalcMode.EndsWith("1") ? 0 : -1;
|
||||
int contractNaturalDays = baseNaturalDays + calcFirstDays + notCalcLastDays;
|
||||
string interestCalcModeText = interestCalcMode == "00"
|
||||
? "不计头不计尾"
|
||||
: interestCalcMode == "01"
|
||||
? "不计头计尾"
|
||||
: interestCalcMode == "10"
|
||||
? "计头不计尾"
|
||||
: "计头计尾";
|
||||
string interestCalcModeDescription = interestCalcMode == "00"
|
||||
? "不计入起始日,不计入到期日"
|
||||
: interestCalcMode == "01"
|
||||
? "不计入起始日,计入到期日"
|
||||
: interestCalcMode == "10"
|
||||
? "计入起始日,不计入到期日"
|
||||
: "计入起始日,计入到期日";
|
||||
|
||||
return new RiskVariableValueDetail(
|
||||
contractNaturalDays,
|
||||
$"起始日为{startDate:yyyy-MM-dd},到期日为{exerciseDate:yyyy-MM-dd},计息方式为{interestCalcModeText},{interestCalcModeDescription}");
|
||||
```
|
||||
|
||||
规则字段口径:
|
||||
|
||||
```text
|
||||
@@ -2423,13 +2589,14 @@ WHERE t.id = @TradeId;
|
||||
取数流程:
|
||||
|
||||
```text
|
||||
1. 根据 TradeId 查 swap_position。
|
||||
2. 限定 IsInitial=1、Invalid=0、PosiDirection=2、UnderlyingCode 非空,取浮动支付端。
|
||||
3. 从浮动支付端取 PosiNetNoFeePrice 和 UnderlyingCode。
|
||||
4. 用 swap_position.UnderlyingCode 关联 china_bond_valuation.bond_id。
|
||||
5. 限定 valuation_date < trade.TradeDate,取交易日前估值。
|
||||
6. 按 credibility ASC、valuation_date DESC 排序,优先 credibility=1,再取最近估值日。
|
||||
7. 计算 ABS(PosiNetNoFeePrice * 100 - net_price),大于 5 则命中。
|
||||
1. 根据 TradeId 查 trade.TradeDate。
|
||||
2. 通过银行间日历 Country=IB 计算交易日的上一银行间交易日,查不到日历或上一交易日时报异常。
|
||||
3. 根据 TradeId 查 swap_position。
|
||||
4. 限定 IsInitial=1、Invalid=0、PosiDirection=2、UnderlyingCode 非空,取所有浮动支付端。
|
||||
5. 从浮动支付端取 PosiNetNoFeePrice 和 UnderlyingCode。
|
||||
6. 用 swap_position.UnderlyingCode 关联 china_bond_valuation.bond_id。
|
||||
7. 限定 valuation_date 为上一银行间交易日当天,优先取 credibility=1。
|
||||
8. 计算 ABS(PosiNetNoFeePrice * 100 - net_price),大于 5 则命中。
|
||||
```
|
||||
|
||||
规则公式:
|
||||
@@ -2438,6 +2605,13 @@ WHERE t.id = @TradeId;
|
||||
ABS(浮动支付端.PosiNetNoFeePrice * 100 - 上一收盘日中债估值.net_price) > 5
|
||||
```
|
||||
|
||||
变量 Roslyn 示例:变量名为“债券类净价偏离值”,DataType 为 Numeric;规则前端配置“债券类净价偏离值 > 阈值”。具体取数、上一银行间交易日确认、估值匹配、债券/非债券差异处理统一放在 `RiskMarketDeviationHelper` 中,变量公式只保留公共方法调用。
|
||||
|
||||
```csharp
|
||||
return YLErp.Modules.RiskEngine.RiskMarketDeviationHelper.GetBondNetPriceDeviation(DbContext, TradeId);
|
||||
```
|
||||
|
||||
|
||||
注释规则定义:
|
||||
|
||||
```csharp
|
||||
@@ -2461,9 +2635,42 @@ ABS(浮动支付端.PosiNetNoFeePrice * 100 - 上一收盘日中债估值.net_pr
|
||||
```sql
|
||||
SET @TradeId = 3001699;
|
||||
|
||||
WITH RECURSIVE candidate_dates AS (
|
||||
SELECT DATE(t.TradeDate) - INTERVAL 1 DAY AS CandidateDate
|
||||
FROM trade t
|
||||
WHERE t.id = @TradeId
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT CandidateDate - INTERVAL 1 DAY
|
||||
FROM candidate_dates
|
||||
WHERE CandidateDate > DATE_SUB((SELECT DATE(TradeDate) FROM trade WHERE id = @TradeId), INTERVAL 370 DAY)
|
||||
),
|
||||
previous_trading_day AS (
|
||||
SELECT cd.CandidateDate
|
||||
FROM candidate_dates cd
|
||||
INNER JOIN calendar c
|
||||
ON c.Year = YEAR(cd.CandidateDate)
|
||||
AND UPPER(c.Country) = 'IB'
|
||||
AND (c.ValidState IS NULL OR c.ValidState <> 'InValid')
|
||||
WHERE NOT JSON_CONTAINS(c.HolidayJson, JSON_QUOTE(DATE_FORMAT(cd.CandidateDate, '%Y,%m,%d')))
|
||||
ORDER BY cd.CandidateDate DESC
|
||||
LIMIT 1
|
||||
),
|
||||
bond_valuation_ranked AS (
|
||||
SELECT
|
||||
bv.*,
|
||||
ROW_NUMBER() OVER (PARTITION BY bv.bond_id ORDER BY bv.credibility ASC, bv.id ASC) AS RowNo
|
||||
FROM china_bond_valuation bv
|
||||
INNER JOIN previous_trading_day ptd
|
||||
ON bv.valuation_date >= ptd.CandidateDate
|
||||
AND bv.valuation_date < DATE_ADD(ptd.CandidateDate, INTERVAL 1 DAY)
|
||||
WHERE bv.net_price IS NOT NULL
|
||||
)
|
||||
SELECT
|
||||
t.id AS TradeId,
|
||||
t.TradeDate,
|
||||
ptd.CandidateDate AS PreviousInterbankTradingDay,
|
||||
|
||||
sp.id AS SwapPositionId,
|
||||
sp.SwapTradeId,
|
||||
@@ -2487,6 +2694,7 @@ SELECT
|
||||
ELSE 0
|
||||
END AS IsGreaterThan5
|
||||
FROM trade t
|
||||
CROSS JOIN previous_trading_day ptd
|
||||
INNER JOIN swap_position sp
|
||||
ON sp.SwapTradeId = t.id
|
||||
AND sp.IsInitial = 1
|
||||
@@ -2494,14 +2702,13 @@ INNER JOIN swap_position sp
|
||||
AND sp.PosiDirection = 2
|
||||
AND sp.UnderlyingCode IS NOT NULL
|
||||
AND sp.UnderlyingCode <> ''
|
||||
LEFT JOIN china_bond_valuation bv
|
||||
LEFT JOIN bond_valuation_ranked bv
|
||||
ON bv.bond_id = sp.UnderlyingCode
|
||||
AND bv.valuation_date < DATE(t.TradeDate)
|
||||
AND bv.RowNo = 1
|
||||
WHERE t.id = @TradeId
|
||||
ORDER BY
|
||||
bv.credibility ASC,
|
||||
bv.valuation_date DESC
|
||||
LIMIT 1;
|
||||
ABS(sp.PosiNetNoFeePrice * 100 - bv.net_price) DESC,
|
||||
sp.id ASC;
|
||||
```
|
||||
|
||||
---
|
||||
@@ -2726,6 +2933,48 @@ COUNT(DISTINCT swap_position.UnderlyingCode) > 10
|
||||
//});
|
||||
```
|
||||
|
||||
变量形式:
|
||||
|
||||
变量名:单一交易对手累计标的数量
|
||||
DataType:Numeric
|
||||
规则配置:单一交易对手累计标的数量 > 阈值(示例 10)
|
||||
|
||||
变量取值表达式:
|
||||
|
||||
```csharp
|
||||
int? currentClientId = DbContext.trade
|
||||
.Where(t => t.id == TradeId)
|
||||
.Select(t => (int?)t.ClientId)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (!currentClientId.HasValue)
|
||||
throw new Exception("当前交易不存在或交易对手为空");
|
||||
|
||||
// 查询同一交易对手下所有有效交易的实时存续持仓,对标的去重计数
|
||||
var underlyingCodes = DbContext.swap_position
|
||||
.Where(p => !p.IsInitial
|
||||
&& p.PosiQuantity > 0
|
||||
&& !p.Invalid
|
||||
&& p.PosiDirection > 0
|
||||
&& !string.IsNullOrEmpty(p.UnderlyingCode)
|
||||
&& DbContext.trade.Any(t => t.id == p.SwapTradeId
|
||||
&& t.ValidState != "InValid"
|
||||
&& t.ClientId == currentClientId.Value))
|
||||
.Select(p => p.UnderlyingCode)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
int distinctCount = underlyingCodes.Count;
|
||||
|
||||
string underlyingList = underlyingCodes.Any()
|
||||
? string.Join("、", underlyingCodes.OrderBy(c => c))
|
||||
: "无";
|
||||
|
||||
return new RiskVariableValueDetail(
|
||||
distinctCount,
|
||||
$"交易对手ID为{currentClientId},存续标的共{distinctCount}个:{underlyingList}");
|
||||
```
|
||||
|
||||
汇总 SQL:
|
||||
|
||||
```sql
|
||||
|
||||
@@ -5017,7 +5017,7 @@ namespace YLErp.Modules.RiskModule
|
||||
}
|
||||
riskWarning += "风险预警: " + quotaTrial.RiskWarningDetails;
|
||||
}
|
||||
log.risk_warning = riskWarning;
|
||||
log.risk_warning = TruncateRiskCheckLogText(riskWarning);
|
||||
log.limit_warning = quotaTrial.QuotaCheckDetails;
|
||||
log.remark = RiskCheckTriggerRemark;
|
||||
if (isOldRiskErrorSpecialApproval)
|
||||
@@ -5411,6 +5411,17 @@ namespace YLErp.Modules.RiskModule
|
||||
return detail + "\n";
|
||||
}
|
||||
|
||||
private const int RiskCheckLogTextMaxLength = 500;
|
||||
|
||||
private static string TruncateRiskCheckLogText(string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text) || text.Length <= RiskCheckLogTextMaxLength)
|
||||
return text;
|
||||
|
||||
const string suffix = "……(日志内容过长已截断)";
|
||||
return text.Substring(0, RiskCheckLogTextMaxLength - suffix.Length) + suffix;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验标的白名单
|
||||
/// </summary>
|
||||
|
||||
@@ -22,6 +22,16 @@ namespace YLErp.Modules.RiskModule
|
||||
{
|
||||
}
|
||||
|
||||
private const int RiskCheckLogTextMaxLength = 500;
|
||||
|
||||
private static string TruncateRiskCheckLogText(string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text) || text.Length <= RiskCheckLogTextMaxLength)
|
||||
return text;
|
||||
|
||||
const string suffix = "……(日志内容过长已截断)";
|
||||
return text.Substring(0, RiskCheckLogTextMaxLength - suffix.Length) + suffix;
|
||||
}
|
||||
|
||||
public SearchListResult<trade_risk_check_log> Search(SearchTradeRiskCheckLogRequest req)
|
||||
{
|
||||
@@ -153,7 +163,7 @@ namespace YLErp.Modules.RiskModule
|
||||
}
|
||||
riskWarning += "风控预警: " + quotaTrial.RiskWarningDetails;
|
||||
}
|
||||
log.risk_warning = riskWarning;
|
||||
log.risk_warning = TruncateRiskCheckLogText(riskWarning);
|
||||
log.limit_warning = quotaTrial.QuotaCheckDetails;
|
||||
log.remark = quotaTrial.Remark;
|
||||
log.create_user = UserId;
|
||||
@@ -202,9 +212,10 @@ namespace YLErp.Modules.RiskModule
|
||||
log.client_name = quotaTrial.ClientName;
|
||||
log.trader = td?.TraderName;
|
||||
log.trade_number = quotaTrial.TradeNumber;
|
||||
log.risk_warning = string.IsNullOrWhiteSpace(quotaTrial.RiskWarningDetails)
|
||||
var riskWarning = string.IsNullOrWhiteSpace(quotaTrial.RiskWarningDetails)
|
||||
? $"[风控预警处理]{decision}"
|
||||
: quotaTrial.RiskWarningDetails + Environment.NewLine + $"[风控预警处理]{decision}";
|
||||
log.risk_warning = TruncateRiskCheckLogText(riskWarning);
|
||||
log.limit_warning = quotaTrial.QuotaCheckDetails;
|
||||
log.remark = string.IsNullOrWhiteSpace(quotaTrial.Remark)
|
||||
? $"风控预警处理结果:{decision}"
|
||||
|
||||
@@ -2493,8 +2493,8 @@ namespace YLErp.Web.Controllers
|
||||
}
|
||||
var result = new TradeConfirmService(CurUser).tradeConfirm(tradeidArr, ignoreMoneyCheck, isSkipApproval, ignoreRiskWarning, ignoreRiskRuleIdArr);
|
||||
|
||||
//如果客户缺少资金而操作者有交易特批权限
|
||||
if (!ignoreMoneyCheck && !ignoreRiskWarning && result.LackOfMoney)
|
||||
//如果需要前端确认信息,触发新老风控
|
||||
if (result.LackOfMoney)
|
||||
{
|
||||
if (result.type == TradeOpenRetCode.RiskWarning.ToString())
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user