Browse Source

Migrate overall performance calculation to time weighted

pull/2778/head
Reto Kaul 2 years ago
committed by Thomas Kaul
parent
commit
53efd4f79a
  1. 1
      apps/api/src/app/portfolio/portfolio-calculator-baln-buy-and-sell.spec.ts
  2. 1
      apps/api/src/app/portfolio/portfolio-calculator-baln-buy.spec.ts
  3. 1
      apps/api/src/app/portfolio/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts
  4. 1
      apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell-partially.spec.ts
  5. 1
      apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell.spec.ts
  6. 58
      apps/api/src/app/portfolio/portfolio-calculator.ts
  7. 1
      libs/common/src/lib/interfaces/timeline-position.interface.ts

1
apps/api/src/app/portfolio/portfolio-calculator-baln-buy-and-sell.spec.ts

@ -92,6 +92,7 @@ describe('PortfolioCalculator', () => {
marketPrice: 148.9, marketPrice: 148.9,
quantity: new Big('0'), quantity: new Big('0'),
symbol: 'BALN.SW', symbol: 'BALN.SW',
timeWeightedInvestment: new Big('285.8'),
transactionCount: 2 transactionCount: 2
} }
], ],

1
apps/api/src/app/portfolio/portfolio-calculator-baln-buy.spec.ts

@ -81,6 +81,7 @@ describe('PortfolioCalculator', () => {
marketPrice: 148.9, marketPrice: 148.9,
quantity: new Big('2'), quantity: new Big('2'),
symbol: 'BALN.SW', symbol: 'BALN.SW',
timeWeightedInvestment: new Big('273.2'),
transactionCount: 1 transactionCount: 1
} }
], ],

1
apps/api/src/app/portfolio/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts

@ -92,6 +92,7 @@ describe('PortfolioCalculator', () => {
marketPrice: 13657.2, marketPrice: 13657.2,
quantity: new Big('1'), quantity: new Big('1'),
symbol: 'BTCUSD', symbol: 'BTCUSD',
timeWeightedInvestment: new Big('640.56763686131386861314'),
transactionCount: 2 transactionCount: 2
} }
], ],

1
apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell-partially.spec.ts

@ -92,6 +92,7 @@ describe('PortfolioCalculator', () => {
marketPrice: 87.8, marketPrice: 87.8,
quantity: new Big('1'), quantity: new Big('1'),
symbol: 'NOVN.SW', symbol: 'NOVN.SW',
timeWeightedInvestment: new Big('145.10285714285714285714'),
transactionCount: 2 transactionCount: 2
} }
], ],

1
apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell.spec.ts

@ -112,6 +112,7 @@ describe('PortfolioCalculator', () => {
marketPrice: 87.8, marketPrice: 87.8,
quantity: new Big('0'), quantity: new Big('0'),
symbol: 'NOVN.SW', symbol: 'NOVN.SW',
timeWeightedInvestment: new Big('151.6'),
transactionCount: 2 transactionCount: 2
} }
], ],

58
apps/api/src/app/portfolio/portfolio-calculator.ts

