diff --git a/Framework/YLErp.Core/Commons/RiskCfgRedisKey.cs b/Framework/YLErp.Core/Commons/RiskCfgRedisKey.cs
index aff0be48..36090934 100644
--- a/Framework/YLErp.Core/Commons/RiskCfgRedisKey.cs
+++ b/Framework/YLErp.Core/Commons/RiskCfgRedisKey.cs
@@ -32,12 +32,19 @@ namespace YLErp.Commons
/// 交易维度-价格偏离度
///
private static string TRADE_PRICE_RATE = "risk:cfg:trade:price_rate";
-
-
+ ///
+ /// 交易维度-DV
+ ///
+ private static string TRADE_DV = "risk:cfg:trade:dv";
///
/// 客户维度-名义本金
///
private static string CLIENT_PRINCIPAL = "risk:cfg:client:principal";
+ ///
+ /// 全局维度-DV
+ ///
+
+ private static string GLOBAL_DV = "risk:cfg:global:dv";
@@ -45,6 +52,8 @@ namespace YLErp.Commons
{
switch (quoteType)
{
+ case QuotaTypeEnum.GLOBAL_ALL:
+ return GLOBAL_DV;
case QuotaTypeEnum.GLOBAL_SWAP:
return SWAP_PRINCIPAL;
case QuotaTypeEnum.UNDERLYING:
@@ -54,6 +63,10 @@ namespace YLErp.Commons
}
return ASSET_ROLL;
case QuotaTypeEnum.TRADE:
+ if ("DV".Equals(quoteIndex))
+ {
+ return TRADE_DV;
+ }
return TRADE_PRICE_RATE;
case QuotaTypeEnum.CLIENT:
return CLIENT_PRINCIPAL;
diff --git a/Framework/YLErp.Core/DBModels/EodSwap.cs b/Framework/YLErp.Core/DBModels/EodSwap.cs
index 61f2289a..4d6294a0 100644
--- a/Framework/YLErp.Core/DBModels/EodSwap.cs
+++ b/Framework/YLErp.Core/DBModels/EodSwap.cs
@@ -148,6 +148,11 @@ namespace YLErp.DBModels
[DataChange]
public decimal TdCloseQty { get; set; }
///
+ /// 基点价值DV01
+ ///
+ [DataChange]
+ public decimal? dv01 { get; set; }
+ ///
/// 平仓起始日期
///
[NotMapped]
diff --git a/Framework/YLErp.Core/DBModels/EodSwapPosition.cs b/Framework/YLErp.Core/DBModels/EodSwapPosition.cs
index 1a2944e9..f4c8f745 100644
--- a/Framework/YLErp.Core/DBModels/EodSwapPosition.cs
+++ b/Framework/YLErp.Core/DBModels/EodSwapPosition.cs
@@ -433,6 +433,11 @@ namespace YLErp.DBModels
///
public int? interest_rule { get; set; }
///
+ /// 基点价值DV01
+ ///
+ [DataChange]
+ public decimal? dv01 { get; set; }
+ ///
/// 持仓编码
///
[NotMapped]
diff --git a/Framework/YLErp.Core/DBModels/QuotaMonitor.cs b/Framework/YLErp.Core/DBModels/QuotaMonitor.cs
index 43fdddce..3de7acf1 100644
--- a/Framework/YLErp.Core/DBModels/QuotaMonitor.cs
+++ b/Framework/YLErp.Core/DBModels/QuotaMonitor.cs
@@ -891,6 +891,26 @@ namespace YLErp.DBModels
/// CCR
///
public double? Quota_CCR { get; set; } = double.NaN;
+ ///
+ /// DV
+ ///
+ public double? DV { get; set; }
+ ///
+ /// DV限额
+ ///
+ public double? Quota_DV_Upper { get; set; } = double.NaN;
+ ///
+ /// DV限额
+ ///
+ public double? Quota_DV_Lower { get; set; } = double.NaN;
+ ///
+ /// DV限额
+ ///
+ public double? Quota_DV_wUpper { get; set; } = double.NaN;
+ ///
+ /// DV限额
+ ///
+ public double? Quota_DV_wLower { get; set; } = double.NaN;
}
///
@@ -1257,5 +1277,25 @@ namespace YLErp.DBModels
public double? Quota_ThisYearTotalPnl_Lower { get; set; } = double.NaN;
public double? Quota_ThisYearTotalPnl_wUpper { get; set; } = double.NaN;
public double? Quota_ThisYearTotalPnl_wLower { get; set; } = double.NaN;
+ ///
+ /// DV
+ ///
+ public double? DV { get; set; }
+ ///
+ /// DV限额
+ ///
+ public double? Quota_DV_Upper { get; set; } = double.NaN;
+ ///
+ /// DV限额
+ ///
+ public double? Quota_DV_Lower { get; set; } = double.NaN;
+ ///
+ /// DV限额
+ ///
+ public double? Quota_DV_wUpper { get; set; } = double.NaN;
+ ///
+ /// DV限额
+ ///
+ public double? Quota_DV_wLower { get; set; } = double.NaN;
}
}
diff --git a/Framework/YLErp.Core/DBModels/UnderlyingBond.cs b/Framework/YLErp.Core/DBModels/UnderlyingBond.cs
index 5797f652..535e8746 100644
--- a/Framework/YLErp.Core/DBModels/UnderlyingBond.cs
+++ b/Framework/YLErp.Core/DBModels/UnderlyingBond.cs
@@ -80,5 +80,9 @@
/// 债券期限(利率债)
///
public string BondTerm { get; set; }
- }
+ ///
+ /// 债券单位面值,默认100
+ ///
+ public decimal? Price { get; set; }
+ }
}
diff --git a/Framework/YLErp.Core/Enums/QuotaMonitorEnums.cs b/Framework/YLErp.Core/Enums/QuotaMonitorEnums.cs
index 18486318..18980071 100644
--- a/Framework/YLErp.Core/Enums/QuotaMonitorEnums.cs
+++ b/Framework/YLErp.Core/Enums/QuotaMonitorEnums.cs
@@ -111,7 +111,7 @@ namespace YLErp.Enums
{
return new List()
{
- //new SelectItem() {Text="全局",Value="16" },
+ new SelectItem() {Text="全局",Value="16" },
//new SelectItem() {Text="场外业务",Value="0" },
//new SelectItem() {Text="场外期权",Value="1" },
new SelectItem() {Text="互换",Value="2" },
diff --git a/YLErpDAL/Helpers/Dv01Helper.cs b/YLErpDAL/Helpers/Dv01Helper.cs
new file mode 100644
index 00000000..70c0b370
--- /dev/null
+++ b/YLErpDAL/Helpers/Dv01Helper.cs
@@ -0,0 +1,51 @@
+using System;
+using YLErp.DBModels;
+using YLErp.DBModels.Enums;
+using YLErp.Modules;
+
+namespace YLErp.Helpers
+{
+ ///
+ /// DV01计算工具类
+ /// DV01 = 方向 * (券面总额(万元) / 单位面值) * 中债基点价值
+ ///
+ public static class Dv01Helper
+ {
+ ///
+ /// 计算DV01
+ ///
+ /// 标的代码
+ /// 持仓数量
+ /// 收支方向: 1=收取, 2=支付
+ /// 多空方向: 1=多头, 2=空头
+ /// 中债基点价值
+ /// DV01值
+ public static decimal CalcDv01(string underlyingCode, decimal posiQuantity, int posiDirection, int positionType, decimal vobp)
+ {
+ if (vobp == 0 || posiQuantity == 0) return 0;
+
+ var um = DataCacheProvider.GetUnderlyingDataSource().GetData(underlyingCode);
+ if (um == null || !um.IsBond()) return 0;
+
+ // 方向: 支付多头=-1, 收取多头=1, 支付空头=1, 收取空头=-1
+ // = (PosiDirection==收取?1:-1) * (PositionType==多头?1:-1)
+ int direction = (posiDirection == (int)SwapDirectionEnum.收取 ? 1 : -1)
+ * (positionType == (int)PositionTypeFlag.Long ? 1 : -1);
+
+ // 券面总额(万元) = 交易数量 / 10000
+ double faceValueWan = Convert.ToDouble(posiQuantity) / 10000d;
+
+ // 单位面值, 默认100
+ decimal unitFaceValue = 100m;
+ if (!string.IsNullOrEmpty(um.ExJson))
+ {
+ var bond = JsonHelper.Deserialize(um.ExJson);
+ if (bond?.Price != null && bond.Price != 0)
+ unitFaceValue = bond.Price.Value;
+ }
+
+ decimal result = Convert.ToDecimal(direction * (faceValueWan / Convert.ToDouble(unitFaceValue)) * Convert.ToDouble(vobp));
+ return Math.Round(result, 4);
+ }
+ }
+}
diff --git a/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs b/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs
index 7c43ed2c..3247dd67 100644
--- a/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs
+++ b/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs
@@ -794,6 +794,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>((obj, settings) =>
{
obj.StockEqvNotional = Convert.ToDouble(posiQuery.Sum(s => s.PosiNotionalValue));
@@ -906,6 +919,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()
@@ -914,6 +931,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>();
var underly = GetTradePositionPnl();
@@ -925,6 +947,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()
@@ -932,14 +969,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
{
@@ -962,10 +1005,14 @@ namespace YLErp.Modules.RiskModule
///
public List 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>((obj, settings) =>
{
obj.StockEqvNotional = Convert.ToDouble(posiQuery.Sum(s => s.PosiNotionalValue));
@@ -987,6 +1034,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()
@@ -995,6 +1046,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()
{
@@ -1008,7 +1064,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
{
@@ -1322,11 +1383,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();
+
+ // 批量获取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);
@@ -1336,6 +1409,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);
@@ -1350,6 +1430,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);
@@ -1429,11 +1510,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();
+
+ // 批量获取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);
@@ -1443,10 +1539,19 @@ 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);
}
+ DbContext.SaveChanges();
+
var total = new QuotaMonitor_Trade()
{
TradeNumber = "合计",
@@ -1457,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);
@@ -3521,6 +3627,20 @@ namespace YLErp.Modules.RiskModule
singleUnderlyingRate = valuedateBLL.SystemDate.SingleUnderlyingRate;
_quotaSettings = new List();
#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 互换
@@ -3616,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 客户
@@ -4394,6 +4528,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);
@@ -4421,7 +4558,7 @@ namespace YLErp.Modules.RiskModule
{
continue;
}
- clientRiskCheckItem = CheckQuota(checkPoisiList, settingItem.QuotaType, clientRiskCheckItem, settingItem.QuotaRange, allList);
+ clientRiskCheckItem = CheckQuota(checkPoisiList, settingItem.QuotaType, clientRiskCheckItem, settingItem.QuotaRange, allList, totalDv, currentDv);
if (clientRiskCheckItem != null)
{
clientRiskCheckItem.quotaType = $"{EnumHelper.GetDescriptionByName(settingItem.QuotaType)}({settingItem.QuotaIndex})";
@@ -4453,6 +4590,50 @@ namespace YLErp.Modules.RiskModule
.Where(x => umCodes.Contains(x.UnderlyingCode))
.ToList();
}
+ ///
+ /// 计算风控检查用的DV(全局DV + 当前单笔DV)
+ ///
+ private (double totalDv, double currentDv) CalcRiskCheckDv(List 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 tradeTypes = new List { "利率债", "信用债", "其它债券" };
+ 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, List) GetClientPositionsAndOrders(BondOmsDBContext bondDb, long orderId)
{
var clientPositions = bondDb.client_position.AsNoTracking().ToList();
@@ -4898,8 +5079,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);
}
}
}
@@ -5298,7 +5481,7 @@ namespace YLErp.Modules.RiskModule
/// 试算类型是预警还是警告
/// 试算结果描述
///
- private bool CheckQuota(List positionList, List posiList, List setting, List settingAll, bool warning, out string msg)
+ private bool CheckQuota(List positionList, List posiList, List setting, List settingAll, bool warning, out string msg, double totalDv = 0, double currentDv = 0)
{
msg = "";
var msgList = new List();
@@ -5308,11 +5491,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));
@@ -5366,17 +5552,25 @@ namespace YLErp.Modules.RiskModule
///
/// 单笔名义本金
///
- private ClientRiskCheckItem CheckQuota(List positionList, QuotaTypeEnum quotaType, ClientRiskCheckItem checkItem, int quotaRange, List settingAll)
+ private ClientRiskCheckItem CheckQuota(List positionList, QuotaTypeEnum quotaType, ClientRiskCheckItem checkItem, int quotaRange, List 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);
@@ -5411,7 +5605,8 @@ namespace YLErp.Modules.RiskModule
BusinessType = "标的交易",
PositionPnl=0,
DeltaExposure=0,
- StockEqvNotional=0
+ StockEqvNotional=0,
+ DV=0
};
List tradetypes = new List { "利率债", "信用债", "其它债券" };
var tposis = DbContext.TradePosition.Where(x => tradetypes.Contains(x.TradeType)).AsNoTracking().ToList();
@@ -5433,6 +5628,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;
@@ -5455,7 +5656,8 @@ namespace YLErp.Modules.RiskModule
BusinessType = "标的交易",
PositionPnl = 0,
DeltaExposure = 0,
- StockEqvNotional = 0
+ StockEqvNotional = 0,
+ DV = 0
};
List tradetypes = new List { "利率债", "信用债", "其它债券" };
var tposis = DbContext.eod_trade_position.Where(x => tradetypes.Contains(x.TradeType)&&x.ValueDate==valueDate).AsNoTracking().ToList();
@@ -5477,6 +5679,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;
@@ -5540,6 +5749,27 @@ namespace YLErp.Modules.RiskModule
}
return messageList;
}
+ ///
+ /// 检验全局DV限额
+ ///
+ private List checkGlobalDv(List positionList, QuotaSetting[] settings, bool warning, double totalDv = -1)
+ {
+ var messageList = new List();
+ 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 positionList, string tag_prefix, ClientRiskCheckItem checkItem, QuotaTypeEnum quotaType)
{
@@ -5574,7 +5804,7 @@ namespace YLErp.Modules.RiskModule
}
return null;
}
- private List checkTrade(List positionList, List posiList, string tag_prefix, QuotaSetting[] settings, bool warning)
+ private List checkTrade(List positionList, List posiList, string tag_prefix, QuotaSetting[] settings, bool warning, double currentDv = -1)
{
if (string.IsNullOrEmpty(tag_prefix))
{
@@ -5590,6 +5820,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)
@@ -5632,6 +5865,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;
}
@@ -5639,7 +5876,7 @@ namespace YLErp.Modules.RiskModule
}
return messageList;
}
- private ClientRiskCheckItem checkTrade(List positionList, string tag_prefix, ClientRiskCheckItem checkItem)
+ private ClientRiskCheckItem checkTrade(List positionList, string tag_prefix, ClientRiskCheckItem checkItem, double currentDv = 0)
{
if (string.IsNullOrEmpty(tag_prefix))
{
@@ -5671,10 +5908,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 checkUnderlying(List positionList, List posiList, string tag_prefix, QuotaSetting[] settings, List settingAll, bool warning)
diff --git a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs
index 6cfcbcb7..929e557b 100644
--- a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs
+++ b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs
@@ -3,6 +3,7 @@ using Newtonsoft.Json;
using System.Linq.Expressions;
using YLErp.DBModels.Consts;
using YLErp.DBModels.Enums;
+using YLErp.Helpers;
using YLErp.Model;
using YLErp.Model.Enum;
using YLErp.Models;
@@ -1291,6 +1292,7 @@ namespace YLErp.Modules.SwapModule
//浮动端估值用信息
newEodPayPosition.UnderlyingPrice = UnderlyingCodePrice(newEodPayPosition.UnderlyingCode, eventFlow.EventDate, out decimal vobp);
+ newEodPayPosition.dv01 = Dv01Helper.CalcDv01(newEodPayPosition.UnderlyingCode, newEodPayPosition.PosiQuantity, newEodPayPosition.PosiDirection, newEodPayPosition.PositionType, vobp);
newEodPayPosition.UnderlyingMarketValue = newEodPayPosition.UnderlyingPrice * newEodPayPosition.PosiQuantity * newEodPayPosition.ContractSize * shortRatio;
//当日已实现
newEodPayPosition.TdCloseQty = closeQty;
@@ -1361,6 +1363,7 @@ namespace YLErp.Modules.SwapModule
int directionRatio = eod.PosiDirection == (int)SwapDirectionEnum.收取 ? 1 : -1;
curretEod.PosiStatus = curretEod.PosiQuantity == 0 ? 1 : 0;
var price = UnderlyingCodePrice(eod.UnderlyingCode, dealDate, out decimal vobp);
+ curretEod.dv01 = Dv01Helper.CalcDv01(eod.UnderlyingCode, curretEod.PosiQuantity, eod.PosiDirection, eod.PositionType, vobp);
decimal tax = um.ValueAddedTax ?? 0;
BondPaymentService bondPaymentService = new BondPaymentService(UserInfo);
if (valueDate > td.StartDate.Value && curretEod.PosiQuantity > 0)
@@ -1442,6 +1445,7 @@ namespace YLErp.Modules.SwapModule
int directionRatio = eod.PosiDirection == (int)SwapDirectionEnum.收取 ? 1 : -1;
var price = UnderlyingCodePrice(eod.UnderlyingCode, dealDate, out decimal vobp);
SetPriceInfoByFlowEvent(eod, curretEod, unwindEvents, swapPosition);
+ curretEod.dv01 = Dv01Helper.CalcDv01(eod.UnderlyingCode, curretEod.PosiQuantity, eod.PosiDirection, eod.PositionType, vobp);
curretEod.UnderlyingPrice = price;
curretEod.UnderlyingMarketValue = curretEod.UnderlyingPrice * curretEod.PosiQuantity * curretEod.ContractSize * shortRatio;
curretEod.PosiMtmPnL = (curretEod.UnderlyingPrice - curretEod.PosiGrossPrice) * curretEod.PosiQuantity * curretEod.ContractSize * shortRatio * directionRatio;
@@ -1614,6 +1618,7 @@ namespace YLErp.Modules.SwapModule
curretEod.PosiTradingFee = position.PosiTradingFee;
curretEod.UnderlyingPrice = UnderlyingCodePrice(position.UnderlyingCode, dealDate, out decimal vobp);
SetPriceInfoByFlowEvent(eod, curretEod, unwindEvents, position);
+ curretEod.dv01 = Dv01Helper.CalcDv01(curretEod.UnderlyingCode, curretEod.PosiQuantity, curretEod.PosiDirection, curretEod.PositionType, vobp);
//if (settleDate == td.TradeDate)
//{
// curretEod.UnderlyingPrice = curretEod.PosiGrossPrice;
diff --git a/YLErpWeb/wwwroot/Scripts/app/risk/quotaMonitor.js b/YLErpWeb/wwwroot/Scripts/app/risk/quotaMonitor.js
index bcb2bea8..4c159554 100644
--- a/YLErpWeb/wwwroot/Scripts/app/risk/quotaMonitor.js
+++ b/YLErpWeb/wwwroot/Scripts/app/risk/quotaMonitor.js
@@ -1,4 +1,4 @@
-var varieties = [{ id: 0, Name: '全部' }].concat(ylotc.varieties);
+var varieties = [{ id: 0, Name: '全部' }].concat(ylotc.varieties);
var remarkDict = {};
var valueDateShowStatus = true;
$(function () {
@@ -1587,6 +1587,33 @@ var colModel_trade = [
var style = "style='" + checkQuota(rowObject, 'SwapPercent') + "'";
return style;
}
+ }, {
+ name: 'Quota_DV_Upper',
+ label: 'DV限额',
+ index: 'Quota_DV_Upper',
+ width: 120,
+ align: 'right',
+ sortable: false,
+ formatter: function (cellvalue, options, rowObject) {
+ return formatQuotaAbs(rowObject, 'DV');
+ }
+ }, {
+ name: 'DV',
+ label: 'DV',
+ index: 'DV',
+ width: 120,
+ align: 'right',
+ sortable: false,
+ formatter: function (cellvalue, options, rowObject) {
+ if (!cellvalue && cellvalue !== 0 || cellvalue == "NaN") return "";
+ var abs = Math.abs(cellvalue);
+ if (abs < 0.0001) return "0";
+ return cellvalue.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 4 });
+ },
+ cellattr: function (cellvalue, options, rowObject) {
+ var style = "style='" + checkQuota(rowObject, 'DV') + "'";
+ return style;
+ }
}
];
@@ -1676,6 +1703,51 @@ var colModel_global = [
formatter: function (cellvalue, options, rowObject) {
return !cellvalue || cellvalue == "NaN" ? "" : cellvalue;
}
+ }, {
+ name: 'Quota_DV_Upper',
+ label: 'DV限额',
+ index: 'Quota_DV_Upper',
+ width: 120,
+ align: 'right',
+ sortable: false,
+ formatter: function (cellvalue, options, rowObject) {
+ return formatQuotaAbs(rowObject, 'DV');
+ },
+ cellattr: function (cellvalue, options, rowObject) {
+ var style = '';
+ if (!rowObject.ParentKey) {
+ style += 'font-weight: bold;'
+ if (rowObject.BusinessType == "合计") {
+ style += 'font-size: 1rem;'
+ }
+ }
+ return "style='" + style + "'";
+ }
+ }, {
+ name: 'DV',
+ label: 'DV',
+ index: 'DV',
+ width: 120,
+ align: 'right',
+ sortable: false,
+ formatter: function (cellvalue, options, rowObject) {
+ if (rowObject.BusinessType == "场内业务") {
+ return '';
+ }
+ if (!cellvalue && cellvalue !== 0 || cellvalue == "NaN") return "";
+ if (Math.abs(cellvalue) < 0.0001) return "0";
+ return cellvalue.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 4 });
+ },
+ cellattr: function (cellvalue, options, rowObject) {
+ var style = checkQuota(rowObject, 'DV');
+ if (!rowObject.ParentKey) {
+ style += 'font-weight: bold;'
+ if (rowObject.BusinessType == "合计") {
+ style += 'font-size: 1rem;'
+ }
+ }
+ return "style='" + style + "'";
+ }
}
];
diff --git a/YLErpWeb/wwwroot/Scripts/app/risk/quotaSetting.js b/YLErpWeb/wwwroot/Scripts/app/risk/quotaSetting.js
index d0ad962e..543e8caa 100644
--- a/YLErpWeb/wwwroot/Scripts/app/risk/quotaSetting.js
+++ b/YLErpWeb/wwwroot/Scripts/app/risk/quotaSetting.js
@@ -430,6 +430,7 @@ var colModel = [
case "非权益类业务规模":
case "本年累计盈亏":
case "浮动盈亏":
+ case "DV": // DV是绝对值指标,不允许百分比模式
break;
default:
html += "onclick='changePercent(this)' ";
diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/EodPositionRisks.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/EodPositionRisks.js
index 3818a18f..9770c225 100644
--- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/EodPositionRisks.js
+++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/EodPositionRisks.js
@@ -610,6 +610,13 @@ function colModelGridEodSwap() {
width: 150,
align: 'center',
formatter: StockEqvNotionalFormat,
+ }, {
+ name: 'position.dv01',
+ label: 'DV',
+ index: 'position.dv01',
+ width: 120,
+ align: 'center',
+ formatter: StockEqvNotionalFormat,
}, {
name: 'SwapTradeTypeStr',
label: '互换类型',