/** * swapCalc.js — 互换结算/平仓纯计算函数(与 C# FrontendCalcReference 对齐) * ============================================================================ * 设计要点: * - 无 Vue / otcformat / jQuery / lodash 依赖;复用 swapPricePrecision 的字符串十进制运算。 * - 浏览器:挂到 window.SwapCalc(需在 incomeSwapTrade.js / swapTradeEdit.js 之前加载)。 * - Node: module.exports(UMD 包装),供 fe-tests/*.test.js 使用。 * - 公式与 YLErpDAL/Helpers/FrontendCalcReference.cs 保持一致,是前后端同一份金标准。 * * 生产接线状态(tested == used):incomeSwapTrade.js / swapTradeEdit.js 已调用 * getPriceScale / deriveTradingAmountAvg / calcFloatPnlSum / calcStockEqvNotional * 这 4 个叶子函数(对应真实出过的 4 个 bug:20ea93d8 / dcf649f2 / 3c5f25a5 / f873239a)。 * calcUnwind / calcIncome 仅用于 swapCalc.test.js 的前后端金标准交叉校验,未接入生产代码。 * * 守卫的 bug(见 git 历史): * - 20ea93d8 / dcf649f2:deriveTradingAmountAvg 必须用 PosiGrossPrice(全价) 且债券 ×100 * - 3c5f25a5:calcFloatPnlSum 必须 .toFixed(2)(保留 2 位小数) * - f873239a:calcStockEqvNotional 必须 round 到 2 位 * ============================================================================ */ (function (root, factory) { if (typeof module === 'object' && module.exports) { module.exports = factory(require('./swapPricePrecisionHelper.js')); } else { root.SwapCalc = factory(root.swapPricePrecision); } })(typeof self !== 'undefined' ? self : this, function (swapPricePrecision) { 'use strict'; // 四舍五入(远离零),对齐 C# MidpointRounding.AwayFromZero function roundHalfAwayFromZero(value, digits) { var f = Math.pow(10, digits); var n = Number(value) * f; var sign = n < 0 ? -1 : 1; var r = Math.round(Math.abs(n)) * sign; var result = r / f; return result === 0 ? 0 : result; // 消除 -0 } // 价格缩放因子:债券(multiplier=100)界面为百分比态,计算用相对价需 ÷100 function getPriceScale(multiplier) { return multiplier === 100 ? 0.01 : 1; } // 期末全价(界面态) = 期初全价(相对价) × multiplier // 必须用 PosiGrossPrice(全价),非 PosiNetPrice(净价);债券 ×100 转界面百分比态 function deriveTradingAmountAvg(posiGrossPrice, multiplier) { return posiGrossPrice * multiplier; } // 普通打开时以期初全价作为期末价默认值;审批打开时回显已提交的期末相对价。 function resolveIncomeTradingAmountAvg(posiGrossPrice, submittedTradingAmountAvg, multiplier, isUseApproval) { var relativePrice = isUseApproval && submittedTradingAmountAvg !== undefined && submittedTradingAmountAvg !== null ? submittedTradingAmountAvg : posiGrossPrice; return relativePrice * multiplier; } // 金额四舍五入到指定小数位(避免 0.1+0.2 类浮点误差) function roundMoney(value, digits) { return roundHalfAwayFromZero(value, digits); } // 浮动盈亏合计 = (平仓盈亏 + 交易费用 + 待结算费用 + 分红).toFixed(2) function calcFloatPnlSum(markClosePnl, tradingFee, tradingFeePending, dividendIn) { var sum = (+markClosePnl) + (+tradingFee) + (+tradingFeePending) + (+dividendIn); return roundHalfAwayFromZero(sum, 2); } // 名义本金 = 期初全价 × 因子,保留 2 位(EQD-6090) // factor 在前端 = 数量 × 乘数(national);传入乘数时避免数量先被 JS Number 相乘。 function calcStockEqvNotional(posiGrossPrice, quantity, contractSize) { var factor = contractSize === undefined ? quantity : swapPricePrecision.multiplyDecimal(quantity, contractSize); var product = factor === null ? null : swapPricePrecision.multiplyDecimal(posiGrossPrice, factor); if (product === null) return 0; var rounded = swapPricePrecision.roundDecimal(product, 2); return typeof posiGrossPrice === 'string' || typeof quantity === 'string' || typeof contractSize === 'string' ? rounded : Number(rounded); } // 平仓名义本金 = 平仓比例 × 剩余持仓名义本金(PosiNotionalValue) // 多次部分平仓后必须用剩余本金 PosiNotionalValue,不能用原始 NotionalValue,否则偏大 function calcCloseNotionalByRemaining(closePercent, posiNotionalValue) { return roundHalfAwayFromZero(Number(closePercent) * Number(posiNotionalValue), 2); } // 平仓比例 = 平仓名义本金 / 剩余持仓名义本金(PosiNotionalValue) // 多次部分平仓后必须以剩余本金为分母,否则比例偏小,导致后端预付金返还本金计算错误 function calcClosePercentByRemaining(closeNotionalValue, posiNotionalValue) { if (Number(posiNotionalValue) === 0) return 0; return roundHalfAwayFromZero(Number(closeNotionalValue) / Number(posiNotionalValue), 6); } // 平仓数量(占期初口径 A):CloseQty = PositionQty × (closePercent / oriClosePercent) // closePercent 是"占期初名义本金比例"(A),需先除以 oriClosePercent(=剩余/期初) 转成"占剩余比例"(B), // 再乘以剩余持仓数量 PositionQty。 // 多次部分平仓后必须这样转换,否则全部↔部分切换时 ClosePercent 没变但 CloseQty 会变(不自洽)。 // 除零保护:oriClosePercent=0(剩余为0,已全部平完)时返回 0。 function calcCloseQtyByOriginalPercent(closePercent, oriClosePercent, positionQty) { var ori = Number(oriClosePercent); if (ori === 0) return 0; if (Number(closePercent) >= ori) return Number(positionQty); return roundHalfAwayFromZero(Number(positionQty) * (Number(closePercent) / ori), 2); } // 平仓比例(占期初口径 A)= (CloseQty / PositionQty) × oriClosePercent // CloseQty/PositionQty 得到"占剩余比例"(B),乘以 oriClosePercent(=剩余/期初) 转成"占期初比例"(A)。 // 除零保护:PositionQty=0 时返回 0。 function calcOriginalClosePercentByQty(closeQty, positionQty, oriClosePercent) { var qty = Number(positionQty); if (qty === 0) return 0; return roundHalfAwayFromZero((Number(closeQty) / qty) * Number(oriClosePercent), 6); } // 盯市平仓盈亏(unwind):CloseQty × (期末全价×scale − 期初全价) × floatRatio × longRatio // 对齐 FrontendCalcReference.CalcUnwind:先 ×10000 取整再 ÷10000,最后 toFixed(2) // 干净输入下等价于直接 round(.., 2) function calcMarkClosePnl(closeQty, tradingAmountAvg, scale, entryPrice, floatRatio, longRatio) { var product = closeQty * (tradingAmountAvg * scale - entryPrice) * floatRatio * longRatio; var step = Math.round(product * 10000) / 10000; // 对齐 C# Math.Round(.. * 10000) / 10000 return roundHalfAwayFromZero(step, 2); } // ---- 组合函数:对齐 C# FrontendCalcReference.CalcUnwind / CalcIncome ---- // 用途:作为「前端 JS 完整盈亏聚合公式」与「后端 C# 金标准」的交叉校验 // (见 swapCalc.test.js 的 FC_001~FC_008 八个冻结场景)。 // 注意:以下 calcUnwind / calcIncome **未接入生产代码**——生产 Vue 组件只调用上方 // 4 个叶子函数。它们是冻结完整聚合逻辑的参考规格;若要让生产聚合逻辑也被自动守卫, // 需把 incomeSwapTrade.js / swapTradeEdit.js / unwindSwapTrade.js 的聚合计算也改调它们。 function parseOrZero(s) { return (s === undefined || s === null || s === '') ? 0 : Number(s); } function sumLegs(legs) { return (legs || []).reduce(function (acc, l) { return acc + parseOrZero(l.interestClosePnL); }, 0); } // 平仓页(unwind)盈亏汇总 — 对齐 FrontendCalcReference.CalcUnwind function calcUnwind(input) { var entryPrice = input.posiGrossPrice; var scale = input.multiplier === 100 ? 0.01 : 1; var floatRatio = input.payDirection === 1 ? 1 : -1; var longRatio = input.positionType === 1 ? 1 : -1; var tradingFee = parseOrZero(input.tradingFee); var tradingFeePending = parseOrZero(input.tradingFeePending); var dividendIn = parseOrZero(input.dividendIn); var markClosePnl = calcMarkClosePnl( input.closeQty, input.tradingAmountAvg, scale, entryPrice, floatRatio, longRatio); markClosePnl = roundHalfAwayFromZero(markClosePnl, 2); var floatPnlSum = roundHalfAwayFromZero(markClosePnl + tradingFee + tradingFeePending + dividendIn, 2); var swapRealizedPnL = floatPnlSum + sumLegs(input.interestLegs) + sumLegs(input.marginLegs); var swapCloseAmount = floatPnlSum + sumLegs(input.interestLegs) + sumLegs(input.marginLegs); var swapMarginRebatePnl = sumLegs(input.marginLegs); var ratio = input.positionType === 1 ? 1 : -1; var tradingAmountFeeAvg = input.closeQty === 0 ? 0 : input.tradingAmountAvg * scale + (tradingFee / input.closeQty) * ratio; return { MarkClosePnl: roundHalfAwayFromZero(markClosePnl, 2), FloatPnlSum: floatPnlSum, SwapRealizedPnL: roundHalfAwayFromZero(swapRealizedPnL, 2), SwapCloseAmount: roundHalfAwayFromZero(swapCloseAmount, 2), SwapMarginRebatePnl: roundHalfAwayFromZero(swapMarginRebatePnl, 2), TradingAmountFeeAvg: tradingAmountFeeAvg }; } // 结息页(income)盈亏汇总 — 对齐 FrontendCalcReference.CalcIncome function calcIncome(input) { var entryPrice = input.posiGrossPrice; var scale = input.multiplier === 100 ? 0.01 : 1; var floatRatio = input.payDirection === 1 ? 1 : -1; var longRatio = input.positionType === 1 ? 1 : -1; var tradingFee = parseOrZero(input.tradingFee); var tradingFeePending = parseOrZero(input.tradingFeePending); var dividendIn = parseOrZero(input.dividendIn); var contractSize = input.contractSize === undefined || input.contractSize === null ? 1 : Number(input.contractSize); var markClosePnl = roundHalfAwayFromZero( input.positionQty * contractSize * (input.tradingAmountAvg * scale - entryPrice) * floatRatio * longRatio, 2); var floatPnlSum = roundHalfAwayFromZero(markClosePnl + tradingFee + tradingFeePending + dividendIn, 2); var swapRealizedPnL = floatPnlSum + sumLegs(input.interestLegs) + sumLegs(input.marginLegs); var swapCloseAmount = floatPnlSum + sumLegs(input.interestLegs) + sumLegs(input.marginLegs); var swapMarginRebatePnl = sumLegs(input.marginLegs); var tradingAmountFeeAvg = input.closeQty > 0 ? input.tradingAmountAvg * scale + (tradingFee / input.closeQty) * floatRatio : input.tradingAmountAvg * scale; return { MarkClosePnl: markClosePnl, FloatPnlSum: floatPnlSum, SwapRealizedPnL: roundHalfAwayFromZero(swapRealizedPnL, 2), SwapCloseAmount: roundHalfAwayFromZero(swapCloseAmount, 2), SwapMarginRebatePnl: roundHalfAwayFromZero(swapMarginRebatePnl, 2), TradingAmountFeeAvg: tradingAmountFeeAvg }; } /** * 债券净价/全价/收益率三字段互算:以 driver(CP/DP/YD) 为"源",回写时跳过源字段(保留用户手输值), * 其余两字段用计算器结果覆盖。 * - driver: 'CP'净价 / 'DP'全价 / 'YD'收益率,即本次回车的输入依据 * - calc: { cleanPrice, dirtyPrice, ytm } 来自 /Bond/CalcBond 的 resp.obj * - state: 持有三个字段的对象(直接原地写回) * 设计(对应交互约定1+2): * 回车某字段即以其为源调计算器,另两个字段被反算覆盖;源字段保持用户手输值不被回写。 * 故用户回车净价→全价/收益率被重算;再次回车全价→净价/收益率被重算(最后回车者恒为源)。 * 纯函数,jest 可直接测(见 fe-tests/bondCalc.test.js)。 */ function applyBondCalcResult(state, calc, driver) { var EPS = 1e-4; function write(field, type, value) { if (driver === type) return; // 源字段:保留用户手输值,不回写 if (value === undefined || value === null) return; // 计算器未返回该值则不覆盖 var cur = state[field]; if (typeof cur === 'number' && Math.abs(cur - value) < EPS) return; // 无变化不写,避免光标跳动 state[field] = value; } write('cleanPrice', 'CP', calc && calc.cleanPrice); write('dirtyPrice', 'DP', calc && calc.dirtyPrice); write('ytm', 'YD', calc && calc.ytm); } /** * 债券价格【存储态小数 ↔ 展示态每百元百分比】边界换算。 * 约定(见 ConsGlobal.bondPriceMultiple=0.01 / bondShowPriceMultiple=100、BondPriceConverter): * - 模型/DB 存【存储态小数】(如 0.995 = 99.5 元/百元面值); * - 债券计算器(bond-calc,经 zszq-bond-oms 代理)要【展示态百分比】(如 99.5)。 * 历史坑:calcBondForItem 曾漏掉这正反两步,把存储态 0.995 当 99.5 发给计算器、 * 又把返回 97.82 原样落库;配合 vue-number-input 的 percent:true(显示再×100) * 造成 -117.93 / 378543 离谱值。故发计算器前 bondPriceToCalc(×100)、回写前 bondCalcPriceToStorage(÷100)。 * 纯函数,jest 可直接测。 */ function bondPriceToCalc(storagePrice) { return Number(storagePrice) * 100; // 存储态小数 → 展示态每百元百分比 } function bondCalcPriceToStorage(displayPrice) { return Number(displayPrice) / 100; // 展示态百分比 → 存储态小数 } /** * 清空债券三字段互算的【源/AUTO/REV】标志(纯函数,jest 可测)。 * 用途:切换标的(setUnderlyingCode)时调用,避免旧债券的状态污染新债券(D1 修复)。 * 直接对传入对象赋值;在 Vue 组件里该 item 已是响应式对象,重赋值能正常触发响应式更新。 */ function clearBondCalcFlags(state) { state.bondDriverType = null; state.bondAuto = { CP: false, DP: false, YD: false }; state.bondRev = { CP: false, DP: false, YD: false }; return state; } /** * 交互约定2(纯函数,jest 可测):计算器调用【成功】后的标识落地。 * - 以 driver(CP/DP/YD) 为"源",标记 bondDriverType=driver; * - 其余两字段标记 bondAuto=true("AUTO"=计算器算出的); * - 清除 bondRev(人工输入标记)。 * 不写任何价格数值——数值由 applyBondCalcResult 回写。 */ function applyBondCalcSuccess(state, driver) { state.bondDriverType = driver; state.bondAuto = { CP: driver !== 'CP', DP: driver !== 'DP', YD: driver !== 'YD' }; state.bondRev = { CP: false, DP: false, YD: false }; return state; } /** * 交互约定2(纯函数,jest 可测):计算器调用【失败】后的标识与数值落地。 * - 保留用户刚回车的"源"字段值(driver),让用户可就地修改; * - 另两个字段清空为空白("另外两个数值都更新为空白"); * - 三个数值旁边的标识全部清空("三个数值旁边都清空标识")。 */ function applyBondCalcFailure(state, driver) { var fields = { CP: 'PosiNetNoFeePrice', DP: 'PosiGrossPrice', YD: 'InitYtm' }; Object.keys(fields).forEach(function (t) { if (t !== driver) state[fields[t]] = null; // 非源字段清空 }); state.bondDriverType = null; state.bondAuto = { CP: false, DP: false, YD: false }; state.bondRev = { CP: false, DP: false, YD: false }; return state; } /** * 交互约定3(纯函数,jest 可测):编辑某字段但未回车(失焦/输入)时的标识落地。 * - 清除 源 与 AUTO 标识(不再代表任何已算结果); * - 本字段标 bondRev[type]=true("REV"=人工输入),不联动另外两字段; * - ★ 累计 REV(不清空其它已手工编辑字段的 REV):连续手动编辑多字段(不回车)时, * 每个被改过的字段都保留 REV,使 UI 正确显示"全部为人工输入",且不会被后续计算器覆盖 * (见文档步骤3-b)。此前的实现每次重置全部 REV 再只标当前字段,导致"只有最后编辑的字段留 REV", * 中间手工编辑的字段 REV 被静默抹掉(步骤3-b 不满足需求)。 */ function applyBondManualEdit(state, type) { state.bondDriverType = null; state.bondAuto = { CP: false, DP: false, YD: false }; if (!state.bondRev || typeof state.bondRev !== 'object') { state.bondRev = { CP: false, DP: false, YD: false }; } if (type) state.bondRev[type] = true; // 累加:仅置当前字段,保留其它已手工编辑字段的 REV return state; } /** * 从 /Bond/CalcBond 响应(resp.obj,即 CalBondResult)中提取应展示给用户的错误文案; * 无错误返回 null(调用方据此决定是否回写、是否提示)。 * 覆盖: * - 空响应(计算器无响应) * - 业务错误码 errCode!=0(债券不存在 / 债券信息不全 / 参数非法) * - 防御性值域校验(errCode=0 但数值离谱):即便计算器返回 success, * 仍可能因【前端↔计算器的单位换算不匹配】或 * 行权收益率哨兵(-999999) 而给出负净价 / 收益率量级爆炸(如 378543) 之类的垃圾值。 * 现有 errCode 守卫拦不住这类"成功但离谱"的响应,故在此加值域闸门, * 宁可不回写并提示用户,也绝不用垃圾值覆盖手工输入。 * 与 C# BondCalcHepler 的 errCode 守卫一一对应,确保"坏结果"不会静默回写覆盖手工输入。 * 纯函数,jest 可直接测(见 fe-tests/bondCalc.test.js)。 */ function getBondCalcErrorMessage(resp) { if (!resp) return "债券计算器无响应,已保留手工输入"; if (resp.errCode && resp.errCode !== 0) { return resp.errMsg || "债券计算失败,请检查标的或参数"; } // 防御性值域校验:拦截 errCode=0 但数值离谱的响应(部署环境主数据量纲错误 / 哨兵值) var SENTINEL = -999999; // bond-calc 行权收益率不可用哨兵 var cp = resp.cleanPrice, dp = resp.dirtyPrice, yd = resp.ytm; var absurd = function (v) { return typeof v === 'number' && (v === SENTINEL || v === SENTINEL * 100); }; if (absurd(cp) || absurd(dp) || absurd(yd)) { return "债券计算返回哨兵值(部分指标不可用),已保留手工输入,请检查估值日/价格输入或联系管理员核对债券计算服务"; } // 净价/全价:占面值百分比,正常约 20~300,绝不为负、也不会破千 if ((typeof cp === 'number' && (cp <= 0 || cp > 1000)) || (typeof dp === 'number' && (dp <= 0 || dp > 1000))) { return "债券计算净价/全价超出合理范围(应为面值百分比且为正),已保留手工输入;" + "请检查估值日/价格输入或联系管理员核对债券计算服务"; } // 到期收益率:百分数口径(如 6.37 表示 6.37%),正常约 -5~30,|收益率|>100 视为爆炸 if (typeof yd === 'number' && Math.abs(yd) > 100) { return "债券计算收益率量级异常(" + yd + "),已保留手工输入;" + "请检查估值日/价格输入或联系管理员核对债券计算服务"; } return null; } /** * 加载路径(重开/刷新已保存债券单)调用(纯函数,jest 可测): * 把三字段互算的状态标识全部清空,使已保存的净价/全价/收益率纯展示、无任何 源/AUTO/REV 标记。 * - 不锁定、不置源:交互约定3规定"编辑未回车不联动",故用户载入后即使改某格(未回车)也不会反算另两格; * 只有用户主动回车某格时,才以该格为源重新推导(符合交互约定1)。 * 仅在加载路径对"债券且三值齐全"的标的调用,全新未填的债券无副作用。 */ function clearBondCalcMarksOnLoad(state) { state.bondDriverType = null; state.bondAuto = { CP: false, DP: false, YD: false }; state.bondRev = { CP: false, DP: false, YD: false }; return state; } /** * D3 修复(纯函数,jest 可测):债券计算器失败提示去重。 * 不可算的债券上用户逐键手填时,v-on:input 每次按键都触发一次失败计算并弹 toast,会连刷数条相同提示。 * 这里按"同一错误文案连续出现只提示一次"抑制噪声;错误文案变化(如 债券不存在→信息不全)则照常提示, * 成功(传入 null/空)时清掉标记,便于下次真出不同错误时仍能提示。 * 纯做"是否该弹"的决策并维护 state._lastBondErr,不触碰任何计算逻辑,零风险。 */ function shouldShowBondErr(state, err) { if (!err) { state._lastBondErr = null; return false; } // 成功/无错误:清标记、不提示 if (state._lastBondErr === err) return false; // 连续相同错误 → 抑制重复 toast state._lastBondErr = err; return true; } /** * 估值日(开始日)缺失校验(纯函数,jest 可测)。 * 需求:债券净价/全价/收益率以【互换起始日 StartDate】估值;开始日现已默认即有, * 故不再"悄悄回退到交易日",而是真的为空时返回错误文案,由上层提示用户 * (并允许其手动填写三项数值,见需求2)。 * 返回 非空字符串=缺失需提示;返回 null=已具备估值日。 */ function getBondStartDateMissingMsg(startDate) { if (startDate) return null; return "请先填写开始日(互换起始日,作为估值日)后再计算债券净价/全价/收益率;" + "若暂不需要计算,可手动填写净价/全价/收益率三项数值"; } /** * 判断按键是否会【改变】债券三字段的数值(纯函数,jest 可测)。 * 用于 onBondPriceKeydown:仅"会改变数值"的按键才清 源/AUTO 标识并标本字段 REV; * 其余(回车/修饰键/导航键/功能键/纯 Shift)一律不改标识,避免"没改值却出现 REV"。 * 正向白名单(最稳,不会漏掉未来新增的导航键): * - 数字键:主键盘 48-57 / 小键盘 96-105 * - 小数点:主键盘 190 / 小键盘 110 * - 减号: 主键盘 189 / 小键盘 109 * - Backspace(8) / Delete(46) * 明确排除:回车(13/108,由 v-on:enter 单独处理)、Ctrl/Alt/Meta 组合键、 * Tab(9)/Home(36)/End(35)/←(37)/→(39)/↑(38)/↓(40)/Insert(45)/PageUp(33)/PageDown(34)、 * F1-F12(112-123)、纯 Shift(16)。这些键不改数值,不应清标识/标 REV。 */ function isBondPriceValueKey(event) { if (!event) return false; if (event.ctrlKey || event.altKey || event.metaKey) return false; // 组合键(如 Ctrl+C/V)不改数值 var kc = event.which || event.keyCode; if (kc === 13 || kc === 108) return false; // 回车由 enter 事件处理 // 纯 Shift 单独按下不改数值(Shift+数字在数值框不产生数字,亦不改数值) if (kc === 16) return false; // 导航/功能键不改数值 if (kc >= 33 && kc <= 40) return false; // PageUp/PageDown/End/Home/←/↑/→/↓ if (kc === 45) return false; // Insert if (kc >= 112 && kc <= 123) return false; // F1-F12 if (kc === 9) return false; // Tab // 以下为会改变数值的键 var valueKeys = [8, 46, 48,49,50,51,52,53,54,55,56,57, 96,97,98,99,100,101,102,103,104,105, 109,110,189,190]; return valueKeys.indexOf(kc) !== -1; } return { applyBondCalcResult: applyBondCalcResult, isBondPriceValueKey: isBondPriceValueKey, getBondCalcErrorMessage: getBondCalcErrorMessage, bondPriceToCalc: bondPriceToCalc, bondCalcPriceToStorage: bondCalcPriceToStorage, clearBondCalcFlags: clearBondCalcFlags, applyBondCalcSuccess: applyBondCalcSuccess, applyBondCalcFailure: applyBondCalcFailure, applyBondManualEdit: applyBondManualEdit, clearBondCalcMarksOnLoad: clearBondCalcMarksOnLoad, shouldShowBondErr: shouldShowBondErr, getBondStartDateMissingMsg: getBondStartDateMissingMsg, roundHalfAwayFromZero: roundHalfAwayFromZero, getPriceScale: getPriceScale, deriveTradingAmountAvg: deriveTradingAmountAvg, resolveIncomeTradingAmountAvg: resolveIncomeTradingAmountAvg, roundMoney: roundMoney, calcFloatPnlSum: calcFloatPnlSum, calcStockEqvNotional: calcStockEqvNotional, calcCloseNotionalByRemaining: calcCloseNotionalByRemaining, calcClosePercentByRemaining: calcClosePercentByRemaining, calcCloseQtyByOriginalPercent: calcCloseQtyByOriginalPercent, calcOriginalClosePercentByQty: calcOriginalClosePercentByQty, calcMarkClosePnl: calcMarkClosePnl, calcUnwind: calcUnwind, calcIncome: calcIncome }; });