|
|
@ -12,7 +12,7 @@ import { |
|
|
User |
|
|
User |
|
|
} from '@ghostfolio/common/interfaces'; |
|
|
} from '@ghostfolio/common/interfaces'; |
|
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; |
|
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; |
|
|
import { Market, MarketAdvanced } from '@ghostfolio/common/types'; |
|
|
import { MarketAdvanced } from '@ghostfolio/common/types'; |
|
|
import { translate } from '@ghostfolio/ui/i18n'; |
|
|
import { translate } from '@ghostfolio/ui/i18n'; |
|
|
import { GfPortfolioProportionChartComponent } from '@ghostfolio/ui/portfolio-proportion-chart'; |
|
|
import { GfPortfolioProportionChartComponent } from '@ghostfolio/ui/portfolio-proportion-chart'; |
|
|
import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; |
|
|
import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; |
|
|
@ -24,7 +24,9 @@ import { GfWorldMapChartComponent } from '@ghostfolio/ui/world-map-chart'; |
|
|
import { |
|
|
import { |
|
|
ChangeDetectorRef, |
|
|
ChangeDetectorRef, |
|
|
Component, |
|
|
Component, |
|
|
|
|
|
computed, |
|
|
DestroyRef, |
|
|
DestroyRef, |
|
|
|
|
|
inject, |
|
|
OnInit |
|
|
OnInit |
|
|
} from '@angular/core'; |
|
|
} from '@angular/core'; |
|
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; |
|
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; |
|
|
@ -41,6 +43,9 @@ import { |
|
|
} from '@prisma/client'; |
|
|
} from '@prisma/client'; |
|
|
import { isNumber } from 'lodash'; |
|
|
import { isNumber } from 'lodash'; |
|
|
import { DeviceDetectorService } from 'ngx-device-detector'; |
|
|
import { DeviceDetectorService } from 'ngx-device-detector'; |
|
|
|
|
|
import { filter, switchMap, tap } from 'rxjs'; |
|
|
|
|
|
|
|
|
|
|
|
import { AllocationsPageParams } from './interfaces/interfaces'; |
|
|
|
|
|
|
|
|
@Component({ |
|
|
@Component({ |
|
|
imports: [ |
|
|
imports: [ |
|
|
@ -57,21 +62,23 @@ import { DeviceDetectorService } from 'ngx-device-detector'; |
|
|
templateUrl: './allocations-page.html' |
|
|
templateUrl: './allocations-page.html' |
|
|
}) |
|
|
}) |
|
|
export class GfAllocationsPageComponent implements OnInit { |
|
|
export class GfAllocationsPageComponent implements OnInit { |
|
|
public accounts: { |
|
|
protected accounts: { |
|
|
[id: string]: Pick<Account, 'name'> & { |
|
|
[id: string]: Pick<Account, 'name'> & { |
|
|
id: string; |
|
|
id: string; |
|
|
value: number; |
|
|
value: number; |
|
|
}; |
|
|
}; |
|
|
}; |
|
|
}; |
|
|
public continents: { |
|
|
protected continents: { |
|
|
[code: string]: { name: string; value: number }; |
|
|
[code: string]: { name: string; value: number }; |
|
|
}; |
|
|
}; |
|
|
public countries: { |
|
|
protected countries: { |
|
|
[code: string]: { name: string; value: number }; |
|
|
[code: string]: { name: string; value: number }; |
|
|
}; |
|
|
}; |
|
|
public deviceType: string; |
|
|
protected readonly deviceType = computed( |
|
|
public hasImpersonationId: boolean; |
|
|
() => this.deviceDetectorService.deviceInfo().deviceType |
|
|
public holdings: { |
|
|
); |
|
|
|
|
|
protected hasImpersonationId: boolean; |
|
|
|
|
|
protected holdings: { |
|
|
[symbol: string]: Pick< |
|
|
[symbol: string]: Pick< |
|
|
PortfolioPosition['assetProfile'], |
|
|
PortfolioPosition['assetProfile'], |
|
|
| 'assetClass' |
|
|
| 'assetClass' |
|
|
@ -82,28 +89,26 @@ export class GfAllocationsPageComponent implements OnInit { |
|
|
| 'name' |
|
|
| 'name' |
|
|
> & { etfProvider: string; exchange?: string; value: number }; |
|
|
> & { etfProvider: string; exchange?: string; value: number }; |
|
|
}; |
|
|
}; |
|
|
public isLoading = false; |
|
|
protected isLoading = false; |
|
|
public markets: { |
|
|
protected markets: PortfolioDetails['markets']; |
|
|
[key in Market]: { id: Market; valueInPercentage: number }; |
|
|
protected marketsAdvanced: { |
|
|
}; |
|
|
|
|
|
public marketsAdvanced: { |
|
|
|
|
|
[key in MarketAdvanced]: { |
|
|
[key in MarketAdvanced]: { |
|
|
id: MarketAdvanced; |
|
|
id: MarketAdvanced; |
|
|
name: string; |
|
|
name: string; |
|
|
value: number; |
|
|
value: number; |
|
|
}; |
|
|
}; |
|
|
}; |
|
|
}; |
|
|
public platforms: { |
|
|
protected platforms: { |
|
|
[id: string]: Pick<Platform, 'name'> & { |
|
|
[id: string]: Pick<Platform, 'name'> & { |
|
|
id: string; |
|
|
id: string; |
|
|
value: number; |
|
|
value: number; |
|
|
}; |
|
|
}; |
|
|
}; |
|
|
}; |
|
|
public portfolioDetails: PortfolioDetails; |
|
|
protected portfolioDetails: PortfolioDetails; |
|
|
public sectors: { |
|
|
protected sectors: { |
|
|
[name: string]: { name: string; value: number }; |
|
|
[name: string]: { name: string; value: number }; |
|
|
}; |
|
|
}; |
|
|
public symbols: { |
|
|
protected symbols: { |
|
|
[name: string]: { |
|
|
[name: string]: { |
|
|
dataSource?: DataSource; |
|
|
dataSource?: DataSource; |
|
|
name: string; |
|
|
name: string; |
|
|
@ -111,38 +116,46 @@ export class GfAllocationsPageComponent implements OnInit { |
|
|
value: number; |
|
|
value: number; |
|
|
}; |
|
|
}; |
|
|
}; |
|
|
}; |
|
|
public topHoldings: HoldingWithParents[]; |
|
|
protected topHoldings: HoldingWithParents[]; |
|
|
public topHoldingsMap: { |
|
|
protected readonly UNKNOWN_KEY = UNKNOWN_KEY; |
|
|
|
|
|
protected user: User; |
|
|
|
|
|
|
|
|
|
|
|
private topHoldingsMap: { |
|
|
[name: string]: { name: string; value: number }; |
|
|
[name: string]: { name: string; value: number }; |
|
|
}; |
|
|
}; |
|
|
public totalValueInEtf = 0; |
|
|
private totalValueInEtf = 0; |
|
|
public UNKNOWN_KEY = UNKNOWN_KEY; |
|
|
|
|
|
public user: User; |
|
|
private readonly changeDetectorRef = inject(ChangeDetectorRef); |
|
|
public worldMapChartFormat: string; |
|
|
private readonly dataService = inject(DataService); |
|
|
|
|
|
private readonly destroyRef = inject(DestroyRef); |
|
|
public constructor( |
|
|
private readonly deviceDetectorService = inject(DeviceDetectorService); |
|
|
private changeDetectorRef: ChangeDetectorRef, |
|
|
private readonly dialog = inject(MatDialog); |
|
|
private dataService: DataService, |
|
|
private readonly impersonationStorageService = inject( |
|
|
private destroyRef: DestroyRef, |
|
|
ImpersonationStorageService |
|
|
private deviceDetectorService: DeviceDetectorService, |
|
|
); |
|
|
private dialog: MatDialog, |
|
|
private readonly route = inject(ActivatedRoute); |
|
|
private impersonationStorageService: ImpersonationStorageService, |
|
|
private readonly router = inject(Router); |
|
|
private route: ActivatedRoute, |
|
|
private readonly userService = inject(UserService); |
|
|
private router: Router, |
|
|
|
|
|
private userService: UserService |
|
|
public constructor() { |
|
|
) { |
|
|
|
|
|
this.route.queryParams |
|
|
this.route.queryParams |
|
|
.pipe(takeUntilDestroyed(this.destroyRef)) |
|
|
.pipe(takeUntilDestroyed(this.destroyRef)) |
|
|
.subscribe((params) => { |
|
|
.subscribe( |
|
|
if (params['accountId'] && params['accountDetailDialog']) { |
|
|
({ accountId, accountDetailDialog }: AllocationsPageParams) => { |
|
|
this.openAccountDetailDialog(params['accountId']); |
|
|
if (accountId && accountDetailDialog) { |
|
|
|
|
|
this.openAccountDetailDialog(accountId); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
}); |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public ngOnInit() { |
|
|
protected get worldMapChartFormat(): string { |
|
|
this.deviceType = this.deviceDetectorService.getDeviceInfo().deviceType; |
|
|
return this.showValuesInPercentage() |
|
|
|
|
|
? '{0}%' |
|
|
|
|
|
: `{0} ${this.user?.settings?.baseCurrency}`; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public ngOnInit() { |
|
|
this.impersonationStorageService |
|
|
this.impersonationStorageService |
|
|
.onChangeHasImpersonation() |
|
|
.onChangeHasImpersonation() |
|
|
.pipe(takeUntilDestroyed(this.destroyRef)) |
|
|
.pipe(takeUntilDestroyed(this.destroyRef)) |
|
|
@ -151,56 +164,58 @@ export class GfAllocationsPageComponent implements OnInit { |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
this.userService.stateChanged |
|
|
this.userService.stateChanged |
|
|
.pipe(takeUntilDestroyed(this.destroyRef)) |
|
|
.pipe( |
|
|
.subscribe((state) => { |
|
|
filter((state) => !!state?.user), |
|
|
if (state?.user) { |
|
|
tap((state) => { |
|
|
this.user = state.user; |
|
|
this.user = state.user; |
|
|
|
|
|
|
|
|
this.worldMapChartFormat = this.showValuesInPercentage() |
|
|
|
|
|
? `{0}%` |
|
|
|
|
|
: `{0} ${this.user?.settings?.baseCurrency}`; |
|
|
|
|
|
|
|
|
|
|
|
this.isLoading = true; |
|
|
this.isLoading = true; |
|
|
|
|
|
|
|
|
this.initialize(); |
|
|
this.initialize(); |
|
|
|
|
|
|
|
|
this.fetchPortfolioDetails() |
|
|
this.changeDetectorRef.markForCheck(); |
|
|
.pipe(takeUntilDestroyed(this.destroyRef)) |
|
|
}), |
|
|
.subscribe((portfolioDetails) => { |
|
|
switchMap(() => this.fetchPortfolioDetails()), |
|
|
this.initialize(); |
|
|
takeUntilDestroyed(this.destroyRef) |
|
|
|
|
|
) |
|
|
this.portfolioDetails = portfolioDetails; |
|
|
.subscribe((portfolioDetails) => { |
|
|
|
|
|
this.initialize(); |
|
|
|
|
|
|
|
|
this.initializeAllocationsData(); |
|
|
this.portfolioDetails = portfolioDetails; |
|
|
|
|
|
|
|
|
this.isLoading = false; |
|
|
this.initializeAllocationsData(); |
|
|
|
|
|
|
|
|
this.changeDetectorRef.markForCheck(); |
|
|
this.isLoading = false; |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
this.changeDetectorRef.markForCheck(); |
|
|
this.changeDetectorRef.markForCheck(); |
|
|
} |
|
|
|
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
this.initialize(); |
|
|
this.initialize(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public onAccountChartClicked({ accountId }: { accountId: string }) { |
|
|
protected onAccountChartClicked({ accountId }: { accountId: string }) { |
|
|
if (accountId && accountId !== UNKNOWN_KEY) { |
|
|
if (accountId && accountId !== UNKNOWN_KEY) { |
|
|
this.router.navigate([], { |
|
|
void this.router.navigate([], { |
|
|
queryParams: { accountId, accountDetailDialog: true } |
|
|
queryParams: { accountId, accountDetailDialog: true } |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public onSymbolChartClicked({ dataSource, symbol }: AssetProfileIdentifier) { |
|
|
protected onSymbolChartClicked({ |
|
|
|
|
|
dataSource, |
|
|
|
|
|
symbol |
|
|
|
|
|
}: AssetProfileIdentifier) { |
|
|
if (dataSource && symbol) { |
|
|
if (dataSource && symbol) { |
|
|
this.router.navigate([], { |
|
|
void this.router.navigate([], { |
|
|
queryParams: { dataSource, symbol, holdingDetailDialog: true } |
|
|
queryParams: { dataSource, symbol, holdingDetailDialog: true } |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected showValuesInPercentage() { |
|
|
|
|
|
return this.hasImpersonationId || this.user?.settings?.isRestrictedView; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
private extractCurrency({ |
|
|
private extractCurrency({ |
|
|
assetClass, |
|
|
assetClass, |
|
|
assetSubClass, |
|
|
assetSubClass, |
|
|
@ -226,9 +241,9 @@ export class GfAllocationsPageComponent implements OnInit { |
|
|
name |
|
|
name |
|
|
}: { |
|
|
}: { |
|
|
assetSubClass: PortfolioPosition['assetProfile']['assetSubClass']; |
|
|
assetSubClass: PortfolioPosition['assetProfile']['assetSubClass']; |
|
|
name: string; |
|
|
name?: string; |
|
|
}) { |
|
|
}) { |
|
|
if (assetSubClass === 'ETF') { |
|
|
if (assetSubClass === 'ETF' && name) { |
|
|
const [firstWord] = name.split(' '); |
|
|
const [firstWord] = name.split(' '); |
|
|
return firstWord; |
|
|
return firstWord; |
|
|
} |
|
|
} |
|
|
@ -298,7 +313,7 @@ export class GfAllocationsPageComponent implements OnInit { |
|
|
this.platforms = {}; |
|
|
this.platforms = {}; |
|
|
this.portfolioDetails = { |
|
|
this.portfolioDetails = { |
|
|
accounts: {}, |
|
|
accounts: {}, |
|
|
createdAt: undefined, |
|
|
createdAt: new Date(), |
|
|
holdings: {}, |
|
|
holdings: {}, |
|
|
platforms: {}, |
|
|
platforms: {}, |
|
|
summary: undefined |
|
|
summary: undefined |
|
|
@ -327,7 +342,7 @@ export class GfAllocationsPageComponent implements OnInit { |
|
|
let value = 0; |
|
|
let value = 0; |
|
|
|
|
|
|
|
|
if (this.showValuesInPercentage()) { |
|
|
if (this.showValuesInPercentage()) { |
|
|
value = valueInPercentage; |
|
|
value = valueInPercentage ?? 0; |
|
|
} else { |
|
|
} else { |
|
|
value = valueInBaseCurrency; |
|
|
value = valueInBaseCurrency; |
|
|
} |
|
|
} |
|
|
@ -342,30 +357,24 @@ export class GfAllocationsPageComponent implements OnInit { |
|
|
for (const [symbol, position] of Object.entries( |
|
|
for (const [symbol, position] of Object.entries( |
|
|
this.portfolioDetails.holdings |
|
|
this.portfolioDetails.holdings |
|
|
)) { |
|
|
)) { |
|
|
let value = 0; |
|
|
|
|
|
|
|
|
|
|
|
if (this.showValuesInPercentage()) { |
|
|
|
|
|
value = position.allocationInPercentage; |
|
|
|
|
|
} else { |
|
|
|
|
|
value = position.valueInBaseCurrency; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this.holdings[symbol] = { |
|
|
this.holdings[symbol] = { |
|
|
value, |
|
|
|
|
|
assetClass: |
|
|
assetClass: |
|
|
position.assetProfile.assetClass || (UNKNOWN_KEY as AssetClass), |
|
|
position.assetProfile.assetClass || (UNKNOWN_KEY as AssetClass), |
|
|
assetClassLabel: position.assetProfile.assetClassLabel || UNKNOWN_KEY, |
|
|
assetClassLabel: position.assetProfile.assetClassLabel ?? UNKNOWN_KEY, |
|
|
assetSubClass: |
|
|
assetSubClass: |
|
|
position.assetProfile.assetSubClass || (UNKNOWN_KEY as AssetSubClass), |
|
|
position.assetProfile.assetSubClass || (UNKNOWN_KEY as AssetSubClass), |
|
|
assetSubClassLabel: |
|
|
assetSubClassLabel: |
|
|
position.assetProfile.assetSubClassLabel || UNKNOWN_KEY, |
|
|
position.assetProfile.assetSubClassLabel ?? UNKNOWN_KEY, |
|
|
currency: this.extractCurrency(position.assetProfile), |
|
|
currency: this.extractCurrency(position.assetProfile), |
|
|
etfProvider: this.extractEtfProvider({ |
|
|
etfProvider: this.extractEtfProvider({ |
|
|
assetSubClass: position.assetProfile.assetSubClass, |
|
|
assetSubClass: position.assetProfile.assetSubClass, |
|
|
name: position.assetProfile.name |
|
|
name: position.assetProfile.name |
|
|
}), |
|
|
}), |
|
|
exchange: position.exchange, |
|
|
exchange: position.exchange, |
|
|
name: position.assetProfile.name |
|
|
name: position.assetProfile.name, |
|
|
|
|
|
value: this.showValuesInPercentage() |
|
|
|
|
|
? position.allocationInPercentage |
|
|
|
|
|
: (position.valueInBaseCurrency ?? 0) |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
// Prepare analysis data by continents, countries, holdings and sectors
|
|
|
// Prepare analysis data by continents, countries, holdings and sectors
|
|
|
@ -373,53 +382,50 @@ export class GfAllocationsPageComponent implements OnInit { |
|
|
if (position.assetProfile.countries.length > 0) { |
|
|
if (position.assetProfile.countries.length > 0) { |
|
|
for (const country of position.assetProfile.countries) { |
|
|
for (const country of position.assetProfile.countries) { |
|
|
const { code, continent, weight } = country; |
|
|
const { code, continent, weight } = country; |
|
|
|
|
|
const value = |
|
|
|
|
|
(isNumber(position.valueInBaseCurrency) |
|
|
|
|
|
? position.valueInBaseCurrency |
|
|
|
|
|
: position.valueInPercentage) ?? 0; |
|
|
|
|
|
|
|
|
|
|
|
const continentData = this.continents[continent]; |
|
|
|
|
|
|
|
|
if (this.continents[continent]?.value) { |
|
|
if (continentData) { |
|
|
this.continents[continent].value += |
|
|
continentData.value += weight * value; |
|
|
weight * |
|
|
|
|
|
(isNumber(position.valueInBaseCurrency) |
|
|
|
|
|
? position.valueInBaseCurrency |
|
|
|
|
|
: position.valueInPercentage); |
|
|
|
|
|
} else { |
|
|
} else { |
|
|
this.continents[continent] = { |
|
|
this.continents[continent] = { |
|
|
name: translate(continent), |
|
|
name: translate(continent), |
|
|
value: |
|
|
value: weight * value |
|
|
weight * |
|
|
|
|
|
(isNumber(position.valueInBaseCurrency) |
|
|
|
|
|
? this.portfolioDetails.holdings[symbol].valueInBaseCurrency |
|
|
|
|
|
: this.portfolioDetails.holdings[symbol].valueInPercentage) |
|
|
|
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (this.countries[code]?.value) { |
|
|
const countryData = this.countries[code]; |
|
|
this.countries[code].value += |
|
|
|
|
|
weight * |
|
|
if (countryData) { |
|
|
(isNumber(position.valueInBaseCurrency) |
|
|
countryData.value += weight * value; |
|
|
? position.valueInBaseCurrency |
|
|
|
|
|
: position.valueInPercentage); |
|
|
|
|
|
} else { |
|
|
} else { |
|
|
this.countries[code] = { |
|
|
this.countries[code] = { |
|
|
name: getCountryName({ code }), |
|
|
name: getCountryName({ code }), |
|
|
value: |
|
|
value: weight * value |
|
|
weight * |
|
|
|
|
|
(isNumber(position.valueInBaseCurrency) |
|
|
|
|
|
? this.portfolioDetails.holdings[symbol].valueInBaseCurrency |
|
|
|
|
|
: this.portfolioDetails.holdings[symbol].valueInPercentage) |
|
|
|
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} else { |
|
|
} else { |
|
|
this.continents[UNKNOWN_KEY].value += isNumber( |
|
|
const value = |
|
|
position.valueInBaseCurrency |
|
|
(isNumber(position.valueInBaseCurrency) |
|
|
) |
|
|
? position.valueInBaseCurrency |
|
|
? this.portfolioDetails.holdings[symbol].valueInBaseCurrency |
|
|
: position.valueInPercentage) ?? 0; |
|
|
: this.portfolioDetails.holdings[symbol].valueInPercentage; |
|
|
|
|
|
|
|
|
const continentData = this.continents[UNKNOWN_KEY]; |
|
|
this.countries[UNKNOWN_KEY].value += isNumber( |
|
|
|
|
|
position.valueInBaseCurrency |
|
|
if (continentData) { |
|
|
) |
|
|
continentData.value += value; |
|
|
? this.portfolioDetails.holdings[symbol].valueInBaseCurrency |
|
|
} |
|
|
: this.portfolioDetails.holdings[symbol].valueInPercentage; |
|
|
|
|
|
|
|
|
const countryData = this.countries[UNKNOWN_KEY]; |
|
|
|
|
|
|
|
|
|
|
|
if (countryData) { |
|
|
|
|
|
countryData.value += value; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (position.assetProfile.holdings.length > 0) { |
|
|
if (position.assetProfile.holdings.length > 0) { |
|
|
@ -429,21 +435,18 @@ export class GfAllocationsPageComponent implements OnInit { |
|
|
valueInBaseCurrency |
|
|
valueInBaseCurrency |
|
|
} of position.assetProfile.holdings) { |
|
|
} of position.assetProfile.holdings) { |
|
|
const normalizedAssetName = this.normalizeAssetName(name); |
|
|
const normalizedAssetName = this.normalizeAssetName(name); |
|
|
|
|
|
const value = isNumber(valueInBaseCurrency) |
|
|
|
|
|
? valueInBaseCurrency |
|
|
|
|
|
: allocationInPercentage * (position.valueInPercentage ?? 0); |
|
|
|
|
|
|
|
|
|
|
|
const holdingData = this.topHoldingsMap[normalizedAssetName]; |
|
|
|
|
|
|
|
|
if (this.topHoldingsMap[normalizedAssetName]?.value) { |
|
|
if (holdingData) { |
|
|
this.topHoldingsMap[normalizedAssetName].value += isNumber( |
|
|
holdingData.value += value; |
|
|
valueInBaseCurrency |
|
|
|
|
|
) |
|
|
|
|
|
? valueInBaseCurrency |
|
|
|
|
|
: allocationInPercentage * |
|
|
|
|
|
this.portfolioDetails.holdings[symbol].valueInPercentage; |
|
|
|
|
|
} else { |
|
|
} else { |
|
|
this.topHoldingsMap[normalizedAssetName] = { |
|
|
this.topHoldingsMap[normalizedAssetName] = { |
|
|
name, |
|
|
name, |
|
|
value: isNumber(valueInBaseCurrency) |
|
|
value |
|
|
? valueInBaseCurrency |
|
|
|
|
|
: allocationInPercentage * |
|
|
|
|
|
this.portfolioDetails.holdings[symbol].valueInPercentage |
|
|
|
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
@ -452,30 +455,33 @@ export class GfAllocationsPageComponent implements OnInit { |
|
|
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 = |
|
|
|
|
|
(isNumber(position.valueInBaseCurrency) |
|
|
|
|
|
? position.valueInBaseCurrency |
|
|
|
|
|
: position.valueInPercentage) ?? 0; |
|
|
|
|
|
|
|
|
if (this.sectors[name]?.value) { |
|
|
const sectorData = this.sectors[name]; |
|
|
this.sectors[name].value += |
|
|
|
|
|
weight * |
|
|
if (sectorData) { |
|
|
(isNumber(position.valueInBaseCurrency) |
|
|
sectorData.value += weight * value; |
|
|
? position.valueInBaseCurrency |
|
|
|
|
|
: position.valueInPercentage); |
|
|
|
|
|
} else { |
|
|
} else { |
|
|
this.sectors[name] = { |
|
|
this.sectors[name] = { |
|
|
name: translate(name), |
|
|
name: translate(name), |
|
|
value: |
|
|
value: weight * value |
|
|
weight * |
|
|
|
|
|
(isNumber(position.valueInBaseCurrency) |
|
|
|
|
|
? this.portfolioDetails.holdings[symbol].valueInBaseCurrency |
|
|
|
|
|
: this.portfolioDetails.holdings[symbol].valueInPercentage) |
|
|
|
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} else { |
|
|
} else { |
|
|
this.sectors[UNKNOWN_KEY].value += isNumber( |
|
|
const value = |
|
|
position.valueInBaseCurrency |
|
|
(isNumber(position.valueInBaseCurrency) |
|
|
) |
|
|
? position.valueInBaseCurrency |
|
|
? this.portfolioDetails.holdings[symbol].valueInBaseCurrency |
|
|
: position.valueInPercentage) ?? 0; |
|
|
: this.portfolioDetails.holdings[symbol].valueInPercentage; |
|
|
|
|
|
|
|
|
const sectorData = this.sectors[UNKNOWN_KEY]; |
|
|
|
|
|
|
|
|
|
|
|
if (sectorData) { |
|
|
|
|
|
sectorData.value += value; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (this.holdings[symbol].assetSubClass === 'ETF') { |
|
|
if (this.holdings[symbol].assetSubClass === 'ETF') { |
|
|
@ -484,23 +490,26 @@ export class GfAllocationsPageComponent implements OnInit { |
|
|
|
|
|
|
|
|
this.symbols[prettifySymbol(symbol)] = { |
|
|
this.symbols[prettifySymbol(symbol)] = { |
|
|
dataSource: position.assetProfile.dataSource, |
|
|
dataSource: position.assetProfile.dataSource, |
|
|
name: position.assetProfile.name, |
|
|
name: position.assetProfile.name ?? '', |
|
|
symbol: prettifySymbol(symbol), |
|
|
symbol: prettifySymbol(symbol), |
|
|
value: isNumber(position.valueInBaseCurrency) |
|
|
value: |
|
|
? position.valueInBaseCurrency |
|
|
(isNumber(position.valueInBaseCurrency) |
|
|
: position.valueInPercentage |
|
|
? position.valueInBaseCurrency |
|
|
|
|
|
: position.valueInPercentage) ?? 0 |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
this.markets = this.portfolioDetails.markets; |
|
|
this.markets = this.portfolioDetails.markets; |
|
|
|
|
|
|
|
|
Object.values(this.portfolioDetails.marketsAdvanced).forEach( |
|
|
if (this.portfolioDetails.marketsAdvanced) { |
|
|
({ id, valueInBaseCurrency, valueInPercentage }) => { |
|
|
Object.values(this.portfolioDetails.marketsAdvanced).forEach( |
|
|
this.marketsAdvanced[id].value = isNumber(valueInBaseCurrency) |
|
|
({ id, valueInBaseCurrency, valueInPercentage }) => { |
|
|
? valueInBaseCurrency |
|
|
this.marketsAdvanced[id].value = isNumber(valueInBaseCurrency) |
|
|
: valueInPercentage; |
|
|
? valueInBaseCurrency |
|
|
} |
|
|
: valueInPercentage; |
|
|
); |
|
|
} |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
for (const [ |
|
|
for (const [ |
|
|
id, |
|
|
id, |
|
|
@ -509,7 +518,7 @@ export class GfAllocationsPageComponent implements OnInit { |
|
|
let value = 0; |
|
|
let value = 0; |
|
|
|
|
|
|
|
|
if (this.showValuesInPercentage()) { |
|
|
if (this.showValuesInPercentage()) { |
|
|
value = valueInPercentage; |
|
|
value = valueInPercentage ?? 0; |
|
|
} else { |
|
|
} else { |
|
|
value = valueInBaseCurrency; |
|
|
value = valueInBaseCurrency; |
|
|
} |
|
|
} |
|
|
@ -522,12 +531,11 @@ export class GfAllocationsPageComponent implements OnInit { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
this.topHoldings = Object.values(this.topHoldingsMap) |
|
|
this.topHoldings = Object.values(this.topHoldingsMap) |
|
|
.map(({ name, value }) => { |
|
|
.map(({ name, value }): HoldingWithParents => { |
|
|
if (this.showValuesInPercentage()) { |
|
|
if (this.showValuesInPercentage()) { |
|
|
return { |
|
|
return { |
|
|
name, |
|
|
name, |
|
|
allocationInPercentage: value, |
|
|
allocationInPercentage: value |
|
|
valueInBaseCurrency: null |
|
|
|
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -547,11 +555,12 @@ export class GfAllocationsPageComponent implements OnInit { |
|
|
} |
|
|
} |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
return currentParentHolding |
|
|
return currentParentHolding && |
|
|
|
|
|
isNumber(currentParentHolding.valueInBaseCurrency) |
|
|
? { |
|
|
? { |
|
|
allocationInPercentage: |
|
|
allocationInPercentage: |
|
|
currentParentHolding.valueInBaseCurrency / value, |
|
|
currentParentHolding.valueInBaseCurrency / value, |
|
|
name: holding.assetProfile.name, |
|
|
name: holding.assetProfile.name ?? '', |
|
|
position: holding, |
|
|
position: holding, |
|
|
symbol: prettifySymbol(symbol), |
|
|
symbol: prettifySymbol(symbol), |
|
|
valueInBaseCurrency: |
|
|
valueInBaseCurrency: |
|
|
@ -596,26 +605,22 @@ export class GfAllocationsPageComponent implements OnInit { |
|
|
autoFocus: false, |
|
|
autoFocus: false, |
|
|
data: { |
|
|
data: { |
|
|
accountId: aAccountId, |
|
|
accountId: aAccountId, |
|
|
deviceType: this.deviceType, |
|
|
deviceType: this.deviceType(), |
|
|
hasImpersonationId: this.hasImpersonationId, |
|
|
hasImpersonationId: this.hasImpersonationId, |
|
|
hasPermissionToCreateActivity: |
|
|
hasPermissionToCreateActivity: |
|
|
!this.hasImpersonationId && |
|
|
!this.hasImpersonationId && |
|
|
hasPermission(this.user?.permissions, permissions.createActivity) && |
|
|
hasPermission(this.user?.permissions, permissions.createActivity) && |
|
|
!this.user?.settings?.isRestrictedView |
|
|
!this.user?.settings?.isRestrictedView |
|
|
}, |
|
|
}, |
|
|
height: this.deviceType === 'mobile' ? '98vh' : '80vh', |
|
|
height: this.deviceType() === 'mobile' ? '98vh' : '80vh', |
|
|
width: this.deviceType === 'mobile' ? '100vw' : '50rem' |
|
|
width: this.deviceType() === 'mobile' ? '100vw' : '50rem' |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
dialogRef |
|
|
dialogRef |
|
|
.afterClosed() |
|
|
.afterClosed() |
|
|
.pipe(takeUntilDestroyed(this.destroyRef)) |
|
|
.pipe(takeUntilDestroyed(this.destroyRef)) |
|
|
.subscribe(() => { |
|
|
.subscribe(() => { |
|
|
this.router.navigate(['.'], { relativeTo: this.route }); |
|
|
void this.router.navigate(['.'], { relativeTo: this.route }); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public showValuesInPercentage() { |
|
|
|
|
|
return this.hasImpersonationId || this.user?.settings?.isRestrictedView; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
|