();
+ return renewTrade;
+ }
///
/// 详情
///
@@ -115,6 +217,12 @@ namespace YLErp.Web.Controllers
{
return ShowError("没有找到交易数据");
}
+ var marginTemplateConfig = SwapMarginTemplateConfigService.GetConfig();
+ if (string.IsNullOrWhiteSpace(tradeObj.MarginTemplateName)
+ || !marginTemplateConfig.options.Any(item => item.Value == tradeObj.MarginTemplateName))
+ {
+ tradeObj.MarginTemplateName = marginTemplateConfig.defaultValue;
+ }
TradeViewModel model;
model = new TradeViewModel(tradeObj)
@@ -1111,4 +1219,4 @@ namespace YLErp.Web.Controllers
}
-}
\ No newline at end of file
+}
diff --git a/YLErpWeb/Controllers/SwapTradeController.cs b/YLErpWeb/Controllers/SwapTradeController.cs
index 8746b33b..ad82b7c7 100644
--- a/YLErpWeb/Controllers/SwapTradeController.cs
+++ b/YLErpWeb/Controllers/SwapTradeController.cs
@@ -303,7 +303,7 @@ namespace YLErp.Web.Controllers
TradeDate = valuedateBLL.ValueDate,
TraderId = CurUser.UserId,
TraderName = CurUser.UserName,
- MarginTemplateName = "系统默认",
+ MarginTemplateName = null,
OpponentRole = "乙方",
trade_swap = new trade_swap()
{
@@ -1196,4 +1196,4 @@ namespace YLErp.Web.Controllers
return JsonSuccess("", 0);
}
}
-}
\ No newline at end of file
+}
diff --git a/YLErpWeb/Views/SwapTrade2/EventList.cshtml b/YLErpWeb/Views/SwapTrade2/EventList.cshtml
index cfbe53fc..c3905e38 100644
--- a/YLErpWeb/Views/SwapTrade2/EventList.cshtml
+++ b/YLErpWeb/Views/SwapTrade2/EventList.cshtml
@@ -12,7 +12,7 @@
-
+
}
@@ -28,4 +28,4 @@
\ No newline at end of file
+
diff --git a/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml b/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml
index 661828bd..2e707b9c 100644
--- a/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml
+++ b/YLErpWeb/Views/SwapTrade2/TradeEdit.cshtml
@@ -1,5 +1,6 @@
@using YLErp.Web.Models.JsModels;
@using YLErp.Commons;
+@using YLErp.Modules.SwapModule;
@model trade
@{
ViewBag.Title = "交易信息 | 编辑";
@@ -13,12 +14,14 @@
var jsClient = canChangeClient && Model.ClientId > 0 ? jsClients.FirstOrDefault(n => n.id == Model.ClientId) : null;
var jsAssetUnits = JsDataModel.GetAssetUnits(CurUser);
var tradeMarginTemplates = new tradeController().GetMarginTemplates();
+ var swapMarginTemplateItems = SwapMarginTemplateConfigService.GetConfig().options;
var tradeMarginTemplateItems = new tradeController().GetMarginTemplateItems();
var jsAssetUnit = Model.AssetId > 0 ? jsAssetUnits.FirstOrDefault(n => n.id == Model.AssetId) : null;
var assetunits = JsDataModel.GetAssetUnits(CurUser);
var jsTraders = canAddNewTrader ? JsDataModel.GetTraders(assetunits) : Enumerable.Empty
();
var jsTrader = canAddNewTrader && Model.TraderId > 0 ? jsTraders.FirstOrDefault(n => n.id == Model.TraderId) : null;
var currencys = CurrencyController.getList();
+ var categoryTagOptions = DictionaryBLL.GetList("利息端类别", false, "互换利率");
List places = new List();
List agencys = new List();
var tradingPlaceMap = YLErp.DBModels.Consts.ConsReport.TradingPlaceMapDisplay;
@@ -66,6 +69,7 @@
jsTrader,
jsTraders,
tradeMarginTemplates = tradeMarginTemplates,
+ swapMarginTemplateItems = swapMarginTemplateItems,
tradeMarginTemplateItems = tradeMarginTemplateItems,
needRemark = !isAdd && valuedateBLL.SystemDate.EditTradeNeedRemark,
parentTradeId = ViewBag.ParentTradeId,
@@ -266,6 +270,14 @@
+
+
+
+
@@ -351,6 +363,7 @@
计息方式 |
重置频率(天) |
利率准则 |
+
类别 |
结算规则 |
|
@@ -401,6 +414,14 @@
+
+
+ |
|
@@ -429,6 +450,7 @@
期初标的成交收益率% |
期初标的价格 |
数量 |
+
基础费率 |
交易费用后付 |
@@ -452,13 +474,13 @@
|
-
+ 源
|
-
+ 源
|
-
+ 源重算
|
@@ -466,6 +488,16 @@
|
{{item.underlying!=null?item.underlying.QuoteUnitString:''}}
|
+
+
+
+ %
+
+
+
+ ¥
+
+ |
我方{{item.PosiDirection==1?"支付":"收取"}}交易费用
diff --git a/YLErpWeb/Views/SwapTrade2/TradeView.cshtml b/YLErpWeb/Views/SwapTrade2/TradeView.cshtml
index 310b7897..4cdf1c8e 100644
--- a/YLErpWeb/Views/SwapTrade2/TradeView.cshtml
+++ b/YLErpWeb/Views/SwapTrade2/TradeView.cshtml
@@ -236,6 +236,10 @@
| 派息金额支付日 |
@(trade.trade_extend.ExtendObj.DividendPayDate == 0 ? "到期结算日" : "派息日+" + (trade.trade_extend.ExtendObj.DividendPayDate - 1)) |
+
+ | 保证金模板 |
+ @trade.MarginTemplateName |
+
@@ -300,6 +304,7 @@
计息方式 |
重置频率(天) |
利率准则 |
+ 类别 |
结算规则 |
@if (trade.swap_positions != null)
@@ -344,6 +349,7 @@
@item.interest_rest_days |
@((item.interest_rule != null) ? (SwapInterestRule)item.interest_rule : "") |
+ @(string.IsNullOrEmpty(item.category_tag) ? "互换利率" : item.category_tag) |
|
@@ -378,6 +384,7 @@
@initYtmTitle |
}
数量 |
+ 基础费率 |
交易费用后付 |
@foreach (var item in paySwapPositions)
@@ -403,6 +410,16 @@
@item.PosiQuantity.OtcFormat(OtcFormatFlag.StockEqvNotional)
|
+
+ @if (item.PosiFeeType == 0)
+ {
+ @(item.PosiTradingFeeUnit.ToString("0.0000") + "%")
+ }
+ else
+ {
+ @item.PosiTradingFeeUnit.ToString("0.00")
+ }
+ |
@item.PosiTradingFeePending.OtcFormat(OtcFormatFlag.StockEqvNotional)
|
@@ -504,6 +521,7 @@
计息方式 |
重置频率(天) |
利率准则 |
+ 类别 |
结算规则 |
@{
@@ -545,6 +563,7 @@
@(item.InterestType == 0 ? "单利" : "复利") |
@item.interest_rest_days |
@((item.interest_rule != null) ? (SwapInterestRule)item.interest_rule : "") |
+ @(string.IsNullOrEmpty(item.category_tag) ? "互换利率" : item.category_tag) |
|
}
diff --git a/YLErpWeb/Views/SwapTrade2/header.cshtml b/YLErpWeb/Views/SwapTrade2/header.cshtml
index 1e9a93e1..d6706aff 100644
--- a/YLErpWeb/Views/SwapTrade2/header.cshtml
+++ b/YLErpWeb/Views/SwapTrade2/header.cshtml
@@ -157,6 +157,7 @@
{
@MyControls.Btn("收益结算", string.Format("unWindLongShortSwap('{0}')", tradeModel.EncryptId))
}
+ @MyControls.Btn("续做", string.Format("renewTrade('{0}')", tradeModel.EncryptId))
}
diff --git a/YLErpWeb/Views/entryexit/entryexitList.cshtml b/YLErpWeb/Views/entryexit/entryexitList.cshtml
index aaafc651..ca20dcc2 100644
--- a/YLErpWeb/Views/entryexit/entryexitList.cshtml
+++ b/YLErpWeb/Views/entryexit/entryexitList.cshtml
@@ -355,7 +355,7 @@
return numeral(-1 * cellValue).format("0,0.00") === 'NaN' ? "0" : numeral(-1 * cellValue).format("0,0.00");
}
else {
- return numeral(cellValue).format("0,0.000") === 'NaN' ? "0" : numeral(cellValue).format("0,0.000");
+ return numeral(cellValue).format("0,0.00") === 'NaN' ? "0" : numeral(cellValue).format("0,0.00");
}
}
diff --git a/YLErpWeb/fe-tests/closePercentContract.test.js b/YLErpWeb/fe-tests/closePercentContract.test.js
new file mode 100644
index 00000000..2addeb24
--- /dev/null
+++ b/YLErpWeb/fe-tests/closePercentContract.test.js
@@ -0,0 +1,168 @@
+/**
+ * closePercentContract.test.js — 平仓比例口径A(占期初名义本金)前端接线守卫
+ * ============================================================================
+ * 产品需求(不可违背):ClosePercent 永远是"占期初名义本金(NotionalValue)的比例",即口径A。
+ *
+ * 本文件是"接线测试"(wiring test):读 unwindSwapTrade.js 源码文本,断言关键逻辑
+ * 仍然使用口径A的公式。如果有人把公式改成口径B(如 c9071a4e 曾犯的错误),
+ * 对应断言会立即变红。
+ *
+ * 与 swapCalc.test.js 的区别:
+ * swapCalc.test.js 测 SwapCalc 纯函数本身正确性(零件级)
+ * 本文件测 unwindSwapTrade.js 确实在调用这些函数/使用正确公式(装配级)
+ * ============================================================================
+ */
+const fs = require('fs');
+const path = require('path');
+
+const unwindSrc = fs.readFileSync(
+ path.join(__dirname, '..', 'wwwroot', 'Scripts', 'app', 'swaptrade', 'unwindSwapTrade.js'),
+ 'utf8'
+);
+
+describe('口径A产品契约:unwindSwapTrade.js 接线守卫', () => {
+
+ // ====================================================================
+ // 契约1:oriClosePercent 必须用 PosiNotionalValue / NotionalValue 计算
+ // 不能硬编码为 1(c9071a4e 的错误)
+ // ====================================================================
+ describe('oriClosePercent 必须为剩余/期初', () => {
+ test('源码中 oriClosePercent 必须包含 PosiNotionalValue / NotionalValue 公式', () => {
+ // 正确代码:this.oriClosePercent = ... PosiNotionalValue / ... NotionalValue
+ expect(unwindSrc).toMatch(/oriClosePercent.*PosiNotionalValue.*\/.*NotionalValue/s);
+ });
+
+ test('源码中 oriClosePercent 不能被硬编码为 1', () => {
+ // c9071a4e 的错误:this.deal.ClosePercent = 1 (直接覆盖)
+ // 检查 initDeal 中不存在 oriClosePercent = 1 的硬编码
+ const initDealSection = unwindSrc.match(/initDeal\(\)[\s\S]*?\},/);
+ expect(initDealSection).toBeTruthy();
+ // 不应出现 oriClosePercent = 1 或 ClosePercent = 1 的硬编码
+ //(CloseMethod===1 时设置 ClosePercent=1 是允许的,但 oriClosePercent 不应被设为1)
+ expect(initDealSection[0]).not.toMatch(/oriClosePercent\s*=\s*1\b/);
+ });
+ });
+
+ // ====================================================================
+ // 契约2:calcCloseQtyByPercent 必须调用 SwapCalc.calcCloseQtyByOriginalPercent
+ // 不能直接 PositionQty × ClosePercent(c9071a4e 的错误)
+ // ====================================================================
+ describe('calcCloseQtyByPercent 必须调用 SwapCalc', () => {
+ test('源码中 calcCloseQtyByPercent 必须调用 SwapCalc.calcCloseQtyByOriginalPercent', () => {
+ expect(unwindSrc).toContain('SwapCalc.calcCloseQtyByOriginalPercent');
+ });
+
+ test('changeCloseMethod 的部分平仓分支必须调用 calcCloseQtyByPercent', () => {
+ // 正确代码:this.deal.CloseQty = this.calcCloseQtyByPercent(this.deal.ClosePercent);
+ expect(unwindSrc).toMatch(/calcCloseQtyByPercent\s*\(this\.deal\.ClosePercent\)/);
+ });
+
+ test('changeClosePercent 必须调用 calcCloseQtyByPercent', () => {
+ expect(unwindSrc).toMatch(/this\.deal\.CloseQty\s*=\s*this\.calcCloseQtyByPercent/);
+ });
+
+ test('changeCloseNotionalValue 必须调用 calcCloseQtyByPercent', () => {
+ expect(unwindSrc).toMatch(/this\.deal\.CloseQty\s*=\s*this\.calcCloseQtyByPercent/);
+ });
+ });
+
+ // ====================================================================
+ // 契约3:CloseNotionalValue 必须用 NotionalValue 做乘数/分母(口径A)
+ // 不能用 PosiNotionalValue(c9071a4e 的错误)
+ // ====================================================================
+ describe('CloseNotionalValue 必须基于 NotionalValue(口径A)', () => {
+ test('changeCloseQty: CloseNotionalValue = ClosePercent × NotionalValue', () => {
+ // 正确:ClosePercent × parseFloat(this.deal.NotionalValue)
+ // 错误:ClosePercent × parseFloat(this.deal.PosiNotionalValue)
+ const changeCloseQtySection = unwindSrc.match(/changeCloseQty\(\)[\s\S]*?\n\s*\},/);
+ expect(changeCloseQtySection).toBeTruthy();
+ expect(changeCloseQtySection[0]).toMatch(/CloseNotionalValue.*NotionalValue/);
+ expect(changeCloseQtySection[0]).not.toMatch(/CloseNotionalValue.*PosiNotionalValue/);
+ });
+
+ test('changeClosePercent: CloseNotionalValue = ClosePercent × NotionalValue', () => {
+ const changeClosePercentSection = unwindSrc.match(/changeClosePercent\(\)[\s\S]*?\n\s*\},/);
+ expect(changeClosePercentSection).toBeTruthy();
+ expect(changeClosePercentSection[0]).toMatch(/CloseNotionalValue.*NotionalValue/);
+ expect(changeClosePercentSection[0]).not.toMatch(/CloseNotionalValue.*PosiNotionalValue/);
+ });
+ });
+
+ // ====================================================================
+ // 契约4:changeCloseQty 的 ClosePercent 必须乘以 oriClosePercent(口径A→B→A 转换)
+ // 不能直接 CloseQty / PositionQty(c9071a4e 的错误)
+ // ====================================================================
+ describe('changeCloseQty 的 ClosePercent 必须乘以 oriClosePercent', () => {
+ test('ClosePercent = (CloseQty/PositionQty) × oriClosePercent', () => {
+ // 正确:× ori(把占剩余比例转回占期初口径)
+ // 错误:不乘 ori(直接用占剩余比例作为 ClosePercent)
+ const changeCloseQtySection = unwindSrc.match(/changeCloseQty\(\)[\s\S]*?\n\s*\},/);
+ expect(changeCloseQtySection).toBeTruthy();
+ expect(changeCloseQtySection[0]).toMatch(/oriClosePercent/);
+ expect(changeCloseQtySection[0]).toMatch(/\*\s*ori/);
+ });
+
+ test('changeCloseNotionalValue 的 ClosePercent 必须除以 NotionalValue', () => {
+ // 正确:ClosePercent = CloseNotionalValue / NotionalValue
+ // 错误:ClosePercent = CloseNotionalValue / PosiNotionalValue
+ const changeCloseNotionalSection = unwindSrc.match(/changeCloseNotionalValue\(\)[\s\S]*?\n\s*\},/);
+ expect(changeCloseNotionalSection).toBeTruthy();
+ expect(changeCloseNotionalSection[0]).toMatch(/ClosePercent.*NotionalValue/);
+ expect(changeCloseNotionalSection[0]).not.toMatch(/ClosePercent.*PosiNotionalValue/);
+ });
+ });
+
+ // ====================================================================
+ // 契约5:getInterestList 必须传 notionalValue 和 posiNotionalValue 给后端
+ // 后端 GetUnwindInterestList 需要这两个值做 A→B 转换
+ // 如果删掉(c9071a4e 的错误),后端不转换,利息用错口径计算
+ // ====================================================================
+ describe('getInterestList 必须传 notionalValue/posiNotionalValue', () => {
+ test('postData 必须包含 notionalValue', () => {
+ expect(unwindSrc).toMatch(/notionalValue:\s*thisObj\.deal\.NotionalValue/);
+ });
+
+ test('postData 必须包含 posiNotionalValue', () => {
+ expect(unwindSrc).toMatch(/posiNotionalValue:\s*thisObj\.deal\.PosiNotionalValue/);
+ });
+
+ test('getInterestList 的 postData 不能只有 closePercent 而缺少 notionalValue', () => {
+ // 精确匹配 getInterestList 方法定义(以 getInterestList() { 开头,到 main.post 结束)
+ // 匹配模式:方法名+参数列表+花括号开始,一直到包含 main.post 的 postData 定义
+ const methodMatch = unwindSrc.match(
+ /getInterestList\(\)\s*\{[\s\S]*?var\s+postData\s*=\s*\{[^}]*\}/
+ );
+ expect(methodMatch).toBeTruthy();
+ const postData = methodMatch[0];
+ expect(postData).toContain('notionalValue');
+ expect(postData).toContain('posiNotionalValue');
+ });
+ });
+
+ // ====================================================================
+ // 契约6:CloseMethod 赋值对象必须是 deal(不是 floatPosition)
+ // c9071a4e 在 changeClosePercent 中误赋值到 floatPosition.CloseMethod
+ // ====================================================================
+ describe('CloseMethod 必须赋值给 deal', () => {
+ test('changeClosePercent 的 CloseMethod 必须赋值给 this.deal', () => {
+ const changeClosePercentSection = unwindSrc.match(/changeClosePercent\(\)[\s\S]*?\n\s*\},/);
+ expect(changeClosePercentSection).toBeTruthy();
+ expect(changeClosePercentSection[0]).toMatch(/this\.deal\.CloseMethod\s*=/);
+ expect(changeClosePercentSection[0]).not.toMatch(/this\.floatPosition\.CloseMethod\s*=/);
+ });
+
+ test('changeCloseNotionalValue 的 CloseMethod 必须赋值给 this.deal', () => {
+ const changeCloseNotionalSection = unwindSrc.match(/changeCloseNotionalValue\(\)[\s\S]*?\n\s*\},/);
+ expect(changeCloseNotionalSection).toBeTruthy();
+ expect(changeCloseNotionalSection[0]).toMatch(/this\.deal\.CloseMethod\s*=/);
+ expect(changeCloseNotionalSection[0]).not.toMatch(/this\.floatPosition\.CloseMethod\s*=/);
+ });
+
+ test('changeCloseNotionalValue 必须设置 CloseMethod(不能删除)', () => {
+ // c9071a4e 完全删除了 changeCloseNotionalValue 中的 CloseMethod 判断
+ const changeCloseNotionalSection = unwindSrc.match(/changeCloseNotionalValue\(\)[\s\S]*?\n\s*\},/);
+ expect(changeCloseNotionalSection).toBeTruthy();
+ expect(changeCloseNotionalSection[0]).toMatch(/CloseMethod/);
+ });
+ });
+});
diff --git a/YLErpWeb/fe-tests/unwindSwapTrade.test.js b/YLErpWeb/fe-tests/unwindSwapTrade.test.js
new file mode 100644
index 00000000..b977b41e
--- /dev/null
+++ b/YLErpWeb/fe-tests/unwindSwapTrade.test.js
@@ -0,0 +1,92 @@
+const fs = require('fs');
+const path = require('path');
+const vm = require('vm');
+
+function createNumberFormat(precision) {
+ const formatter = (value) => Number(Number(value || 0).toFixed(precision));
+ formatter.precision = precision;
+ return formatter;
+}
+
+function loadUnwindHelpers() {
+ const filePath = path.join(__dirname, '../wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js');
+ const code = fs.readFileSync(filePath, 'utf8') + '\nmodule.exports = { swapPosiFeeCalc, consPosiFeeType };';
+
+ const stockEqvNotional = createNumberFormat(2);
+ const sandbox = {
+ module: { exports: {} },
+ exports: {},
+ console,
+ require,
+ window: { otcformat: { options: {} } },
+ otcformat: {
+ options: {},
+ trading: {
+ premiumRateP: { precision: 4 },
+ tradePrice: { precision: 4 },
+ notional: { precision: 6 },
+ StockEqvNotional: stockEqvNotional,
+ marginRateP: { precision: 4 },
+ umpriceP: { precision: 4 }
+ },
+ fixed6: createNumberFormat(6)
+ },
+ model: {
+ ValueDate: '2026-07-27',
+ FlowEvents: [],
+ StructureType: '',
+ TradeStartDate: ''
+ },
+ isUseApproval: false,
+ Vue: function (options) { return options; },
+ FastVue: {
+ vueDatePicker() { return {}; },
+ vueNumberInput() { return {}; }
+ },
+ tradeHelper: { IsBond() { return false; } },
+ main: {
+ post() {
+ return {
+ done() { return this; }
+ };
+ },
+ message() { }
+ },
+ SwapCalc: {
+ roundHalfAwayFromZero(value) { return value; },
+ calcCloseQtyByOriginalPercent() { return 0; }
+ },
+ _: {
+ round(value, precision) {
+ return Number(Number(value || 0).toFixed(precision || 0));
+ }
+ }
+ };
+
+ sandbox.window.otcformat = sandbox.otcformat;
+ vm.runInNewContext(code, sandbox, { filename: filePath });
+ return sandbox.module.exports;
+}
+
+function expectClose(actual, expected, tolerance) {
+ expect(Math.abs(actual - expected)).toBeLessThanOrEqual(tolerance || 1e-6);
+}
+
+describe('unwindSwapTrade 基础费率计算', () => {
+ const { swapPosiFeeCalc, consPosiFeeType } = loadUnwindHelpers();
+
+ test('百分比模式按平仓名义本金计算并保留两位', () => {
+ const result = swapPosiFeeCalc.calcTradingFee(consPosiFeeType.Percent, 0.1234, 1000000, 5000);
+ expectClose(result, 1234.00);
+ });
+
+ test('单位数量模式按平仓数量计算并保留两位', () => {
+ const result = swapPosiFeeCalc.calcTradingFee(consPosiFeeType.Unit, 1.235, 1000000, 10);
+ expectClose(result, 12.35);
+ });
+
+ test('未知模式默认按百分比模式处理', () => {
+ const result = swapPosiFeeCalc.calcTradingFee(99, 0.1, 200000, 10);
+ expectClose(result, 200.00);
+ });
+});
diff --git a/YLErpWeb/wwwroot/Scripts/app/risk/SubstandardTrade.js b/YLErpWeb/wwwroot/Scripts/app/risk/SubstandardTrade.js
index c351e43c..02f7f83d 100644
--- a/YLErpWeb/wwwroot/Scripts/app/risk/SubstandardTrade.js
+++ b/YLErpWeb/wwwroot/Scripts/app/risk/SubstandardTrade.js
@@ -246,7 +246,7 @@ function getColModelDefault() {
width: 85,
align: 'center',
sortable: false,
- formatter: otcformat.trading.umprice
+ formatter: EventTwoDecimalFormat
},
{
name: 'TradeType2',
@@ -374,7 +374,7 @@ function getColModelDefault() {
width: 65,
align: 'center',
sortable: false,
- formatter: otcformat.trading.umprice
+ formatter: EventTwoDecimalFormat
});
return tradeHelper.getAmountToNotional(colModelGrid);
@@ -388,6 +388,14 @@ function ShowStructFormater(cellValue, options, rowObject) {
return cellValue || '';
}
+function EventTwoDecimalFormat(cellValue) {
+ if (cellValue === null || cellValue === undefined || cellValue === '') {
+ return '';
+ }
+ var price = Number(cellValue);
+ return isFinite(price) ? price.toFixed(2) : cellValue;
+}
+
function StrikeFormatter(cellValue, options, rowObject) {
if (rowObject.TradeType === "自定义交易") {
return "";
@@ -396,20 +404,12 @@ function StrikeFormatter(cellValue, options, rowObject) {
return "--";
}
- if (rowObject.Strike) {
- if (rowObject.IsMoneynessOption === "是") {
- return otcformat.trading.premiumRateP(rowObject.Strike);
- } else {
- return otcformat.trading.umprice(rowObject.Strike);
- }
- } else {
- if (rowObject.Strike === 0) {
- return otcformat.trading.umprice(0);
- }
- else {
- return "";
- }
- }
+ if (cellValue === null || cellValue === undefined || cellValue === '' || cellValue === 'NaN') return '';
+ var strike = Number(cellValue);
+ if (!isFinite(strike)) return '';
+ return rowObject.IsMoneynessOption === "是"
+ ? (strike * 100).toFixed(2) + '%'
+ : EventTwoDecimalFormat(strike);
}
function CommissionFormatter(cellValue, options, rowObject) {
@@ -651,4 +651,4 @@ function showcolumnChooser() {
function getColModel() {
return getColModelDefault();
-}
\ No newline at end of file
+}
diff --git a/YLErpWeb/wwwroot/Scripts/app/risk/quotaMonitor.js b/YLErpWeb/wwwroot/Scripts/app/risk/quotaMonitor.js
index 4c159554..4d6328a9 100644
--- a/YLErpWeb/wwwroot/Scripts/app/risk/quotaMonitor.js
+++ b/YLErpWeb/wwwroot/Scripts/app/risk/quotaMonitor.js
@@ -361,6 +361,16 @@ function formatQuotaAbs(obj, fieldName) {
return html;
}
+function formatFixedTwoDecimals(cellvalue) {
+ if (cellvalue === null || cellvalue === undefined || cellvalue === '' || cellvalue === 'NaN') {
+ return '';
+ }
+ var numberValue = Number(cellvalue);
+ return isFinite(numberValue)
+ ? numberValue.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
+ : '';
+}
+
function formatQuotaRef(obj, fieldName) {
var html = "";
var upperValue = obj["Quota_" + fieldName + "_Upper"];
@@ -544,8 +554,21 @@ function quotaMonitorUploadForm(url) {
}
function Output() {
- var fileName = new moment().format("YYYYMMDD") + $('#myTab .active a').text();
- main.toExcel("listGrid", fileName, "xls", null, [" ", "操作"]);
+ var summaryType = $('#myTab .active a').text();
+ var fileName = new moment().format("YYYYMMDD") + summaryType;
+ var formatters = null;
+ if (summaryType === '整体业务汇总' || summaryType === '标的汇总' || summaryType === '客户汇总') {
+ formatters = [{
+ colName: '名义本金',
+ formatter: function (cellvalue, options, rowObject) {
+ if (summaryType === '整体业务汇总' && rowObject.BusinessType === '场内业务') {
+ return '';
+ }
+ return formatFixedTwoDecimals(cellvalue);
+ }
+ }];
+ }
+ main.toExcel("listGrid", fileName, "xls", null, [" ", "操作"], formatters);
}
function confirmAllSelect() {
@@ -1090,7 +1113,7 @@ var colModel_undelrying = [
align: 'right',
sortable: false,
formatter: function (cellvalue, options, rowObject) {
- return !cellvalue || cellvalue == "NaN" ? "" : cellvalue.toLocaleString();
+ return formatFixedTwoDecimals(cellvalue);
},
cellattr: function (cellvalue, options, rowObject) {
var style = checkQuota(rowObject, 'StockEqvNotional');
@@ -1507,7 +1530,7 @@ var colModel_client = [
align: 'right',
sortable: false,
formatter: function (cellvalue, options, rowObject) {
- return !cellvalue || cellvalue == "NaN" ? "" : cellvalue.toLocaleString();
+ return formatFixedTwoDecimals(cellvalue);
},
cellattr: function (cellvalue, options, rowObject) {
var style = "style='" + checkQuota(rowObject, 'StockEqvNotional') + "'";
@@ -1658,10 +1681,7 @@ var colModel_global = [
if (rowObject.BusinessType == "场内业务") {
return '';
}
- if (page.IsGuoXin && cellvalue != null) {
- return cellvalue == "NaN" ? "" : cellvalue.toLocaleString();
- }
- return !cellvalue || cellvalue == "NaN" ? "" : cellvalue.toLocaleString();
+ return formatFixedTwoDecimals(cellvalue);
},
cellattr: function (cellvalue, options, rowObject) {
var style = checkQuota(rowObject, 'StockEqvNotional');
@@ -2132,4 +2152,4 @@ var colModel_Log = [
align: 'center',
sortable: false
}
-];
\ No newline at end of file
+];
diff --git a/YLErpWeb/wwwroot/Scripts/app/scenario/tradeScenario.js b/YLErpWeb/wwwroot/Scripts/app/scenario/tradeScenario.js
index ccdd4c86..c30a7782 100644
--- a/YLErpWeb/wwwroot/Scripts/app/scenario/tradeScenario.js
+++ b/YLErpWeb/wwwroot/Scripts/app/scenario/tradeScenario.js
@@ -13,20 +13,12 @@ const colModelGrid = (function () {
}
function StrikeFormatter(cellValue, options, rowObject) {
- if (rowObject.Strike) {
- if (rowObject.IsMoneynessOption === "是") {
- return otcformat.trading.premiumRateP(rowObject.Strike);
- } else {
- return otcformat.trading.umprice(rowObject.Strike);
- }
- } else {
- if (rowObject.Strike === 0) {
- return 0;
- }
- else {
- return "";
- }
- }
+ if (cellValue === null || cellValue === undefined || cellValue === '' || cellValue === 'NaN') return '';
+ var strike = Number(cellValue);
+ if (!isFinite(strike)) return '';
+ return rowObject.IsMoneynessOption === "是"
+ ? (strike * 100).toFixed(2) + '%'
+ : strike.toFixed(2);
}
var col = [
@@ -273,4 +265,4 @@ function setTestValue(list) {
testStstus = false;
main.alert("计算完成");
}
-}
\ No newline at end of file
+}
diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/EodPositionRisks.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/EodPositionRisks.js
index 99067de4..ca12a59f 100644
--- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/EodPositionRisks.js
+++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/EodPositionRisks.js
@@ -168,7 +168,9 @@ function colModelGridEodPosition() {
index: 'eodPosition.PosiNetPrice',
width: 90,
align: 'center',
- formatter: PriceFormat
+ formatter: PriceFormat,
+ exportFormatter: ExportPriceNineDecimalFormat,
+ exportNumberFormat: '0.000000000'
}, {
name: 'eodPosition.PosiGrossPrice',
label: '期初价格-不含费',
@@ -176,6 +178,8 @@ function colModelGridEodPosition() {
width: 90,
align: 'center',
formatter: PriceFormat,
+ exportFormatter: ExportPriceNineDecimalFormat,
+ exportNumberFormat: '0.000000000'
}, {
name: 'eodPosition.PosiQuantity',
label: '名义数量',
@@ -743,7 +747,7 @@ function exportVisibleColumns() {
var tabName = page.tabIndex == 2 ? '框架合约' : '日终持仓';
var fileName = '日终持仓风险_互换_' + tabName + (dateStr ? '_' + dateStr : '');
if (page.tabIndex != 2) {
- main.exportVisibleColumnsToExcel(jgrid, fileName, null);
+ exportEodPositionRows(jgrid, fileName);
return;
}
@@ -770,6 +774,26 @@ function exportVisibleColumns() {
});
}
+function exportEodPositionRows(jgrid, fileName) {
+ var exportPostData = $.extend({}, GetPostData(), {
+ page: jgrid.jqGrid('getGridParam', 'page'),
+ rows: jgrid.jqGrid('getGridParam', 'rowNum'),
+ sidx: jgrid.jqGrid('getGridParam', 'sortname'),
+ sord: jgrid.jqGrid('getGridParam', 'sortorder')
+ });
+ $.ajax({
+ url: queryurl,
+ type: 'POST',
+ dataType: 'json',
+ traditional: true,
+ data: exportPostData
+ }).done(function (result) {
+ main.exportVisibleColumnsToExcel(jgrid, fileName, null, result && result.rows ? result.rows : []);
+ }).fail(function () {
+ main.message && main.message('导出失败,无法获取日终持仓数据');
+ });
+}
+
function getVisibleEodSwapBusinessColumnNames(jgrid) {
var colModel = jgrid.jqGrid('getGridParam', 'colModel') || [];
return colModel.filter(function (col) {
@@ -803,6 +827,14 @@ function PriceFormat(cellValue, options, rowObject) {
return otcformat.trading.umprice(cellValue);
}
+function ExportPriceNineDecimalFormat(cellValue) {
+ if (cellValue === null || cellValue === undefined || cellValue === '') {
+ return '';
+ }
+ var price = Number(cellValue);
+ return isFinite(price) ? price.toFixed(9) : cellValue;
+}
+
function RealizedPnlFormat(cellValue, options, rowObject) {
return otcformat.trading.tradePrice(cellValue);
}
diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/eventlist.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/eventlist.js
index e9f8f6b7..c8c32767 100644
--- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/eventlist.js
+++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/eventlist.js
@@ -176,36 +176,31 @@ var getColModelGrid = function () {
label: '成交全价',
width: 90,
align: 'center',
- formatter: otcformat.trading.umprice
+ formatter: EventTwoDecimalFormat
}, {
name: 'TradingAmountFeeAvg',
label: '成交全价(含费)',
width: 90,
align: 'center',
- formatter: otcformat.trading.umprice
+ formatter: EventTwoDecimalFormat
}, {
name: 'TradingAmountNetAvg',
label: '成交净价',
width: 90,
align: 'center',
- formatter: otcformat.trading.umprice
+ formatter: EventTwoDecimalFormat
}, {
name: 'InitYtm',
label: '成交收益率',
width: 90,
align: 'center',
- formatter: function (cellValue, options, rowObject) {
- if (cellValue == null) {
- return "";
- }
- return otcformat.trading.premiumRateP(cellValue);
- }
+ formatter: EventNineDecimalFormat
}, {
name: 'TradingAmountNetFeeAvg',
label: '成交净价(含费)',
width: 90,
align: 'center',
- formatter: otcformat.trading.umprice
+ formatter: EventTwoDecimalFormat
}, {
name: 'Quantity',
label: '成交数量/张数',
@@ -217,7 +212,7 @@ var getColModelGrid = function () {
label: '成交金额(元)',
width: 90,
align: 'center',
- formatter: otcformat.trading.umprice
+ formatter: EventTwoDecimalFormat
}, {
name: 'ContractSize',
label: '乘数',
@@ -230,31 +225,31 @@ var getColModelGrid = function () {
label: '交易费用佣金',
width: 90,
align: 'center',
- formatter: otcformat.trading.umprice
+ formatter: EventTwoDecimalFormat
}, {
name: 'TradingFeePending',
label: '待结算交易费用佣金',
width: 90,
align: 'center',
- formatter: otcformat.trading.umprice
+ formatter: EventTwoDecimalFormat
}, {
name: 'DividendPending',
label: '待结算分红收益',
width: 90,
align: 'center',
- formatter: otcformat.trading.umprice
+ formatter: EventTwoDecimalFormat
}, {
name: 'MarkClosePnl',
label: '浮动端平仓盈亏·浮动',
width: 160,
align: 'center',
- formatter: otcformat.trading.umprice
+ formatter: EventTwoDecimalFormat
}, {
name: 'DividendIn',
label: '浮动端平仓盈亏·分红',
width: 160,
align: 'center',
- formatter: otcformat.trading.umprice
+ formatter: EventTwoDecimalFormat
}
];
return col;
@@ -262,6 +257,23 @@ var getColModelGrid = function () {
var colModelGrid = getColModelGrid();
+function EventTwoDecimalFormat(cellValue) {
+ if (cellValue === null || cellValue === undefined || cellValue === '') {
+ return '';
+ }
+ var price = Number(cellValue);
+ return isFinite(price) ? price.toFixed(2) : cellValue;
+}
+
+function EventNineDecimalFormat(cellValue) {
+ if (cellValue === null || cellValue === undefined || cellValue === '') {
+ return '';
+ }
+ var price = Number(cellValue);
+ return isFinite(price) ? price.toFixed(9) : cellValue;
+}
+
+
function gridComplete() {
$('.ui-jqgrid-bdiv', '#gbox_listGrid').floatingScroll();
}
diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js
index 91b2e26a..c3eecede 100644
--- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js
+++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeEdit.js
@@ -12,11 +12,39 @@ const inputFormatTradeAmount = Object.freeze({ precision: otcformat.trading.noti
const inputFormatSwapRate = Object.freeze({ precision: otcformat.trading.premiumRateP.precision, negative: true, append: '%' });
const inputFormatTradePrice = Object.freeze({ precision: otcformat.trading.tradePrice.precision, negative: true, append: '' });
const inputFormatTradeSinglePrice = Object.freeze({ precision: otcformat.trading.tradeSinglePrice.precision, negative: true, append: '', percent: false });
+const inputFormatPosiFeePercent = Object.freeze({ precision: 4, negative: true, append: '%' });
+const inputFormatPosiFeeUnit = Object.freeze({ precision: 2, 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) {
+ return Number(feeType) === consPosiFeeType.Unit ? consPosiFeeType.Unit : consPosiFeeType.Percent;
+ },
+ calcPending(feeType, feeUnit, stockEqvNotional, quantity) {
+ const normalizedFeeType = this.normalizeFeeType(feeType);
+ const normalizedFeeUnit = Number(feeUnit) || 0;
+ const normalizedNotional = Number(stockEqvNotional) || 0;
+ const normalizedQuantity = Number(quantity) || 0;
+ const tradingFeePending = normalizedFeeType === consPosiFeeType.Percent
+ ? normalizedFeeUnit / 100 * normalizedNotional
+ : normalizedFeeUnit * normalizedQuantity;
+ return otcformat.trading.tradeSinglePrice(tradingFeePending);
+ },
+ calcFeeUnit(feeType, tradingFeePending, stockEqvNotional, quantity) {
+ const normalizedFeeType = this.normalizeFeeType(feeType);
+ const normalizedTradingFeePending = Number(tradingFeePending) || 0;
+ const normalizedNotional = Number(stockEqvNotional) || 0;
+ const normalizedQuantity = Number(quantity) || 0;
+ if (normalizedFeeType === consPosiFeeType.Percent) {
+ return normalizedNotional === 0 ? 0 : _.round(normalizedTradingFeePending / normalizedNotional * 100, inputFormatPosiFeePercent.precision);
+ }
+ return normalizedQuantity === 0 ? 0 : _.round(normalizedTradingFeePending / normalizedQuantity, inputFormatPosiFeeUnit.precision);
+ }
+});
const consUnderlyingFlagBase = (function () {
let unSelFlag = tradeHelper.UnderlyingSelectFlag;
@@ -153,6 +181,7 @@ const vue = new Vue({
currencys: page.currencys,
getNotionalSingleFee: 0,
isSingleFee: page.Trade.trade_extend.ExtendObj.OpenFeeType == 0,
+ posiFeeModePercent: true,
observation: {//互换观察日
ObservationInterval: "",
IntervalList: [],
@@ -236,6 +265,41 @@ const vue = new Vue({
const isBond = tradeHelper.IsBond(item && item.UnderlyingInstrumentType);
return `${index}-${field}-${isBond ? 'bond' : 'other'}`;
},
+ getCurrentPosiFeeType() {
+ return this.posiFeeModePercent ? consPosiFeeType.Percent : consPosiFeeType.Unit;
+ },
+ normalizePosiFeeType(feeType) {
+ return swapPosiFeeCalc.normalizeFeeType(feeType);
+ },
+ syncPosiFeeModeByItem(item) {
+ this.posiFeeModePercent = this.normalizePosiFeeType(item && item.PosiFeeType) !== consPosiFeeType.Unit;
+ },
+ syncPayItemFeeType(item) {
+ item.PosiFeeType = this.getCurrentPosiFeeType();
+ },
+ refreshTradingFeePendingByUnit(item) {
+ this.syncPayItemFeeType(item);
+ item.PosiTradingFeePending = swapPosiFeeCalc.calcPending(
+ item.PosiFeeType,
+ item.PosiTradingFeeUnit,
+ this.trade.StockEqvNotional,
+ item.PosiQuantity
+ );
+ },
+ refreshTradingFeeUnitByPending(item) {
+ this.syncPayItemFeeType(item);
+ item.PosiTradingFeeUnit = swapPosiFeeCalc.calcFeeUnit(
+ item.PosiFeeType,
+ item.PosiTradingFeePending,
+ this.trade.StockEqvNotional,
+ item.PosiQuantity
+ );
+ },
+ refreshPayTradingFeesByUnit() {
+ this.paySwapList.forEach(item => {
+ this.refreshTradingFeePendingByUnit(item);
+ });
+ },
changeStructureType() {
this.trade.StockEqvNotional = 0;
let direction = this.trade.trade_extend.ExtendObj.Direction;
@@ -382,17 +446,18 @@ const vue = new Vue({
changeContractSize(item) {
this.calcNotional();
},
- //变更名义本金
+ //变更名义本金(仅格式化,不反算数量)
changeStockEqvNotional() {
this.trade.StockEqvNotional = otcformat.trading.StockEqvNotional(this.trade.StockEqvNotional);
+ this.refreshPayTradingFeesByUnit();
//计算数量
- 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();
- }
+ // 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();
+ // }
},
//变更初始预付金 为¥
@@ -474,6 +539,7 @@ const vue = new Vue({
var stockEqvNotional = SwapCalc.calcStockEqvNotional(deliveryPrice, national);//名义本金=期初价格*数量*乘数
this.trade.StockEqvNotional = otcformat.trading.stockEqvNotional(stockEqvNotional);
payItem.PosiNotionalValue = this.trade.StockEqvNotional;
+ this.refreshPayTradingFeesByUnit();
}
},
//变更到期日
@@ -509,26 +575,27 @@ const vue = new Vue({
},
//变更单位交易费用
changeTradingFeeUnit(item) {
- //计算交易费用
- //if (this.trade.trade_extend.ExtendObj.OpenFeeType == 0) {//按手数收费
- // item.PosiTradingFee = item.ContractSize == 0 ? 0 : otcformat.trading.tradeSinglePrice(item.PosiQuantity * item.PosiTradingFeeUnit / item.ContractSize);
- //} else {
- // item.PosiTradingFee = otcformat.trading.tradeSinglePrice(item.PosiQuantity * item.PosiTradingFeeUnit);
- //}
-
+ this.refreshTradingFeePendingByUnit(item);
},
//变更交易费用
changeTradingFee(item) {
- //计算单位交易费用
- //if (item.PosiQuantity == 0) {
- // item.PosiTradingFeeUnit = 0;
- // return
- //}
- //if (this.trade.trade_extend.ExtendObj.OpenFeeType == 0) {//按手数收费
- // item.PosiTradingFeeUnit = item.ContractSize == 0 ? 0 : otcformat.trading.tradeSinglePrice(item.PosiTradingFee * item.ContractSize / item.PosiQuantity);
- //} else {
- // item.PosiTradingFeeUnit = otcformat.trading.tradeSinglePrice(item.PosiTradingFee / item.PosiQuantity);
- //}
+ this.refreshTradingFeeUnitByPending(item);
+ },
+ showPayAbsPrice() {
+ this.posiFeeModePercent = false;
+ this.paySwapList.forEach(item => {
+ item.PosiFeeType = consPosiFeeType.Unit;
+ item.PosiTradingFeeUnit = 0;
+ item.PosiTradingFeePending = 0;
+ });
+ },
+ showPayPercentPrice() {
+ this.posiFeeModePercent = true;
+ this.paySwapList.forEach(item => {
+ item.PosiFeeType = consPosiFeeType.Percent;
+ item.PosiTradingFeeUnit = 0;
+ item.PosiTradingFeePending = 0;
+ });
},
savetrade() {
if (!this.checkSubmitData()) {
@@ -618,6 +685,7 @@ const vue = new Vue({
x.PosiGrossPrice = thisObj.roundStorageDeliveryPrice(x, x.PosiGrossPrice);
x.PosiNetNoFeePrice = thisObj.roundStorageBondNetPriceAndYtm(x.PosiNetNoFeePrice);
x.InitYtm = x.InitYtm == null ? null : thisObj.roundStorageBondNetPriceAndYtm(x.InitYtm);
+ x.PosiFeeType = thisObj.normalizePosiFeeType(x.PosiFeeType);
thisObj.trade.swap_positions.push(x);
});
} else {
@@ -1414,6 +1482,7 @@ const vue = new Vue({
thisObj.getSwapList = thisObj.trade.swap_positions.filter(x => { if ((x.UnderlyingCode == null || x.UnderlyingCode.length == 0) && x.IsInitial && (x.InterestMode == 1 || x.InterestMode == 2 || x.InterestMode == 7 || x.InterestMode == 8 || x.InterestMode == 9)) return x; });
thisObj.getSwapList.forEach((val, num, arr) => {
arr[num].index = num;
+ arr[num].category_tag = arr[num].category_tag || '互换利率';
// 解析 InterestSwapInterval 为 SwapIntervalList
if (arr[num].InterestSwapInterval && !arr[num].SwapIntervalList) {
try {
@@ -1438,6 +1507,7 @@ const vue = new Vue({
thisObj.paySwapList = thisObj.trade.swap_positions.filter(x => { if (x.UnderlyingCode != null && x.UnderlyingCode.length != 0 && x.IsInitial) return x; });
thisObj.paySwapList.forEach((val, num, arr) => {
arr[num].index = num;
+ arr[num].PosiFeeType = thisObj.normalizePosiFeeType(arr[num].PosiFeeType);
this.StockEqvNotional = val.ContractSize * val.PosiQuantity * val.PosiGrossPrice;
// D2 修复:重开(审批重开/刷新)已保存的债券成交单时,三字段互算的手动标志随页面重置而丢失;
// 若不锁,用户一旦编辑任一价格字段就会以它为源重新反算、覆盖当初保存的其他两格。
@@ -1449,6 +1519,9 @@ const vue = new Vue({
thisObj.$set(arr[num], 'bondManual', { CP: true, DP: true, YD: true });
}
});
+ if (thisObj.paySwapList.length > 0) {
+ thisObj.syncPosiFeeModeByItem(thisObj.paySwapList[0]);
+ }
}
if (thisObj.paySwapList.length == 0) {
@@ -1502,7 +1575,8 @@ const vue = new Vue({
HappenDate: null,//发生日期,
Currency: 'CNY',//币种
interest_rest_days: 7,//重置频率
- interest_rule: null//利率准则
+ interest_rule: null,//利率准则
+ category_tag: '互换利率'//类别
}
thisObj.getSwapList.push(getSwap);
},
@@ -1574,6 +1648,7 @@ const vue = new Vue({
PosiTradingFee: 0,//交易费用
PosiTradingFeePending: 0,//交易费用后付
PosiTradingFeeUnit: 0,//单位交易费用
+ PosiFeeType: thisObj.getCurrentPosiFeeType(),//单位交易费用模式
InterestDirection: 0,//利息收支方式
InterestRateDefault: 0,//计息利率
InterestMode: 0,//计息基本类型
diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeView.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeView.js
index d0759efc..585a37cb 100644
--- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeView.js
+++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/swapTradeView.js
@@ -26,6 +26,10 @@ function editTrade(enid) {
window.location.href = `/swapTrade2/tradeEdit/?enid=${enid}`;
}
+function renewTrade(enid) {
+ window.location.href = `/swapTrade2/tradeEdit/?enid=0&renewEnid=${encodeURIComponent(enid)}`;
+}
+
function editTradeRemarkInfo(enid) {
main.open("修改备注", "/trade/EditRemarkInfo?enid=" + enid, { area: ["700px", "500px"] });
}
@@ -586,4 +590,4 @@ function SubmissionFields(enid) {
function SubmissionFieldsHistory(enid) {
main.open("报送相关字段填写", "/trade/submissionFieldsHistory?encryptId=" + enid);
-}
\ No newline at end of file
+}
diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/tradeEndConfirmList.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/tradeEndConfirmList.js
index 6f82f664..f6e515c6 100644
--- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/tradeEndConfirmList.js
+++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/tradeEndConfirmList.js
@@ -423,9 +423,9 @@ const colModelGrid = (new function () {
}, {
name: 'trade.ClientName', label: '客户名称', index: 'trade.ClientName', width: 180, align: 'left'
}, {
- name: 'swap_flow_event.Quantity', label: '份额', index: 'swap_flow_event.Quantity', width: 150, align: 'left', formatter: ShowNotionalFormater
+ name: 'swap_flow_event.Quantity', label: '份额', index: 'swap_flow_event.Quantity', width: 150, align: 'left', formatter: otcformat.fixed2
}, {
- name: 'swap_flow_event.TradingAmountAvg', label: '结算价', index: 'swap_flow_event.TradingAmountAvg', width: 150, align: 'left', formatter: otcformat.trading.umprice
+ name: 'swap_flow_event.TradingAmountAvg', label: '结算价', index: 'swap_flow_event.TradingAmountAvg', width: 150, align: 'left', formatter: otcformat.fixed2
}, {
name: 'swap_event.unwindData.SwapCloseAmount', label: '了结总额', index: 'swap_event.unwindData.SwapCloseAmount', width: 150, align: 'left', formatter: otcformat.trading.StockEqvNotional, sortable: false,
}, {
diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js
index 3838b7ea..554329ac 100644
--- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js
+++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js
@@ -8,6 +8,22 @@ const inputFormatEqvNotional = Object.freeze({ precision: otcformat.trading.Stoc
const inputFormatMarginRate = Object.freeze({ precision: otcformat.trading.marginRateP.precision, append: '%' });
const inputFormatMarginRateNoPercent = Object.freeze({ precision: otcformat.trading.umpriceP.precision, append: '', percent: true });
const inputFormatSwapDeliveryPrice = Object.freeze({ precision: 9, append: '', negative: true });
+const consPosiFeeType = Object.freeze({ Percent: 0, Unit: 1 });
+const swapPosiFeeCalc = {
+ normalizeFeeType(feeType) {
+ return Number(feeType) === consPosiFeeType.Unit ? consPosiFeeType.Unit : consPosiFeeType.Percent;
+ },
+ calcTradingFee(feeType, feeUnit, closeNotionalValue, closeQty) {
+ const normalizedFeeType = this.normalizeFeeType(feeType);
+ const normalizedFeeUnit = Number(feeUnit) || 0;
+ const normalizedCloseNotionalValue = Number(closeNotionalValue) || 0;
+ const normalizedCloseQty = Number(closeQty) || 0;
+ const tradingFee = normalizedFeeType === consPosiFeeType.Unit
+ ? normalizedFeeUnit * normalizedCloseQty
+ : normalizedFeeUnit / 100 * normalizedCloseNotionalValue;
+ return otcformat.trading.StockEqvNotional(_.round(tradingFee, 2));
+ }
+};
let ValueDate = model.ValueDate;
const vue = new Vue({
el: '#vueDiv',
@@ -140,6 +156,7 @@ const vue = new Vue({
this.deal.CloseQty = this.calcCloseQtyByPercent(this.deal.ClosePercent);
}
this.calcTradingFeePending();
+ this.refreshTradingFeeByUnit();
this.getInterestList();
this.calcFloatClosePnl();
},
@@ -153,6 +170,13 @@ const vue = new Vue({
calcTradingFeePending() {
this.floatPosition.TradingFeePending = this.floatPosition.BeforeCloseFee * parseFloat(this.deal.ClosePercent);
},
+ refreshTradingFeeByUnit() {
+ this.floatPosition.TradingFee = swapPosiFeeCalc.calcTradingFee(
+ this.floatPosition.PosiFeeType,
+ this.floatPosition.PosiTradingFeeUnit,
+ this.deal.CloseNotionalValue,
+ this.deal.CloseQty);
+ },
changeCloseQty() {//修改平仓数量
if (parseFloat(this.deal.CloseQty) > parseFloat(this.deal.PositionQty)) {
main.message("平仓数量不能超过持仓数量");
@@ -169,6 +193,7 @@ const vue = new Vue({
// 占期初口径:平仓名义本金 = 平仓比例 × 期初名义本金(NotionalValue)
this.deal.CloseNotionalValue = otcformat.trading.StockEqvNotional(parseFloat(this.deal.ClosePercent) * parseFloat(this.deal.NotionalValue));
this.calcTradingFeePending();
+ this.refreshTradingFeeByUnit();
this.getInterestList();
this.calcFloatClosePnl();
},
@@ -187,6 +212,7 @@ const vue = new Vue({
this.deal.CloseMethod = 2;
}
this.calcTradingFeePending();
+ this.refreshTradingFeeByUnit();
this.getInterestList();
this.calcFloatClosePnl();
},
@@ -205,6 +231,7 @@ const vue = new Vue({
this.deal.CloseMethod = 2;
}
this.calcTradingFeePending();
+ this.refreshTradingFeeByUnit();
this.getInterestList();
this.calcFloatClosePnl();
},
diff --git a/YLErpWeb/wwwroot/Scripts/app/trade/swapTradeEdit.js b/YLErpWeb/wwwroot/Scripts/app/trade/swapTradeEdit.js
index 95b22a66..0ad5dc9e 100644
--- a/YLErpWeb/wwwroot/Scripts/app/trade/swapTradeEdit.js
+++ b/YLErpWeb/wwwroot/Scripts/app/trade/swapTradeEdit.js
@@ -263,18 +263,18 @@ const vue = new Vue({
this.CnStockEqvNotional();
this.changeStockEqvNotional();
},
- //变更名义本金
+ //变更名义本金(仅格式化,不反算数量)
changeStockEqvNotional() {
this.trade.StockEqvNotional = otcformat.trading.StockEqvNotional(this.trade.StockEqvNotional);
if (this.trade.trade_swap.IsPayFloatingProfit) {
- var spotPrice = isPaySyntheticUnderlying ? payMaxPrice : $("#trade_swap\\.PaySpotPrice").val();
- var notional = spotPrice != 0 ? this.trade.StockEqvNotional / spotPrice : 0;
- this.trade.trade_swap.PayNotional = otcformat.trading.notional(Math.abs(notional));
- var tradeAmount = notional / payCountRatio;
- this.trade.trade_swap.PayTradeAmount = otcformat.trading.notional(Math.abs(tradeAmount));
- var lots = notional / payContractSize;
- this.trade.trade_swap.PayLot = otcformat.trading.notional(Math.abs(lots));
+ // var spotPrice = isPaySyntheticUnderlying ? payMaxPrice : $("#trade_swap\\.PaySpotPrice").val();
+ // var notional = spotPrice != 0 ? this.trade.StockEqvNotional / spotPrice : 0;
+ // this.trade.trade_swap.PayNotional = otcformat.trading.notional(Math.abs(notional));
+ // var tradeAmount = notional / payCountRatio;
+ // this.trade.trade_swap.PayTradeAmount = otcformat.trading.notional(Math.abs(tradeAmount));
+ // var lots = notional / payContractSize;
+ // this.trade.trade_swap.PayLot = otcformat.trading.notional(Math.abs(lots));
if (this.isSingleFee) {
this.changeGetSingleFee();
}
@@ -284,13 +284,6 @@ const vue = new Vue({
}
if (this.trade.trade_swap.IsGetFloatingProfit) {
- var spotPrice = isGetSyntheticUnderlying ? getMaxPrice : $("#trade_swap\\.GetSpotPrice").val();
- var notional = spotPrice != 0 ? this.trade.StockEqvNotional / spotPrice : 0;
- this.trade.trade_swap.GetNotional = otcformat.trading.notional(Math.abs(notional));
- var tradeAmount = notional / getCountRatio;
- this.trade.trade_swap.GetTradeAmount = otcformat.trading.notional(Math.abs(tradeAmount));
- var lots = notional / getContractSize;
- this.trade.trade_swap.GetLot = otcformat.trading.notional(Math.abs(lots));
if (this.isSingleFee) {
this.changePaySingleFee();
}
diff --git a/YLErpWeb/wwwroot/Scripts/utils.js b/YLErpWeb/wwwroot/Scripts/utils.js
index a615eda2..95c78d12 100644
--- a/YLErpWeb/wwwroot/Scripts/utils.js
+++ b/YLErpWeb/wwwroot/Scripts/utils.js
@@ -1236,7 +1236,9 @@ main.exportVisibleColumnsToExcel = function (jgrid, fileName, groupConfig, expor
exportCols.forEach(function (col) {
var colIndex = colModel.indexOf(col);
var rawValue = $.jgrid.getAccessor(row, col.name);
- var formattedValue = gridElement && gridElement.formatter
+ var formattedValue = typeof col.exportFormatter === 'function'
+ ? col.exportFormatter(rawValue, row)
+ : gridElement && gridElement.formatter
? gridElement.formatter(rowIndex + 1, rawValue, colIndex, row, 'add')
: rawValue;
formattedRow[col.name] = $('').html(formattedValue == null ? '' : String(formattedValue)).text().replace(/\u00a0/g, '');
@@ -1263,7 +1265,9 @@ main.exportVisibleColumnsToExcel = function (jgrid, fileName, groupConfig, expor
for (var c = 0; c < exportCols.length; c++) {
var val = rows[i][exportCols[c].name];
if (val === undefined || val === null) val = '';
- html += '
' + escapeXml(String(val)) + ' | ';
+ var numberFormat = exportCols[c].exportNumberFormat;
+ var style = numberFormat ? ' style="mso-number-format:\'' + escapeXml(String(numberFormat)) + '\';"' : '';
+ html += '' + escapeXml(String(val)) + ' | ';
}
html += '';
}