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> { public async getReport(impersonationId: string): Promise<PortfolioReport> {
const userId = await this.getUserId(impersonationId, this.request.user.id); const userId = await this.getUserId(impersonationId, this.request.user.id);
const user = await this.userService.user({ id: userId }); const userSettings = <UserSettings>this.request.user.Settings.settings;
const userCurrency = this.getUserCurrency(user);
const { activities } =
await this.orderService.getOrdersForPortfolioCalculator({
userCurrency,
userId
});
const portfolioCalculator = this.calculatorFactory.createCalculator({ const { accounts, holdings, summary } = await this.getDetails({
activities, impersonationId,
userId, userId,
calculationType: PerformanceCalculationType.TWR, withMarkets: true,
currency: this.request.user.Settings.settings.baseCurrency 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 { return {
rules: { rules: {
accountClusterRisk: isEmpty(activities) accountClusterRisk:
? undefined summary.ordersCount > 0
: await this.rulesService.evaluate( ? await this.rulesService.evaluate(
[ [
new AccountClusterRiskCurrentInvestment( new AccountClusterRiskCurrentInvestment(
this.exchangeRateDataService, this.exchangeRateDataService,
accounts accounts
), ),
new AccountClusterRiskSingleAccount( new AccountClusterRiskSingleAccount(
this.exchangeRateDataService, this.exchangeRateDataService,
accounts accounts
) )
], ],
userSettings userSettings
), )
currencyClusterRisk: isEmpty(activities) : undefined,
? undefined currencyClusterRisk:
: await this.rulesService.evaluate( summary.ordersCount > 0
[ ? await this.rulesService.evaluate(
new CurrencyClusterRiskBaseCurrencyCurrentInvestment( [
this.exchangeRateDataService, new CurrencyClusterRiskBaseCurrencyCurrentInvestment(
positions this.exchangeRateDataService,
), Object.values(holdings)
new CurrencyClusterRiskCurrentInvestment( ),
this.exchangeRateDataService, new CurrencyClusterRiskCurrentInvestment(
positions this.exchangeRateDataService,
) Object.values(holdings)
], )
userSettings ],
), userSettings
)
: undefined,
emergencyFund: await this.rulesService.evaluate( emergencyFund: await this.rulesService.evaluate(
[ [
new EmergencyFundSetup( new EmergencyFundSetup(
@ -1229,8 +1204,8 @@ export class PortfolioService {
[ [
new FeeRatioInitialInvestment( new FeeRatioInitialInvestment(
this.exchangeRateDataService, this.exchangeRateDataService,
totalInvestment.toNumber(), summary.committedFunds,
totalFeesWithCurrencyEffect.toNumber() summary.fees
) )
], ],
userSettings userSettings

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

@ -1,8 +1,9 @@
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { groupBy } from '@ghostfolio/common/helper'; import { groupBy } from '@ghostfolio/common/helper';
import { UserSettings } from '@ghostfolio/common/interfaces'; import { PortfolioPosition, UserSettings } from '@ghostfolio/common/interfaces';
import { TimelinePosition } from '@ghostfolio/common/models';
import { Big } from 'big.js';
import { EvaluationResult } from './interfaces/evaluation-result.interface'; import { EvaluationResult } from './interfaces/evaluation-result.interface';
import { RuleInterface } from './interfaces/rule.interface'; import { RuleInterface } from './interfaces/rule.interface';
@ -33,24 +34,26 @@ export abstract class Rule<T extends RuleSettings> implements RuleInterface<T> {
return this.name; return this.name;
} }
public groupCurrentPositionsByAttribute( public groupCurrentHoldingsByAttribute(
positions: TimelinePosition[], holdings: PortfolioPosition[],
attribute: keyof TimelinePosition, attribute: keyof PortfolioPosition,
baseCurrency: string baseCurrency: string
) { ) {
return Array.from(groupBy(attribute, positions).entries()).map( return Array.from(groupBy(attribute, holdings).entries()).map(
([attributeValue, objs]) => ({ ([attributeValue, objs]) => ({
groupKey: attributeValue, groupKey: attributeValue,
investment: objs.reduce( investment: objs.reduce(
(previousValue, currentValue) => (previousValue, currentValue) =>
previousValue + currentValue.investment.toNumber(), previousValue + currentValue.investment,
0 0
), ),
value: objs.reduce( value: objs.reduce(
(previousValue, currentValue) => (previousValue, currentValue) =>
previousValue + previousValue +
this.exchangeRateDataService.toCurrency( this.exchangeRateDataService.toCurrency(
currentValue.quantity.mul(currentValue.marketPrice).toNumber(), new Big(currentValue.quantity)
.mul(currentValue.marketPrice)
.toNumber(),
currentValue.currency, currentValue.currency,
baseCurrency 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 { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { Rule } from '@ghostfolio/api/models/rule'; import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { UserSettings } from '@ghostfolio/common/interfaces'; import { PortfolioPosition, UserSettings } from '@ghostfolio/common/interfaces';
import { TimelinePosition } from '@ghostfolio/common/models';
export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule<Settings> { export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule<Settings> {
private positions: TimelinePosition[]; private holdings: PortfolioPosition[];
public constructor( public constructor(
protected exchangeRateDataService: ExchangeRateDataService, protected exchangeRateDataService: ExchangeRateDataService,
positions: TimelinePosition[] holdings: PortfolioPosition[]
) { ) {
super(exchangeRateDataService, { super(exchangeRateDataService, {
key: CurrencyClusterRiskBaseCurrencyCurrentInvestment.name, key: CurrencyClusterRiskBaseCurrencyCurrentInvestment.name,
name: 'Investment: Base Currency' name: 'Investment: Base Currency'
}); });
this.positions = positions; this.holdings = holdings;
} }
public evaluate(ruleSettings: Settings) { public evaluate(ruleSettings: Settings) {
const positionsGroupedByCurrency = this.groupCurrentPositionsByAttribute( const holdingsGroupedByCurrency = this.groupCurrentHoldingsByAttribute(
this.positions, this.holdings,
'currency', 'currency',
ruleSettings.baseCurrency ruleSettings.baseCurrency
); );
let maxItem = positionsGroupedByCurrency[0]; let maxItem = holdingsGroupedByCurrency[0];
let totalValue = 0; let totalValue = 0;
positionsGroupedByCurrency.forEach((groupItem) => { holdingsGroupedByCurrency.forEach((groupItem) => {
// Calculate total value // Calculate total value
totalValue += groupItem.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; 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 { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { Rule } from '@ghostfolio/api/models/rule'; import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { UserSettings } from '@ghostfolio/common/interfaces'; import { PortfolioPosition, UserSettings } from '@ghostfolio/common/interfaces';
import { TimelinePosition } from '@ghostfolio/common/models';
export class CurrencyClusterRiskCurrentInvestment extends Rule<Settings> { export class CurrencyClusterRiskCurrentInvestment extends Rule<Settings> {
private positions: TimelinePosition[]; private holdings: PortfolioPosition[];
public constructor( public constructor(
protected exchangeRateDataService: ExchangeRateDataService, protected exchangeRateDataService: ExchangeRateDataService,
positions: TimelinePosition[] holdings: PortfolioPosition[]
) { ) {
super(exchangeRateDataService, { super(exchangeRateDataService, {
key: CurrencyClusterRiskCurrentInvestment.name, key: CurrencyClusterRiskCurrentInvestment.name,
name: 'Investment' name: 'Investment'
}); });
this.positions = positions; this.holdings = holdings;
} }
public evaluate(ruleSettings: Settings) { public evaluate(ruleSettings: Settings) {
const positionsGroupedByCurrency = this.groupCurrentPositionsByAttribute( const holdingsGroupedByCurrency = this.groupCurrentHoldingsByAttribute(
this.positions, this.holdings,
'currency', 'currency',
ruleSettings.baseCurrency ruleSettings.baseCurrency
); );
let maxItem = positionsGroupedByCurrency[0]; let maxItem = holdingsGroupedByCurrency[0];
let totalValue = 0; let totalValue = 0;
positionsGroupedByCurrency.forEach((groupItem) => { holdingsGroupedByCurrency.forEach((groupItem) => {
// Calculate total value // Calculate total value
totalValue += groupItem.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, @Inject(MAT_DIALOG_DATA) public data: IRuleSettingsDialogParams,
public dialogRef: MatDialogRef<GfRuleSettingsDialogComponent> public dialogRef: MatDialogRef<GfRuleSettingsDialogComponent>
) { ) {
console.log(this.data.rule);
this.settings = this.data.rule.settings; this.settings = this.data.rule.settings;
} }
} }

Loading…
Cancel
Save