Feature/optimize calculation of allocations by market (#3249)

* Optimize calculation of allocations by market

* Update changelog
pull/3252/head
Thomas Kaul 11 months ago committed by GitHub
parent b51255a543
commit 719bbe156e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Optimized the calculation of allocations by market
- Improved the url validation in the create and update platform endpoint - Improved the url validation in the create and update platform endpoint
- Improved the language localization for German (`de`) - Improved the language localization for German (`de`)

@ -78,9 +78,11 @@ export class PortfolioController {
@Query('assetClasses') filterByAssetClasses?: string, @Query('assetClasses') filterByAssetClasses?: string,
@Query('range') dateRange: DateRange = 'max', @Query('range') dateRange: DateRange = 'max',
@Query('tags') filterByTags?: string, @Query('tags') filterByTags?: string,
@Query('withLiabilities') withLiabilitiesParam = 'false' @Query('withLiabilities') withLiabilitiesParam = 'false',
@Query('withMarkets') withMarketsParam = 'false'
): Promise<PortfolioDetails & { hasError: boolean }> { ): Promise<PortfolioDetails & { hasError: boolean }> {
const withLiabilities = withLiabilitiesParam === 'true'; const withLiabilities = withLiabilitiesParam === 'true';
const withMarkets = withMarketsParam === 'true';
let hasDetails = true; let hasDetails = true;
let hasError = false; let hasError = false;
@ -106,6 +108,7 @@ export class PortfolioController {
filters, filters,
impersonationId, impersonationId,
withLiabilities, withLiabilities,
withMarkets,
userId: this.request.user.id, userId: this.request.user.id,
withSummary: true withSummary: true
}); });

