From 9b024aa5138693735bfd3dafc24f7d053cf9992b Mon Sep 17 00:00:00 2001 From: Thomas <4159106+dtslvr@users.noreply.github.com> Date: Fri, 29 Sep 2023 20:58:22 +0200 Subject: [PATCH] dd new static portfolio analysis rule: Emergency fund setup --- .../src/app/portfolio/portfolio.service.ts | 5 +++ .../current-investment.ts | 9 ++-- .../account-cluster-risk/single-account.ts | 9 ++-- .../base-currency-current-investment.ts | 9 ++-- .../current-investment.ts | 9 ++-- .../emergency-fund/emergency-fund-setup.ts | 41 +++++++++++++++++++ .../fees/fee-ratio-initial-investment.ts | 15 ++++--- .../portfolio/fire/fire-page.component.ts | 3 ++ .../app/pages/portfolio/fire/fire-page.html | 27 +++++++++--- 9 files changed, 104 insertions(+), 23 deletions(-) create mode 100644 apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 228ab18f6..775abd44c 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -10,6 +10,7 @@ import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rule import { AccountClusterRiskSingleAccount } from '@ghostfolio/api/models/rules/account-cluster-risk/single-account'; 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 { EmergencyFundSetup } from '@ghostfolio/api/models/rules/emergency-fund/emergency-fund-setup'; import { FeeRatioInitialInvestment } from '@ghostfolio/api/models/rules/fees/fee-ratio-initial-investment'; import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; @@ -1277,6 +1278,10 @@ export class PortfolioService { ], this.request.user.Settings.settings ), + emergencyFund: await this.rulesService.evaluate( + [new EmergencyFundSetup(this.exchangeRateDataService)], + this.request.user.Settings.settings + ), fees: await this.rulesService.evaluate( [ new FeeRatioInitialInvestment( diff --git a/apps/api/src/models/rules/account-cluster-risk/current-investment.ts b/apps/api/src/models/rules/account-cluster-risk/current-investment.ts index d0cdbb58c..23d3307de 100644 --- a/apps/api/src/models/rules/account-cluster-risk/current-investment.ts +++ b/apps/api/src/models/rules/account-cluster-risk/current-investment.ts @@ -1,4 +1,5 @@ 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 { PortfolioDetails, @@ -6,16 +7,18 @@ import { UserSettings } from '@ghostfolio/common/interfaces'; -import { Rule } from '../../rule'; - export class AccountClusterRiskCurrentInvestment extends Rule { + private accounts: PortfolioDetails['accounts']; + public constructor( protected exchangeRateDataService: ExchangeRateDataService, - private accounts: PortfolioDetails['accounts'] + accounts: PortfolioDetails['accounts'] ) { super(exchangeRateDataService, { name: 'Investment' }); + + this.accounts = accounts; } public evaluate(ruleSettings: Settings) { diff --git a/apps/api/src/models/rules/account-cluster-risk/single-account.ts b/apps/api/src/models/rules/account-cluster-risk/single-account.ts index 3be323d7c..b5028228a 100644 --- a/apps/api/src/models/rules/account-cluster-risk/single-account.ts +++ b/apps/api/src/models/rules/account-cluster-risk/single-account.ts @@ -1,17 +1,20 @@ 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 { PortfolioDetails, UserSettings } from '@ghostfolio/common/interfaces'; -import { Rule } from '../../rule'; - export class AccountClusterRiskSingleAccount extends Rule { + private accounts: PortfolioDetails['accounts']; + public constructor( protected exchangeRateDataService: ExchangeRateDataService, - private accounts: PortfolioDetails['accounts'] + accounts: PortfolioDetails['accounts'] ) { super(exchangeRateDataService, { name: 'Single Account' }); + + this.accounts = accounts; } public evaluate() { diff --git a/apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts b/apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts index 2facb8803..a23a208c3 100644 --- a/apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts +++ b/apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts @@ -1,17 +1,20 @@ 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 { TimelinePosition, UserSettings } from '@ghostfolio/common/interfaces'; -import { Rule } from '../../rule'; - export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule { + private positions: TimelinePosition[]; + public constructor( protected exchangeRateDataService: ExchangeRateDataService, - private positions: TimelinePosition[] + positions: TimelinePosition[] ) { super(exchangeRateDataService, { name: 'Investment: Base Currency' }); + + this.positions = positions; } public evaluate(ruleSettings: Settings) { diff --git a/apps/api/src/models/rules/currency-cluster-risk/current-investment.ts b/apps/api/src/models/rules/currency-cluster-risk/current-investment.ts index 2d69865f5..bd6e060ef 100644 --- a/apps/api/src/models/rules/currency-cluster-risk/current-investment.ts +++ b/apps/api/src/models/rules/currency-cluster-risk/current-investment.ts @@ -1,17 +1,20 @@ 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 { TimelinePosition, UserSettings } from '@ghostfolio/common/interfaces'; -import { Rule } from '../../rule'; - export class CurrencyClusterRiskCurrentInvestment extends Rule { + private positions: TimelinePosition[]; + public constructor( protected exchangeRateDataService: ExchangeRateDataService, - private positions: TimelinePosition[] + positions: TimelinePosition[] ) { super(exchangeRateDataService, { name: 'Investment' }); + + this.positions = positions; } public evaluate(ruleSettings: Settings) { diff --git a/apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts b/apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts new file mode 100644 index 000000000..f00880297 --- /dev/null +++ b/apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts @@ -0,0 +1,41 @@ +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 EmergencyFundSetup extends Rule { + public constructor( + protected exchangeRateDataService: ExchangeRateDataService + ) { + super(exchangeRateDataService, { + name: 'Emergency Fund: Set up' + }); + } + + public evaluate(ruleSettings: Settings) { + if (ruleSettings.threshold > 0) { + return { + evaluation: `You have set up an emergency fund`, + value: true + }; + } + + return { + evaluation: `You have not set up an emergency fund yet`, + value: false + }; + } + + public getSettings(aUserSettings: UserSettings): Settings { + return { + baseCurrency: aUserSettings.baseCurrency, + isActive: true, + threshold: aUserSettings.emergencyFund + }; + } +} + +interface Settings extends RuleSettings { + baseCurrency: string; + threshold: number; +} diff --git a/apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts b/apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts index dfe375c43..f160b1cd4 100644 --- a/apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts +++ b/apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts @@ -1,18 +1,23 @@ 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 { Rule } from '../../rule'; - export class FeeRatioInitialInvestment extends Rule { + private fees: number; + private totalInvestment: number; + public constructor( protected exchangeRateDataService: ExchangeRateDataService, - private totalInvestment: number, - private fees: number + totalInvestment: number, + fees: number ) { super(exchangeRateDataService, { - name: 'Investment' + name: 'Fee Ratio' }); + + this.fees = fees; + this.totalInvestment = totalInvestment; } public evaluate(ruleSettings: Settings) { diff --git a/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts b/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts index 58d0b3702..909ca774a 100644 --- a/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts +++ b/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts @@ -18,6 +18,7 @@ export class FirePageComponent implements OnDestroy, OnInit { public accountClusterRiskRules: PortfolioReportRule[]; public currencyClusterRiskRules: PortfolioReportRule[]; public deviceType: string; + public emergencyFundRules: PortfolioReportRule[]; public feeRules: PortfolioReportRule[]; public fireWealth: Big; public hasImpersonationId: boolean; @@ -67,6 +68,8 @@ export class FirePageComponent implements OnDestroy, OnInit { portfolioReport.rules['accountClusterRisk'] || null; this.currencyClusterRiskRules = portfolioReport.rules['currencyClusterRisk'] || null; + this.emergencyFundRules = + portfolioReport.rules['emergencyFund'] || null; this.feeRules = portfolioReport.rules['fees'] || null; this.changeDetectorRef.markForCheck(); diff --git a/apps/client/src/app/pages/portfolio/fire/fire-page.html b/apps/client/src/app/pages/portfolio/fire/fire-page.html index 793eadc77..e8cfef2de 100644 --- a/apps/client/src/app/pages/portfolio/fire/fire-page.html +++ b/apps/client/src/app/pages/portfolio/fire/fire-page.html @@ -96,8 +96,10 @@

X-ray

- Ghostfolio X-ray uses static analysis to identify potential issues and - risks in your portfolio. + Ghostfolio X-ray uses static analysis to identify potential issues + and risks in your portfolio. It will be highly configurable in the future: activate / deactivate rules and customize the thresholds to match your personal investment @@ -106,7 +108,7 @@

- Currency Cluster RisksCurrency Cluster Risks

- Account Cluster RisksAccount Cluster Risks

-
+

- FeesFees

+
+

+ Emergency Fund +

+ +