Feature/add developed vs emerging markets calculation (#767)

* Add allocations by market

* Update changelog
This commit is contained in:
Thomas Kaul
2022-03-26 13:11:30 +01:00
committed by GitHub
parent 2b4319454d
commit b4848be914
18 changed files with 238 additions and 10 deletions

View File

@@ -13,8 +13,9 @@ export class PortfolioServiceStrategy {
@Inject(REQUEST) private readonly request: RequestWithUser
) {}
public get() {
public get(newCalculationEngine?: boolean) {
if (
newCalculationEngine ||
this.request.user?.Settings?.settings?.['isNewCalculationEngine'] === true
) {
return this.portfolioServiceNew;

View File

@@ -120,7 +120,7 @@ export class PortfolioController {
const { accounts, holdings, hasErrors } =
await this.portfolioServiceStrategy
.get()
.get(true)
.getDetails(impersonationId, this.request.user.id, range);
if (hasErrors || hasNotDefinedValuesInObject(holdings)) {
@@ -277,7 +277,7 @@ export class PortfolioController {
}
const { holdings } = await this.portfolioServiceStrategy
.get()
.get(true)
.getDetails(access.userId, access.userId);
const portfolioPublicDetails: PortfolioPublicDetails = {
@@ -304,6 +304,7 @@ export class PortfolioController {
allocationCurrent: portfolioPosition.allocationCurrent,
countries: hasDetails ? portfolioPosition.countries : [],
currency: portfolioPosition.currency,
markets: portfolioPosition.markets,
name: portfolioPosition.name,
sectors: hasDetails ? portfolioPosition.sectors : [],
value: portfolioPosition.value / totalValue

View File

@@ -40,6 +40,7 @@ import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.in
import type {
AccountWithValue,
DateRange,
Market,
OrderWithAccount,
RequestWithUser
} from '@ghostfolio/common/types';
@@ -71,6 +72,9 @@ import {
import { PortfolioCalculatorNew } from './portfolio-calculator-new';
import { RulesService } from './rules.service';
const developedMarkets = require('../../assets/countries/developed-markets.json');
const emergingMarkets = require('../../assets/countries/emerging-markets.json');
@Injectable()
export class PortfolioServiceNew {
public constructor(
@@ -380,7 +384,31 @@ export class PortfolioServiceNew {
const value = item.quantity.mul(item.marketPrice);
const symbolProfile = symbolProfileMap[item.symbol];
const dataProviderResponse = dataProviderResponses[item.symbol];
const markets: { [key in Market]: number } = {
developedMarkets: 0,
emergingMarkets: 0,
otherMarkets: 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();
}
}
holdings[item.symbol] = {
markets,
allocationCurrent: value.div(totalValue).toNumber(),
allocationInvestment: item.investment.div(totalInvestment).toNumber(),
assetClass: symbolProfile.assetClass,

View File

@@ -0,0 +1,26 @@
[
"AT",
"AU",
"BE",
"CA",
"CH",
"DE",
"DK",
"ES",
"FI",
"FR",
"GB",
"HK",
"IE",
"IL",
"IT",
"JP",
"LU",
"NL",
"NO",
"NZ",
"PT",
"SE",
"SG",
"US"
]

View File

@@ -0,0 +1,28 @@
[
"AE",
"BR",
"CL",
"CN",
"CO",
"CY",
"CZ",
"EG",
"GR",
"HK",
"HU",
"ID",
"IN",
"KR",
"KW",
"MX",
"MY",
"PE",
"PH",
"PL",
"QA",
"SA",
"TH",
"TR",
"TW",
"ZA"
]

View File

@@ -14,7 +14,7 @@ import {
User
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { ToggleOption } from '@ghostfolio/common/types';
import { Market, ToggleOption } from '@ghostfolio/common/types';
import { Account, AssetClass, DataSource } from '@prisma/client';
import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject, Subscription } from 'rxjs';
@@ -42,6 +42,9 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
public deviceType: string;
public hasImpersonationId: boolean;
public hasPermissionToCreateOrder: boolean;
public markets: {
[key in Market]: { name: string; value: number };
};
public period = 'current';
public periodOptions: ToggleOption[] = [
{ label: 'Initial', value: 'original' },
@@ -160,6 +163,20 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
value: 0
}
};
this.markets = {
developedMarkets: {
name: 'developedMarkets',
value: 0
},
emergingMarkets: {
name: 'emergingMarkets',
value: 0
},
otherMarkets: {
name: 'otherMarkets',
value: 0
}
};
this.positions = {};
this.positionsArray = [];
this.sectors = {
@@ -219,6 +236,16 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
// Prepare analysis data by continents, countries and sectors except for cash
if (position.countries.length > 0) {
this.markets.developedMarkets.value +=
position.markets.developedMarkets *
(aPeriod === 'original' ? position.investment : position.value);
this.markets.emergingMarkets.value +=
position.markets.emergingMarkets *
(aPeriod === 'original' ? position.investment : position.value);
this.markets.otherMarkets.value +=
position.markets.otherMarkets *
(aPeriod === 'original' ? position.investment : position.value);
for (const country of position.countries) {
const { code, continent, name, weight } = country;
@@ -294,6 +321,18 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
};
}
}
const marketsTotal =
this.markets.developedMarkets.value +
this.markets.emergingMarkets.value +
this.markets.otherMarkets.value;
this.markets.developedMarkets.value =
this.markets.developedMarkets.value / marketsTotal;
this.markets.emergingMarkets.value =
this.markets.emergingMarkets.value / marketsTotal;
this.markets.otherMarkets.value =
this.markets.otherMarkets.value / marketsTotal;
}
public onChangePeriod(aValue: string) {

View File

@@ -190,6 +190,32 @@
[countries]="countries"
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
></gf-world-map-chart>
<div class="row">
<div class="col-xs-12 col-md-4 my-2">
<gf-value
label="Developed Markets"
size="large"
[isPercent]="true"
[value]="markets?.developedMarkets?.value"
></gf-value>
</div>
<div class="col-xs-12 col-md-4 my-2">
<gf-value
label="Emerging Markets"
size="large"
[isPercent]="true"
[value]="markets?.emergingMarkets?.value"
></gf-value>
</div>
<div class="col-xs-12 col-md-4 my-2">
<gf-value
label="Other Markets"
size="large"
[isPercent]="true"
[value]="markets?.otherMarkets?.value"
></gf-value>
</div>
</div>
</mat-card-content>
</mat-card>
</div>

View File

@@ -5,6 +5,7 @@ import { GfPositionsTableModule } from '@ghostfolio/client/components/positions-
import { GfToggleModule } from '@ghostfolio/client/components/toggle/toggle.module';
import { GfWorldMapChartModule } from '@ghostfolio/client/components/world-map-chart/world-map-chart.module';
import { GfPortfolioProportionChartModule } from '@ghostfolio/ui/portfolio-proportion-chart/portfolio-proportion-chart.module';
import { GfValueModule } from '@ghostfolio/ui/value';
import { AllocationsPageRoutingModule } from './allocations-page-routing.module';
import { AllocationsPageComponent } from './allocations-page.component';
@@ -19,6 +20,7 @@ import { AllocationsPageComponent } from './allocations-page.component';
GfPositionsTableModule,
GfToggleModule,
GfWorldMapChartModule,
GfValueModule,
MatCardModule
],
providers: [],

View File

@@ -7,6 +7,7 @@ import {
PortfolioPosition,
PortfolioPublicDetails
} from '@ghostfolio/common/interfaces';
import { Market } from '@ghostfolio/common/types';
import { StatusCodes } from 'http-status-codes';
import { DeviceDetectorService } from 'ngx-device-detector';
import { EMPTY, Subject } from 'rxjs';
@@ -26,6 +27,9 @@ export class PublicPageComponent implements OnInit {
[code: string]: { name: string; value: number };
};
public deviceType: string;
public markets: {
[key in Market]: { name: string; value: number };
};
public portfolioPublicDetails: PortfolioPublicDetails;
public positions: {
[symbol: string]: Pick<PortfolioPosition, 'currency' | 'name' | 'value'>;
@@ -96,6 +100,20 @@ export class PublicPageComponent implements OnInit {
value: 0
}
};
this.markets = {
developedMarkets: {
name: 'developedMarkets',
value: 0
},
emergingMarkets: {
name: 'emergingMarkets',
value: 0
},
otherMarkets: {
name: 'otherMarkets',
value: 0
}
};
this.positions = {};
this.sectors = {
[UNKNOWN_KEY]: {
@@ -123,6 +141,13 @@ export class PublicPageComponent implements OnInit {
};
if (position.countries.length > 0) {
this.markets.developedMarkets.value +=
position.markets.developedMarkets * position.value;
this.markets.emergingMarkets.value +=
position.markets.emergingMarkets * position.value;
this.markets.otherMarkets.value +=
position.markets.otherMarkets * position.value;
for (const country of position.countries) {
const { code, continent, name, weight } = country;
@@ -176,6 +201,18 @@ export class PublicPageComponent implements OnInit {
value: position.value
};
}
const marketsTotal =
this.markets.developedMarkets.value +
this.markets.emergingMarkets.value +
this.markets.otherMarkets.value;
this.markets.developedMarkets.value =
this.markets.developedMarkets.value / marketsTotal;
this.markets.emergingMarkets.value =
this.markets.emergingMarkets.value / marketsTotal;
this.markets.otherMarkets.value =
this.markets.otherMarkets.value / marketsTotal;
}
public ngOnDestroy() {

View File

@@ -79,12 +79,38 @@
[countries]="countries"
[isInPercent]="true"
></gf-world-map-chart>
<div class="row">
<div class="col-xs-12 col-md-4 my-2">
<gf-value
label="Developed Markets"
size="large"
[isPercent]="true"
[value]="markets?.developedMarkets?.value"
></gf-value>
</div>
<div class="col-xs-12 col-md-4 my-2">
<gf-value
label="Emerging Markets"
size="large"
[isPercent]="true"
[value]="markets?.emergingMarkets?.value"
></gf-value>
</div>
<div class="col-xs-12 col-md-4 my-2">
<gf-value
label="Other Markets"
size="large"
[isPercent]="true"
[value]="markets?.otherMarkets?.value"
></gf-value>
</div>
</div>
</mat-card-content>
</mat-card>
</div>
</div>
<div class="row my-5">
<div class="col-md-8 offset-md-2">
<div class="col-md-10 offset-md-1">
<h2 class="h4 mb-1 text-center">
Would you like to <strong>refine</strong> your
<strong>personal investment strategy</strong>?

View File

@@ -4,6 +4,7 @@ import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { GfWorldMapChartModule } from '@ghostfolio/client/components/world-map-chart/world-map-chart.module';
import { GfPortfolioProportionChartModule } from '@ghostfolio/ui/portfolio-proportion-chart/portfolio-proportion-chart.module';
import { GfValueModule } from '@ghostfolio/ui/value';
import { PublicPageRoutingModule } from './public-page-routing.module';
import { PublicPageComponent } from './public-page.component';
@@ -14,6 +15,7 @@ import { PublicPageComponent } from './public-page.component';
imports: [
CommonModule,
GfPortfolioProportionChartModule,
GfValueModule,
GfWorldMapChartModule,
MatButtonModule,
MatCardModule,