Browse Source

Fix portfolio calculation for holdings with same symbol

pull/7664/head
Thomas Kaul 1 week ago
parent
commit
24655251fc
  1. 46
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  2. 14
      apps/api/src/app/portfolio/portfolio.service.ts
  3. 56
      apps/api/src/helper/object.helper.spec.ts
  4. 33
      apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
  5. 27
      apps/client/src/app/pages/public/public-page.component.ts

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

@ -527,48 +527,51 @@ export abstract class PortfolioCalculator {
}
}
const assetProfileIdentifiers = Object.keys(valuesByAssetProfileIdentifier);
for (const dateString of chartDates) {
for (const assetProfileIdentifier of Object.keys(
valuesByAssetProfileIdentifier
)) {
const symbolValues =
for (const assetProfileIdentifier of assetProfileIdentifiers) {
const assetProfileValues =
valuesByAssetProfileIdentifier[assetProfileIdentifier];
const currentValue =
symbolValues.currentValues?.[dateString] ?? new Big(0);
assetProfileValues.currentValues?.[dateString] ?? new Big(0);
const currentValueWithCurrencyEffect =
symbolValues.currentValuesWithCurrencyEffect?.[dateString] ??
assetProfileValues.currentValuesWithCurrencyEffect?.[dateString] ??
new Big(0);
const investmentValueAccumulated =
symbolValues.investmentValuesAccumulated?.[dateString] ?? new Big(0);
assetProfileValues.investmentValuesAccumulated?.[dateString] ??
new Big(0);
const investmentValueAccumulatedWithCurrencyEffect =
symbolValues.investmentValuesAccumulatedWithCurrencyEffect?.[
assetProfileValues.investmentValuesAccumulatedWithCurrencyEffect?.[
dateString
] ?? new Big(0);
const investmentValueWithCurrencyEffect =
symbolValues.investmentValuesWithCurrencyEffect?.[dateString] ??
assetProfileValues.investmentValuesWithCurrencyEffect?.[dateString] ??
new Big(0);
const netPerformanceValue =
symbolValues.netPerformanceValues?.[dateString] ?? new Big(0);
assetProfileValues.netPerformanceValues?.[dateString] ?? new Big(0);
const netPerformanceValueWithCurrencyEffect =
symbolValues.netPerformanceValuesWithCurrencyEffect?.[dateString] ??
new Big(0);
assetProfileValues.netPerformanceValuesWithCurrencyEffect?.[
dateString
] ?? new Big(0);
const netWorthValueWithCurrencyEffect =
symbolValues.netWorthValuesWithCurrencyEffect?.[dateString] ??
assetProfileValues.netWorthValuesWithCurrencyEffect?.[dateString] ??
new Big(0);
const timeWeightedInvestmentValue =
symbolValues.timeWeightedInvestmentValues?.[dateString] ?? new Big(0);
assetProfileValues.timeWeightedInvestmentValues?.[dateString] ??
new Big(0);
const timeWeightedInvestmentValueWithCurrencyEffect =
symbolValues.timeWeightedInvestmentValuesWithCurrencyEffect?.[
assetProfileValues.timeWeightedInvestmentValuesWithCurrencyEffect?.[
dateString
] ?? new Big(0);
@ -989,7 +992,7 @@ export abstract class PortfolioCalculator {
@LogPerformance
private computeTransactionPoints() {
this.transactionPoints = [];
const symbols: {
const transactionPointSymbols: {
[assetProfileIdentifier: string]: TransactionPointSymbol;
} = {};
@ -1017,7 +1020,8 @@ export abstract class PortfolioCalculator {
const assetProfileIdentifier = getAssetProfileIdentifier(assetProfile);
const oldAccumulatedSymbol = symbols[assetProfileIdentifier];
const oldAccumulatedSymbol =
transactionPointSymbols[assetProfileIdentifier];
if (oldAccumulatedSymbol) {
let investment = oldAccumulatedSymbol.investment;
@ -1099,7 +1103,8 @@ export abstract class PortfolioCalculator {
'id'
);
symbols[assetProfileIdentifier] = currentTransactionPointItem;
transactionPointSymbols[assetProfileIdentifier] =
currentTransactionPointItem;
const items = lastTransactionPoint?.items ?? [];
@ -1110,7 +1115,10 @@ export abstract class PortfolioCalculator {
newItems.push(currentTransactionPointItem);
newItems.sort((a, b) => {
return a.symbol?.localeCompare(b.symbol);
return (
a.symbol?.localeCompare(b.symbol) ||
a.dataSource?.localeCompare(b.dataSource)
);
});
let fees = new Big(0);

14
apps/api/src/app/portfolio/portfolio.service.ts

@ -737,14 +737,20 @@ export class PortfolioService {
valueInBaseCurrency: emergencyFundInCash
};
const indexOfHoldingInBaseCurrency = holdings.findIndex(
const emergencyFundCashHoldingAssetProfileIdentifier =
getAssetProfileIdentifier(emergencyFundCashHolding.assetProfile);
const indexOfEmergencyFundCashHolding = holdings.findIndex(
({ assetProfile }) => {
return assetProfile.symbol === userCurrency;
return (
getAssetProfileIdentifier(assetProfile) ===
emergencyFundCashHoldingAssetProfileIdentifier
);
}
);
if (indexOfHoldingInBaseCurrency >= 0) {
holdings[indexOfHoldingInBaseCurrency] = emergencyFundCashHolding;
if (indexOfEmergencyFundCashHolding >= 0) {
holdings[indexOfEmergencyFundCashHolding] = emergencyFundCashHolding;
} else {
holdings.push(emergencyFundCashHolding);
}

56
apps/api/src/helper/object.helper.spec.ts

@ -109,8 +109,8 @@ describe('redactAttributes', () => {
}
},
hasError: false,
holdings: {
'AAPL.US': {
holdings: [
{
activitiesCount: 1,
currency: 'USD',
markets: {
@ -162,7 +162,7 @@ describe('redactAttributes', () => {
valueInBaseCurrency: 11039.5,
valueInPercentage: 0.0694356974830054
},
'ALV.DE': {
{
activitiesCount: 2,
currency: 'EUR',
markets: {
@ -209,7 +209,7 @@ describe('redactAttributes', () => {
valueInBaseCurrency: 6616.826601205088,
valueInPercentage: 0.04161818652826481
},
AMZN: {
{
activitiesCount: 1,
currency: 'USD',
markets: {
@ -261,7 +261,7 @@ describe('redactAttributes', () => {
valueInBaseCurrency: 18799,
valueInPercentage: 0.11824101426541227
},
bitcoin: {
{
activitiesCount: 1,
currency: 'USD',
markets: {
@ -312,7 +312,7 @@ describe('redactAttributes', () => {
valueInBaseCurrency: 36985.0332704,
valueInPercentage: 0.232626620912395
},
BONDORA_GO_AND_GROW: {
{
activitiesCount: 5,
currency: 'EUR',
markets: {
@ -363,7 +363,7 @@ describe('redactAttributes', () => {
valueInBaseCurrency: 2231.644722160232,
valueInPercentage: 0.014036487867880205
},
FRANKLY95P: {
{
activitiesCount: 6,
currency: 'CHF',
markets: {
@ -487,7 +487,7 @@ describe('redactAttributes', () => {
valueInBaseCurrency: 22363.19795483481,
valueInPercentage: 0.14065892911313693
},
MSFT: {
{
activitiesCount: 1,
currency: 'USD',
markets: {
@ -539,7 +539,7 @@ describe('redactAttributes', () => {
valueInBaseCurrency: 12840.6,
valueInPercentage: 0.08076416659271518
},
TSLA: {
{
activitiesCount: 1,
currency: 'USD',
markets: {
@ -591,7 +591,7 @@ describe('redactAttributes', () => {
valueInBaseCurrency: 39069,
valueInPercentage: 0.2457342510950259
},
VTI: {
{
activitiesCount: 5,
currency: 'USD',
markets: {
@ -763,7 +763,7 @@ describe('redactAttributes', () => {
valueInBaseCurrency: 14102.5,
valueInPercentage: 0.08870120238725339
},
'VWRL.SW': {
{
activitiesCount: 5,
currency: 'CHF',
markets: {
@ -1171,7 +1171,7 @@ describe('redactAttributes', () => {
valueInBaseCurrency: 23079.20085622547,
valueInPercentage: 0.145162408515095
},
'XDWD.DE': {
{
activitiesCount: 1,
currency: 'EUR',
markets: {
@ -1449,7 +1449,7 @@ describe('redactAttributes', () => {
valueInBaseCurrency: 8847.35550100424,
valueInPercentage: 0.055647656152211074
},
USD: {
{
activitiesCount: 0,
currency: 'USD',
allocationInPercentage: 0.20291717628620132,
@ -1476,7 +1476,7 @@ describe('redactAttributes', () => {
valueInBaseCurrency: 49890,
valueInPercentage: 0.3137956381563603
}
},
],
platforms: {
'a5b14588-49a0-48e4-b9f7-e186b27860b7': {
balance: 0,
@ -1613,8 +1613,8 @@ describe('redactAttributes', () => {
}
},
hasError: false,
holdings: {
'AAPL.US': {
holdings: [
{
activitiesCount: 1,
currency: 'USD',
markets: {
@ -1666,7 +1666,7 @@ describe('redactAttributes', () => {
valueInBaseCurrency: null,
valueInPercentage: 0.0694356974830054
},
'ALV.DE': {
{
activitiesCount: 2,
currency: 'EUR',
markets: {
@ -1713,7 +1713,7 @@ describe('redactAttributes', () => {
valueInBaseCurrency: null,
valueInPercentage: 0.04161818652826481
},
AMZN: {
{
activitiesCount: 1,
currency: 'USD',
markets: {
@ -1765,7 +1765,7 @@ describe('redactAttributes', () => {
valueInBaseCurrency: null,
valueInPercentage: 0.11824101426541227
},
bitcoin: {
{
activitiesCount: 1,
currency: 'USD',
markets: {
@ -1816,7 +1816,7 @@ describe('redactAttributes', () => {
valueInBaseCurrency: null,
valueInPercentage: 0.232626620912395
},
BONDORA_GO_AND_GROW: {
{
activitiesCount: 5,
currency: 'EUR',
markets: {
@ -1867,7 +1867,7 @@ describe('redactAttributes', () => {
valueInBaseCurrency: null,
valueInPercentage: 0.014036487867880205
},
FRANKLY95P: {
{
activitiesCount: 6,
currency: 'CHF',
markets: {
@ -1971,7 +1971,7 @@ describe('redactAttributes', () => {
valueInBaseCurrency: null,
valueInPercentage: 0.14065892911313693
},
MSFT: {
{
activitiesCount: 1,
currency: 'USD',
markets: {
@ -2023,7 +2023,7 @@ describe('redactAttributes', () => {
valueInBaseCurrency: null,
valueInPercentage: 0.08076416659271518
},
TSLA: {
{
activitiesCount: 1,
currency: 'USD',
markets: {
@ -2075,7 +2075,7 @@ describe('redactAttributes', () => {
valueInBaseCurrency: null,
valueInPercentage: 0.2457342510950259
},
VTI: {
{
activitiesCount: 5,
currency: 'USD',
markets: {
@ -2247,7 +2247,7 @@ describe('redactAttributes', () => {
valueInBaseCurrency: null,
valueInPercentage: 0.08870120238725339
},
'VWRL.SW': {
{
activitiesCount: 5,
currency: 'CHF',
markets: {
@ -2647,7 +2647,7 @@ describe('redactAttributes', () => {
valueInBaseCurrency: null,
valueInPercentage: 0.145162408515095
},
'XDWD.DE': {
{
activitiesCount: 1,
currency: 'EUR',
markets: {
@ -2925,7 +2925,7 @@ describe('redactAttributes', () => {
valueInBaseCurrency: null,
valueInPercentage: 0.055647656152211074
},
USD: {
{
activitiesCount: 0,
currency: 'USD',
allocationInPercentage: 0.20291717628620132,
@ -2952,7 +2952,7 @@ describe('redactAttributes', () => {
valueInBaseCurrency: null,
valueInPercentage: 0.3137956381563603
}
},
],
platforms: {
'a5b14588-49a0-48e4-b9f7-e186b27860b7': {
balance: null,

33
apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts

@ -119,7 +119,7 @@ export class GfAllocationsPageComponent implements OnInit {
[name: string]: { name: string; value: number };
};
protected symbols: {
[name: string]: {
[symbol: string]: {
dataSource?: DataSource;
isClickable?: boolean;
name: string;
@ -505,16 +505,27 @@ export class GfAllocationsPageComponent implements OnInit {
this.totalValueInEtf += this.holdings[assetProfileIdentifier].value;
}
this.symbols[assetProfileIdentifier] = {
dataSource: position.assetProfile.dataSource,
isClickable: canOpenHoldingDetail(position),
name: position.assetProfile.name ?? '',
symbol: position.assetProfile.symbol,
value:
(isNumber(position.valueInBaseCurrency)
? position.valueInBaseCurrency
: position.valueInPercentage) ?? 0
};
const symbol = position.assetProfile.symbol;
const value =
(isNumber(position.valueInBaseCurrency)
? position.valueInBaseCurrency
: position.valueInPercentage) ?? 0;
const symbolData = this.symbols[symbol];
if (symbolData) {
// Aggregate holdings with the same symbol from different data sources
symbolData.value += value;
} else {
this.symbols[symbol] = {
symbol,
value,
dataSource: position.assetProfile.dataSource,
isClickable: canOpenHoldingDetail(position),
name: position.assetProfile.name ?? ''
};
}
}
this.markets = this.portfolioDetails.markets;

27
apps/client/src/app/pages/public/public-page.component.ts

@ -93,7 +93,7 @@ export class GfPublicPageComponent implements OnInit {
[name: string]: { name: string; value: number };
};
protected symbols: {
[name: string]: { name: string; symbol: string; value: number };
[symbol: string]: { name: string; symbol: string; value: number };
};
protected readonly UNKNOWN_KEY = UNKNOWN_KEY;
@ -245,13 +245,24 @@ export class GfPublicPageComponent implements OnInit {
}
}
this.symbols[assetProfileIdentifier] = {
name: position.assetProfile.name ?? position.assetProfile.symbol,
symbol: position.assetProfile.symbol,
value: isNumber(position.valueInBaseCurrency)
? position.valueInBaseCurrency
: (position.valueInPercentage ?? 0)
};
const symbol = position.assetProfile.symbol;
const value = isNumber(position.valueInBaseCurrency)
? position.valueInBaseCurrency
: (position.valueInPercentage ?? 0);
const symbolData = this.symbols[symbol];
if (symbolData) {
// Aggregate holdings with the same symbol from different data sources
symbolData.value += value;
} else {
this.symbols[symbol] = {
symbol,
value,
name: position.assetProfile.name ?? symbol
};
}
}
}
}

Loading…
Cancel
Save