diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 51822212a..400b0c3a9 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -8,6 +8,7 @@ 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 { AssetClassClusterRiskEquity } from '@ghostfolio/api/models/rules/asset-class-cluster-risk/equity'; +import { AssetClassClusterRiskFixedIncome } from '@ghostfolio/api/models/rules/asset-class-cluster-risk/fixed-income'; 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 { EconomicMarketClusterRiskDevelopedMarkets } from '@ghostfolio/api/models/rules/economic-market-cluster-risk/developed-markets'; @@ -1206,6 +1207,10 @@ export class PortfolioService { new AssetClassClusterRiskEquity( this.exchangeRateDataService, Object.values(holdings) + ), + new AssetClassClusterRiskFixedIncome( + this.exchangeRateDataService, + Object.values(holdings) ) ], userSettings diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index 948bbced1..bac6ed19b 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -6,6 +6,7 @@ import { getRandomString } from '@ghostfolio/api/helper/string.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 { AssetClassClusterRiskEquity } from '@ghostfolio/api/models/rules/asset-class-cluster-risk/equity'; +import { AssetClassClusterRiskFixedIncome } from '@ghostfolio/api/models/rules/asset-class-cluster-risk/fixed-income'; 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 { EconomicMarketClusterRiskDevelopedMarkets } from '@ghostfolio/api/models/rules/economic-market-cluster-risk/developed-markets'; @@ -231,6 +232,10 @@ export class UserService { undefined, undefined ).getSettings(user.Settings.settings), + AssetClassClusterRiskFixedIncome: new AssetClassClusterRiskFixedIncome( + undefined, + undefined + ).getSettings(user.Settings.settings), CurrencyClusterRiskBaseCurrencyCurrentInvestment: new CurrencyClusterRiskBaseCurrencyCurrentInvestment( undefined, diff --git a/apps/api/src/models/rules/asset-class-cluster-risk/fixed-income.ts b/apps/api/src/models/rules/asset-class-cluster-risk/fixed-income.ts new file mode 100644 index 000000000..eb744a143 --- /dev/null +++ b/apps/api/src/models/rules/asset-class-cluster-risk/fixed-income.ts @@ -0,0 +1,95 @@ +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 { PortfolioPosition, UserSettings } from '@ghostfolio/common/interfaces'; + +export class AssetClassClusterRiskFixedIncome extends Rule { + private holdings: PortfolioPosition[]; + + public constructor( + protected exchangeRateDataService: ExchangeRateDataService, + holdings: PortfolioPosition[] + ) { + super(exchangeRateDataService, { + key: AssetClassClusterRiskFixedIncome.name, + name: 'Fixed Income' + }); + + this.holdings = holdings; + } + + public evaluate(ruleSettings: Settings) { + const holdingsGroupedByAssetClass = this.groupCurrentHoldingsByAttribute( + this.holdings, + 'assetClass', + ruleSettings.baseCurrency + ); + let totalValue = 0; + + const fixedIncomeValueInBaseCurrency = + holdingsGroupedByAssetClass.find(({ groupKey }) => { + return groupKey === 'FIXED_INCOME'; + })?.value ?? 0; + + for (const { value } of holdingsGroupedByAssetClass) { + totalValue += value; + } + + const fixedIncomeValueRatio = totalValue + ? fixedIncomeValueInBaseCurrency / totalValue + : 0; + + if (fixedIncomeValueRatio > ruleSettings.thresholdMax) { + return { + evaluation: `The fixed income contribution of your current investment (${(fixedIncomeValueRatio * 100).toPrecision(3)}%) exceeds ${( + ruleSettings.thresholdMax * 100 + ).toPrecision(3)}%`, + value: false + }; + } else if (fixedIncomeValueRatio < ruleSettings.thresholdMin) { + return { + evaluation: `The fixed income contribution of your current investment (${(fixedIncomeValueRatio * 100).toPrecision(3)}%) is below ${( + ruleSettings.thresholdMin * 100 + ).toPrecision(3)}%`, + value: false + }; + } + + return { + evaluation: `The fixed income contribution of your current investment (${(fixedIncomeValueRatio * 100).toPrecision(3)}%) is within the range of ${( + ruleSettings.thresholdMin * 100 + ).toPrecision( + 3 + )}% and ${(ruleSettings.thresholdMax * 100).toPrecision(3)}%`, + value: true + }; + } + + public getConfiguration() { + return { + threshold: { + max: 1, + min: 0, + step: 0.01, + unit: '%' + }, + thresholdMax: true, + thresholdMin: true + }; + } + + public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { + return { + baseCurrency, + isActive: xRayRules?.[this.getKey()]?.isActive ?? true, + thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.22, + thresholdMin: xRayRules?.[this.getKey()]?.thresholdMin ?? 0.18 + }; + } +} + +interface Settings extends RuleSettings { + baseCurrency: string; + thresholdMin: number; + thresholdMax: number; +} diff --git a/libs/common/src/lib/interfaces/x-ray-rules-settings.interface.ts b/libs/common/src/lib/interfaces/x-ray-rules-settings.interface.ts index 13c67c3e0..858ffa31b 100644 --- a/libs/common/src/lib/interfaces/x-ray-rules-settings.interface.ts +++ b/libs/common/src/lib/interfaces/x-ray-rules-settings.interface.ts @@ -2,6 +2,7 @@ export interface XRayRulesSettings { AccountClusterRiskCurrentInvestment?: RuleSettings; AccountClusterRiskSingleAccount?: RuleSettings; AssetClassClusterRiskEquity?: RuleSettings; + AssetClassClusterRiskFixedIncome?: RuleSettings; CurrencyClusterRiskBaseCurrencyCurrentInvestment?: RuleSettings; CurrencyClusterRiskCurrentInvestment?: RuleSettings; EconomicMarketClusterRiskDevelopedMarkets?: RuleSettings;