diff --git a/apps/api/src/app/portfolio/calculator/constantPortfolioReturn/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/constantPortfolioReturn/portfolio-calculator.ts index f47c27610..1856e183f 100644 --- a/apps/api/src/app/portfolio/calculator/constantPortfolioReturn/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/constantPortfolioReturn/portfolio-calculator.ts @@ -1,27 +1,34 @@ import { LogPerformance } from '@ghostfolio/api/aop/logging.interceptor'; +import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; +import { OrderService } from '@ghostfolio/api/app/order/order.service'; +import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; import { getFactor, getInterval } from '@ghostfolio/api/helper/portfolio.helper'; +import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; +import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; import { MAX_CHART_ITEMS } from '@ghostfolio/common/config'; import { DATE_FORMAT, parseDate, resetHours } from '@ghostfolio/common/helper'; import { HistoricalDataItem } from '@ghostfolio/common/interfaces'; import { DateRange } from '@ghostfolio/common/types'; -import { Logger } from '@nestjs/common'; +import { Inject, Logger } from '@nestjs/common'; import { Big } from 'big.js'; import { addDays, differenceInDays, eachDayOfInterval, + endOfDay, format, isAfter, isBefore, - isEqual, subDays } from 'date-fns'; +import { CurrentRateService } from '../../current-rate.service'; +import { DateQuery } from '../../interfaces/date-query.interface'; import { PortfolioOrder } from '../../interfaces/portfolio-order.interface'; import { TWRPortfolioCalculator } from '../twr/portfolio-calculator'; @@ -29,6 +36,47 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { private holdings: { [date: string]: { [symbol: string]: Big } } = {}; private holdingCurrencies: { [symbol: string]: string } = {}; + constructor( + { + accountBalanceItems, + activities, + configurationService, + currency, + currentRateService, + dateRange, + exchangeRateDataService, + redisCacheService, + useCache, + userId + }: { + accountBalanceItems: HistoricalDataItem[]; + activities: Activity[]; + configurationService: ConfigurationService; + currency: string; + currentRateService: CurrentRateService; + dateRange: DateRange; + exchangeRateDataService: ExchangeRateDataService; + redisCacheService: RedisCacheService; + useCache: boolean; + userId: string; + }, + @Inject() + private orderService: OrderService + ) { + super({ + accountBalanceItems, + activities, + configurationService, + currency, + currentRateService, + dateRange, + exchangeRateDataService, + redisCacheService, + useCache, + userId + }); + } + @LogPerformance public async getChart({ dateRange = 'max', @@ -55,52 +103,118 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { if (!withTimeWeightedReturn) { return item; } else { + let itemResult = await item; + let dates = itemResult.map((item) => parseDate(item.date)); let timeWeighted = await this.getTimeWeightedChartData({ - step, - end: endDate, - start: startDate + dates }); - return item.then((data) => { - return data.map((item) => { - let timeWeightedItem = timeWeighted.find( - (timeWeightedItem) => timeWeightedItem.date === item.date - ); - if (timeWeightedItem) { - item.timeWeightedPerformance = - timeWeightedItem.netPerformanceInPercentage; - item.timeWeightedPerformanceWithCurrencyEffect = - timeWeightedItem.netPerformanceInPercentageWithCurrencyEffect; - } + return itemResult.map((item) => { + let timeWeightedItem = timeWeighted.find( + (timeWeightedItem) => timeWeightedItem.date === item.date + ); + if (timeWeightedItem) { + item.timeWeightedPerformance = + timeWeightedItem.netPerformanceInPercentage; + item.timeWeightedPerformanceWithCurrencyEffect = + timeWeightedItem.netPerformanceInPercentageWithCurrencyEffect; + } - return item; - }); + return item; }); } } + @LogPerformance + public async getUnfilteredNetWorth(currency: string): Promise { + const activities = await this.orderService.getOrders({ + userId: this.userId, + userCurrency: currency, + types: ['BUY', 'SELL', 'STAKE'], + withExcludedAccounts: true + }); + const orders = this.activitiesToPortfolioOrder(activities.activities); + const start = orders.reduce( + (date, order) => + parseDate(date.date).getTime() < parseDate(order.date).getTime() + ? date + : order, + { date: orders[0].date } + ).date; + + const end = new Date(Date.now()); + + const holdings = await this.getHoldings(orders, parseDate(start), end); + const marketMap = await this.currentRateService.getToday( + this.mapToDataGatheringItems(orders) + ); + const endString = format(end, DATE_FORMAT); + let exchangeRates = await Promise.all( + Object.keys(holdings[endString]).map(async (holding) => { + let symbol = marketMap.find((m) => m.symbol === holding); + let symbolCurrency = this.getCurrencyFromActivities(orders, holding); + let exchangeRate = await this.exchangeRateDataService.toCurrencyAtDate( + 1, + symbolCurrency, + this.currency, + end + ); + return { symbolCurrency, exchangeRate }; + }) + ); + let currencyRates = exchangeRates.reduce<{ [currency: string]: number }>( + (all, currency): { [currency: string]: number } => { + all[currency.symbolCurrency] ??= currency.exchangeRate; + return all; + }, + {} + ); + + let totalInvestment = await Object.keys(holdings[endString]).reduce( + (sum, holding) => { + if (!holdings[endString][holding].toNumber()) { + return sum; + } + let symbol = marketMap.find((m) => m.symbol === holding); + + if (symbol?.marketPrice === undefined) { + Logger.warn( + `Missing historical market data for ${holding} (${end})`, + 'PortfolioCalculator' + ); + return sum; + } else { + let symbolCurrency = this.getCurrency(holding); + let price = new Big(currencyRates[symbolCurrency]).mul( + symbol.marketPrice + ); + return sum.plus(new Big(price).mul(holdings[endString][holding])); + } + }, + new Big(0) + ); + return totalInvestment; + } + @LogPerformance private async getTimeWeightedChartData({ - end = new Date(Date.now()), - start, - step = 1 + dates }: { - end?: Date; - start: Date; - step?: number; + dates?: Date[]; }): Promise { - let marketMapTask = this.computeMarketMap({ gte: start, lte: end }); - const timelineHoldings = await this.getHoldings(start, end); - - const calculationDates = Object.keys(timelineHoldings) - .filter((date) => { - let parsed = parseDate(date); - return ( - isAfter(parsed, subDays(start, 1)) && - isBefore(parsed, addDays(end, 1)) - ); - }) - .sort((a, b) => parseDate(a).getTime() - parseDate(b).getTime()); + dates = dates.sort((a, b) => a.getTime() - b.getTime()); + const start = dates[0]; + const end = dates[dates.length - 1]; + let marketMapTask = this.computeMarketMap({ + gte: start, + lt: addDays(end, 1) + }); + const timelineHoldings = await this.getHoldings( + this.activities, + start, + end + ); + let data: HistoricalDataItem[] = []; const startString = format(start, DATE_FORMAT); @@ -119,7 +233,7 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { valueWithCurrencyEffect: 0 }); - await marketMapTask; + this.marketMap = await marketMapTask; let totalInvestment = Object.keys(timelineHoldings[startString]).reduce( (sum, holding) => { @@ -135,9 +249,9 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { let previousNetPerformanceInPercentage = new Big(0); let previousNetPerformanceInPercentageWithCurrencyEffect = new Big(0); - for (let i = 1; i < calculationDates.length; i++) { - const date = calculationDates[i]; - const previousDate = calculationDates[i - 1]; + for (let i = 1; i < dates.length; i++) { + const date = format(dates[i], DATE_FORMAT); + const previousDate = format(dates[i - 1], DATE_FORMAT); const holdings = timelineHoldings[previousDate]; let newTotalInvestment = new Big(0); let netPerformanceInPercentage = new Big(0); @@ -158,24 +272,28 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { netPerformanceInPercentageWithCurrencyEffect, newTotalInvestment )); - totalInvestment = newTotalInvestment; } + totalInvestment = newTotalInvestment; - previousNetPerformanceInPercentage = - previousNetPerformanceInPercentage.mul( - netPerformanceInPercentage.plus(1) - ); + previousNetPerformanceInPercentage = previousNetPerformanceInPercentage + .plus(1) + .mul(netPerformanceInPercentage.plus(1)) + .minus(1); previousNetPerformanceInPercentageWithCurrencyEffect = - previousNetPerformanceInPercentageWithCurrencyEffect.mul( - netPerformanceInPercentageWithCurrencyEffect.plus(1) - ); + previousNetPerformanceInPercentageWithCurrencyEffect + .plus(1) + .mul(netPerformanceInPercentageWithCurrencyEffect.plus(1)) + .minus(1); data.push({ date, - netPerformanceInPercentage: - previousNetPerformanceInPercentage.toNumber(), + netPerformanceInPercentage: previousNetPerformanceInPercentage + .mul(100) + .toNumber(), netPerformanceInPercentageWithCurrencyEffect: - previousNetPerformanceInPercentageWithCurrencyEffect.toNumber() + previousNetPerformanceInPercentageWithCurrencyEffect + .mul(100) + .toNumber() }); } @@ -210,8 +328,9 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { if (previousHolding.eq(0)) { return { - netPerformanceInPercentage: new Big(0), - netPerformanceInPercentageWithCurrencyEffect: new Big(0), + netPerformanceInPercentage: netPerformanceInPercentage, + netPerformanceInPercentageWithCurrencyEffect: + netPerformanceInPercentageWithCurrencyEffect, newTotalInvestment: newTotalInvestment.plus( timelineHoldings[date][holding].mul(priceInBaseCurrency) ) @@ -223,8 +342,9 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { 'PortfolioCalculator' ); return { - netPerformanceInPercentage: new Big(0), - netPerformanceInPercentageWithCurrencyEffect: new Big(0), + netPerformanceInPercentage: netPerformanceInPercentage, + netPerformanceInPercentageWithCurrencyEffect: + netPerformanceInPercentageWithCurrencyEffect, newTotalInvestment: newTotalInvestment.plus( timelineHoldings[date][holding].mul(priceInBaseCurrency) ) @@ -242,7 +362,7 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { : new Big(0); const portfolioWeight = totalInvestment.toNumber() ? previousHolding.mul(previousPriceInBaseCurrency).div(totalInvestment) - : 0; + : new Big(0); netPerformanceInPercentage = netPerformanceInPercentage.plus( currentPrice.div(previousPrice).minus(1).mul(portfolioWeight) @@ -268,8 +388,16 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { @LogPerformance private getCurrency(symbol: string) { + return this.getCurrencyFromActivities(this.activities, symbol); + } + + @LogPerformance + private getCurrencyFromActivities( + activities: PortfolioOrder[], + symbol: string + ) { if (!this.holdingCurrencies[symbol]) { - this.holdingCurrencies[symbol] = this.activities.find( + this.holdingCurrencies[symbol] = activities.find( (a) => a.SymbolProfile.symbol === symbol ).SymbolProfile.currency; } @@ -278,7 +406,11 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { } @LogPerformance - private async getHoldings(start: Date, end: Date) { + private async getHoldings( + activities: PortfolioOrder[], + start: Date, + end: Date + ) { if ( this.holdings && Object.keys(this.holdings).some((h) => @@ -291,13 +423,25 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { return this.holdings; } - this.computeHoldings(start, end); + this.computeHoldings(activities, start, end); return this.holdings; } @LogPerformance - private async computeHoldings(start: Date, end: Date) { - const investmentByDate = this.getInvestmentByDate(); + private async computeHoldings( + activities: PortfolioOrder[], + start: Date, + end: Date + ) { + const investmentByDate = this.getInvestmentByDate(activities); + this.calculateHoldings(investmentByDate, start, end); + } + + private calculateHoldings( + investmentByDate: { [date: string]: PortfolioOrder[] }, + start: Date, + end: Date + ) { const transactionDates = Object.keys(investmentByDate).sort(); let dates = eachDayOfInterval({ start, end }, { step: 1 }) .map((date) => { @@ -360,8 +504,10 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { } @LogPerformance - private getInvestmentByDate(): { [date: string]: PortfolioOrder[] } { - return this.activities.reduce((groupedByDate, order) => { + private getInvestmentByDate(activities: PortfolioOrder[]): { + [date: string]: PortfolioOrder[]; + } { + return activities.reduce((groupedByDate, order) => { if (!groupedByDate[order.date]) { groupedByDate[order.date] = []; } @@ -373,8 +519,10 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { } @LogPerformance - private async computeMarketMap(dateQuery: { gte: Date; lte: Date }) { - const dataGatheringItems: IDataGatheringItem[] = this.activities + private mapToDataGatheringItems( + orders: PortfolioOrder[] + ): IDataGatheringItem[] { + return orders .map((activity) => { return { symbol: activity.SymbolProfile.symbol, @@ -385,6 +533,14 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { (gathering, i, arr) => arr.findIndex((t) => t.symbol === gathering.symbol) === i ); + } + + @LogPerformance + private async computeMarketMap(dateQuery: DateQuery): Promise<{ + [date: string]: { [symbol: string]: Big }; + }> { + const dataGatheringItems: IDataGatheringItem[] = + this.mapToDataGatheringItems(this.activities); const { values: marketSymbols } = await this.currentRateService.getValues({ dataGatheringItems, dateQuery @@ -408,6 +564,41 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { } } - this.marketMap = marketSymbolMap; + return marketSymbolMap; + } + + @LogPerformance + private activitiesToPortfolioOrder(activities: Activity[]): PortfolioOrder[] { + return activities + .map( + ({ + date, + fee, + quantity, + SymbolProfile, + tags = [], + type, + unitPrice + }) => { + if (isAfter(date, new Date(Date.now()))) { + // Adapt date to today if activity is in future (e.g. liability) + // to include it in the interval + date = endOfDay(new Date(Date.now())); + } + + return { + SymbolProfile, + tags, + type, + date: format(date, DATE_FORMAT), + fee: new Big(fee), + quantity: new Big(quantity), + unitPrice: new Big(unitPrice) + }; + } + ) + .sort((a, b) => { + return a.date?.localeCompare(b.date); + }); } } diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.factory.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.factory.ts index 38e2b3eaf..b6089247e 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.factory.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.factory.ts @@ -8,6 +8,7 @@ import { DateRange, UserWithSettings } from '@ghostfolio/common/types'; import { Injectable } from '@nestjs/common'; +import { OrderService } from '../../order/order.service'; import { CPRPortfolioCalculator } from './constantPortfolioReturn/portfolio-calculator'; import { MWRPortfolioCalculator } from './mwr/portfolio-calculator'; import { PortfolioCalculator } from './portfolio-calculator'; @@ -25,7 +26,8 @@ export class PortfolioCalculatorFactory { private readonly configurationService: ConfigurationService, private readonly currentRateService: CurrentRateService, private readonly exchangeRateDataService: ExchangeRateDataService, - private readonly redisCacheService: RedisCacheService + private readonly redisCacheService: RedisCacheService, + private readonly orderservice: OrderService ) {} public createCalculator({ @@ -64,31 +66,37 @@ export class PortfolioCalculatorFactory { redisCacheService: this.redisCacheService }); case PerformanceCalculationType.TWR: - return new CPRPortfolioCalculator({ - accountBalanceItems, - activities, - currency, - currentRateService: this.currentRateService, - dateRange, - useCache, - userId, - configurationService: this.configurationService, - exchangeRateDataService: this.exchangeRateDataService, - redisCacheService: this.redisCacheService - }); + return new CPRPortfolioCalculator( + { + accountBalanceItems, + activities, + currency, + currentRateService: this.currentRateService, + dateRange, + useCache, + userId, + configurationService: this.configurationService, + exchangeRateDataService: this.exchangeRateDataService, + redisCacheService: this.redisCacheService + }, + this.orderservice + ); case PerformanceCalculationType.CPR: - return new CPRPortfolioCalculator({ - accountBalanceItems, - activities, - currency, - currentRateService: this.currentRateService, - dateRange, - useCache, - userId, - configurationService: this.configurationService, - exchangeRateDataService: this.exchangeRateDataService, - redisCacheService: this.redisCacheService - }); + return new CPRPortfolioCalculator( + { + accountBalanceItems, + activities, + currency, + currentRateService: this.currentRateService, + dateRange, + useCache, + userId, + configurationService: this.configurationService, + exchangeRateDataService: this.exchangeRateDataService, + redisCacheService: this.redisCacheService + }, + this.orderservice + ); default: throw new Error('Invalid calculation type'); } diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts index a45801677..57d5771ed 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts @@ -65,7 +65,7 @@ export abstract class PortfolioCalculator { private startDate: Date; private transactionPoints: TransactionPoint[]; private useCache: boolean; - private userId: string; + protected userId: string; protected marketMap: { [date: string]: { [symbol: string]: Big } } = {}; public constructor({ diff --git a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts index 340f16b87..44e812289 100644 --- a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts +++ b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts @@ -61,7 +61,8 @@ describe('PortfolioCalculator', () => { configurationService, currentRateService, exchangeRateDataService, - redisCacheService + redisCacheService, + null ); }); diff --git a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy-and-sell.spec.ts index 53ebdf19f..308c58c6a 100644 --- a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy-and-sell.spec.ts +++ b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy-and-sell.spec.ts @@ -61,7 +61,8 @@ describe('PortfolioCalculator', () => { configurationService, currentRateService, exchangeRateDataService, - redisCacheService + redisCacheService, + null ); }); 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 bab265887..7e73f811e 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 @@ -61,7 +61,8 @@ describe('PortfolioCalculator', () => { configurationService, currentRateService, exchangeRateDataService, - redisCacheService + redisCacheService, + null ); }); diff --git a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts index eba5d4674..fc8f7d016 100644 --- a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts +++ b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts @@ -74,7 +74,8 @@ describe('PortfolioCalculator', () => { configurationService, currentRateService, exchangeRateDataService, - redisCacheService + redisCacheService, + null ); }); diff --git a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-fee.spec.ts b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-fee.spec.ts index 88d7adb71..3cbcb3c44 100644 --- a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-fee.spec.ts +++ b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-fee.spec.ts @@ -61,7 +61,8 @@ describe('PortfolioCalculator', () => { configurationService, currentRateService, exchangeRateDataService, - redisCacheService + redisCacheService, + null ); }); diff --git a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-googl-buy.spec.ts b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-googl-buy.spec.ts index 690f1eb51..f44cf65bf 100644 --- a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-googl-buy.spec.ts +++ b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-googl-buy.spec.ts @@ -74,7 +74,8 @@ describe('PortfolioCalculator', () => { configurationService, currentRateService, exchangeRateDataService, - redisCacheService + redisCacheService, + null ); }); diff --git a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-item.spec.ts b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-item.spec.ts index 422d119b2..0ee5e5aaf 100644 --- a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-item.spec.ts +++ b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-item.spec.ts @@ -61,7 +61,8 @@ describe('PortfolioCalculator', () => { configurationService, currentRateService, exchangeRateDataService, - redisCacheService + redisCacheService, + null ); }); diff --git a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-liability.spec.ts b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-liability.spec.ts index d468e8e00..541a29681 100644 --- a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-liability.spec.ts +++ b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-liability.spec.ts @@ -61,7 +61,8 @@ describe('PortfolioCalculator', () => { configurationService, currentRateService, exchangeRateDataService, - redisCacheService + redisCacheService, + null ); }); diff --git a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-msft-buy-with-dividend.spec.ts b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-msft-buy-with-dividend.spec.ts index 094c6cc2e..5047319d0 100644 --- a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-msft-buy-with-dividend.spec.ts +++ b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-msft-buy-with-dividend.spec.ts @@ -74,7 +74,8 @@ describe('PortfolioCalculator', () => { configurationService, currentRateService, exchangeRateDataService, - redisCacheService + redisCacheService, + null ); }); diff --git a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-no-orders.spec.ts b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-no-orders.spec.ts index 6bb432bfc..8f5b7d057 100644 --- a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-no-orders.spec.ts +++ b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-no-orders.spec.ts @@ -57,7 +57,8 @@ describe('PortfolioCalculator', () => { configurationService, currentRateService, exchangeRateDataService, - redisCacheService + redisCacheService, + null ); }); diff --git a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-novn-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-novn-buy-and-sell-partially.spec.ts index f65d2ba61..4b0f9ffc1 100644 --- a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-novn-buy-and-sell-partially.spec.ts +++ b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-novn-buy-and-sell-partially.spec.ts @@ -61,7 +61,8 @@ describe('PortfolioCalculator', () => { configurationService, currentRateService, exchangeRateDataService, - redisCacheService + redisCacheService, + null ); }); 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 902f710ee..8fda68554 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 @@ -61,7 +61,8 @@ describe('PortfolioCalculator', () => { configurationService, currentRateService, exchangeRateDataService, - redisCacheService + redisCacheService, + null ); }); diff --git a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.spec.ts b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.spec.ts index 536581070..a2ad578bd 100644 --- a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.spec.ts +++ b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.spec.ts @@ -29,7 +29,8 @@ describe('PortfolioCalculator', () => { configurationService, currentRateService, exchangeRateDataService, - redisCacheService + redisCacheService, + null ); }); diff --git a/apps/api/src/app/portfolio/current-rate.service.ts b/apps/api/src/app/portfolio/current-rate.service.ts index 0d7d6814f..bc9bd5572 100644 --- a/apps/api/src/app/portfolio/current-rate.service.ts +++ b/apps/api/src/app/portfolio/current-rate.service.ts @@ -1,6 +1,7 @@ import { OrderService } from '@ghostfolio/api/app/order/order.service'; import { DateQueryHelper } from '@ghostfolio/api/helper/dateQueryHelper'; import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; +import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { resetHours } from '@ghostfolio/common/helper'; import { @@ -48,38 +49,12 @@ export class CurrentRateService { if (includesToday) { promises.push( - this.dataProviderService - .getQuotes({ items: dataGatheringItems, user: this.request?.user }) - .then((dataResultProvider) => { - const result: GetValueObject[] = []; - - for (const dataGatheringItem of dataGatheringItems) { - if ( - dataResultProvider?.[dataGatheringItem.symbol]?.dataProviderInfo - ) { - dataProviderInfos.push( - dataResultProvider[dataGatheringItem.symbol].dataProviderInfo - ); - } - - if (dataResultProvider?.[dataGatheringItem.symbol]?.marketPrice) { - result.push({ - dataSource: dataGatheringItem.dataSource, - date: today, - marketPrice: - dataResultProvider?.[dataGatheringItem.symbol]?.marketPrice, - symbol: dataGatheringItem.symbol - }); - } else { - quoteErrors.push({ - dataSource: dataGatheringItem.dataSource, - symbol: dataGatheringItem.symbol - }); - } - } - - return result; - }) + this.getTodayPrivate( + dataGatheringItems, + dataProviderInfos, + today, + quoteErrors + ) ); } @@ -169,6 +144,61 @@ export class CurrentRateService { return response; } + public async getToday( + dataGatheringItems: IDataGatheringItem[] + ): Promise { + const dataProviderInfos: DataProviderInfo[] = []; + const quoteErrors: UniqueAsset[] = []; + const today = resetHours(new Date()); + + return this.getTodayPrivate( + dataGatheringItems, + dataProviderInfos, + today, + quoteErrors + ); + } + + private async getTodayPrivate( + dataGatheringItems: IDataGatheringItem[], + dataProviderInfos: DataProviderInfo[], + today: Date, + quoteErrors: UniqueAsset[] + ): Promise { + return this.dataProviderService + .getQuotes({ items: dataGatheringItems, user: this.request?.user }) + .then((dataResultProvider) => { + const result: GetValueObject[] = []; + + for (const dataGatheringItem of dataGatheringItems) { + if ( + dataResultProvider?.[dataGatheringItem.symbol]?.dataProviderInfo + ) { + dataProviderInfos.push( + dataResultProvider[dataGatheringItem.symbol].dataProviderInfo + ); + } + + if (dataResultProvider?.[dataGatheringItem.symbol]?.marketPrice) { + result.push({ + dataSource: dataGatheringItem.dataSource, + date: today, + marketPrice: + dataResultProvider?.[dataGatheringItem.symbol]?.marketPrice, + symbol: dataGatheringItem.symbol + }); + } else { + quoteErrors.push({ + dataSource: dataGatheringItem.dataSource, + symbol: dataGatheringItem.symbol + }); + } + } + + return result; + }); + } + private containsToday(dates: Date[]): boolean { for (const date of dates) { if (isToday(date)) { diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 3da510d1f..03c7ace10 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -74,6 +74,7 @@ import { } from 'date-fns'; import { isEmpty, isNumber, last, uniq, uniqBy } from 'lodash'; +import { CPRPortfolioCalculator } from './calculator/constantPortfolioReturn/portfolio-calculator'; import { PortfolioCalculator } from './calculator/portfolio-calculator'; import { PerformanceCalculationType, @@ -1824,12 +1825,17 @@ export class PortfolioService { .plus(totalOfExcludedActivities) .toNumber(); - const netWorth = new Big(balanceInBaseCurrency) - .plus(currentValueInBaseCurrency) - .plus(valuables) - .plus(excludedAccountsAndActivities) - .minus(liabilities) - .toNumber(); + const netWorth = + portfolioCalculator instanceof CPRPortfolioCalculator + ? await (portfolioCalculator as CPRPortfolioCalculator) + .getUnfilteredNetWorth(this.getUserCurrency()) + .then((value) => value.toNumber()) + : new Big(balanceInBaseCurrency) + .plus(currentValueInBaseCurrency) + .plus(valuables) + .plus(excludedAccountsAndActivities) + .minus(liabilities) + .toNumber(); const daysInMarket = differenceInDays(new Date(), firstOrderDate); diff --git a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts index 6cd8bfb16..dc8e30a5b 100644 --- a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts +++ b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts @@ -337,24 +337,9 @@ export class AnalysisPageComponent implements OnDestroy, OnInit { value: netPerformanceInPercentageWithCurrencyEffect }); if ((this.timeWeightedPerformance ?? 'N') !== 'N') { - let lastPerformance = 0; - if (index > 0) { - lastPerformance = new Big( - chart[index - 1].timeWeightedPerformance - ) - .div(100) - .plus(1) - .mul( - new Big(chart[index].timeWeightedPerformance).div(100).plus(1) - ) - .minus(1) - .mul(100) - .toNumber(); - } - chart[index].timeWeightedPerformance = lastPerformance; this.performanceDataItemsTimeWeightedInPercentage.push({ date, - value: lastPerformance + value: chart[index].timeWeightedPerformance }); } }