From 25166a4fd603673ee6601f3cc192451b9d355dff Mon Sep 17 00:00:00 2001 From: Shaunak Das <51281688+shaun-ak@users.noreply.github.com> Date: Wed, 22 Jan 2025 23:23:38 +0530 Subject: [PATCH] add regional north america cluster risk --- .../src/app/portfolio/portfolio.service.ts | 33 +++++-- .../north-america.ts | 87 +++++++++++++++++++ 2 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 apps/api/src/models/rules/regional-market-cluster-risk/north-america.ts diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 8b295aad4..041edf4b3 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -15,6 +15,7 @@ import { EconomicMarketClusterRiskDevelopedMarkets } from '@ghostfolio/api/model import { EconomicMarketClusterRiskEmergingMarkets } from '@ghostfolio/api/models/rules/economic-market-cluster-risk/emerging-markets'; import { EmergencyFundSetup } from '@ghostfolio/api/models/rules/emergency-fund/emergency-fund-setup'; import { FeeRatioInitialInvestment } from '@ghostfolio/api/models/rules/fees/fee-ratio-initial-investment'; +import { RegionalMarketClusterRiskNorthAmerica } from '@ghostfolio/api/models/rules/regional-market-cluster-risk/north-america'; import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service'; @@ -1167,12 +1168,13 @@ export class PortfolioService { const userId = await this.getUserId(impersonationId, this.request.user.id); const userSettings = this.request.user.Settings.settings as UserSettings; - const { accounts, holdings, markets, summary } = await this.getDetails({ - impersonationId, - userId, - withMarkets: true, - withSummary: true - }); + const { accounts, holdings, markets, marketsAdvanced, summary } = + await this.getDetails({ + impersonationId, + userId, + withMarkets: true, + withSummary: true + }); const marketsTotalInBaseCurrency = getSum( Object.values(markets).map(({ valueInBaseCurrency }) => { @@ -1180,7 +1182,26 @@ export class PortfolioService { }) ).toNumber(); + const marketsAdvancedTotalInBaseCurrency = getSum( + Object.values(marketsAdvanced).map(({ valueInBaseCurrency }) => { + return new Big(valueInBaseCurrency); + }) + ).toNumber(); + const rules: PortfolioReportResponse['rules'] = { + RegionalMarketClusterRiskNorthAmerica: + summary.ordersCount > 0 + ? await this.rulesService.evaluate( + [ + new RegionalMarketClusterRiskNorthAmerica( + this.exchangeRateDataService, + marketsAdvancedTotalInBaseCurrency, + marketsAdvanced['northAmerica'] + ) + ], + userSettings + ) + : undefined, accountClusterRisk: summary.ordersCount > 0 ? await this.rulesService.evaluate( diff --git a/apps/api/src/models/rules/regional-market-cluster-risk/north-america.ts b/apps/api/src/models/rules/regional-market-cluster-risk/north-america.ts new file mode 100644 index 000000000..d3e47d3af --- /dev/null +++ b/apps/api/src/models/rules/regional-market-cluster-risk/north-america.ts @@ -0,0 +1,87 @@ +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'; +import { MarketAdvanced } from '@ghostfolio/common/types'; + +export class RegionalMarketClusterRiskNorthAmerica extends Rule { + private currentValueInBaseCurrency: number; + private regionalMarketNorthAmericaValueInBaseCurrency: number; + + public constructor( + protected exchangeRateDataService: ExchangeRateDataService, + currentValueInBaseCurrency: number, + marketsAdvanced: { + id: MarketAdvanced; + valueInBaseCurrency?: number; + valueInPercentage: number; + } + ) { + super(exchangeRateDataService, { + key: RegionalMarketClusterRiskNorthAmerica.name, + name: 'Regional Markets' + }); + this.currentValueInBaseCurrency = currentValueInBaseCurrency; + this.regionalMarketNorthAmericaValueInBaseCurrency = + marketsAdvanced.valueInBaseCurrency; + } + + public evaluate(ruleSettings: Settings) { + const northAmericaMarketValueRatio = this.currentValueInBaseCurrency + ? this.regionalMarketNorthAmericaValueInBaseCurrency / + this.currentValueInBaseCurrency + : 0; + if (northAmericaMarketValueRatio > ruleSettings.thresholdMax) { + return { + evaluation: `The north america market contribution of your current investment (${(northAmericaMarketValueRatio * 100).toPrecision(3)}%) exceeds ${( + ruleSettings.thresholdMax * 100 + ).toPrecision(3)}%`, + value: false + }; + } else if (northAmericaMarketValueRatio < ruleSettings.thresholdMin) { + return { + evaluation: `The north america market contribution of your current investment (${(northAmericaMarketValueRatio * 100).toPrecision(3)}%) is below ${( + ruleSettings.thresholdMin * 100 + ).toPrecision(3)}%`, + value: false + }; + } + + return { + evaluation: `The north america market contribution of your current investment (${(northAmericaMarketValueRatio * 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.69, + thresholdMin: xRayRules?.[this.getKey()]?.thresholdMin ?? 0.65 + }; + } +} + +interface Settings extends RuleSettings { + baseCurrency: string; + thresholdMin: number; + thresholdMax: number; +}