Feature/add emergency fund setup to static portfolio analysis rules (#2400)
* Add new static portfolio analysis rule: Emergency fund setup * Update changelog
This commit is contained in:
@@ -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';
|
||||
@@ -1214,12 +1215,6 @@ export class PortfolioService {
|
||||
userId
|
||||
});
|
||||
|
||||
if (isEmpty(orders)) {
|
||||
return {
|
||||
rules: {}
|
||||
};
|
||||
}
|
||||
|
||||
const portfolioCalculator = new PortfolioCalculator({
|
||||
currency: userCurrency,
|
||||
currentRateService: this.currentRateService,
|
||||
@@ -1228,7 +1223,9 @@ export class PortfolioService {
|
||||
|
||||
portfolioCalculator.setTransactionPoints(transactionPoints);
|
||||
|
||||
const portfolioStart = parseDate(transactionPoints[0].date);
|
||||
const portfolioStart = parseDate(
|
||||
transactionPoints[0]?.date ?? format(new Date(), DATE_FORMAT)
|
||||
);
|
||||
const currentPositions =
|
||||
await portfolioCalculator.getCurrentPositions(portfolioStart);
|
||||
|
||||
@@ -1249,33 +1246,48 @@ export class PortfolioService {
|
||||
userId
|
||||
});
|
||||
|
||||
const userSettings = <UserSettings>this.request.user.Settings.settings;
|
||||
|
||||
return {
|
||||
rules: {
|
||||
accountClusterRisk: await this.rulesService.evaluate(
|
||||
[
|
||||
new AccountClusterRiskCurrentInvestment(
|
||||
this.exchangeRateDataService,
|
||||
accounts
|
||||
accountClusterRisk: isEmpty(orders)
|
||||
? undefined
|
||||
: await this.rulesService.evaluate(
|
||||
[
|
||||
new AccountClusterRiskCurrentInvestment(
|
||||
this.exchangeRateDataService,
|
||||
accounts
|
||||
),
|
||||
new AccountClusterRiskSingleAccount(
|
||||
this.exchangeRateDataService,
|
||||
accounts
|
||||
)
|
||||
],
|
||||
userSettings
|
||||
),
|
||||
new AccountClusterRiskSingleAccount(
|
||||
currencyClusterRisk: isEmpty(orders)
|
||||
? undefined
|
||||
: await this.rulesService.evaluate(
|
||||
[
|
||||
new CurrencyClusterRiskBaseCurrencyCurrentInvestment(
|
||||
this.exchangeRateDataService,
|
||||
positions
|
||||
),
|
||||
new CurrencyClusterRiskCurrentInvestment(
|
||||
this.exchangeRateDataService,
|
||||
positions
|
||||
)
|
||||
],
|
||||
userSettings
|
||||
),
|
||||
emergencyFund: await this.rulesService.evaluate(
|
||||
[
|
||||
new EmergencyFundSetup(
|
||||
this.exchangeRateDataService,
|
||||
accounts
|
||||
userSettings.emergencyFund
|
||||
)
|
||||
],
|
||||
<UserSettings>this.request.user.Settings.settings
|
||||
),
|
||||
currencyClusterRisk: await this.rulesService.evaluate(
|
||||
[
|
||||
new CurrencyClusterRiskBaseCurrencyCurrentInvestment(
|
||||
this.exchangeRateDataService,
|
||||
positions
|
||||
),
|
||||
new CurrencyClusterRiskCurrentInvestment(
|
||||
this.exchangeRateDataService,
|
||||
positions
|
||||
)
|
||||
],
|
||||
<UserSettings>this.request.user.Settings.settings
|
||||
userSettings
|
||||
),
|
||||
fees: await this.rulesService.evaluate(
|
||||
[
|
||||
@@ -1285,7 +1297,7 @@ export class PortfolioService {
|
||||
this.getFees({ userCurrency, activities: orders }).toNumber()
|
||||
)
|
||||
],
|
||||
<UserSettings>this.request.user.Settings.settings
|
||||
userSettings
|
||||
)
|
||||
}
|
||||
};
|
||||
|
@@ -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<Settings> {
|
||||
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) {
|
||||
|
@@ -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<RuleSettings> {
|
||||
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() {
|
||||
|
@@ -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<Settings> {
|
||||
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) {
|
||||
|
@@ -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<Settings> {
|
||||
private positions: TimelinePosition[];
|
||||
|
||||
public constructor(
|
||||
protected exchangeRateDataService: ExchangeRateDataService,
|
||||
private positions: TimelinePosition[]
|
||||
positions: TimelinePosition[]
|
||||
) {
|
||||
super(exchangeRateDataService, {
|
||||
name: 'Investment'
|
||||
});
|
||||
|
||||
this.positions = positions;
|
||||
}
|
||||
|
||||
public evaluate(ruleSettings: Settings) {
|
||||
|
@@ -0,0 +1,46 @@
|
||||
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<Settings> {
|
||||
private emergencyFund: number;
|
||||
|
||||
public constructor(
|
||||
protected exchangeRateDataService: ExchangeRateDataService,
|
||||
emergencyFund: number
|
||||
) {
|
||||
super(exchangeRateDataService, {
|
||||
name: 'Emergency Fund: Set up'
|
||||
});
|
||||
|
||||
this.emergencyFund = emergencyFund;
|
||||
}
|
||||
|
||||
public evaluate(ruleSettings: Settings) {
|
||||
if (this.emergencyFund > ruleSettings.threshold) {
|
||||
return {
|
||||
evaluation: 'An emergency fund has been set up',
|
||||
value: true
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
evaluation: 'No emergency fund has been set up',
|
||||
value: false
|
||||
};
|
||||
}
|
||||
|
||||
public getSettings(aUserSettings: UserSettings): Settings {
|
||||
return {
|
||||
baseCurrency: aUserSettings.baseCurrency,
|
||||
isActive: true,
|
||||
threshold: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
interface Settings extends RuleSettings {
|
||||
baseCurrency: string;
|
||||
threshold: number;
|
||||
}
|
@@ -1,22 +1,29 @@
|
||||
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<Settings> {
|
||||
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) {
|
||||
const feeRatio = this.fees / this.totalInvestment;
|
||||
const feeRatio = this.totalInvestment
|
||||
? this.fees / this.totalInvestment
|
||||
: 0;
|
||||
|
||||
if (feeRatio > ruleSettings.threshold) {
|
||||
return {
|
||||
|
Reference in New Issue
Block a user