diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index 719302f35..348a8d8d3 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -536,6 +536,8 @@ export class AdminService { })?._count ?? 0; return { + activitiesCount, + date: dateOfFirstActivity, dataSource, marketDataItemCount, symbol, 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 920321bd5..88ff11b47 100644 --- a/apps/api/src/app/portfolio/calculator/constantPortfolioReturn/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/constantPortfolioReturn/portfolio-calculator.ts @@ -2,16 +2,13 @@ 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 { getFactor } 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 { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper'; import { DATE_FORMAT, parseDate, resetHours } from '@ghostfolio/common/helper'; -import { HistoricalDataItem } from '@ghostfolio/common/interfaces'; +import { Filter, HistoricalDataItem } from '@ghostfolio/common/interfaces'; import { DateRange } from '@ghostfolio/common/types'; import { Inject, Logger } from '@nestjs/common'; @@ -43,21 +40,19 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { configurationService, currency, currentRateService, - dateRange, exchangeRateDataService, redisCacheService, - useCache, - userId + userId, + filters }: { accountBalanceItems: HistoricalDataItem[]; activities: Activity[]; configurationService: ConfigurationService; currency: string; currentRateService: CurrentRateService; - dateRange: DateRange; exchangeRateDataService: ExchangeRateDataService; redisCacheService: RedisCacheService; - useCache: boolean; + filters: Filter[]; userId: string; }, @Inject() @@ -68,11 +63,10 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { activities, configurationService, currency, + filters, currentRateService, - dateRange, exchangeRateDataService, redisCacheService, - useCache, userId }); } @@ -87,23 +81,31 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { withDataDecimation?: boolean; withTimeWeightedReturn?: boolean; }): Promise { - const { endDate, startDate } = getInterval(dateRange, this.getStartDate()); + const { endDate, startDate } = getIntervalFromDateRange( + dateRange, + this.getStartDate() + ); const daysInMarket = differenceInDays(endDate, startDate) + 1; const step = withDataDecimation - ? Math.round(daysInMarket / Math.min(daysInMarket, MAX_CHART_ITEMS)) + ? Math.round( + daysInMarket / + Math.min( + daysInMarket, + this.configurationService.get('MAX_CHART_ITEMS') + ) + ) : 1; - let item = super.getChartData({ - step, + let item = await super.getPerformance({ end: endDate, start: startDate }); if (!withTimeWeightedReturn) { - return item; + return item.chart; } else { - let itemResult = await item; + let itemResult = item.chart; let dates = itemResult.map((item) => parseDate(item.date)); let timeWeighted = await this.getTimeWeightedChartData({ dates @@ -145,13 +147,14 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { 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 marketMap = await this.currentRateService.getValues({ + dataGatheringItems: this.mapToDataGatheringItems(orders), + dateQuery: { in: [end] } + }); 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 symbol = marketMap.values.find((m) => m.symbol === holding); let symbolCurrency = this.getCurrencyFromActivities(orders, holding); let exchangeRate = await this.exchangeRateDataService.toCurrencyAtDate( 1, @@ -175,7 +178,7 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator { if (!holdings[endString][holding].toNumber()) { return sum; } - let symbol = marketMap.find((m) => m.symbol === holding); + let symbol = marketMap.values.find((m) => m.symbol === holding); if (symbol?.marketPrice === undefined) { Logger.warn( 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 462ef26b3..a43d9b3e6 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.factory.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.factory.ts @@ -66,12 +66,11 @@ export class PortfolioCalculatorFactory { activities, currency, currentRateService: this.currentRateService, - dateRange, - useCache, userId, configurationService: this.configurationService, exchangeRateDataService: this.exchangeRateDataService, - redisCacheService: this.redisCacheService + redisCacheService: this.redisCacheService, + filters }, this.orderservice ); @@ -82,12 +81,11 @@ export class PortfolioCalculatorFactory { activities, currency, currentRateService: this.currentRateService, - dateRange, - useCache, userId, configurationService: this.configurationService, exchangeRateDataService: this.exchangeRateDataService, - redisCacheService: this.redisCacheService + redisCacheService: this.redisCacheService, + filters }, this.orderservice ); diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts index 57bd00825..a3d490252 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts @@ -49,7 +49,7 @@ export abstract class PortfolioCalculator { protected accountBalanceItems: HistoricalDataItem[]; protected activities: PortfolioOrder[]; - private configurationService: ConfigurationService; + protected configurationService: ConfigurationService; protected currency: string; protected currentRateService: CurrentRateService; private dataProviderInfos: DataProviderInfo[]; @@ -379,15 +379,15 @@ export abstract class PortfolioCalculator { dataSource: item.dataSource, fee: item.fee, firstBuyDate: item.firstBuyDate, - grossPerformance: !hasErrors ? grossPerformance ?? null : null, + grossPerformance: !hasErrors ? (grossPerformance ?? null) : null, grossPerformancePercentage: !hasErrors - ? grossPerformancePercentage ?? null + ? (grossPerformancePercentage ?? null) : null, grossPerformancePercentageWithCurrencyEffect: !hasErrors - ? grossPerformancePercentageWithCurrencyEffect ?? null + ? (grossPerformancePercentageWithCurrencyEffect ?? null) : null, grossPerformanceWithCurrencyEffect: !hasErrors - ? grossPerformanceWithCurrencyEffect ?? null + ? (grossPerformanceWithCurrencyEffect ?? null) : null, investment: totalInvestment, investmentWithCurrencyEffect: totalInvestmentWithCurrencyEffect, @@ -395,15 +395,15 @@ export abstract class PortfolioCalculator { marketSymbolMap[endDateString]?.[item.symbol]?.toNumber() ?? null, marketPriceInBaseCurrency: marketPriceInBaseCurrency?.toNumber() ?? null, - netPerformance: !hasErrors ? netPerformance ?? null : null, + netPerformance: !hasErrors ? (netPerformance ?? null) : null, netPerformancePercentage: !hasErrors - ? netPerformancePercentage ?? null + ? (netPerformancePercentage ?? null) : null, netPerformancePercentageWithCurrencyEffectMap: !hasErrors - ? netPerformancePercentageWithCurrencyEffectMap ?? null + ? (netPerformancePercentageWithCurrencyEffectMap ?? null) : null, netPerformanceWithCurrencyEffectMap: !hasErrors - ? netPerformanceWithCurrencyEffectMap ?? null + ? (netPerformanceWithCurrencyEffectMap ?? null) : null, quantity: item.quantity, symbol: item.symbol, @@ -491,8 +491,8 @@ export abstract class PortfolioCalculator { return date === dateString; }).value ) - : accumulatedValuesByDate[lastDate] - ?.totalAccountBalanceWithCurrencyEffect ?? new Big(0), + : (accumulatedValuesByDate[lastDate] + ?.totalAccountBalanceWithCurrencyEffect ?? new Big(0)), totalCurrentValue: ( accumulatedValuesByDate[dateString]?.totalCurrentValue ?? new Big(0) ).add(currentValue), @@ -873,74 +873,6 @@ export abstract class PortfolioCalculator { return chartDateMap; } - private getChartDateMap({ - endDate, - startDate, - step - }: { - endDate: Date; - startDate: Date; - step: number; - }) { - // Create a map of all relevant chart dates: - // 1. Add transaction point dates - let chartDateMap = this.transactionPoints.reduce((result, { date }) => { - result[date] = true; - return result; - }, {}); - - // 2. Add dates between transactions respecting the specified step size - for (let 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 (let 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 (let 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 (let 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; - } - @LogPerformance protected computeTransactionPoints() { this.transactionPoints = []; 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 5da458ac3..4f053355c 100644 --- a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts @@ -1,4 +1,3 @@ -import { LogPerformance } from '@ghostfolio/api/aop/logging.interceptor'; import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator'; import { PortfolioOrderItem } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order-item.interface'; import { getFactor } from '@ghostfolio/api/helper/portfolio.helper'; @@ -12,7 +11,6 @@ import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models'; import { DateRange } from '@ghostfolio/common/types'; import { Logger } from '@nestjs/common'; -import { DataSource } from '@prisma/client'; import { Big } from 'big.js'; import { addDays, @@ -25,7 +23,6 @@ import { import { cloneDeep, first, last, sortBy } from 'lodash'; export class TWRPortfolioCalculator extends PortfolioCalculator { - @LogPerformance protected calculateOverallPerformance( positions: TimelinePosition[] ): PortfolioSnapshot { @@ -115,7 +112,6 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { }; } - @LogPerformance protected getSymbolMetrics({ chartDateMap, dataSource, @@ -167,7 +163,6 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { let totalAccountBalanceInBaseCurrency = new Big(0); let totalDividend = new Big(0); - let totalStakeRewards = new Big(0); let totalDividendInBaseCurrency = new Big(0); let totalInterest = new Big(0); let totalInterestInBaseCurrency = new Big(0); @@ -195,7 +190,6 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { return { currentValues: {}, currentValuesWithCurrencyEffect: {}, - unitPrices: {}, feesWithCurrencyEffect: new Big(0), grossPerformance: new Big(0), grossPerformancePercentage: new Big(0), @@ -209,10 +203,11 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { investmentValuesWithCurrencyEffect: {}, netPerformance: new Big(0), netPerformancePercentage: new Big(0), + netPerformanceValuesPercentage: {}, + unitPrices: {}, netPerformancePercentageWithCurrencyEffectMap: {}, netPerformanceValues: {}, netPerformanceValuesWithCurrencyEffect: {}, - netPerformanceValuesPercentage: {}, netPerformanceWithCurrencyEffectMap: {}, timeWeightedInvestment: new Big(0), timeWeightedInvestmentValues: {}, @@ -247,7 +242,6 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { return { currentValues: {}, currentValuesWithCurrencyEffect: {}, - unitPrices: {}, feesWithCurrencyEffect: new Big(0), grossPerformance: new Big(0), grossPerformancePercentage: new Big(0), @@ -265,7 +259,6 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { netPerformanceWithCurrencyEffectMap: {}, netPerformanceValues: {}, netPerformanceValuesWithCurrencyEffect: {}, - netPerformanceValuesPercentage: {}, timeWeightedInvestment: new Big(0), timeWeightedInvestmentValues: {}, timeWeightedInvestmentValuesWithCurrencyEffect: {}, @@ -280,7 +273,9 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { totalLiabilities: new Big(0), totalLiabilitiesInBaseCurrency: new Big(0), totalValuables: new Big(0), - totalValuablesInBaseCurrency: new Big(0) + totalValuablesInBaseCurrency: new Big(0), + netPerformanceValuesPercentage: {}, + unitPrices: {} }; } @@ -381,316 +376,6 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { let sumOfTimeWeightedInvestments = new Big(0); let sumOfTimeWeightedInvestmentsWithCurrencyEffect = new Big(0); - ({ - totalDividend, - totalDividendInBaseCurrency, - totalInterest, - totalInterestInBaseCurrency, - totalValuables, - totalValuablesInBaseCurrency, - totalLiabilities, - totalLiabilitiesInBaseCurrency, - totalUnits, - investmentAtStartDate, - totalInvestment, - investmentAtStartDateWithCurrencyEffect, - totalInvestmentWithCurrencyEffect, - valueAtStartDate, - valueAtStartDateWithCurrencyEffect, - totalQuantityFromBuyTransactions, - totalInvestmentFromBuyTransactions, - totalInvestmentFromBuyTransactionsWithCurrencyEffect, - initialValue, - initialValueWithCurrencyEffect, - fees, - feesWithCurrencyEffect, - lastAveragePrice, - lastAveragePriceWithCurrencyEffect, - grossPerformanceFromSells, - grossPerformanceFromSellsWithCurrencyEffect, - grossPerformance, - grossPerformanceWithCurrencyEffect, - feesAtStartDate, - feesAtStartDateWithCurrencyEffect, - grossPerformanceAtStartDate, - grossPerformanceAtStartDateWithCurrencyEffect, - totalInvestmentDays, - sumOfTimeWeightedInvestments, - sumOfTimeWeightedInvestmentsWithCurrencyEffect - } = this.handleOrders( - orders, - exchangeRates, - totalDividend, - totalDividendInBaseCurrency, - totalInterest, - totalInterestInBaseCurrency, - totalValuables, - totalValuablesInBaseCurrency, - totalLiabilities, - totalLiabilitiesInBaseCurrency, - indexOfStartOrder, - unitPriceAtStartDate, - currentExchangeRate, - marketSymbolMap, - symbol, - totalUnits, - investmentAtStartDate, - totalInvestment, - investmentAtStartDateWithCurrencyEffect, - totalInvestmentWithCurrencyEffect, - valueAtStartDate, - valueAtStartDateWithCurrencyEffect, - totalQuantityFromBuyTransactions, - totalInvestmentFromBuyTransactions, - totalInvestmentFromBuyTransactionsWithCurrencyEffect, - initialValue, - initialValueWithCurrencyEffect, - fees, - feesWithCurrencyEffect, - lastAveragePrice, - lastAveragePriceWithCurrencyEffect, - grossPerformanceFromSells, - grossPerformanceFromSellsWithCurrencyEffect, - grossPerformance, - grossPerformanceWithCurrencyEffect, - feesAtStartDate, - feesAtStartDateWithCurrencyEffect, - grossPerformanceAtStartDate, - grossPerformanceAtStartDateWithCurrencyEffect, - totalInvestmentDays, - sumOfTimeWeightedInvestments, - sumOfTimeWeightedInvestmentsWithCurrencyEffect, - isChartMode, - currentValues, - currentValuesWithCurrencyEffect, - netPerformanceValues, - netPerformanceValuesWithCurrencyEffect, - investmentValuesAccumulated, - investmentValuesAccumulatedWithCurrencyEffect, - investmentValuesWithCurrencyEffect, - timeWeightedInvestmentValues, - timeWeightedInvestmentValuesWithCurrencyEffect, - indexOfEndOrder - )); - - const totalGrossPerformance = grossPerformance.minus( - grossPerformanceAtStartDate - ); - - const totalGrossPerformanceWithCurrencyEffect = - grossPerformanceWithCurrencyEffect.minus( - grossPerformanceAtStartDateWithCurrencyEffect - ); - - const totalNetPerformance = grossPerformance - .minus(grossPerformanceAtStartDate) - .minus(fees.minus(feesAtStartDate)); - - const totalNetPerformanceWithCurrencyEffect = - grossPerformanceWithCurrencyEffect - .minus(grossPerformanceAtStartDateWithCurrencyEffect) - .minus(feesWithCurrencyEffect.minus(feesAtStartDateWithCurrencyEffect)); - - const timeWeightedAverageInvestmentBetweenStartAndEndDate = - totalInvestmentDays > 0 - ? sumOfTimeWeightedInvestments.div(totalInvestmentDays) - : new Big(0); - - const timeWeightedAverageInvestmentBetweenStartAndEndDateWithCurrencyEffect = - totalInvestmentDays > 0 - ? sumOfTimeWeightedInvestmentsWithCurrencyEffect.div( - totalInvestmentDays - ) - : new Big(0); - - const grossPerformancePercentage = - timeWeightedAverageInvestmentBetweenStartAndEndDate.gt(0) - ? totalGrossPerformance.div( - timeWeightedAverageInvestmentBetweenStartAndEndDate - ) - : new Big(0); - - const grossPerformancePercentageWithCurrencyEffect = - timeWeightedAverageInvestmentBetweenStartAndEndDateWithCurrencyEffect.gt( - 0 - ) - ? totalGrossPerformanceWithCurrencyEffect.div( - timeWeightedAverageInvestmentBetweenStartAndEndDateWithCurrencyEffect - ) - : new Big(0); - - const feesPerUnit = totalUnits.gt(0) - ? fees.minus(feesAtStartDate).div(totalUnits) - : new Big(0); - - const feesPerUnitWithCurrencyEffect = totalUnits.gt(0) - ? feesWithCurrencyEffect - .minus(feesAtStartDateWithCurrencyEffect) - .div(totalUnits) - : new Big(0); - - const netPerformancePercentage = - timeWeightedAverageInvestmentBetweenStartAndEndDate.gt(0) - ? totalNetPerformance.div( - timeWeightedAverageInvestmentBetweenStartAndEndDate - ) - : new Big(0); - - const netPerformancePercentageWithCurrencyEffect = - timeWeightedAverageInvestmentBetweenStartAndEndDateWithCurrencyEffect.gt( - 0 - ) - ? totalNetPerformanceWithCurrencyEffect.div( - timeWeightedAverageInvestmentBetweenStartAndEndDateWithCurrencyEffect - ) - : new Big(0); - - if (PortfolioCalculator.ENABLE_LOGGING) { - console.log( - ` - ${symbol} - Unit price: ${orders[indexOfStartOrder].unitPrice.toFixed( - 2 - )} -> ${unitPriceAtEndDate.toFixed(2)} - Total investment: ${totalInvestment.toFixed(2)} - Total investment with currency effect: ${totalInvestmentWithCurrencyEffect.toFixed( - 2 - )} - Time weighted investment: ${timeWeightedAverageInvestmentBetweenStartAndEndDate.toFixed( - 2 - )} - Time weighted investment with currency effect: ${timeWeightedAverageInvestmentBetweenStartAndEndDateWithCurrencyEffect.toFixed( - 2 - )} - Total dividend: ${totalDividend.toFixed(2)} - Gross performance: ${totalGrossPerformance.toFixed( - 2 - )} / ${grossPerformancePercentage.mul(100).toFixed(2)}% - Gross performance with currency effect: ${totalGrossPerformanceWithCurrencyEffect.toFixed( - 2 - )} / ${grossPerformancePercentageWithCurrencyEffect - .mul(100) - .toFixed(2)}% - Fees per unit: ${feesPerUnit.toFixed(2)} - Fees per unit with currency effect: ${feesPerUnitWithCurrencyEffect.toFixed( - 2 - )} - Net performance: ${totalNetPerformance.toFixed( - 2 - )} / ${netPerformancePercentage.mul(100).toFixed(2)}% - Net performance with currency effect: ${totalNetPerformanceWithCurrencyEffect.toFixed( - 2 - )} / ${netPerformancePercentageWithCurrencyEffect.mul(100).toFixed(2)}%` - ); - } - - let unitPrices = Object.keys(marketSymbolMap) - .map((date) => { - return { [date]: marketSymbolMap[date][symbol] }; - }) - .reduce((map, u) => { - return { ...u, ...map }; - }, {}); - - return { - currentValues, - currentValuesWithCurrencyEffect, - unitPrices, - feesWithCurrencyEffect, - grossPerformancePercentage, - grossPerformancePercentageWithCurrencyEffect, - initialValue, - initialValueWithCurrencyEffect, - investmentValuesAccumulated, - investmentValuesAccumulatedWithCurrencyEffect, - investmentValuesWithCurrencyEffect, - netPerformancePercentage, - netPerformancePercentageWithCurrencyEffect, - netPerformanceValues, - netPerformanceValuesWithCurrencyEffect, - netPerformanceValuesPercentage: {}, - timeWeightedInvestmentValues, - timeWeightedInvestmentValuesWithCurrencyEffect, - totalAccountBalanceInBaseCurrency, - totalDividend, - totalDividendInBaseCurrency, - totalInterest, - totalInterestInBaseCurrency, - totalInvestment, - totalInvestmentWithCurrencyEffect, - totalLiabilities, - totalLiabilitiesInBaseCurrency, - totalValuables, - totalValuablesInBaseCurrency, - grossPerformance: totalGrossPerformance, - grossPerformanceWithCurrencyEffect: - totalGrossPerformanceWithCurrencyEffect, - hasErrors: totalUnits.gt(0) && (!initialValue || !unitPriceAtEndDate), - netPerformance: totalNetPerformance, - netPerformanceWithCurrencyEffect: totalNetPerformanceWithCurrencyEffect, - timeWeightedInvestment: - timeWeightedAverageInvestmentBetweenStartAndEndDate, - timeWeightedInvestmentWithCurrencyEffect: - timeWeightedAverageInvestmentBetweenStartAndEndDateWithCurrencyEffect - }; - } - - @LogPerformance - protected handleOrders( - orders: PortfolioOrderItem[], - exchangeRates: { [dateString: string]: number }, - totalDividend, - totalDividendInBaseCurrency, - totalInterest, - totalInterestInBaseCurrency, - totalValuables, - totalValuablesInBaseCurrency, - totalLiabilities, - totalLiabilitiesInBaseCurrency, - indexOfStartOrder: number, - unitPriceAtStartDate: Big, - currentExchangeRate: number, - marketSymbolMap: { [date: string]: { [symbol: string]: Big } }, - symbol: string, - totalUnits, - investmentAtStartDate: Big, - totalInvestment, - investmentAtStartDateWithCurrencyEffect: Big, - totalInvestmentWithCurrencyEffect, - valueAtStartDate: Big, - valueAtStartDateWithCurrencyEffect: Big, - totalQuantityFromBuyTransactions, - totalInvestmentFromBuyTransactions, - totalInvestmentFromBuyTransactionsWithCurrencyEffect, - initialValue: Big, - initialValueWithCurrencyEffect: Big, - fees, - feesWithCurrencyEffect, - lastAveragePrice, - lastAveragePriceWithCurrencyEffect, - grossPerformanceFromSells, - grossPerformanceFromSellsWithCurrencyEffect, - grossPerformance, - grossPerformanceWithCurrencyEffect, - feesAtStartDate, - feesAtStartDateWithCurrencyEffect, - grossPerformanceAtStartDate, - grossPerformanceAtStartDateWithCurrencyEffect, - totalInvestmentDays: number, - sumOfTimeWeightedInvestments, - sumOfTimeWeightedInvestmentsWithCurrencyEffect, - isChartMode: boolean, - currentValues: { [date: string]: Big }, - currentValuesWithCurrencyEffect: { [date: string]: Big }, - netPerformanceValues: { [date: string]: Big }, - netPerformanceValuesWithCurrencyEffect: { [date: string]: Big }, - investmentValuesAccumulated: { [date: string]: Big }, - investmentValuesAccumulatedWithCurrencyEffect: { [date: string]: Big }, - investmentValuesWithCurrencyEffect: { [date: string]: Big }, - timeWeightedInvestmentValues: { [date: string]: Big }, - timeWeightedInvestmentValuesWithCurrencyEffect: { [date: string]: Big }, - indexOfEndOrder: number - ) { for (let i = 0; i < orders.length; i += 1) { const order = orders[i]; @@ -707,27 +392,35 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { const exchangeRateAtOrderDate = exchangeRates[order.date]; - ({ - totalDividend, - totalDividendInBaseCurrency, - totalInterest, - totalInterestInBaseCurrency, - totalValuables, - totalValuablesInBaseCurrency, - totalLiabilities, - totalLiabilitiesInBaseCurrency - } = this.handleOrderType( - order, - totalDividend, - totalDividendInBaseCurrency, - exchangeRateAtOrderDate, - totalInterest, - totalInterestInBaseCurrency, - totalValuables, - totalValuablesInBaseCurrency, - totalLiabilities, - totalLiabilitiesInBaseCurrency - )); + if (order.type === 'DIVIDEND') { + const dividend = order.quantity.mul(order.unitPrice); + + totalDividend = totalDividend.plus(dividend); + totalDividendInBaseCurrency = totalDividendInBaseCurrency.plus( + dividend.mul(exchangeRateAtOrderDate ?? 1) + ); + } else if (order.type === 'INTEREST') { + const interest = order.quantity.mul(order.unitPrice); + + totalInterest = totalInterest.plus(interest); + totalInterestInBaseCurrency = totalInterestInBaseCurrency.plus( + interest.mul(exchangeRateAtOrderDate ?? 1) + ); + } else if (order.type === 'ITEM') { + const valuables = order.quantity.mul(order.unitPrice); + + totalValuables = totalValuables.plus(valuables); + totalValuablesInBaseCurrency = totalValuablesInBaseCurrency.plus( + valuables.mul(exchangeRateAtOrderDate ?? 1) + ); + } else if (order.type === 'LIABILITY') { + const liabilities = order.quantity.mul(order.unitPrice); + + totalLiabilities = totalLiabilities.plus(liabilities); + totalLiabilitiesInBaseCurrency = totalLiabilitiesInBaseCurrency.plus( + liabilities.mul(exchangeRateAtOrderDate ?? 1) + ); + } if (order.itemType === 'start') { // Take the unit price of the order as the market price if there are no @@ -745,15 +438,7 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { ); } - if (order.type === 'STAKE') { - order.unitPrice = marketSymbolMap[order.date]?.[symbol] ?? new Big(0); - } - - if (order.type === 'STAKE') { - order.unitPrice = marketSymbolMap[order.date]?.[symbol] ?? new Big(0); - } - - const unitPrice = ['BUY', 'SELL', 'STAKE'].includes(order.type) + const unitPrice = ['BUY', 'SELL'].includes(order.type) ? order.unitPrice : order.unitPriceFromMarketData; @@ -787,23 +472,38 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { let transactionInvestment = new Big(0); let transactionInvestmentWithCurrencyEffect = new Big(0); - ({ - transactionInvestment, - transactionInvestmentWithCurrencyEffect, - totalQuantityFromBuyTransactions, - totalInvestmentFromBuyTransactions, - totalInvestmentFromBuyTransactionsWithCurrencyEffect - } = this.handleBuyAndSellOrders( - order, - transactionInvestment, - transactionInvestmentWithCurrencyEffect, - totalQuantityFromBuyTransactions, - totalInvestmentFromBuyTransactions, - totalInvestmentFromBuyTransactionsWithCurrencyEffect, - totalUnits, - totalInvestment, - totalInvestmentWithCurrencyEffect - )); + if (order.type === 'BUY') { + transactionInvestment = order.quantity + .mul(order.unitPriceInBaseCurrency) + .mul(getFactor(order.type)); + + transactionInvestmentWithCurrencyEffect = order.quantity + .mul(order.unitPriceInBaseCurrencyWithCurrencyEffect) + .mul(getFactor(order.type)); + + totalQuantityFromBuyTransactions = + totalQuantityFromBuyTransactions.plus(order.quantity); + + totalInvestmentFromBuyTransactions = + totalInvestmentFromBuyTransactions.plus(transactionInvestment); + + totalInvestmentFromBuyTransactionsWithCurrencyEffect = + totalInvestmentFromBuyTransactionsWithCurrencyEffect.plus( + transactionInvestmentWithCurrencyEffect + ); + } else if (order.type === 'SELL') { + if (totalUnits.gt(0)) { + transactionInvestment = totalInvestment + .div(totalUnits) + .mul(order.quantity) + .mul(getFactor(order.type)); + transactionInvestmentWithCurrencyEffect = + totalInvestmentWithCurrencyEffect + .div(totalUnits) + .mul(order.quantity) + .mul(getFactor(order.type)); + } + } if (PortfolioCalculator.ENABLE_LOGGING) { console.log('order.quantity', order.quantity.toNumber()); @@ -815,140 +515,90 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { ); } - let valueOfInvestment; - let valueOfInvestmentWithCurrencyEffect; - let totalInvestmentBeforeTransaction; - let totalInvestmentBeforeTransactionWithCurrencyEffect; - ({ - valueOfInvestment, - valueOfInvestmentWithCurrencyEffect, - totalInvestmentBeforeTransaction, - totalInvestmentBeforeTransactionWithCurrencyEffect, - totalInvestment, - totalInvestmentWithCurrencyEffect, - initialValue, - initialValueWithCurrencyEffect, - fees, - feesWithCurrencyEffect, - totalUnits - } = this.calculateInvestmentValues( - totalInvestment, - totalInvestmentWithCurrencyEffect, - transactionInvestment, - transactionInvestmentWithCurrencyEffect, - i, - indexOfStartOrder, - initialValue, - valueOfInvestmentBeforeTransaction, - initialValueWithCurrencyEffect, - valueOfInvestmentBeforeTransactionWithCurrencyEffect, - fees, - order, - feesWithCurrencyEffect, - totalUnits - )); - - ({ - lastAveragePrice, - lastAveragePriceWithCurrencyEffect, - grossPerformanceFromSells, - grossPerformanceFromSellsWithCurrencyEffect, - grossPerformance, - grossPerformanceWithCurrencyEffect - } = this.calculatePerformances( - order, - lastAveragePrice, - lastAveragePriceWithCurrencyEffect, - grossPerformanceFromSells, - grossPerformanceFromSellsWithCurrencyEffect, - totalQuantityFromBuyTransactions, - totalInvestmentFromBuyTransactions, - totalInvestmentFromBuyTransactionsWithCurrencyEffect, - valueOfInvestment, - totalInvestment, - valueOfInvestmentWithCurrencyEffect, - totalInvestmentWithCurrencyEffect, - grossPerformance, - grossPerformanceWithCurrencyEffect - )); + const totalInvestmentBeforeTransaction = totalInvestment; - if (order.itemType === 'start') { - feesAtStartDate = fees; - feesAtStartDateWithCurrencyEffect = feesWithCurrencyEffect; - grossPerformanceAtStartDate = grossPerformance; + const totalInvestmentBeforeTransactionWithCurrencyEffect = + totalInvestmentWithCurrencyEffect; - grossPerformanceAtStartDateWithCurrencyEffect = - grossPerformanceWithCurrencyEffect; - } + totalInvestment = totalInvestment.plus(transactionInvestment); - if ( - i > indexOfStartOrder && - ['BUY', 'SELL', 'STAKE'].includes(order.type) - ) { - // Only consider periods with an investment for the calculation of - // the time weighted investment - ({ - totalInvestmentDays, - sumOfTimeWeightedInvestments, - sumOfTimeWeightedInvestmentsWithCurrencyEffect - } = this.calculateTimeWeightedInvestments( - valueOfInvestmentBeforeTransaction, - order, - orders, - i, - totalInvestmentDays, - sumOfTimeWeightedInvestments, - valueAtStartDate, - investmentAtStartDate, - totalInvestmentBeforeTransaction, - sumOfTimeWeightedInvestmentsWithCurrencyEffect, - valueAtStartDateWithCurrencyEffect, - investmentAtStartDateWithCurrencyEffect, - totalInvestmentBeforeTransactionWithCurrencyEffect, - isChartMode, - currentValues, - valueOfInvestment, - currentValuesWithCurrencyEffect, - valueOfInvestmentWithCurrencyEffect, - netPerformanceValues, - grossPerformance, - grossPerformanceAtStartDate, - fees, - feesAtStartDate, - netPerformanceValuesWithCurrencyEffect, - grossPerformanceWithCurrencyEffect, - grossPerformanceAtStartDateWithCurrencyEffect, - feesWithCurrencyEffect, - feesAtStartDateWithCurrencyEffect, - investmentValuesAccumulated, - totalInvestment, - investmentValuesAccumulatedWithCurrencyEffect, - totalInvestmentWithCurrencyEffect, - investmentValuesWithCurrencyEffect, - transactionInvestmentWithCurrencyEffect, - timeWeightedInvestmentValues, - timeWeightedInvestmentValuesWithCurrencyEffect - )); + totalInvestmentWithCurrencyEffect = + totalInvestmentWithCurrencyEffect.plus( + transactionInvestmentWithCurrencyEffect + ); + + if (i >= indexOfStartOrder && !initialValue) { + if ( + i === indexOfStartOrder && + !valueOfInvestmentBeforeTransaction.eq(0) + ) { + initialValue = valueOfInvestmentBeforeTransaction; + + initialValueWithCurrencyEffect = + valueOfInvestmentBeforeTransactionWithCurrencyEffect; + } else if (transactionInvestment.gt(0)) { + initialValue = transactionInvestment; + + initialValueWithCurrencyEffect = + transactionInvestmentWithCurrencyEffect; + } } - if (PortfolioCalculator.ENABLE_LOGGING) { - console.log('totalInvestment', totalInvestment.toNumber()); + fees = fees.plus(order.feeInBaseCurrency ?? 0); - console.log( - 'totalInvestmentWithCurrencyEffect', - totalInvestmentWithCurrencyEffect.toNumber() - ); + feesWithCurrencyEffect = feesWithCurrencyEffect.plus( + order.feeInBaseCurrencyWithCurrencyEffect ?? 0 + ); - console.log( - 'totalGrossPerformance', - grossPerformance.minus(grossPerformanceAtStartDate).toNumber() + totalUnits = totalUnits.plus(order.quantity.mul(getFactor(order.type))); + + const valueOfInvestment = totalUnits.mul(order.unitPriceInBaseCurrency); + + const valueOfInvestmentWithCurrencyEffect = totalUnits.mul( + order.unitPriceInBaseCurrencyWithCurrencyEffect + ); + + const grossPerformanceFromSell = + order.type === 'SELL' + ? order.unitPriceInBaseCurrency + .minus(lastAveragePrice) + .mul(order.quantity) + : new Big(0); + + const grossPerformanceFromSellWithCurrencyEffect = + order.type === 'SELL' + ? order.unitPriceInBaseCurrencyWithCurrencyEffect + .minus(lastAveragePriceWithCurrencyEffect) + .mul(order.quantity) + : new Big(0); + + grossPerformanceFromSells = grossPerformanceFromSells.plus( + grossPerformanceFromSell + ); + + grossPerformanceFromSellsWithCurrencyEffect = + grossPerformanceFromSellsWithCurrencyEffect.plus( + grossPerformanceFromSellWithCurrencyEffect ); + lastAveragePrice = totalQuantityFromBuyTransactions.eq(0) + ? new Big(0) + : totalInvestmentFromBuyTransactions.div( + totalQuantityFromBuyTransactions + ); + + lastAveragePriceWithCurrencyEffect = totalQuantityFromBuyTransactions.eq( + 0 + ) + ? new Big(0) + : totalInvestmentFromBuyTransactionsWithCurrencyEffect.div( + totalQuantityFromBuyTransactions + ); + + if (PortfolioCalculator.ENABLE_LOGGING) { console.log( - 'totalGrossPerformanceWithCurrencyEffect', - grossPerformanceWithCurrencyEffect - .minus(grossPerformanceAtStartDateWithCurrencyEffect) - .toNumber() + 'grossPerformanceFromSells', + grossPerformanceFromSells.toNumber() ); console.log( 'grossPerformanceFromSellWithCurrencyEffect', @@ -1226,9 +876,9 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { // differs from the buying price. dateRange === 'max' ? new Big(0) - : netPerformanceValuesWithCurrencyEffect[ + : (netPerformanceValuesWithCurrencyEffect[ format(startDate, DATE_FORMAT) - ] ?? new Big(0) + ] ?? new Big(0)) ) ?? new Big(0); netPerformancePercentageWithCurrencyEffectMap[dateRange] = average.gt(0) @@ -1274,6 +924,7 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { ].toFixed(2)}%` ); } + return { currentValues, currentValuesWithCurrencyEffect, @@ -1292,10 +943,15 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { netPerformanceWithCurrencyEffectMap, timeWeightedInvestmentValues, timeWeightedInvestmentValuesWithCurrencyEffect, + totalAccountBalanceInBaseCurrency, totalDividend, totalDividendInBaseCurrency, totalInterest, totalInterestInBaseCurrency, + totalInvestment, + totalInvestmentWithCurrencyEffect, + totalLiabilities, + totalLiabilitiesInBaseCurrency, totalValuables, totalValuablesInBaseCurrency, grossPerformance: totalGrossPerformance, @@ -1306,7 +962,9 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { timeWeightedInvestment: timeWeightedAverageInvestmentBetweenStartAndEndDate, timeWeightedInvestmentWithCurrencyEffect: - timeWeightedAverageInvestmentBetweenStartAndEndDateWithCurrencyEffect + timeWeightedAverageInvestmentBetweenStartAndEndDateWithCurrencyEffect, + netPerformanceValuesPercentage: {}, + unitPrices: {} }; } }