Browse Source

Feature/add regional market cluster risk for north america (#4240)

* Add regional market cluster risk for north america

* Update changelog
pull/4259/merge
Shaunak Das 18 hours ago
committed by GitHub
parent
commit
8bd869e1b2
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 4
      CHANGELOG.md
  2. 23
      apps/api/src/app/portfolio/portfolio.service.ts
  3. 7
      apps/api/src/app/user/user.service.ts
  4. 82
      apps/api/src/models/rules/regional-market-cluster-risk/north-america.ts
  5. 24
      apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html
  6. 6
      apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts
  7. 1
      libs/common/src/lib/interfaces/x-ray-rules-settings.interface.ts

4
CHANGELOG.md

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Added
- Added a new static portfolio analysis rule: _Regional Market Cluster Risk_ (North America)
### Changed
- Migrated the database seeding to _TypeScript_

23
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,13 +1168,20 @@ 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({
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 }) => {
return new Big(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) };

7
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,6 +269,12 @@ export class UserService {
undefined,
undefined,
undefined
).getSettings(user.Settings.settings),
RegionalMarketClusterRiskNorthAmerica:
new RegionalMarketClusterRiskNorthAmerica(
undefined,
undefined,
undefined
).getSettings(user.Settings.settings)
};

82
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<Settings> {
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;
}

24
apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html

@ -144,6 +144,30 @@
(rulesUpdated)="onRulesUpdated($event)"
/>
</div>
<div
class="mb-4"
[ngClass]="{
'd-none': regionalMarketClusterRiskRules?.length === 0
}"
>
<h4 class="align-items-center d-flex m-0">
<span i18n>Regional Market Cluster Risks</span>
@if (user?.subscription?.type === 'Basic') {
<gf-premium-indicator class="ml-1" />
}
</h4>
<gf-rules
[hasPermissionToUpdateUserSettings]="
!hasImpersonationId &&
hasPermissionToUpdateUserSettings &&
user?.settings?.isExperimentalFeatures
"
[isLoading]="isLoading"
[rules]="regionalMarketClusterRiskRules"
[settings]="user?.settings?.xRayRules"
(rulesUpdated)="onRulesUpdated($event)"
/>
</div>
<div
class="mb-4"
[ngClass]="{

6
apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts

@ -29,6 +29,7 @@ export class XRayPageComponent {
public hasPermissionToUpdateUserSettings: boolean;
public inactiveRules: PortfolioReportRule[];
public isLoading = false;
public regionalMarketClusterRiskRules: PortfolioReportRule[];
public statistics: PortfolioReportResponse['statistics'];
public user: User;
@ -129,6 +130,11 @@ export class XRayPageComponent {
return isActive;
}) ?? null;
this.regionalMarketClusterRiskRules =
rules['regionalMarketClusterRisk']?.filter(({ isActive }) => {
return isActive;
}) ?? null;
this.isLoading = false;
this.changeDetectorRef.markForCheck();

1
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 {

Loading…
Cancel
Save