resolve confilct QuotaMonitorService.cs
This commit is contained in:
@@ -795,6 +795,19 @@ namespace YLErp.Modules.RiskModule
|
||||
join p in DbContext.swap_position on t.id equals p.SwapTradeId
|
||||
where !p.IsInitial && p.PosiDirection > 0 && !p.Invalid
|
||||
select p;
|
||||
// 计算互换DV汇总(按标的缓存bondPrice)
|
||||
var swapPositions = posiQuery.ToList();
|
||||
var swapDV = 0d;
|
||||
var swapBPCache = swapPositions.Select(p => p.UnderlyingCode).Distinct()
|
||||
.ToDictionary(code => code, code => EodPriceQueryService.GetBondPrice(valuedateBLL.ValueDate, code));
|
||||
foreach (var pos in swapPositions)
|
||||
{
|
||||
if (!swapBPCache.TryGetValue(pos.UnderlyingCode, out var bondPrice) || bondPrice == null) continue;
|
||||
var vobp = bondPrice.Vobp ?? 0;
|
||||
if (vobp == 0) continue;
|
||||
swapDV += Convert.ToDouble(Dv01Helper.CalcDv01(pos.UnderlyingCode, pos.PosiQuantity, pos.PosiDirection, pos.PositionType, vobp));
|
||||
}
|
||||
|
||||
var setValue = new Action<QuotaMonitor_Global, List<QuotaSetting>>((obj, settings) =>
|
||||
{
|
||||
obj.StockEqvNotional = Convert.ToDouble(posiQuery.Sum(s => s.PosiNotionalValue));
|
||||
@@ -907,6 +920,10 @@ namespace YLErp.Modules.RiskModule
|
||||
|
||||
var allSetting = QueryCurrentQuotaSetting(QuotaTypeEnum.GLOBAL_ALL);
|
||||
allSetting = MargeQuotaSetting(allSetting, 0, 0);
|
||||
var globalDvSetting = allSetting.Where(O => O.IsValid && O.Status == QuotaSettingApprovalStatus.Valid && O.QuotaRange == 0 && O.QuotaIndex == "DV").FirstOrDefault()?.Clone();
|
||||
var tradeSetting = QueryCurrentQuotaSetting(QuotaTypeEnum.TRADE);
|
||||
tradeSetting = MargeQuotaSetting(tradeSetting, 0, 0);
|
||||
var tradeDvSetting = tradeSetting.Where(O => O.IsValid && O.Status == QuotaSettingApprovalStatus.Valid && O.QuotaRange == 0 && O.QuotaIndex == "DV").FirstOrDefault()?.Clone();
|
||||
var swapSetting = QueryCurrentQuotaSetting(QuotaTypeEnum.GLOBAL_SWAP);
|
||||
swapSetting = MargeQuotaSetting(swapSetting, 0, 0);
|
||||
var swap = new QuotaMonitor_Global()
|
||||
@@ -915,6 +932,11 @@ namespace YLErp.Modules.RiskModule
|
||||
BusinessType = "互换"
|
||||
};
|
||||
setValue(swap, swapSetting);
|
||||
swap.DV = swapDV;
|
||||
//swap.Quota_DV_Upper = tradeDvSetting?.QuotaUpperLimit ?? double.NaN;
|
||||
//swap.Quota_DV_Lower = tradeDvSetting?.QuotaLowerLimit ?? double.NaN;
|
||||
//swap.Quota_DV_wUpper = tradeDvSetting?.WarningUpperLimit ?? double.NaN;
|
||||
//swap.Quota_DV_wLower = tradeDvSetting?.WarningLowerLimit ?? double.NaN;
|
||||
|
||||
var positionList = new List<KeyValuePair<trade, realtime_trade_risk>>();
|
||||
var underly = GetTradePositionPnl();
|
||||
@@ -926,6 +948,21 @@ namespace YLErp.Modules.RiskModule
|
||||
positionList.AddRange(swapPositionList);
|
||||
}
|
||||
var checkPosiList = GetCheckPosiList();
|
||||
// 计算checkPosiList中所有持仓的DV合计,用于算未簿记DV(按标的缓存bondPrice)
|
||||
var totalCheckPosiDV = 0d;
|
||||
if (checkPosiList.Any())
|
||||
{
|
||||
var ckBPCache = checkPosiList.Select(p => p.UnderlyingCode).Distinct()
|
||||
.ToDictionary(code => code, code => EodPriceQueryService.GetBondPrice(valuedateBLL.ValueDate, code));
|
||||
foreach (var pos in checkPosiList)
|
||||
{
|
||||
if (!ckBPCache.TryGetValue(pos.UnderlyingCode, out var bp) || bp == null) continue;
|
||||
var vobp = bp.Vobp ?? 0;
|
||||
if (vobp == 0) continue;
|
||||
int positionType = pos.Side == 0 ? (int)PositionTypeFlag.Long : (int)PositionTypeFlag.Short;
|
||||
totalCheckPosiDV += Convert.ToDouble(Dv01Helper.CalcDv01(pos.UnderlyingCode, pos.Qty, pos.Direction, positionType, vobp));
|
||||
}
|
||||
}
|
||||
var posiStockEqvNotional = checkPosiList.Sum(s => s.Pv);
|
||||
var delta = checkPosiList.Sum(s => s.Delta);
|
||||
var unTrade = new QuotaMonitor_Global()
|
||||
@@ -933,14 +970,20 @@ namespace YLErp.Modules.RiskModule
|
||||
ParentKey = "场外",
|
||||
BusinessType = "未簿记合约",
|
||||
StockEqvNotional = Convert.ToDouble(posiStockEqvNotional) - swap.StockEqvNotional.Normalize(),
|
||||
DeltaExposure=(double)delta- (swap.DeltaExposure??0)
|
||||
DeltaExposure=(double)delta- (swap.DeltaExposure??0),
|
||||
DV = totalCheckPosiDV - (swap.DV ?? 0),
|
||||
};
|
||||
var all = new QuotaMonitor_Global()
|
||||
{
|
||||
BusinessType = "全局",
|
||||
StockEqvNotional = Convert.ToDouble(posiStockEqvNotional),
|
||||
PositionPnl = underly.PositionPnl + swap.PositionPnl,
|
||||
DeltaExposure=(double)delta+ underly.DeltaExposure
|
||||
DeltaExposure=(double)delta+ underly.DeltaExposure,
|
||||
DV = (swap.DV ?? 0) + (underly.DV ?? 0) + (unTrade.DV ?? 0),
|
||||
Quota_DV_Upper = globalDvSetting?.QuotaUpperLimit ?? double.NaN,
|
||||
Quota_DV_Lower = globalDvSetting?.QuotaLowerLimit ?? double.NaN,
|
||||
Quota_DV_wUpper = globalDvSetting?.WarningUpperLimit ?? double.NaN,
|
||||
Quota_DV_wLower = globalDvSetting?.WarningLowerLimit ?? double.NaN,
|
||||
};
|
||||
var list = new List<QuotaMonitor_Global>
|
||||
{
|
||||
@@ -963,9 +1006,13 @@ namespace YLErp.Modules.RiskModule
|
||||
/// <returns></returns>
|
||||
public List<QuotaMonitor_Global> QueryEodGlobalFromCalc(QuotaMonitorReq req)
|
||||
{
|
||||
var posiQuery = DbContext.eod_swap_position.Where(O =>O.ValueDate==req.ValueDate&&O.PosiQuantity>0);
|
||||
var maxDate = DbContext.eod_swap_position.Where(O => O.PosiQuantity > 0).Max(O => (DateTime?)O.ValueDate) ?? req.ValueDate;
|
||||
var posiQuery = DbContext.eod_swap_position.Where(O =>O.ValueDate==maxDate&&O.PosiQuantity>0);
|
||||
|
||||
req.PvPercent = 1 - req.PvPercent;
|
||||
|
||||
// 互换DV汇总:直接取eod_swap_position中已计算好的dv01
|
||||
var eodSwapDV = Convert.ToDouble(posiQuery.Sum(p => p.dv01));
|
||||
|
||||
var setValue = new Action<QuotaMonitor_Global, List<QuotaSetting>>((obj, settings) =>
|
||||
{
|
||||
@@ -988,6 +1035,10 @@ namespace YLErp.Modules.RiskModule
|
||||
|
||||
var allSetting = QueryCurrentQuotaSetting(QuotaTypeEnum.GLOBAL_ALL);
|
||||
allSetting = MargeQuotaSetting(allSetting, 0, 0);
|
||||
var globalDvSetting = allSetting.Where(O => O.IsValid && O.Status == QuotaSettingApprovalStatus.Valid && O.QuotaRange == 0 && O.QuotaIndex == "DV").FirstOrDefault()?.Clone();
|
||||
var tradeSetting = QueryCurrentQuotaSetting(QuotaTypeEnum.TRADE);
|
||||
tradeSetting = MargeQuotaSetting(tradeSetting, 0, 0);
|
||||
var tradeDvSetting = tradeSetting.Where(O => O.IsValid && O.Status == QuotaSettingApprovalStatus.Valid && O.QuotaRange == 0 && O.QuotaIndex == "DV").FirstOrDefault()?.Clone();
|
||||
var swapSetting = QueryCurrentQuotaSetting(QuotaTypeEnum.GLOBAL_SWAP);
|
||||
swapSetting = MargeQuotaSetting(swapSetting, 0, 0);
|
||||
var swap = new QuotaMonitor_Global()
|
||||
@@ -996,6 +1047,11 @@ namespace YLErp.Modules.RiskModule
|
||||
BusinessType = "互换",
|
||||
};
|
||||
setValue(swap, swapSetting);
|
||||
swap.DV = eodSwapDV;
|
||||
swap.Quota_DV_Upper = tradeDvSetting?.QuotaUpperLimit ?? double.NaN;
|
||||
swap.Quota_DV_Lower = tradeDvSetting?.QuotaLowerLimit ?? double.NaN;
|
||||
swap.Quota_DV_wUpper = tradeDvSetting?.WarningUpperLimit ?? double.NaN;
|
||||
swap.Quota_DV_wLower = tradeDvSetting?.WarningLowerLimit ?? double.NaN;
|
||||
var underly = GetEodTradePositionPnl(req.ValueDate);
|
||||
var unTrade = new QuotaMonitor_Global()
|
||||
{
|
||||
@@ -1009,7 +1065,12 @@ namespace YLErp.Modules.RiskModule
|
||||
BusinessType = "全局",
|
||||
StockEqvNotional = swap.StockEqvNotional,
|
||||
PositionPnl = underly.PositionPnl + swap.PositionPnl,
|
||||
DeltaExposure = swap.DeltaExposure+ underly.DeltaExposure
|
||||
DeltaExposure = swap.DeltaExposure+ underly.DeltaExposure,
|
||||
DV = (swap.DV ?? 0) + (underly.DV ?? 0) + (unTrade.DV ?? 0),
|
||||
Quota_DV_Upper = globalDvSetting?.QuotaUpperLimit ?? double.NaN,
|
||||
Quota_DV_Lower = globalDvSetting?.QuotaLowerLimit ?? double.NaN,
|
||||
Quota_DV_wUpper = globalDvSetting?.WarningUpperLimit ?? double.NaN,
|
||||
Quota_DV_wLower = globalDvSetting?.WarningLowerLimit ?? double.NaN,
|
||||
};
|
||||
var list = new List<QuotaMonitor_Global>
|
||||
{
|
||||
@@ -1323,11 +1384,23 @@ namespace YLErp.Modules.RiskModule
|
||||
obj.Quota_SwapPercent_Lower = swapPercentSetting?.QuotaLowerLimit ?? double.NaN;
|
||||
obj.Quota_SwapPercent_wUpper = swapPercentSetting?.WarningUpperLimit ?? double.NaN;
|
||||
obj.Quota_SwapPercent_wLower = swapPercentSetting?.WarningLowerLimit ?? double.NaN;
|
||||
var dvSetting = temp.Where(O => O.QuotaIndex == "DV").FirstOrDefault()?.Clone();
|
||||
obj.Quota_DV_Upper = dvSetting?.QuotaUpperLimit ?? double.NaN;
|
||||
obj.Quota_DV_Lower = dvSetting?.QuotaLowerLimit ?? double.NaN;
|
||||
obj.Quota_DV_wUpper = dvSetting?.WarningUpperLimit ?? double.NaN;
|
||||
obj.Quota_DV_wLower = dvSetting?.WarningLowerLimit ?? double.NaN;
|
||||
});
|
||||
|
||||
var setting = QueryCurrentQuotaSetting(QuotaTypeEnum.TRADE);
|
||||
setting = MargeQuotaSetting(setting, 0, 0);
|
||||
var ccrDict = new Dictionary<int, double>();
|
||||
|
||||
// 批量获取swap_position方向/多空/数量,用于DV计算
|
||||
var tradeIds = list.Select(O => O.trade.id).Distinct().ToList();
|
||||
var swapPositionDict = DbContext.swap_position
|
||||
.Where(p => tradeIds.Contains(p.SwapTradeId) && !p.IsInitial && p.PosiDirection > 0 && !p.Invalid)
|
||||
.ToDictionary(p => p.SwapTradeId);
|
||||
|
||||
foreach (var item in list)
|
||||
{
|
||||
setValue(item, setting);
|
||||
@@ -1337,6 +1410,13 @@ namespace YLErp.Modules.RiskModule
|
||||
var vobp = bondPrice == null ? 0 : Convert.ToDouble(bondPrice.Vobp);
|
||||
var pricePercent = basePrice == 0 ? 0 : Math.Abs((sportPrice / basePrice) - 1);
|
||||
item.Quota_SwapPercent = pricePercent;
|
||||
|
||||
// 计算DV
|
||||
if (swapPositionDict.TryGetValue(item.trade.id, out var swapPos) && bondPrice != null && vobp != 0)
|
||||
{
|
||||
item.DV = Convert.ToDouble(Dv01Helper.CalcDv01(item.trade.UnderlyingCode, swapPos.PosiQuantity, swapPos.PosiDirection, swapPos.PositionType, Convert.ToDecimal(vobp)));
|
||||
}
|
||||
|
||||
var obj = new QuotaMonitor_Trade();
|
||||
ObjectHelper.MapValues(obj, item);
|
||||
result.Add(obj);
|
||||
@@ -1351,6 +1431,7 @@ namespace YLErp.Modules.RiskModule
|
||||
VegaCash = result.Sum(O => O.VegaCash),
|
||||
Quota_CCR = result.Sum(O => O.Quota_CCR),
|
||||
PnL = result.Sum(O => O.PnL),
|
||||
DV = result.Sum(O => O.DV),
|
||||
};
|
||||
result.Add(total);
|
||||
|
||||
@@ -1430,11 +1511,26 @@ namespace YLErp.Modules.RiskModule
|
||||
obj.Quota_SwapPercent_Lower = swapPercentSetting?.QuotaLowerLimit ?? double.NaN;
|
||||
obj.Quota_SwapPercent_wUpper = swapPercentSetting?.WarningUpperLimit ?? double.NaN;
|
||||
obj.Quota_SwapPercent_wLower = swapPercentSetting?.WarningLowerLimit ?? double.NaN;
|
||||
var dvSetting = temp.Where(O => O.QuotaIndex == "DV").FirstOrDefault()?.Clone();
|
||||
obj.Quota_DV_Upper = dvSetting?.QuotaUpperLimit ?? double.NaN;
|
||||
obj.Quota_DV_Lower = dvSetting?.QuotaLowerLimit ?? double.NaN;
|
||||
obj.Quota_DV_wUpper = dvSetting?.WarningUpperLimit ?? double.NaN;
|
||||
obj.Quota_DV_wLower = dvSetting?.WarningLowerLimit ?? double.NaN;
|
||||
});
|
||||
|
||||
var setting = QueryCurrentQuotaSetting(QuotaTypeEnum.TRADE);
|
||||
setting = MargeQuotaSetting(setting, 0, 0);
|
||||
var ccrDict = new Dictionary<int, double>();
|
||||
|
||||
// 批量获取eod_swap_position方向/多空/数量,用于DV计算和dv01回写
|
||||
var tradeIds = list.Select(O => O.trade.id).Distinct().ToList();
|
||||
var eodSwapPosDict = DbContext.eod_swap_position
|
||||
.Where(p => p.ValueDate == req.ValueDate && tradeIds.Contains(p.SwapTradeId) && p.PosiQuantity > 0)
|
||||
.ToDictionary(p => p.SwapTradeId);
|
||||
var eodSwapDict = DbContext.eod_swap
|
||||
.Where(p => p.ValueDate == req.ValueDate && tradeIds.Contains(p.SwapTradeId))
|
||||
.ToDictionary(p => p.SwapTradeId);
|
||||
|
||||
foreach (var item in list)
|
||||
{
|
||||
setValue(item, setting);
|
||||
@@ -1444,10 +1540,18 @@ namespace YLErp.Modules.RiskModule
|
||||
var vobp = bondPrice == null ? 0 : Convert.ToDouble(bondPrice.Vobp);
|
||||
var pricePercent = basePrice == 0 ? 0 : Math.Abs((sportPrice / basePrice) - 1);
|
||||
item.Quota_SwapPercent = pricePercent;
|
||||
|
||||
// 计算DV,并回写dv01
|
||||
if (eodSwapPosDict.TryGetValue(item.trade.id, out var eodSwapPos))
|
||||
{
|
||||
item.DV = Convert.ToDouble(eodSwapPos.dv01);
|
||||
}
|
||||
|
||||
var obj = new QuotaMonitor_Trade();
|
||||
ObjectHelper.MapValues(obj, item);
|
||||
result.Add(obj);
|
||||
}
|
||||
|
||||
var total = new QuotaMonitor_Trade()
|
||||
{
|
||||
TradeNumber = "合计",
|
||||
@@ -1458,6 +1562,7 @@ namespace YLErp.Modules.RiskModule
|
||||
VegaCash = result.Sum(O => O.VegaCash),
|
||||
Quota_CCR = result.Sum(O => O.Quota_CCR),
|
||||
PnL = result.Sum(O => O.PnL),
|
||||
DV = result.Sum(O => O.DV),
|
||||
};
|
||||
result.Add(total);
|
||||
|
||||
@@ -3522,6 +3627,20 @@ namespace YLErp.Modules.RiskModule
|
||||
singleUnderlyingRate = valuedateBLL.SystemDate.SingleUnderlyingRate;
|
||||
_quotaSettings = new List<QuotaSetting>();
|
||||
#region 整体业务
|
||||
//全局-DV
|
||||
_quotaSettings.Add(new QuotaSetting()
|
||||
{
|
||||
QuotaType = QuotaTypeEnum.GLOBAL_ALL,
|
||||
QuotaRange = 0,
|
||||
QuotaIndex = "DV",
|
||||
QuotaLowerLimit = null,
|
||||
QuotaUpperLimit = null,
|
||||
WarningLowerLimit = null,
|
||||
WarningUpperLimit = null,
|
||||
Percent = false,
|
||||
IsValid = true,
|
||||
Status = QuotaSettingApprovalStatus.Valid,
|
||||
});
|
||||
#endregion
|
||||
#region 互换
|
||||
|
||||
@@ -3617,6 +3736,20 @@ namespace YLErp.Modules.RiskModule
|
||||
IsValid = true,
|
||||
Status = QuotaSettingApprovalStatus.Valid,
|
||||
});
|
||||
//交易-DV
|
||||
_quotaSettings.Add(new QuotaSetting()
|
||||
{
|
||||
QuotaType = QuotaTypeEnum.TRADE,
|
||||
QuotaRange = 0,
|
||||
QuotaIndex = "DV",
|
||||
QuotaLowerLimit = null,
|
||||
QuotaUpperLimit = null,
|
||||
WarningLowerLimit = null,
|
||||
WarningUpperLimit = null,
|
||||
Percent = false,
|
||||
IsValid = true,
|
||||
Status = QuotaSettingApprovalStatus.Valid,
|
||||
});
|
||||
|
||||
#endregion
|
||||
#region 客户
|
||||
@@ -4335,7 +4468,7 @@ namespace YLErp.Modules.RiskModule
|
||||
return QuotaCheck(ref res, false);
|
||||
}
|
||||
|
||||
public bool QuotaCheck(ref TradeOpenResult res, bool ignoreRiskWarning, IEnumerable<string> ignoreRiskRuleIds = null)
|
||||
public bool QuotaCheck(ref TradeOpenResult res, bool ignoreRiskWarning)
|
||||
{
|
||||
if (res == null || res.Trade == null)
|
||||
{
|
||||
@@ -4368,6 +4501,7 @@ namespace YLErp.Modules.RiskModule
|
||||
new TradeRiskCheckLogService(UserInfo).AddLog(quotaObj);
|
||||
}
|
||||
//否则的情况是上次没算,这次是预警,或上次算了,结果是不通过\通过或预警,这次是预警或不通过,提示用户;
|
||||
res.RetCode = TradeOpenRetCode.QuotaTrialError;
|
||||
res.TrialDataId = quotaObj.id;
|
||||
//汇总各类检查详情,避免只展示RiskWarningDetails而漏掉其它老风控检查
|
||||
var detailParts = new List<string>();
|
||||
@@ -4381,27 +4515,14 @@ namespace YLErp.Modules.RiskModule
|
||||
detailParts.Add($"限额预警:{quotaObj.QuotaWarningDetails}");
|
||||
res.ErrorMsg = string.Join("\n", detailParts);
|
||||
//确认本次为需审批后,二次特批。因为只靠点击“交易特批”的ignoreRiskWarning,不能保证本次校验通过。
|
||||
var isRiskApprovalWarning = quotaObj.TrialStatus == QuotaTrialStatusEnum.RiskWarning
|
||||
&& !string.IsNullOrWhiteSpace(quotaObj.RiskWarningDetails)
|
||||
&& (quotaObj.ApprovalRuleIds?.Any() ?? false);
|
||||
//当前为老风控特批,不是新风控需审批
|
||||
res.OldRiskNeedSpecialApproval = quotaObj.TrialStatus == QuotaTrialStatusEnum.Warning;
|
||||
if (isRiskApprovalWarning)
|
||||
var isRiskApprovalWarning = quotaObj.TrialStatus == QuotaTrialStatusEnum.Warning
|
||||
&& !string.IsNullOrWhiteSpace(quotaObj.RiskWarningDetails);
|
||||
//需清除ErrorMsg,不然外部调用会认为失败
|
||||
//触发需审批,交易特批逻辑
|
||||
if (ignoreRiskWarning && isRiskApprovalWarning)
|
||||
{
|
||||
var currentApprovalRuleIds = quotaObj.ApprovalRuleIds ?? new List<string>();
|
||||
res.IgnoredRiskRuleIds = currentApprovalRuleIds;
|
||||
//需清除ErrorMsg,不然外部调用会认为失败
|
||||
//触发需审批,交易特批逻辑
|
||||
if (ignoreRiskWarning)
|
||||
{
|
||||
var ignoredRuleIdSet = new HashSet<string>((ignoreRiskRuleIds ?? Enumerable.Empty<string>()).Where(o => !string.IsNullOrWhiteSpace(o)));
|
||||
var newApprovalRuleIds = currentApprovalRuleIds.Where(o => !ignoredRuleIdSet.Contains(o)).ToList();
|
||||
if (!newApprovalRuleIds.Any())
|
||||
{
|
||||
res.ErrorMsg = string.Empty;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
res.ErrorMsg = string.Empty;
|
||||
return true;
|
||||
}
|
||||
res.RetCode = isRiskApprovalWarning ? TradeOpenRetCode.RiskWarning : TradeOpenRetCode.QuotaTrialError;
|
||||
return false;
|
||||
@@ -4440,6 +4561,9 @@ namespace YLErp.Modules.RiskModule
|
||||
var ums = GetUnderlyings(umCodes);
|
||||
|
||||
var checkPoisiList = BuildCheckQuotaMoitorModels(clientPositions, ums, dealDate);
|
||||
// 仅在限额设置中有DV指标时才计算DV
|
||||
//var (totalDv, currentDv) = precheckQuotaSettingList.Any(s => s.QuotaIndex == "DV")
|
||||
// ? CalcRiskCheckDv(checkPoisiList, dealDate) : (0d, 0d);
|
||||
var underlyingPositions = checkPoisiList.Where(x => x.UnderlyingCode == clientRiskCheckReq.securityId).ToList();
|
||||
var sameQty = underlyingPositions.Where(x=>x.ClientId== clientRiskCheckReq.clientId && x.Side== clientRiskCheckReq.side).Sum(s => s.Qty);
|
||||
var qty = underlyingPositions.Where(x => x.ClientId == clientRiskCheckReq.clientId && x.Side != clientRiskCheckReq.side).Sum(s => s.Qty);
|
||||
@@ -4467,7 +4591,7 @@ namespace YLErp.Modules.RiskModule
|
||||
{
|
||||
continue;
|
||||
}
|
||||
clientRiskCheckItem = CheckQuota(checkPoisiList, settingItem.QuotaType, clientRiskCheckItem, settingItem.QuotaRange, allList);
|
||||
clientRiskCheckItem = CheckQuota(checkPoisiList, settingItem.QuotaType, clientRiskCheckItem, settingItem.QuotaRange, allList, 0d, 0d);
|
||||
if (clientRiskCheckItem != null)
|
||||
{
|
||||
clientRiskCheckItem.quotaType = $"{EnumHelper.GetDescriptionByName(settingItem.QuotaType)}({settingItem.QuotaIndex})";
|
||||
@@ -4499,6 +4623,50 @@ namespace YLErp.Modules.RiskModule
|
||||
.Where(x => umCodes.Contains(x.UnderlyingCode))
|
||||
.ToList();
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算风控检查用的DV(全局DV + 当前单笔DV)
|
||||
/// </summary>
|
||||
private (double totalDv, double currentDv) CalcRiskCheckDv(List<CheckQuotaMoitorModel> checkPoisiList, DateTime dealDate)
|
||||
{
|
||||
var totalDv = 0d;
|
||||
var currentDv = 0d;
|
||||
// 按标的缓存bondPrice
|
||||
var bpCache = checkPoisiList.Select(p => p.UnderlyingCode).Distinct()
|
||||
.ToDictionary(code => code, code => EodPriceQueryService.GetBondPrice(dealDate, code));
|
||||
// 全部持仓DV
|
||||
totalDv = checkPoisiList.Sum(pos =>
|
||||
{
|
||||
if (!bpCache.TryGetValue(pos.UnderlyingCode, out var bp) || bp == null) return 0d;
|
||||
var vobp = bp.Vobp ?? 0;
|
||||
if (vobp == 0) return 0d;
|
||||
int posType = pos.Side == 0 ? (int)PositionTypeFlag.Long : (int)PositionTypeFlag.Short;
|
||||
return Convert.ToDouble(Dv01Helper.CalcDv01(pos.UnderlyingCode, pos.Qty, pos.Direction, posType, vobp));
|
||||
});
|
||||
// 当前单笔DV
|
||||
var cur = checkPoisiList.FirstOrDefault(x => x.Current);
|
||||
if (cur != null && bpCache.TryGetValue(cur.UnderlyingCode, out var curBp) && curBp != null)
|
||||
{
|
||||
var vobp = curBp.Vobp ?? 0;
|
||||
if (vobp != 0)
|
||||
{
|
||||
int pt = cur.Side == 0 ? (int)PositionTypeFlag.Long : (int)PositionTypeFlag.Short;
|
||||
currentDv = Convert.ToDouble(Dv01Helper.CalcDv01(cur.UnderlyingCode, cur.Qty, cur.Direction, pt, vobp));
|
||||
}
|
||||
}
|
||||
// 标的交易DV
|
||||
List<string> tradeTypes = new List<string> { "利率债", "信用债", "其它债券" };
|
||||
var tposis = DbContext.TradePosition.Where(x => tradeTypes.Contains(x.TradeType)).AsNoTracking().ToList();
|
||||
foreach (var code in tposis.Select(x => x.UnderlyingCode).Distinct().Where(c => !bpCache.ContainsKey(c)))
|
||||
bpCache[code] = EodPriceQueryService.GetBondPrice(dealDate, code);
|
||||
foreach (var item in tposis)
|
||||
{
|
||||
if (!bpCache.TryGetValue(item.UnderlyingCode, out var tpBp) || tpBp == null) continue;
|
||||
var vobp = tpBp.Vobp ?? 0;
|
||||
if (vobp == 0) continue;
|
||||
totalDv += Convert.ToDouble(Dv01Helper.CalcDv01(item.UnderlyingCode, Convert.ToDecimal(Math.Abs(item.Position)), (int)SwapDirectionEnum.收取, (int)item.PositionType, vobp));
|
||||
}
|
||||
return (totalDv, currentDv);
|
||||
}
|
||||
private (List<ClientPosition>, List<ClientOrder>) GetClientPositionsAndOrders(BondOmsDBContext bondDb, long orderId)
|
||||
{
|
||||
var clientPositions = bondDb.client_position.AsNoTracking().ToList();
|
||||
@@ -4944,8 +5112,10 @@ namespace YLErp.Modules.RiskModule
|
||||
checkQuotaMoitorModel.Current = checkQuotaMoitorModel.id == 0;
|
||||
checkPoisiList.Add(checkQuotaMoitorModel);
|
||||
}
|
||||
quotaWarningStatus = CheckQuota(checkPoisiList, posiList, list, allList, true, out quotaWarningMsg);
|
||||
quotaStatus = CheckQuota(checkPoisiList, posiList, list, allList, false, out quotaMsg);
|
||||
// 预计算DV,避免warning/error两次检查各算一遍
|
||||
var (quotaTotalDv, quotaCurrentDv) = list.Any(s => s.QuotaIndex == "DV") ? CalcRiskCheckDv(checkPoisiList, tradeObj.TradeDate.Value) : (0d, 0d);
|
||||
quotaWarningStatus = CheckQuota(checkPoisiList, posiList, list, allList, true, out quotaWarningMsg, quotaTotalDv, quotaCurrentDv);
|
||||
quotaStatus = CheckQuota(checkPoisiList, posiList, list, allList, false, out quotaMsg, quotaTotalDv, quotaCurrentDv);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4972,6 +5142,8 @@ namespace YLErp.Modules.RiskModule
|
||||
result.RiskWarningDetails = riskWarningMsg ?? "";
|
||||
result.QuotaWarningDetails = quotaWarningMsg ?? "";
|
||||
result.AvailableForClient = availableMsg ?? "";
|
||||
result.TrialSource = trialSource;
|
||||
_logger.Info($"[限额试算] 试算完成 - tradeId: {tradeId}, TrialStatus: {result.TrialStatus}, FundCheck: {fundStatus}, QuotaCheck: {quotaStatus}");
|
||||
|
||||
// ===== 新风控引擎接入点(设计文档 4.6.1) =====
|
||||
try
|
||||
@@ -4982,6 +5154,7 @@ namespace YLErp.Modules.RiskModule
|
||||
TradeId = tradeId,
|
||||
TriggerPoint = "BOOK_CONFIRM"
|
||||
};
|
||||
tradeObj.StockEqvNotional = 10000000000;
|
||||
riskContext.DataMap["trade"] = tradeObj;
|
||||
var sameUnderlyingTotalNotional = DbContext.trade
|
||||
.Where(t => t.ValidState != "InValid"
|
||||
@@ -5021,25 +5194,12 @@ namespace YLErp.Modules.RiskModule
|
||||
}
|
||||
if (riskResult.NeedApproval)
|
||||
{
|
||||
if (result.TrialStatus != QuotaTrialStatusEnum.Error && result.TrialStatus != QuotaTrialStatusEnum.Warning)
|
||||
if (result.TrialStatus != QuotaTrialStatusEnum.Error)
|
||||
{
|
||||
result.TrialStatus = QuotaTrialStatusEnum.RiskWarning;
|
||||
result.TrialStatus = QuotaTrialStatusEnum.Warning;
|
||||
}
|
||||
result.RiskWarningDetails += "[风控引擎] 规则触发:需审批\n";
|
||||
var approvalTriggeredRules = riskResult.TriggeredRules.Where(r => r.ControlStrategy == RiskControlStrategy.Approval).ToList();
|
||||
var approvalTriggeredRuleIds = approvalTriggeredRules.Select(r => r.RuleId)
|
||||
.Where(r => !string.IsNullOrWhiteSpace(r))
|
||||
.ToList();
|
||||
var showTipTriggeredRules = riskResult.TriggeredRules.Where(r => r.ControlStrategy == RiskControlStrategy.ShowTip).ToList();
|
||||
var showTipTriggeredRuleIds = showTipTriggeredRules.Select(r => r.RuleId)
|
||||
.Where(r => !string.IsNullOrWhiteSpace(r))
|
||||
.ToList();
|
||||
// 二次确认时,需审批规则允许按首次命中结果忽略;同时已展示过的提示规则也不再重复展示
|
||||
var processedRiskRuleIds = approvalTriggeredRuleIds.Concat(showTipTriggeredRuleIds)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
result.ApprovalRuleIds = processedRiskRuleIds;
|
||||
foreach (var triggeredRule in approvalTriggeredRules)
|
||||
foreach (var triggeredRule in riskResult.TriggeredRules.Where(r => r.ControlStrategy == RiskControlStrategy.Approval))
|
||||
{
|
||||
result.RiskWarningDetails += $"规则ID:{triggeredRule.RuleId};规则名称:{triggeredRule.RuleName};规则说明:{triggeredRule.RuleText}\n";
|
||||
}
|
||||
@@ -5064,7 +5224,6 @@ namespace YLErp.Modules.RiskModule
|
||||
}
|
||||
// ===== 新风控引擎接入结束 =====
|
||||
|
||||
//_logger.Info($"[限额试算] 试算完成 - tradeId: {tradeId}, TrialStatus: {result.TrialStatus}, FundCheck: {fundStatus}, QuotaCheck: {quotaStatus}");
|
||||
SaveQuotaTrial(result);
|
||||
return result;
|
||||
}
|
||||
@@ -5435,7 +5594,7 @@ namespace YLErp.Modules.RiskModule
|
||||
/// <param name="warning">试算类型是预警还是警告</param>
|
||||
/// <param name="msg">试算结果描述</param>
|
||||
/// <returns></returns>
|
||||
private bool CheckQuota(List<CheckQuotaMoitorModel> positionList, List<CheckQuotaMoitorModel> posiList, List<QuotaSetting> setting, List<QuotaSetting> settingAll, bool warning, out string msg)
|
||||
private bool CheckQuota(List<CheckQuotaMoitorModel> positionList, List<CheckQuotaMoitorModel> posiList, List<QuotaSetting> setting, List<QuotaSetting> settingAll, bool warning, out string msg, double totalDv = 0, double currentDv = 0)
|
||||
{
|
||||
msg = "";
|
||||
var msgList = new List<string>();
|
||||
@@ -5445,11 +5604,14 @@ namespace YLErp.Modules.RiskModule
|
||||
{
|
||||
switch (item.Key.QuotaType)
|
||||
{
|
||||
case QuotaTypeEnum.GLOBAL_ALL:
|
||||
msgList.AddRange(checkGlobalDv(positionList, item.Value, warning, totalDv));
|
||||
break;
|
||||
case QuotaTypeEnum.GLOBAL_SWAP:
|
||||
msgList.AddRange(checkGlobal(positionList, posiList, "场外业务-互换", item.Value, warning));
|
||||
break;
|
||||
case QuotaTypeEnum.TRADE:
|
||||
msgList.AddRange(checkTrade(positionList, posiList, "单笔交易", item.Value, warning));
|
||||
msgList.AddRange(checkTrade(positionList, posiList, "单笔交易", item.Value, warning, currentDv));
|
||||
break;
|
||||
case QuotaTypeEnum.UNDERLYING:
|
||||
msgList.AddRange(checkUnderlying(positionList, posiList, "标的资产", item.Value, settingAll, warning));
|
||||
@@ -5503,17 +5665,25 @@ namespace YLErp.Modules.RiskModule
|
||||
/// <param name="quotaRange"></param>
|
||||
/// <param name="stockEqvNotional">单笔名义本金</param>
|
||||
/// <returns></returns>
|
||||
private ClientRiskCheckItem CheckQuota(List<CheckQuotaMoitorModel> positionList, QuotaTypeEnum quotaType, ClientRiskCheckItem checkItem, int quotaRange, List<QuotaSetting> settingAll)
|
||||
private ClientRiskCheckItem CheckQuota(List<CheckQuotaMoitorModel> positionList, QuotaTypeEnum quotaType, ClientRiskCheckItem checkItem, int quotaRange, List<QuotaSetting> settingAll, double totalDv = 0, double currentDv = 0)
|
||||
{
|
||||
var tag_prefix = "";
|
||||
var positionListAll = positionList;
|
||||
switch (quotaType)
|
||||
{
|
||||
//case QuotaTypeEnum.GLOBAL_ALL:
|
||||
// if (checkItem.quotaType == "DV")
|
||||
// {
|
||||
// checkItem.currentValue = Math.Round(totalDv, 2);
|
||||
// if (!ValidateQuoteResult(checkItem)) return checkItem;
|
||||
// return null;
|
||||
// }
|
||||
// break;
|
||||
case QuotaTypeEnum.GLOBAL_SWAP:
|
||||
checkItem = checkGlobal(positionList, "场外业务-互换", checkItem, quotaType);
|
||||
break;
|
||||
case QuotaTypeEnum.TRADE:
|
||||
checkItem = checkTrade(positionList, "单笔交易", checkItem);
|
||||
checkItem = checkTrade(positionList, "单笔交易", checkItem, currentDv);
|
||||
break;
|
||||
case QuotaTypeEnum.UNDERLYING:
|
||||
checkItem = checkUnderlying(positionList, "标的资产", checkItem, quotaRange, settingAll);
|
||||
@@ -5548,7 +5718,8 @@ namespace YLErp.Modules.RiskModule
|
||||
BusinessType = "标的交易",
|
||||
PositionPnl=0,
|
||||
DeltaExposure=0,
|
||||
StockEqvNotional=0
|
||||
StockEqvNotional=0,
|
||||
DV=0
|
||||
};
|
||||
List<string> tradetypes = new List<string> { "利率债", "信用债", "其它债券" };
|
||||
var tposis = DbContext.TradePosition.Where(x => tradetypes.Contains(x.TradeType)).AsNoTracking().ToList();
|
||||
@@ -5570,6 +5741,12 @@ namespace YLErp.Modules.RiskModule
|
||||
{
|
||||
var bondPrice = EodPriceQueryService.GetBondPrice(valuedateBLL.ValueDate, item.UnderlyingCode);
|
||||
lastPrice = bondPrice != null ? bondPrice.ClosePrice : (um.Price ?? 0) * Convert.ToDouble(ConsGlobal.bondPriceMultiple);
|
||||
// 计算标的交易DV
|
||||
var vobp = bondPrice != null ? bondPrice.Vobp ?? 0 : 0;
|
||||
if (vobp != 0)
|
||||
{
|
||||
quotaMonitor_Global.DV += Convert.ToDouble(Dv01Helper.CalcDv01(item.UnderlyingCode,Math.Abs(Convert.ToDecimal(item.Position)), (int)SwapDirectionEnum.收取, (int)item.PositionType, vobp));
|
||||
}
|
||||
}
|
||||
}
|
||||
quotaMonitor_Global.PositionPnl += lastPrice * item.Position - item.PositionCost;
|
||||
@@ -5592,7 +5769,8 @@ namespace YLErp.Modules.RiskModule
|
||||
BusinessType = "标的交易",
|
||||
PositionPnl = 0,
|
||||
DeltaExposure = 0,
|
||||
StockEqvNotional = 0
|
||||
StockEqvNotional = 0,
|
||||
DV = 0
|
||||
};
|
||||
List<string> tradetypes = new List<string> { "利率债", "信用债", "其它债券" };
|
||||
var tposis = DbContext.eod_trade_position.Where(x => tradetypes.Contains(x.TradeType)&&x.ValueDate==valueDate).AsNoTracking().ToList();
|
||||
@@ -5614,6 +5792,13 @@ namespace YLErp.Modules.RiskModule
|
||||
{
|
||||
var bondPrice = EodPriceQueryService.GetBondPrice(valuedateBLL.ValueDate, item.UnderlyingCode);
|
||||
lastPrice = bondPrice != null ? bondPrice.ClosePrice : (um.Price ?? 0) * Convert.ToDouble(ConsGlobal.bondPriceMultiple);
|
||||
// 计算标的交易DV: Amount有符号(正=多,负=空)
|
||||
var vobp = bondPrice != null ? bondPrice.Vobp ?? 0 : 0;
|
||||
if (vobp != 0)
|
||||
{
|
||||
int positionType = item.Amount > 0 ? (int)PositionTypeFlag.Long : (int)PositionTypeFlag.Short;
|
||||
quotaMonitor_Global.DV += Convert.ToDouble(Dv01Helper.CalcDv01(item.UnderlyingCode, Convert.ToDecimal(Math.Abs(item.Amount)), (int)SwapDirectionEnum.收取, positionType, vobp));
|
||||
}
|
||||
}
|
||||
}
|
||||
quotaMonitor_Global.PositionPnl += item.PositionPnL;
|
||||
@@ -5677,6 +5862,27 @@ namespace YLErp.Modules.RiskModule
|
||||
}
|
||||
return messageList;
|
||||
}
|
||||
/// <summary>
|
||||
/// 检验全局DV限额
|
||||
/// </summary>
|
||||
private List<string> checkGlobalDv(List<CheckQuotaMoitorModel> positionList, QuotaSetting[] settings, bool warning, double totalDv = -1)
|
||||
{
|
||||
var messageList = new List<string>();
|
||||
var dvSettings = settings.Where(s => s.QuotaIndex == "DV").ToArray();
|
||||
if (!dvSettings.Any()) return messageList;
|
||||
// totalDv未预计算时(其他调用方),才在这里算
|
||||
if (totalDv < 0)
|
||||
totalDv = CalcRiskCheckDv(positionList, valuedateBLL.ValueDate).totalDv;
|
||||
foreach (var settingItem in dvSettings)
|
||||
{
|
||||
double? upperLimit = warning ? settingItem.WarningUpperLimit : settingItem.QuotaUpperLimit;
|
||||
double? lowerLimit = warning ? settingItem.WarningLowerLimit : settingItem.QuotaLowerLimit;
|
||||
if (upperLimit == null && lowerLimit == null) continue;
|
||||
var tag = $"全局(DV)";
|
||||
messageList.Add(SetMsg(tag, totalDv, upperLimit, lowerLimit, settingItem.Percent, warning));
|
||||
}
|
||||
return messageList;
|
||||
}
|
||||
|
||||
private ClientRiskCheckItem checkGlobal(List<CheckQuotaMoitorModel> positionList, string tag_prefix, ClientRiskCheckItem checkItem, QuotaTypeEnum quotaType)
|
||||
{
|
||||
@@ -5711,7 +5917,7 @@ namespace YLErp.Modules.RiskModule
|
||||
}
|
||||
return null;
|
||||
}
|
||||
private List<string> checkTrade(List<CheckQuotaMoitorModel> positionList, List<CheckQuotaMoitorModel> posiList, string tag_prefix, QuotaSetting[] settings, bool warning)
|
||||
private List<string> checkTrade(List<CheckQuotaMoitorModel> positionList, List<CheckQuotaMoitorModel> posiList, string tag_prefix, QuotaSetting[] settings, bool warning, double currentDv = -1)
|
||||
{
|
||||
if (string.IsNullOrEmpty(tag_prefix))
|
||||
{
|
||||
@@ -5727,6 +5933,9 @@ namespace YLErp.Modules.RiskModule
|
||||
double? upperLimit, lowerLimit, currentValue = null;
|
||||
|
||||
CheckQuotaMoitorModel current = positionList.FirstOrDefault(x => x.Current);
|
||||
// currentDv未预计算时(其他调用方),才在这里算
|
||||
if (currentDv < 0 && settings.Any(s => s.QuotaIndex == "DV"))
|
||||
currentDv = CalcRiskCheckDv(positionList, valuedateBLL.ValueDate).currentDv;
|
||||
foreach (var settingItem in settings)
|
||||
{
|
||||
if (warning)
|
||||
@@ -5769,6 +5978,10 @@ namespace YLErp.Modules.RiskModule
|
||||
currentValue = Convert.ToDouble(current.Delta);
|
||||
messageList.Add(SetMsg(tag, currentValue, upperLimit, lowerLimit, settingItem.Percent, warning));
|
||||
break;
|
||||
case "DV":
|
||||
currentValue = currentDv;
|
||||
messageList.Add(SetMsg(tag, currentValue, upperLimit, lowerLimit, settingItem.Percent, warning));
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
@@ -5776,7 +5989,7 @@ namespace YLErp.Modules.RiskModule
|
||||
}
|
||||
return messageList;
|
||||
}
|
||||
private ClientRiskCheckItem checkTrade(List<CheckQuotaMoitorModel> positionList, string tag_prefix, ClientRiskCheckItem checkItem)
|
||||
private ClientRiskCheckItem checkTrade(List<CheckQuotaMoitorModel> positionList, string tag_prefix, ClientRiskCheckItem checkItem, double currentDv = 0)
|
||||
{
|
||||
if (string.IsNullOrEmpty(tag_prefix))
|
||||
{
|
||||
@@ -5808,10 +6021,18 @@ namespace YLErp.Modules.RiskModule
|
||||
return checkItem;
|
||||
}
|
||||
return null;
|
||||
//case "DV":
|
||||
// currentValue = currentDv;
|
||||
// break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
checkItem.currentValue = Math.Round(currentValue ?? 0, 2);
|
||||
if (!ValidateQuoteResult(checkItem))
|
||||
{
|
||||
return checkItem;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<string> checkUnderlying(List<CheckQuotaMoitorModel> positionList, List<CheckQuotaMoitorModel> posiList, string tag_prefix, QuotaSetting[] settings, List<QuotaSetting> settingAll, bool warning)
|
||||
|
||||
Reference in New Issue
Block a user