diff --git a/YLErpDAL/Modules/SwapModule/SwapDealService.cs b/YLErpDAL/Modules/SwapModule/SwapDealService.cs index 7bf7f637..ba8b980c 100644 --- a/YLErpDAL/Modules/SwapModule/SwapDealService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapDealService.cs @@ -37,7 +37,6 @@ namespace YLErp.Modules.SwapModule protected virtual long SaveSwapDeal(UnwindData unwindData, int eventType, int clientCashId, string eventResason = "", bool approve = false) { NormalizeNotionalValues(unwindData); - NormalizeDeliveryPrices(unwindData.FlowEvents); return SaveSwapDealInternal(unwindData, eventType, clientCashId, eventResason, approve); } @@ -48,49 +47,6 @@ namespace YLErp.Modules.SwapModule unwindData.CloseNotionalValue = Math.Round(unwindData.CloseNotionalValue, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); } - private static int GetStorageDeliveryPriceRound(swap_flow_event flowEvent) - { - if (ConsGlobal.InstrumentType.IsBond(flowEvent?.UnderlyingInstrumentType)) - { - return ConsGlobal.PriceRound; - } - if (string.IsNullOrEmpty(flowEvent?.UnderlyingCode)) - { - return ConsGlobal.SwapDeliveryPriceRound; - } - var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(flowEvent?.UnderlyingCode); - return underlying?.IsBond() == true ? ConsGlobal.PriceRound : ConsGlobal.SwapDeliveryPriceRound; - } - - private static void ValidateDeliveryPrices(UnwindData unwindData) - { - if (unwindData.FlowEvents == null) - { - return; - } - foreach (var item in unwindData.FlowEvents.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode))) - { - var roundedPrice = Math.Round(item.TradingAmountAvg, GetStorageDeliveryPriceRound(item), MidpointRounding.AwayFromZero); - if (item.TradingAmountAvg != roundedPrice) - { - throw new ServiceException($"期末交割价最多保留{ConsGlobal.SwapDeliveryPriceRound}位小数"); - } - item.TradingAmountAvg = roundedPrice; - } - } - - private static void NormalizeDeliveryPrices(IEnumerable flowEvents) - { - if (flowEvents == null) - { - return; - } - foreach (var item in flowEvents.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode))) - { - item.TradingAmountAvg = Math.Round(item.TradingAmountAvg, GetStorageDeliveryPriceRound(item), MidpointRounding.AwayFromZero); - } - } - /// 保存所有变更(生产: DbContext.SaveChanges;测试: 空操作) protected virtual void SaveAllChanges() { @@ -1291,7 +1247,6 @@ namespace YLErp.Modules.SwapModule { throw new ServiceException("未找到交易信息"); } - ValidateDeliveryPrices(unwindData); NormalizeNotionalValues(unwindData); //CheckLastEod(unwindData.ValueDate, td.StartDate.Value, unwindData.SwapTradeId); //去掉平仓收盘限制 ValidateFrontendPnL(unwindData, isIncome: false); // 只读校验告警,不阻断交易 @@ -1776,7 +1731,6 @@ namespace YLErp.Modules.SwapModule { throw new ServiceException("未找到交易信息"); } - ValidateDeliveryPrices(unwindData); NormalizeIncomeUnwindDate(unwindData); ValidateIncomeValueDate(unwindData, td); //CheckLastEod(unwindData.ValueDate, td.StartDate.Value, unwindData.SwapTradeId); //去掉平仓收盘限制 @@ -1817,14 +1771,12 @@ namespace YLErp.Modules.SwapModule throw new Exception("该笔交易状态为平仓待复核,未找到相关记录,请检查该笔交易是否有效"); } swapEvent.unwindData = JsonConvert.DeserializeObject(swapEvent.EventData); - NormalizeDeliveryPrices(swapEvent.unwindData.FlowEvents); if (eventType == (int)SwapEventTypeEnum.互换) { NormalizeIncomeUnwindDate(swapEvent.unwindData); ValidateIncomeValueDate(swapEvent.unwindData, td); } var flowList = FindFlowEventsByEventId(swapEvent.id); - NormalizeDeliveryPrices(flowList); string action = eventType == (int)SwapEventTypeEnum.互换 ? ClientCashInCashOut.系统操作_互换 : ClientCashInCashOut.系统操作_平仓费; int clientCashId = AddClientCash(td, Convert.ToDouble(-swapEvent.unwindData.SwapRealizedPnL), action, swapEvent.unwindData.ValueDate); if (swapEvent.unwindData.SwapMarginAmount != 0) @@ -1867,7 +1819,6 @@ namespace YLErp.Modules.SwapModule { throw new ServiceException("未找到交易信息"); } - ValidateDeliveryPrices(unwindData); if (eventType == (int)SwapEventTypeEnum.互换) { NormalizeIncomeUnwindDate(unwindData); diff --git a/YLErpDAL/Modules/SwapModule/SwapTradeService.cs b/YLErpDAL/Modules/SwapModule/SwapTradeService.cs index f32f97fe..3c673d43 100644 --- a/YLErpDAL/Modules/SwapModule/SwapTradeService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapTradeService.cs @@ -52,16 +52,6 @@ namespace YLErp.Modules.SwapModule } - private static decimal ValidateDeliveryPrice(decimal price, string fieldName) - { - var roundedPrice = Math.Round(price, ConsGlobal.SwapDeliveryPriceRound, MidpointRounding.AwayFromZero); - if (price != roundedPrice) - { - throw new ServiceException($"{fieldName}最多保留{ConsGlobal.SwapDeliveryPriceRound}位小数"); - } - return roundedPrice; - } - private static decimal? RoundSwapBondNetPriceAndYtm(decimal? value) { return value.HasValue @@ -602,7 +592,7 @@ namespace YLErp.Modules.SwapModule dbTrade.trade_extend = req.trade_extend; dbTrade.swap_positions = req.swap_positions; dbTrade.MetaDic = req.MetaDic; - dbTrade.InitYtm = RoundSwapBondNetPriceAndYtm(req.swap_positions.FirstOrDefault(p => p.InitYtm != null)?.InitYtm); + dbTrade.InitYtm = req.swap_positions.FirstOrDefault(p => p.InitYtm != null)?.InitYtm; InnerSaveTrade(false, dbTrade, changsStr, changeConfirmStatus); return dbTrade; @@ -1381,16 +1371,10 @@ namespace YLErp.Modules.SwapModule position.UnderlyingCode = swap.UnderlyingCode; position.UnderlyingInstrumentType = swap.UnderlyingInstrumentType; position.PosiDirection = swap.PosiDirection; - // position.PosiGrossPrice = string.IsNullOrEmpty(swap.UnderlyingCode) - // ? Math.Round(swap.PosiGrossPrice, ConsGlobal.SwapDeliveryPriceRound, MidpointRounding.AwayFromZero) - // : ValidateDeliveryPrice(swap.PosiGrossPrice, "期初交割价"); - var storagePriceRound = ConsGlobal.InstrumentType.IsBond(swap.UnderlyingInstrumentType) - ? ConsGlobal.PriceRound - : ConsGlobal.SwapDeliveryPriceRound; - position.PosiGrossPrice = Math.Round(swap.PosiGrossPrice, storagePriceRound, MidpointRounding.AwayFromZero); + position.PosiGrossPrice = swap.PosiGrossPrice; 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 = RoundSwapBondNetPriceAndYtm(swap.PosiNetNoFeePrice); + position.PosiNetNoFeePrice = swap.PosiNetNoFeePrice; position.PosiNetFeePrice = swap.PosiQuantity == 0 ? 0 : (swap.PosiNetNoFeePrice + (position.PosiTradingFeePending / swap.PosiQuantity) * ratio); position.PosiNetFeePrice = Math.Round(position.PosiNetFeePrice??0, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero); position.PosiNotionalValue = Math.Round(swap.PosiNotionalValue, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); @@ -1416,7 +1400,7 @@ namespace YLErp.Modules.SwapModule position.interest_rest_days = swap.interest_rest_days; position.interest_rule = swap.interest_rule; position.category_tag = string.IsNullOrEmpty(swap.category_tag) ? "互换利率" : swap.category_tag; - position.InitYtm = RoundSwapBondNetPriceAndYtm(swap.InitYtm); + position.InitYtm = swap.InitYtm; if (position.InitYtm != null && position.InitYtm > 0) { td.InitYtm = position.InitYtm; diff --git a/YLErpWeb/App_Data/Config/otcformat.js b/YLErpWeb/App_Data/Config/otcformat.js index 626aa7c8..d4c79353 100644 --- a/YLErpWeb/App_Data/Config/otcformat.js +++ b/YLErpWeb/App_Data/Config/otcformat.js @@ -1,2 +1,125 @@ var main = main || {}; -main.formatOptions = { "trading": { "umprice": { "trimTailZeros": true, "precision": 9, "grouping": true, "rounded": true, "percent": false, "minDecimals": 2, "maxDecimals": 9 }, "umpriceP": { "trimTailZeros": true, "percent": true, "precision": 9, "grouping": false, "rounded": true, "minDecimals": 2, "maxDecimals": 9 }, "umpricePR": { "trimTailZeros": true, "precision": 9, "grouping": false, "rounded": true, "percent": false, "minDecimals": 2, "maxDecimals": 9 }, "tradeSinglePrice": { "trimTailZeros": true, "precision": 2, "grouping": true, "rounded": true, "percent": false, "minDecimals": 2, "maxDecimals": 9 }, "premiumRateP": { "trimTailZeros": true, "percent": true, "precision": 9, "grouping": false, "rounded": true, "minDecimals": 2, "maxDecimals": 9 }, "premiumRate": { "trimTailZeros": true, "precision": 9, "grouping": false, "rounded": true, "percent": false, "minDecimals": 2, "maxDecimals": 9 }, "tradePrice": { "trimTailZeros": true, "precision": 9, "grouping": false, "rounded": true, "percent": false, "minDecimals": 2, "maxDecimals": 9 }, "StockEqvNotional": { "trimTailZeros": true, "precision": 9, "grouping": true, "rounded": true, "percent": false, "minDecimals": 2, "maxDecimals": 9 }, "notional": { "trimTailZeros": true, "precision": 9, "grouping": true, "rounded": true, "percent": false, "minDecimals": 2, "maxDecimals": 9 }, "notionalP": { "trimTailZeros": true, "percent": true, "precision": 9, "grouping": false, "rounded": true, "minDecimals": 2, "maxDecimals": 9 }, "volatility": { "trimTailZeros": true, "precision": 9, "grouping": false, "rounded": true, "percent": false, "minDecimals": 2, "maxDecimals": 9 }, "volatilityP": { "trimTailZeros": true, "percent": true, "precision": 9, "grouping": false, "rounded": true, "minDecimals": 2, "maxDecimals": 9 }, "greek": { "trimTailZeros": true, "precision": 9, "grouping": false, "rounded": true, "percent": false, "minDecimals": 2, "maxDecimals": 9 }, "marginRateP": { "trimTailZeros": true, "percent": true, "precision": 9, "grouping": false, "rounded": true, "minDecimals": 2, "maxDecimals": 9 }, "marginRate": { "trimTailZeros": true, "precision": 9, "grouping": false, "rounded": true, "percent": false, "minDecimals": 2, "maxDecimals": 9 } } }; \ No newline at end of file +main.formatOptions={ + "trading": { + "umprice": { + "precision": 9, + "grouping": true, + "rounded": true, + "percent": false, + "minDecimals": 9, + "maxDecimals": 0 + }, + "umpriceP": { + "percent": true, + "precision": 2, + "grouping": false, + "rounded": true, + "minDecimals": 2, + "maxDecimals": 0 + }, + "umpricePR": { + "precision": 4, + "grouping": false, + "rounded": true, + "percent": false, + "minDecimals": 4, + "maxDecimals": 2 + }, + "tradeSinglePrice": { + "precision": 2, + "grouping": true, + "rounded": true, + "percent": false, + "minDecimals": 2, + "maxDecimals": 0 + }, + "premiumRateP": { + "percent": true, + "precision": 2, + "grouping": false, + "rounded": true, + "minDecimals": 2, + "maxDecimals": 0 + }, + "premiumRate": { + "precision": 4, + "grouping": false, + "rounded": true, + "percent": false, + "minDecimals": 4, + "maxDecimals": 2 + }, + "tradePrice": { + "precision": 2, + "grouping": false, + "rounded": true, + "percent": false, + "minDecimals": 2, + "maxDecimals": 0 + }, + "StockEqvNotional": { + "precision": 2, + "grouping": true, + "rounded": true, + "percent": false, + "minDecimals": 2, + "maxDecimals": 0 + }, + "notional": { + "precision": 2, + "grouping": true, + "rounded": true, + "percent": false, + "minDecimals": 2, + "maxDecimals": 0 + }, + "notionalP": { + "percent": true, + "precision": 2, + "grouping": false, + "rounded": true, + "minDecimals": 2, + "maxDecimals": 0 + }, + "volatility": { + "precision": 4, + "grouping": false, + "rounded": true, + "percent": false, + "minDecimals": 4, + "maxDecimals": 2 + }, + "volatilityP": { + "percent": true, + "precision": 2, + "grouping": false, + "rounded": true, + "minDecimals": 2, + "maxDecimals": 0 + }, + "greek": { + "precision": 2, + "grouping": false, + "rounded": true, + "percent": false, + "minDecimals": 2, + "maxDecimals": 0 + }, + "marginRateP": { + "percent": true, + "precision": 2, + "grouping": false, + "rounded": true, + "minDecimals": 2, + "maxDecimals": 0 + }, + "marginRate": { + "precision": 4, + "grouping": false, + "rounded": true, + "percent": false, + "minDecimals": 4, + "maxDecimals": 2 + } + } +}; \ No newline at end of file diff --git a/YLErpWeb/App_Data/Config/swappriceprecision.js b/YLErpWeb/App_Data/Config/swappriceprecision.js new file mode 100644 index 00000000..e6ec17d7 --- /dev/null +++ b/YLErpWeb/App_Data/Config/swappriceprecision.js @@ -0,0 +1,46 @@ +// 通过 /front/swappriceprecision 加载。可按 UnderlyingInstrumentType 修改;缺项或非法值由页面精度组件回退内置默认规则。 +window.main = window.main || {}; +window.main.swapPricePrecision = { + Stock: { integerDigits: 7, precision: 2 }, + StockIndex: { integerDigits: 7, precision: 2 }, + StockIF: { integerDigits: 7, precision: 4 }, + CommodityFutures: { integerDigits: 7, precision: 4 }, + CommoditySpot: { integerDigits: 7, precision: 4 }, + NewOtcStock: { integerDigits: 7, precision: 4 }, + HKStock: { integerDigits: 7, precision: 4 }, + HKStockIndex: { integerDigits: 7, precision: 4 }, + Fund: { integerDigits: 7, precision: 4 }, + Bond: { + grossPrice: { integerDigits: 6, precision: 9 }, + netPrice: { integerDigits: 6, precision: 9 }, + yield: { integerDigits: 2, precision: 6 } + }, + TBonds: { + grossPrice: { integerDigits: 6, precision: 9 }, + netPrice: { integerDigits: 6, precision: 9 }, + yield: { integerDigits: 2, precision: 6 } + }, + CreditBonds: { + grossPrice: { integerDigits: 6, precision: 9 }, + netPrice: { integerDigits: 6, precision: 9 }, + yield: { integerDigits: 2, precision: 6 } + }, + OtherBonds: { + grossPrice: { integerDigits: 6, precision: 9 }, + netPrice: { integerDigits: 6, precision: 9 }, + yield: { integerDigits: 2, precision: 6 } + }, + TBFutures: { integerDigits: 8, precision: 4 }, + OtherFutures: { integerDigits: 8, precision: 4 }, + GoldSpot: { integerDigits: 8, precision: 4 }, + OtherSpot: { integerDigits: 8, precision: 4 }, + AbroadFutures: { integerDigits: 8, precision: 4 }, + AbroadSpot: { integerDigits: 8, precision: 4 }, + AbroadStock: { integerDigits: 8, precision: 2 }, + AbroadStockIndex: { integerDigits: 8, precision: 4 }, + ExRate: { integerDigits: 2, precision: 8 }, + Shibor: { integerDigits: 2, precision: 4 }, + FixingRepoRate: { integerDigits: 2, precision: 4 }, + + // TODO: 利率收益率(6+8)、债券指数(6+4)、黄金期货(6+4)待对应的 UnderlyingInstrumentType 枚举确认后启用。 +}; diff --git a/YLErpWeb/Controllers/FrontController.cs b/YLErpWeb/Controllers/FrontController.cs index 3f5925e8..66234ef1 100644 --- a/YLErpWeb/Controllers/FrontController.cs +++ b/YLErpWeb/Controllers/FrontController.cs @@ -37,6 +37,17 @@ namespace YLErp.Web.Controllers return Content(js, "text/javascript"); } + [AllowAnonymous] + [ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)] + public ActionResult SwapPricePrecision() + { + var filePath = Server.MapPath("~/App_Data/Config/swappriceprecision.js"); + var js = System.IO.File.Exists(filePath) + ? System.IO.File.ReadAllText(filePath) + : "var main = main || {}; main.swapPricePrecision = {};"; + return Content(js, "text/javascript"); + } + //今年及前后两年的日历数据(缓存120s) [ResponseCache(Duration = 120, Location = ResponseCacheLocation.Any)] public ActionResult Calendar() diff --git a/YLErpWeb/Views/SwapTrade2/EodPositionRisks.cshtml b/YLErpWeb/Views/SwapTrade2/EodPositionRisks.cshtml index c877df9b..aded5bf8 100644 --- a/YLErpWeb/Views/SwapTrade2/EodPositionRisks.cshtml +++ b/YLErpWeb/Views/SwapTrade2/EodPositionRisks.cshtml @@ -18,6 +18,8 @@ + + } @@ -49,4 +51,4 @@ @Html.Raw(JqGridSimple.OutTable()) - \ No newline at end of file + diff --git a/YLErpWeb/Views/SwapTrade2/SwapIncome.cshtml b/YLErpWeb/Views/SwapTrade2/SwapIncome.cshtml index c86b8774..4d3d6a5a 100644 --- a/YLErpWeb/Views/SwapTrade2/SwapIncome.cshtml +++ b/YLErpWeb/Views/SwapTrade2/SwapIncome.cshtml @@ -28,6 +28,8 @@ + + } @@ -163,7 +165,7 @@ {{priceFormat(floatPosition.TradingAmountNetAvg > 0 ? floatPosition.TradingAmountNetAvg : floatPosition.PosiNetPrice)}} - + diff --git a/YLErpWeb/Views/SwapTrade2/SwapUnwind.cshtml b/YLErpWeb/Views/SwapTrade2/SwapUnwind.cshtml index e1ecadcb..d7683a3b 100644 --- a/YLErpWeb/Views/SwapTrade2/SwapUnwind.cshtml +++ b/YLErpWeb/Views/SwapTrade2/SwapUnwind.cshtml @@ -22,6 +22,8 @@ + + } @@ -200,7 +202,7 @@ {{priceFormat(floatPosition.PosiGrossPrice)}} - + diff --git a/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml b/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml index 0643749a..ecc17156 100644 --- a/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml +++ b/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml @@ -100,6 +100,8 @@ + + } @@ -474,16 +476,16 @@ - + - + - 重算 + 重算 - + {{item.underlying!=null?item.underlying.QuoteUnitString:''}} diff --git a/YLErpWeb/Views/SwapTrade2/TradeView.cshtml b/YLErpWeb/Views/SwapTrade2/TradeView.cshtml index 4cdf1c8e..cc33c15b 100644 --- a/YLErpWeb/Views/SwapTrade2/TradeView.cshtml +++ b/YLErpWeb/Views/SwapTrade2/TradeView.cshtml @@ -1,4 +1,5 @@ @using YLErp.Enums; +@using System.Globalization; @model TradeViewModel @{ @@ -29,6 +30,7 @@ var positions = trade.swap_positions.Where(x=>x.PosiDirection>0&&x.IsInitial).ToList(); var realPositions = trade.swap_positions.Where(x => x.PosiDirection > 0 && !x.IsInitial).ToList(); var sr = trade.trade_extend.ExtendObj.SettlementRules; + string SwapPriceData(decimal? value) => value?.ToString(CultureInfo.InvariantCulture) ?? string.Empty; } @section CSS{ @@ -46,6 +48,8 @@ + + } @@ -396,15 +400,15 @@ @(item.PositionType == (int)PositionTypeFlag.Long ? "多头" : "空头") @item.UnderlyingCode - @((item.PosiGrossPrice * multiplier).OtcFormat(OtcFormatFlag.umprice)) + @if (isBond) { - @(((item.PosiNetNoFeePrice ?? 0) * multiplier).OtcFormat(OtcFormatFlag.umprice)) + - @((item.InitYtm * multiplier).OtcFormat(OtcFormatFlag.umprice)) + } @@ -613,12 +617,12 @@ @item.UnderlyingCode - @((item.PosiGrossPrice * multiplier).OtcFormat(OtcFormatFlag.umprice)) + @if (isBond) { - @(((item.PosiNetNoFeePrice ?? 0) * multiplier).OtcFormat(OtcFormatFlag.umprice)) + } @@ -818,13 +822,13 @@ @closeFloat.UnderlyingCode @if (isBond) { - @((closeFloat.PosiGrossPrice * multiplier).OtcFormat(OtcFormatFlag.umprice)) - @((closeFloat.TradingAmountAvg * multiplier).OtcFormat(OtcFormatFlag.umprice)) + + } else { - @(closeFloat.PosiGrossPrice.OtcFormat(OtcFormatFlag.umprice)) - @(closeFloat.TradingAmountAvg.OtcFormat(OtcFormatFlag.umprice)) + + } @(closeFloat.Quantity.OtcFormat(OtcFormatFlag.StockEqvNotional)) @(closeFloat.TradingFee.OtcFormat(OtcFormatFlag.StockEqvNotional)) @@ -1014,14 +1018,14 @@ @closeFloat.UnderlyingCode @if (isBond) { - @((closeFloat.PosiGrossPrice * multiplier).OtcFormat(OtcFormatFlag.umprice)) - @(((closeFloat.TradingAmountNetAvg ?? 0) * multiplier).OtcFormat(OtcFormatFlag.umprice)) + + } else { - @((closeFloat.TradingAmountAvg * multiplier).OtcFormat(OtcFormatFlag.umprice)) + } - @((closeFloat.TradingAmountAvg * multiplier).OtcFormat(OtcFormatFlag.umprice)) + @((closeFloat.PositionQty??0).OtcFormat(OtcFormatFlag.StockEqvNotional)) @(closeFloat.TradingFee.OtcFormat(OtcFormatFlag.StockEqvNotional)) @(closeFloat.DividendIn.OtcFormat(OtcFormatFlag.StockEqvNotional)) diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/EodPositionRisks.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/EodPositionRisks.js index 99067de4..7d38fc21 100644 --- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/EodPositionRisks.js +++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/EodPositionRisks.js @@ -800,7 +800,10 @@ function exportEodSwapRows(jgrid, fileName, groupConfig, exportColumnNames) { } //---------------------------Formatter--------------------------------- function PriceFormat(cellValue, options, rowObject) { - return otcformat.trading.umprice(cellValue); + return swapPricePrecision.format( + cellValue, + rowObject && rowObject.eodPosition && rowObject.eodPosition.UnderlyingInstrumentType, + 'grossPrice'); } function RealizedPnlFormat(cellValue, options, rowObject) { diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/incomeSwapTrade.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/incomeSwapTrade.js index a0057f28..2159db48 100644 --- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/incomeSwapTrade.js +++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/incomeSwapTrade.js @@ -73,14 +73,18 @@ const vue = new Vue({ $(this.$refs.incomeValueDatePicker.$el).val(MaxIncomeValueDate); return false; }, - // 守卫: 价格缩放因子(债券 multiplier=100 时界面为百分比态, 计算用相对价需 ÷100) - // 计算已外置到 swapCalc.getPriceScale; 改动需同步 swapCalc.test.js - getPriceScale() { - return SwapCalc.getPriceScale(this.multiplier); + getDeliveryPriceInputFormat() { + return swapPricePrecision.getInputFormat( + this.floatPosition && this.floatPosition.UnderlyingInstrumentType, + 'grossPrice', + inputFormatSwapDeliveryPrice); }, getStorageDeliveryPrice() { - const precision = inputFormatSwapDeliveryPrice.precision + (this.multiplier === 100 ? 2 : 0); - return SwapCalc.roundHalfAwayFromZero(Number(this.floatPosition.TradingAmountAvg) * this.getPriceScale(), precision); + return swapPricePrecision.roundForSubmit( + swapPricePrecision.shiftDecimal(this.floatPosition.TradingAmountAvg, this.multiplier === 100 ? -2 : 0), + this.floatPosition && this.floatPosition.UnderlyingInstrumentType, + 'grossPrice', + this.multiplier === 100 ? 2 : 0); }, initDeal() { var positions = model.FlowEvents.filter((item) => { @@ -110,8 +114,8 @@ const vue = new Vue({ return tradeHelper.IsBond(instType); }, priceFormat(price) { - price = price * this.multiplier; - var pricef = otcformat.trading.umprice(price); + price = swapPricePrecision.shiftDecimal(price, this.multiplier === 100 ? 2 : 0); + var pricef = swapPricePrecision.format(price, this.floatPosition && this.floatPosition.UnderlyingInstrumentType, 'grossPrice'); return pricef; }, dataFormat() { @@ -165,8 +169,10 @@ const vue = new Vue({ main.post("/underlying_manager/GetUnderlyingPriceByCode", { code: thisObj.floatPosition.UnderlyingCode, valuedate: thisObj.deal.ValueDate }) .done(function (res) { - res.obj = res.obj * thisObj.multiplier; - thisObj.floatPosition.TradingAmountAvg = _.round(Number(res.obj), 9); + thisObj.floatPosition.TradingAmountAvg = swapPricePrecision.roundForSubmit( + swapPricePrecision.shiftDecimal(res.obj, thisObj.multiplier === 100 ? 2 : 0), + thisObj.floatPosition.UnderlyingInstrumentType, + 'grossPrice'); thisObj.calcFloatClosePnl(); }); }, @@ -382,6 +388,7 @@ const vue = new Vue({ components: { 'vue-datepicker': FastVue.vueDatePicker(), 'vue-number-input': FastVue.vueNumberInput(), + 'vue-swap-price-input': swapPricePrecision.createVueInputComponent(), } }); diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapPricePrecisionHelper.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapPricePrecisionHelper.js new file mode 100644 index 00000000..84888db9 --- /dev/null +++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapPricePrecisionHelper.js @@ -0,0 +1,249 @@ +var swapPricePrecision = (function (global) { + const defaults = Object.freeze({ + Stock: { integerDigits: 7, precision: 2 }, + StockIndex: { integerDigits: 7, precision: 2 }, + StockIF: { integerDigits: 7, precision: 4 }, + CommodityFutures: { integerDigits: 7, precision: 4 }, + CommoditySpot: { integerDigits: 7, precision: 4 }, + NewOtcStock: { integerDigits: 7, precision: 4 }, + HKStock: { integerDigits: 7, precision: 4 }, + HKStockIndex: { integerDigits: 7, precision: 4 }, + Fund: { integerDigits: 7, precision: 4 }, + Bond: { + grossPrice: { integerDigits: 6, precision: 11 }, + netPrice: { integerDigits: 6, precision: 11 }, + yield: { integerDigits: 2, precision: 6 } + }, + TBonds: { + grossPrice: { integerDigits: 6, precision: 11 }, + netPrice: { integerDigits: 6, precision: 11 }, + yield: { integerDigits: 2, precision: 6 } + }, + CreditBonds: { + grossPrice: { integerDigits: 6, precision: 11 }, + netPrice: { integerDigits: 6, precision: 11 }, + yield: { integerDigits: 2, precision: 6 } + }, + OtherBonds: { + grossPrice: { integerDigits: 6, precision: 11 }, + netPrice: { integerDigits: 6, precision: 11 }, + yield: { integerDigits: 2, precision: 6 } + }, + TBFutures: { integerDigits: 8, precision: 4 }, + OtherFutures: { integerDigits: 8, precision: 4 }, + GoldSpot: { integerDigits: 8, precision: 4 }, + OtherSpot: { integerDigits: 8, precision: 4 }, + AbroadFutures: { integerDigits: 8, precision: 4 }, + AbroadSpot: { integerDigits: 8, precision: 4 }, + AbroadStock: { integerDigits: 8, precision: 2 }, + AbroadStockIndex: { integerDigits: 8, precision: 4 }, + ExRate: { integerDigits: 2, precision: 8 }, + Shibor: { integerDigits: 2, precision: 4 }, + FixingRepoRate: { integerDigits: 2, precision: 4 } + // TODO: Add InterestYield, BondIndex and GoldFutures after their enum values are confirmed. + }); + + function normalizeDecimal(value) { + if (value === null || value === undefined || value === '') return ''; + let text = String(value).trim(); + if (/[eE]/.test(text)) { + const number = Number(text); + if (!Number.isFinite(number)) return null; + text = number.toFixed(20).replace(/0+$/, '').replace(/\.$/, ''); + } + if (!/^[+-]?(?:\d+|\d*\.\d+)$/.test(text)) return null; + const negative = text.charAt(0) === '-'; + text = text.replace(/^[+-]/, ''); + const parts = text.split('.'); + const integerPart = parts[0].replace(/^0+(?=\d)/, '') || '0'; + const decimalPart = parts.length > 1 ? parts[1] : ''; + const result = integerPart + (decimalPart ? '.' + decimalPart : ''); + return negative && !/^0(?:\.0*)?$/.test(result) ? '-' + result : result; + } + + function shiftDecimal(value, places) { + let normalized = normalizeDecimal(value); + if (!normalized || !Number.isInteger(places) || places === 0) return normalized; + const negative = normalized.charAt(0) === '-'; + const parts = (negative ? normalized.substring(1) : normalized).split('.'); + const integerPart = parts[0]; + const decimalPart = parts.length > 1 ? parts[1] : ''; + const digits = integerPart + decimalPart; + const decimalIndex = integerPart.length + places; + let text; + if (decimalIndex <= 0) text = '0.' + '0'.repeat(-decimalIndex) + digits; + else if (decimalIndex >= digits.length) text = digits + '0'.repeat(decimalIndex - digits.length); + else text = digits.substring(0, decimalIndex) + '.' + digits.substring(decimalIndex); + return normalizeDecimal((negative ? '-' : '') + text); + } + + function incrementDigits(value) { + let carry = 1; + let result = ''; + for (let index = value.length - 1; index >= 0; index--) { + const digit = value.charCodeAt(index) - 48 + carry; + if (digit === 10) { + result = '0' + result; + carry = 1; + } else { + result = String(digit) + result; + carry = 0; + } + } + return carry ? '1' + result : result; + } + + function roundDecimal(value, precision) { + const normalized = normalizeDecimal(value); + if (normalized === null || !Number.isInteger(precision) || precision < 0) return value; + const negative = normalized.charAt(0) === '-'; + const parts = (negative ? normalized.substring(1) : normalized).split('.'); + let integerPart = parts[0]; + const decimalPart = parts.length > 1 ? parts[1] : ''; + if (decimalPart.length <= precision) return normalized; + + let digits = integerPart + decimalPart.substring(0, precision); + if (decimalPart.charAt(precision) >= '5') digits = incrementDigits(digits); + if (digits.length <= precision) digits = digits.padStart(precision + 1, '0'); + integerPart = precision === 0 ? digits : digits.substring(0, digits.length - precision); + const roundedDecimal = precision === 0 ? '' : digits.substring(digits.length - precision); + return normalizeDecimal((negative ? '-' : '') + integerPart + (roundedDecimal ? '.' + roundedDecimal : '')); + } + + function normalizeRule(rule) { + if (!rule || typeof rule !== 'object') return null; + const integerDigits = Number(rule.integerDigits); + const precision = Number(rule.precision); + if (!Number.isInteger(integerDigits) || integerDigits < 1 || integerDigits > 18 + || !Number.isInteger(precision) || precision < 0 || precision > 13) return null; + return { integerDigits: integerDigits, precision: precision }; + } + + function findRule(source, instrumentType, field) { + const typeRule = source && source[instrumentType]; + return typeRule ? normalizeRule(typeRule[field] || typeRule) : null; + } + + function getRule(instrumentType, field) { + const fallback = findRule(defaults, instrumentType, field); + if (!fallback) return null; + return findRule(global.main && global.main.swapPricePrecision, instrumentType, field) || fallback; + } + + function format(value, instrumentType, field) { + const rule = getRule(instrumentType, field); + if (value === null || value === undefined || value === '') return ''; + if (!rule) return global.otcformat.trading.umprice(value); + const rounded = roundDecimal(value, rule.precision); + return rounded === null ? '' : rounded.replace(/(\.\d*?[1-9])0+$/, '$1').replace(/\.0+$/, ''); + } + + function normalizeInput(value, format, shouldRound) { + const options = format || {}; + const maxIntegerDigits = Number(options.integerDigits) || 0; + const precision = Number(options.precision) || 0; + const source = String(value === null || value === undefined ? '' : value).trim().replaceAll(',', '').replaceAll('。', '.'); + let negative = false; + let hasDot = false; + let integerPart = ''; + let decimalPart = ''; + for (let index = 0; index < source.length; index++) { + const ch = source.charAt(index); + if (ch >= '0' && ch <= '9') { + if (hasDot) { + if (shouldRound || decimalPart.length < precision) decimalPart += ch; + } else if (!maxIntegerDigits || integerPart.length < maxIntegerDigits) { + integerPart += ch; + } + } else if (ch === '.' && !hasDot && precision > 0) { + hasDot = true; + } else if (ch === '-' && index === 0 && options.negative) { + negative = true; + } + } + if (!integerPart && !decimalPart) return negative ? '-' : ''; + const text = (negative ? '-' : '') + (integerPart || '0') + (hasDot ? '.' + decimalPart : ''); + if (!shouldRound || text.endsWith('.')) return text; + return roundDecimal(text, precision); + } + + function createVueInputComponent() { + return { + props: { + value: { type: [Number, String], default: '' }, + format: { type: Object, default: function () { return {}; } }, + disabled: { type: Boolean } + }, + data: function () { + return { text: '' }; + }, + mounted: function () { + this.text = this.toDisplay(this.value); + }, + methods: { + toDisplay: function (value) { + if (value === null || value === undefined || value === '') return ''; + const displayValue = this.format && this.format.percent ? shiftDecimal(value, 2) : String(value); + return normalizeInput(displayValue, this.format, true); + }, + toModel: function (value) { + if (!value || value === '-') return ''; + return this.format && this.format.percent ? shiftDecimal(value, -2) : value; + }, + updateValue: function (value, shouldRound, shouldCommit) { + this.text = normalizeInput(value, this.format, shouldRound); + if (this.text.endsWith('.') && !shouldRound) return; + if (shouldCommit) this.$emit('input', this.toModel(this.text)); + }, + onInput: function (event) { + this.updateValue(event.target.value, false, false); + // When the normalized value is unchanged, Vue skips the DOM patch. + // Write it directly so excess digits do not remain in the native input. + event.target.value = this.text; + }, + onPaste: function (event) { + const clipboard = event.clipboardData || global.clipboardData; + if (!clipboard) return; + event.preventDefault(); + this.updateValue(clipboard.getData('text'), true, false); + event.target.value = this.text; + }, + onBlur: function () { + if (this.text.endsWith('.')) this.text = this.text.substring(0, this.text.length - 1); + this.updateValue(this.text, true, true); + event.target.value = this.text; + } + }, + watch: { + value: function (value) { + const display = this.toDisplay(value); + if (display !== this.text) this.text = display; + }, + format: { + deep: true, + handler: function () { + this.text = this.toDisplay(this.value); + } + } + }, + template: '' + }; + } + + return Object.freeze({ + getRule: getRule, + getInputFormat: function (instrumentType, field, options) { + const rule = getRule(instrumentType, field); + return rule ? Object.assign({}, options, rule) : Object.assign({}, options); + }, + format: format, + roundForSubmit: function (value, instrumentType, field, storagePrecisionOffset) { + const rule = getRule(instrumentType, field); + if (value === null || value === undefined || value === '' || !rule) return value; + const offset = Number.isInteger(storagePrecisionOffset) ? storagePrecisionOffset : 0; + return roundDecimal(value, rule.precision + offset); + }, + shiftDecimal: shiftDecimal, + createVueInputComponent: createVueInputComponent + }); +}(window)); diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js index c4522b8d..960f47fa 100644 --- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js +++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js @@ -16,9 +16,6 @@ const inputFormatPosiFeePercent = Object.freeze({ precision: 4, negative: true, const inputFormatPosiFeeUnit = Object.freeze({ precision: 6, negative: true, append: '' }); const inputFormatMarginRate = Object.freeze({ precision: otcformat.trading.marginRateP.precision, append: '%' }); const inputFormatSwapDeliveryPrice = Object.freeze({ precision: 9, negative: true, append: '', percent: false }); -const inputFormatSwapBondDeliveryPrice = Object.freeze({ precision: 9, negative: true, append: '', percent: true }); -const inputFormatSwapBondNetPriceAndYtm = Object.freeze({ precision: 9, negative: true, append: '', percent: true }); -const swapBondStoragePricePrecision = inputFormatSwapBondDeliveryPrice.precision + 2; const consPosiFeeType = Object.freeze({ Percent: 0, Unit: 1 }); const swapPosiFeeCalc = Object.freeze({ normalizeFeeType(feeType) { @@ -251,19 +248,26 @@ const vue = new Vue({ }); }, methods: { - roundStorageDeliveryPrice(item, price) { - const precision = tradeHelper.IsBond(item && item.UnderlyingInstrumentType) - ? swapBondStoragePricePrecision - : inputFormatSwapDeliveryPrice.precision; - return _.round(Number(price), precision); + getPosiPriceInputFormat(item, field) { + return swapPricePrecision.getInputFormat( + item && item.UnderlyingInstrumentType, + field, + Object.assign({}, inputFormatSwapDeliveryPrice, { + percent: this.trade.StructureType !== '普通收益互换' + })); }, - roundStorageBondNetPriceAndYtm(value) { - return value == null ? value : _.round(Number(value), swapBondStoragePricePrecision); + roundStoragePrice(item, price, field) { + const storagePrecisionOffset = tradeHelper.IsBond(item && item.UnderlyingInstrumentType) ? 2 : 0; + return swapPricePrecision.roundForSubmit( + price, + item && item.UnderlyingInstrumentType, + field, + storagePrecisionOffset); }, getPosiPriceFormatKey(item, field) { const index = item && item.index != null ? item.index : ''; - const isBond = tradeHelper.IsBond(item && item.UnderlyingInstrumentType); - return `${index}-${field}-${isBond ? 'bond' : 'other'}`; + const instrumentType = item && item.UnderlyingInstrumentType ? item.UnderlyingInstrumentType : 'unknown'; + return `${index}-${field}-${instrumentType}`; }, getCurrentPosiFeeType() { return this.posiFeeModePercent ? consPosiFeeType.Percent : consPosiFeeType.Unit; @@ -404,7 +408,7 @@ const vue = new Vue({ return; } item._lastBondErr = null; // 成功则清标记,便于下次真出不同错误时仍能提示 - // 以既有三字段为代理,调用纯函数(已手动设过的字段不被覆盖),再写回。 + // 以既有三字段为代理,调用纯函数;手工输入字段保留原始十进制字符串,避免经 Number 参与计算后丢失末位。 // 关键:proxy 内必须统一为【展示态】(per-100-face),因为 applyBondCalcResult 写入的是计算器返回的展示态。 // 模型字段是【存储态小数】(percent:true 下 1.00 对应界面 100),所以初始化时要 bondPriceToCalc(×100); // 若直接用存储态初始化,则用户手填字段被 applyBondCalcResult 跳过后,proxy 中仍残留存储态, @@ -418,9 +422,9 @@ const vue = new Vue({ SwapCalc.applyBondCalcResult(proxy, resp.obj, manual); // 回写前 bondCalcPriceToStorage(÷100)(展示态→存储态小数):proxy 里均为展示态; // 模型字段存存储态(0.995),须 ÷100 落回模型,否则配合 percent:true 显示会 ×100 成离谱值。 - item.PosiNetNoFeePrice = SwapCalc.bondCalcPriceToStorage(proxy.cleanPrice); - item.PosiGrossPrice = SwapCalc.bondCalcPriceToStorage(proxy.dirtyPrice); - item.InitYtm = SwapCalc.bondCalcPriceToStorage(proxy.ytm); + if (!manual.CP) item.PosiNetNoFeePrice = self.roundStoragePrice(item, SwapCalc.bondCalcPriceToStorage(proxy.cleanPrice), 'netPrice'); + if (!manual.DP) item.PosiGrossPrice = self.roundStoragePrice(item, SwapCalc.bondCalcPriceToStorage(proxy.dirtyPrice), 'grossPrice'); + if (!manual.YD) item.InitYtm = self.roundStoragePrice(item, SwapCalc.bondCalcPriceToStorage(proxy.ytm), 'yield'); // 名义本金依赖全价(PosiGrossPrice):以净价/收益率为源反算出的全价被回写后, // 直接赋值不会触发组件 input 事件,需在此显式重算,保持名义本金与全价一致。 if (self.calcNotional) self.calcNotional(); @@ -453,7 +457,6 @@ const vue = new Vue({ //计算数量 // if (this.paySwapList.length > 0) { // var item = this.paySwapList[0]; - // var deliveryPrice = this.roundStorageDeliveryPrice(item, item.PosiGrossPrice); // var notional = deliveryPrice * item.ContractSize; // item.PosiQuantity = notional == 0 ? 0 : _.round(this.trade.StockEqvNotional / notional, page.otcFormatConfig.StockEqvNotional.precision); // this.calcNotional(); @@ -535,7 +538,7 @@ const vue = new Vue({ } var national = payItem.PosiQuantity * payItem.ContractSize; // 守卫: 名义本金必须 round 到 2 位 → 对应历史 bug f873239a(缺 _.round); 外置到 swapCalc.calcStockEqvNotional - var deliveryPrice = this.roundStorageDeliveryPrice(payItem, payItem.PosiGrossPrice); + var deliveryPrice = this.roundStoragePrice(payItem, payItem.PosiGrossPrice, 'grossPrice'); var stockEqvNotional = SwapCalc.calcStockEqvNotional(deliveryPrice, national);//名义本金=期初价格*数量*乘数 this.trade.StockEqvNotional = otcformat.trading.stockEqvNotional(stockEqvNotional); payItem.PosiNotionalValue = this.trade.StockEqvNotional; @@ -682,9 +685,9 @@ const vue = new Vue({ errorcount++; return false; } - x.PosiGrossPrice = thisObj.roundStorageDeliveryPrice(x, x.PosiGrossPrice); - x.PosiNetNoFeePrice = thisObj.roundStorageBondNetPriceAndYtm(x.PosiNetNoFeePrice); - x.InitYtm = x.InitYtm == null ? null : thisObj.roundStorageBondNetPriceAndYtm(x.InitYtm); + x.PosiGrossPrice = thisObj.roundStoragePrice(x, x.PosiGrossPrice, 'grossPrice'); + x.PosiNetNoFeePrice = thisObj.roundStoragePrice(x, x.PosiNetNoFeePrice, 'netPrice'); + x.InitYtm = x.InitYtm == null ? null : thisObj.roundStoragePrice(x, x.InitYtm, 'yield'); x.PosiFeeType = thisObj.normalizePosiFeeType(x.PosiFeeType); thisObj.trade.swap_positions.push(x); }); @@ -820,8 +823,8 @@ const vue = new Vue({ // 非 EodPrice 分支 ×bondPriceMultiple=0.01)。故此处仅做精度格式化,**不可**再 bondCalcPriceToStorage(÷100), // 否则默认价 1.0 被除成 0.01,界面 percent:true 再 ×100 显示为 1("被自动除以100"bug)。 // 计算器(/Bond/CalcBond)返回的才是展示态,其 ÷100 落库逻辑在 calcBondForItem 内处理。 - item.PosiNetNoFeePrice = thisObj.roundStorageBondNetPriceAndYtm(resp.obj.netPrice); - item.PosiGrossPrice = thisObj.roundStorageDeliveryPrice(item, resp.obj.price); + item.PosiNetNoFeePrice = thisObj.roundStoragePrice(item, resp.obj.netPrice, 'netPrice'); + item.PosiGrossPrice = thisObj.roundStoragePrice(item, resp.obj.price, 'grossPrice'); thisObj.calcNotional(); }); }, @@ -1739,6 +1742,7 @@ const vue = new Vue({ components: { 'vue-datepicker': FastVue.vueDatePicker(), 'vue-number-input': FastVue.vueNumberInput(), + 'vue-swap-price-input': swapPricePrecision.createVueInputComponent(), 'vue-underlying-nonbond': vueUnderlyingNonBond(), 'vue-underlying-bond': vueUnderlyingBond(), 'vue-underlying-rate': vueUnderlyingRate() diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeView.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeView.js index 585a37cb..456633e8 100644 --- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeView.js +++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeView.js @@ -1,5 +1,14 @@ const inputFormatDouble2 = Object.freeze({ precision: 2, append: '' }); +function formatSwapPriceElements() { + $('.js-swap-price').each(function () { + this.textContent = swapPricePrecision.format( + this.dataset.value, + this.dataset.instrumentType, + this.dataset.field); + }); +} + function deletetrade(id) { //无效化 main.confirm(page.ConfirmInfo, function () { $.ajax({ @@ -541,6 +550,7 @@ function chk_onclick(obj) { } $(function () { + formatSwapPriceElements(); refreshEntryExit(); }); diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js index 554329ac..5bf4e295 100644 --- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js +++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js @@ -54,12 +54,18 @@ const vue = new Vue({ this.setUnwindDate(); }, methods: { - getPriceScale() { - return this.multiplier == 100 ? 0.01 : 1; + getDeliveryPriceInputFormat() { + return swapPricePrecision.getInputFormat( + this.floatPosition && this.floatPosition.UnderlyingInstrumentType, + 'grossPrice', + inputFormatSwapDeliveryPrice); }, getStorageDeliveryPrice() { - const precision = inputFormatSwapDeliveryPrice.precision + (this.multiplier === 100 ? 2 : 0); - return SwapCalc.roundHalfAwayFromZero(Number(this.floatPosition.TradingAmountAvg) * this.getPriceScale(), precision); + return swapPricePrecision.roundForSubmit( + swapPricePrecision.shiftDecimal(this.floatPosition.TradingAmountAvg, this.multiplier === 100 ? -2 : 0), + this.floatPosition && this.floatPosition.UnderlyingInstrumentType, + 'grossPrice', + this.multiplier === 100 ? 2 : 0); }, initDeal() { var positions = model.FlowEvents.filter((item) => { @@ -81,15 +87,15 @@ const vue = new Vue({ ? this.deal.PosiNotionalValue / this.deal.NotionalValue : 1; // 转换期末标的价格为百分比形式 if (this.floatPosition.TradingAmountAvg) { - this.floatPosition.TradingAmountAvg = this.floatPosition.TradingAmountAvg * this.multiplier; + this.floatPosition.TradingAmountAvg = swapPricePrecision.shiftDecimal(this.floatPosition.TradingAmountAvg, this.multiplier === 100 ? 2 : 0); } }, IsBond(instType) { return tradeHelper.IsBond(instType); }, priceFormat(price) { - price = price * this.multiplier; - var pricef = otcformat.trading.umprice(price); + price = swapPricePrecision.shiftDecimal(price, this.multiplier === 100 ? 2 : 0); + var pricef = swapPricePrecision.format(price, this.floatPosition && this.floatPosition.UnderlyingInstrumentType, 'grossPrice'); return pricef; }, dataFormat() { @@ -105,7 +111,10 @@ const vue = new Vue({ this.deal.SwapCloseAmount = otcformat.trading.StockEqvNotional(this.deal.SwapCloseAmount); //this.floatPosition.PosiNetPrice = otcformat.trading.tradeSinglePrice(this.floatPosition.PosiNetPrice); //this.floatPosition.PosiGrossPrice = otcformat.trading.tradeSinglePrice(this.floatPosition.PosiGrossPrice); - this.floatPosition.TradingAmountAvg = _.round(Number(this.floatPosition.TradingAmountAvg), 9); + this.floatPosition.TradingAmountAvg = swapPricePrecision.roundForSubmit( + this.floatPosition.TradingAmountAvg, + this.floatPosition.UnderlyingInstrumentType, + 'grossPrice'); this.floatPosition.TradingFee = otcformat.trading.StockEqvNotional(this.floatPosition.TradingFee); this.floatPosition.TradingFeePending = otcformat.trading.StockEqvNotional(this.floatPosition.TradingFeePending); this.floatPosition.DividendIn = parseFloat(this.floatPosition.DividendIn).toFixed(2); @@ -243,8 +252,10 @@ const vue = new Vue({ main.post("/underlying_manager/GetUnderlyingPriceByCode", { code: thisObj.floatPosition.UnderlyingCode, valuedate: thisObj.deal.ValueDate }) .done(function (res) { - res.obj = res.obj * thisObj.multiplier; - thisObj.floatPosition.TradingAmountAvg = _.round(Number(res.obj), 9); + thisObj.floatPosition.TradingAmountAvg = swapPricePrecision.roundForSubmit( + swapPricePrecision.shiftDecimal(res.obj, thisObj.multiplier === 100 ? 2 : 0), + thisObj.floatPosition.UnderlyingInstrumentType, + 'grossPrice'); thisObj.calcFloatClosePnl(); }); }, @@ -492,5 +503,6 @@ const vue = new Vue({ components: { 'vue-datepicker': FastVue.vueDatePicker(), 'vue-number-input': FastVue.vueNumberInput(), + 'vue-swap-price-input': swapPricePrecision.createVueInputComponent(), } }); diff --git a/YLErpWeb/wwwroot/Scripts/base/otcformat.js b/YLErpWeb/wwwroot/Scripts/base/otcformat.js index f4d61f46..ae83eb9f 100644 --- a/YLErpWeb/wwwroot/Scripts/base/otcformat.js +++ b/YLErpWeb/wwwroot/Scripts/base/otcformat.js @@ -217,4 +217,4 @@ var main = main || {}; global.otcformat = _format; -}(window)); \ No newline at end of file +}(window)); diff --git a/YLErpWeb/wwwroot/Scripts/fast/fastVue.base.js b/YLErpWeb/wwwroot/Scripts/fast/fastVue.base.js index f57f9c4a..a4501da0 100644 --- a/YLErpWeb/wwwroot/Scripts/fast/fastVue.base.js +++ b/YLErpWeb/wwwroot/Scripts/fast/fastVue.base.js @@ -590,4 +590,4 @@ }; }; -}(window.FastVue)); \ No newline at end of file +}(window.FastVue));