diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index bbc8f437e..9c8feaee8 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -7,6 +7,7 @@ import { UserService } from '@ghostfolio/api/app/user/user.service'; import { getFactor } from '@ghostfolio/api/helper/portfolio.helper'; import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment'; import { AccountClusterRiskSingleAccount } from '@ghostfolio/api/models/rules/account-cluster-risk/single-account'; +import { AllocationClusterRiskEmergingMarkets } from '@ghostfolio/api/models/rules/allocation-cluster-risk/emerging-markets'; import { CurrencyClusterRiskBaseCurrencyCurrentInvestment } from '@ghostfolio/api/models/rules/currency-cluster-risk/base-currency-current-investment'; import { CurrencyClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/currency-cluster-risk/current-investment'; import { EmergencyFundSetup } from '@ghostfolio/api/models/rules/emergency-fund/emergency-fund-setup'; @@ -1160,7 +1161,7 @@ export class PortfolioService { const userId = await this.getUserId(impersonationId, this.request.user.id); const userSettings = this.request.user.Settings.settings; - const { accounts, holdings, summary } = await this.getDetails({ + const { accounts, holdings, summary, markets } = await this.getDetails({ impersonationId, userId, withMarkets: true, @@ -1180,6 +1181,11 @@ export class PortfolioService { new AccountClusterRiskSingleAccount( this.exchangeRateDataService, accounts + ), + new AllocationClusterRiskEmergingMarkets( + this.exchangeRateDataService, + summary.committedFunds, + markets.emergingMarkets.valueInBaseCurrency ) ], userSettings diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index e8a437be6..dce6e787c 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -4,6 +4,7 @@ import { environment } from '@ghostfolio/api/environments/environment'; import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.event'; import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment'; import { AccountClusterRiskSingleAccount } from '@ghostfolio/api/models/rules/account-cluster-risk/single-account'; +import { AllocationClusterRiskEmergingMarkets } from '@ghostfolio/api/models/rules/allocation-cluster-risk/emerging-markets'; import { CurrencyClusterRiskBaseCurrencyCurrentInvestment } from '@ghostfolio/api/models/rules/currency-cluster-risk/base-currency-current-investment'; import { CurrencyClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/currency-cluster-risk/current-investment'; import { EmergencyFundSetup } from '@ghostfolio/api/models/rules/emergency-fund/emergency-fund-setup'; @@ -215,6 +216,10 @@ export class UserService { undefined, {} ).getSettings(user.Settings.settings), + AllocationClusterRiskEmergingMarkets: + new AllocationClusterRiskEmergingMarkets(undefined, 0, 0).getSettings( + user.Settings.settings + ), CurrencyClusterRiskBaseCurrencyCurrentInvestment: new CurrencyClusterRiskBaseCurrencyCurrentInvestment( undefined, diff --git a/apps/api/src/models/rules/allocation-cluster-risk/emerging-markets.ts b/apps/api/src/models/rules/allocation-cluster-risk/emerging-markets.ts new file mode 100644 index 000000000..5ba227ece --- /dev/null +++ b/apps/api/src/models/rules/allocation-cluster-risk/emerging-markets.ts @@ -0,0 +1,84 @@ +import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; +import { Rule } from '@ghostfolio/api/models/rule'; +import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; +import { UserSettings } from '@ghostfolio/common/interfaces'; + +export class AllocationClusterRiskEmergingMarkets extends Rule { + private emergingMarketsValueInBaseCurrency: number; + private totalInvestment: number; + + public constructor( + protected exchangeRateDataService: ExchangeRateDataService, + totalInvestment: number, + emergingMarketsValueInBaseCurrency: number + ) { + super(exchangeRateDataService, { + key: AllocationClusterRiskEmergingMarkets.name, + name: 'Emerging Markets' + }); + + this.emergingMarketsValueInBaseCurrency = + emergingMarketsValueInBaseCurrency; + this.totalInvestment = totalInvestment; + } + + public evaluate(ruleSettings: Settings) { + const emergingMarketsValueRatio = this.totalInvestment + ? this.emergingMarketsValueInBaseCurrency / this.totalInvestment + : 0; + + if (emergingMarketsValueRatio > ruleSettings.thresholdMax) { + return { + evaluation: `The emerging markets contribution exceed ${( + ruleSettings.thresholdMax * 100 + ).toPrecision( + 3 + )}% of your initial investment (${(emergingMarketsValueRatio * 100).toPrecision(3)}%)`, + value: false + }; + } else if (emergingMarketsValueRatio < ruleSettings.thresholdMin) { + return { + evaluation: `The emerging markets contribution does not exceed ${( + ruleSettings.thresholdMin * 100 + ).toPrecision( + 3 + )}% of your initial investment (${(emergingMarketsValueRatio * 100).toPrecision(3)}%)`, + value: false + }; + } + + return { + evaluation: `The emerging markets contribution does not exceed ${ + ruleSettings.thresholdMax * 100 + }% of your initial investment (${(emergingMarketsValueRatio * 100).toPrecision(3)}%)`, + value: true + }; + } + + public getConfiguration() { + return { + threshold: { + max: 1, + min: 0, + step: 0.0025, + unit: '%' + }, + thresholdMax: true + }; + } + + public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { + return { + baseCurrency, + isActive: xRayRules?.[this.getKey()]?.isActive ?? true, + thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.32, + thresholdMin: xRayRules?.[this.getKey()]?.thresholdMin ?? 0.28 + }; + } +} + +interface Settings extends RuleSettings { + baseCurrency: string; + thresholdMin: number; + thresholdMax: number; +} diff --git a/libs/common/src/lib/types/x-ray-rules-settings.type.ts b/libs/common/src/lib/types/x-ray-rules-settings.type.ts index fddd708cc..a8fc714bb 100644 --- a/libs/common/src/lib/types/x-ray-rules-settings.type.ts +++ b/libs/common/src/lib/types/x-ray-rules-settings.type.ts @@ -1,5 +1,6 @@ export type XRayRulesSettings = { AccountClusterRiskCurrentInvestment?: RuleSettings; + AllocationClusterRiskEmergingMarkets?: RuleSettings; AccountClusterRiskSingleAccount?: RuleSettings; CurrencyClusterRiskBaseCurrencyCurrentInvestment?: RuleSettings; CurrencyClusterRiskCurrentInvestment?: RuleSettings;