diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bdba941dd..94ff1d0d1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added - Added support for the new license key format. A proprietary key can now grant a subset of the library: a function your key does not include evaluates to a `#LIC!` error, and the matching parts of the API throw a `LicenseCapabilityMissingError`. `getAvailableFunctions()` and `getFunctionDetails()` describe only the functions your key includes. [#1728](https://github.com/handsontable/hyperformula/pull/1728) +- Added new functions: ACCRINTM, DISC, INTRATE, PRICEDISC, RECEIVED, YIELDDISC. [#1799](https://github.com/handsontable/hyperformula/pull/1799) ### Changed diff --git a/docs/guide/list-of-differences.md b/docs/guide/list-of-differences.md index a109f82616..05bc580620 100644 --- a/docs/guide/list-of-differences.md +++ b/docs/guide/list-of-differences.md @@ -77,6 +77,7 @@ To remove the differences, create [custom implementations](custom-functions.md) | LCMP | =LCM(1000000, 1000001, 1000002, 1000003) | NUM | 5.00003E+23 | NUM | | TBILLPRICE | =TBILLPRICE(0, 180, 1.9) | 5.0000 | NUM | 5.0000 | | TBILLPRICE | =TBILLPRICE(0, 180, 2) | 0.0000 | NUM | 0.0000 | +| ACCRINTM | =ACCRINTM(DATE(2008,4,1), DATE(2008,6,15), 0.1) | 20.5556 | 20.5556 | Wrong number | | NPV | =NPV(1, TRUE(), 1) | 0.7500 | 0.5000 | 0.7500 | | NPV | =NPV(1,B1) where B1 = true | 0.5000 | 0.0000 | 0.0000 | | POISSON.DIST | =POISSON.DIST(-0.01, 0, FALSE()) | NUM | 1.0000 | NUM | diff --git a/src/DateTimeHelper.ts b/src/DateTimeHelper.ts index aab02290c5..d2de40838e 100644 --- a/src/DateTimeHelper.ts +++ b/src/DateTimeHelper.ts @@ -211,6 +211,43 @@ export class DateTimeHelper { } } + /** + * Counts the days between two dates under a day-count basis, and returns the number of days in a year under the + * same basis. YEARFRAC and the securities functions (DISC, INTRATE, PRICEDISC, RECEIVED, YIELDDISC, ACCRINTM) + * compute their year fraction as `dayCount / yearDays`. + * + * | `basis` | `dayCount` | `yearDays` | + * |---------|----------------------------------------------|-----------------------------| + * | 0 | US (NASD) 30/360, as adjusted by `toBasisUS` | 360 | + * | 1 | actual days | `yearLengthForBasis` | + * | 2 | actual days | 360 | + * | 3 | actual days | 365 | + * | 4 | European 30/360, as adjusted by `toBasisEU` | 360 | + * + * @param {number} startDate - date serial number, an integer not greater than `endDate` + * @param {number} endDate - date serial number, an integer + * @param {number} basis - day-count basis, an integer from 0 to 4 + */ + public dayCountByBasis(startDate: number, endDate: number, basis: number): { dayCount: number, yearDays: number } { + const start = this.numberToSimpleDate(startDate) + const end = this.numberToSimpleDate(endDate) + switch (basis) { + case 0: { + const [adjustedStart, adjustedEnd] = this.toBasisUS({...start}, {...end}) + return {dayCount: daysIn30DayMonths(adjustedStart, adjustedEnd), yearDays: 360} + } + case 1: + return {dayCount: endDate - startDate, yearDays: this.yearLengthForBasis(start, end)} + case 2: + return {dayCount: endDate - startDate, yearDays: 360} + case 3: + return {dayCount: endDate - startDate, yearDays: 365} + case 4: + return {dayCount: daysIn30DayMonths(toBasisEU(start), toBasisEU(end)), yearDays: 360} + } + throw new Error('Should not be reachable.') + } + private parseSingleFormat(dateString: string, dateFormat?: string, timeFormat?: string): Maybe { const dateTime = this.parseDateTime(dateString, dateFormat, timeFormat) if (instanceOfSimpleDate(dateTime)) { @@ -319,3 +356,11 @@ export function timeToNumber(time: SimpleTime): number { export function toBasisEU(date: SimpleDate): SimpleDate { return {year: date.year, month: date.month, day: Math.min(30, date.day)} } + +/** + * Counts the days between two dates as if every month had 30 days. The dates must already be adjusted to a 30/360 + * basis (`toBasisUS` or `toBasisEU`). + */ +function daysIn30DayMonths(start: SimpleDate, end: SimpleDate): number { + return 360 * (end.year - start.year) + 30 * (end.month - start.month) + end.day - start.day +} diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index fea286083c..5ae7308e86 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ABS', + ACCRINTM: 'ACCRINTM', ACOS: 'ARCCOS', ACOSH: 'ARCCOSH', ACOT: 'ACOT', @@ -96,6 +97,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'DECIMAL', DEGREES: 'DEGREES', DELTA: 'DELTA', + DISC: 'DISC', DOLLARDE: 'DOLLARDE', DOLLARFR: 'DOLLARFR', EDATE: 'EDATE', @@ -124,6 +126,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEX', INT: 'CELÁ.ČÁST', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'INTRATE', IPMT: 'PLATBA.ÚROK', IRR: 'MÍRA.VÝNOSNOSTI', ISBINARY: 'ISBINARY', @@ -184,11 +187,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'SOUČIN', POWER: 'POWER', PPMT: 'PLATBA.ZÁKLAD', + PRICEDISC: 'PRICEDISC', PROPER: 'VELKÁ2', PV: 'SOUČHODNOTA', RADIANS: 'RADIANS', RAND: 'NÁHČÍSLO', RATE: 'ÚROKOVÁ.MÍRA', + RECEIVED: 'RECEIVED', REPLACE: 'NAHRADIT', REPT: 'OPAKOVAT', RIGHT: 'ZPRAVA', @@ -259,6 +264,7 @@ const dictionary: RawTranslationPackage = { XOR: 'XOR', YEAR: 'ROK', YEARFRAC: 'YEARFRAC', + YIELDDISC: 'YIELDDISC', ROMAN: 'ROMAN', ARABIC: 'ARABIC', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index 9264551249..38df364820 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ABS', + ACCRINTM: 'PÅLØBRENTE.UDLØB', ACOS: 'ARCCOS', ACOSH: 'ARCCOSH', ACOT: 'ARCCOT', @@ -96,6 +97,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'DECIMAL', DEGREES: 'GRADER', DELTA: 'DELTA', + DISC: 'DISKONTO', DOLLARDE: 'KR.DECIMAL', DOLLARFR: 'KR.BRØK', EDATE: 'EDATO', @@ -124,6 +126,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEKS', INT: 'HELTAL', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'RENTEFOD', IPMT: 'R.YDELSE', IRR: 'IA', ISBINARY: 'ISBINARY', @@ -184,11 +187,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'PRODUKT', POWER: 'POTENS', PPMT: 'H.YDELSE', + PRICEDISC: 'KURS.DISKONTO', PROPER: 'STORT.FORBOGSTAV', PV: 'NV', RADIANS: 'RADIANER', RAND: 'SLUMP', RATE: 'RENTE', + RECEIVED: 'MODTAGET.VED.UDLØB', REPLACE: 'ERSTAT', REPT: 'GENTAG', RIGHT: 'HØJRE', @@ -259,6 +264,7 @@ const dictionary: RawTranslationPackage = { XOR: 'XELLER', YEAR: 'ÅR', YEARFRAC: 'ÅR.BRØK', + YIELDDISC: 'AFKAST.DISKONTO', ROMAN: 'ROMERTAL', ARABIC: 'ARABISK', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index 4d3d39319b..87d3f1c4e5 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ABS', + ACCRINTM: 'AUFGELZINSF', ACOS: 'ARCCOS', ACOSH: 'ARCCOSHYP', ACOT: 'ARCCOTAN', @@ -96,6 +97,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'DEZIMAL', DEGREES: 'GRAD', DELTA: 'DELTA', + DISC: 'DISAGIO', DOLLARDE: 'NOTIERUNGDEZ', DOLLARFR: 'NOTIERUNGBRU', EDATE: 'EDATUM', @@ -124,6 +126,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEX', INT: 'GANZZAHL', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'ZINSSATZ', IPMT: 'ZINSZ', IRR: 'IKV', ISBINARY: 'ISBINARY', @@ -184,11 +187,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'PRODUKT', POWER: 'POTENZ', PPMT: 'KAPZ', + PRICEDISC: 'KURSDISAGIO', PROPER: 'GROSS2', PV: 'BW', RADIANS: 'BOGENMASS', RAND: 'ZUFALLSZAHL', RATE: 'ZINS', + RECEIVED: 'AUSZAHLUNG', REPLACE: 'ERSETZEN', REPT: 'WIEDERHOLEN', RIGHT: 'RECHTS', @@ -259,6 +264,7 @@ const dictionary: RawTranslationPackage = { XOR: 'XODER', YEAR: 'JAHR', YEARFRAC: 'BRTEILJAHRE', + YIELDDISC: 'RENDITEDIS', ROMAN: 'RÖMISCH', ARABIC: 'ARABISCH', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index d271b732cd..14fc955a11 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ABS', + ACCRINTM: 'ACCRINTM', ACOS: 'ACOS', ACOSH: 'ACOSH', ACOT: 'ACOT', @@ -97,6 +98,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'DECIMAL', DEGREES: 'DEGREES', DELTA: 'DELTA', + DISC: 'DISC', DOLLARDE: 'DOLLARDE', DOLLARFR: 'DOLLARFR', EDATE: 'EDATE', @@ -125,6 +127,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEX', INT: 'INT', INTERVAL: 'INTERVAL', + INTRATE: 'INTRATE', IPMT: 'IPMT', IRR: 'IRR', ISBINARY: 'ISBINARY', @@ -185,11 +188,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'PRODUCT', POWER: 'POWER', PPMT: 'PPMT', + PRICEDISC: 'PRICEDISC', PROPER: 'PROPER', PV: 'PV', RADIANS: 'RADIANS', RAND: 'RAND', RATE: 'RATE', + RECEIVED: 'RECEIVED', REPLACE: 'REPLACE', REPT: 'REPT', RIGHT: 'RIGHT', @@ -261,6 +266,7 @@ const dictionary: RawTranslationPackage = { XLOOKUP: 'XLOOKUP', YEAR: 'YEAR', YEARFRAC: 'YEARFRAC', + YIELDDISC: 'YIELDDISC', 'HF.ADD': 'HF.ADD', 'HF.CONCAT': 'HF.CONCAT', 'HF.DIVIDE': 'HF.DIVIDE', diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index 9c6b817b98..c9fddad644 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -25,6 +25,7 @@ export const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ABS', + ACCRINTM: 'INT.ACUM.V', ACOS: 'ACOS', ACOSH: 'ACOSH', ACOT: 'ACOT', @@ -96,6 +97,7 @@ export const dictionary: RawTranslationPackage = { DECIMAL: 'CONV.DECIMAL', DEGREES: 'GRADOS', DELTA: 'DELTA', + DISC: 'TASA.DESC', DOLLARDE: 'MONEDA.DEC', DOLLARFR: 'MONEDA.FRAC', EDATE: 'FECHA.MES', @@ -124,6 +126,7 @@ export const dictionary: RawTranslationPackage = { INDEX: 'INDICE', INT: 'ENTERO', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'TASA.INT', IPMT: 'PAGOINT', IRR: 'TIR', ISBINARY: 'ISBINARY', @@ -184,11 +187,13 @@ export const dictionary: RawTranslationPackage = { PRODUCT: 'PRODUCTO', POWER: 'POTENCIA', PPMT: 'PAGOPRIN', + PRICEDISC: 'PRECIO.DESCUENTO', PROPER: 'NOMPROPIO', PV: 'VA', RADIANS: 'RADIANES', RAND: 'ALEATORIO', RATE: 'TASA', + RECEIVED: 'CANTIDAD.RECIBIDA', REPLACE: 'REEMPLAZAR', REPT: 'REPETIR', RIGHT: 'DERECHA', @@ -259,6 +264,7 @@ export const dictionary: RawTranslationPackage = { XOR: 'XOR', YEAR: 'AÑO', YEARFRAC: 'FRAC.AÑO', + YIELDDISC: 'RENDTO.DESC', ROMAN: 'NUMERO.ROMANO', ARABIC: 'ARABIC', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index 99ff05708f..92ae19c978 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ITSEISARVO', + ACCRINTM: 'KERTYNYT.KORKO.LOPUSSA', ACOS: 'ACOS', ACOSH: 'ACOSH', ACOT: 'ACOT', @@ -96,6 +97,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'DESIMAALI', DEGREES: 'ASTEET', DELTA: 'SAMA.ARVO', + DISC: 'DISKONTTOKORKO', DOLLARDE: 'VALUUTTA.DES', DOLLARFR: 'VALUUTTA.MURTO', EDATE: 'PÄIVÄ.KUUKAUSI', @@ -124,6 +126,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEKSI', INT: 'KOKONAISLUKU', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'KORKO.ARVOPAPERI', IPMT: 'IPMT', IRR: 'SISÄINEN.KORKO', ISBINARY: 'ISBINARY', @@ -184,11 +187,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'TULO', POWER: 'POTENSSI', PPMT: 'PPMT', + PRICEDISC: 'HINTA.DISK', PROPER: 'ERISNIMI', PV: 'NA', RADIANS: 'RADIAANIT', RAND: 'SATUNNAISLUKU', RATE: 'KORKO', + RECEIVED: 'SAATU.HINTA', REPLACE: 'KORVAA', REPT: 'TOISTA', RIGHT: 'OIKEA', @@ -259,6 +264,7 @@ const dictionary: RawTranslationPackage = { XOR: 'EHDOTON.TAI', YEAR: 'VUOSI', YEARFRAC: 'VUOSI.OSA', + YIELDDISC: 'TUOTTO.DISK', ROMAN: 'ROMAN', ARABIC: 'ARABIA', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index f7b9be45f7..46ddc3341a 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ABS', + ACCRINTM: 'INTERET.ACC.MAT', ACOS: 'ACOS', ACOSH: 'ACOSH', ACOT: 'ACOT', @@ -96,6 +97,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'DECIMAL', DEGREES: 'DEGRES', DELTA: 'DELTA', + DISC: 'TAUX.ESCOMPTE', DOLLARDE: 'PRIX.DEC', DOLLARFR: 'PRIX.FRAC', EDATE: 'MOIS.DECALER', @@ -124,6 +126,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEX', INT: 'ENT', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'TAUX.INTERET', IPMT: 'INTPER', IRR: 'TRI', ISBINARY: 'ISBINARY', @@ -184,11 +187,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'PRODUIT', POWER: 'PUISSANCE', PPMT: 'PRINCPER', + PRICEDISC: 'VALEUR.ENCAISSEMENT', PROPER: 'NOMPROPRE', PV: 'VA', RADIANS: 'RADIANS', RAND: 'ALEA', RATE: 'TAUX', + RECEIVED: 'VALEUR.NOMINALE', REPLACE: 'REMPLACER', REPT: 'REPT', RIGHT: 'DROITE', @@ -259,6 +264,7 @@ const dictionary: RawTranslationPackage = { XOR: 'OUX', YEAR: 'ANNEE', YEARFRAC: 'FRACTION.ANNEE', + YIELDDISC: 'RENDEMENT.SIMPLE', ROMAN: 'ROMAIN', ARABIC: 'ARABE', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index e40219553a..be10141f63 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ABS', + ACCRINTM: 'LEJÁRATI.KAMAT', ACOS: 'ARCCOS', ACOSH: 'ARCCOSH', ACOT: 'ARCCOT', @@ -96,6 +97,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'TIZEDES', DEGREES: 'FOK', DELTA: 'DELTA', + DISC: 'LESZÁM', DOLLARDE: 'FORINT.DEC', DOLLARFR: 'FORINT.TÖRT', EDATE: 'KALK.DÁTUM', @@ -124,6 +126,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEX', INT: 'INT', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'KAMATRÁTA', IPMT: 'RRÉSZLET', IRR: 'BMR', ISBINARY: 'ISBINARY', @@ -184,11 +187,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'SZORZAT', POWER: 'HATVÁNY', PPMT: 'PRÉSZLET', + PRICEDISC: 'ÁR.LESZÁM', PROPER: 'TNÉV', PV: 'MÉ', RADIANS: 'RADIÁN', RAND: 'VÉL', RATE: 'RÁTA', + RECEIVED: 'KAPOTT', REPLACE: 'CSERE', REPT: 'SOKSZOR', RIGHT: 'JOBB', @@ -259,6 +264,7 @@ const dictionary: RawTranslationPackage = { XOR: 'XVAGY', YEAR: 'ÉV', YEARFRAC: 'TÖRTÉV', + YIELDDISC: 'HOZAM.LESZÁM', ROMAN: 'RÓMAI', ARABIC: 'ARAB', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/idID.ts b/src/i18n/languages/idID.ts index 719cb11db6..362748cb91 100644 --- a/src/i18n/languages/idID.ts +++ b/src/i18n/languages/idID.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'BATASAN.MATRIKS', ARRAYFORMULA: 'RUMUS.MATRIKS', ABS: 'ABS', + ACCRINTM: 'ACCRINTM', ACOS: 'ACOS', ACOSH: 'ACOSH', ACOT: 'ACOT', @@ -97,6 +98,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'DESIMAL', DEGREES: 'DERAJAT', DELTA: 'DELTA', + DISC: 'DISC', DOLLARDE: 'DOLAR.DES', DOLLARFR: 'DOLAR.PEC', EDATE: 'EDATE', @@ -125,6 +127,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEKS', INT: 'BULAT.BAWAH', INTERVAL: 'INTERVAL', + INTRATE: 'INTRATE', IPMT: 'IPMT', IRR: 'IRR', ISBINARY: 'ADALAH.BINER', @@ -185,11 +188,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'PRODUK', POWER: 'PANGKAT', PPMT: 'PPMT', + PRICEDISC: 'PRICEDISC', PROPER: 'HURUF.AWAL.BESAR', PV: 'PV', RADIANS: 'RADIAN', RAND: 'ACAK', RATE: 'SUKU.BUNGA', + RECEIVED: 'RECEIVED', REPLACE: 'GANTI', REPT: 'ULANG', RIGHT: 'KANAN', @@ -261,6 +266,7 @@ const dictionary: RawTranslationPackage = { XLOOKUP: 'XLOOKUP', YEAR: 'TAHUN', YEARFRAC: 'FRAC.TAHUN', + YIELDDISC: 'YIELDDISC', 'HF.ADD': 'HF.ADD', 'HF.CONCAT': 'HF.CONCAT', 'HF.DIVIDE': 'HF.DIVIDE', diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index 1a494628f8..661d93f61d 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ASS', + ACCRINTM: 'INT.MATURATO.SCAD', ACOS: 'ARCCOS', ACOSH: 'ARCCOSH', ACOT: 'ARCCOT', @@ -96,6 +97,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'DECIMALE', DEGREES: 'GRADI', DELTA: 'DELTA', + DISC: 'TASSO.SCONTO', DOLLARDE: 'VALUTA.DEC', DOLLARFR: 'VALUTA.FRAZ', EDATE: 'DATA.MESE', @@ -124,6 +126,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDICE', INT: 'INT', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'TASSO.INT', IPMT: 'INTERESSI', IRR: 'TIR.COST', ISBINARY: 'ISBINARY', @@ -184,11 +187,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'PRODOTTO', POWER: 'POTENZA', PPMT: 'P.RATA', + PRICEDISC: 'PREZZO.SCONT', PROPER: 'MAIUSC.INIZ', PV: 'VA', RADIANS: 'RADIANTI', RAND: 'CASUALE', RATE: 'TASSO', + RECEIVED: 'RICEV.SCAD', REPLACE: 'RIMPIAZZA', REPT: 'RIPETI', RIGHT: 'DESTRA', @@ -259,6 +264,7 @@ const dictionary: RawTranslationPackage = { XOR: 'XOR', YEAR: 'ANNO', YEARFRAC: 'FRAZIONE.ANNO', + YIELDDISC: 'REND.TITOLI.SCONT', ROMAN: 'ROMANO', ARABIC: 'ARABO', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index ff15415a98..f3049f460a 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ABS', + ACCRINTM: 'PÅLØPT.FORFALLSRENTE', ACOS: 'ARCCOS', ACOSH: 'ARCCOSH', ACOT: 'ACOT', @@ -96,6 +97,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'DESIMAL', DEGREES: 'GRADER', DELTA: 'DELTA', + DISC: 'DISKONTERT', DOLLARDE: 'DOLLARDE', DOLLARFR: 'DOLLARBR', EDATE: 'DAG.ETTER', @@ -124,6 +126,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEKS', INT: 'HELTALL', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'RENTESATS', IPMT: 'RAVDRAG', IRR: 'IR', ISBINARY: 'ISBINARY', @@ -184,11 +187,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'PRODUKT', POWER: 'OPPHØYD.I', PPMT: 'AMORT', + PRICEDISC: 'PRIS.DISKONTERT', PROPER: 'STOR.FORBOKSTAV', PV: 'NÅVERDI', RADIANS: 'RADIANER', RAND: 'TILFELDIG', RATE: 'RENTE', + RECEIVED: 'MOTTATT.AVKAST', REPLACE: 'ERSTATT', REPT: 'GJENTA', RIGHT: 'HØYRE', @@ -259,6 +264,7 @@ const dictionary: RawTranslationPackage = { XOR: 'EKSKLUSIVELLER', YEAR: 'ÅR', YEARFRAC: 'ÅRDEL', + YIELDDISC: 'AVKAST.DISKONTERT', ROMAN: 'ROMERTALL', ARABIC: 'ARABISK', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index c491e2f44b..fa876c35df 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ABS', + ACCRINTM: 'SAMENG.RENTE.V', ACOS: 'BOOGCOS', ACOSH: 'BOOGCOSH', ACOT: 'BOOGCOT', @@ -96,6 +97,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'DECIMAAL', DEGREES: 'GRADEN', DELTA: 'DELTA', + DISC: 'DISCONTO', DOLLARDE: 'EURO.DE', DOLLARFR: 'EURO.BR', EDATE: 'ZELFDE.DAG', @@ -124,6 +126,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEX', INT: 'INTEGER', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'RENTEPERCENTAGE', IPMT: 'IBET', IRR: 'IR', ISBINARY: 'ISBINARY', @@ -184,11 +187,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'PRODUCT', POWER: 'MACHT', PPMT: 'PBET', + PRICEDISC: 'PRIJS.DISCONTO', PROPER: 'BEGINLETTERS', PV: 'HW', RADIANS: 'RADIALEN', RAND: 'ASELECT', RATE: 'RENTE', + RECEIVED: 'OPBRENGST', REPLACE: 'VERVANGEN', REPT: 'HERHALING', RIGHT: 'RECHTS', @@ -259,6 +264,7 @@ const dictionary: RawTranslationPackage = { XOR: 'EX.OF', YEAR: 'JAAR', YEARFRAC: 'JAAR.DEEL', + YIELDDISC: 'REND.DISCONTO', ROMAN: 'ROMEINS', ARABIC: 'ARABISCH', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index 6fa4f016b4..73942b01c9 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'WART.BEZWGL', + ACCRINTM: 'NAL.ODS.WYKUP', ACOS: 'ACOS', ACOSH: 'ACOSH', ACOT: 'ACOT', @@ -96,6 +97,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'DZIESIĘTNA', DEGREES: 'STOPNIE', DELTA: 'DELTA', + DISC: 'STOPA.DYSK', DOLLARDE: 'CENA.DZIES', DOLLARFR: 'CENA.UŁAM', EDATE: 'NR.SER.DATY', @@ -124,6 +126,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEKS', INT: 'ZAOKR.DO.CAŁK', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'STOPA.PROC', IPMT: 'IPMT', IRR: 'IRR', ISBINARY: 'ISBINARY', @@ -184,11 +187,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'ILOCZYN', POWER: 'POTĘGA', PPMT: 'PPMT', + PRICEDISC: 'CENA.DYSK', PROPER: 'Z.WIELKIEJ.LITERY', PV: 'PV', RADIANS: 'RADIANY', RAND: 'LOSUJ', RATE: 'RATE', + RECEIVED: 'KWOTA.WYKUP', REPLACE: 'ZASTĄP', REPT: 'POWT', RIGHT: 'PRAWY', @@ -259,6 +264,7 @@ const dictionary: RawTranslationPackage = { XOR: 'XOR', YEAR: 'ROK', YEARFRAC: 'CZĘŚĆ.ROKU', + YIELDDISC: 'RENT.DYSK', ROMAN: 'RZYMSKIE', ARABIC: 'ARABSKA', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index 6c1e68c328..3928f321a1 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ABS', + ACCRINTM: 'JUROSACUMV', ACOS: 'ACOS', ACOSH: 'ACOSH', ACOT: 'ACOT', @@ -96,6 +97,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'DECIMAL', DEGREES: 'GRAUS', DELTA: 'DELTA', + DISC: 'DESC', DOLLARDE: 'MOEDADEC', DOLLARFR: 'MOEDAFRA', EDATE: 'DATAM', @@ -124,6 +126,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'ÍNDICE', INT: 'INT', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'TAXAJUROS', IPMT: 'IPGTO', IRR: 'TIR', ISBINARY: 'ISBINARY', @@ -184,11 +187,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'MULT', POWER: 'POTÊNCIA', PPMT: 'PPGTO', + PRICEDISC: 'PREÇODESC', PROPER: 'PRI.MAIÚSCULA', PV: 'VP', RADIANS: 'RADIANOS', RAND: 'ALEATÓRIO', RATE: 'TAXA', + RECEIVED: 'RECEBER', REPLACE: 'MUDAR', REPT: 'REPT', RIGHT: 'DIREITA', @@ -259,6 +264,7 @@ const dictionary: RawTranslationPackage = { XOR: 'OUEXCL', YEAR: 'ANO', YEARFRAC: 'FRAÇÃOANO', + YIELDDISC: 'LUCRODESC', ROMAN: 'ROMANO', ARABIC: 'CARDINAL', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index 6b35c1ce2f..17f8d6e854 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ABS', + ACCRINTM: 'НАКОПДОХОДПОГАШ', ACOS: 'ACOS', ACOSH: 'ACOSH', ACOT: 'ACOT', @@ -96,6 +97,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'ДЕС', DEGREES: 'ГРАДУСЫ', DELTA: 'ДЕЛЬТА', + DISC: 'СКИДКА', DOLLARDE: 'РУБЛЬ.ДЕС', DOLLARFR: 'РУБЛЬ.ДРОБЬ', EDATE: 'ДАТАМЕС', @@ -124,6 +126,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'ИНДЕКС', INT: 'ЦЕЛОЕ', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'ИНОРМА', IPMT: 'ПРПЛТ', IRR: 'ВСД', ISBINARY: 'ISBINARY', @@ -184,11 +187,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'ПРОИЗВЕД', POWER: 'СТЕПЕНЬ', PPMT: 'ОСПЛТ', + PRICEDISC: 'ЦЕНАСКИДКА', PROPER: 'ПРОПНАЧ', PV: 'ПС', RADIANS: 'РАДИАНЫ', RAND: 'СЛЧИС', RATE: 'СТАВКА', + RECEIVED: 'ПОЛУЧЕНО', REPLACE: 'ЗАМЕНИТЬ', REPT: 'ПОВТОР', RIGHT: 'ПРАВСИМВ', @@ -259,6 +264,7 @@ const dictionary: RawTranslationPackage = { XOR: 'ИСКЛИЛИ', YEAR: 'ГОД', YEARFRAC: 'ДОЛЯГОДА', + YIELDDISC: 'ДОХОДСКИДКА', ROMAN: 'РИМСКОЕ', ARABIC: 'АРАБСКОЕ', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index 5f87580087..21ac96033f 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'ABS', + ACCRINTM: 'UPPLOBLRÄNTA', ACOS: 'ARCCOS', ACOSH: 'ARCCOSH', ACOT: 'ACOT', @@ -96,6 +97,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'DECIMAL', DEGREES: 'GRADER', DELTA: 'DELTA', + DISC: 'DISK', DOLLARDE: 'DECTAL', DOLLARFR: 'BRÅK', EDATE: 'EDATUM', @@ -124,6 +126,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'INDEX', INT: 'HELTAL', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'ÅRSRÄNTA', IPMT: 'RBETALNING', IRR: 'IR', ISBINARY: 'ISBINARY', @@ -184,11 +187,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'PRODUKT', POWER: 'UPPHÖJT.TILL', PPMT: 'AMORT', + PRICEDISC: 'PRISDISK', PROPER: 'INITIAL', PV: 'NUVÄRDE', RADIANS: 'RADIANER', RAND: 'SLUMP', RATE: 'RÄNTA', + RECEIVED: 'BELOPP', REPLACE: 'ERSÄTT', REPT: 'REP', RIGHT: 'HÖGER', @@ -259,6 +264,7 @@ const dictionary: RawTranslationPackage = { XOR: 'XOR', YEAR: 'ÅR', YEARFRAC: 'ÅRDEL', + YIELDDISC: 'NOMAVKDISK', ROMAN: 'ROMERSK', ARABIC: 'ARABIC', 'HF.ADD': 'HF.ADD', diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index 93c2176d37..dc5a53cbb2 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -25,6 +25,7 @@ const dictionary: RawTranslationPackage = { 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', ABS: 'MUTLAK', + ACCRINTM: 'GERÇEKFAİZV', ACOS: 'ACOS', ACOSH: 'ACOSH', ACOT: 'ACOT', @@ -96,6 +97,7 @@ const dictionary: RawTranslationPackage = { DECIMAL: 'ONDALIK', DEGREES: 'DERECE', DELTA: 'DELTA', + DISC: 'İNDİRİM', DOLLARDE: 'LİRAON', DOLLARFR: 'LİRAKES', EDATE: 'SERİTARİH', @@ -124,6 +126,7 @@ const dictionary: RawTranslationPackage = { INDEX: 'İNDİS', INT: 'TAMSAYI', INTERVAL: 'INTERVAL', //FIXME + INTRATE: 'FAİZORANI', IPMT: 'FAİZTUTARI', IRR: 'İÇ_VERİM_ORANI', ISBINARY: 'ISBINARY', @@ -184,11 +187,13 @@ const dictionary: RawTranslationPackage = { PRODUCT: 'ÇARPIM', POWER: 'KUVVET', PPMT: 'ANA_PARA_ÖDEMESİ', + PRICEDISC: 'DEĞERİND', PROPER: 'YAZIM.DÜZENİ', PV: 'BD', RADIANS: 'RADYAN', RAND: 'S_SAYI_ÜRET', RATE: 'FAİZ_ORANI', + RECEIVED: 'GETİRİ', REPLACE: 'DEĞİŞTİR', REPT: 'YİNELE', RIGHT: 'SAĞ', @@ -259,6 +264,7 @@ const dictionary: RawTranslationPackage = { XOR: 'ÖZELVEYA', YEAR: 'YIL', YEARFRAC: 'YILORAN', + YIELDDISC: 'ÖDEMEİND', ROMAN: 'ROMEN', ARABIC: 'ARAPÇA', 'HF.ADD': 'HF.ADD', diff --git a/src/interpreter/functionMetadata/categories/financial.ts b/src/interpreter/functionMetadata/categories/financial.ts index f673c11953..febe1ab7c7 100644 --- a/src/interpreter/functionMetadata/categories/financial.ts +++ b/src/interpreter/functionMetadata/categories/financial.ts @@ -10,6 +10,13 @@ import {FunctionDoc} from '../FunctionDescription' * truth for the function metadata API, and `docs/guide/built-in-functions.md` is generated from it. */ export const FINANCIAL_DOCS: Record = { + ACCRINTM: { + category: 'Financial', + shortDescription: 'Returns the accrued interest of a security that pays interest at maturity.', + parameters: [{name: 'issue', description: 'The issue date, as a date serial number, truncated to an integer.'}, {name: 'settlement', description: 'The settlement date, as a date serial number, truncated to an integer; must not be earlier than `issue` (equal dates give 0).'}, {name: 'rate', description: 'The annual coupon rate; must be greater than 0.'}, {name: 'par', description: 'The par value; must be greater than 0. If the argument is omitted or empty, 1000 is used.'}, {name: 'basis', description: 'The day-count basis, truncated to an integer: 0 (default) US 30/360, 1 actual/actual, 2 actual/360, 3 actual/365, 4 European 30/360.'}], + documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', + examples: ['=ACCRINTM(DATE(2008,4,1), DATE(2008,6,15), 0.1, 1000, 3)', '=ACCRINTM(A1, B1, 0.05)'], + }, CUMIPMT: { category: 'Financial', shortDescription: 'Returns the cumulative interest paid on a loan between a start period and an end period.', @@ -38,6 +45,13 @@ export const FINANCIAL_DOCS: Record = { documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', examples: ['=DDB(10000, 1000, 6, 2)', '=DDB(10000, 1000, 6, 1, 1.5)'], }, + DISC: { + category: 'Financial', + shortDescription: 'Returns the discount rate of a security.', + parameters: [{name: 'settlement', description: 'The settlement date, as a date serial number, truncated to an integer.'}, {name: 'maturity', description: 'The maturity date, as a date serial number, truncated to an integer; must be later than `settlement`.'}, {name: 'pr', description: 'The price per $100 face value; must be greater than 0.'}, {name: 'redemption', description: 'The redemption value per $100 face value; must be greater than 0.'}, {name: 'basis', description: 'The day-count basis, truncated to an integer: 0 (default) US 30/360, 1 actual/actual, 2 actual/360, 3 actual/365, 4 European 30/360.'}], + documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', + examples: ['=DISC(DATE(2018,7,1), DATE(2048,1,1), 97.975, 100, 1)', '=DISC(A1, B1, 99, 100)'], + }, DOLLARDE: { category: 'Financial', shortDescription: 'Converts a price entered with a special notation to a price displayed as a decimal number.', @@ -73,6 +87,13 @@ export const FINANCIAL_DOCS: Record = { documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', examples: ['=FVSCHEDULE(1000, A1:A3)', '=FVSCHEDULE(5000, B2:B5)'], }, + INTRATE: { + category: 'Financial', + shortDescription: 'Returns the interest rate of a fully invested security.', + parameters: [{name: 'settlement', description: 'The settlement date, as a date serial number, truncated to an integer.'}, {name: 'maturity', description: 'The maturity date, as a date serial number, truncated to an integer; must be later than `settlement`.'}, {name: 'investment', description: 'The amount invested; must be greater than 0.'}, {name: 'redemption', description: 'The amount received at `maturity`; must be greater than 0.'}, {name: 'basis', description: 'The day-count basis, truncated to an integer: 0 (default) US 30/360, 1 actual/actual, 2 actual/360, 3 actual/365, 4 European 30/360.'}], + documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', + examples: ['=INTRATE(DATE(2008,2,15), DATE(2008,5,15), 1000000, 1014420, 2)', '=INTRATE(A1, B1, 1000, 1050)'], + }, IPMT: { category: 'Financial', shortDescription: 'Returns the interest portion of a given loan payment in a given payment period.', @@ -143,6 +164,13 @@ export const FINANCIAL_DOCS: Record = { documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', examples: ['=PPMT(0.05/12, 1, 60, 20000)', '=PPMT(0.04/12, 12, 360, 200000, 0, 1)'], }, + PRICEDISC: { + category: 'Financial', + shortDescription: 'Returns the price per $100 face value of a discounted security.', + parameters: [{name: 'settlement', description: 'The settlement date, as a date serial number, truncated to an integer.'}, {name: 'maturity', description: 'The maturity date, as a date serial number, truncated to an integer; must be later than `settlement`.'}, {name: 'discount', description: 'The discount rate; must be greater than 0. A large `discount` gives a zero or negative price.'}, {name: 'redemption', description: 'The redemption value per $100 face value; must be greater than 0.'}, {name: 'basis', description: 'The day-count basis, truncated to an integer: 0 (default) US 30/360, 1 actual/actual, 2 actual/360, 3 actual/365, 4 European 30/360.'}], + documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', + examples: ['=PRICEDISC(DATE(2008,2,16), DATE(2008,3,1), 0.0525, 100, 2)', '=PRICEDISC(A1, B1, 0.05, 100)'], + }, PV: { category: 'Financial', shortDescription: 'Returns the present value of an investment.', @@ -157,6 +185,13 @@ export const FINANCIAL_DOCS: Record = { documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', examples: ['=RATE(60, -100, 5000)', '=RATE(360, -1000, 200000, 0, 0, 0.05)'], }, + RECEIVED: { + category: 'Financial', + shortDescription: 'Returns the amount received at maturity for a fully invested security.', + parameters: [{name: 'settlement', description: 'The settlement date, as a date serial number, truncated to an integer.'}, {name: 'maturity', description: 'The maturity date, as a date serial number, truncated to an integer; must be later than `settlement`.'}, {name: 'investment', description: 'The amount invested; must be greater than 0.'}, {name: 'discount', description: 'The discount rate; must be greater than 0. If `discount` multiplied by the year fraction from `settlement` to `maturity` is 1 or more, the result is #NUM!.'}, {name: 'basis', description: 'The day-count basis, truncated to an integer: 0 (default) US 30/360, 1 actual/actual, 2 actual/360, 3 actual/365, 4 European 30/360.'}], + documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', + examples: ['=RECEIVED(DATE(2008,2,15), DATE(2008,5,15), 1000000, 0.0575, 2)', '=RECEIVED(A1, B1, 1000, 0.05)'], + }, RRI: { category: 'Financial', shortDescription: 'Returns an equivalent interest rate for the growth of an investment.', @@ -213,4 +248,11 @@ export const FINANCIAL_DOCS: Record = { documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', examples: ['=XIRR(A1:A4, B1:B4)', '=XIRR(A1:A5, B1:B5, 0.1)'], }, + YIELDDISC: { + category: 'Financial', + shortDescription: 'Returns the annual yield of a discounted security.', + parameters: [{name: 'settlement', description: 'The settlement date, as a date serial number, truncated to an integer.'}, {name: 'maturity', description: 'The maturity date, as a date serial number, truncated to an integer; must be later than `settlement`.'}, {name: 'pr', description: 'The price per $100 face value; must be greater than 0.'}, {name: 'redemption', description: 'The redemption value per $100 face value; must be greater than 0.'}, {name: 'basis', description: 'The day-count basis, truncated to an integer: 0 (default) US 30/360, 1 actual/actual, 2 actual/360, 3 actual/365, 4 European 30/360.'}], + documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html', + examples: ['=YIELDDISC(DATE(2008,2,16), DATE(2008,3,1), 99.795, 100, 2)', '=YIELDDISC(A1, B1, 99, 100)'], + }, } diff --git a/src/interpreter/plugin/DateTimePlugin.ts b/src/interpreter/plugin/DateTimePlugin.ts index fd1d3fad01..366c5a0db7 100644 --- a/src/interpreter/plugin/DateTimePlugin.ts +++ b/src/interpreter/plugin/DateTimePlugin.ts @@ -532,22 +532,8 @@ export class DateTimePlugin extends FunctionPlugin implements FunctionPluginType if (startDate > endDate) { [startDate, endDate] = [endDate, startDate] } - switch (mode) { - case 0: - return this.days360Core(startDate, endDate, false) / 360 - case 1: - return (endDate - startDate) / this.dateTimeHelper.yearLengthForBasis( - this.dateTimeHelper.numberToSimpleDate(startDate), - this.dateTimeHelper.numberToSimpleDate(endDate) - ) - case 2: - return (endDate - startDate) / 360 - case 3: - return (endDate - startDate) / 365 - case 4: - return this.days360Core(startDate, endDate, true) / 360 - } - throw new Error('Should not be reachable.') + const {dayCount, yearDays} = this.dateTimeHelper.dayCountByBasis(startDate, endDate, mode) + return dayCount / yearDays } ) } diff --git a/src/interpreter/plugin/FinancialPlugin.ts b/src/interpreter/plugin/FinancialPlugin.ts index c1f0c782a8..a821df544e 100644 --- a/src/interpreter/plugin/FinancialPlugin.ts +++ b/src/interpreter/plugin/FinancialPlugin.ts @@ -240,6 +240,66 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp ], returnNumberType: NumberType.NUMBER_PERCENT }, + 'DISC': { + method: 'disc', + parameters: [ + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR, defaultValue: 0}, + ], + }, + 'INTRATE': { + method: 'intrate', + parameters: [ + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR, defaultValue: 0}, + ], + }, + 'PRICEDISC': { + method: 'pricedisc', + parameters: [ + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR, defaultValue: 0}, + ], + }, + 'RECEIVED': { + method: 'received', + parameters: [ + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR, defaultValue: 0}, + ], + }, + 'YIELDDISC': { + method: 'yielddisc', + parameters: [ + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR, defaultValue: 0}, + ], + }, + 'ACCRINTM': { + method: 'accrintm', + parameters: [ + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR}, + {argumentType: FunctionArgumentType.SCALAR, defaultValue: 1000, emptyAsDefault: true}, + {argumentType: FunctionArgumentType.SCALAR, defaultValue: 0}, + ], + }, 'FVSCHEDULE': { method: 'fvschedule', parameters: [ @@ -649,6 +709,128 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp ) } + /** + * Corresponds to DISC(settlement, maturity, pr, redemption, [basis]). + * + * Returns the discount rate of a security. + * + * @param ast + * @param state + */ + public disc(ast: ProcedureAst, state: InterpreterState): InterpreterValue { + return this.discountedSecurity(ast, state, 'DISC', + (dayCount: number, yearDays: number, price: number, redemption: number) => (1 - price / redemption) * (yearDays / dayCount) + ) + } + + /** + * Corresponds to INTRATE(settlement, maturity, investment, redemption, [basis]). + * + * Returns the interest rate of a fully invested security. + * + * @param ast + * @param state + */ + public intrate(ast: ProcedureAst, state: InterpreterState): InterpreterValue { + return this.discountedSecurity(ast, state, 'INTRATE', + (dayCount: number, yearDays: number, investment: number, redemption: number) => (redemption - investment) / investment * (yearDays / dayCount) + ) + } + + /** + * Corresponds to PRICEDISC(settlement, maturity, discount, redemption, [basis]). + * + * Returns the price per $100 face value of a discounted security. + * + * @param ast + * @param state + */ + public pricedisc(ast: ProcedureAst, state: InterpreterState): InterpreterValue { + return this.discountedSecurity(ast, state, 'PRICEDISC', + (dayCount: number, yearDays: number, discount: number, redemption: number) => redemption - discount * redemption * (dayCount / yearDays) + ) + } + + /** + * Corresponds to RECEIVED(settlement, maturity, investment, discount, [basis]). + * + * Returns the amount received at maturity for a fully invested security. + * Returns #NUM! when the discount for the whole period reaches the investment, that is when + * `discount * dayCount / yearDays` is 1 or more. + * + * @param ast + * @param state + */ + public received(ast: ProcedureAst, state: InterpreterState): InterpreterValue { + return this.discountedSecurity(ast, state, 'RECEIVED', + (dayCount: number, yearDays: number, investment: number, discount: number) => { + const denominator = 1 - discount * (dayCount / yearDays) + if (denominator <= 0) { + return new CellError(ErrorType.NUM, ErrorMessage.ValueLarge) + } + return investment / denominator + } + ) + } + + /** + * Corresponds to YIELDDISC(settlement, maturity, pr, redemption, [basis]). + * + * Returns the annual yield of a discounted security. + * + * @param ast + * @param state + */ + public yielddisc(ast: ProcedureAst, state: InterpreterState): InterpreterValue { + return this.discountedSecurity(ast, state, 'YIELDDISC', + (dayCount: number, yearDays: number, price: number, redemption: number) => (redemption - price) / price * (yearDays / dayCount) + ) + } + + /** + * Corresponds to ACCRINTM(issue, settlement, rate, [par], [basis]). + * + * Returns the accrued interest of a security that pays interest at maturity. An empty or omitted `par` is 1000. + * Arguments are validated from left to right, as in Excel. `issue` equal to `settlement` gives 0. + * + * @param ast + * @param state + */ + public accrintm(ast: ProcedureAst, state: InterpreterState): InterpreterValue { + return this.runFunction(ast.args, state, this.metadata('ACCRINTM'), + (issueArg: InternalScalarValue, settlementArg: InternalScalarValue, rateArg: InternalScalarValue, parArg: InternalScalarValue, basisArg: InternalScalarValue) => { + const issue = this.coerceToSecurityDate(issueArg) + if (issue instanceof CellError) { + return issue + } + const settlement = this.coerceToSecurityDate(settlementArg) + if (settlement instanceof CellError) { + return settlement + } + const rate = this.strictNumber(rateArg) + if (rate instanceof CellError) { + return rate + } + const par = this.strictNumber(parArg) + if (par instanceof CellError) { + return par + } + const basis = this.coerceToDayCountBasis(basisArg) + if (basis instanceof CellError) { + return basis + } + if (issue > settlement) { + return new CellError(ErrorType.NUM, ErrorMessage.StartEndDate) + } + if (rate <= 0 || par <= 0) { + return new CellError(ErrorType.NUM, ErrorMessage.ValueSmall) + } + const {dayCount, yearDays} = this.dateTimeHelper.dayCountByBasis(issue, settlement, basis) + return par * rate * (dayCount / yearDays) + } + ) + } + public fvschedule(ast: ProcedureAst, state: InterpreterState): InterpreterValue { return this.runFunction(ast.args, state, this.metadata('FVSCHEDULE'), (value: number, ratios: SimpleRangeValue) => { @@ -841,6 +1023,111 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp } ) } + + /** + * Runs DISC, INTRATE, PRICEDISC, RECEIVED and YIELDDISC, which share the arguments + * (settlement, maturity, first amount, second amount, [basis]). + * + * Validates in Excel's order: settlement and maturity, then basis, then the two amounts (errors and #VALUE!), + * then settlement before maturity and both amounts positive (#NUM!). Passes the day count and the days in the year + * between settlement and maturity to `calculate`. + * + * @param {ProcedureAst} ast - the function's AST + * @param {InterpreterState} state - the interpreter state + * @param {string} functionName - the function's id, used to look up its metadata + * @param {Function} calculate - computes the result from the day count, the days in the year and the two amounts + */ + private discountedSecurity( + ast: ProcedureAst, + state: InterpreterState, + functionName: string, + calculate: (dayCount: number, yearDays: number, firstAmount: number, secondAmount: number) => number | CellError, + ): InterpreterValue { + return this.runFunction(ast.args, state, this.metadata(functionName), + (settlementArg: InternalScalarValue, maturityArg: InternalScalarValue, firstAmountArg: InternalScalarValue, secondAmountArg: InternalScalarValue, basisArg: InternalScalarValue) => { + const settlement = this.coerceToSecurityDate(settlementArg) + if (settlement instanceof CellError) { + return settlement + } + const maturity = this.coerceToSecurityDate(maturityArg) + if (maturity instanceof CellError) { + return maturity + } + const basis = this.coerceToDayCountBasis(basisArg) + if (basis instanceof CellError) { + return basis + } + const firstAmount = this.strictNumber(firstAmountArg) + if (firstAmount instanceof CellError) { + return firstAmount + } + const secondAmount = this.strictNumber(secondAmountArg) + if (secondAmount instanceof CellError) { + return secondAmount + } + if (settlement >= maturity) { + return new CellError(ErrorType.NUM, ErrorMessage.StartEndDate) + } + if (firstAmount <= 0 || secondAmount <= 0) { + return new CellError(ErrorType.NUM, ErrorMessage.ValueSmall) + } + const {dayCount, yearDays} = this.dateTimeHelper.dayCountByBasis(settlement, maturity, basis) + return calculate(dayCount, yearDays, firstAmount, secondAmount) + } + ) + } + + /** + * Converts a date argument of a securities function: a number as `strictNumber` accepts it, truncated to an integer. + * A date outside the supported range is #NUM!, and so is a negative value even when it truncates to 0, as in Excel. + */ + private coerceToSecurityDate(value: InternalScalarValue): number | CellError { + const dateNumber = this.strictNumber(value) + if (dateNumber instanceof CellError) { + return dateNumber + } + if (dateNumber < 0) { + return new CellError(ErrorType.NUM, ErrorMessage.DateBounds) + } + const date = Math.trunc(dateNumber) + if (this.dateTimeHelper.getWithinBounds(date) === undefined) { + return new CellError(ErrorType.NUM, ErrorMessage.DateBounds) + } + return date + } + + /** + * Converts the day-count `basis` argument of a securities function: a number as `strictNumber` accepts it, from 0 + * to less than 5, truncated to an integer. A negative value is #NUM! even when it truncates to 0, as in Excel. + */ + private coerceToDayCountBasis(value: InternalScalarValue): number | CellError { + const basis = this.strictNumber(value) + if (basis instanceof CellError) { + return basis + } + if (basis < 0) { + return new CellError(ErrorType.NUM, ErrorMessage.ValueSmall) + } + if (basis >= 5) { + return new CellError(ErrorType.NUM, ErrorMessage.ValueLarge) + } + return Math.trunc(basis) + } + + /** + * Converts an argument the way Excel does for these functions: numbers, dates, numeric text and empty cells are + * accepted, while a boolean or the empty text is #VALUE!. Errors are passed on. + */ + private strictNumber(value: InternalScalarValue): number | CellError { + if (value instanceof CellError) { + return value + } + if (typeof value === 'boolean' || value === '') { + return new CellError(ErrorType.VALUE, ErrorMessage.NumberCoercion) + } + const coerced = this.coerceScalarToNumberOrError(value) + return coerced instanceof CellError ? coerced : getRawValue(coerced) + } } function pmtCore(rate: number, periods: number, present: number, future: number, type: number): number { diff --git a/src/license/functionCapabilities.ts b/src/license/functionCapabilities.ts index a26733ec5f..0161886524 100644 --- a/src/license/functionCapabilities.ts +++ b/src/license/functionCapabilities.ts @@ -75,28 +75,31 @@ const FUNCTION_GROUPS: ReadonlyMap = new Map([ * gated at all, which is exactly the treatment a custom function should get. */ const UNGROUPED_FUNCTIONS = [ - 'ACOSH', 'ACOT', 'ACOTH', 'ARABIC', 'ASINH', 'ATANH', 'AVEDEV', 'BASE', 'BESSELI', 'BESSELJ', 'BESSELK', + 'ACCRINTM', 'ACOSH', 'ACOT', 'ACOTH', 'ARABIC', 'ASINH', 'ATANH', 'AVEDEV', 'BASE', 'BESSELI', 'BESSELJ', + 'BESSELK', 'BESSELY', 'BETA.DIST', 'BETA.INV', 'BIN2DEC', 'BIN2HEX', 'BIN2OCT', 'BINOM.DIST', 'BINOM.INV', 'BITAND', 'BITLSHIFT', 'BITOR', 'BITRSHIFT', 'BITXOR', 'CEILING.MATH', 'CEILING.PRECISE', 'CHISQ.DIST', 'CHISQ.DIST.RT', 'CHISQ.INV', 'CHISQ.INV.RT', 'CHISQ.TEST', 'COMBIN', 'COMBINA', 'COMPLEX', 'CONFIDENCE.NORM', 'CONFIDENCE.T', 'CORREL', 'COSH', 'COT', 'COTH', 'COUNTUNIQUE', 'COVARIANCE.P', 'COVARIANCE.S', 'CSC', 'CSCH', 'CUMIPMT', 'CUMPRINC', 'DAVERAGE', 'DB', 'DCOUNT', 'DCOUNTA', 'DDB', - 'DEC2BIN', 'DEC2OCT', 'DECIMAL', 'DEGREES', 'DELTA', 'DEVSQ', 'DGET', 'DMAX', 'DMIN', 'DOLLARDE', + 'DEC2BIN', 'DEC2OCT', 'DECIMAL', 'DEGREES', 'DELTA', 'DEVSQ', 'DGET', 'DISC', 'DMAX', 'DMIN', 'DOLLARDE', 'DOLLARFR', 'DPRODUCT', 'DSTDEV', 'DSTDEVP', 'DSUM', 'DVAR', 'DVARP', 'EFFECT', 'ERF', 'ERFC', 'EXPON.DIST', 'F.DIST', 'F.DIST.RT', 'F.INV', 'F.INV.RT', 'F.TEST', 'FACT', 'FACTDOUBLE', 'FISHER', 'FISHERINV', 'FLOOR.MATH', 'FLOOR.PRECISE', 'FORMULATEXT', 'FVSCHEDULE', 'GAMMA', 'GAMMA.DIST', 'GAMMA.INV', 'GAMMALN', 'GAUSS', 'GCD', 'GEOMEAN', 'HARMEAN', 'HEX2BIN', 'HEX2OCT', 'HYPGEOM.DIST', 'IMABS', 'IMAGINARY', 'IMARGUMENT', 'IMCONJUGATE', 'IMCOS', 'IMCOSH', 'IMCOT', 'IMCSC', 'IMCSCH', 'IMDIV', 'IMEXP', 'IMLN', 'IMLOG10', 'IMLOG2', 'IMPOWER', 'IMPRODUCT', 'IMREAL', 'IMSEC', 'IMSECH', 'IMSIN', - 'IMSINH', 'IMSQRT', 'IMSUB', 'IMSUM', 'IMTAN', 'INTERVAL', 'ISBINARY', 'ISFORMULA', 'ISNONTEXT', 'ISPMT', + 'IMSINH', 'IMSQRT', 'IMSUB', 'IMSUM', 'IMTAN', 'INTERVAL', 'INTRATE', 'ISBINARY', 'ISFORMULA', + 'ISNONTEXT', 'ISPMT', 'ISREF', 'LCM', 'LOG10', 'LOGNORM.DIST', 'LOGNORM.INV', 'MAXA', 'MAXPOOL', 'MEDIANPOOL', 'MINA', 'MIRR', 'MMULT', 'MULTINOMIAL', 'NEGBINOM.DIST', 'NETWORKDAYS.INTL', 'NOMINAL', 'NORM.DIST', 'NORM.INV', 'NORM.S.DIST', 'NORM.S.INV', 'NPER', 'OCT2BIN', 'OCT2DEC', 'OCT2HEX', 'PDURATION', 'PERCENTILE.EXC', - 'PHI', 'POISSON.DIST', 'QUARTILE.EXC', 'QUARTILE.INC', 'RADIANS', 'ROMAN', 'RRI', 'RSQ', 'SEC', 'SECH', + 'PHI', 'POISSON.DIST', 'PRICEDISC', 'QUARTILE.EXC', 'QUARTILE.INC', 'RADIANS', 'RECEIVED', 'ROMAN', 'RRI', + 'RSQ', 'SEC', 'SECH', 'SERIESSUM', 'SHEET', 'SHEETS', 'SINH', 'SKEW', 'SKEW.P', 'SLOPE', 'SPLIT', 'SQRTPI', 'STANDARDIZE', 'STEYX', 'SUMX2MY2', 'SUMX2PY2', 'SYD', 'T.DIST', 'T.DIST.2T', 'T.DIST.RT', 'T.INV', 'T.INV.2T', 'T.TEST', 'TANH', 'TBILLEQ', 'TBILLPRICE', 'TBILLYIELD', 'TDIST', 'TIMEVALUE', 'UNICODE', 'VARA', 'VARPA', - 'WEIBULL.DIST', 'WORKDAY.INTL', 'Z.TEST', + 'WEIBULL.DIST', 'WORKDAY.INTL', 'YIELDDISC', 'Z.TEST', ] /**