diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts index c3edc86d9..48fcaf343 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts @@ -29,7 +29,7 @@ import { max, subDays } from 'date-fns'; -import { isNumber, last, uniq } from 'lodash'; +import { last, uniq } from 'lodash'; export abstract class PortfolioCalculator { protected static readonly ENABLE_LOGGING = false; @@ -80,23 +80,6 @@ export abstract class PortfolioCalculator { positions: TimelinePosition[] ): CurrentPositions; - public getAnnualizedPerformancePercent({ - daysInMarket, - netPerformancePercent - }: { - daysInMarket: number; - netPerformancePercent: Big; - }): Big { - if (isNumber(daysInMarket) && daysInMarket > 0) { - const exponent = new Big(365).div(daysInMarket).toNumber(); - return new Big( - Math.pow(netPerformancePercent.plus(1).toNumber(), exponent) - ).minus(1); - } - - return new Big(0); - } - public async getChartData({ end = new Date(Date.now()), start, 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 b68f4358d..365593846 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 @@ -1,12 +1,7 @@ -import { - PerformanceCalculationType, - PortfolioCalculatorFactory -} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; +import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; -import { Big } from 'big.js'; - describe('PortfolioCalculator', () => { let currentRateService: CurrentRateService; let exchangeRateDataService: ExchangeRateDataService; @@ -28,64 +23,5 @@ describe('PortfolioCalculator', () => { ); }); - describe('annualized performance percentage', () => { - it('Get annualized performance', async () => { - const portfolioCalculator = factory.createCalculator({ - activities: [], - calculationType: PerformanceCalculationType.TWR, - currency: 'CHF' - }); - - expect( - portfolioCalculator - .getAnnualizedPerformancePercent({ - daysInMarket: NaN, // differenceInDays of date-fns returns NaN for the same day - netPerformancePercent: new Big(0) - }) - .toNumber() - ).toEqual(0); - - expect( - portfolioCalculator - .getAnnualizedPerformancePercent({ - daysInMarket: 0, - netPerformancePercent: new Big(0) - }) - .toNumber() - ).toEqual(0); - - /** - * Source: https://www.readyratios.com/reference/analysis/annualized_rate.html - */ - expect( - portfolioCalculator - .getAnnualizedPerformancePercent({ - daysInMarket: 65, // < 1 year - netPerformancePercent: new Big(0.1025) - }) - .toNumber() - ).toBeCloseTo(0.729705); - - expect( - portfolioCalculator - .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( - portfolioCalculator - .getAnnualizedPerformancePercent({ - daysInMarket: 575, // > 1 year - netPerformancePercent: new Big(0.2374) - }) - .toNumber() - ).toBeCloseTo(0.145); - }); - }); + test.skip('Skip empty test', () => 1); }); diff --git a/apps/api/src/app/portfolio/portfolio.service.spec.ts b/apps/api/src/app/portfolio/portfolio.service.spec.ts new file mode 100644 index 000000000..7654b7df3 --- /dev/null +++ b/apps/api/src/app/portfolio/portfolio.service.spec.ts @@ -0,0 +1,78 @@ +import { Big } from 'big.js'; + +import { PortfolioService } from './portfolio.service'; + +describe('PortfolioService', () => { + let portfolioService: PortfolioService; + + beforeAll(async () => { + portfolioService = new PortfolioService( + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ); + }); + + describe('annualized performance percentage', () => { + it('Get annualized performance', async () => { + expect( + portfolioService + .getAnnualizedPerformancePercent({ + daysInMarket: NaN, // differenceInDays of date-fns returns NaN for the same day + netPerformancePercent: new Big(0) + }) + .toNumber() + ).toEqual(0); + + expect( + portfolioService + .getAnnualizedPerformancePercent({ + daysInMarket: 0, + netPerformancePercent: new Big(0) + }) + .toNumber() + ).toEqual(0); + + /** + * Source: https://www.readyratios.com/reference/analysis/annualized_rate.html + */ + expect( + portfolioService + .getAnnualizedPerformancePercent({ + daysInMarket: 65, // < 1 year + netPerformancePercent: new Big(0.1025) + }) + .toNumber() + ).toBeCloseTo(0.729705); + + expect( + portfolioService + .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( + portfolioService + .getAnnualizedPerformancePercent({ + daysInMarket: 575, // > 1 year + netPerformancePercent: new Big(0.2374) + }) + .toNumber() + ).toBeCloseTo(0.145); + }); + }); +}); diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 0bdafec48..17a1ea4a0 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -78,7 +78,7 @@ import { parseISO, set } from 'date-fns'; -import { isEmpty, last, uniq, uniqBy } from 'lodash'; +import { isEmpty, isNumber, last, uniq, uniqBy } from 'lodash'; import { PortfolioCalculator } from './calculator/portfolio-calculator'; import { @@ -217,6 +217,24 @@ export class PortfolioService { }; } + public getAnnualizedPerformancePercent({ + daysInMarket, + netPerformancePercent + }: { + daysInMarket: number; + netPerformancePercent: Big; + }): Big { + if (isNumber(daysInMarket) && daysInMarket > 0) { + const exponent = new Big(365).div(daysInMarket).toNumber(); + + return new Big( + Math.pow(netPerformancePercent.plus(1).toNumber(), exponent) + ).minus(1); + } + + return new Big(0); + } + public async getDividends({ activities, groupBy @@ -1769,34 +1787,20 @@ export class PortfolioService { const daysInMarket = differenceInDays(new Date(), firstOrderDate); - const annualizedPerformancePercent = this.calculatorFactory - .createCalculator({ - activities: [], - calculationType: PerformanceCalculationType.TWR, - currency: userCurrency - }) - .getAnnualizedPerformancePercent({ + const annualizedPerformancePercent = this.getAnnualizedPerformancePercent({ + daysInMarket, + netPerformancePercent: new Big( + performanceInformation.performance.currentNetPerformancePercent + ) + })?.toNumber(); + + const annualizedPerformancePercentWithCurrencyEffect = + this.getAnnualizedPerformancePercent({ daysInMarket, netPerformancePercent: new Big( - performanceInformation.performance.currentNetPerformancePercent + performanceInformation.performance.currentNetPerformancePercentWithCurrencyEffect ) - }) - ?.toNumber(); - - const annualizedPerformancePercentWithCurrencyEffect = - this.calculatorFactory - .createCalculator({ - activities: [], - calculationType: PerformanceCalculationType.TWR, - currency: userCurrency - }) - .getAnnualizedPerformancePercent({ - daysInMarket, - netPerformancePercent: new Big( - performanceInformation.performance.currentNetPerformancePercentWithCurrencyEffect - ) - }) - ?.toNumber(); + })?.toNumber(); return { ...performanceInformation.performance,