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: 9 }, netPrice: { integerDigits: 6, precision: 9 }, yield: { integerDigits: 2, precision: 4 } }, TBonds: { grossPrice: { integerDigits: 6, precision: 9 }, netPrice: { integerDigits: 6, precision: 9 }, yield: { integerDigits: 2, precision: 4 } }, CreditBonds: { grossPrice: { integerDigits: 6, precision: 9 }, netPrice: { integerDigits: 6, precision: 9 }, yield: { integerDigits: 2, precision: 4 } }, OtherBonds: { grossPrice: { integerDigits: 6, precision: 9 }, netPrice: { integerDigits: 6, precision: 9 }, yield: { integerDigits: 2, precision: 4 } }, 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: '', enterPressed: false, formatSnapshot: '' }; }, mounted: function () { this.text = this.toDisplay(this.value); this.formatSnapshot = JSON.stringify(this.format || {}); }, 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; }, onKeydown: function (event) { this.enterPressed = false; this.$emit('keydown', event); const keyCode = event.which || event.keyCode; if (event.key !== 'Enter' && keyCode !== 13 && keyCode !== 108) return; this.enterPressed = true; event.target.blur(); }, onChange: function (event) { if (this.text.endsWith('.')) this.text = this.text.substring(0, this.text.length - 1); this.updateValue(this.text, true, false); const value = this.toModel(this.text); this.$emit('input', value); if (this.enterPressed) this.$emit('enter', value); this.enterPressed = false; 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 () { const snapshot = JSON.stringify(this.format || {}); if (snapshot === this.formatSnapshot) return; this.formatSnapshot = snapshot; 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));