diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c47042c1..9ffd570f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,14 +9,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added a new static portfolio analysis rule: _Regional Market Cluster Risk_ (North America) - Added support for ETF sector data in the _Yahoo Finance_ data enhancer ### Changed +- Extracted the scraper configuration to a sub form in the asset profile details dialog of the admin control - Migrated the database seeding to _TypeScript_ +- Improved the language localization for German (`de`) - Upgraded `@trivago/prettier-plugin-sort-imports` from version `4.3.0` to `5.2.1` - Upgraded `bull` from version `4.16.4` to `4.16.5` - Upgraded `ng-extract-i18n-merge` from version `2.13.1` to `2.14.1` +- Upgraded `prisma` from version `6.2.1` to `6.3.0` + +### Fixed + +- Fixed the dynamic numerical precision for cryptocurrencies in the holding detail dialog ## 2.136.0 - 2025-01-24 diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 8b295aad4..a14f97d26 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,19 @@ 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 marketsAdvancedTotalInBaseCurrency = getSum( + Object.values(marketsAdvanced).map(({ valueInBaseCurrency }) => { + return new Big(valueInBaseCurrency); + }) + ).toNumber(); const marketsTotalInBaseCurrency = getSum( Object.values(markets).map(({ valueInBaseCurrency }) => { @@ -1265,7 +1273,20 @@ export class PortfolioService { ) ], userSettings - ) + ), + regionalMarketClusterRisk: + summary.ordersCount > 0 + ? await this.rulesService.evaluate( + [ + new RegionalMarketClusterRiskNorthAmerica( + this.exchangeRateDataService, + marketsAdvancedTotalInBaseCurrency, + marketsAdvanced.northAmerica.valueInBaseCurrency + ) + ], + userSettings + ) + : undefined }; return { rules, statistics: this.getReportStatistics(rules) }; diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index b5c71179f..415cbc99d 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -13,6 +13,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 { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; @@ -268,7 +269,13 @@ export class UserService { undefined, undefined, undefined - ).getSettings(user.Settings.settings) + ).getSettings(user.Settings.settings), + RegionalMarketClusterRiskNorthAmerica: + new RegionalMarketClusterRiskNorthAmerica( + undefined, + undefined, + undefined + ).getSettings(user.Settings.settings) }; let currentPermissions = getPermissions(user.role); 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..a137f7abf --- /dev/null +++ b/apps/api/src/models/rules/regional-market-cluster-risk/north-america.ts @@ -0,0 +1,82 @@ +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 RegionalMarketClusterRiskNorthAmerica extends Rule { + private currentValueInBaseCurrency: number; + private northAmericaValueInBaseCurrency: number; + + public constructor( + protected exchangeRateDataService: ExchangeRateDataService, + currentValueInBaseCurrency: number, + valueInBaseCurrency + ) { + super(exchangeRateDataService, { + key: RegionalMarketClusterRiskNorthAmerica.name, + name: 'North America' + }); + + this.currentValueInBaseCurrency = currentValueInBaseCurrency; + this.northAmericaValueInBaseCurrency = valueInBaseCurrency; + } + + public evaluate(ruleSettings: Settings) { + const northAmericaMarketValueRatio = this.currentValueInBaseCurrency + ? this.northAmericaValueInBaseCurrency / 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; +} diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.scss b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.scss index 7057aad83..a9e135783 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.scss +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.scss @@ -7,5 +7,21 @@ gf-line-chart { aspect-ratio: 16/9; } + + .mat-expansion-panel { + --mat-expansion-container-background-color: transparent; + + ::ng-deep { + .mat-expansion-panel-body { + padding: 0; + } + } + + .mat-expansion-panel-header { + &:hover { + --mat-expansion-header-hover-state-layer-color: transparent; + } + } + } } } diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts index a144bae87..bb19ad96c 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts @@ -21,7 +21,8 @@ import { Component, Inject, OnDestroy, - OnInit + OnInit, + signal } from '@angular/core'; import { FormBuilder, FormControl, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; @@ -64,7 +65,14 @@ export class AssetProfileDialog implements OnDestroy, OnInit { csvString: '' }), name: ['', Validators.required], - scraperConfiguration: '', + scraperConfiguration: this.formBuilder.group({ + defaultMarketPrice: null, + headers: JSON.stringify({}), + locale: '', + mode: '', + selector: '', + url: '' + }), sectors: '', symbolMapping: '', url: '' @@ -79,6 +87,11 @@ export class AssetProfileDialog implements OnDestroy, OnInit { public historicalDataItems: LineChartItem[]; public isBenchmark = false; public marketDataItems: MarketData[] = []; + public modeValues = [ + { value: 'lazy', viewValue: $localize`Lazy` }, + { value: 'instant', viewValue: $localize`Instant` } + ]; + public scraperConfiguationIsExpanded = signal(false); public sectors: { [name: string]: { name: string; value: number }; }; @@ -181,9 +194,18 @@ export class AssetProfileDialog implements OnDestroy, OnInit { csvString: AssetProfileDialog.HISTORICAL_DATA_TEMPLATE }, name: this.assetProfile.name ?? this.assetProfile.symbol, - scraperConfiguration: JSON.stringify( - this.assetProfile?.scraperConfiguration ?? {} - ), + scraperConfiguration: { + defaultMarketPrice: + this.assetProfile?.scraperConfiguration?.defaultMarketPrice ?? + null, + headers: JSON.stringify( + this.assetProfile?.scraperConfiguration?.headers ?? {} + ), + locale: this.assetProfile?.scraperConfiguration?.locale ?? '', + mode: this.assetProfile?.scraperConfiguration?.mode ?? 'lazy', + selector: this.assetProfile?.scraperConfiguration?.selector ?? '', + url: this.assetProfile?.scraperConfiguration?.url ?? '' + }, sectors: JSON.stringify(this.assetProfile?.sectors ?? []), symbolMapping: JSON.stringify(this.assetProfile?.symbolMapping ?? {}), url: this.assetProfile?.url ?? '' @@ -252,9 +274,31 @@ export class AssetProfileDialog implements OnDestroy, OnInit { } catch {} try { - scraperConfiguration = JSON.parse( - this.assetProfileForm.get('scraperConfiguration').value - ); + scraperConfiguration = { + defaultMarketPrice: + this.assetProfileForm.controls['scraperConfiguration'].controls[ + 'defaultMarketPrice' + ].value, + headers: JSON.parse( + this.assetProfileForm.controls['scraperConfiguration'].controls[ + 'headers' + ].value + ), + locale: + this.assetProfileForm.controls['scraperConfiguration'].controls[ + 'locale' + ].value, + mode: this.assetProfileForm.controls['scraperConfiguration'].controls[ + 'mode' + ].value, + selector: + this.assetProfileForm.controls['scraperConfiguration'].controls[ + 'selector' + ].value, + url: this.assetProfileForm.controls['scraperConfiguration'].controls[ + 'url' + ].value + }; } catch {} try { @@ -306,8 +350,31 @@ export class AssetProfileDialog implements OnDestroy, OnInit { this.adminService .testMarketData({ dataSource: this.data.dataSource, - scraperConfiguration: this.assetProfileForm.get('scraperConfiguration') - .value, + scraperConfiguration: JSON.stringify({ + defaultMarketPrice: + this.assetProfileForm.controls['scraperConfiguration'].controls[ + 'defaultMarketPrice' + ].value, + headers: JSON.parse( + this.assetProfileForm.controls['scraperConfiguration'].controls[ + 'headers' + ].value + ), + locale: + this.assetProfileForm.controls['scraperConfiguration'].controls[ + 'locale' + ].value, + mode: this.assetProfileForm.controls['scraperConfiguration'].controls[ + 'mode' + ].value, + selector: + this.assetProfileForm.controls['scraperConfiguration'].controls[ + 'selector' + ].value, + url: this.assetProfileForm.controls['scraperConfiguration'].controls[ + 'url' + ].value + }), symbol: this.data.symbol }) .pipe( diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html index eeb43e932..b48c4ac0e 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -278,31 +278,106 @@ @if (assetProfile?.dataSource === 'MANUAL') { -
- - Scraper Configuration -
- - -
-
+
+ + + + Scraper Configuration + +
+
+ + Default Market Price + + +
+
+ + Headers + + +
+
+ + Locale + + +
+
+ + Mode + + @for (modeValue of modeValues; track modeValue) { + {{ + modeValue.viewValue + }} + } + + +
+
+ + + Selector* + + + +
+
+ + + Url* + + + +
+
+ +
+
+
+
+ } + @if (assetProfile?.dataSource === 'MANUAL') {
Sectors diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.module.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.module.ts index d5e14ecb5..9b9876dbc 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.module.ts +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.module.ts @@ -13,6 +13,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatDialogModule } from '@angular/material/dialog'; +import { MatExpansionModule } from '@angular/material/expansion'; import { MatInputModule } from '@angular/material/input'; import { MatMenuModule } from '@angular/material/menu'; import { MatSelectModule } from '@angular/material/select'; @@ -34,6 +35,7 @@ import { AssetProfileDialog } from './asset-profile-dialog.component'; MatButtonModule, MatCheckboxModule, MatDialogModule, + MatExpansionModule, MatInputModule, MatMenuModule, MatSelectModule, diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts index 94d5bd915..d13158898 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts @@ -296,7 +296,7 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { if (Number.isInteger(this.quantity)) { this.quantityPrecision = 0; - } else if (this.SymbolProfile?.assetSubClass === 'CRYPTOCURRENCY') { + } else if (SymbolProfile?.assetSubClass === 'CRYPTOCURRENCY') { if (this.quantity < 1) { this.quantityPrecision = 7; } else if (this.quantity < 1000) { diff --git a/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html b/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html index ceba5f52c..6ec5722b7 100644 --- a/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html +++ b/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html @@ -144,6 +144,30 @@ (rulesUpdated)="onRulesUpdated($event)" />
+
+

+ Regional Market Cluster Risks + @if (user?.subscription?.type === 'Basic') { + + } +

+ +
apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 154 + 178 @@ -7051,7 +7051,7 @@ Inactive apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 173 + 197 @@ -7664,6 +7664,14 @@ 16 + + Regional Market Cluster Risks + Regional Market Cluster Risks + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 154 + + diff --git a/apps/client/src/locales/messages.de.xlf b/apps/client/src/locales/messages.de.xlf index 343ae2f53..dbff7f5f5 100644 --- a/apps/client/src/locales/messages.de.xlf +++ b/apps/client/src/locales/messages.de.xlf @@ -3770,7 +3770,7 @@ apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 154 + 178 @@ -7051,7 +7051,7 @@ Inaktiv apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 173 + 197 @@ -7658,12 +7658,20 @@ Early Access - Early Access + Early Access apps/client/src/app/components/admin-settings/admin-settings.component.html 16 + + Regional Market Cluster Risks + Regionale Marktklumpenrisiken + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 154 + + diff --git a/apps/client/src/locales/messages.es.xlf b/apps/client/src/locales/messages.es.xlf index f3ea374e3..b51e320ef 100644 --- a/apps/client/src/locales/messages.es.xlf +++ b/apps/client/src/locales/messages.es.xlf @@ -3771,7 +3771,7 @@ apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 154 + 178 @@ -7052,7 +7052,7 @@ Inactive apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 173 + 197 @@ -7665,6 +7665,14 @@ 16 + + Regional Market Cluster Risks + Regional Market Cluster Risks + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 154 + + diff --git a/apps/client/src/locales/messages.fr.xlf b/apps/client/src/locales/messages.fr.xlf index c2ea4d6f7..19f5b4710 100644 --- a/apps/client/src/locales/messages.fr.xlf +++ b/apps/client/src/locales/messages.fr.xlf @@ -3770,7 +3770,7 @@ apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 154 + 178 @@ -7051,7 +7051,7 @@ Inactif apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 173 + 197 @@ -7664,6 +7664,14 @@ 16 + + Regional Market Cluster Risks + Regional Market Cluster Risks + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 154 + + diff --git a/apps/client/src/locales/messages.it.xlf b/apps/client/src/locales/messages.it.xlf index 70b0ec374..f9252e982 100644 --- a/apps/client/src/locales/messages.it.xlf +++ b/apps/client/src/locales/messages.it.xlf @@ -3771,7 +3771,7 @@ apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 154 + 178 @@ -7052,7 +7052,7 @@ Inattivo apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 173 + 197 @@ -7665,6 +7665,14 @@ 16 + + Regional Market Cluster Risks + Regional Market Cluster Risks + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 154 + + diff --git a/apps/client/src/locales/messages.nl.xlf b/apps/client/src/locales/messages.nl.xlf index 18cf83bb3..8c1aa20d0 100644 --- a/apps/client/src/locales/messages.nl.xlf +++ b/apps/client/src/locales/messages.nl.xlf @@ -3770,7 +3770,7 @@ apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 154 + 178 @@ -7051,7 +7051,7 @@ Inactive apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 173 + 197 @@ -7664,6 +7664,14 @@ 16 + + Regional Market Cluster Risks + Regional Market Cluster Risks + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 154 + + diff --git a/apps/client/src/locales/messages.pl.xlf b/apps/client/src/locales/messages.pl.xlf index 38b8fe231..1c9b7308f 100644 --- a/apps/client/src/locales/messages.pl.xlf +++ b/apps/client/src/locales/messages.pl.xlf @@ -2599,7 +2599,7 @@ apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 154 + 178 @@ -7051,7 +7051,7 @@ Nieaktywny apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 173 + 197 @@ -7664,6 +7664,14 @@ 16 + + Regional Market Cluster Risks + Regional Market Cluster Risks + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 154 + + diff --git a/apps/client/src/locales/messages.pt.xlf b/apps/client/src/locales/messages.pt.xlf index 61ff18a29..c83bc8c9a 100644 --- a/apps/client/src/locales/messages.pt.xlf +++ b/apps/client/src/locales/messages.pt.xlf @@ -3770,7 +3770,7 @@ apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 154 + 178 @@ -7051,7 +7051,7 @@ Inactive apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 173 + 197 @@ -7664,6 +7664,14 @@ 16 + + Regional Market Cluster Risks + Regional Market Cluster Risks + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 154 + + diff --git a/apps/client/src/locales/messages.tr.xlf b/apps/client/src/locales/messages.tr.xlf index a598fd6d1..468154c85 100644 --- a/apps/client/src/locales/messages.tr.xlf +++ b/apps/client/src/locales/messages.tr.xlf @@ -2639,7 +2639,7 @@ apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 154 + 178 @@ -7051,7 +7051,7 @@ Inactive apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 173 + 197 @@ -7664,6 +7664,14 @@ 16 + + Regional Market Cluster Risks + Regional Market Cluster Risks + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 154 + + diff --git a/apps/client/src/locales/messages.uk.xlf b/apps/client/src/locales/messages.uk.xlf index cb2cbb57d..0062e169c 100644 --- a/apps/client/src/locales/messages.uk.xlf +++ b/apps/client/src/locales/messages.uk.xlf @@ -2751,7 +2751,7 @@ apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 154 + 178 @@ -5591,7 +5591,7 @@ Неактивний apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 173 + 197 @@ -7664,6 +7664,14 @@ 16 + + Regional Market Cluster Risks + Regional Market Cluster Risks + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 154 + + diff --git a/apps/client/src/locales/messages.xlf b/apps/client/src/locales/messages.xlf index 0b7afdfa9..df7aa4f9d 100644 --- a/apps/client/src/locales/messages.xlf +++ b/apps/client/src/locales/messages.xlf @@ -2440,7 +2440,7 @@ apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 154 + 178 @@ -6394,7 +6394,7 @@ Inactive apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 173 + 197 @@ -6933,6 +6933,13 @@ 16 + + Regional Market Cluster Risks + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 154 + + diff --git a/apps/client/src/locales/messages.zh.xlf b/apps/client/src/locales/messages.zh.xlf index 9aaa47267..c89487f69 100644 --- a/apps/client/src/locales/messages.zh.xlf +++ b/apps/client/src/locales/messages.zh.xlf @@ -2616,7 +2616,7 @@ apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 154 + 178 @@ -7052,7 +7052,7 @@ Inactive apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 173 + 197 @@ -7665,6 +7665,14 @@ 16 + + Regional Market Cluster Risks + Regional Market Cluster Risks + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 154 + + 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 858ffa31b..61f79d65f 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 @@ -9,6 +9,7 @@ export interface XRayRulesSettings { EconomicMarketClusterRiskEmergingMarkets?: RuleSettings; EmergencyFundSetup?: RuleSettings; FeeRatioInitialInvestment?: RuleSettings; + RegionalMarketClusterRiskNorthAmerica?: RuleSettings; } interface RuleSettings { diff --git a/package-lock.json b/package-lock.json index 087d945c8..643ac7566 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,7 +40,7 @@ "@nestjs/platform-express": "10.4.15", "@nestjs/schedule": "4.1.2", "@nestjs/serve-static": "4.0.2", - "@prisma/client": "6.2.1", + "@prisma/client": "6.3.0", "@simplewebauthn/browser": "9.0.1", "@simplewebauthn/server": "9.0.3", "@stripe/stripe-js": "5.4.0", @@ -151,7 +151,7 @@ "nx": "20.3.2", "prettier": "3.4.2", "prettier-plugin-organize-attributes": "1.0.0", - "prisma": "6.2.1", + "prisma": "6.3.0", "react": "18.2.0", "react-dom": "18.2.0", "replace-in-file": "8.3.0", @@ -8847,71 +8847,75 @@ "license": "MIT" }, "node_modules/@prisma/client": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.2.1.tgz", - "integrity": "sha512-msKY2iRLISN8t5X0Tj7hU0UWet1u0KuxSPHWuf3IRkB4J95mCvGpyQBfQ6ufcmvKNOMQSq90O2iUmJEN2e5fiA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.3.0.tgz", + "integrity": "sha512-BY3Fi28PUSk447Bpv22LhZp4HgNPo7NsEN+EteM1CLDnLjig5863jpW+3c3HHLFmml+nB/eJv1CjSriFZ8z7Cg==", "hasInstallScript": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" }, "peerDependencies": { - "prisma": "*" + "prisma": "*", + "typescript": ">=5.1.0" }, "peerDependenciesMeta": { "prisma": { "optional": true + }, + "typescript": { + "optional": true } } }, "node_modules/@prisma/debug": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.2.1.tgz", - "integrity": "sha512-0KItvt39CmQxWkEw6oW+RQMD6RZ43SJWgEUnzxN8VC9ixMysa7MzZCZf22LCK5DSooiLNf8vM3LHZm/I/Ni7bQ==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.3.0.tgz", + "integrity": "sha512-m1lQv//0Rc5RG8TBpNUuLCxC35Ghi5XfpPmL83Gh04/GICHD2J5H2ndMlaljrUNaQDF9dOxIuFAYP1rE9wkXkg==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.2.1.tgz", - "integrity": "sha512-lTBNLJBCxVT9iP5I7Mn6GlwqAxTpS5qMERrhebkUhtXpGVkBNd/jHnNJBZQW4kGDCKaQg/r2vlJYkzOHnAb7ZQ==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.3.0.tgz", + "integrity": "sha512-RXqYhlZb9sx/xkUfYIZuEPn7sT0WgTxNOuEYQ7AGw3IMpP9QGVEDVsluc/GcNkM8NTJszeqk8AplJzI9lm7Jxw==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.2.1", - "@prisma/engines-version": "6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69", - "@prisma/fetch-engine": "6.2.1", - "@prisma/get-platform": "6.2.1" + "@prisma/debug": "6.3.0", + "@prisma/engines-version": "6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0", + "@prisma/fetch-engine": "6.3.0", + "@prisma/get-platform": "6.3.0" } }, "node_modules/@prisma/engines-version": { - "version": "6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69.tgz", - "integrity": "sha512-7tw1qs/9GWSX6qbZs4He09TOTg1ff3gYsB3ubaVNN0Pp1zLm9NC5C5MZShtkz7TyQjx7blhpknB7HwEhlG+PrQ==", + "version": "6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0.tgz", + "integrity": "sha512-R/ZcMuaWZT2UBmgX3Ko6PAV3f8//ZzsjRIG1eKqp3f2rqEqVtCv+mtzuH2rBPUC9ujJ5kCb9wwpxeyCkLcHVyA==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/fetch-engine": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.2.1.tgz", - "integrity": "sha512-OO7O9d6Mrx2F9i+Gu1LW+DGXXyUFkP7OE5aj9iBfA/2jjDXEJjqa9X0ZmM9NZNo8Uo7ql6zKm6yjDcbAcRrw1A==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.3.0.tgz", + "integrity": "sha512-GBy0iT4f1mH31ePzfcpVSUa7JLRTeq4914FG2vR3LqDwRweSm4ja1o5flGDz+eVIa/BNYfkBvRRxv4D6ve6Eew==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.2.1", - "@prisma/engines-version": "6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69", - "@prisma/get-platform": "6.2.1" + "@prisma/debug": "6.3.0", + "@prisma/engines-version": "6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0", + "@prisma/get-platform": "6.3.0" } }, "node_modules/@prisma/get-platform": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.2.1.tgz", - "integrity": "sha512-zp53yvroPl5m5/gXYLz7tGCNG33bhG+JYCm74ohxOq1pPnrL47VQYFfF3RbTZ7TzGWCrR3EtoiYMywUBw7UK6Q==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.3.0.tgz", + "integrity": "sha512-V8zZ1d0xfyi6FjpNP4AcYuwSpGcdmu35OXWnTPm8IW594PYALzKXHwIa9+o0f+Lo9AecFWrwrwaoYe56UNfTtQ==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.2.1" + "@prisma/debug": "6.3.0" } }, "node_modules/@redis/bloom": { @@ -27181,14 +27185,14 @@ } }, "node_modules/prisma": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.2.1.tgz", - "integrity": "sha512-hhyM0H13pQleQ+br4CkzGizS5I0oInoeTw3JfLw1BRZduBSQxPILlJLwi+46wZzj9Je7ndyQEMGw/n5cN2fknA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.3.0.tgz", + "integrity": "sha512-y+Zh3Qg+xGCWyyrNUUNaFW/OltaV/yXYuTa0WRgYkz5LGyifmAsgpv94I47+qGRocZrMGcbF2A/78/oO2zgifA==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/engines": "6.2.1" + "@prisma/engines": "6.3.0" }, "bin": { "prisma": "build/index.js" @@ -27198,6 +27202,14 @@ }, "optionalDependencies": { "fsevents": "2.3.3" + }, + "peerDependencies": { + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/prismjs": { diff --git a/package.json b/package.json index e808eccd5..f8e56f3f8 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "@nestjs/platform-express": "10.4.15", "@nestjs/schedule": "4.1.2", "@nestjs/serve-static": "4.0.2", - "@prisma/client": "6.2.1", + "@prisma/client": "6.3.0", "@simplewebauthn/browser": "9.0.1", "@simplewebauthn/server": "9.0.3", "@stripe/stripe-js": "5.4.0", @@ -197,7 +197,7 @@ "nx": "20.3.2", "prettier": "3.4.2", "prettier-plugin-organize-attributes": "1.0.0", - "prisma": "6.2.1", + "prisma": "6.3.0", "react": "18.2.0", "react-dom": "18.2.0", "replace-in-file": "8.3.0",