diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f6647cb8..329f43a3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed + +- Improved the annualized performance in the new calculation engine + ## 1.107.0 - 24.01.2022 ### Added diff --git a/apps/api/src/app/portfolio/interfaces/current-positions.interface.ts b/apps/api/src/app/portfolio/interfaces/current-positions.interface.ts index a02d2a6a6..29550b43a 100644 --- a/apps/api/src/app/portfolio/interfaces/current-positions.interface.ts +++ b/apps/api/src/app/portfolio/interfaces/current-positions.interface.ts @@ -6,7 +6,7 @@ export interface CurrentPositions { positions: TimelinePosition[]; grossPerformance: Big; grossPerformancePercentage: Big; - netAnnualizedPerformance: Big; + netAnnualizedPerformance?: Big; netPerformance: Big; netPerformancePercentage: Big; currentValue: Big; diff --git a/apps/api/src/app/portfolio/portfolio-calculator-new.spec.ts b/apps/api/src/app/portfolio/portfolio-calculator-new.spec.ts new file mode 100644 index 000000000..72e3091f1 --- /dev/null +++ b/apps/api/src/app/portfolio/portfolio-calculator-new.spec.ts @@ -0,0 +1,73 @@ +import Big from 'big.js'; + +import { CurrentRateService } from './current-rate.service'; +import { PortfolioCalculatorNew } from './portfolio-calculator-new'; + +describe('PortfolioCalculatorNew', () => { + let currentRateService: CurrentRateService; + + beforeEach(() => { + currentRateService = new CurrentRateService(null, null, null); + }); + + describe('annualized performance percentage', () => { + const portfolioCalculatorNew = new PortfolioCalculatorNew({ + currentRateService, + currency: 'USD', + orders: [] + }); + + it('Get annualized performance', async () => { + expect( + portfolioCalculatorNew + .getAnnualizedPerformancePercent({ + daysInMarket: NaN, // differenceInDays of date-fns returns NaN for the same day + netPerformancePercent: new Big(0) + }) + .toNumber() + ).toEqual(0); + + expect( + portfolioCalculatorNew + .getAnnualizedPerformancePercent({ + daysInMarket: 0, + netPerformancePercent: new Big(0) + }) + .toNumber() + ).toEqual(0); + + /** + * Source: https://www.readyratios.com/reference/analysis/annualized_rate.html + */ + expect( + portfolioCalculatorNew + .getAnnualizedPerformancePercent({ + daysInMarket: 65, // < 1 year + netPerformancePercent: new Big(0.1025) + }) + .toNumber() + ).toBeCloseTo(0.729705); + + expect( + portfolioCalculatorNew + .getAnnualizedPerformancePercent({ + daysInMarket: 365, // 1 year + netPerformancePercent: new Big(0.05) + }) + .toNumber() + ).toBeCloseTo(0.05); + + /** + * Source: https://www.investopedia.com/terms/a/annualized-total-return.asp#annualized-return-formula-and-calculation + */ + expect( + portfolioCalculatorNew + .getAnnualizedPerformancePercent({ + daysInMarket: 575, // > 1 year + netPerformancePercent: new Big(0.2374) + }) + .toNumber() + ).toBeCloseTo(0.145); + }); + }); +}); diff --git a/apps/api/src/app/portfolio/portfolio-calculator-new.ts b/apps/api/src/app/portfolio/portfolio-calculator-new.ts index 1f812a154..3ebad797c 100644 --- a/apps/api/src/app/portfolio/portfolio-calculator-new.ts +++ b/apps/api/src/app/portfolio/portfolio-calculator-new.ts @@ -10,7 +10,6 @@ import { addMilliseconds, addMonths, addYears, - differenceInDays, endOfDay, format, isAfter, @@ -130,7 +129,10 @@ export class PortfolioCalculatorNew { netPerformancePercent: Big; }): Big { if (isNumber(daysInMarket) && daysInMarket > 0) { - return netPerformancePercent.mul(daysInMarket).div(365); + const exponent = new Big(365).div(daysInMarket).toNumber(); + return new Big( + Math.pow(netPerformancePercent.plus(1).toNumber(), exponent) + ).minus(1); } return new Big(0); @@ -151,7 +153,6 @@ export class PortfolioCalculatorNew { hasErrors: false, grossPerformance: new Big(0), grossPerformancePercentage: new Big(0), - netAnnualizedPerformance: new Big(0), netPerformance: new Big(0), netPerformancePercentage: new Big(0), positions: [], @@ -632,10 +633,6 @@ export class PortfolioCalculatorNew { let netPerformance = new Big(0); let netPerformancePercentage = new Big(0); let completeInitialValue = new Big(0); - let netAnnualizedPerformance = new Big(0); - - // use Date.now() to use the mock for today - const today = new Date(Date.now()); for (const currentPosition of positions) { if (currentPosition.marketPrice) { @@ -664,16 +661,6 @@ export class PortfolioCalculatorNew { grossPerformancePercentage = grossPerformancePercentage.plus( currentPosition.grossPerformancePercentage.mul(currentInitialValue) ); - - netAnnualizedPerformance = netAnnualizedPerformance.plus( - this.getAnnualizedPerformancePercent({ - daysInMarket: differenceInDays( - today, - parseDate(currentPosition.firstBuyDate) - ), - netPerformancePercent: currentPosition.netPerformancePercentage - }).mul(currentInitialValue) - ); netPerformancePercentage = netPerformancePercentage.plus( currentPosition.netPerformancePercentage.mul(currentInitialValue) ); @@ -690,8 +677,6 @@ export class PortfolioCalculatorNew { grossPerformancePercentage.div(completeInitialValue); netPerformancePercentage = netPerformancePercentage.div(completeInitialValue); - netAnnualizedPerformance = - netAnnualizedPerformance.div(completeInitialValue); } return { @@ -699,7 +684,6 @@ export class PortfolioCalculatorNew { grossPerformance, grossPerformancePercentage, hasErrors, - netAnnualizedPerformance, netPerformance, netPerformancePercentage, totalInvestment diff --git a/apps/api/src/app/portfolio/portfolio.service-new.ts b/apps/api/src/app/portfolio/portfolio.service-new.ts index da6bd44aa..8396dd249 100644 --- a/apps/api/src/app/portfolio/portfolio.service-new.ts +++ b/apps/api/src/app/portfolio/portfolio.service-new.ts @@ -42,6 +42,7 @@ import { REQUEST } from '@nestjs/core'; import { AssetClass, DataSource, Type as TypeOfOrder } from '@prisma/client'; import Big from 'big.js'; import { + differenceInDays, endOfToday, format, isAfter, @@ -480,7 +481,6 @@ export class PortfolioServiceNew { } = position; // Convert investment, gross and net performance to currency of user - const userCurrency = this.request.user.Settings.currency; const investment = this.exchangeRateDataService.toCurrency( position.investment?.toNumber(), currency, @@ -736,7 +736,6 @@ export class PortfolioServiceNew { return { hasErrors: false, performance: { - annualizedPerformancePercent: 0, currentGrossPerformance: 0, currentGrossPerformancePercent: 0, currentNetPerformance: 0, @@ -755,8 +754,6 @@ export class PortfolioServiceNew { ); const hasErrors = currentPositions.hasErrors; - const annualizedPerformancePercent = - currentPositions.netAnnualizedPerformance.toNumber(); const currentValue = currentPositions.currentValue.toNumber(); const currentGrossPerformance = currentPositions.grossPerformance.toNumber(); @@ -769,7 +766,6 @@ export class PortfolioServiceNew { return { hasErrors: currentPositions.hasErrors || hasErrors, performance: { - annualizedPerformancePercent, currentGrossPerformance, currentGrossPerformancePercent, currentNetPerformance, @@ -898,8 +894,24 @@ export class PortfolioServiceNew { .plus(performanceInformation.performance.currentValue) .toNumber(); + const daysInMarket = differenceInDays(new Date(), firstOrderDate); + + const annualizedPerformancePercent = new PortfolioCalculatorNew({ + currency: userCurrency, + currentRateService: this.currentRateService, + orders: [] + }) + .getAnnualizedPerformancePercent({ + daysInMarket, + netPerformancePercent: new Big( + performanceInformation.performance.currentNetPerformancePercent + ) + }) + ?.toNumber(); + return { ...performanceInformation.performance, + annualizedPerformancePercent, dividend, fees, firstOrderDate, diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index fbbd5db0f..f54f4fe95 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -881,6 +881,8 @@ export class PortfolioService { netWorth, totalBuy, totalSell, + annualizedPerformancePercent: + performanceInformation.performance.annualizedPerformancePercent, cash: balance, committedFunds: committedFunds.toNumber(), ordersCount: orders.filter((order) => { diff --git a/libs/common/src/lib/interfaces/portfolio-performance.interface.ts b/libs/common/src/lib/interfaces/portfolio-performance.interface.ts index 3a2770786..670d69018 100644 --- a/libs/common/src/lib/interfaces/portfolio-performance.interface.ts +++ b/libs/common/src/lib/interfaces/portfolio-performance.interface.ts @@ -1,5 +1,5 @@ export interface PortfolioPerformance { - annualizedPerformancePercent: number; + annualizedPerformancePercent?: number; currentGrossPerformance: number; currentGrossPerformancePercent: number; currentNetPerformance: number;