|
|
@ -8,8 +8,10 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; |
|
|
import { MAX_TOP_HOLDINGS, UNKNOWN_KEY } from '@ghostfolio/common/config'; |
|
|
import { MAX_TOP_HOLDINGS, UNKNOWN_KEY } from '@ghostfolio/common/config'; |
|
|
import { |
|
|
import { |
|
|
canOpenHoldingDetail, |
|
|
canOpenHoldingDetail, |
|
|
|
|
|
convertValuesToPercentagesOfTotal, |
|
|
getAssetProfileIdentifier, |
|
|
getAssetProfileIdentifier, |
|
|
getCountryName |
|
|
getCountryName, |
|
|
|
|
|
isCashPosition |
|
|
} from '@ghostfolio/common/helper'; |
|
|
} from '@ghostfolio/common/helper'; |
|
|
import { |
|
|
import { |
|
|
HoldingWithParents, |
|
|
HoldingWithParents, |
|
|
@ -270,6 +272,20 @@ export class GfAllocationsPageComponent implements OnInit { |
|
|
return UNKNOWN_KEY; |
|
|
return UNKNOWN_KEY; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private extractValue({ |
|
|
|
|
|
valueInBaseCurrency, |
|
|
|
|
|
valueInPercentage |
|
|
|
|
|
}: { |
|
|
|
|
|
valueInBaseCurrency?: PortfolioPosition['valueInBaseCurrency']; |
|
|
|
|
|
valueInPercentage?: PortfolioPosition['valueInPercentage']; |
|
|
|
|
|
}) { |
|
|
|
|
|
return ( |
|
|
|
|
|
(isNumber(valueInBaseCurrency) |
|
|
|
|
|
? valueInBaseCurrency |
|
|
|
|
|
: valueInPercentage) ?? 0 |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
private fetchPortfolioDetails() { |
|
|
private fetchPortfolioDetails() { |
|
|
return this.dataService.fetchPortfolioDetails({ |
|
|
return this.dataService.fetchPortfolioDetails({ |
|
|
filters: this.userService.getFilters(), |
|
|
filters: this.userService.getFilters(), |
|
|
@ -373,6 +389,8 @@ export class GfAllocationsPageComponent implements OnInit { |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
let totalValueExcludingCashPositions = 0; |
|
|
|
|
|
|
|
|
for (const position of this.portfolioDetails.holdings) { |
|
|
for (const position of this.portfolioDetails.holdings) { |
|
|
const assetProfileIdentifier = getAssetProfileIdentifier( |
|
|
const assetProfileIdentifier = getAssetProfileIdentifier( |
|
|
position.assetProfile |
|
|
position.assetProfile |
|
|
@ -397,111 +415,104 @@ export class GfAllocationsPageComponent implements OnInit { |
|
|
: (position.valueInBaseCurrency ?? 0) |
|
|
: (position.valueInBaseCurrency ?? 0) |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
// Prepare analysis data by continents, countries, holdings and sectors
|
|
|
if (!isCashPosition(position.assetProfile)) { |
|
|
|
|
|
// Prepare analysis data by continents, countries, holdings and sectors
|
|
|
if (position.assetProfile.countries.length > 0) { |
|
|
// except for cash
|
|
|
for (const country of position.assetProfile.countries) { |
|
|
|
|
|
const { code, continent, weight } = country; |
|
|
totalValueExcludingCashPositions += this.extractValue(position); |
|
|
const value = |
|
|
|
|
|
(isNumber(position.valueInBaseCurrency) |
|
|
if (position.assetProfile.countries.length > 0) { |
|
|
? position.valueInBaseCurrency |
|
|
for (const country of position.assetProfile.countries) { |
|
|
: position.valueInPercentage) ?? 0; |
|
|
const { code, continent, weight } = country; |
|
|
|
|
|
const value = this.extractValue(position); |
|
|
|
|
|
|
|
|
|
|
|
const continentData = this.continents[continent]; |
|
|
|
|
|
|
|
|
|
|
|
if (continentData) { |
|
|
|
|
|
continentData.value += weight * value; |
|
|
|
|
|
} else { |
|
|
|
|
|
this.continents[continent] = { |
|
|
|
|
|
name: translate(continent), |
|
|
|
|
|
value: weight * value |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const countryData = this.countries[code]; |
|
|
|
|
|
|
|
|
|
|
|
if (countryData) { |
|
|
|
|
|
countryData.value += weight * value; |
|
|
|
|
|
} else { |
|
|
|
|
|
this.countries[code] = { |
|
|
|
|
|
name: getCountryName({ code }), |
|
|
|
|
|
value: weight * value |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
const value = this.extractValue(position); |
|
|
|
|
|
|
|
|
const continentData = this.continents[continent]; |
|
|
const continentData = this.continents[UNKNOWN_KEY]; |
|
|
|
|
|
|
|
|
if (continentData) { |
|
|
if (continentData) { |
|
|
continentData.value += weight * value; |
|
|
continentData.value += value; |
|
|
} else { |
|
|
|
|
|
this.continents[continent] = { |
|
|
|
|
|
name: translate(continent), |
|
|
|
|
|
value: weight * value |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const countryData = this.countries[code]; |
|
|
const countryData = this.countries[UNKNOWN_KEY]; |
|
|
|
|
|
|
|
|
if (countryData) { |
|
|
if (countryData) { |
|
|
countryData.value += weight * value; |
|
|
countryData.value += value; |
|
|
} else { |
|
|
|
|
|
this.countries[code] = { |
|
|
|
|
|
name: getCountryName({ code }), |
|
|
|
|
|
value: weight * value |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} else { |
|
|
|
|
|
const value = |
|
|
|
|
|
(isNumber(position.valueInBaseCurrency) |
|
|
|
|
|
? position.valueInBaseCurrency |
|
|
|
|
|
: position.valueInPercentage) ?? 0; |
|
|
|
|
|
|
|
|
|
|
|
const continentData = this.continents[UNKNOWN_KEY]; |
|
|
|
|
|
|
|
|
|
|
|
if (continentData) { |
|
|
|
|
|
continentData.value += value; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const countryData = this.countries[UNKNOWN_KEY]; |
|
|
|
|
|
|
|
|
|
|
|
if (countryData) { |
|
|
if (position.assetProfile.holdings.length > 0) { |
|
|
countryData.value += value; |
|
|
for (const { |
|
|
} |
|
|
allocationInPercentage, |
|
|
} |
|
|
name, |
|
|
|
|
|
valueInBaseCurrency |
|
|
if (position.assetProfile.holdings.length > 0) { |
|
|
} of position.assetProfile.holdings) { |
|
|
for (const { |
|
|
const normalizedAssetName = this.normalizeAssetName(name); |
|
|
allocationInPercentage, |
|
|
const value = isNumber(valueInBaseCurrency) |
|
|
name, |
|
|
? valueInBaseCurrency |
|
|
valueInBaseCurrency |
|
|
: allocationInPercentage * (position.valueInPercentage ?? 0); |
|
|
} of position.assetProfile.holdings) { |
|
|
|
|
|
const normalizedAssetName = this.normalizeAssetName(name); |
|
|
const holdingData = this.topHoldingsMap[normalizedAssetName]; |
|
|
const value = isNumber(valueInBaseCurrency) |
|
|
|
|
|
? valueInBaseCurrency |
|
|
if (holdingData) { |
|
|
: allocationInPercentage * (position.valueInPercentage ?? 0); |
|
|
holdingData.value += value; |
|
|
|
|
|
} else { |
|
|
const holdingData = this.topHoldingsMap[normalizedAssetName]; |
|
|
this.topHoldingsMap[normalizedAssetName] = { |
|
|
|
|
|
name, |
|
|
if (holdingData) { |
|
|
value |
|
|
holdingData.value += value; |
|
|
}; |
|
|
} else { |
|
|
} |
|
|
this.topHoldingsMap[normalizedAssetName] = { |
|
|
|
|
|
name, |
|
|
|
|
|
value |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (position.assetProfile.sectors.length > 0) { |
|
|
if (position.assetProfile.sectors.length > 0) { |
|
|
for (const sector of position.assetProfile.sectors) { |
|
|
for (const sector of position.assetProfile.sectors) { |
|
|
const { name, weight } = sector; |
|
|
const { name, weight } = sector; |
|
|
const value = |
|
|
const value = this.extractValue(position); |
|
|
(isNumber(position.valueInBaseCurrency) |
|
|
|
|
|
? position.valueInBaseCurrency |
|
|
const sectorData = this.sectors[name]; |
|
|
: position.valueInPercentage) ?? 0; |
|
|
|
|
|
|
|
|
if (sectorData) { |
|
|
|
|
|
sectorData.value += weight * value; |
|
|
|
|
|
} else { |
|
|
|
|
|
this.sectors[name] = { |
|
|
|
|
|
name: translate(name), |
|
|
|
|
|
value: weight * value |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
const value = this.extractValue(position); |
|
|
|
|
|
|
|
|
const sectorData = this.sectors[name]; |
|
|
const sectorData = this.sectors[UNKNOWN_KEY]; |
|
|
|
|
|
|
|
|
if (sectorData) { |
|
|
if (sectorData) { |
|
|
sectorData.value += weight * value; |
|
|
sectorData.value += value; |
|
|
} else { |
|
|
|
|
|
this.sectors[name] = { |
|
|
|
|
|
name: translate(name), |
|
|
|
|
|
value: weight * value |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} else { |
|
|
|
|
|
const value = |
|
|
|
|
|
(isNumber(position.valueInBaseCurrency) |
|
|
|
|
|
? position.valueInBaseCurrency |
|
|
|
|
|
: position.valueInPercentage) ?? 0; |
|
|
|
|
|
|
|
|
|
|
|
const sectorData = this.sectors[UNKNOWN_KEY]; |
|
|
|
|
|
|
|
|
|
|
|
if (sectorData) { |
|
|
|
|
|
sectorData.value += value; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (this.holdings[assetProfileIdentifier].assetSubClass === 'ETF') { |
|
|
if (this.holdings[assetProfileIdentifier].assetSubClass === 'ETF') { |
|
|
@ -510,10 +521,7 @@ export class GfAllocationsPageComponent implements OnInit { |
|
|
|
|
|
|
|
|
const symbol = position.assetProfile.symbol; |
|
|
const symbol = position.assetProfile.symbol; |
|
|
|
|
|
|
|
|
const value = |
|
|
const value = this.extractValue(position); |
|
|
(isNumber(position.valueInBaseCurrency) |
|
|
|
|
|
? position.valueInBaseCurrency |
|
|
|
|
|
: position.valueInPercentage) ?? 0; |
|
|
|
|
|
|
|
|
|
|
|
const symbolData = this.symbols[symbol]; |
|
|
const symbolData = this.symbols[symbol]; |
|
|
|
|
|
|
|
|
@ -533,6 +541,17 @@ export class GfAllocationsPageComponent implements OnInit { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (this.showValuesInPercentage()) { |
|
|
|
|
|
// The values are percentages of the whole portfolio, but the analysis
|
|
|
|
|
|
// data does not contain the cash positions
|
|
|
|
|
|
for (const values of [this.continents, this.countries, this.sectors]) { |
|
|
|
|
|
convertValuesToPercentagesOfTotal({ |
|
|
|
|
|
values, |
|
|
|
|
|
total: totalValueExcludingCashPositions |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
this.markets = this.portfolioDetails.markets; |
|
|
this.markets = this.portfolioDetails.markets; |
|
|
|
|
|
|
|
|
if (this.portfolioDetails.marketsAdvanced) { |
|
|
if (this.portfolioDetails.marketsAdvanced) { |
|
|
|