Feature/improve annualized performance (#651)

* Improve annualized performance calculation

* Update changelog
pull/653/head
Thomas Kaul 3 years ago committed by GitHub
parent b464fefc57
commit 5d3bbb8f30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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/), 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). 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 ## 1.107.0 - 24.01.2022
### Added ### Added

@ -6,7 +6,7 @@ export interface CurrentPositions {
positions: TimelinePosition[]; positions: TimelinePosition[];
grossPerformance: Big; grossPerformance: Big;
grossPerformancePercentage: Big; grossPerformancePercentage: Big;
netAnnualizedPerformance: Big; netAnnualizedPerformance?: Big;
netPerformance: Big; netPerformance: Big;
netPerformancePercentage: Big; netPerformancePercentage: Big;
currentValue: Big; currentValue: Big;

@ -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);
});
});
});

@ -10,7 +10,6 @@ import {
addMilliseconds, addMilliseconds,
addMonths, addMonths,
addYears, addYears,
differenceInDays,
endOfDay, endOfDay,
format, format,
isAfter, isAfter,
@ -130,7 +129,10 @@ export class PortfolioCalculatorNew {
netPerformancePercent: Big; netPerformancePercent: Big;
}): Big { }): Big {
if (isNumber(daysInMarket) && daysInMarket > 0) { 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); return new Big(0);
@ -151,7 +153,6 @@ export class PortfolioCalculatorNew {
hasErrors: false, hasErrors: false,
grossPerformance: new Big(0), grossPerformance: new Big(0),
grossPerformancePercentage: new Big(0), grossPerformancePercentage: new Big(0),
netAnnualizedPerformance: new Big(0),
netPerformance: new Big(0), netPerformance: new Big(0),
netPerformancePercentage: new Big(0), netPerformancePercentage: new Big(0),
positions: [], positions: [],
@ -632,10 +633,6 @@ export class PortfolioCalculatorNew {
let netPerformance = new Big(0); let netPerformance = new Big(0);
let netPerformancePercentage = new Big(0); let netPerformancePercentage = new Big(0);
let completeInitialValue = 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) { for (const currentPosition of positions) {
if (currentPosition.marketPrice) { if (currentPosition.marketPrice) {
@ -664,16 +661,6 @@ export class PortfolioCalculatorNew {
grossPerformancePercentage = grossPerformancePercentage.plus( grossPerformancePercentage = grossPerformancePercentage.plus(
currentPosition.grossPerformancePercentage.mul(currentInitialValue) currentPosition.grossPerformancePercentage.mul(currentInitialValue)
); );
netAnnualizedPerformance = netAnnualizedPerformance.plus(
this.getAnnualizedPerformancePercent({
daysInMarket: differenceInDays(
today,
parseDate(currentPosition.firstBuyDate)
),
netPerformancePercent: currentPosition.netPerformancePercentage
}).mul(currentInitialValue)
);
netPerformancePercentage = netPerformancePercentage.plus( netPerformancePercentage = netPerformancePercentage.plus(
currentPosition.netPerformancePercentage.mul(currentInitialValue) currentPosition.netPerformancePercentage.mul(currentInitialValue)
); );
@ -690,8 +677,6 @@ export class PortfolioCalculatorNew {
grossPerformancePercentage.div(completeInitialValue); grossPerformancePercentage.div(completeInitialValue);
netPerformancePercentage = netPerformancePercentage =
netPerformancePercentage.div(completeInitialValue); netPerformancePercentage.div(completeInitialValue);
netAnnualizedPerformance =
netAnnualizedPerformance.div(completeInitialValue);
} }
return { return {
@ -699,7 +684,6 @@ export class PortfolioCalculatorNew {
grossPerformance, grossPerformance,
grossPerformancePercentage, grossPerformancePercentage,
hasErrors, hasErrors,
netAnnualizedPerformance,
netPerformance, netPerformance,
netPerformancePercentage, netPerformancePercentage,
totalInvestment totalInvestment

@ -42,6 +42,7 @@ import { REQUEST } from '@nestjs/core';
import { AssetClass, DataSource, Type as TypeOfOrder } from '@prisma/client'; import { AssetClass, DataSource, Type as TypeOfOrder } from '@prisma/client';
import Big from 'big.js'; import Big from 'big.js';
import { import {
differenceInDays,
endOfToday, endOfToday,
format, format,
isAfter, isAfter,
@ -480,7 +481,6 @@ export class PortfolioServiceNew {
} = position; } = position;
// Convert investment, gross and net performance to currency of user // Convert investment, gross and net performance to currency of user
const userCurrency = this.request.user.Settings.currency;
const investment = this.exchangeRateDataService.toCurrency( const investment = this.exchangeRateDataService.toCurrency(
position.investment?.toNumber(), position.investment?.toNumber(),
currency, currency,
@ -736,7 +736,6 @@ export class PortfolioServiceNew {
return { return {
hasErrors: false, hasErrors: false,
performance: { performance: {
annualizedPerformancePercent: 0,
currentGrossPerformance: 0, currentGrossPerformance: 0,
currentGrossPerformancePercent: 0, currentGrossPerformancePercent: 0,
currentNetPerformance: 0, currentNetPerformance: 0,
@ -755,8 +754,6 @@ export class PortfolioServiceNew {
); );
const hasErrors = currentPositions.hasErrors; const hasErrors = currentPositions.hasErrors;
const annualizedPerformancePercent =
currentPositions.netAnnualizedPerformance.toNumber();
const currentValue = currentPositions.currentValue.toNumber(); const currentValue = currentPositions.currentValue.toNumber();
const currentGrossPerformance = const currentGrossPerformance =
currentPositions.grossPerformance.toNumber(); currentPositions.grossPerformance.toNumber();
@ -769,7 +766,6 @@ export class PortfolioServiceNew {
return { return {
hasErrors: currentPositions.hasErrors || hasErrors, hasErrors: currentPositions.hasErrors || hasErrors,
performance: { performance: {
annualizedPerformancePercent,
currentGrossPerformance, currentGrossPerformance,
currentGrossPerformancePercent, currentGrossPerformancePercent,
currentNetPerformance, currentNetPerformance,
@ -898,8 +894,24 @@ export class PortfolioServiceNew {
.plus(performanceInformation.performance.currentValue) .plus(performanceInformation.performance.currentValue)
.toNumber(); .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 { return {
...performanceInformation.performance, ...performanceInformation.performance,
annualizedPerformancePercent,
dividend, dividend,
fees, fees,
firstOrderDate, firstOrderDate,

@ -881,6 +881,8 @@ export class PortfolioService {
netWorth, netWorth,
totalBuy, totalBuy,
totalSell, totalSell,
annualizedPerformancePercent:
performanceInformation.performance.annualizedPerformancePercent,
cash: balance, cash: balance,
committedFunds: committedFunds.toNumber(), committedFunds: committedFunds.toNumber(),
ordersCount: orders.filter((order) => { ordersCount: orders.filter((order) => {

@ -1,5 +1,5 @@
export interface PortfolioPerformance { export interface PortfolioPerformance {
annualizedPerformancePercent: number; annualizedPerformancePercent?: number;
currentGrossPerformance: number; currentGrossPerformance: number;
currentGrossPerformancePercent: number; currentGrossPerformancePercent: number;
currentNetPerformance: number; currentNetPerformance: number;

Loading…
Cancel
Save