Browse Source

Modernize rules implementation

pull/3869/head
Thomas Kaul 11 months ago
parent
commit
6cc6075838
  1. 103
      apps/api/src/app/portfolio/portfolio.service.ts
  2. 19
      apps/api/src/models/rule.ts
  3. 19
      apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts
  4. 17
      apps/api/src/models/rules/currency-cluster-risk/current-investment.ts
  5. 2
      apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.component.ts

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

@ -1148,74 +1148,49 @@ export class PortfolioService {
public async getReport(impersonationId: string): Promise<PortfolioReport> {
const userId = await this.getUserId(impersonationId, this.request.user.id);
const user = await this.userService.user({ id: userId });
const userCurrency = this.getUserCurrency(user);
const { activities } =
await this.orderService.getOrdersForPortfolioCalculator({
userCurrency,
userId
});
const userSettings = <UserSettings>this.request.user.Settings.settings;
const portfolioCalculator = this.calculatorFactory.createCalculator({
activities,
const { accounts, holdings, summary } = await this.getDetails({
impersonationId,
userId,
calculationType: PerformanceCalculationType.TWR,
currency: this.request.user.Settings.settings.baseCurrency
withMarkets: true,
withSummary: true
});
let { totalFeesWithCurrencyEffect, positions, totalInvestment } =
await portfolioCalculator.getSnapshot();
positions = positions.filter((item) => !item.quantity.eq(0));
const portfolioItemsNow: { [symbol: string]: TimelinePosition } = {};
for (const position of positions) {
portfolioItemsNow[position.symbol] = position;
}
const { accounts } = await this.getValueOfAccountsAndPlatforms({
activities,
portfolioItemsNow,
userCurrency,
userId
});
const userSettings = <UserSettings>this.request.user.Settings.settings;
return {
rules: {
accountClusterRisk: isEmpty(activities)
? undefined
: await this.rulesService.evaluate(
[
new AccountClusterRiskCurrentInvestment(
this.exchangeRateDataService,
accounts
),
new AccountClusterRiskSingleAccount(
this.exchangeRateDataService,
accounts
)
],
userSettings
),
currencyClusterRisk: isEmpty(activities)
? undefined
: await this.rulesService.evaluate(
[
new CurrencyClusterRiskBaseCurrencyCurrentInvestment(
this.exchangeRateDataService,
positions
),
new CurrencyClusterRiskCurrentInvestment(
this.exchangeRateDataService,
positions
)
],
userSettings
),
accountClusterRisk:
summary.ordersCount > 0
? await this.rulesService.evaluate(
[
new AccountClusterRiskCurrentInvestment(
this.exchangeRateDataService,
accounts
),
new AccountClusterRiskSingleAccount(
this.exchangeRateDataService,
accounts
)
],
userSettings
)
: undefined,
currencyClusterRisk:
summary.ordersCount > 0
? await this.rulesService.evaluate(
[
new CurrencyClusterRiskBaseCurrencyCurrentInvestment(
this.exchangeRateDataService,
Object.values(holdings)
),
new CurrencyClusterRiskCurrentInvestment(
this.exchangeRateDataService,
Object.values(holdings)
)
],
userSettings
)
: undefined,
emergencyFund: await this.rulesService.evaluate(
[
new EmergencyFundSetup(
@ -1229,8 +1204,8 @@ export class PortfolioService {
[
new FeeRatioInitialInvestment(
this.exchangeRateDataService,
totalInvestment.toNumber(),
totalFeesWithCurrencyEffect.toNumber()
summary.committedFunds,
summary.fees
)
],
userSettings

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

@ -1,8 +1,9 @@
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { groupBy } from '@ghostfolio/common/helper';
import { UserSettings } from '@ghostfolio/common/interfaces';
import { TimelinePosition } from '@ghostfolio/common/models';
import { PortfolioPosition, UserSettings } from '@ghostfolio/common/interfaces';
import { Big } from 'big.js';
import { EvaluationResult } from './interfaces/evaluation-result.interface';
import { RuleInterface } from './interfaces/rule.interface';
@ -33,24 +34,26 @@ export abstract class Rule<T extends RuleSettings> implements RuleInterface<T> {
return this.name;
}
public groupCurrentPositionsByAttribute(
positions: TimelinePosition[],
attribute: keyof TimelinePosition,
public groupCurrentHoldingsByAttribute(
holdings: PortfolioPosition[],
attribute: keyof PortfolioPosition,
baseCurrency: string
) {
return Array.from(groupBy(attribute, positions).entries()).map(
return Array.from(groupBy(attribute, holdings).entries()).map(
([attributeValue, objs]) => ({
groupKey: attributeValue,
investment: objs.reduce(
(previousValue, currentValue) =>
previousValue + currentValue.investment.toNumber(),
previousValue + currentValue.investment,
0
),
value: objs.reduce(
(previousValue, currentValue) =>
previousValue +
this.exchangeRateDataService.toCurrency(
currentValue.quantity.mul(currentValue.marketPrice).toNumber(),
new Big(currentValue.quantity)
.mul(currentValue.marketPrice)
.toNumber(),
currentValue.currency,
baseCurrency
),

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

@ -1,35 +1,34 @@
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 { TimelinePosition } from '@ghostfolio/common/models';
import { PortfolioPosition, UserSettings } from '@ghostfolio/common/interfaces';
export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule<Settings> {
private positions: TimelinePosition[];
private holdings: PortfolioPosition[];
public constructor(
protected exchangeRateDataService: ExchangeRateDataService,
positions: TimelinePosition[]
holdings: PortfolioPosition[]
) {
super(exchangeRateDataService, {
key: CurrencyClusterRiskBaseCurrencyCurrentInvestment.name,
name: 'Investment: Base Currency'
});
this.positions = positions;
this.holdings = holdings;
}
public evaluate(ruleSettings: Settings) {
const positionsGroupedByCurrency = this.groupCurrentPositionsByAttribute(
this.positions,
const holdingsGroupedByCurrency = this.groupCurrentHoldingsByAttribute(
this.holdings,
'currency',
ruleSettings.baseCurrency
);
let maxItem = positionsGroupedByCurrency[0];
let maxItem = holdingsGroupedByCurrency[0];
let totalValue = 0;
positionsGroupedByCurrency.forEach((groupItem) => {
holdingsGroupedByCurrency.forEach((groupItem) => {
// Calculate total value
totalValue += groupItem.value;
@ -39,7 +38,7 @@ export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule<Setti
}
});
const baseCurrencyItem = positionsGroupedByCurrency.find((item) => {
const baseCurrencyItem = holdingsGroupedByCurrency.find((item) => {
return item.groupKey === ruleSettings.baseCurrency;
});

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

@ -1,35 +1,34 @@
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 { TimelinePosition } from '@ghostfolio/common/models';
import { PortfolioPosition, UserSettings } from '@ghostfolio/common/interfaces';
export class CurrencyClusterRiskCurrentInvestment extends Rule<Settings> {
private positions: TimelinePosition[];
private holdings: PortfolioPosition[];
public constructor(
protected exchangeRateDataService: ExchangeRateDataService,
positions: TimelinePosition[]
holdings: PortfolioPosition[]
) {
super(exchangeRateDataService, {
key: CurrencyClusterRiskCurrentInvestment.name,
name: 'Investment'
});
this.positions = positions;
this.holdings = holdings;
}
public evaluate(ruleSettings: Settings) {
const positionsGroupedByCurrency = this.groupCurrentPositionsByAttribute(
this.positions,
const holdingsGroupedByCurrency = this.groupCurrentHoldingsByAttribute(
this.holdings,
'currency',
ruleSettings.baseCurrency
);
let maxItem = positionsGroupedByCurrency[0];
let maxItem = holdingsGroupedByCurrency[0];
let totalValue = 0;
positionsGroupedByCurrency.forEach((groupItem) => {
holdingsGroupedByCurrency.forEach((groupItem) => {
// Calculate total value
totalValue += groupItem.value;

2
apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.component.ts

@ -35,8 +35,6 @@ export class GfRuleSettingsDialogComponent {
@Inject(MAT_DIALOG_DATA) public data: IRuleSettingsDialogParams,
public dialogRef: MatDialogRef<GfRuleSettingsDialogComponent>
) {
console.log(this.data.rule);
this.settings = this.data.rule.settings;
}
}

Loading…
Cancel
Save