From 1e0e318c17c1d24fd657f8dd58ca152c24077330 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 1 May 2025 15:16:40 +0200 Subject: [PATCH] Open portfolio calculator to overwrite calcluations --- .../calculator/portfolio-calculator.ts | 180 ++++++++++-------- ...rtfolio-calculator-symbolmetrics-helper.ts | 3 + .../calculator/roi/portfolio-calculator.ts | 68 ++++++- 3 files changed, 166 insertions(+), 85 deletions(-) diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts index 9cfbefd08..8fa1a41a8 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts @@ -71,8 +71,8 @@ export abstract class PortfolioCalculator { private filters: Filter[]; private portfolioSnapshotService: PortfolioSnapshotService; private redisCacheService: RedisCacheService; - private snapshot: PortfolioSnapshot; - private snapshotPromise: Promise; + protected snapshot: PortfolioSnapshot; + protected snapshotPromise: Promise; private startDate: Date; private transactionPoints: TransactionPoint[]; protected userId: string; @@ -557,56 +557,9 @@ export abstract class PortfolioCalculator { } } - const historicalData: HistoricalDataItem[] = Object.entries( + const historicalData: HistoricalDataItem[] = this.getHistoricalDataItems( accumulatedValuesByDate - ).map(([date, values]) => { - const { - investmentValueWithCurrencyEffect, - totalAccountBalanceWithCurrencyEffect, - totalCurrentValue, - totalCurrentValueWithCurrencyEffect, - totalInvestmentValue, - totalInvestmentValueWithCurrencyEffect, - totalNetPerformanceValue, - totalNetPerformanceValueWithCurrencyEffect, - totalTimeWeightedInvestmentValue, - totalTimeWeightedInvestmentValueWithCurrencyEffect - } = values; - - const netPerformanceInPercentage = totalTimeWeightedInvestmentValue.eq(0) - ? 0 - : totalNetPerformanceValue - .div(totalTimeWeightedInvestmentValue) - .toNumber(); - - const netPerformanceInPercentageWithCurrencyEffect = - totalTimeWeightedInvestmentValueWithCurrencyEffect.eq(0) - ? 0 - : totalNetPerformanceValueWithCurrencyEffect - .div(totalTimeWeightedInvestmentValueWithCurrencyEffect) - .toNumber(); - - return { - date, - netPerformanceInPercentage, - netPerformanceInPercentageWithCurrencyEffect, - investmentValueWithCurrencyEffect: - investmentValueWithCurrencyEffect.toNumber(), - netPerformance: totalNetPerformanceValue.toNumber(), - netPerformanceWithCurrencyEffect: - totalNetPerformanceValueWithCurrencyEffect.toNumber(), - // TODO: Add valuables - netWorth: totalCurrentValueWithCurrencyEffect - .plus(totalAccountBalanceWithCurrencyEffect) - .toNumber(), - totalAccountBalance: totalAccountBalanceWithCurrencyEffect.toNumber(), - totalInvestment: totalInvestmentValue.toNumber(), - totalInvestmentValueWithCurrencyEffect: - totalInvestmentValueWithCurrencyEffect.toNumber(), - value: totalCurrentValue.toNumber(), - valueWithCurrencyEffect: totalCurrentValueWithCurrencyEffect.toNumber() - }; - }); + ); const overall = this.calculateOverallPerformance(positions); @@ -865,39 +818,69 @@ export abstract class PortfolioCalculator { }; } - public getStartDate() { - let firstAccountBalanceDate: Date; - let firstActivityDate: Date; - - try { - const firstAccountBalanceDateString = this.accountBalanceItems[0]?.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]); - } + @LogPerformance + protected getHistoricalDataItems(accumulatedValuesByDate: { + [date: string]: { + investmentValueWithCurrencyEffect: Big; + totalAccountBalanceWithCurrencyEffect: Big; + totalCurrentValue: Big; + totalCurrentValueWithCurrencyEffect: Big; + totalInvestmentValue: Big; + totalInvestmentValueWithCurrencyEffect: Big; + totalNetPerformanceValue: Big; + totalNetPerformanceValueWithCurrencyEffect: Big; + totalTimeWeightedInvestmentValue: Big; + totalTimeWeightedInvestmentValueWithCurrencyEffect: Big; + }; + }): HistoricalDataItem[] { + return Object.entries(accumulatedValuesByDate).map(([date, values]) => { + const { + investmentValueWithCurrencyEffect, + totalAccountBalanceWithCurrencyEffect, + totalCurrentValue, + totalCurrentValueWithCurrencyEffect, + totalInvestmentValue, + totalInvestmentValueWithCurrencyEffect, + totalNetPerformanceValue, + totalNetPerformanceValueWithCurrencyEffect, + totalTimeWeightedInvestmentValue, + totalTimeWeightedInvestmentValueWithCurrencyEffect + } = values; - public getTransactionPoints() { - return this.transactionPoints; - } + const netPerformanceInPercentage = totalTimeWeightedInvestmentValue.eq(0) + ? 0 + : totalNetPerformanceValue + .div(totalTimeWeightedInvestmentValue) + .toNumber(); - public async getValuablesInBaseCurrency() { - await this.snapshotPromise; + const netPerformanceInPercentageWithCurrencyEffect = + totalTimeWeightedInvestmentValueWithCurrencyEffect.eq(0) + ? 0 + : totalNetPerformanceValueWithCurrencyEffect + .div(totalTimeWeightedInvestmentValueWithCurrencyEffect) + .toNumber(); - return this.snapshot.totalValuablesWithCurrencyEffect; + return { + date, + netPerformanceInPercentage, + netPerformanceInPercentageWithCurrencyEffect, + investmentValueWithCurrencyEffect: + investmentValueWithCurrencyEffect.toNumber(), + netPerformance: totalNetPerformanceValue.toNumber(), + netPerformanceWithCurrencyEffect: + totalNetPerformanceValueWithCurrencyEffect.toNumber(), + // TODO: Add valuables + netWorth: totalCurrentValueWithCurrencyEffect + .plus(totalAccountBalanceWithCurrencyEffect) + .toNumber(), + totalAccountBalance: totalAccountBalanceWithCurrencyEffect.toNumber(), + totalInvestment: totalInvestmentValue.toNumber(), + totalInvestmentValueWithCurrencyEffect: + totalInvestmentValueWithCurrencyEffect.toNumber(), + value: totalCurrentValue.toNumber(), + valueWithCurrencyEffect: totalCurrentValueWithCurrencyEffect.toNumber() + }; + }); } @LogPerformance @@ -1281,6 +1264,41 @@ export abstract class PortfolioCalculator { ); } + public getStartDate() { + let firstAccountBalanceDate: Date; + let firstActivityDate: Date; + + try { + const firstAccountBalanceDateString = this.accountBalanceItems[0]?.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; + } + + public async getValuablesInBaseCurrency() { + await this.snapshotPromise; + + return this.snapshot.totalValuablesWithCurrencyEffect; + } + private calculateHoldings( investmentByDate: { [date: string]: PortfolioOrder[] }, start: Date, diff --git a/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-symbolmetrics-helper.ts b/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-symbolmetrics-helper.ts index 9177befb1..2bae172a6 100644 --- a/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-symbolmetrics-helper.ts +++ b/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-symbolmetrics-helper.ts @@ -1,3 +1,4 @@ +import { LogPerformance } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.interceptor'; import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { SymbolMetrics } from '@ghostfolio/common/interfaces'; @@ -27,6 +28,7 @@ export class RoiPortfolioCalculatorSymbolMetricsHelper { this.chartDates = chartDates; } + @LogPerformance public calculateNetPerformanceByDateRange( start: Date, symbolMetricsHelper: PortfolioCalculatorSymbolMetricsHelperObject @@ -119,6 +121,7 @@ export class RoiPortfolioCalculatorSymbolMetricsHelper { } } + @LogPerformance public processOrderMetrics( orders: PortfolioOrderItem[], i: number, diff --git a/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator.ts index 150bacdda..b11896557 100644 --- a/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator.ts @@ -1,6 +1,8 @@ import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator'; +import { LogPerformance } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.interceptor'; import { AssetProfileIdentifier, + HistoricalDataItem, SymbolMetrics } from '@ghostfolio/common/interfaces'; import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models'; @@ -15,6 +17,63 @@ import { RoiPortfolioCalculatorSymbolMetricsHelper } from './portfolio-calculato export class RoiPortfolioCalculator extends PortfolioCalculator { private chartDates: string[]; + + //TODO Overwrite historicalData creation for ROI --> Use TimeWeighted as used for chart + + @LogPerformance + public override async getPerformance({ + end, + start + }: { + end: string | number | Date; + start: string | number | Date; + }): Promise<{ + chart: HistoricalDataItem[]; + netPerformance: number; + netPerformanceInPercentage: number; + netPerformanceWithCurrencyEffect: number; + netPerformanceInPercentageWithCurrencyEffect: number; + netWorth: number; + totalInvestment: number; + valueWithCurrencyEffect: number; + }> { + await this.snapshotPromise; + const { positions } = this.snapshot; + + const { chart } = await super.getPerformance({ start, end }); + + const last = chart.at(-1); + const netWorth = last.netWorth; + const totalInvestment = last.totalInvestment; + const valueWithCurrencyEffect = last.valueWithCurrencyEffect; + + let netPerformance: number; + let netPerformanceInPercentage: number; + let netPerformanceWithCurrencyEffect: number; + let netPerformanceInPercentageWithCurrencyEffect: number; + + for (const position of positions) { + netPerformance = netPerformance + position.netPerformance.toNumber(); + netPerformanceInPercentage = + netPerformanceInPercentage * + position.valueInBaseCurrency.div(netWorth).toNumber(); + + //TODO Calculate performance values not using chart + } + + return { + chart, + netPerformance, + netPerformanceInPercentage, + netPerformanceWithCurrencyEffect, + netPerformanceInPercentageWithCurrencyEffect, + netWorth, + totalInvestment, + valueWithCurrencyEffect + }; + } + + @LogPerformance protected calculateOverallPerformance( positions: TimelinePosition[] ): PortfolioSnapshot { @@ -76,10 +135,7 @@ export class RoiPortfolioCalculator extends PortfolioCalculator { }; } - protected getPerformanceCalculationType() { - return PerformanceCalculationType.ROI; - } - + @LogPerformance protected getSymbolMetrics({ chartDateMap, dataSource, @@ -183,6 +239,10 @@ export class RoiPortfolioCalculator extends PortfolioCalculator { return symbolMetricsHelper.symbolMetrics; } + protected getPerformanceCalculationType() { + return PerformanceCalculationType.ROI; + } + private calculatePositionMetrics( currentPosition: TimelinePosition, totalFeesWithCurrencyEffect: Big,