diff --git a/apps/api/src/app/core/portfolio-calculator.spec.ts b/apps/api/src/app/core/portfolio-calculator.spec.ts index fc1776a98..472098ac2 100644 --- a/apps/api/src/app/core/portfolio-calculator.spec.ts +++ b/apps/api/src/app/core/portfolio-calculator.spec.ts @@ -623,6 +623,9 @@ describe('PortfolioCalculator', () => { expect(currentPositions).toEqual({ hasErrors: false, + currentValue: new Big('657.62'), + grossPerformance: new Big('-61.84'), + grossPerformancePercentage: new Big('-0.08456342256692519389'), positions: [ { averagePrice: new Big('719.46'), @@ -658,6 +661,9 @@ describe('PortfolioCalculator', () => { expect(currentPositions).toEqual({ hasErrors: false, + currentValue: new Big('657.62'), + grossPerformance: new Big('-61.84'), + grossPerformancePercentage: new Big('-0.08456342256692519389'), positions: [ { averagePrice: new Big('719.46'), @@ -693,6 +699,9 @@ describe('PortfolioCalculator', () => { expect(currentPositions).toEqual({ hasErrors: false, + currentValue: new Big('657.62'), + grossPerformance: new Big('-9.04'), + grossPerformancePercentage: new Big('-0.01206012060120601206'), positions: [ { averagePrice: new Big('719.46'), @@ -728,6 +737,9 @@ describe('PortfolioCalculator', () => { expect(currentPositions).toEqual({ hasErrors: false, + currentValue: new Big('4871.5'), + grossPerformance: new Big('240.4'), + grossPerformancePercentage: new Big('0.08908669575467971768'), positions: [ { averagePrice: new Big('178.438'), @@ -805,6 +817,9 @@ describe('PortfolioCalculator', () => { spy.mockRestore(); expect(currentPositions).toEqual({ hasErrors: false, + currentValue: new Big('3897.2'), + grossPerformance: new Big('303.2'), + grossPerformancePercentage: new Big('0.2759628350186678759'), positions: [ { averagePrice: new Big('146.185'), @@ -875,6 +890,9 @@ describe('PortfolioCalculator', () => { expect(currentPositions).toEqual({ hasErrors: false, + currentValue: new Big('1192327.999656600298238721'), + grossPerformance: new Big('92327.999656600898394721'), + grossPerformancePercentage: new Big('0.09788598099999947809'), positions: [ { averagePrice: new Big('1.01287018290924923237'), // 1'100'000 / 1'086'022.689344542 diff --git a/apps/api/src/app/core/portfolio-calculator.ts b/apps/api/src/app/core/portfolio-calculator.ts index bcba3df54..558e0f773 100644 --- a/apps/api/src/app/core/portfolio-calculator.ts +++ b/apps/api/src/app/core/portfolio-calculator.ts @@ -114,11 +114,17 @@ export class PortfolioCalculator { public async getCurrentPositions(start: Date): Promise<{ hasErrors: boolean; positions: TimelinePosition[]; + grossPerformance: Big; + grossPerformancePercentage: Big; + currentValue: Big; }> { if (!this.transactionPoints?.length) { return { hasErrors: false, - positions: [] + positions: [], + grossPerformance: new Big(0), + grossPerformancePercentage: new Big(0), + currentValue: new Big(0) }; } @@ -197,6 +203,8 @@ export class PortfolioCalculator { const invalidSymbols = []; const lastInvestments: { [symbol: string]: Big } = {}; const lastQuantities: { [symbol: string]: Big } = {}; + const initialValues: { [symbol: string]: Big } = {}; + for (let i = firstIndex; i < this.transactionPoints.length; i++) { const currentDate = i === firstIndex ? startString : this.transactionPoints[i].date; @@ -233,6 +241,9 @@ export class PortfolioCalculator { initialValue = item.investment; investedValue = item.investment; } + if (i === firstIndex || !initialValues[item.symbol]) { + initialValues[item.symbol] = initialValue; + } if (!initialValue) { invalidSymbols.push(item.symbol); hasErrors = true; @@ -287,7 +298,48 @@ export class PortfolioCalculator { }); } - return { hasErrors, positions }; + let currentValue = new Big(0); + let overallGrossPerformance = new Big(0); + let grossPerformancePercentage = new Big(1); + let completeInitialValue = new Big(0); + for (const currentPosition of positions) { + currentValue = currentValue.add( + new Big(currentPosition.marketPrice).mul(currentPosition.quantity) + ); + if (currentPosition.grossPerformance) { + overallGrossPerformance = overallGrossPerformance.plus( + currentPosition.grossPerformance + ); + } else { + hasErrors = true; + } + if ( + currentPosition.grossPerformancePercentage && + initialValues[currentPosition.symbol] + ) { + const currentInitialValue = initialValues[currentPosition.symbol]; + completeInitialValue = completeInitialValue.plus(currentInitialValue); + grossPerformancePercentage = grossPerformancePercentage.plus( + currentPosition.grossPerformancePercentage.mul(currentInitialValue) + ); + } else { + console.log(initialValues); + console.error( + 'initial value is missing for symbol', + currentPosition.symbol + ); + hasErrors = true; + } + } + + return { + hasErrors, + positions, + grossPerformance: overallGrossPerformance, + grossPerformancePercentage: + grossPerformancePercentage.div(completeInitialValue), + currentValue + }; } public async calculateTimeline( diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index ac3b1133e..280d313a6 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -484,34 +484,12 @@ export class PortfolioService { startDate ); - let currentValue = new Big(0); - let grossPerformance = new Big(0); - let grossPerformancePercentage = new Big(1); - let hasErrors = false; - for (const currentPosition of currentPositions.positions) { - currentValue = currentValue.add( - new Big(currentPosition.marketPrice).mul(currentPosition.quantity) - ); - if (currentPosition.grossPerformance) { - grossPerformance = grossPerformance.plus( - currentPosition.grossPerformance - ); - } else { - hasErrors = true; - } - if (currentPosition.grossPerformancePercentage) { - grossPerformancePercentage = grossPerformancePercentage.mul( - currentPosition.grossPerformancePercentage.plus(1) - ); - } else { - hasErrors = true; - } - } - - const currentGrossPerformance = grossPerformance.toNumber(); - const currentGrossPerformancePercent = grossPerformancePercentage - .minus(1) - .toNumber(); + const hasErrors = currentPositions.hasErrors; + const currentValue = currentPositions.currentValue.toNumber(); + const currentGrossPerformance = + currentPositions.grossPerformance.toNumber(); + const currentGrossPerformancePercent = + currentPositions.grossPerformancePercentage.toNumber(); return { hasErrors: currentPositions.hasErrors || hasErrors, performance: { @@ -520,7 +498,7 @@ export class PortfolioService { // TODO: the next two should include fees currentNetPerformance: currentGrossPerformance, currentNetPerformancePercent: currentGrossPerformancePercent, - currentValue: currentValue.toNumber() + currentValue: currentValue } }; }