From b183c45027ac68eb8c1a4bb4afc55ffae7851e13 Mon Sep 17 00:00:00 2001 From: gizmodus Date: Wed, 27 Dec 2023 15:55:35 +0100 Subject: [PATCH] Time weighted portfolio performance calculation (#2778) * Implement time weighted portfolio performance calculation * Update changelog --- CHANGELOG.md | 1 + ...folio-calculator-baln-buy-and-sell.spec.ts | 1 + .../portfolio-calculator-baln-buy.spec.ts | 1 + ...ator-btcusd-buy-and-sell-partially.spec.ts | 9 +- ...ulator-novn-buy-and-sell-partially.spec.ts | 9 +- ...folio-calculator-novn-buy-and-sell.spec.ts | 1 + .../src/app/portfolio/portfolio-calculator.ts | 271 +++++++++++------- .../interfaces/timeline-position.interface.ts | 1 + 8 files changed, 185 insertions(+), 109 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9f94abe8..7b6a834af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Changed the performance calculation to a time-weighted approach - Used the `HasPermission` annotation in endpoints ## 2.32.0 - 2023-12-26 diff --git a/apps/api/src/app/portfolio/portfolio-calculator-baln-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/portfolio-calculator-baln-buy-and-sell.spec.ts index c66323c72..f4fec026a 100644 --- a/apps/api/src/app/portfolio/portfolio-calculator-baln-buy-and-sell.spec.ts +++ b/apps/api/src/app/portfolio/portfolio-calculator-baln-buy-and-sell.spec.ts @@ -92,6 +92,7 @@ describe('PortfolioCalculator', () => { marketPrice: 148.9, quantity: new Big('0'), symbol: 'BALN.SW', + timeWeightedInvestment: new Big('285.8'), transactionCount: 2 } ], diff --git a/apps/api/src/app/portfolio/portfolio-calculator-baln-buy.spec.ts b/apps/api/src/app/portfolio/portfolio-calculator-baln-buy.spec.ts index 9f49c13e0..e2560cfbb 100644 --- a/apps/api/src/app/portfolio/portfolio-calculator-baln-buy.spec.ts +++ b/apps/api/src/app/portfolio/portfolio-calculator-baln-buy.spec.ts @@ -81,6 +81,7 @@ describe('PortfolioCalculator', () => { marketPrice: 148.9, quantity: new Big('2'), symbol: 'BALN.SW', + timeWeightedInvestment: new Big('273.2'), transactionCount: 1 } ], diff --git a/apps/api/src/app/portfolio/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts index e0761ebe5..4f1bc8cdc 100644 --- a/apps/api/src/app/portfolio/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts +++ b/apps/api/src/app/portfolio/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts @@ -73,10 +73,10 @@ describe('PortfolioCalculator', () => { currentValue: new Big('13657.2'), errors: [], grossPerformance: new Big('27172.74'), - grossPerformancePercentage: new Big('42.40043067128546016291'), + grossPerformancePercentage: new Big('42.41978276196153750666'), hasErrors: false, netPerformance: new Big('27172.74'), - netPerformancePercentage: new Big('42.40043067128546016291'), + netPerformancePercentage: new Big('42.41978276196153750666'), positions: [ { averagePrice: new Big('320.43'), @@ -85,13 +85,14 @@ describe('PortfolioCalculator', () => { fee: new Big('0'), firstBuyDate: '2015-01-01', grossPerformance: new Big('27172.74'), - grossPerformancePercentage: new Big('42.40043067128546016291'), + grossPerformancePercentage: new Big('42.41978276196153750666'), investment: new Big('320.43'), netPerformance: new Big('27172.74'), - netPerformancePercentage: new Big('42.40043067128546016291'), + netPerformancePercentage: new Big('42.41978276196153750666'), marketPrice: 13657.2, quantity: new Big('1'), symbol: 'BTCUSD', + timeWeightedInvestment: new Big('640.56763686131386861314'), transactionCount: 2 } ], diff --git a/apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell-partially.spec.ts index 6adfc9347..2c8bff238 100644 --- a/apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell-partially.spec.ts +++ b/apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell-partially.spec.ts @@ -73,10 +73,10 @@ describe('PortfolioCalculator', () => { currentValue: new Big('87.8'), errors: [], grossPerformance: new Big('21.93'), - grossPerformancePercentage: new Big('0.14465699208443271768'), + grossPerformancePercentage: new Big('0.15113417083448194384'), hasErrors: false, netPerformance: new Big('17.68'), - netPerformancePercentage: new Big('0.11662269129287598945'), + netPerformancePercentage: new Big('0.12184460284330327256'), positions: [ { averagePrice: new Big('75.80'), @@ -85,13 +85,14 @@ describe('PortfolioCalculator', () => { fee: new Big('4.25'), firstBuyDate: '2022-03-07', grossPerformance: new Big('21.93'), - grossPerformancePercentage: new Big('0.14465699208443271768'), + grossPerformancePercentage: new Big('0.15113417083448194384'), investment: new Big('75.80'), netPerformance: new Big('17.68'), - netPerformancePercentage: new Big('0.11662269129287598945'), + netPerformancePercentage: new Big('0.12184460284330327256'), marketPrice: 87.8, quantity: new Big('1'), symbol: 'NOVN.SW', + timeWeightedInvestment: new Big('145.10285714285714285714'), transactionCount: 2 } ], diff --git a/apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell.spec.ts index feed5923b..3b34a0e34 100644 --- a/apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell.spec.ts +++ b/apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell.spec.ts @@ -112,6 +112,7 @@ describe('PortfolioCalculator', () => { marketPrice: 87.8, quantity: new Big('0'), symbol: 'NOVN.SW', + timeWeightedInvestment: new Big('151.6'), transactionCount: 2 } ], diff --git a/apps/api/src/app/portfolio/portfolio-calculator.ts b/apps/api/src/app/portfolio/portfolio-calculator.ts index c11e514e4..06a5ca6ae 100644 --- a/apps/api/src/app/portfolio/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/portfolio-calculator.ts @@ -15,6 +15,7 @@ import { addMilliseconds, addMonths, addYears, + differenceInDays, endOfDay, format, isAfter, @@ -43,7 +44,7 @@ import { TransactionPointSymbol } from './interfaces/transaction-point-symbol.in import { TransactionPoint } from './interfaces/transaction-point.interface'; export class PortfolioCalculator { - private static readonly CALCULATE_PERCENTAGE_PERFORMANCE_WITH_MAX_INVESTMENT = + private static readonly CALCULATE_PERCENTAGE_PERFORMANCE_WITH_TIME_WEIGHTED_INVESTMENT = true; private static readonly ENABLE_LOGGING = false; @@ -238,12 +239,13 @@ export class PortfolioCalculator { } } - const valuesByDate: { + const accumulatedValuesByDate: { [date: string]: { maxTotalInvestmentValue: Big; totalCurrentValue: Big; totalInvestmentValue: Big; totalNetPerformanceValue: Big; + totalTimeWeightedInvestmentValue: Big; }; } = {}; @@ -253,6 +255,7 @@ export class PortfolioCalculator { investmentValues: { [date: string]: Big }; maxInvestmentValues: { [date: string]: Big }; netPerformanceValues: { [date: string]: Big }; + timeWeightedInvestmentValues: { [date: string]: Big }; }; } = {}; @@ -261,7 +264,8 @@ export class PortfolioCalculator { currentValues, investmentValues, maxInvestmentValues, - netPerformanceValues + netPerformanceValues, + timeWeightedInvestmentValues } = this.getSymbolMetrics({ end, marketSymbolMap, @@ -275,7 +279,8 @@ export class PortfolioCalculator { currentValues, investmentValues, maxInvestmentValues, - netPerformanceValues + netPerformanceValues, + timeWeightedInvestmentValues }; } @@ -293,38 +298,50 @@ export class PortfolioCalculator { symbolValues.maxInvestmentValues?.[dateString] ?? new Big(0); const netPerformanceValue = symbolValues.netPerformanceValues?.[dateString] ?? new Big(0); + const timeWeightedInvestmentValue = + symbolValues.timeWeightedInvestmentValues?.[dateString] ?? new Big(0); - valuesByDate[dateString] = { + accumulatedValuesByDate[dateString] = { totalCurrentValue: ( - valuesByDate[dateString]?.totalCurrentValue ?? new Big(0) + accumulatedValuesByDate[dateString]?.totalCurrentValue ?? new Big(0) ).add(currentValue), totalInvestmentValue: ( - valuesByDate[dateString]?.totalInvestmentValue ?? new Big(0) + accumulatedValuesByDate[dateString]?.totalInvestmentValue ?? + new Big(0) ).add(investmentValue), + totalTimeWeightedInvestmentValue: ( + accumulatedValuesByDate[dateString] + ?.totalTimeWeightedInvestmentValue ?? new Big(0) + ).add(timeWeightedInvestmentValue), maxTotalInvestmentValue: ( - valuesByDate[dateString]?.maxTotalInvestmentValue ?? new Big(0) + accumulatedValuesByDate[dateString]?.maxTotalInvestmentValue ?? + new Big(0) ).add(maxInvestmentValue), totalNetPerformanceValue: ( - valuesByDate[dateString]?.totalNetPerformanceValue ?? new Big(0) + accumulatedValuesByDate[dateString]?.totalNetPerformanceValue ?? + new Big(0) ).add(netPerformanceValue) }; } } - return Object.entries(valuesByDate).map(([date, values]) => { + return Object.entries(accumulatedValuesByDate).map(([date, values]) => { const { maxTotalInvestmentValue, totalCurrentValue, totalInvestmentValue, - totalNetPerformanceValue + totalNetPerformanceValue, + totalTimeWeightedInvestmentValue } = values; - const netPerformanceInPercentage = maxTotalInvestmentValue.eq(0) + let investmentValue = + PortfolioCalculator.CALCULATE_PERCENTAGE_PERFORMANCE_WITH_TIME_WEIGHTED_INVESTMENT + ? totalTimeWeightedInvestmentValue + : maxTotalInvestmentValue; + + const netPerformanceInPercentage = investmentValue.eq(0) ? 0 - : totalNetPerformanceValue - .div(maxTotalInvestmentValue) - .mul(100) - .toNumber(); + : totalNetPerformanceValue.div(investmentValue).mul(100).toNumber(); return { date, @@ -447,7 +464,6 @@ export class PortfolioCalculator { if (firstIndex > 0) { firstIndex--; } - const initialValues: { [symbol: string]: Big } = {}; const positions: TimelinePosition[] = []; let hasAnySymbolMetricsErrors = false; @@ -461,9 +477,9 @@ export class PortfolioCalculator { grossPerformance, grossPerformancePercentage, hasErrors, - initialValue, netPerformance, - netPerformancePercentage + netPerformancePercentage, + timeWeightedInvestment } = this.getSymbolMetrics({ end, marketSymbolMap, @@ -472,9 +488,9 @@ export class PortfolioCalculator { }); hasAnySymbolMetricsErrors = hasAnySymbolMetricsErrors || hasErrors; - initialValues[item.symbol] = initialValue; positions.push({ + timeWeightedInvestment, averagePrice: item.quantity.eq(0) ? new Big(0) : item.investment.div(item.quantity), @@ -509,7 +525,7 @@ export class PortfolioCalculator { } } - const overall = this.calculateOverallPerformance(positions, initialValues); + const overall = this.calculateOverallPerformance(positions); return { ...overall, @@ -732,18 +748,13 @@ export class PortfolioCalculator { }; } - private calculateOverallPerformance( - positions: TimelinePosition[], - initialValues: { [symbol: string]: Big } - ) { + private calculateOverallPerformance(positions: TimelinePosition[]) { let currentValue = new Big(0); let grossPerformance = new Big(0); - let grossPerformancePercentage = new Big(0); let hasErrors = false; let netPerformance = new Big(0); - let netPerformancePercentage = new Big(0); - let sumOfWeights = new Big(0); let totalInvestment = new Big(0); + let totalTimeWeightedInvestment = new Big(0); for (const currentPosition of positions) { if (currentPosition.marketPrice) { @@ -766,21 +777,9 @@ export class PortfolioCalculator { hasErrors = true; } - if (currentPosition.grossPerformancePercentage) { - // Use the average from the initial value and the current investment as - // a weight - 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) + if (currentPosition.timeWeightedInvestment) { + totalTimeWeightedInvestment = totalTimeWeightedInvestment.plus( + currentPosition.timeWeightedInvestment ); } else if (!currentPosition.quantity.eq(0)) { Logger.warn( @@ -791,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 { currentValue, grossPerformance, - grossPerformancePercentage, hasErrors, netPerformance, - netPerformancePercentage, - totalInvestment + totalInvestment, + netPerformancePercentage: totalTimeWeightedInvestment.eq(0) + ? new Big(0) + : netPerformance.div(totalTimeWeightedInvestment), + grossPerformancePercentage: totalTimeWeightedInvestment.eq(0) + ? new Big(0) + : grossPerformance.div(totalTimeWeightedInvestment) }; } @@ -1018,6 +1013,7 @@ export class PortfolioCalculator { let averagePriceAtEndDate = new Big(0); let averagePriceAtStartDate = new Big(0); + const currentValues: { [date: string]: Big } = {}; let feesAtStartDate = new Big(0); let fees = new Big(0); let grossPerformance = new Big(0); @@ -1025,12 +1021,12 @@ export class PortfolioCalculator { let grossPerformanceFromSells = new Big(0); let initialValue: Big; let investmentAtStartDate: Big; - const currentValues: { [date: string]: Big } = {}; const investmentValues: { [date: string]: Big } = {}; const maxInvestmentValues: { [date: string]: Big } = {}; let lastAveragePrice = new Big(0); let maxTotalInvestment = new Big(0); const netPerformanceValues: { [date: string]: Big } = {}; + const timeWeightedInvestmentValues: { [date: string]: Big } = {}; let totalInvestment = new Big(0); let totalInvestmentWithGrossPerformanceFromSell = new Big(0); let totalUnits = new Big(0); @@ -1122,6 +1118,9 @@ export class PortfolioCalculator { return order.itemType === 'end'; }); + let totalInvestmentDays = 0; + let sumOfTimeWeightedInvestments = new Big(0); + for (let i = 0; i < orders.length; i += 1) { const order = orders[i]; @@ -1162,11 +1161,11 @@ export class PortfolioCalculator { order.type === 'BUY' ? order.quantity.mul(order.unitPrice).mul(this.getFactor(order.type)) : totalUnits.gt(0) - ? totalInvestment - .div(totalUnits) - .mul(order.quantity) - .mul(this.getFactor(order.type)) - : new Big(0); + ? totalInvestment + .div(totalUnits) + .mul(order.quantity) + .mul(this.getFactor(order.type)) + : new Big(0); if (PortfolioCalculator.ENABLE_LOGGING) { console.log('totalInvestment', totalInvestment.toNumber()); @@ -1174,6 +1173,7 @@ export class PortfolioCalculator { console.log('transactionInvestment', transactionInvestment.toNumber()); } + const totalInvestmentBeforeTransaction = totalInvestment; totalInvestment = totalInvestment.plus(transactionInvestment); if (i >= indexOfStartOrder && totalInvestment.gt(maxTotalInvestment)) { @@ -1243,14 +1243,48 @@ export class PortfolioCalculator { grossPerformanceAtStartDate = grossPerformance; } - if (isChartMode && i > indexOfStartOrder) { - currentValues[order.date] = valueOfInvestment; - netPerformanceValues[order.date] = grossPerformance - .minus(grossPerformanceAtStartDate) - .minus(fees.minus(feesAtStartDate)); + if (i > indexOfStartOrder) { + // Only consider periods with an investment for the calculation of + // the time weighted investment + if (totalInvestmentBeforeTransaction.gt(0)) { + // Calculate the number of days since the previous order + const orderDate = new Date(order.date); + const previousOrderDate = new Date(orders[i - 1].date); + + let daysSinceLastOrder = differenceInDays( + orderDate, + previousOrderDate + ); + + // Set to at least 1 day, otherwise the transactions on the same day + // would not be considered in the time weighted calculation + if (daysSinceLastOrder <= 0) { + daysSinceLastOrder = 1; + } + + // Sum up the total investment days since the start date to calculate + // the time weighted investment + totalInvestmentDays += daysSinceLastOrder; - investmentValues[order.date] = totalInvestment; - maxInvestmentValues[order.date] = maxTotalInvestment; + sumOfTimeWeightedInvestments = sumOfTimeWeightedInvestments.add( + totalInvestmentBeforeTransaction.mul(daysSinceLastOrder) + ); + } + + if (isChartMode) { + currentValues[order.date] = valueOfInvestment; + netPerformanceValues[order.date] = grossPerformance + .minus(grossPerformanceAtStartDate) + .minus(fees.minus(feesAtStartDate)); + + investmentValues[order.date] = totalInvestment; + maxInvestmentValues[order.date] = maxTotalInvestment; + + timeWeightedInvestmentValues[order.date] = + totalInvestmentDays > 0 + ? sumOfTimeWeightedInvestments.div(totalInvestmentDays) + : new Big(0); + } } if (PortfolioCalculator.ENABLE_LOGGING) { @@ -1274,50 +1308,79 @@ export class PortfolioCalculator { .minus(grossPerformanceAtStartDate) .minus(fees.minus(feesAtStartDate)); + const timeWeightedAverageInvestmentBetweenStartAndEndDate = + totalInvestmentDays > 0 + ? sumOfTimeWeightedInvestments.div(totalInvestmentDays) + : new Big(0); + const maxInvestmentBetweenStartAndEndDate = valueAtStartDate.plus( maxTotalInvestment.minus(investmentAtStartDate) ); - const grossPerformancePercentage = - PortfolioCalculator.CALCULATE_PERCENTAGE_PERFORMANCE_WITH_MAX_INVESTMENT || - averagePriceAtStartDate.eq(0) || - averagePriceAtEndDate.eq(0) || - orders[indexOfStartOrder].unitPrice.eq(0) - ? maxInvestmentBetweenStartAndEndDate.gt(0) - ? totalGrossPerformance.div(maxInvestmentBetweenStartAndEndDate) - : new Big(0) - : // This formula has the issue that buying more units with a price - // lower than the average buying price results in a positive - // performance even if the market price stays constant - unitPriceAtEndDate - .div(averagePriceAtEndDate) - .div( - orders[indexOfStartOrder].unitPrice.div(averagePriceAtStartDate) + let grossPerformancePercentage: Big; + + if ( + PortfolioCalculator.CALCULATE_PERCENTAGE_PERFORMANCE_WITH_TIME_WEIGHTED_INVESTMENT + ) { + grossPerformancePercentage = + timeWeightedAverageInvestmentBetweenStartAndEndDate.gt(0) + ? totalGrossPerformance.div( + timeWeightedAverageInvestmentBetweenStartAndEndDate ) - .minus(1); + : new Big(0); + } else { + grossPerformancePercentage = + averagePriceAtStartDate.eq(0) || + averagePriceAtEndDate.eq(0) || + orders[indexOfStartOrder].unitPrice.eq(0) + ? maxInvestmentBetweenStartAndEndDate.gt(0) + ? totalGrossPerformance.div(maxInvestmentBetweenStartAndEndDate) + : new Big(0) + : // This formula has the issue that buying more units with a price + // lower than the average buying price results in a positive + // performance even if the market price stays constant + unitPriceAtEndDate + .div(averagePriceAtEndDate) + .div( + orders[indexOfStartOrder].unitPrice.div(averagePriceAtStartDate) + ) + .minus(1); + } const feesPerUnit = totalUnits.gt(0) ? fees.minus(feesAtStartDate).div(totalUnits) : new Big(0); - const netPerformancePercentage = - PortfolioCalculator.CALCULATE_PERCENTAGE_PERFORMANCE_WITH_MAX_INVESTMENT || - averagePriceAtStartDate.eq(0) || - averagePriceAtEndDate.eq(0) || - orders[indexOfStartOrder].unitPrice.eq(0) - ? maxInvestmentBetweenStartAndEndDate.gt(0) - ? totalNetPerformance.div(maxInvestmentBetweenStartAndEndDate) - : new Big(0) - : // This formula has the issue that buying more units with a price - // lower than the average buying price results in a positive - // performance even if the market price stays constant - unitPriceAtEndDate - .minus(feesPerUnit) - .div(averagePriceAtEndDate) - .div( - orders[indexOfStartOrder].unitPrice.div(averagePriceAtStartDate) + let netPerformancePercentage: Big; + + if ( + PortfolioCalculator.CALCULATE_PERCENTAGE_PERFORMANCE_WITH_TIME_WEIGHTED_INVESTMENT + ) { + netPerformancePercentage = + timeWeightedAverageInvestmentBetweenStartAndEndDate.gt(0) + ? totalNetPerformance.div( + timeWeightedAverageInvestmentBetweenStartAndEndDate ) - .minus(1); + : new Big(0); + } else { + netPerformancePercentage = + averagePriceAtStartDate.eq(0) || + averagePriceAtEndDate.eq(0) || + orders[indexOfStartOrder].unitPrice.eq(0) + ? maxInvestmentBetweenStartAndEndDate.gt(0) + ? totalNetPerformance.div(maxInvestmentBetweenStartAndEndDate) + : new Big(0) + : // This formula has the issue that buying more units with a price + // lower than the average buying price results in a positive + // performance even if the market price stays constant + unitPriceAtEndDate + .minus(feesPerUnit) + .div(averagePriceAtEndDate) + .div( + orders[indexOfStartOrder].unitPrice.div(averagePriceAtStartDate) + ) + .minus(1); + } if (PortfolioCalculator.ENABLE_LOGGING) { console.log( @@ -1330,6 +1393,9 @@ export class PortfolioCalculator { 2 )} -> ${averagePriceAtEndDate.toFixed(2)} Total investment: ${totalInvestment.toFixed(2)} + Time weighted investment: ${timeWeightedAverageInvestmentBetweenStartAndEndDate.toFixed( + 2 + )} Max. total investment: ${maxTotalInvestment.toFixed(2)} Gross performance: ${totalGrossPerformance.toFixed( 2 @@ -1349,9 +1415,12 @@ export class PortfolioCalculator { maxInvestmentValues, netPerformancePercentage, netPerformanceValues, + timeWeightedInvestmentValues, grossPerformance: totalGrossPerformance, hasErrors: totalUnits.gt(0) && (!initialValue || !unitPriceAtEndDate), - netPerformance: totalNetPerformance + netPerformance: totalNetPerformance, + timeWeightedInvestment: + timeWeightedAverageInvestmentBetweenStartAndEndDate }; } diff --git a/libs/common/src/lib/interfaces/timeline-position.interface.ts b/libs/common/src/lib/interfaces/timeline-position.interface.ts index de4c3ff19..1b27de8dc 100644 --- a/libs/common/src/lib/interfaces/timeline-position.interface.ts +++ b/libs/common/src/lib/interfaces/timeline-position.interface.ts @@ -16,5 +16,6 @@ export interface TimelinePosition { quantity: Big; symbol: string; tags?: Tag[]; + timeWeightedInvestment: Big; transactionCount: number; }