fix(swap): 修复收益互换价格转换和初始保证金计算问题

- 移除债券价格显示态和存储态转换的错误判断逻辑
- 简化债券价格转换条件,只对债券标的进行存储态转换
- 添加初始保证金计算前的金额精度统一处理
- 重构初始保证金准备逻辑到独立方法中
- 修复因价格单位不一致导致的浮动损益计算错误
This commit is contained in:
张名锐
2026-07-23 10:13:09 +08:00
parent bbf98ba61c
commit 018136c615
2 changed files with 24 additions and 15 deletions
@@ -162,11 +162,7 @@ namespace YLErp.Modules.SwapModule
/// <summary>
/// 获取用于互换浮动腿盯市的标的价格。
///
/// 普通债券类收益互换的新录入页面将全价按小数保存,例如页面录入 20% 后
/// PosiGrossPrice 为 0.2;而历史交易中仍可能存在直接保存为 20 的展示态价格。
/// 中债估值正常经 EodPriceQueryService 转换后应为小数价格,但手工维护的历史
/// 行情可能仍以展示态进入该服务,例如 2000 经一次转换后得到 20。若将 20
/// 与 0.2 直接相减,会把 20% 的价格差误算成 1,980,000 的浮动损益。
/// 普通债券类收益互换的新录入页面将全价按小数保存,例如页面录入 20% 后 PosiGrossPrice 为 0.2
///
/// 因此仅当交易期初价已经是小数口径、且当前债券价明显仍处于展示态时,再做
/// 一次展示态到存储态转换。期初价本身是历史展示态口径的存量交易保持原价格,
@@ -176,10 +172,10 @@ namespace YLErp.Modules.SwapModule
{
var price = GetUnderlyingPrice(code, settleDate, out vobp);
var underlying = GetUnderlyingData(code);
var usesStoragePrice = Math.Abs(posiGrossPrice) < 2m;
var usesDisplayPrice = Math.Abs(price) >= 10m;
// 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}");
@@ -381,7 +381,10 @@ namespace YLErp.Modules.SwapModule
CountRatio = underlying.CountRatio,
ContractSize = Convert.ToDecimal(underlying.ContractSize),
PosiNetPrice = flowMerge.TradingAmountFeeAvgAbs,
PosiGrossPrice = Math.Round(flowMerge.TradingAmountAvg, ConsGlobal.SwapDeliveryPriceRound, MidpointRounding.AwayFromZero),
PosiGrossPrice = Math.Round(
flowMerge.TradingAmountAvg,
underlying.IsBond() ? ConsGlobal.PriceRound : ConsGlobal.SwapDeliveryPriceRound,
MidpointRounding.AwayFromZero),
PosiNetFeePrice = flowMerge.TradingAmountNetFeeAvg ?? 0,
PosiNetNoFeePrice = flowMerge.TradingAmountNetAvg ?? 0,
PosiQuantity = flowMerge.TradingQtyAbs,
@@ -684,11 +687,7 @@ namespace YLErp.Modules.SwapModule
private bool PrepareTrade(trade req, TradeSourceEnum dataSource, underlying_manager um)
{
bool tradeNumberGenerated = false;
req.InitialMargin = Convert.ToDouble(req.trade_Initial_Margin.MarginValue);
if (req.trade_Initial_Margin.MarginType == 0)
{
req.InitialMargin = req.StockEqvNotional == 0 ? 0 : Convert.ToDouble(req.trade_Initial_Margin.MarginValue) * req.StockEqvNotional;
};
PrepareInitialMargin(req);
var isAddNew = req.id == 0;
if (isAddNew)
{
@@ -731,6 +730,17 @@ namespace YLErp.Modules.SwapModule
return tradeNumberGenerated;
}
private static void PrepareInitialMargin(trade req)
{
// 初始预付金依赖最终入库的名义本金,须先统一金额精度,避免两者无法勾稽。
req.StockEqvNotional = Math.Round(req.StockEqvNotional, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero);
req.InitialMargin = Convert.ToDouble(req.trade_Initial_Margin.MarginValue);
if (req.trade_Initial_Margin.MarginType == 0)
{
req.InitialMargin = req.StockEqvNotional == 0 ? 0 : Convert.ToDouble(req.trade_Initial_Margin.MarginValue) * req.StockEqvNotional;
}
}
//准备单个交易
private trade PrepareSingleTrade(trade req, TradeSourceEnum dataSource, bool isAddNew, underlying_manager um)
{
@@ -1364,7 +1374,10 @@ namespace YLErp.Modules.SwapModule
// position.PosiGrossPrice = string.IsNullOrEmpty(swap.UnderlyingCode)
// ? Math.Round(swap.PosiGrossPrice, ConsGlobal.SwapDeliveryPriceRound, MidpointRounding.AwayFromZero)
// : ValidateDeliveryPrice(swap.PosiGrossPrice, "期初交割价");
position.PosiGrossPrice = Math.Round(swap.PosiGrossPrice, ConsGlobal.SwapDeliveryPriceRound, MidpointRounding.AwayFromZero);
var storagePriceRound = ConsGlobal.InstrumentType.IsBond(swap.UnderlyingInstrumentType)
? ConsGlobal.PriceRound
: ConsGlobal.SwapDeliveryPriceRound;
position.PosiGrossPrice = Math.Round(swap.PosiGrossPrice, storagePriceRound, MidpointRounding.AwayFromZero);
position.PosiNetPrice = swap.PosiQuantity == 0 ? 0 : (position.PosiGrossPrice + (position.PosiTradingFeePending / swap.PosiQuantity) * ratio);
position.PosiNetPrice = Math.Round(position.PosiNetPrice, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
position.PosiNetNoFeePrice = swap.PosiNetNoFeePrice;