Merge branch 'glms/feature/dotnumber' into glms/feature/1.4.2
# Conflicts: # YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml # YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js
This commit is contained in:
@@ -32,11 +32,30 @@ namespace YLErp.Modules.SwapModule
|
||||
|
||||
}
|
||||
|
||||
private int GetStorageDeliveryPriceRound(string underlyingInstrumentType, string underlyingCode)
|
||||
{
|
||||
if (ConsGlobal.InstrumentType.IsBond(underlyingInstrumentType))
|
||||
{
|
||||
return ConsGlobal.PriceRound;
|
||||
}
|
||||
if (string.IsNullOrEmpty(underlyingCode))
|
||||
{
|
||||
return ConsGlobal.SwapDeliveryPriceRound;
|
||||
}
|
||||
return GetUnderlyingData(underlyingCode)?.IsBond() == true
|
||||
? ConsGlobal.PriceRound
|
||||
: ConsGlobal.SwapDeliveryPriceRound;
|
||||
}
|
||||
|
||||
#region 可测试化接缝(Seams)——override 这些虚方法可在测试中替换 DB/外部调用,生产代码行为不变
|
||||
|
||||
/// <summary>持久化 eod 持仓记录(生产: DbContext.Add;测试: 收集到列表)</summary>
|
||||
protected virtual void PersistEodSwapPosition(eod_swap_position position)
|
||||
{
|
||||
var storagePriceRound = GetStorageDeliveryPriceRound(position.UnderlyingInstrumentType, position.UnderlyingCode);
|
||||
position.PosiGrossPrice = Math.Round(position.PosiGrossPrice, storagePriceRound, MidpointRounding.AwayFromZero);
|
||||
position.UnderlyingPrice = Math.Round(position.UnderlyingPrice, storagePriceRound, MidpointRounding.AwayFromZero);
|
||||
position.PosiNotionalValue = Math.Round(position.PosiNotionalValue, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
if (position.id == 0)
|
||||
{
|
||||
DbContext.eod_swap_position.Add(position);
|
||||
@@ -156,34 +175,18 @@ namespace YLErp.Modules.SwapModule
|
||||
return UnderlyingCodePrice(code, settleDate, out vobp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用于互换浮动腿盯市的标的价格。
|
||||
///
|
||||
/// 普通债券类收益互换的新录入页面将全价按小数保存,例如页面录入 20% 后
|
||||
/// PosiGrossPrice 为 0.2;而历史交易中仍可能存在直接保存为 20 的展示态价格。
|
||||
/// 中债估值正常经 EodPriceQueryService 转换后应为小数价格,但手工维护的历史
|
||||
/// 行情可能仍以展示态进入该服务,例如 2000 经一次转换后得到 20。若将 20
|
||||
/// 与 0.2 直接相减,会把 20% 的价格差误算成 1,980,000 的浮动损益。
|
||||
///
|
||||
/// 因此仅当交易期初价已经是小数口径、且当前债券价明显仍处于展示态时,再做
|
||||
/// 一次展示态到存储态转换。期初价本身是历史展示态口径的存量交易保持原价格,
|
||||
/// 避免修改日终估值链路后改变其既有损益。
|
||||
/// </summary>
|
||||
private decimal GetSwapValuationPrice(string code, decimal posiGrossPrice, DateTime settleDate, out decimal vobp)
|
||||
/// <summary>获取用于互换浮动腿盯市的标的价格。</summary>
|
||||
private decimal GetSwapValuationPrice(string code, DateTime settleDate, out decimal vobp)
|
||||
{
|
||||
var price = GetUnderlyingPrice(code, settleDate, out vobp);
|
||||
var underlying = GetUnderlyingData(code);
|
||||
var usesStoragePrice = Math.Abs(posiGrossPrice) < 2m;
|
||||
var usesDisplayPrice = Math.Abs(price) >= 10m;
|
||||
|
||||
if (underlying?.IsBond() == true && usesStoragePrice && usesDisplayPrice)
|
||||
if (underlying?.IsBond() == true)
|
||||
{
|
||||
var normalizedPrice = BondPriceConverter.ToStorage(price);
|
||||
Log.Error($"互换债券日终价格按展示态返回,已转换为存储态: UnderlyingCode={code}, ValueDate={settleDate:yyyy-MM-dd}, PosiGrossPrice={posiGrossPrice}, SourcePrice={price}, NormalizedPrice={normalizedPrice}");
|
||||
return normalizedPrice;
|
||||
return Math.Round(price, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
return price;
|
||||
return Math.Round(price, ConsGlobal.SwapDeliveryPriceRound, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
/// <summary>获取标的缓存数据(生产: DataCacheProvider;测试: 返回内存对象)</summary>
|
||||
@@ -1472,7 +1475,10 @@ namespace YLErp.Modules.SwapModule
|
||||
newEodPayPosition.ContractSize = eventFlow.ContractSize;
|
||||
newEodPayPosition.CountRatio = eventFlow.CountRatio;
|
||||
newEodPayPosition.PosiNetPrice = netPrice;
|
||||
newEodPayPosition.PosiGrossPrice = grossPrice;
|
||||
newEodPayPosition.PosiGrossPrice = Math.Round(
|
||||
grossPrice,
|
||||
GetStorageDeliveryPriceRound(eventFlow.UnderlyingInstrumentType, eventFlow.UnderlyingCode),
|
||||
MidpointRounding.AwayFromZero);
|
||||
newEodPayPosition.PosiNetFeePrice = netFeePrice;
|
||||
newEodPayPosition.PosiNetNoFeePrice = netNoFeePrice;
|
||||
newEodPayPosition.PosiQuantity = payQty;
|
||||
@@ -1557,7 +1563,7 @@ namespace YLErp.Modules.SwapModule
|
||||
int shortRatio = eod.PositionType == (int)PositionTypeFlag.Long ? 1 : -1;
|
||||
int directionRatio = eod.PosiDirection == (int)SwapDirectionEnum.收取 ? 1 : -1;
|
||||
curretEod.PosiStatus = curretEod.PosiQuantity == 0 ? 1 : 0;
|
||||
var price = GetSwapValuationPrice(eod.UnderlyingCode, eod.PosiGrossPrice, dealDate, out decimal vobp);
|
||||
var price = GetSwapValuationPrice(eod.UnderlyingCode, dealDate, out decimal vobp);
|
||||
curretEod.dv01 = Dv01Helper.CalcDv01(eod.UnderlyingCode, curretEod.PosiQuantity, eod.PosiDirection, eod.PositionType, vobp);
|
||||
decimal tax = um.ValueAddedTax ?? 0;
|
||||
if (valueDate > td.StartDate.Value && curretEod.PosiQuantity > 0)
|
||||
@@ -1649,7 +1655,7 @@ namespace YLErp.Modules.SwapModule
|
||||
var dealDate = curretEod.ValueDate;
|
||||
int shortRatio = eod.PositionType == (int)PositionTypeFlag.Long ? 1 : -1;
|
||||
int directionRatio = eod.PosiDirection == (int)SwapDirectionEnum.收取 ? 1 : -1;
|
||||
var price = GetSwapValuationPrice(eod.UnderlyingCode, eod.PosiGrossPrice, dealDate, out decimal vobp);
|
||||
var price = GetSwapValuationPrice(eod.UnderlyingCode, dealDate, out decimal vobp);
|
||||
var todayConsumedDividend = CalcConsumedDividend(curretEod, unwindEvents);
|
||||
var originNotional = (decimal)td.OriginalStockEqvNotional / swapPosition.PosiNetPrice;
|
||||
decimal totalPayment = CalcBondPayment(curretEod.UnderlyingCode, td.StartDate.Value, valueDate, (decimal)originNotional, shortRatio, directionRatio);
|
||||
@@ -1771,7 +1777,10 @@ namespace YLErp.Modules.SwapModule
|
||||
posiQty = 0;
|
||||
}
|
||||
curretEod.PosiGrossPrice = (eod.PosiGrossPrice * eod.PosiQuantity + openFlowEvents.Sum(a => a.Quantity * a.TradingAmountAvg)) / (eod.PosiQuantity + openQty);
|
||||
curretEod.PosiGrossPrice = Math.Round(curretEod.PosiGrossPrice, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
|
||||
curretEod.PosiGrossPrice = Math.Round(
|
||||
curretEod.PosiGrossPrice,
|
||||
GetStorageDeliveryPriceRound(curretEod.UnderlyingInstrumentType, curretEod.UnderlyingCode),
|
||||
MidpointRounding.AwayFromZero);
|
||||
curretEod.PosiNetPrice = (eod.PosiNetPrice * eod.PosiQuantity + openFlowEvents.Sum(a => a.Quantity * a.TradingAmountFeeAvg)) / (eod.PosiQuantity + openQty);
|
||||
curretEod.PosiNetPrice = Math.Round(curretEod.PosiNetPrice, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
|
||||
curretEod.PosiNetNoFeePrice = (eod.PosiNetNoFeePrice * eod.PosiQuantity + openFlowEvents.Sum(a => a.Quantity * a.TradingAmountNetAvg)) / (eod.PosiQuantity + openQty);
|
||||
@@ -1835,7 +1844,7 @@ namespace YLErp.Modules.SwapModule
|
||||
curretEod.ContractSize = position.ContractSize;
|
||||
curretEod.CountRatio = position.CountRatio;
|
||||
curretEod.PosiTradingFee = position.PosiTradingFee;
|
||||
curretEod.UnderlyingPrice = GetSwapValuationPrice(position.UnderlyingCode, position.PosiGrossPrice, dealDate, out decimal vobp);
|
||||
curretEod.UnderlyingPrice = GetSwapValuationPrice(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)
|
||||
@@ -1897,14 +1906,14 @@ namespace YLErp.Modules.SwapModule
|
||||
}
|
||||
if (data.IsBond())
|
||||
{
|
||||
return BondPrice(data, settleDate, out vobp);
|
||||
return Math.Round(BondPrice(data, settleDate, out vobp), ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
var price = data.Price ?? 0;
|
||||
if (EodPriceQueryService.TryGetEodPrice(settleDate, code, out var eodPrice))
|
||||
{
|
||||
price = eodPrice.GetPrice(SettlementTypeEnum.ClosePrice);
|
||||
}
|
||||
return Convert.ToDecimal(price);
|
||||
return Math.Round(Convert.ToDecimal(price), ConsGlobal.SwapDeliveryPriceRound, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取债券收盘价格
|
||||
@@ -1949,9 +1958,9 @@ namespace YLErp.Modules.SwapModule
|
||||
var positions = eodSwapPositions.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode)).ToList();//持仓腿
|
||||
// 框架合约的方向约定:多头为正、空头为负;总名义本金取交易原始规模,
|
||||
// 不能直接用多空腿相加,否则会把对冲方向误当成合约规模变化。
|
||||
eod_Swap.NotionalValueLong = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.PosiNotionalValue);
|
||||
eod_Swap.NotionalValueShort = -Math.Abs(positions.Where(x => x.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.PosiNotionalValue));
|
||||
eod_Swap.NotionalValue = Convert.ToDecimal(td.OriginalStockEqvNotional ?? td.StockEqvNotional);
|
||||
eod_Swap.NotionalValueLong = Math.Round(positions.Where(x => x.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.PosiNotionalValue), ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
eod_Swap.NotionalValueShort = Math.Round(-Math.Abs(positions.Where(x => x.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.PosiNotionalValue)), ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
eod_Swap.NotionalValue = Math.Round(Convert.ToDecimal(td.OriginalStockEqvNotional ?? td.StockEqvNotional), ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
eod_Swap.SwapTradeId = td.id;
|
||||
eod_Swap.SwapTradeNo = td.TradeNumber;
|
||||
eod_Swap.ClientId = td.ClientId;
|
||||
@@ -2026,9 +2035,9 @@ namespace YLErp.Modules.SwapModule
|
||||
var eodSwapPositions = DbContext.eod_swap_position.Where(x => x.SwapTradeId == td.id && x.ValueDate == settleDate && !x.Invalid).ToList();
|
||||
var interestPositions = eodSwapPositions.Where(x => string.IsNullOrEmpty(x.UnderlyingCode)).ToList();//利息腿
|
||||
var positions = eodSwapPositions.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode)).ToList();//持仓腿
|
||||
eod_Swap.NotionalValue = Convert.ToDecimal(td.OriginalStockEqvNotional ?? td.StockEqvNotional);
|
||||
eod_Swap.NotionalValueLong = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.PosiNotionalValue);
|
||||
eod_Swap.NotionalValueShort = -Math.Abs(positions.Where(x => x.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.PosiNotionalValue));
|
||||
eod_Swap.NotionalValue = Math.Round(Convert.ToDecimal(td.OriginalStockEqvNotional ?? td.StockEqvNotional), ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
eod_Swap.NotionalValueLong = Math.Round(positions.Where(x => x.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.PosiNotionalValue), ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
eod_Swap.NotionalValueShort = Math.Round(-Math.Abs(positions.Where(x => x.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.PosiNotionalValue)), ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
|
||||
eod_Swap.MarketValueLong = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.UnderlyingMarketValue);
|
||||
eod_Swap.MarketValueShort = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.UnderlyingMarketValue);
|
||||
eod_Swap.FloatingPnL = positions.Sum(s => s.PosiProfitSum);
|
||||
@@ -2318,24 +2327,23 @@ namespace YLErp.Modules.SwapModule
|
||||
item.PeriodPaymentValuation = item.FloatingUnrealizedPnl + item.position.InterestPnL;
|
||||
}
|
||||
// eod_swap 的保证金本金来自 trade_span;缺少 span 数据时会被保存为 0。
|
||||
// 本风险页改按日终保证金腿展示,且该页面保证金本金采用原始本金的 1/10 口径。
|
||||
// 利息仍使用原始本金累积值,不能同步缩放,否则会破坏保证金利息金额。
|
||||
// 本风险页改按日终保证金腿的实际本金展示。
|
||||
item.position.InitMarginGain = marginLegs
|
||||
.Where(x => x.InterestMode == (int)InterestModeEnum.初始预付金
|
||||
&& x.InterestDirection == (int)SwapDirectionEnum.收取)
|
||||
.Sum(x => Math.Abs(x.InterestPrincipalFix)) / 10m;
|
||||
.Sum(x => Math.Abs(x.InterestPrincipalFix));
|
||||
item.position.InitMarginLoss = marginLegs
|
||||
.Where(x => x.InterestMode == (int)InterestModeEnum.初始预付金
|
||||
&& x.InterestDirection == (int)SwapDirectionEnum.支付)
|
||||
.Sum(x => Math.Abs(x.InterestPrincipalFix)) / 10m;
|
||||
.Sum(x => Math.Abs(x.InterestPrincipalFix));
|
||||
item.position.PostionMarginGain = marginLegs
|
||||
.Where(x => x.InterestMode == (int)InterestModeEnum.追加预付金
|
||||
&& x.InterestDirection == (int)SwapDirectionEnum.收取)
|
||||
.Sum(x => Math.Abs(x.InterestPrincipalFix)) / 10m;
|
||||
.Sum(x => Math.Abs(x.InterestPrincipalFix));
|
||||
item.position.PostionMarginLoss = marginLegs
|
||||
.Where(x => x.InterestMode == (int)InterestModeEnum.追加预付金
|
||||
&& x.InterestDirection == (int)SwapDirectionEnum.支付)
|
||||
.Sum(x => Math.Abs(x.InterestPrincipalFix)) / 10m;
|
||||
.Sum(x => Math.Abs(x.InterestPrincipalFix));
|
||||
|
||||
// 保证金本金方向与我方的利息现金流方向相反:原始“收取”保证金
|
||||
// 表示我方占用客户资金,应向客户支付利息;支付金额按负数展示。
|
||||
|
||||
Reference in New Issue
Block a user