@ -63,7 +63,8 @@ import {
DataSource, DataSource,
Order, Order,
Platform, Platform,
Prisma Prisma,
SymbolProfile
} from '@prisma/client'; } from '@prisma/client';
import { Big } from 'big.js'; import { Big } from 'big.js';
import { isUUID } from 'class-validator'; import { isUUID } from 'class-validator';
@ -337,6 +338,7 @@ export class PortfolioService {
userId, userId,
withExcludedAccounts = false, withExcludedAccounts = false,
withLiabilities = false, withLiabilities = false,
withMarkets = false,
withSummary = false withSummary = false
}: { }: {
dateRange?: DateRange; dateRange?: DateRange;
@ -345,6 +347,7 @@ export class PortfolioService {
userId: string; userId: string;
withExcludedAccounts?: boolean; withExcludedAccounts?: boolean;
withLiabilities?: boolean; withLiabilities?: boolean;
withMarkets?: boolean;
withSummary?: boolean; withSummary?: boolean;
}): Promise<PortfolioDetails & { hasErrors: boolean }> { }): Promise<PortfolioDetails & { hasErrors: boolean }> {
userId = await this.getUserId(impersonationId, userId); userId = await this.getUserId(impersonationId, userId);
@ -484,77 +487,17 @@ export class PortfolioService {
} }
} }
const symbolProfile = symbolProfileMap[symbol]; const assetProfile = symbolProfileMap[symbol];
const dataProviderResponse = dataProviderResponses[symbol]; const dataProviderResponse = dataProviderResponses[symbol];
const markets: PortfolioPosition['markets'] = { let markets: PortfolioPosition['markets'];
[UNKNOWN_KEY]: 0, let marketsAdvanced: PortfolioPosition['marketsAdvanced'];
developedMarkets: 0,
emergingMarkets: 0,
otherMarkets: 0
};
const marketsAdvanced: PortfolioPosition['marketsAdvanced'] = {
[UNKNOWN_KEY]: 0,
asiaPacific: 0,
emergingMarkets: 0,
europe: 0,
japan: 0,
northAmerica: 0,
otherMarkets: 0
};
if (symbolProfile.countries.length > 0) {
for (const country of symbolProfile.countries) {
if (developedMarkets.includes(country.code)) {
markets.developedMarkets = new Big(markets.developedMarkets)
.plus(country.weight)
.toNumber();
} else if (emergingMarkets.includes(country.code)) {
markets.emergingMarkets = new Big(markets.emergingMarkets)
.plus(country.weight)
.toNumber();
} else {
markets.otherMarkets = new Big(markets.otherMarkets)
.plus(country.weight)
.toNumber();
}
if (country.code === 'JP') {
marketsAdvanced.japan = new Big(marketsAdvanced.japan)
.plus(country.weight)
.toNumber();
} else if (country.code === 'CA' || country.code === 'US') {
marketsAdvanced.northAmerica = new Big(marketsAdvanced.northAmerica)
.plus(country.weight)
.toNumber();
} else if (asiaPacificMarkets.includes(country.code)) {
marketsAdvanced.asiaPacific = new Big(marketsAdvanced.asiaPacific)
.plus(country.weight)
.toNumber();
} else if (emergingMarkets.includes(country.code)) {
marketsAdvanced.emergingMarkets = new Big(
marketsAdvanced.emergingMarkets
)
.plus(country.weight)
.toNumber();
} else if (europeMarkets.includes(country.code)) {
marketsAdvanced.europe = new Big(marketsAdvanced.europe)
.plus(country.weight)
.toNumber();
} else {
marketsAdvanced.otherMarkets = new Big(marketsAdvanced.otherMarkets)
.plus(country.weight)
.toNumber();
}
}
} else {
markets[UNKNOWN_KEY] = new Big(markets[UNKNOWN_KEY])
.plus(valueInBaseCurrency)
.toNumber();
marketsAdvanced[UNKNOWN_KEY] = new Big(marketsAdvanced[UNKNOWN_KEY]) if (withMarkets) {
.plus(valueInBaseCurrency) ({ markets, marketsAdvanced } = this.getMarkets({
.toNumber(); assetProfile,
valueInBaseCurrency
}));
} }
holdings[symbol] = { holdings[symbol] = {
@ -568,10 +511,10 @@ export class PortfolioService {
allocationInPercentage: filteredValueInBaseCurrency.eq(0) allocationInPercentage: filteredValueInBaseCurrency.eq(0)
? 0 ? 0
: valueInBaseCurrency.div(filteredValueInBaseCurrency).toNumber(), : valueInBaseCurrency.div(filteredValueInBaseCurrency).toNumber(),
assetClass: symbolProfile.assetClass, assetClass: assetProfile.assetClass,
assetSubClass: symbolProfile.assetSubClass, assetSubClass: assetProfile.assetSubClass,
countries: symbolProfile.countries, countries: assetProfile.countries,
dataSource: symbolProfile.dataSource, dataSource: assetProfile.dataSource,
dateOfFirstActivity: parseDate(firstBuyDate), dateOfFirstActivity: parseDate(firstBuyDate),
dividend: dividend?.toNumber() ?? 0, dividend: dividend?.toNumber() ?? 0,
grossPerformance: grossPerformance?.toNumber() ?? 0, grossPerformance: grossPerformance?.toNumber() ?? 0,
@ -582,7 +525,7 @@ export class PortfolioService {
grossPerformanceWithCurrencyEffect?.toNumber() ?? 0, grossPerformanceWithCurrencyEffect?.toNumber() ?? 0,
investment: investment.toNumber(), investment: investment.toNumber(),
marketState: dataProviderResponse?.marketState ?? 'delayed', marketState: dataProviderResponse?.marketState ?? 'delayed',
name: symbolProfile.name, name: assetProfile.name,
netPerformance: netPerformance?.toNumber() ?? 0, netPerformance: netPerformance?.toNumber() ?? 0,
netPerformancePercent: netPerformancePercentage?.toNumber() ?? 0, netPerformancePercent: netPerformancePercentage?.toNumber() ?? 0,
netPerformancePercentWithCurrencyEffect: netPerformancePercentWithCurrencyEffect:
@ -590,8 +533,8 @@ export class PortfolioService {
netPerformanceWithCurrencyEffect: netPerformanceWithCurrencyEffect:
netPerformanceWithCurrencyEffect?.toNumber() ?? 0, netPerformanceWithCurrencyEffect?.toNumber() ?? 0,
quantity: quantity.toNumber(), quantity: quantity.toNumber(),
sectors: symbolProfile.sectors, sectors: assetProfile.sectors,
url: symbolProfile.url, url: assetProfile.url,
valueInBaseCurrency: valueInBaseCurrency.toNumber() valueInBaseCurrency: valueInBaseCurrency.toNumber()
}; };
} }
@ -1630,6 +1573,86 @@ export class PortfolioService {
}; };
} }
private getMarkets({
assetProfile,
valueInBaseCurrency
}: {
assetProfile: EnhancedSymbolProfile;
valueInBaseCurrency: Big;
}) {
const markets = {
[UNKNOWN_KEY]: 0,
developedMarkets: 0,
emergingMarkets: 0,
otherMarkets: 0
};
const marketsAdvanced = {
[UNKNOWN_KEY]: 0,
asiaPacific: 0,
emergingMarkets: 0,
europe: 0,
japan: 0,
northAmerica: 0,
otherMarkets: 0
};
if (assetProfile.countries.length > 0) {
for (const country of assetProfile.countries) {
if (developedMarkets.includes(country.code)) {
markets.developedMarkets = new Big(markets.developedMarkets)
.plus(country.weight)
.toNumber();
} else if (emergingMarkets.includes(country.code)) {
markets.emergingMarkets = new Big(markets.emergingMarkets)
.plus(country.weight)
.toNumber();
} else {
markets.otherMarkets = new Big(markets.otherMarkets)
.plus(country.weight)
.toNumber();
}
if (country.code === 'JP') {
marketsAdvanced.japan = new Big(marketsAdvanced.japan)
.plus(country.weight)
.toNumber();
} else if (country.code === 'CA' || country.code === 'US') {
marketsAdvanced.northAmerica = new Big(marketsAdvanced.northAmerica)
.plus(country.weight)
.toNumber();
} else if (asiaPacificMarkets.includes(country.code)) {
marketsAdvanced.asiaPacific = new Big(marketsAdvanced.asiaPacific)
.plus(country.weight)
.toNumber();
} else if (emergingMarkets.includes(country.code)) {
marketsAdvanced.emergingMarkets = new Big(
marketsAdvanced.emergingMarkets
)
.plus(country.weight)
.toNumber();
} else if (europeMarkets.includes(country.code)) {
marketsAdvanced.europe = new Big(marketsAdvanced.europe)
.plus(country.weight)
.toNumber();
} else {
marketsAdvanced.otherMarkets = new Big(marketsAdvanced.otherMarkets)
.plus(country.weight)
.toNumber();
}
}
} else {
markets[UNKNOWN_KEY] = new Big(markets[UNKNOWN_KEY])
.plus(valueInBaseCurrency)
.toNumber();
marketsAdvanced[UNKNOWN_KEY] = new Big(marketsAdvanced[UNKNOWN_KEY])
.plus(valueInBaseCurrency)
.toNumber();
}
return { markets, marketsAdvanced };
}
private getStreaks({ private getStreaks({
investments, investments,
savingsRate savingsRate

@ -205,7 +205,8 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
private fetchPortfolioDetails() { private fetchPortfolioDetails() {
return this.dataService.fetchPortfolioDetails({ return this.dataService.fetchPortfolioDetails({
filters: this.userService.getFilters() filters: this.userService.getFilters(),
withMarkets: true
}); });
} }

@ -411,10 +411,12 @@ export class DataService {
public fetchPortfolioDetails({ public fetchPortfolioDetails({
filters, filters,
withLiabilities = false withLiabilities = false,
withMarkets = false
}: { }: {
filters?: Filter[]; filters?: Filter[];
withLiabilities?: boolean; withLiabilities?: boolean;
withMarkets?: boolean;
} = {}): Observable<PortfolioDetails> { } = {}): Observable<PortfolioDetails> {
let params = this.buildFiltersAsQueryParams({ filters }); let params = this.buildFiltersAsQueryParams({ filters });
@ -422,6 +424,10 @@ export class DataService {
params = params.append('withLiabilities', withLiabilities); params = params.append('withLiabilities', withLiabilities);
} }
if (withMarkets) {
params = params.append('withMarkets', withMarkets);
}
return this.http return this.http
.get<any>('/api/v1/portfolio/details', { .get<any>('/api/v1/portfolio/details', {
params params

Loading…
Cancel
Save