Feature/optimize calculation of allocations by market (#3249)
* Optimize calculation of allocations by market * Update changelog
This commit is contained in:
parent
b51255a543
commit
719bbe156e
@ -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) {
|
if (withMarkets) {
|
||||||
for (const country of symbolProfile.countries) {
|
({ markets, marketsAdvanced } = this.getMarkets({
|
||||||
if (developedMarkets.includes(country.code)) {
|
assetProfile,
|
||||||
markets.developedMarkets = new Big(markets.developedMarkets)
|
valueInBaseCurrency
|
||||||
.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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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…
x
Reference in New Issue
Block a user