From 74d564a2b2a18dd12cb46483d0557858ea92f3ad Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 19 Sep 2026 22:29:22 +0200 Subject: [PATCH] Task/rename time weighted investment to average investment (#7918) Rename time weighted investment to average investment --- .../calculator/portfolio-calculator.ts | 111 +++++++++--------- ...tfolio-calculator-baln-buy-and-buy.spec.ts | 8 +- ...aln-buy-and-sell-in-two-activities.spec.ts | 8 +- ...folio-calculator-baln-buy-and-sell.spec.ts | 4 +- .../portfolio-calculator-baln-buy.spec.ts | 4 +- .../roai/portfolio-calculator-btceur.spec.ts | 4 +- ...ator-btcusd-buy-and-sell-partially.spec.ts | 8 +- .../roai/portfolio-calculator-btcusd.spec.ts | 4 +- .../roai/portfolio-calculator-cash.spec.ts | 8 +- .../portfolio-calculator-googl-buy.spec.ts | 4 +- ...ulator-novn-buy-and-sell-partially.spec.ts | 8 +- ...folio-calculator-novn-buy-and-sell.spec.ts | 4 +- .../portfolio-calculator-valuable.spec.ts | 4 +- .../calculator/roai/portfolio-calculator.ts | 98 +++++++--------- .../holding-performance.interface.ts | 16 +-- .../app/portfolio/portfolio.service.spec.ts | 4 +- .../src/app/portfolio/portfolio.service.ts | 14 +-- .../lib/models/portfolio-snapshot-holding.ts | 43 +++++-- 18 files changed, 184 insertions(+), 170 deletions(-) diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts index 124346812f..4c36598396 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts @@ -355,6 +355,8 @@ export abstract class PortfolioCalculator { const accumulatedValuesByDate: { [date: string]: { investmentValueWithCurrencyEffect: Big; + totalAverageInvestmentValue: Big; + totalAverageInvestmentValueWithCurrencyEffect: Big; totalCashValueWithCurrencyEffect: Big; totalCurrentValue: Big; totalCurrentValueWithCurrencyEffect: Big; @@ -363,13 +365,13 @@ export abstract class PortfolioCalculator { totalNetPerformanceValue: Big; totalNetPerformanceValueWithCurrencyEffect: Big; totalNetWorthValueWithCurrencyEffect: Big; - totalTimeWeightedInvestmentValue: Big; - totalTimeWeightedInvestmentValueWithCurrencyEffect: Big; }; } = {}; const valuesByAssetProfileIdentifier: { [assetProfileIdentifier: string]: { + averageInvestmentValues: { [date: string]: Big }; + averageInvestmentValuesWithCurrencyEffect: { [date: string]: Big }; currentValues: { [date: string]: Big }; currentValuesWithCurrencyEffect: { [date: string]: Big }; investmentValuesAccumulated: { [date: string]: Big }; @@ -378,8 +380,6 @@ export abstract class PortfolioCalculator { netPerformanceValues: { [date: string]: Big }; netPerformanceValuesWithCurrencyEffect: { [date: string]: Big }; netWorthValuesWithCurrencyEffect: { [date: string]: Big }; - timeWeightedInvestmentValues: { [date: string]: Big }; - timeWeightedInvestmentValuesWithCurrencyEffect: { [date: string]: Big }; }; } = {}; @@ -403,6 +403,10 @@ export abstract class PortfolioCalculator { item.symbol === this.currency; const { + averageInvestment, + averageInvestmentValues, + averageInvestmentValuesWithCurrencyEffect, + averageInvestmentWithCurrencyEffect, currentValues, currentValuesWithCurrencyEffect, grossPerformance, @@ -419,10 +423,6 @@ export abstract class PortfolioCalculator { netPerformanceValues, netPerformanceValuesWithCurrencyEffect, netPerformanceWithCurrencyEffectMap, - timeWeightedInvestment, - timeWeightedInvestmentValues, - timeWeightedInvestmentValuesWithCurrencyEffect, - timeWeightedInvestmentWithCurrencyEffect, totalDividend, totalDividendInBaseCurrency, totalInterestInBaseCurrency, @@ -450,6 +450,8 @@ export abstract class PortfolioCalculator { valuesByAssetProfileIdentifier[assetProfileIdentifier] = isCashInBaseCurrency ? { + averageInvestmentValues: {}, + averageInvestmentValuesWithCurrencyEffect: {}, currentValues: {}, currentValuesWithCurrencyEffect: {}, investmentValuesAccumulated: {}, @@ -457,11 +459,11 @@ export abstract class PortfolioCalculator { investmentValuesWithCurrencyEffect: {}, netPerformanceValues: {}, netPerformanceValuesWithCurrencyEffect: {}, - netWorthValuesWithCurrencyEffect: currentValuesWithCurrencyEffect, - timeWeightedInvestmentValues: {}, - timeWeightedInvestmentValuesWithCurrencyEffect: {} + netWorthValuesWithCurrencyEffect: currentValuesWithCurrencyEffect } : { + averageInvestmentValues, + averageInvestmentValuesWithCurrencyEffect, currentValues, currentValuesWithCurrencyEffect, investmentValuesAccumulated, @@ -469,14 +471,12 @@ export abstract class PortfolioCalculator { investmentValuesWithCurrencyEffect, netPerformanceValues, netPerformanceValuesWithCurrencyEffect, - timeWeightedInvestmentValues, - timeWeightedInvestmentValuesWithCurrencyEffect, netWorthValuesWithCurrencyEffect: currentValuesWithCurrencyEffect }; positions.push({ - timeWeightedInvestment, - timeWeightedInvestmentWithCurrencyEffect, + averageInvestment, + averageInvestmentWithCurrencyEffect, activitiesCount: item.activitiesCount, averagePrice: item.averagePrice, currency: item.currency, @@ -586,12 +586,12 @@ export abstract class PortfolioCalculator { assetProfileValues.netWorthValuesWithCurrencyEffect?.[dateString] ?? new Big(0); - const timeWeightedInvestmentValue = - assetProfileValues.timeWeightedInvestmentValues?.[dateString] ?? + const averageInvestmentValue = + assetProfileValues.averageInvestmentValues?.[dateString] ?? new Big(0); - const timeWeightedInvestmentValueWithCurrencyEffect = - assetProfileValues.timeWeightedInvestmentValuesWithCurrencyEffect?.[ + const averageInvestmentValueWithCurrencyEffect = + assetProfileValues.averageInvestmentValuesWithCurrencyEffect?.[ dateString ] ?? new Big(0); @@ -600,6 +600,14 @@ export abstract class PortfolioCalculator { accumulatedValuesByDate[dateString] ?.investmentValueWithCurrencyEffect ?? new Big(0) ).add(investmentValueWithCurrencyEffect), + totalAverageInvestmentValue: ( + accumulatedValuesByDate[dateString]?.totalAverageInvestmentValue ?? + new Big(0) + ).add(averageInvestmentValue), + totalAverageInvestmentValueWithCurrencyEffect: ( + accumulatedValuesByDate[dateString] + ?.totalAverageInvestmentValueWithCurrencyEffect ?? new Big(0) + ).add(averageInvestmentValueWithCurrencyEffect), totalCashValueWithCurrencyEffect: ( accumulatedValuesByDate[dateString] ?.totalCashValueWithCurrencyEffect ?? new Big(0) @@ -634,15 +642,7 @@ export abstract class PortfolioCalculator { totalNetWorthValueWithCurrencyEffect: ( accumulatedValuesByDate[dateString] ?.totalNetWorthValueWithCurrencyEffect ?? new Big(0) - ).add(netWorthValueWithCurrencyEffect), - totalTimeWeightedInvestmentValue: ( - accumulatedValuesByDate[dateString] - ?.totalTimeWeightedInvestmentValue ?? new Big(0) - ).add(timeWeightedInvestmentValue), - totalTimeWeightedInvestmentValueWithCurrencyEffect: ( - accumulatedValuesByDate[dateString] - ?.totalTimeWeightedInvestmentValueWithCurrencyEffect ?? new Big(0) - ).add(timeWeightedInvestmentValueWithCurrencyEffect) + ).add(netWorthValueWithCurrencyEffect) }; } } @@ -652,6 +652,8 @@ export abstract class PortfolioCalculator { ).map(([date, values]) => { const { investmentValueWithCurrencyEffect, + totalAverageInvestmentValue, + totalAverageInvestmentValueWithCurrencyEffect, totalCashValueWithCurrencyEffect, totalCurrentValue, totalCurrentValueWithCurrencyEffect, @@ -659,22 +661,18 @@ export abstract class PortfolioCalculator { totalInvestmentValueWithCurrencyEffect, totalNetPerformanceValue, totalNetPerformanceValueWithCurrencyEffect, - totalNetWorthValueWithCurrencyEffect, - totalTimeWeightedInvestmentValue, - totalTimeWeightedInvestmentValueWithCurrencyEffect + totalNetWorthValueWithCurrencyEffect } = values; - const netPerformanceInPercentage = totalTimeWeightedInvestmentValue.eq(0) + const netPerformanceInPercentage = totalAverageInvestmentValue.eq(0) ? 0 - : totalNetPerformanceValue - .div(totalTimeWeightedInvestmentValue) - .toNumber(); + : totalNetPerformanceValue.div(totalAverageInvestmentValue).toNumber(); const netPerformanceInPercentageWithCurrencyEffect = - totalTimeWeightedInvestmentValueWithCurrencyEffect.eq(0) + totalAverageInvestmentValueWithCurrencyEffect.eq(0) ? 0 : totalNetPerformanceValueWithCurrencyEffect - .div(totalTimeWeightedInvestmentValueWithCurrencyEffect) + .div(totalAverageInvestmentValueWithCurrencyEffect) .toNumber(); return { @@ -868,6 +866,10 @@ export abstract class PortfolioCalculator { protected getEmptyHoldingPerformance(): HoldingPerformance { return { + averageInvestment: new Big(0), + averageInvestmentValues: {}, + averageInvestmentValuesWithCurrencyEffect: {}, + averageInvestmentWithCurrencyEffect: new Big(0), currentValues: {}, currentValuesWithCurrencyEffect: {}, grossPerformance: new Big(0), @@ -884,10 +886,6 @@ export abstract class PortfolioCalculator { netPerformanceValues: {}, netPerformanceValuesWithCurrencyEffect: {}, netPerformanceWithCurrencyEffectMap: {}, - timeWeightedInvestment: new Big(0), - timeWeightedInvestmentValues: {}, - timeWeightedInvestmentValuesWithCurrencyEffect: {}, - timeWeightedInvestmentWithCurrencyEffect: new Big(0), totalDividend: new Big(0), totalDividendInBaseCurrency: new Big(0), totalInterestInBaseCurrency: new Big(0), @@ -1346,12 +1344,12 @@ export abstract class PortfolioCalculator { const chart: HistoricalDataItem[] = []; + const averageInvestmentValues: number[] = []; + const averageInvestmentValuesWithCurrencyEffect: number[] = []; let grossPerformanceAtStartDate: number; let grossPerformanceWithCurrencyEffectAtStartDate: number; let netPerformanceAtStartDate: number; let netPerformanceWithCurrencyEffectAtStartDate: number; - const timeWeightedInvestmentValues: number[] = []; - const timeWeightedInvestmentValuesWithCurrencyEffect: number[] = []; for (const historicalDataItem of historicalData) { const date = resetHours(parseDate(historicalDataItem.date)); @@ -1382,28 +1380,27 @@ export abstract class PortfolioCalculator { // investment of each day. Thus the range starts with the value of its // first day, and subsequent buy and sell activities stay included. if (historicalDataItem.totalInvestment > 0) { - timeWeightedInvestmentValues.push( + averageInvestmentValues.push( historicalDataItem.totalInvestment + grossPerformanceAtStartDate ); } if (historicalDataItem.totalInvestmentValueWithCurrencyEffect > 0) { - timeWeightedInvestmentValuesWithCurrencyEffect.push( + averageInvestmentValuesWithCurrencyEffect.push( historicalDataItem.totalInvestmentValueWithCurrencyEffect + grossPerformanceWithCurrencyEffectAtStartDate ); } - const timeWeightedInvestmentValue = - timeWeightedInvestmentValues.length > 0 - ? sum(timeWeightedInvestmentValues) / - timeWeightedInvestmentValues.length + const averageInvestmentValue = + averageInvestmentValues.length > 0 + ? sum(averageInvestmentValues) / averageInvestmentValues.length : 0; - const timeWeightedInvestmentValueWithCurrencyEffect = - timeWeightedInvestmentValuesWithCurrencyEffect.length > 0 - ? sum(timeWeightedInvestmentValuesWithCurrencyEffect) / - timeWeightedInvestmentValuesWithCurrencyEffect.length + const averageInvestmentValueWithCurrencyEffect = + averageInvestmentValuesWithCurrencyEffect.length > 0 + ? sum(averageInvestmentValuesWithCurrencyEffect) / + averageInvestmentValuesWithCurrencyEffect.length : 0; chart.push({ @@ -1412,13 +1409,13 @@ export abstract class PortfolioCalculator { netPerformanceWithCurrencyEffect: netPerformanceWithCurrencyEffectSinceStartDate, netPerformanceInPercentage: - timeWeightedInvestmentValue > 0 - ? netPerformanceSinceStartDate / timeWeightedInvestmentValue + averageInvestmentValue > 0 + ? netPerformanceSinceStartDate / averageInvestmentValue : 0, netPerformanceInPercentageWithCurrencyEffect: - timeWeightedInvestmentValueWithCurrencyEffect > 0 + averageInvestmentValueWithCurrencyEffect > 0 ? netPerformanceWithCurrencyEffectSinceStartDate / - timeWeightedInvestmentValueWithCurrencyEffect + averageInvestmentValueWithCurrencyEffect : 0 }); } diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts index 932a50c7e2..1b2fee98d9 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts @@ -148,6 +148,10 @@ describe('PortfolioCalculator', () => { positions: [ { activitiesCount: 2, + averageInvestment: new Big('474.93846153846153846154'), + averageInvestmentWithCurrencyEffect: new Big( + '474.93846153846153846154' + ), averagePrice: new Big('139.75'), currency: 'CHF', dataSource: 'YAHOO', @@ -177,10 +181,6 @@ describe('PortfolioCalculator', () => { quantity: new Big('4'), symbol: 'BALN.SW', tags: [], - timeWeightedInvestment: new Big('474.93846153846153846154'), - timeWeightedInvestmentWithCurrencyEffect: new Big( - '474.93846153846153846154' - ), valueInBaseCurrency: new Big('595.6') } ], diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts index 4687fe4df0..54f1dfcb48 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts @@ -164,6 +164,10 @@ describe('PortfolioCalculator', () => { positions: [ { activitiesCount: 3, + averageInvestment: new Big('285.80000000000000396627'), + averageInvestmentWithCurrencyEffect: new Big( + '285.80000000000000396627' + ), averagePrice: new Big('0'), currency: 'CHF', dataSource: 'YAHOO', @@ -191,10 +195,6 @@ describe('PortfolioCalculator', () => { quantity: new Big('0'), symbol: 'BALN.SW', tags: [], - timeWeightedInvestment: new Big('285.80000000000000396627'), - timeWeightedInvestmentWithCurrencyEffect: new Big( - '285.80000000000000396627' - ), valueInBaseCurrency: new Big('0') } ], diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts index d16b9be6f6..408076717f 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts @@ -155,6 +155,8 @@ describe('PortfolioCalculator', () => { positions: [ { activitiesCount: 2, + averageInvestment: new Big('285.8'), + averageInvestmentWithCurrencyEffect: new Big('285.8'), averagePrice: new Big('0'), currency: 'CHF', dataSource: 'YAHOO', @@ -184,8 +186,6 @@ describe('PortfolioCalculator', () => { quantity: new Big('0'), symbol: 'BALN.SW', tags: [], - timeWeightedInvestment: new Big('285.8'), - timeWeightedInvestmentWithCurrencyEffect: new Big('285.8'), valueInBaseCurrency: new Big('0') } ], diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts index eeed8e957e..19f3c64369 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts @@ -138,6 +138,8 @@ describe('PortfolioCalculator', () => { positions: [ { activitiesCount: 1, + averageInvestment: new Big('273.2'), + averageInvestmentWithCurrencyEffect: new Big('273.2'), averagePrice: new Big('136.6'), currency: 'CHF', dataSource: 'YAHOO', @@ -173,8 +175,6 @@ describe('PortfolioCalculator', () => { quantity: new Big('2'), symbol: 'BALN.SW', tags: [], - timeWeightedInvestment: new Big('273.2'), - timeWeightedInvestmentWithCurrencyEffect: new Big('273.2'), valueInBaseCurrency: new Big('297.8') } ], diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts index 0e62bd0a38..aadf2e23a0 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts @@ -199,6 +199,8 @@ describe('PortfolioCalculator', () => { positions: [ { activitiesCount: 1, + averageInvestment: new Big('44558.42'), + averageInvestmentWithCurrencyEffect: new Big('44558.42'), averagePrice: new Big('44558.42'), currency: 'USD', dataSource: 'YAHOO', @@ -228,8 +230,6 @@ describe('PortfolioCalculator', () => { quantity: new Big('1'), symbol: 'BTCUSD', tags: [], - timeWeightedInvestment: new Big('44558.42'), - timeWeightedInvestmentWithCurrencyEffect: new Big('44558.42'), valueInBaseCurrency: new Big('43099.7') } ], diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts index 237c1ae220..6a9ed1f71c 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts @@ -167,6 +167,10 @@ describe('PortfolioCalculator', () => { positions: [ { activitiesCount: 2, + averageInvestment: new Big('623.73992504096715328467'), + averageInvestmentWithCurrencyEffect: new Big( + '636.79469348020066587024' + ), averagePrice: new Big('320.43'), currency: 'USD', dataSource: 'YAHOO', @@ -202,10 +206,6 @@ describe('PortfolioCalculator', () => { quantity: new Big('1'), symbol: 'BTCUSD', tags: [], - timeWeightedInvestment: new Big('623.73992504096715328467'), - timeWeightedInvestmentWithCurrencyEffect: new Big( - '636.79469348020066587024' - ), valueInBaseCurrency: new Big('13298.425356') } ], diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts index 4e7f693fc2..fe86d9d457 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts @@ -199,6 +199,8 @@ describe('PortfolioCalculator', () => { positions: [ { activitiesCount: 1, + averageInvestment: new Big('44558.42'), + averageInvestmentWithCurrencyEffect: new Big('44558.42'), averagePrice: new Big('44558.42'), currency: 'USD', dataSource: 'YAHOO', @@ -228,8 +230,6 @@ describe('PortfolioCalculator', () => { quantity: new Big('1'), symbol: 'BTCUSD', tags: [], - timeWeightedInvestment: new Big('44558.42'), - timeWeightedInvestmentWithCurrencyEffect: new Big('44558.42'), valueInBaseCurrency: new Big('43099.7') } ], diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts index ebbc9342ee..1ced004e42 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts @@ -252,6 +252,10 @@ describe('PortfolioCalculator', () => { */ expect(position).toMatchObject({ activitiesCount: 2, + averageInvestment: new Big('912.47956403269754768392'), + averageInvestmentWithCurrencyEffect: new Big( + '852.45231607629427792916' + ), averagePrice: new Big(1), currency: 'USD', dataSource: DataSource.YAHOO, @@ -292,10 +296,6 @@ describe('PortfolioCalculator', () => { }, quantity: new Big(2000), symbol: 'USD', - timeWeightedInvestment: new Big('912.47956403269754768392'), - timeWeightedInvestmentWithCurrencyEffect: new Big( - '852.45231607629427792916' - ), valueInBaseCurrency: new Big(1820) }); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts index 8a2b1aec18..e048d2df51 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts @@ -144,6 +144,8 @@ describe('PortfolioCalculator', () => { positions: [ { activitiesCount: 1, + averageInvestment: new Big('89.12').mul(0.8854), + averageInvestmentWithCurrencyEffect: new Big('82.329056'), averagePrice: new Big('89.12'), currency: 'USD', dataSource: 'YAHOO', @@ -173,8 +175,6 @@ describe('PortfolioCalculator', () => { quantity: new Big('1'), symbol: 'GOOGL', tags: [], - timeWeightedInvestment: new Big('89.12').mul(0.8854), - timeWeightedInvestmentWithCurrencyEffect: new Big('82.329056'), valueInBaseCurrency: new Big('103.10483') } ], diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts index 3f498ae4f1..92d078d811 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts @@ -144,6 +144,10 @@ describe('PortfolioCalculator', () => { positions: [ { activitiesCount: 2, + averageInvestment: new Big('145.10285714285714285714'), + averageInvestmentWithCurrencyEffect: new Big( + '145.10285714285714285714' + ), averagePrice: new Big('75.80'), currency: 'CHF', dataSource: 'YAHOO', @@ -173,10 +177,6 @@ describe('PortfolioCalculator', () => { quantity: new Big('1'), symbol: 'NOVN.SW', tags: [], - timeWeightedInvestment: new Big('145.10285714285714285714'), - timeWeightedInvestmentWithCurrencyEffect: new Big( - '145.10285714285714285714' - ), valueInBaseCurrency: new Big('87.8') } ], diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts index 896cb6c991..feb9a3c20e 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts @@ -197,6 +197,8 @@ describe('PortfolioCalculator', () => { positions: [ { activitiesCount: 2, + averageInvestment: new Big('151.6'), + averageInvestmentWithCurrencyEffect: new Big('151.6'), averagePrice: new Big('0'), currency: 'CHF', dataSource: 'YAHOO', @@ -226,8 +228,6 @@ describe('PortfolioCalculator', () => { quantity: new Big('0'), symbol: 'NOVN.SW', tags: [], - timeWeightedInvestment: new Big('151.6'), - timeWeightedInvestmentWithCurrencyEffect: new Big('151.6'), valueInBaseCurrency: new Big('0') } ], diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts index 7326d551bb..3d0ab45f85 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts @@ -121,6 +121,8 @@ describe('PortfolioCalculator', () => { positions: [ { activitiesCount: 1, + averageInvestment: new Big('500000'), + averageInvestmentWithCurrencyEffect: new Big('500000'), averagePrice: new Big('500000'), currency: 'USD', dataSource: 'MANUAL', @@ -148,8 +150,6 @@ describe('PortfolioCalculator', () => { quantity: new Big('1'), symbol: 'dac95060-d4f2-4653-a253-2c45e6fb5cde', tags: [], - timeWeightedInvestment: new Big('500000'), - timeWeightedInvestmentWithCurrencyEffect: new Big('500000'), valueInBaseCurrency: new Big('500000') } ], diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts index 6be9a577e2..a830b762c0 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts @@ -31,12 +31,12 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { let grossPerformanceWithCurrencyEffect = new Big(0); let hasErrors = false; let netPerformance = new Big(0); + let totalAverageInvestment = new Big(0); + let totalAverageInvestmentWithCurrencyEffect = new Big(0); let totalFeesWithCurrencyEffect = new Big(0); const totalInterestWithCurrencyEffect = new Big(0); let totalInvestment = new Big(0); let totalInvestmentWithCurrencyEffect = new Big(0); - let totalTimeWeightedInvestment = new Big(0); - let totalTimeWeightedInvestmentWithCurrencyEffect = new Big(0); for (const currentPosition of positions) { if (currentPosition.valueInBaseCurrency) { @@ -83,14 +83,14 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { hasErrors = true; } - if (currentPosition.timeWeightedInvestment) { - totalTimeWeightedInvestment = totalTimeWeightedInvestment.plus( - currentPosition.timeWeightedInvestment + if (currentPosition.averageInvestment) { + totalAverageInvestment = totalAverageInvestment.plus( + currentPosition.averageInvestment ); - totalTimeWeightedInvestmentWithCurrencyEffect = - totalTimeWeightedInvestmentWithCurrencyEffect.plus( - currentPosition.timeWeightedInvestmentWithCurrencyEffect + totalAverageInvestmentWithCurrencyEffect = + totalAverageInvestmentWithCurrencyEffect.plus( + currentPosition.averageInvestmentWithCurrencyEffect ); } else if (!currentPosition.quantity.eq(0)) { this.logger.warn( @@ -137,14 +137,14 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { }; start: Date; } & AssetProfileIdentifier): HoldingPerformance { - let investmentAtStartDate: Big; - let investmentAtStartDateWithCurrencyEffect: Big; - const timeWeightedInvestmentValues: { [date: string]: Big } = {}; + const averageInvestmentValues: { [date: string]: Big } = {}; - const timeWeightedInvestmentValuesWithCurrencyEffect: { + const averageInvestmentValuesWithCurrencyEffect: { [date: string]: Big; } = {}; + let investmentAtStartDate: Big; + let investmentAtStartDateWithCurrencyEffect: Big; let valueAtStartDate: Big; let valueAtStartDateWithCurrencyEffect: Big; @@ -248,9 +248,9 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { return itemType === 'end'; }); + let sumOfWeightedInvestments = new Big(0); + let sumOfWeightedInvestmentsWithCurrencyEffect = new Big(0); let totalInvestmentDays = 0; - let sumOfTimeWeightedInvestments = new Big(0); - let sumOfTimeWeightedInvestmentsWithCurrencyEffect = new Big(0); for (let i = 0; i < items.length; i += 1) { const item = items[i]; @@ -269,7 +269,7 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { if (i > indexOfStartActivity) { // Only consider periods with an investment for the calculation of - // the time weighted investment + // the average investment if ( item.valueBeforeTransaction.gt(0) && ['BUY', 'SELL'].includes(item.type) @@ -289,18 +289,18 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { } // Sum up the total investment days since the start date to calculate - // the time weighted investment + // the average investment totalInvestmentDays += daysSinceLastActivity; - sumOfTimeWeightedInvestments = sumOfTimeWeightedInvestments.add( + sumOfWeightedInvestments = sumOfWeightedInvestments.add( valueAtStartDate .minus(investmentAtStartDate) .plus(item.investmentBeforeTransaction) .mul(daysSinceLastActivity) ); - sumOfTimeWeightedInvestmentsWithCurrencyEffect = - sumOfTimeWeightedInvestmentsWithCurrencyEffect.add( + sumOfWeightedInvestmentsWithCurrencyEffect = + sumOfWeightedInvestmentsWithCurrencyEffect.add( valueAtStartDateWithCurrencyEffect .minus(investmentAtStartDateWithCurrencyEffect) .plus(item.investmentBeforeTransactionWithCurrencyEffect) @@ -309,17 +309,17 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { } // If duration is effectively zero (first day), use the actual investment as the base. - // Otherwise, use the calculated time-weighted average. - timeWeightedInvestmentValues[item.date] = + // Otherwise, use the calculated average investment. + averageInvestmentValues[item.date] = totalInvestmentDays > Number.EPSILON - ? sumOfTimeWeightedInvestments.div(totalInvestmentDays) + ? sumOfWeightedInvestments.div(totalInvestmentDays) : item.investment.gt(0) ? item.investment : new Big(0); - timeWeightedInvestmentValuesWithCurrencyEffect[item.date] = + averageInvestmentValuesWithCurrencyEffect[item.date] = totalInvestmentDays > Number.EPSILON - ? sumOfTimeWeightedInvestmentsWithCurrencyEffect.div( + ? sumOfWeightedInvestmentsWithCurrencyEffect.div( totalInvestmentDays ) : item.investmentWithCurrencyEffect.gt(0) @@ -363,31 +363,25 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { .minus(grossPerformanceAtStartDate) .minus(fees.minus(feesAtStartDate)); - const timeWeightedAverageInvestmentBetweenStartAndEndDate = + const averageInvestmentBetweenStartAndEndDate = totalInvestmentDays > 0 - ? sumOfTimeWeightedInvestments.div(totalInvestmentDays) + ? sumOfWeightedInvestments.div(totalInvestmentDays) : new Big(0); - const timeWeightedAverageInvestmentBetweenStartAndEndDateWithCurrencyEffect = + const averageInvestmentBetweenStartAndEndDateWithCurrencyEffect = totalInvestmentDays > 0 - ? sumOfTimeWeightedInvestmentsWithCurrencyEffect.div( - totalInvestmentDays - ) + ? sumOfWeightedInvestmentsWithCurrencyEffect.div(totalInvestmentDays) : new Big(0); const grossPerformancePercentage = - timeWeightedAverageInvestmentBetweenStartAndEndDate.gt(0) - ? totalGrossPerformance.div( - timeWeightedAverageInvestmentBetweenStartAndEndDate - ) + averageInvestmentBetweenStartAndEndDate.gt(0) + ? totalGrossPerformance.div(averageInvestmentBetweenStartAndEndDate) : new Big(0); const grossPerformancePercentageWithCurrencyEffect = - timeWeightedAverageInvestmentBetweenStartAndEndDateWithCurrencyEffect.gt( - 0 - ) + averageInvestmentBetweenStartAndEndDateWithCurrencyEffect.gt(0) ? totalGrossPerformanceWithCurrencyEffect.div( - timeWeightedAverageInvestmentBetweenStartAndEndDateWithCurrencyEffect + averageInvestmentBetweenStartAndEndDateWithCurrencyEffect ) : new Big(0); @@ -401,12 +395,11 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { .div(totalQuantity) : new Big(0); - const netPerformancePercentage = - timeWeightedAverageInvestmentBetweenStartAndEndDate.gt(0) - ? totalNetPerformance.div( - timeWeightedAverageInvestmentBetweenStartAndEndDate - ) - : new Big(0); + const netPerformancePercentage = averageInvestmentBetweenStartAndEndDate.gt( + 0 + ) + ? totalNetPerformance.div(averageInvestmentBetweenStartAndEndDate) + : new Big(0); const netPerformancePercentageWithCurrencyEffectMap: { [key: DateRange]: Big; @@ -512,10 +505,10 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { Total investment with currency effect: ${totalInvestmentWithCurrencyEffect.toFixed( 2 )} - Time weighted investment: ${timeWeightedAverageInvestmentBetweenStartAndEndDate.toFixed( + Average investment: ${averageInvestmentBetweenStartAndEndDate.toFixed( 2 )} - Time weighted investment with currency effect: ${timeWeightedAverageInvestmentBetweenStartAndEndDateWithCurrencyEffect.toFixed( + Average investment with currency effect: ${averageInvestmentBetweenStartAndEndDateWithCurrencyEffect.toFixed( 2 )} Total dividend: ${totalDividend.toFixed(2)} @@ -541,6 +534,8 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { } return { + averageInvestmentValues, + averageInvestmentValuesWithCurrencyEffect, currentValues, currentValuesWithCurrencyEffect, grossPerformancePercentage, @@ -553,23 +548,20 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { netPerformanceValues, netPerformanceValuesWithCurrencyEffect, netPerformanceWithCurrencyEffectMap, - timeWeightedInvestmentValues, - timeWeightedInvestmentValuesWithCurrencyEffect, totalDividend, totalDividendInBaseCurrency, totalInterestInBaseCurrency, totalInvestment, totalInvestmentWithCurrencyEffect, totalLiabilitiesInBaseCurrency, + averageInvestment: averageInvestmentBetweenStartAndEndDate, + averageInvestmentWithCurrencyEffect: + averageInvestmentBetweenStartAndEndDateWithCurrencyEffect, grossPerformance: totalGrossPerformance, grossPerformanceWithCurrencyEffect: totalGrossPerformanceWithCurrencyEffect, hasErrors: totalQuantity.gt(0) && (!initialValue || !unitPriceAtEndDate), - netPerformance: totalNetPerformance, - timeWeightedInvestment: - timeWeightedAverageInvestmentBetweenStartAndEndDate, - timeWeightedInvestmentWithCurrencyEffect: - timeWeightedAverageInvestmentBetweenStartAndEndDateWithCurrencyEffect + netPerformance: totalNetPerformance }; } diff --git a/apps/api/src/app/portfolio/interfaces/holding-performance.interface.ts b/apps/api/src/app/portfolio/interfaces/holding-performance.interface.ts index 4a1e2077c8..c94486844a 100644 --- a/apps/api/src/app/portfolio/interfaces/holding-performance.interface.ts +++ b/apps/api/src/app/portfolio/interfaces/holding-performance.interface.ts @@ -3,6 +3,14 @@ import { DateRange } from '@ghostfolio/common/types'; import { Big } from 'big.js'; export interface HoldingPerformance { + averageInvestment: Big; + averageInvestmentValues: { + [date: string]: Big; + }; + averageInvestmentValuesWithCurrencyEffect: { + [date: string]: Big; + }; + averageInvestmentWithCurrencyEffect: Big; currentValues: { [date: string]: Big; }; @@ -31,14 +39,6 @@ export interface HoldingPerformance { }; netPerformanceValuesWithCurrencyEffect: { [date: string]: Big }; netPerformanceWithCurrencyEffectMap: { [key: DateRange]: Big }; - timeWeightedInvestment: Big; - timeWeightedInvestmentValues: { - [date: string]: Big; - }; - timeWeightedInvestmentValuesWithCurrencyEffect: { - [date: string]: Big; - }; - timeWeightedInvestmentWithCurrencyEffect: Big; totalDividend: Big; totalDividendInBaseCurrency: Big; totalInterestInBaseCurrency: Big; diff --git a/apps/api/src/app/portfolio/portfolio.service.spec.ts b/apps/api/src/app/portfolio/portfolio.service.spec.ts index 0882ff7e64..3a5ff8f9a5 100644 --- a/apps/api/src/app/portfolio/portfolio.service.spec.ts +++ b/apps/api/src/app/portfolio/portfolio.service.spec.ts @@ -406,6 +406,8 @@ describe('PortfolioService', () => { const usdPosition = { activitiesCount: 1, + averageInvestment: new Big(0), + averageInvestmentWithCurrencyEffect: new Big(0), averagePrice: new Big(1), currency: 'USD', dataSource: DataSource.YAHOO, @@ -429,8 +431,6 @@ describe('PortfolioService', () => { quantity: new Big(quantity), symbol: 'USD', tags: [], - timeWeightedInvestment: new Big(0), - timeWeightedInvestmentWithCurrencyEffect: new Big(0), valueInBaseCurrency: new Big(1820) }; diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index b33c33e497..de3eecbcaa 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -984,6 +984,8 @@ export class PortfolioService { const { activitiesCount, + averageInvestment, + averageInvestmentWithCurrencyEffect, averagePrice, currency, dateOfFirstActivity, @@ -1000,9 +1002,7 @@ export class PortfolioService { netPerformancePercentageWithCurrencyEffectMap, netPerformanceWithCurrencyEffectMap, quantity, - tags, - timeWeightedInvestment, - timeWeightedInvestmentWithCurrencyEffect + tags } = holding; const activitiesOfHolding = activities.filter((activity) => { @@ -1017,9 +1017,9 @@ export class PortfolioService { new Date(), parseDate(dateOfFirstActivity) ), - netPerformancePercentage: timeWeightedInvestment.eq(0) + netPerformancePercentage: averageInvestment.eq(0) ? new Big(0) - : dividendInBaseCurrency.div(timeWeightedInvestment) + : dividendInBaseCurrency.div(averageInvestment) }); const dividendYieldPercentWithCurrencyEffect = @@ -1028,9 +1028,9 @@ export class PortfolioService { new Date(), parseDate(dateOfFirstActivity) ), - netPerformancePercentage: timeWeightedInvestmentWithCurrencyEffect.eq(0) + netPerformancePercentage: averageInvestmentWithCurrencyEffect.eq(0) ? new Big(0) - : dividendInBaseCurrency.div(timeWeightedInvestmentWithCurrencyEffect) + : dividendInBaseCurrency.div(averageInvestmentWithCurrencyEffect) }); const historicalData = await this.dataProviderService.getHistorical( diff --git a/libs/common/src/lib/models/portfolio-snapshot-holding.ts b/libs/common/src/lib/models/portfolio-snapshot-holding.ts index 5d8dd67f85..7ed40d6b8b 100644 --- a/libs/common/src/lib/models/portfolio-snapshot-holding.ts +++ b/libs/common/src/lib/models/portfolio-snapshot-holding.ts @@ -6,11 +6,44 @@ import { DateRange } from '@ghostfolio/common/types'; import { DataSource, Tag } from '@prisma/client'; import { Big } from 'big.js'; -import { Transform, Type } from 'class-transformer'; +import { Expose, Transform, Type } from 'class-transformer'; + +/** + * @deprecated Backward compatibility to read the portfolio snapshots which + * have been cached with the former name of the property + * + * TODO: Remove the fallback with the next release, together with the + * `@Expose()` decorators of `averageInvestment` and + * `averageInvestmentWithCurrencyEffect` + */ +function transformToBigWithFallback(formerKey: string) { + return ({ obj, value }: { obj: Record; value: string }) => { + const valueOrFallback = value ?? obj[formerKey]; + + return valueOrFallback === undefined + ? undefined + : transformToBig({ value: valueOrFallback }); + }; +} export class PortfolioSnapshotHolding { activitiesCount: number; + @Expose() + @Transform(transformToBigWithFallback('timeWeightedInvestment'), { + toClassOnly: true + }) + @Type(() => Big) + averageInvestment: Big; + + @Expose() + @Transform( + transformToBigWithFallback('timeWeightedInvestmentWithCurrencyEffect'), + { toClassOnly: true } + ) + @Type(() => Big) + averageInvestmentWithCurrencyEffect: Big; + @Transform(transformToBig, { toClassOnly: true }) @Type(() => Big) averagePrice: Big; @@ -83,14 +116,6 @@ export class PortfolioSnapshotHolding { symbol: string; tags?: Tag[]; - @Transform(transformToBig, { toClassOnly: true }) - @Type(() => Big) - timeWeightedInvestment: Big; - - @Transform(transformToBig, { toClassOnly: true }) - @Type(() => Big) - timeWeightedInvestmentWithCurrencyEffect: Big; - @Transform(transformToBig, { toClassOnly: true }) @Type(() => Big) valueInBaseCurrency: Big;