Browse Source

Task/centralize X-ray rule settings (#7887)

Centralize X-ray rule settings
pull/7908/head
Thomas Kaul 1 week ago
committed by GitHub
parent
commit
2f30b11cff
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 69
      apps/api/src/app/portfolio/portfolio.service.ts
  2. 8
      apps/api/src/app/portfolio/rules.service.ts
  3. 143
      apps/api/src/app/user/user.service.ts
  4. 5
      apps/api/src/models/interfaces/rule-settings-configuration.interface.ts
  5. 4
      apps/api/src/models/interfaces/rule.interface.ts
  6. 15
      apps/api/src/models/rule.ts
  7. 22
      apps/api/src/models/rules/account-cluster-risk/current-investment.ts
  8. 19
      apps/api/src/models/rules/account-cluster-risk/single-account.ts
  9. 23
      apps/api/src/models/rules/asset-class-cluster-risk/equity.ts
  10. 23
      apps/api/src/models/rules/asset-class-cluster-risk/fixed-income.ts
  11. 23
      apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts
  12. 24
      apps/api/src/models/rules/currency-cluster-risk/current-investment.ts
  13. 19
      apps/api/src/models/rules/economic-market-cluster-risk/developed-markets.ts
  14. 19
      apps/api/src/models/rules/economic-market-cluster-risk/emerging-markets.ts
  15. 17
      apps/api/src/models/rules/emergency-fund/emergency-fund-coverage.ts
  16. 17
      apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts
  17. 18
      apps/api/src/models/rules/fees/fee-ratio-total-investment-volume.ts
  18. 18
      apps/api/src/models/rules/liquidity/buying-power.ts
  19. 18
      apps/api/src/models/rules/regional-market-cluster-risk/asia-pacific.ts
  20. 18
      apps/api/src/models/rules/regional-market-cluster-risk/emerging-markets.ts
  21. 18
      apps/api/src/models/rules/regional-market-cluster-risk/europe.ts
  22. 18
      apps/api/src/models/rules/regional-market-cluster-risk/japan.ts
  23. 18
      apps/api/src/models/rules/regional-market-cluster-risk/north-america.ts
  24. 129
      apps/api/src/models/rules/rule-settings.spec.ts
  25. 98
      apps/api/src/models/rules/rule-settings.ts
  26. 3
      apps/api/src/models/types/x-ray-rule-key.type.ts

69
apps/api/src/app/portfolio/portfolio.service.ts

@ -33,6 +33,7 @@ import {
import {
DEFAULT_CURRENCY,
DEFAULT_DATE_RANGE,
DEFAULT_LANGUAGE_CODE,
TAG_ID_DRAFT,
TAG_ID_EMERGENCY_FUND,
TAG_ID_EXCLUDE_FROM_ANALYSIS,
@ -1288,6 +1289,8 @@ export class PortfolioService {
userSettings: this.request.user.settings.settings as UserSettings
});
const languageCode = userSettings.language ?? DEFAULT_LANGUAGE_CODE;
const { accounts, holdings, markets, marketsAdvanced, summary } =
await this.getDetails({
user,
@ -1330,8 +1333,8 @@ export class PortfolioService {
{
key: 'liquidity',
name: this.i18nService.getTranslation({
id: 'rule.liquidity.category',
languageCode: userSettings.language
languageCode,
id: 'rule.liquidity.category'
}),
rules: await this.rulesService.evaluate(
[
@ -1339,7 +1342,7 @@ export class PortfolioService {
this.exchangeRateDataService,
this.i18nService,
summary.cash,
userSettings.language
languageCode
)
],
userSettings
@ -1348,15 +1351,15 @@ export class PortfolioService {
{
key: 'emergencyFund',
name: this.i18nService.getTranslation({
id: 'rule.emergencyFund.category',
languageCode: userSettings.language
languageCode,
id: 'rule.emergencyFund.category'
}),
rules: await this.rulesService.evaluate(
[
new EmergencyFundSetup(
this.exchangeRateDataService,
this.i18nService,
userSettings.language,
languageCode,
totalEmergencyFundInBaseCurrency
),
// The coverage is only meaningful once an emergency fund has been
@ -1366,7 +1369,7 @@ export class PortfolioService {
new EmergencyFundCoverage(
this.exchangeRateDataService,
this.i18nService,
userSettings.language,
languageCode,
emergencyFundInBaseCurrency,
emergencyFundHoldingsValueInBaseCurrency,
cashBalanceInBaseCurrency
@ -1380,8 +1383,8 @@ export class PortfolioService {
{
key: 'currencyClusterRisk',
name: this.i18nService.getTranslation({
id: 'rule.currencyClusterRisk.category',
languageCode: userSettings.language
languageCode,
id: 'rule.currencyClusterRisk.category'
}),
rules: hasOpenHoldings
? await this.rulesService.evaluate(
@ -1390,13 +1393,13 @@ export class PortfolioService {
this.exchangeRateDataService,
this.i18nService,
holdings,
userSettings.language
languageCode
),
new CurrencyClusterRiskCurrentInvestment(
this.exchangeRateDataService,
this.i18nService,
holdings,
userSettings.language
languageCode
)
],
userSettings
@ -1406,8 +1409,8 @@ export class PortfolioService {
{
key: 'assetClassClusterRisk',
name: this.i18nService.getTranslation({
id: 'rule.assetClassClusterRisk.category',
languageCode: userSettings.language
languageCode,
id: 'rule.assetClassClusterRisk.category'
}),
rules: hasOpenHoldings
? await this.rulesService.evaluate(
@ -1415,13 +1418,13 @@ export class PortfolioService {
new AssetClassClusterRiskEquity(
this.exchangeRateDataService,
this.i18nService,
userSettings.language,
languageCode,
holdings
),
new AssetClassClusterRiskFixedIncome(
this.exchangeRateDataService,
this.i18nService,
userSettings.language,
languageCode,
holdings
)
],
@ -1432,8 +1435,8 @@ export class PortfolioService {
{
key: 'accountClusterRisk',
name: this.i18nService.getTranslation({
id: 'rule.accountClusterRisk.category',
languageCode: userSettings.language
languageCode,
id: 'rule.accountClusterRisk.category'
}),
rules:
summary.activityCount > 0
@ -1442,13 +1445,13 @@ export class PortfolioService {
new AccountClusterRiskCurrentInvestment(
this.exchangeRateDataService,
this.i18nService,
userSettings.language,
languageCode,
accounts
),
new AccountClusterRiskSingleAccount(
this.exchangeRateDataService,
this.i18nService,
userSettings.language,
languageCode,
accounts
)
],
@ -1459,8 +1462,8 @@ export class PortfolioService {
{
key: 'economicMarketClusterRisk',
name: this.i18nService.getTranslation({
id: 'rule.economicMarketClusterRisk.category',
languageCode: userSettings.language
languageCode,
id: 'rule.economicMarketClusterRisk.category'
}),
rules: hasOpenHoldings
? await this.rulesService.evaluate(
@ -1470,14 +1473,14 @@ export class PortfolioService {
this.i18nService,
marketsTotalInBaseCurrency,
markets.developedMarkets.valueInBaseCurrency,
userSettings.language
languageCode
),
new EconomicMarketClusterRiskEmergingMarkets(
this.exchangeRateDataService,
this.i18nService,
marketsTotalInBaseCurrency,
markets.emergingMarkets.valueInBaseCurrency,
userSettings.language
languageCode
)
],
userSettings
@ -1487,8 +1490,8 @@ export class PortfolioService {
{
key: 'regionalMarketClusterRisk',
name: this.i18nService.getTranslation({
id: 'rule.regionalMarketClusterRisk.category',
languageCode: userSettings.language
languageCode,
id: 'rule.regionalMarketClusterRisk.category'
}),
rules: hasOpenHoldings
? await this.rulesService.evaluate(
@ -1496,35 +1499,35 @@ export class PortfolioService {
new RegionalMarketClusterRiskAsiaPacific(
this.exchangeRateDataService,
this.i18nService,
userSettings.language,
languageCode,
marketsAdvancedTotalInBaseCurrency,
marketsAdvanced.asiaPacific.valueInBaseCurrency
),
new RegionalMarketClusterRiskEmergingMarkets(
this.exchangeRateDataService,
this.i18nService,
userSettings.language,
languageCode,
marketsAdvancedTotalInBaseCurrency,
marketsAdvanced.emergingMarkets.valueInBaseCurrency
),
new RegionalMarketClusterRiskEurope(
this.exchangeRateDataService,
this.i18nService,
userSettings.language,
languageCode,
marketsAdvancedTotalInBaseCurrency,
marketsAdvanced.europe.valueInBaseCurrency
),
new RegionalMarketClusterRiskJapan(
this.exchangeRateDataService,
this.i18nService,
userSettings.language,
languageCode,
marketsAdvancedTotalInBaseCurrency,
marketsAdvanced.japan.valueInBaseCurrency
),
new RegionalMarketClusterRiskNorthAmerica(
this.exchangeRateDataService,
this.i18nService,
userSettings.language,
languageCode,
marketsAdvancedTotalInBaseCurrency,
marketsAdvanced.northAmerica.valueInBaseCurrency
)
@ -1536,15 +1539,15 @@ export class PortfolioService {
{
key: 'fees',
name: this.i18nService.getTranslation({
id: 'rule.fees.category',
languageCode: userSettings.language
languageCode,
id: 'rule.fees.category'
}),
rules: await this.rulesService.evaluate(
[
new FeeRatioTotalInvestmentVolume(
this.exchangeRateDataService,
this.i18nService,
userSettings.language,
languageCode,
summary.totalBuy + summary.totalSell,
summary.fees
)

8
apps/api/src/app/portfolio/rules.service.ts

@ -1,4 +1,5 @@
import { Rule } from '@ghostfolio/api/models/rule';
import { getRuleSettings } from '@ghostfolio/api/models/rules/rule-settings';
import {
PortfolioReportRule,
RuleSettings,
@ -14,9 +15,12 @@ export class RulesService {
aUserSettings: UserSettings
): Promise<PortfolioReportRule[]> {
return aRules.map((rule) => {
const settings = rule.getSettings(aUserSettings);
const settings = getRuleSettings<T>({
key: rule.getKey(),
userSettings: aUserSettings
});
if (settings?.isActive) {
if (settings.isActive) {
const { evaluation, value } = rule.evaluate(settings);
return {

143
apps/api/src/app/user/user.service.ts

@ -4,23 +4,7 @@ import { environment } from '@ghostfolio/api/environments/environment';
import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.event';
import { getSupportedLanguageCode } from '@ghostfolio/api/helper/language.helper';
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';
import { EconomicMarketClusterRiskEmergingMarkets } from '@ghostfolio/api/models/rules/economic-market-cluster-risk/emerging-markets';
import { EmergencyFundCoverage } from '@ghostfolio/api/models/rules/emergency-fund/emergency-fund-coverage';
import { EmergencyFundSetup } from '@ghostfolio/api/models/rules/emergency-fund/emergency-fund-setup';
import { FeeRatioTotalInvestmentVolume } from '@ghostfolio/api/models/rules/fees/fee-ratio-total-investment-volume';
import { BuyingPower } from '@ghostfolio/api/models/rules/liquidity/buying-power';
import { RegionalMarketClusterRiskAsiaPacific } from '@ghostfolio/api/models/rules/regional-market-cluster-risk/asia-pacific';
import { RegionalMarketClusterRiskEmergingMarkets } from '@ghostfolio/api/models/rules/regional-market-cluster-risk/emerging-markets';
import { RegionalMarketClusterRiskEurope } from '@ghostfolio/api/models/rules/regional-market-cluster-risk/europe';
import { RegionalMarketClusterRiskJapan } from '@ghostfolio/api/models/rules/regional-market-cluster-risk/japan';
import { RegionalMarketClusterRiskNorthAmerica } from '@ghostfolio/api/models/rules/regional-market-cluster-risk/north-america';
import { getXRayRulesSettings } from '@ghostfolio/api/models/rules/rule-settings';
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';
@ -405,128 +389,9 @@ export class UserService {
(user.settings.settings as UserSettings).viewMode = 'DEFAULT';
}
(user.settings.settings as UserSettings).xRayRules = {
AccountClusterRiskCurrentInvestment:
new AccountClusterRiskCurrentInvestment(
undefined,
undefined,
undefined,
{}
).getSettings(user.settings.settings),
AccountClusterRiskSingleAccount: new AccountClusterRiskSingleAccount(
undefined,
undefined,
undefined,
{}
).getSettings(user.settings.settings),
AssetClassClusterRiskEquity: new AssetClassClusterRiskEquity(
undefined,
undefined,
undefined,
undefined
).getSettings(user.settings.settings),
AssetClassClusterRiskFixedIncome: new AssetClassClusterRiskFixedIncome(
undefined,
undefined,
undefined,
undefined
).getSettings(user.settings.settings),
BuyingPower: new BuyingPower(
undefined,
undefined,
undefined,
undefined
).getSettings(user.settings.settings),
CurrencyClusterRiskBaseCurrencyCurrentInvestment:
new CurrencyClusterRiskBaseCurrencyCurrentInvestment(
undefined,
undefined,
undefined,
undefined
).getSettings(user.settings.settings),
CurrencyClusterRiskCurrentInvestment:
new CurrencyClusterRiskCurrentInvestment(
undefined,
undefined,
undefined,
undefined
).getSettings(user.settings.settings),
EconomicMarketClusterRiskDevelopedMarkets:
new EconomicMarketClusterRiskDevelopedMarkets(
undefined,
undefined,
undefined,
undefined,
undefined
).getSettings(user.settings.settings),
EconomicMarketClusterRiskEmergingMarkets:
new EconomicMarketClusterRiskEmergingMarkets(
undefined,
undefined,
undefined,
undefined,
undefined
).getSettings(user.settings.settings),
EmergencyFundCoverage: new EmergencyFundCoverage(
undefined,
undefined,
undefined,
undefined,
undefined,
undefined
).getSettings(user.settings.settings),
EmergencyFundSetup: new EmergencyFundSetup(
undefined,
undefined,
undefined,
undefined
).getSettings(user.settings.settings),
FeeRatioTotalInvestmentVolume: new FeeRatioTotalInvestmentVolume(
undefined,
undefined,
undefined,
undefined,
undefined
).getSettings(user.settings.settings),
RegionalMarketClusterRiskAsiaPacific:
new RegionalMarketClusterRiskAsiaPacific(
undefined,
undefined,
undefined,
undefined,
undefined
).getSettings(user.settings.settings),
RegionalMarketClusterRiskEmergingMarkets:
new RegionalMarketClusterRiskEmergingMarkets(
undefined,
undefined,
undefined,
undefined,
undefined
).getSettings(user.settings.settings),
RegionalMarketClusterRiskEurope: new RegionalMarketClusterRiskEurope(
undefined,
undefined,
undefined,
undefined,
undefined
).getSettings(user.settings.settings),
RegionalMarketClusterRiskJapan: new RegionalMarketClusterRiskJapan(
undefined,
undefined,
undefined,
undefined,
undefined
).getSettings(user.settings.settings),
RegionalMarketClusterRiskNorthAmerica:
new RegionalMarketClusterRiskNorthAmerica(
undefined,
undefined,
undefined,
undefined,
undefined
).getSettings(user.settings.settings)
};
(user.settings.settings as UserSettings).xRayRules = getXRayRulesSettings(
user.settings.settings
);
let currentPermissions = getPermissions(user.role);

5
apps/api/src/models/interfaces/rule-settings-configuration.interface.ts

@ -0,0 +1,5 @@
export interface RuleSettingsConfiguration {
thresholdMax?: number;
thresholdMin?: number;
withBaseCurrency?: boolean;
}

4
apps/api/src/models/interfaces/rule.interface.ts

@ -1,9 +1,7 @@
import { RuleSettings, UserSettings } from '@ghostfolio/common/interfaces';
import { RuleSettings } from '@ghostfolio/common/interfaces';
import { EvaluationResult } from './evaluation-result.interface';
export interface RuleInterface<T extends RuleSettings> {
evaluate(aRuleSettings: T): EvaluationResult;
getSettings(aUserSettings: UserSettings): T;
}

15
apps/api/src/models/rule.ts

@ -1,10 +1,9 @@
import { XRayRuleKey } from '@ghostfolio/api/models/types/x-ray-rule-key.type';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config';
import {
PortfolioPosition,
PortfolioReportRule,
RuleSettings,
UserSettings
RuleSettings
} from '@ghostfolio/common/interfaces';
import { Big } from 'big.js';
@ -14,17 +13,17 @@ import { EvaluationResult } from './interfaces/evaluation-result.interface';
import { RuleInterface } from './interfaces/rule.interface';
export abstract class Rule<T extends RuleSettings> implements RuleInterface<T> {
private key: string;
private key: XRayRuleKey;
private languageCode: string;
public constructor(
protected exchangeRateDataService: ExchangeRateDataService,
{
key,
languageCode = DEFAULT_LANGUAGE_CODE
languageCode
}: {
key: string;
languageCode?: string; // TODO: Make mandatory
key: XRayRuleKey;
languageCode: string;
}
) {
this.key = key;
@ -75,6 +74,4 @@ export abstract class Rule<T extends RuleSettings> implements RuleInterface<T> {
>;
public abstract getName(): string;
public abstract getSettings(aUserSettings: UserSettings): T;
}

22
apps/api/src/models/rules/account-cluster-risk/current-investment.ts

@ -1,12 +1,7 @@
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { DEFAULT_CURRENCY, DEFAULT_LOCALE } from '@ghostfolio/common/config';
import {
PortfolioDetails,
RuleSettings,
UserSettings
} from '@ghostfolio/common/interfaces';
import { PortfolioDetails, RuleSettings } from '@ghostfolio/common/interfaces';
import { Account } from '@prisma/client';
@ -21,7 +16,7 @@ export class AccountClusterRiskCurrentInvestment extends Rule<Settings> {
) {
super(exchangeRateDataService, {
languageCode,
key: AccountClusterRiskCurrentInvestment.name
key: 'AccountClusterRiskCurrentInvestment'
});
this.accounts = accounts;
@ -118,19 +113,6 @@ export class AccountClusterRiskCurrentInvestment extends Rule<Settings> {
languageCode: this.getLanguageCode()
});
}
public getSettings({
baseCurrency = DEFAULT_CURRENCY,
locale = DEFAULT_LOCALE,
xRayRules
}: UserSettings): Settings {
return {
baseCurrency,
locale,
isActive: xRayRules?.[this.getKey()]?.isActive ?? true,
thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.5
};
}
}
interface Settings extends RuleSettings {

19
apps/api/src/models/rules/account-cluster-risk/single-account.ts

@ -1,12 +1,7 @@
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { DEFAULT_LOCALE } from '@ghostfolio/common/config';
import {
PortfolioDetails,
RuleSettings,
UserSettings
} from '@ghostfolio/common/interfaces';
import { PortfolioDetails, RuleSettings } from '@ghostfolio/common/interfaces';
export class AccountClusterRiskSingleAccount extends Rule<RuleSettings> {
private accounts: PortfolioDetails['accounts'];
@ -19,7 +14,7 @@ export class AccountClusterRiskSingleAccount extends Rule<RuleSettings> {
) {
super(exchangeRateDataService, {
languageCode,
key: AccountClusterRiskSingleAccount.name
key: 'AccountClusterRiskSingleAccount'
});
this.accounts = accounts;
@ -68,14 +63,4 @@ export class AccountClusterRiskSingleAccount extends Rule<RuleSettings> {
languageCode: this.getLanguageCode()
});
}
public getSettings({
locale = DEFAULT_LOCALE,
xRayRules
}: UserSettings): RuleSettings {
return {
locale,
isActive: xRayRules?.[this.getKey()]?.isActive ?? true
};
}
}

23
apps/api/src/models/rules/asset-class-cluster-risk/equity.ts

@ -1,12 +1,7 @@
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { DEFAULT_CURRENCY, DEFAULT_LOCALE } from '@ghostfolio/common/config';
import {
PortfolioPosition,
RuleSettings,
UserSettings
} from '@ghostfolio/common/interfaces';
import { PortfolioPosition, RuleSettings } from '@ghostfolio/common/interfaces';
export class AssetClassClusterRiskEquity extends Rule<Settings> {
private holdings: PortfolioPosition[];
@ -19,7 +14,7 @@ export class AssetClassClusterRiskEquity extends Rule<Settings> {
) {
super(exchangeRateDataService, {
languageCode,
key: AssetClassClusterRiskEquity.name
key: 'AssetClassClusterRiskEquity'
});
this.holdings = holdings;
@ -106,20 +101,6 @@ export class AssetClassClusterRiskEquity extends Rule<Settings> {
languageCode: this.getLanguageCode()
});
}
public getSettings({
baseCurrency = DEFAULT_CURRENCY,
locale = DEFAULT_LOCALE,
xRayRules
}: UserSettings): Settings {
return {
baseCurrency,
locale,
isActive: xRayRules?.[this.getKey()]?.isActive ?? true,
thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.82,
thresholdMin: xRayRules?.[this.getKey()]?.thresholdMin ?? 0.78
};
}
}
interface Settings extends RuleSettings {

23
apps/api/src/models/rules/asset-class-cluster-risk/fixed-income.ts

@ -1,12 +1,7 @@
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { DEFAULT_CURRENCY, DEFAULT_LOCALE } from '@ghostfolio/common/config';
import {
PortfolioPosition,
RuleSettings,
UserSettings
} from '@ghostfolio/common/interfaces';
import { PortfolioPosition, RuleSettings } from '@ghostfolio/common/interfaces';
export class AssetClassClusterRiskFixedIncome extends Rule<Settings> {
private holdings: PortfolioPosition[];
@ -19,7 +14,7 @@ export class AssetClassClusterRiskFixedIncome extends Rule<Settings> {
) {
super(exchangeRateDataService, {
languageCode,
key: AssetClassClusterRiskFixedIncome.name
key: 'AssetClassClusterRiskFixedIncome'
});
this.holdings = holdings;
@ -106,20 +101,6 @@ export class AssetClassClusterRiskFixedIncome extends Rule<Settings> {
languageCode: this.getLanguageCode()
});
}
public getSettings({
baseCurrency = DEFAULT_CURRENCY,
locale = DEFAULT_LOCALE,
xRayRules
}: UserSettings): Settings {
return {
baseCurrency,
locale,
isActive: xRayRules?.[this.getKey()]?.isActive ?? true,
thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.22,
thresholdMin: xRayRules?.[this.getKey()]?.thresholdMin ?? 0.18
};
}
}
interface Settings extends RuleSettings {

23
apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts

@ -1,12 +1,7 @@
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { DEFAULT_CURRENCY, DEFAULT_LOCALE } from '@ghostfolio/common/config';
import {
PortfolioPosition,
RuleSettings,
UserSettings
} from '@ghostfolio/common/interfaces';
import { PortfolioPosition, RuleSettings } from '@ghostfolio/common/interfaces';
export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule<Settings> {
private holdings: PortfolioPosition[];
@ -18,8 +13,8 @@ export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule<Setti
languageCode: string
) {
super(exchangeRateDataService, {
key: CurrencyClusterRiskBaseCurrencyCurrentInvestment.name,
languageCode
languageCode,
key: 'CurrencyClusterRiskBaseCurrencyCurrentInvestment'
});
this.holdings = holdings;
@ -93,18 +88,6 @@ export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule<Setti
languageCode: this.getLanguageCode()
});
}
public getSettings({
baseCurrency = DEFAULT_CURRENCY,
locale = DEFAULT_LOCALE,
xRayRules
}: UserSettings): Settings {
return {
baseCurrency,
locale,
isActive: xRayRules?.[this.getKey()]?.isActive ?? true
};
}
}
interface Settings extends RuleSettings {

24
apps/api/src/models/rules/currency-cluster-risk/current-investment.ts

@ -1,12 +1,7 @@
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { DEFAULT_CURRENCY, DEFAULT_LOCALE } from '@ghostfolio/common/config';
import {
PortfolioPosition,
RuleSettings,
UserSettings
} from '@ghostfolio/common/interfaces';
import { PortfolioPosition, RuleSettings } from '@ghostfolio/common/interfaces';
export class CurrencyClusterRiskCurrentInvestment extends Rule<Settings> {
private holdings: PortfolioPosition[];
@ -18,8 +13,8 @@ export class CurrencyClusterRiskCurrentInvestment extends Rule<Settings> {
languageCode: string
) {
super(exchangeRateDataService, {
key: CurrencyClusterRiskCurrentInvestment.name,
languageCode
languageCode,
key: 'CurrencyClusterRiskCurrentInvestment'
});
this.holdings = holdings;
@ -94,19 +89,6 @@ export class CurrencyClusterRiskCurrentInvestment extends Rule<Settings> {
languageCode: this.getLanguageCode()
});
}
public getSettings({
baseCurrency = DEFAULT_CURRENCY,
locale = DEFAULT_LOCALE,
xRayRules
}: UserSettings): Settings {
return {
baseCurrency,
locale,
isActive: xRayRules?.[this.getKey()]?.isActive ?? true,
thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.5
};
}
}
interface Settings extends RuleSettings {

19
apps/api/src/models/rules/economic-market-cluster-risk/developed-markets.ts

@ -1,8 +1,7 @@
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { DEFAULT_CURRENCY, DEFAULT_LOCALE } from '@ghostfolio/common/config';
import { RuleSettings, UserSettings } from '@ghostfolio/common/interfaces';
import { RuleSettings } from '@ghostfolio/common/interfaces';
export class EconomicMarketClusterRiskDevelopedMarkets extends Rule<Settings> {
private currentValueInBaseCurrency: number;
@ -17,7 +16,7 @@ export class EconomicMarketClusterRiskDevelopedMarkets extends Rule<Settings> {
) {
super(exchangeRateDataService, {
languageCode,
key: EconomicMarketClusterRiskDevelopedMarkets.name
key: 'EconomicMarketClusterRiskDevelopedMarkets'
});
this.currentValueInBaseCurrency = currentValueInBaseCurrency;
@ -96,20 +95,6 @@ export class EconomicMarketClusterRiskDevelopedMarkets extends Rule<Settings> {
languageCode: this.getLanguageCode()
});
}
public getSettings({
baseCurrency = DEFAULT_CURRENCY,
locale = DEFAULT_LOCALE,
xRayRules
}: UserSettings): Settings {
return {
baseCurrency,
locale,
isActive: xRayRules?.[this.getKey()]?.isActive ?? true,
thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.72,
thresholdMin: xRayRules?.[this.getKey()]?.thresholdMin ?? 0.68
};
}
}
interface Settings extends RuleSettings {

19
apps/api/src/models/rules/economic-market-cluster-risk/emerging-markets.ts

@ -1,8 +1,7 @@
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { DEFAULT_CURRENCY, DEFAULT_LOCALE } from '@ghostfolio/common/config';
import { RuleSettings, UserSettings } from '@ghostfolio/common/interfaces';
import { RuleSettings } from '@ghostfolio/common/interfaces';
export class EconomicMarketClusterRiskEmergingMarkets extends Rule<Settings> {
private currentValueInBaseCurrency: number;
@ -17,7 +16,7 @@ export class EconomicMarketClusterRiskEmergingMarkets extends Rule<Settings> {
) {
super(exchangeRateDataService, {
languageCode,
key: EconomicMarketClusterRiskEmergingMarkets.name
key: 'EconomicMarketClusterRiskEmergingMarkets'
});
this.currentValueInBaseCurrency = currentValueInBaseCurrency;
@ -96,20 +95,6 @@ export class EconomicMarketClusterRiskEmergingMarkets extends Rule<Settings> {
languageCode: this.getLanguageCode()
});
}
public getSettings({
baseCurrency = DEFAULT_CURRENCY,
locale = DEFAULT_LOCALE,
xRayRules
}: UserSettings): Settings {
return {
baseCurrency,
locale,
isActive: xRayRules?.[this.getKey()]?.isActive ?? true,
thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.32,
thresholdMin: xRayRules?.[this.getKey()]?.thresholdMin ?? 0.28
};
}
}
interface Settings extends RuleSettings {

17
apps/api/src/models/rules/emergency-fund/emergency-fund-coverage.ts

@ -1,8 +1,7 @@
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { DEFAULT_CURRENCY, DEFAULT_LOCALE } from '@ghostfolio/common/config';
import { RuleSettings, UserSettings } from '@ghostfolio/common/interfaces';
import { RuleSettings } from '@ghostfolio/common/interfaces';
import { Big } from 'big.js';
@ -17,7 +16,7 @@ export class EmergencyFundCoverage extends Rule<Settings> {
) {
super(exchangeRateDataService, {
languageCode,
key: EmergencyFundCoverage.name
key: 'EmergencyFundCoverage'
});
}
@ -91,18 +90,6 @@ export class EmergencyFundCoverage extends Rule<Settings> {
languageCode: this.getLanguageCode()
});
}
public getSettings({
baseCurrency = DEFAULT_CURRENCY,
locale = DEFAULT_LOCALE,
xRayRules
}: UserSettings): Settings {
return {
baseCurrency,
locale,
isActive: xRayRules?.[this.getKey()]?.isActive ?? true
};
}
}
interface Settings extends RuleSettings {

17
apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts

@ -1,8 +1,7 @@
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { DEFAULT_CURRENCY, DEFAULT_LOCALE } from '@ghostfolio/common/config';
import { RuleSettings, UserSettings } from '@ghostfolio/common/interfaces';
import { RuleSettings } from '@ghostfolio/common/interfaces';
export class EmergencyFundSetup extends Rule<Settings> {
private emergencyFund: number;
@ -15,7 +14,7 @@ export class EmergencyFundSetup extends Rule<Settings> {
) {
super(exchangeRateDataService, {
languageCode,
key: EmergencyFundSetup.name
key: 'EmergencyFundSetup'
});
this.emergencyFund = emergencyFund;
@ -51,18 +50,6 @@ export class EmergencyFundSetup extends Rule<Settings> {
languageCode: this.getLanguageCode()
});
}
public getSettings({
baseCurrency = DEFAULT_CURRENCY,
locale = DEFAULT_LOCALE,
xRayRules
}: UserSettings): Settings {
return {
baseCurrency,
locale,
isActive: xRayRules?.[this.getKey()]?.isActive ?? true
};
}
}
interface Settings extends RuleSettings {

18
apps/api/src/models/rules/fees/fee-ratio-total-investment-volume.ts

@ -1,8 +1,7 @@
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { DEFAULT_CURRENCY, DEFAULT_LOCALE } from '@ghostfolio/common/config';
import { RuleSettings, UserSettings } from '@ghostfolio/common/interfaces';
import { RuleSettings } from '@ghostfolio/common/interfaces';
export class FeeRatioTotalInvestmentVolume extends Rule<Settings> {
private fees: number;
@ -17,7 +16,7 @@ export class FeeRatioTotalInvestmentVolume extends Rule<Settings> {
) {
super(exchangeRateDataService, {
languageCode,
key: FeeRatioTotalInvestmentVolume.name
key: 'FeeRatioTotalInvestmentVolume'
});
this.fees = fees;
@ -75,19 +74,6 @@ export class FeeRatioTotalInvestmentVolume extends Rule<Settings> {
languageCode: this.getLanguageCode()
});
}
public getSettings({
baseCurrency = DEFAULT_CURRENCY,
locale = DEFAULT_LOCALE,
xRayRules
}: UserSettings): Settings {
return {
baseCurrency,
locale,
isActive: xRayRules?.[this.getKey()]?.isActive ?? true,
thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.01
};
}
}
interface Settings extends RuleSettings {

18
apps/api/src/models/rules/liquidity/buying-power.ts

@ -1,8 +1,7 @@
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { DEFAULT_CURRENCY, DEFAULT_LOCALE } from '@ghostfolio/common/config';
import { RuleSettings, UserSettings } from '@ghostfolio/common/interfaces';
import { RuleSettings } from '@ghostfolio/common/interfaces';
export class BuyingPower extends Rule<Settings> {
private buyingPower: number;
@ -15,7 +14,7 @@ export class BuyingPower extends Rule<Settings> {
) {
super(exchangeRateDataService, {
languageCode,
key: BuyingPower.name
key: 'BuyingPower'
});
this.buyingPower = buyingPower;
@ -82,19 +81,6 @@ export class BuyingPower extends Rule<Settings> {
languageCode: this.getLanguageCode()
});
}
public getSettings({
baseCurrency = DEFAULT_CURRENCY,
locale = DEFAULT_LOCALE,
xRayRules
}: UserSettings): Settings {
return {
baseCurrency,
locale,
isActive: xRayRules?.[this.getKey()]?.isActive ?? true,
thresholdMin: xRayRules?.[this.getKey()]?.thresholdMin ?? 0
};
}
}
interface Settings extends RuleSettings {

18
apps/api/src/models/rules/regional-market-cluster-risk/asia-pacific.ts

@ -1,8 +1,6 @@
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { DEFAULT_CURRENCY, DEFAULT_LOCALE } from '@ghostfolio/common/config';
import { UserSettings } from '@ghostfolio/common/interfaces';
import { Settings } from './interfaces/rule-settings.interface';
@ -19,7 +17,7 @@ export class RegionalMarketClusterRiskAsiaPacific extends Rule<Settings> {
) {
super(exchangeRateDataService, {
languageCode,
key: RegionalMarketClusterRiskAsiaPacific.name
key: 'RegionalMarketClusterRiskAsiaPacific'
});
this.asiaPacificValueInBaseCurrency = asiaPacificValueInBaseCurrency;
@ -90,18 +88,4 @@ export class RegionalMarketClusterRiskAsiaPacific extends Rule<Settings> {
languageCode: this.getLanguageCode()
});
}
public getSettings({
baseCurrency = DEFAULT_CURRENCY,
locale = DEFAULT_LOCALE,
xRayRules
}: UserSettings): Settings {
return {
baseCurrency,
locale,
isActive: xRayRules?.[this.getKey()]?.isActive ?? true,
thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.03,
thresholdMin: xRayRules?.[this.getKey()]?.thresholdMin ?? 0.02
};
}
}

18
apps/api/src/models/rules/regional-market-cluster-risk/emerging-markets.ts

@ -1,8 +1,6 @@
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { DEFAULT_CURRENCY, DEFAULT_LOCALE } from '@ghostfolio/common/config';
import { UserSettings } from '@ghostfolio/common/interfaces';
import { Settings } from './interfaces/rule-settings.interface';
@ -19,7 +17,7 @@ export class RegionalMarketClusterRiskEmergingMarkets extends Rule<Settings> {
) {
super(exchangeRateDataService, {
languageCode,
key: RegionalMarketClusterRiskEmergingMarkets.name
key: 'RegionalMarketClusterRiskEmergingMarkets'
});
this.currentValueInBaseCurrency = currentValueInBaseCurrency;
@ -92,18 +90,4 @@ export class RegionalMarketClusterRiskEmergingMarkets extends Rule<Settings> {
languageCode: this.getLanguageCode()
});
}
public getSettings({
baseCurrency = DEFAULT_CURRENCY,
locale = DEFAULT_LOCALE,
xRayRules
}: UserSettings): Settings {
return {
baseCurrency,
locale,
isActive: xRayRules?.[this.getKey()]?.isActive ?? true,
thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.12,
thresholdMin: xRayRules?.[this.getKey()]?.thresholdMin ?? 0.08
};
}
}

18
apps/api/src/models/rules/regional-market-cluster-risk/europe.ts

@ -1,8 +1,6 @@
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { DEFAULT_CURRENCY, DEFAULT_LOCALE } from '@ghostfolio/common/config';
import { UserSettings } from '@ghostfolio/common/interfaces';
import { Settings } from './interfaces/rule-settings.interface';
@ -19,7 +17,7 @@ export class RegionalMarketClusterRiskEurope extends Rule<Settings> {
) {
super(exchangeRateDataService, {
languageCode,
key: RegionalMarketClusterRiskEurope.name
key: 'RegionalMarketClusterRiskEurope'
});
this.currentValueInBaseCurrency = currentValueInBaseCurrency;
@ -90,18 +88,4 @@ export class RegionalMarketClusterRiskEurope extends Rule<Settings> {
languageCode: this.getLanguageCode()
});
}
public getSettings({
baseCurrency = DEFAULT_CURRENCY,
locale = DEFAULT_LOCALE,
xRayRules
}: UserSettings): Settings {
return {
baseCurrency,
locale,
isActive: xRayRules?.[this.getKey()]?.isActive ?? true,
thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.15,
thresholdMin: xRayRules?.[this.getKey()]?.thresholdMin ?? 0.11
};
}
}

18
apps/api/src/models/rules/regional-market-cluster-risk/japan.ts

@ -1,8 +1,6 @@
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { DEFAULT_CURRENCY, DEFAULT_LOCALE } from '@ghostfolio/common/config';
import { UserSettings } from '@ghostfolio/common/interfaces';
import { Settings } from './interfaces/rule-settings.interface';
@ -19,7 +17,7 @@ export class RegionalMarketClusterRiskJapan extends Rule<Settings> {
) {
super(exchangeRateDataService, {
languageCode,
key: RegionalMarketClusterRiskJapan.name
key: 'RegionalMarketClusterRiskJapan'
});
this.currentValueInBaseCurrency = currentValueInBaseCurrency;
@ -90,18 +88,4 @@ export class RegionalMarketClusterRiskJapan extends Rule<Settings> {
languageCode: this.getLanguageCode()
});
}
public getSettings({
baseCurrency = DEFAULT_CURRENCY,
locale = DEFAULT_LOCALE,
xRayRules
}: UserSettings): Settings {
return {
baseCurrency,
locale,
isActive: xRayRules?.[this.getKey()]?.isActive ?? true,
thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.06,
thresholdMin: xRayRules?.[this.getKey()]?.thresholdMin ?? 0.04
};
}
}

18
apps/api/src/models/rules/regional-market-cluster-risk/north-america.ts

@ -1,8 +1,6 @@
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { DEFAULT_CURRENCY, DEFAULT_LOCALE } from '@ghostfolio/common/config';
import { UserSettings } from '@ghostfolio/common/interfaces';
import { Settings } from './interfaces/rule-settings.interface';
@ -19,7 +17,7 @@ export class RegionalMarketClusterRiskNorthAmerica extends Rule<Settings> {
) {
super(exchangeRateDataService, {
languageCode,
key: RegionalMarketClusterRiskNorthAmerica.name
key: 'RegionalMarketClusterRiskNorthAmerica'
});
this.currentValueInBaseCurrency = currentValueInBaseCurrency;
@ -90,18 +88,4 @@ export class RegionalMarketClusterRiskNorthAmerica extends Rule<Settings> {
languageCode: this.getLanguageCode()
});
}
public getSettings({
baseCurrency = DEFAULT_CURRENCY,
locale = DEFAULT_LOCALE,
xRayRules
}: UserSettings): Settings {
return {
baseCurrency,
locale,
isActive: xRayRules?.[this.getKey()]?.isActive ?? true,
thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.69,
thresholdMin: xRayRules?.[this.getKey()]?.thresholdMin ?? 0.65
};
}
}

129
apps/api/src/models/rules/rule-settings.spec.ts

@ -0,0 +1,129 @@
import { getRuleSettings, getXRayRulesSettings } from './rule-settings';
describe('Rule settings', () => {
it('gets default settings for a rule', () => {
expect(
getRuleSettings({
key: 'AssetClassClusterRiskEquity',
userSettings: {}
})
).toEqual({
baseCurrency: 'USD',
isActive: true,
locale: 'en-US',
thresholdMax: 0.82,
thresholdMin: 0.78
});
});
it('gets a default threshold of zero for a rule', () => {
expect(
getRuleSettings({
key: 'BuyingPower',
userSettings: {}
})
).toEqual({
baseCurrency: 'USD',
isActive: true,
locale: 'en-US',
thresholdMin: 0
});
});
it('applies the configured settings for a rule', () => {
expect(
getRuleSettings({
key: 'AssetClassClusterRiskEquity',
userSettings: {
baseCurrency: 'CHF',
locale: 'de-CH',
xRayRules: {
AssetClassClusterRiskEquity: {
isActive: false,
thresholdMax: 0.9,
thresholdMin: 0.7
}
}
}
})
).toEqual({
baseCurrency: 'CHF',
isActive: false,
locale: 'de-CH',
thresholdMax: 0.9,
thresholdMin: 0.7
});
});
it('applies the default for a threshold which is not configured', () => {
expect(
getRuleSettings({
key: 'AssetClassClusterRiskEquity',
userSettings: {
xRayRules: {
AssetClassClusterRiskEquity: { isActive: true, thresholdMax: 0.9 }
}
}
})
).toEqual({
baseCurrency: 'USD',
isActive: true,
locale: 'en-US',
thresholdMax: 0.9,
thresholdMin: 0.78
});
});
it('applies a configured threshold of zero', () => {
expect(
getRuleSettings({
key: 'AssetClassClusterRiskEquity',
userSettings: {
xRayRules: {
AssetClassClusterRiskEquity: { isActive: true, thresholdMin: 0 }
}
}
})
).toEqual({
baseCurrency: 'USD',
isActive: true,
locale: 'en-US',
thresholdMax: 0.82,
thresholdMin: 0
});
});
it('omits the base currency when it is not used by a rule', () => {
expect(
getRuleSettings({
key: 'AccountClusterRiskSingleAccount',
userSettings: {}
})
).toEqual({
isActive: true,
locale: 'en-US'
});
});
it('gets settings for all rules', () => {
expect(Object.keys(getXRayRulesSettings({})).sort()).toEqual([
'AccountClusterRiskCurrentInvestment',
'AccountClusterRiskSingleAccount',
'AssetClassClusterRiskEquity',
'AssetClassClusterRiskFixedIncome',
'BuyingPower',
'CurrencyClusterRiskBaseCurrencyCurrentInvestment',
'CurrencyClusterRiskCurrentInvestment',
'EconomicMarketClusterRiskDevelopedMarkets',
'EconomicMarketClusterRiskEmergingMarkets',
'EmergencyFundCoverage',
'EmergencyFundSetup',
'FeeRatioTotalInvestmentVolume',
'RegionalMarketClusterRiskAsiaPacific',
'RegionalMarketClusterRiskEmergingMarkets',
'RegionalMarketClusterRiskEurope',
'RegionalMarketClusterRiskJapan',
'RegionalMarketClusterRiskNorthAmerica'
]);
});
});

98
apps/api/src/models/rules/rule-settings.ts

@ -0,0 +1,98 @@
import { RuleSettingsConfiguration } from '@ghostfolio/api/models/interfaces/rule-settings-configuration.interface';
import { XRayRuleKey } from '@ghostfolio/api/models/types/x-ray-rule-key.type';
import { DEFAULT_CURRENCY, DEFAULT_LOCALE } from '@ghostfolio/common/config';
import {
RuleSettings,
UserSettings,
XRayRulesSettings
} from '@ghostfolio/common/interfaces';
const RULE_SETTINGS_CONFIGURATIONS = {
AccountClusterRiskCurrentInvestment: { thresholdMax: 0.5 },
AccountClusterRiskSingleAccount: { withBaseCurrency: false },
AssetClassClusterRiskEquity: {
thresholdMax: 0.82,
thresholdMin: 0.78
},
AssetClassClusterRiskFixedIncome: {
thresholdMax: 0.22,
thresholdMin: 0.18
},
BuyingPower: { thresholdMin: 0 },
CurrencyClusterRiskBaseCurrencyCurrentInvestment: {},
CurrencyClusterRiskCurrentInvestment: { thresholdMax: 0.5 },
EconomicMarketClusterRiskDevelopedMarkets: {
thresholdMax: 0.72,
thresholdMin: 0.68
},
EconomicMarketClusterRiskEmergingMarkets: {
thresholdMax: 0.32,
thresholdMin: 0.28
},
EmergencyFundCoverage: {},
EmergencyFundSetup: {},
FeeRatioTotalInvestmentVolume: { thresholdMax: 0.01 },
RegionalMarketClusterRiskAsiaPacific: {
thresholdMax: 0.03,
thresholdMin: 0.02
},
RegionalMarketClusterRiskEmergingMarkets: {
thresholdMax: 0.12,
thresholdMin: 0.08
},
RegionalMarketClusterRiskEurope: {
thresholdMax: 0.15,
thresholdMin: 0.11
},
RegionalMarketClusterRiskJapan: {
thresholdMax: 0.06,
thresholdMin: 0.04
},
RegionalMarketClusterRiskNorthAmerica: {
thresholdMax: 0.69,
thresholdMin: 0.65
}
} as const satisfies Record<XRayRuleKey, RuleSettingsConfiguration>;
export function getRuleSettings<T extends RuleSettings>({
key,
userSettings
}: {
key: XRayRuleKey;
userSettings: UserSettings;
}): T {
const configuration: RuleSettingsConfiguration =
RULE_SETTINGS_CONFIGURATIONS[key] ?? {};
const configuredSettings = userSettings.xRayRules?.[key];
return {
...(configuration.withBaseCurrency === false
? {}
: { baseCurrency: userSettings.baseCurrency ?? DEFAULT_CURRENCY }),
isActive: configuredSettings?.isActive ?? true,
locale: userSettings.locale ?? DEFAULT_LOCALE,
...(configuration.thresholdMax === undefined
? {}
: {
thresholdMax:
configuredSettings?.thresholdMax ?? configuration.thresholdMax
}),
...(configuration.thresholdMin === undefined
? {}
: {
thresholdMin:
configuredSettings?.thresholdMin ?? configuration.thresholdMin
})
} as T;
}
export function getXRayRulesSettings(
userSettings: UserSettings
): XRayRulesSettings {
return Object.fromEntries(
(Object.keys(RULE_SETTINGS_CONFIGURATIONS) as XRayRuleKey[]).map((key) => [
key,
getRuleSettings({ key, userSettings })
])
) as XRayRulesSettings;
}

3
apps/api/src/models/types/x-ray-rule-key.type.ts

@ -0,0 +1,3 @@
import { XRayRulesSettings } from '@ghostfolio/common/interfaces';
export type XRayRuleKey = keyof XRayRulesSettings;
Loading…
Cancel
Save