From a9df9543f0a58104e9bcd8f37b154e3439db366e Mon Sep 17 00:00:00 2001 From: Dan Date: Sat, 16 Nov 2024 09:45:51 +0100 Subject: [PATCH] Fixes --- .../calculator/portfolio-calculator.ts | 250 +++++++++--------- package-lock.json | 49 ++-- 2 files changed, 141 insertions(+), 158 deletions(-) diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts index 30f6ec264..bb83b98a8 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts @@ -162,10 +162,6 @@ export abstract class PortfolioCalculator { this.snapshotPromise = this.initialize(); } - protected abstract calculateOverallPerformance( - positions: TimelinePosition[] - ): PortfolioSnapshot; - @LogPerformance public async computeSnapshot(): Promise { const lastTransactionPoint = last(this.transactionPoints); @@ -769,62 +765,6 @@ export abstract class PortfolioCalculator { return { chart }; } - @LogPerformance - public async getSnapshot() { - await this.snapshotPromise; - - return this.snapshot; - } - - public getStartDate() { - let firstAccountBalanceDate: Date; - let firstActivityDate: Date; - - try { - const firstAccountBalanceDateString = first( - this.accountBalanceItems - )?.date; - firstAccountBalanceDate = firstAccountBalanceDateString - ? parseDate(firstAccountBalanceDateString) - : new Date(); - } catch (error) { - firstAccountBalanceDate = new Date(); - } - - try { - const firstActivityDateString = this.transactionPoints[0].date; - firstActivityDate = firstActivityDateString - ? parseDate(firstActivityDateString) - : new Date(); - } catch (error) { - firstActivityDate = new Date(); - } - - return min([firstAccountBalanceDate, firstActivityDate]); - } - - protected abstract getSymbolMetrics({ - chartDateMap, - dataSource, - end, - exchangeRates, - marketSymbolMap, - start, - symbol - }: { - chartDateMap: { [date: string]: boolean }; - end: Date; - exchangeRates: { [dateString: string]: number }; - marketSymbolMap: { - [date: string]: { [symbol: string]: Big }; - }; - start: Date; - } & AssetProfileIdentifier): SymbolMetrics; - - public getTransactionPoints() { - return this.transactionPoints; - } - @LogPerformance public async getValuablesInBaseCurrency() { await this.snapshotPromise; @@ -832,72 +772,11 @@ export abstract class PortfolioCalculator { return this.snapshot.totalValuablesWithCurrencyEffect; } - private getChartDateMap({ - endDate, - startDate, - step - }: { - endDate: Date; - startDate: Date; - step: number; - }): { [date: string]: true } { - // Create a map of all relevant chart dates: - // 1. Add transaction point dates - const chartDateMap = this.transactionPoints.reduce((result, { date }) => { - result[date] = true; - return result; - }, {}); - - // 2. Add dates between transactions respecting the specified step size - for (const date of eachDayOfInterval( - { end: endDate, start: startDate }, - { step } - )) { - chartDateMap[format(date, DATE_FORMAT)] = true; - } - - if (step > 1) { - // Reduce the step size of last 90 days - for (const date of eachDayOfInterval( - { end: endDate, start: subDays(endDate, 90) }, - { step: 3 } - )) { - chartDateMap[format(date, DATE_FORMAT)] = true; - } - - // Reduce the step size of last 30 days - for (const date of eachDayOfInterval( - { end: endDate, start: subDays(endDate, 30) }, - { step: 1 } - )) { - chartDateMap[format(date, DATE_FORMAT)] = true; - } - } - - // Make sure the end date is present - chartDateMap[format(endDate, DATE_FORMAT)] = true; - - // Make sure some key dates are present - for (const dateRange of ['1d', '1y', '5y', 'max', 'mtd', 'wtd', 'ytd']) { - const { endDate: dateRangeEnd, startDate: dateRangeStart } = - getIntervalFromDateRange(dateRange); - - if ( - !isBefore(dateRangeStart, startDate) && - !isAfter(dateRangeStart, endDate) - ) { - chartDateMap[format(dateRangeStart, DATE_FORMAT)] = true; - } - - if ( - !isBefore(dateRangeEnd, startDate) && - !isAfter(dateRangeEnd, endDate) - ) { - chartDateMap[format(dateRangeEnd, DATE_FORMAT)] = true; - } - } + @LogPerformance + public async getSnapshot() { + await this.snapshotPromise; - return chartDateMap; + return this.snapshot; } @LogPerformance @@ -1120,4 +999,125 @@ export abstract class PortfolioCalculator { await this.initialize(); } } + + public getStartDate() { + let firstAccountBalanceDate: Date; + let firstActivityDate: Date; + + try { + const firstAccountBalanceDateString = first( + this.accountBalanceItems + )?.date; + firstAccountBalanceDate = firstAccountBalanceDateString + ? parseDate(firstAccountBalanceDateString) + : new Date(); + } catch (error) { + firstAccountBalanceDate = new Date(); + } + + try { + const firstActivityDateString = this.transactionPoints[0].date; + firstActivityDate = firstActivityDateString + ? parseDate(firstActivityDateString) + : new Date(); + } catch (error) { + firstActivityDate = new Date(); + } + + return min([firstAccountBalanceDate, firstActivityDate]); + } + + public getTransactionPoints() { + return this.transactionPoints; + } + + private getChartDateMap({ + endDate, + startDate, + step + }: { + endDate: Date; + startDate: Date; + step: number; + }): { [date: string]: true } { + // Create a map of all relevant chart dates: + // 1. Add transaction point dates + const chartDateMap = this.transactionPoints.reduce((result, { date }) => { + result[date] = true; + return result; + }, {}); + + // 2. Add dates between transactions respecting the specified step size + for (const date of eachDayOfInterval( + { end: endDate, start: startDate }, + { step } + )) { + chartDateMap[format(date, DATE_FORMAT)] = true; + } + + if (step > 1) { + // Reduce the step size of last 90 days + for (const date of eachDayOfInterval( + { end: endDate, start: subDays(endDate, 90) }, + { step: 3 } + )) { + chartDateMap[format(date, DATE_FORMAT)] = true; + } + + // Reduce the step size of last 30 days + for (const date of eachDayOfInterval( + { end: endDate, start: subDays(endDate, 30) }, + { step: 1 } + )) { + chartDateMap[format(date, DATE_FORMAT)] = true; + } + } + + // Make sure the end date is present + chartDateMap[format(endDate, DATE_FORMAT)] = true; + + // Make sure some key dates are present + for (const dateRange of ['1d', '1y', '5y', 'max', 'mtd', 'wtd', 'ytd']) { + const { endDate: dateRangeEnd, startDate: dateRangeStart } = + getIntervalFromDateRange(dateRange); + + if ( + !isBefore(dateRangeStart, startDate) && + !isAfter(dateRangeStart, endDate) + ) { + chartDateMap[format(dateRangeStart, DATE_FORMAT)] = true; + } + + if ( + !isBefore(dateRangeEnd, startDate) && + !isAfter(dateRangeEnd, endDate) + ) { + chartDateMap[format(dateRangeEnd, DATE_FORMAT)] = true; + } + } + + return chartDateMap; + } + + protected abstract getSymbolMetrics({ + chartDateMap, + dataSource, + end, + exchangeRates, + marketSymbolMap, + start, + symbol + }: { + chartDateMap: { [date: string]: boolean }; + end: Date; + exchangeRates: { [dateString: string]: number }; + marketSymbolMap: { + [date: string]: { [symbol: string]: Big }; + }; + start: Date; + } & AssetProfileIdentifier): SymbolMetrics; + + protected abstract calculateOverallPerformance( + positions: TimelinePosition[] + ): PortfolioSnapshot; } diff --git a/package-lock.json b/package-lock.json index 9445c23db..beaa52766 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8266,7 +8266,6 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", - "license": "MIT", "peer": true, "dependencies": { "tslib": "^2.0.0" @@ -8279,7 +8278,6 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.5.0.tgz", "integrity": "sha512-BRs5XUAwiyCDQMsVA9IDvDa7UBR9gAvPHgugOeGng3YN6vJ9JYonyDc0lNczErgtCWtucjR5N7VtaonboD/ezg==", - "license": "MIT", "peer": true, "dependencies": { "@peculiar/asn1-schema": "^2.3.8", @@ -8293,10 +8291,9 @@ } }, "node_modules/@peculiar/webcrypto/node_modules/tslib": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", - "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", - "license": "0BSD", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "peer": true }, "node_modules/@phenomnomnominal/tsquery": { @@ -13778,7 +13775,6 @@ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", "integrity": "sha512-wxXCdllwGhI2kCC0MnvTGYTMvnVZTvqgypkiTI8Pa5tcz2i6VqsqwYGgqwXji+4RgCzms6EajE4IxiUH6HH8nQ==", "dev": true, - "license": "MIT", "peer": true, "engines": { "node": ">=0.10.0" @@ -22021,7 +22017,6 @@ "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", "dev": true, - "license": "MIT", "optional": true, "peer": true, "dependencies": { @@ -22056,7 +22051,6 @@ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", "dev": true, - "license": "MIT", "optional": true, "peer": true, "dependencies": { @@ -23851,7 +23845,6 @@ "version": "3.7.1", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", - "license": "MIT", "peer": true }, "node_modules/js-sha256": { @@ -26594,10 +26587,9 @@ } }, "node_modules/msgpackr": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.0.tgz", - "integrity": "sha512-I8qXuuALqJe5laEBYoFykChhSXLikZmUhccjGsPuSJ/7uPip2TJ7lwdIQwWSAi0jGZDXv4WOP8Qg65QZRuXxXw==", - "license": "MIT", + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.2.tgz", + "integrity": "sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g==", "optionalDependencies": { "msgpackr-extract": "^3.0.2" } @@ -28579,7 +28571,6 @@ "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", - "license": "MIT", "peer": true, "funding": { "type": "opencollective", @@ -32851,11 +32842,11 @@ "license": "0BSD" }, "node_modules/tslint": { - "version": "5.20.1", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz", - "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", + "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", + "deprecated": "TSLint has been deprecated in favor of ESLint. Please see https://github.com/palantir/tslint/issues/4534 for more information.", "dev": true, - "license": "Apache-2.0", "peer": true, "dependencies": { "@babel/code-frame": "^7.0.0", @@ -32866,10 +32857,10 @@ "glob": "^7.1.1", "js-yaml": "^3.13.1", "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", + "mkdirp": "^0.5.3", "resolve": "^1.3.2", "semver": "^5.3.0", - "tslib": "^1.8.0", + "tslib": "^1.13.0", "tsutils": "^2.29.0" }, "bin": { @@ -32879,7 +32870,7 @@ "node": ">=4.8.0" }, "peerDependencies": { - "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev" + "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 4.0.0-dev" } }, "node_modules/tslint/node_modules/brace-expansion": { @@ -32887,7 +32878,6 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "balanced-match": "^1.0.0", @@ -32899,7 +32889,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "ISC", "peer": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -32913,7 +32902,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, - "license": "ISC", "peer": true, "bin": { "semver": "bin/semver" @@ -32924,7 +32912,6 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true, - "license": "0BSD", "peer": true }, "node_modules/tsscmp": { @@ -32942,7 +32929,6 @@ "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "tslib": "^1.8.1" @@ -32956,7 +32942,6 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true, - "license": "0BSD", "peer": true }, "node_modules/tuf-js": { @@ -34269,7 +34254,6 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.8.1.tgz", "integrity": "sha512-P+x1MvlNCXlKbLSOY4cYrdreqPG5hbzkmawbcXLKN/mf6DZW0SdNNkZ+sjwsqVkI4A4Ko2sPZmkZtCKY58w83A==", - "license": "MIT", "peer": true, "dependencies": { "@peculiar/asn1-schema": "^2.3.13", @@ -34280,10 +34264,9 @@ } }, "node_modules/webcrypto-core/node_modules/tslib": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", - "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", - "license": "0BSD", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "peer": true }, "node_modules/webidl-conversions": {