@ -464,7 +464,6 @@ export class PortfolioCalculator {
if (firstIndex > 0) { if (firstIndex > 0) {
firstIndex--; firstIndex--;
} }
const initialValues: { [symbol: string]: Big } = {};
const positions: TimelinePosition[] = []; const positions: TimelinePosition[] = [];
let hasAnySymbolMetricsErrors = false; let hasAnySymbolMetricsErrors = false;
@ -478,9 +477,9 @@ export class PortfolioCalculator {
grossPerformance, grossPerformance,
grossPerformancePercentage, grossPerformancePercentage,
hasErrors, hasErrors,
initialValue,
netPerformance, netPerformance,
netPerformancePercentage netPerformancePercentage,
timeWeightedInvestment
} = this.getSymbolMetrics({ } = this.getSymbolMetrics({
end, end,
marketSymbolMap, marketSymbolMap,
@ -489,9 +488,9 @@ export class PortfolioCalculator {
}); });
hasAnySymbolMetricsErrors = hasAnySymbolMetricsErrors || hasErrors; hasAnySymbolMetricsErrors = hasAnySymbolMetricsErrors || hasErrors;
initialValues[item.symbol] = initialValue;
positions.push({ positions.push({
timeWeightedInvestment,
averagePrice: item.quantity.eq(0) averagePrice: item.quantity.eq(0)
? new Big(0) ? new Big(0)
: item.investment.div(item.quantity), : item.investment.div(item.quantity),
@ -526,7 +525,7 @@ export class PortfolioCalculator {
} }
} }
const overall = this.calculateOverallPerformance(positions, initialValues); const overall = this.calculateOverallPerformance(positions);
return { return {
...overall, ...overall,
@ -749,18 +748,13 @@ export class PortfolioCalculator {
}; };
} }
private calculateOverallPerformance( private calculateOverallPerformance(positions: TimelinePosition[]) {
positions: TimelinePosition[],
initialValues: { [symbol: string]: Big }
) {
let currentValue = new Big(0); let currentValue = new Big(0);
let grossPerformance = new Big(0); let grossPerformance = new Big(0);
let grossPerformancePercentage = new Big(0);
let hasErrors = false; let hasErrors = false;
let netPerformance = new Big(0); let netPerformance = new Big(0);
let netPerformancePercentage = new Big(0);
let sumOfWeights = new Big(0);
let totalInvestment = new Big(0); let totalInvestment = new Big(0);
let totalTimeWeightedInvestment = new Big(0);
for (const currentPosition of positions) { for (const currentPosition of positions) {
if (currentPosition.marketPrice) { if (currentPosition.marketPrice) {
@ -783,21 +777,9 @@ export class PortfolioCalculator {
hasErrors = true; hasErrors = true;
} }
if (currentPosition.grossPerformancePercentage) { if (currentPosition.timeWeightedInvestment) {
// Use the average from the initial value and the current investment as totalTimeWeightedInvestment = totalTimeWeightedInvestment.plus(
// a weight currentPosition.timeWeightedInvestment
const weight = (initialValues[currentPosition.symbol] ?? new Big(0))
.plus(currentPosition.investment)
.div(2);
sumOfWeights = sumOfWeights.plus(weight);
grossPerformancePercentage = grossPerformancePercentage.plus(
currentPosition.grossPerformancePercentage.mul(weight)
);
netPerformancePercentage = netPerformancePercentage.plus(
currentPosition.netPerformancePercentage.mul(weight)
); );
} else if (!currentPosition.quantity.eq(0)) { } else if (!currentPosition.quantity.eq(0)) {
Logger.warn( Logger.warn(
@ -808,22 +790,18 @@ export class PortfolioCalculator {
} }
} }
if (sumOfWeights.gt(0)) {
grossPerformancePercentage = grossPerformancePercentage.div(sumOfWeights);
netPerformancePercentage = netPerformancePercentage.div(sumOfWeights);
} else {
grossPerformancePercentage = new Big(0);
netPerformancePercentage = new Big(0);
}
return { return {
currentValue, currentValue,
grossPerformance, grossPerformance,
grossPerformancePercentage,
hasErrors, hasErrors,
netPerformance, netPerformance,
netPerformancePercentage, totalInvestment,
totalInvestment netPerformancePercentage: !totalTimeWeightedInvestment.eq(0)
? netPerformance.div(totalTimeWeightedInvestment)
: new Big(0),
grossPerformancePercentage: !totalTimeWeightedInvestment.eq(0)
? grossPerformance.div(totalTimeWeightedInvestment)
: new Big(0)
}; };
} }
@ -1438,7 +1416,9 @@ export class PortfolioCalculator {
timeWeightedInvestmentValues, timeWeightedInvestmentValues,
grossPerformance: totalGrossPerformance, grossPerformance: totalGrossPerformance,
hasErrors: totalUnits.gt(0) && (!initialValue || !unitPriceAtEndDate), hasErrors: totalUnits.gt(0) && (!initialValue || !unitPriceAtEndDate),
netPerformance: totalNetPerformance netPerformance: totalNetPerformance,
timeWeightedInvestment:
timeWeightedAverageInvestmentBetweenStartAndEndDate
}; };
} }

1
libs/common/src/lib/interfaces/timeline-position.interface.ts

@ -16,5 +16,6 @@ export interface TimelinePosition {
quantity: Big; quantity: Big;
symbol: string; symbol: string;
tags?: Tag[]; tags?: Tag[];
timeWeightedInvestment: Big;
transactionCount: number; transactionCount: number;
} }

Loading…
Cancel
Save