From b1e72dbbd75e27200f3c1b3c562c3ed9e8cc2600 Mon Sep 17 00:00:00 2001 From: Reto Kaul Date: Tue, 20 Aug 2024 22:16:31 +0200 Subject: [PATCH 1/2] Various fixes: * Fix net performance in "max" date range * Fix unit test --- .../calculator/portfolio-calculator.ts | 51 +++++++------------ .../twr/portfolio-calculator-baln-buy.spec.ts | 14 ++--- ...folio-calculator-novn-buy-and-sell.spec.ts | 15 ++++++ .../calculator/twr/portfolio-calculator.ts | 20 ++------ 4 files changed, 44 insertions(+), 56 deletions(-) diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts index 2c644f5fb..bd97e0dcc 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts @@ -36,7 +36,7 @@ import { min, subDays } from 'date-fns'; -import { first, last, sortBy, sum, uniq, uniqBy } from 'lodash'; +import { first, isNumber, last, sortBy, sum, uniq, uniqBy } from 'lodash'; export abstract class PortfolioCalculator { protected static readonly ENABLE_LOGGING = false; @@ -144,24 +144,11 @@ export abstract class PortfolioCalculator { positions: TimelinePosition[] ): PortfolioSnapshot; - private async computeSnapshot( - start: Date, - end?: Date - ): Promise { + private async computeSnapshot(): Promise { const lastTransactionPoint = last(this.transactionPoints); - let endDate = end; - - if (!endDate) { - endDate = new Date(); - - if (lastTransactionPoint) { - endDate = max([endDate, parseDate(lastTransactionPoint.date)]); - } - } - const transactionPoints = this.transactionPoints?.filter(({ date }) => { - return isBefore(parseDate(date), endDate); + return isBefore(parseDate(date), this.endDate); }); if (!transactionPoints.length) { @@ -208,7 +195,7 @@ export abstract class PortfolioCalculator { for (let i = 0; i < transactionPoints.length; i++) { if ( - !isBefore(parseDate(transactionPoints[i].date), start) && + !isBefore(parseDate(transactionPoints[i].date), this.startDate) && firstTransactionPoint === null ) { firstTransactionPoint = transactionPoints[i]; @@ -219,8 +206,8 @@ export abstract class PortfolioCalculator { let exchangeRatesByCurrency = await this.exchangeRateDataService.getExchangeRatesByCurrency({ currencies: uniq(Object.values(currencies)), - endDate: endOfDay(endDate), - startDate: this.getStartDate(), + endDate: endOfDay(this.endDate), + startDate: this.startDate, targetCurrency: this.currency }); @@ -231,8 +218,8 @@ export abstract class PortfolioCalculator { } = await this.currentRateService.getValues({ dataGatheringItems, dateQuery: { - gte: this.getStartDate(), - lt: endDate + gte: this.startDate, + lt: this.endDate } }); @@ -256,14 +243,13 @@ export abstract class PortfolioCalculator { } } - const endDateString = format(endDate, DATE_FORMAT); + const endDateString = format(this.endDate, DATE_FORMAT); - const chartStartDate = this.getStartDate(); - const daysInMarket = differenceInDays(endDate, chartStartDate) + 1; + const daysInMarket = differenceInDays(this.endDate, this.startDate); let chartDateMap = this.getChartDateMap({ - endDate, - startDate: chartStartDate, + endDate: this.endDate, + startDate: this.startDate, step: Math.round(daysInMarket / Math.min(daysInMarket, MAX_CHART_ITEMS)) }); @@ -357,12 +343,12 @@ export abstract class PortfolioCalculator { } = this.getSymbolMetrics({ chartDateMap, marketSymbolMap, - start, dataSource: item.dataSource, - end: endDate, + end: this.endDate, exchangeRates: exchangeRatesByCurrency[`${item.currency}${this.currency}`], isChartMode: true, + start: this.startDate, symbol: item.symbol }); @@ -710,7 +696,7 @@ export abstract class PortfolioCalculator { !isBefore(parseDate(historicalDataItem.date), subDays(start, 1)) && !isAfter(parseDate(historicalDataItem.date), end) ) { - if (!netPerformanceAtStartDate) { + if (!isNumber(netPerformanceAtStartDate)) { netPerformanceAtStartDate = historicalDataItem.netPerformance; netPerformanceWithCurrencyEffectAtStartDate = @@ -1039,10 +1025,7 @@ export abstract class PortfolioCalculator { 'PortfolioCalculator' ); } else { - this.snapshot = await this.computeSnapshot( - this.startDate, - this.endDate - ); + this.snapshot = await this.computeSnapshot(); this.redisCacheService.set( this.redisCacheService.getPortfolioSnapshotKey({ @@ -1061,7 +1044,7 @@ export abstract class PortfolioCalculator { ); } } else { - this.snapshot = await this.computeSnapshot(this.startDate, this.endDate); + this.snapshot = await this.computeSnapshot(); } } } diff --git a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy.spec.ts b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy.spec.ts index c7e3d74b2..e1504681d 100644 --- a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy.spec.ts +++ b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy.spec.ts @@ -149,13 +149,13 @@ describe('PortfolioCalculator', () => { }, netPerformanceWithCurrencyEffect: new Big('23.05'), netPerformanceWithCurrencyEffectMap: { - '1d': new Big('8.45'), // wrong - '1y': new Big('23.05'), // wrong - '5y': new Big('23.05'), // wrong - max: new Big('23.05'), // ok: 2 * (148.9 - 136.6) - 1.55 - mtd: new Big('23.05'), // wrong - wtd: new Big('12.25'), // wrong: 2 * (148.9 - 142) - 1.55 - ytd: new Big('23.05') // wrong + '1d': new Big('10.00'), // 2 * (148.9 - 143.9) -> no fees in this time period + '1y': new Big('23.05'), // 2 * (148.9 - 136.6) - 1.55 + '5y': new Big('23.05'), // 2 * (148.9 - 136.6) - 1.55 + max: new Big('23.05'), // 2 * (148.9 - 136.6) - 1.55 + mtd: new Big('24.60'), // 2 * (148.9 - 136.6) -> no fees in this time period + wtd: new Big('13.80'), // 2 * (148.9 - 142.0) -> no fees in this time period + ytd: new Big('23.05') // 2 * (148.9 - 136.6) - 1.55 }, marketPrice: 148.9, marketPriceInBaseCurrency: 148.9, diff --git a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-novn-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-novn-buy-and-sell.spec.ts index 992489bf9..4d8e3e193 100644 --- a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-novn-buy-and-sell.spec.ts +++ b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-novn-buy-and-sell.spec.ts @@ -120,6 +120,21 @@ describe('PortfolioCalculator', () => { }); expect(portfolioSnapshot.historicalData[0]).toEqual({ + date: '2022-03-06', + investmentValueWithCurrencyEffect: 0, + netPerformance: 0, + netPerformanceInPercentage: 0, + netPerformanceInPercentageWithCurrencyEffect: 0, + netPerformanceWithCurrencyEffect: 0, + netWorth: 0, + totalAccountBalance: 0, + totalInvestment: 0, + totalInvestmentValueWithCurrencyEffect: 0, + value: 0, + valueWithCurrencyEffect: 0 + }); + + expect(portfolioSnapshot.historicalData[1]).toEqual({ date: '2022-03-07', investmentValueWithCurrencyEffect: 151.6, netPerformance: 0, diff --git a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts index d6976c964..0443a49cf 100644 --- a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts @@ -857,23 +857,14 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { 'mtd', 'wtd', 'ytd' - // TODO - // ...eachYearOfInterval({ end, start }) - // .filter((date) => { - // return !isThisYear(date); - // }) - // .map((date) => { - // return format(date, 'yyyy'); - // }) ]) { - // TODO: getIntervalFromDateRange(dateRange, start) let { endDate, startDate } = getIntervalFromDateRange(dateRange); if (isBefore(startDate, start)) { - startDate = addDays(start, 1); + startDate = start; } - const currentValuesAtStartDateWithCurrencyEffect = + const currentValuesAtDateRangeStartWithCurrencyEffect = currentValuesWithCurrencyEffect[format(startDate, DATE_FORMAT)] ?? new Big(0); @@ -882,9 +873,8 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { format(startDate, DATE_FORMAT) ] ?? new Big(0); - // TODO: Rename? - const grossPerformanceAtStartDateWithCurrencyEffect2 = - currentValuesAtStartDateWithCurrencyEffect.minus( + const grossPerformanceAtDateRangeStartWithCurrencyEffect = + currentValuesAtDateRangeStartWithCurrencyEffect.minus( investmentValuesAccumulatedAtStartDateWithCurrencyEffect ); @@ -905,7 +895,7 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { ) { average = average.add( investmentValuesAccumulatedWithCurrencyEffect[date].add( - grossPerformanceAtStartDateWithCurrencyEffect2 + grossPerformanceAtDateRangeStartWithCurrencyEffect ) ); From 56068bbf6d7f3ab7c11f083152229f03c70a19ca Mon Sep 17 00:00:00 2001 From: Reto Kaul Date: Wed, 21 Aug 2024 17:24:12 +0200 Subject: [PATCH 2/2] Improvements after code review --- .../app/portfolio/calculator/twr/portfolio-calculator.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts index 0443a49cf..54155790f 100644 --- a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts @@ -857,7 +857,16 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { 'mtd', 'wtd', 'ytd' + // TODO: + // ...eachYearOfInterval({ end, start }) + // .filter((date) => { + // return !isThisYear(date); + // }) + // .map((date) => { + // return format(date, 'yyyy'); + // }) ]) { + // TODO: getIntervalFromDateRange(dateRange, start) let { endDate, startDate } = getIntervalFromDateRange(dateRange); if (isBefore(startDate, start)) {