refactor rule evaluation
This commit is contained in:
parent
04e6518226
commit
23b2e03923
apps/api/src
app
models
@ -0,0 +1,11 @@
|
|||||||
|
import { TimelinePosition } from '@ghostfolio/common/interfaces';
|
||||||
|
import Big from 'big.js';
|
||||||
|
|
||||||
|
export interface CurrentPositions {
|
||||||
|
hasErrors: boolean;
|
||||||
|
positions: TimelinePosition[];
|
||||||
|
grossPerformance: Big;
|
||||||
|
grossPerformancePercentage: Big;
|
||||||
|
currentValue: Big;
|
||||||
|
totalInvestment: Big;
|
||||||
|
}
|
@ -26,6 +26,7 @@ import {
|
|||||||
subDays
|
subDays
|
||||||
} from 'date-fns';
|
} from 'date-fns';
|
||||||
import { flatten } from 'lodash';
|
import { flatten } from 'lodash';
|
||||||
|
import { CurrentPositions } from '@ghostfolio/api/app/core/interfaces/current-positions.interface';
|
||||||
|
|
||||||
export class PortfolioCalculator {
|
export class PortfolioCalculator {
|
||||||
private transactionPoints: TransactionPoint[];
|
private transactionPoints: TransactionPoint[];
|
||||||
@ -111,14 +112,7 @@ export class PortfolioCalculator {
|
|||||||
this.transactionPoints = transactionPoints;
|
this.transactionPoints = transactionPoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getCurrentPositions(start: Date): Promise<{
|
public async getCurrentPositions(start: Date): Promise<CurrentPositions> {
|
||||||
hasErrors: boolean;
|
|
||||||
positions: TimelinePosition[];
|
|
||||||
grossPerformance: Big;
|
|
||||||
grossPerformancePercentage: Big;
|
|
||||||
currentValue: Big;
|
|
||||||
totalInvestment: Big;
|
|
||||||
}> {
|
|
||||||
if (!this.transactionPoints?.length) {
|
if (!this.transactionPoints?.length) {
|
||||||
return {
|
return {
|
||||||
hasErrors: false,
|
hasErrors: false,
|
||||||
|
@ -4,18 +4,14 @@ import { PortfolioOrder } from '@ghostfolio/api/app/core/interfaces/portfolio-or
|
|||||||
import { TimelineSpecification } from '@ghostfolio/api/app/core/interfaces/timeline-specification.interface';
|
import { TimelineSpecification } from '@ghostfolio/api/app/core/interfaces/timeline-specification.interface';
|
||||||
import { PortfolioCalculator } from '@ghostfolio/api/app/core/portfolio-calculator';
|
import { PortfolioCalculator } from '@ghostfolio/api/app/core/portfolio-calculator';
|
||||||
import { OrderService } from '@ghostfolio/api/app/order/order.service';
|
import { OrderService } from '@ghostfolio/api/app/order/order.service';
|
||||||
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
|
||||||
import { UserService } from '@ghostfolio/api/app/user/user.service';
|
|
||||||
import { OrderType } from '@ghostfolio/api/models/order-type';
|
import { OrderType } from '@ghostfolio/api/models/order-type';
|
||||||
import { Portfolio } from '@ghostfolio/api/models/portfolio';
|
|
||||||
import { DataProviderService } from '@ghostfolio/api/services/data-provider.service';
|
import { DataProviderService } from '@ghostfolio/api/services/data-provider.service';
|
||||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
||||||
import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service';
|
import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service';
|
||||||
import { IOrder, Type } from '@ghostfolio/api/services/interfaces/interfaces';
|
import { Type } from '@ghostfolio/api/services/interfaces/interfaces';
|
||||||
import { RulesService } from '@ghostfolio/api/services/rules.service';
|
import { RulesService } from '@ghostfolio/api/services/rules.service';
|
||||||
import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper';
|
import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper';
|
||||||
import {
|
import {
|
||||||
PortfolioItem,
|
|
||||||
PortfolioOverview,
|
PortfolioOverview,
|
||||||
PortfolioPerformance,
|
PortfolioPerformance,
|
||||||
PortfolioPosition,
|
PortfolioPosition,
|
||||||
@ -30,11 +26,10 @@ import {
|
|||||||
} from '@ghostfolio/common/types';
|
} from '@ghostfolio/common/types';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { REQUEST } from '@nestjs/core';
|
import { REQUEST } from '@nestjs/core';
|
||||||
import { DataSource, Currency, Type as TypeOfOrder } from '@prisma/client';
|
import { Currency, DataSource, Type as TypeOfOrder } from '@prisma/client';
|
||||||
import Big from 'big.js';
|
import Big from 'big.js';
|
||||||
import {
|
import {
|
||||||
add,
|
add,
|
||||||
addMonths,
|
|
||||||
endOfToday,
|
endOfToday,
|
||||||
format,
|
format,
|
||||||
getDate,
|
getDate,
|
||||||
@ -42,7 +37,6 @@ import {
|
|||||||
getYear,
|
getYear,
|
||||||
isAfter,
|
isAfter,
|
||||||
isBefore,
|
isBefore,
|
||||||
isSameDay,
|
|
||||||
max,
|
max,
|
||||||
parse,
|
parse,
|
||||||
parseISO,
|
parseISO,
|
||||||
@ -54,7 +48,6 @@ import {
|
|||||||
subYears
|
subYears
|
||||||
} from 'date-fns';
|
} from 'date-fns';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import * as roundTo from 'round-to';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
HistoricalDataItem,
|
HistoricalDataItem,
|
||||||
@ -83,69 +76,11 @@ export class PortfolioService {
|
|||||||
private readonly exchangeRateDataService: ExchangeRateDataService,
|
private readonly exchangeRateDataService: ExchangeRateDataService,
|
||||||
private readonly impersonationService: ImpersonationService,
|
private readonly impersonationService: ImpersonationService,
|
||||||
private readonly orderService: OrderService,
|
private readonly orderService: OrderService,
|
||||||
private readonly redisCacheService: RedisCacheService,
|
|
||||||
@Inject(REQUEST) private readonly request: RequestWithUser,
|
@Inject(REQUEST) private readonly request: RequestWithUser,
|
||||||
private readonly rulesService: RulesService,
|
private readonly rulesService: RulesService,
|
||||||
private readonly userService: UserService,
|
|
||||||
private readonly symbolProfileService: SymbolProfileService
|
private readonly symbolProfileService: SymbolProfileService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async createPortfolio(aUserId: string): Promise<Portfolio> {
|
|
||||||
let portfolio: Portfolio;
|
|
||||||
const stringifiedPortfolio = await this.redisCacheService.get(
|
|
||||||
`${aUserId}.portfolio`
|
|
||||||
);
|
|
||||||
|
|
||||||
const user = await this.userService.user({ id: aUserId });
|
|
||||||
|
|
||||||
if (stringifiedPortfolio) {
|
|
||||||
// Get portfolio from redis
|
|
||||||
const {
|
|
||||||
orders,
|
|
||||||
portfolioItems
|
|
||||||
}: { orders: IOrder[]; portfolioItems: PortfolioItem[] } =
|
|
||||||
JSON.parse(stringifiedPortfolio);
|
|
||||||
|
|
||||||
portfolio = new Portfolio(
|
|
||||||
this.accountService,
|
|
||||||
this.dataProviderService,
|
|
||||||
this.exchangeRateDataService,
|
|
||||||
this.rulesService
|
|
||||||
).createFromData({ orders, portfolioItems, user });
|
|
||||||
} else {
|
|
||||||
// Get portfolio from database
|
|
||||||
const orders = await this.getOrders(aUserId);
|
|
||||||
|
|
||||||
portfolio = new Portfolio(
|
|
||||||
this.accountService,
|
|
||||||
this.dataProviderService,
|
|
||||||
this.exchangeRateDataService,
|
|
||||||
this.rulesService
|
|
||||||
);
|
|
||||||
portfolio.setUser(user);
|
|
||||||
await portfolio.setOrders(orders);
|
|
||||||
|
|
||||||
// Cache data for the next time...
|
|
||||||
const portfolioData = {
|
|
||||||
orders: portfolio.getOrders(),
|
|
||||||
portfolioItems: portfolio.getPortfolioItems()
|
|
||||||
};
|
|
||||||
|
|
||||||
await this.redisCacheService.set(
|
|
||||||
`${aUserId}.portfolio`,
|
|
||||||
JSON.stringify(portfolioData)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enrich portfolio with current data
|
|
||||||
await portfolio.addCurrentPortfolioItems();
|
|
||||||
|
|
||||||
// Enrich portfolio with future data
|
|
||||||
await portfolio.addFuturePortfolioItems();
|
|
||||||
|
|
||||||
return portfolio;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getInvestments(
|
public async getInvestments(
|
||||||
aImpersonationId: string
|
aImpersonationId: string
|
||||||
): Promise<InvestmentItem[]> {
|
): Promise<InvestmentItem[]> {
|
||||||
@ -603,35 +538,49 @@ export class PortfolioService {
|
|||||||
|
|
||||||
public async getReport(impersonationId: string): Promise<PortfolioReport> {
|
public async getReport(impersonationId: string): Promise<PortfolioReport> {
|
||||||
const userId = await this.getUserId(impersonationId);
|
const userId = await this.getUserId(impersonationId);
|
||||||
const portfolio = await this.createPortfolio(userId);
|
const baseCurrency = this.request.user.Settings.currency;
|
||||||
|
|
||||||
const details = await portfolio.getDetails();
|
const { transactionPoints, orders } = await this.getTransactionPoints(
|
||||||
const { orders } = await this.getTransactionPoints(userId);
|
userId
|
||||||
|
);
|
||||||
|
|
||||||
if (isEmpty(details)) {
|
if (isEmpty(orders)) {
|
||||||
return {
|
return {
|
||||||
rules: {}
|
rules: {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const fees = this.getFees(orders);
|
const portfolioCalculator = new PortfolioCalculator(
|
||||||
|
this.currentRateService,
|
||||||
|
this.request.user.Settings.currency
|
||||||
|
);
|
||||||
|
portfolioCalculator.setTransactionPoints(transactionPoints);
|
||||||
|
|
||||||
const baseCurrency = this.request.user.Settings.currency;
|
const portfolioStart = parseDate(transactionPoints[0].date);
|
||||||
|
const currentPositions = await portfolioCalculator.getCurrentPositions(
|
||||||
|
portfolioStart
|
||||||
|
);
|
||||||
|
|
||||||
|
const portfolioItemsNow: { [symbol: string]: TimelinePosition } = {};
|
||||||
|
for (const position of currentPositions.positions) {
|
||||||
|
portfolioItemsNow[position.symbol] = position;
|
||||||
|
}
|
||||||
|
const accounts = this.getAccounts(orders, portfolioItemsNow, baseCurrency);
|
||||||
return {
|
return {
|
||||||
rules: {
|
rules: {
|
||||||
accountClusterRisk: await this.rulesService.evaluate(
|
accountClusterRisk: await this.rulesService.evaluate(
|
||||||
[
|
[
|
||||||
new AccountClusterRiskInitialInvestment(
|
new AccountClusterRiskInitialInvestment(
|
||||||
this.exchangeRateDataService,
|
this.exchangeRateDataService,
|
||||||
details
|
accounts
|
||||||
),
|
),
|
||||||
new AccountClusterRiskCurrentInvestment(
|
new AccountClusterRiskCurrentInvestment(
|
||||||
this.exchangeRateDataService,
|
this.exchangeRateDataService,
|
||||||
details
|
accounts
|
||||||
),
|
),
|
||||||
new AccountClusterRiskSingleAccount(
|
new AccountClusterRiskSingleAccount(
|
||||||
this.exchangeRateDataService,
|
this.exchangeRateDataService,
|
||||||
details
|
accounts
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
{ baseCurrency }
|
{ baseCurrency }
|
||||||
@ -640,19 +589,19 @@ export class PortfolioService {
|
|||||||
[
|
[
|
||||||
new CurrencyClusterRiskBaseCurrencyInitialInvestment(
|
new CurrencyClusterRiskBaseCurrencyInitialInvestment(
|
||||||
this.exchangeRateDataService,
|
this.exchangeRateDataService,
|
||||||
details
|
currentPositions
|
||||||
),
|
),
|
||||||
new CurrencyClusterRiskBaseCurrencyCurrentInvestment(
|
new CurrencyClusterRiskBaseCurrencyCurrentInvestment(
|
||||||
this.exchangeRateDataService,
|
this.exchangeRateDataService,
|
||||||
details
|
currentPositions
|
||||||
),
|
),
|
||||||
new CurrencyClusterRiskInitialInvestment(
|
new CurrencyClusterRiskInitialInvestment(
|
||||||
this.exchangeRateDataService,
|
this.exchangeRateDataService,
|
||||||
details
|
currentPositions
|
||||||
),
|
),
|
||||||
new CurrencyClusterRiskCurrentInvestment(
|
new CurrencyClusterRiskCurrentInvestment(
|
||||||
this.exchangeRateDataService,
|
this.exchangeRateDataService,
|
||||||
details
|
currentPositions
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
{ baseCurrency }
|
{ baseCurrency }
|
||||||
@ -661,8 +610,8 @@ export class PortfolioService {
|
|||||||
[
|
[
|
||||||
new FeeRatioInitialInvestment(
|
new FeeRatioInitialInvestment(
|
||||||
this.exchangeRateDataService,
|
this.exchangeRateDataService,
|
||||||
details,
|
currentPositions.totalInvestment.toNumber(),
|
||||||
fees
|
this.getFees(orders)
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
{ baseCurrency }
|
{ baseCurrency }
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import { groupBy } from '@ghostfolio/common/helper';
|
import { groupBy } from '@ghostfolio/common/helper';
|
||||||
import { PortfolioPosition } from '@ghostfolio/common/interfaces';
|
import {
|
||||||
|
PortfolioPosition,
|
||||||
|
TimelinePosition
|
||||||
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { Currency } from '@prisma/client';
|
import { Currency } from '@prisma/client';
|
||||||
|
|
||||||
import { ExchangeRateDataService } from '../services/exchange-rate-data.service';
|
import { ExchangeRateDataService } from '../services/exchange-rate-data.service';
|
||||||
@ -30,30 +33,30 @@ export abstract class Rule<T extends RuleSettings> implements RuleInterface<T> {
|
|||||||
return this.name;
|
return this.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public groupPositionsByAttribute(
|
public groupCurrentPositionsByAttribute(
|
||||||
aPositions: { [symbol: string]: PortfolioPosition },
|
positions: TimelinePosition[],
|
||||||
aAttribute: keyof PortfolioPosition,
|
attribute: keyof TimelinePosition,
|
||||||
aBaseCurrency: Currency
|
baseCurrency: Currency
|
||||||
) {
|
) {
|
||||||
return Array.from(
|
return Array.from(groupBy(attribute, positions).entries()).map(
|
||||||
groupBy(aAttribute, Object.values(aPositions)).entries()
|
([attributeValue, objs]) => ({
|
||||||
).map(([attributeValue, objs]) => ({
|
|
||||||
groupKey: attributeValue,
|
groupKey: attributeValue,
|
||||||
investment: objs.reduce(
|
investment: objs.reduce(
|
||||||
(previousValue, currentValue) =>
|
(previousValue, currentValue) =>
|
||||||
previousValue + currentValue.investment,
|
previousValue + currentValue.investment.toNumber(),
|
||||||
0
|
0
|
||||||
),
|
),
|
||||||
value: objs.reduce(
|
value: objs.reduce(
|
||||||
(previousValue, currentValue) =>
|
(previousValue, currentValue) =>
|
||||||
previousValue +
|
previousValue +
|
||||||
this.exchangeRateDataService.toCurrency(
|
this.exchangeRateDataService.toCurrency(
|
||||||
currentValue.quantity * currentValue.marketPrice,
|
currentValue.quantity.mul(currentValue.marketPrice).toNumber(),
|
||||||
currentValue.currency,
|
currentValue.currency,
|
||||||
aBaseCurrency
|
baseCurrency
|
||||||
),
|
),
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,9 @@ import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.in
|
|||||||
export class AccountClusterRiskCurrentInvestment extends Rule<Settings> {
|
export class AccountClusterRiskCurrentInvestment extends Rule<Settings> {
|
||||||
public constructor(
|
public constructor(
|
||||||
protected exchangeRateDataService: ExchangeRateDataService,
|
protected exchangeRateDataService: ExchangeRateDataService,
|
||||||
private positions: { [symbol: string]: PortfolioPosition }
|
private accounts: {
|
||||||
|
[account: string]: { current: number; original: number };
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
super(exchangeRateDataService, {
|
super(exchangeRateDataService, {
|
||||||
name: 'Current Investment'
|
name: 'Current Investment'
|
||||||
@ -22,18 +24,12 @@ export class AccountClusterRiskCurrentInvestment extends Rule<Settings> {
|
|||||||
};
|
};
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
Object.values(this.positions).forEach((position) => {
|
for (const account of Object.keys(this.accounts)) {
|
||||||
for (const [account, { current }] of Object.entries(position.accounts)) {
|
|
||||||
if (accounts[account]?.investment) {
|
|
||||||
accounts[account].investment += current;
|
|
||||||
} else {
|
|
||||||
accounts[account] = {
|
accounts[account] = {
|
||||||
investment: current,
|
name: account,
|
||||||
name: account
|
investment: this.accounts[account].current
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let maxItem;
|
let maxItem;
|
||||||
let totalInvestment = 0;
|
let totalInvestment = 0;
|
||||||
|
@ -8,7 +8,9 @@ import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.in
|
|||||||
export class AccountClusterRiskInitialInvestment extends Rule<Settings> {
|
export class AccountClusterRiskInitialInvestment extends Rule<Settings> {
|
||||||
public constructor(
|
public constructor(
|
||||||
protected exchangeRateDataService: ExchangeRateDataService,
|
protected exchangeRateDataService: ExchangeRateDataService,
|
||||||
private positions: { [symbol: string]: PortfolioPosition }
|
private accounts: {
|
||||||
|
[account: string]: { current: number; original: number };
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
super(exchangeRateDataService, {
|
super(exchangeRateDataService, {
|
||||||
name: 'Initial Investment'
|
name: 'Initial Investment'
|
||||||
@ -22,18 +24,12 @@ export class AccountClusterRiskInitialInvestment extends Rule<Settings> {
|
|||||||
};
|
};
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
Object.values(this.positions).forEach((position) => {
|
for (const account of Object.keys(this.accounts)) {
|
||||||
for (const [account, { original }] of Object.entries(position.accounts)) {
|
|
||||||
if (platforms[account]?.investment) {
|
|
||||||
platforms[account].investment += original;
|
|
||||||
} else {
|
|
||||||
platforms[account] = {
|
platforms[account] = {
|
||||||
investment: original,
|
name: account,
|
||||||
name: account
|
investment: this.accounts[account].original
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let maxItem;
|
let maxItem;
|
||||||
let totalInvestment = 0;
|
let totalInvestment = 0;
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { PortfolioPosition } from '@ghostfolio/common/interfaces';
|
|
||||||
import { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
|
import { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
|
||||||
|
|
||||||
import { Rule } from '../../rule';
|
import { Rule } from '../../rule';
|
||||||
@ -8,7 +7,9 @@ import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.in
|
|||||||
export class AccountClusterRiskSingleAccount extends Rule<RuleSettings> {
|
export class AccountClusterRiskSingleAccount extends Rule<RuleSettings> {
|
||||||
public constructor(
|
public constructor(
|
||||||
protected exchangeRateDataService: ExchangeRateDataService,
|
protected exchangeRateDataService: ExchangeRateDataService,
|
||||||
private positions: { [symbol: string]: PortfolioPosition }
|
private accounts: {
|
||||||
|
[account: string]: { current: number; original: number };
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
super(exchangeRateDataService, {
|
super(exchangeRateDataService, {
|
||||||
name: 'Single Account'
|
name: 'Single Account'
|
||||||
@ -16,15 +17,7 @@ export class AccountClusterRiskSingleAccount extends Rule<RuleSettings> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public evaluate() {
|
public evaluate() {
|
||||||
const accounts: string[] = [];
|
const accounts: string[] = Object.keys(this.accounts);
|
||||||
|
|
||||||
Object.values(this.positions).forEach((position) => {
|
|
||||||
for (const [account] of Object.entries(position.accounts)) {
|
|
||||||
if (!accounts.includes(account)) {
|
|
||||||
accounts.push(account);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (accounts.length === 1) {
|
if (accounts.length === 1) {
|
||||||
return {
|
return {
|
||||||
|
@ -5,11 +5,12 @@ import { PortfolioPosition } from '@ghostfolio/common/interfaces';
|
|||||||
import { Rule } from '../../rule';
|
import { Rule } from '../../rule';
|
||||||
import { UserSettings } from '@ghostfolio/api/models/interfaces/user-settings.interface';
|
import { UserSettings } from '@ghostfolio/api/models/interfaces/user-settings.interface';
|
||||||
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
|
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
|
||||||
|
import { CurrentPositions } from '@ghostfolio/api/app/core/interfaces/current-positions.interface';
|
||||||
|
|
||||||
export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule<Settings> {
|
export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule<Settings> {
|
||||||
public constructor(
|
public constructor(
|
||||||
protected exchangeRateDataService: ExchangeRateDataService,
|
protected exchangeRateDataService: ExchangeRateDataService,
|
||||||
private positions: { [symbol: string]: PortfolioPosition }
|
private currentPositions: CurrentPositions
|
||||||
) {
|
) {
|
||||||
super(exchangeRateDataService, {
|
super(exchangeRateDataService, {
|
||||||
name: 'Current Investment: Base Currency'
|
name: 'Current Investment: Base Currency'
|
||||||
@ -17,8 +18,8 @@ export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule<Setti
|
|||||||
}
|
}
|
||||||
|
|
||||||
public evaluate(ruleSettings: Settings) {
|
public evaluate(ruleSettings: Settings) {
|
||||||
const positionsGroupedByCurrency = this.groupPositionsByAttribute(
|
const positionsGroupedByCurrency = this.groupCurrentPositionsByAttribute(
|
||||||
this.positions,
|
this.currentPositions.positions,
|
||||||
'currency',
|
'currency',
|
||||||
ruleSettings.baseCurrency
|
ruleSettings.baseCurrency
|
||||||
);
|
);
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import { Currency } from '@prisma/client';
|
import { Currency } from '@prisma/client';
|
||||||
import { PortfolioPosition } from '@ghostfolio/common/interfaces';
|
|
||||||
import { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
|
import { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
|
||||||
|
|
||||||
import { Rule } from '../../rule';
|
import { Rule } from '../../rule';
|
||||||
import { UserSettings } from '@ghostfolio/api/models/interfaces/user-settings.interface';
|
import { UserSettings } from '@ghostfolio/api/models/interfaces/user-settings.interface';
|
||||||
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
|
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
|
||||||
|
import { CurrentPositions } from '@ghostfolio/api/app/core/interfaces/current-positions.interface';
|
||||||
|
|
||||||
export class CurrencyClusterRiskBaseCurrencyInitialInvestment extends Rule<Settings> {
|
export class CurrencyClusterRiskBaseCurrencyInitialInvestment extends Rule<Settings> {
|
||||||
public constructor(
|
public constructor(
|
||||||
protected exchangeRateDataService: ExchangeRateDataService,
|
protected exchangeRateDataService: ExchangeRateDataService,
|
||||||
private positions: { [symbol: string]: PortfolioPosition }
|
private currentPositions: CurrentPositions
|
||||||
) {
|
) {
|
||||||
super(exchangeRateDataService, {
|
super(exchangeRateDataService, {
|
||||||
name: 'Initial Investment: Base Currency'
|
name: 'Initial Investment: Base Currency'
|
||||||
@ -17,8 +17,8 @@ export class CurrencyClusterRiskBaseCurrencyInitialInvestment extends Rule<Setti
|
|||||||
}
|
}
|
||||||
|
|
||||||
public evaluate(ruleSettings: Settings) {
|
public evaluate(ruleSettings: Settings) {
|
||||||
const positionsGroupedByCurrency = this.groupPositionsByAttribute(
|
const positionsGroupedByCurrency = this.groupCurrentPositionsByAttribute(
|
||||||
this.positions,
|
this.currentPositions.positions,
|
||||||
'currency',
|
'currency',
|
||||||
ruleSettings.baseCurrency
|
ruleSettings.baseCurrency
|
||||||
);
|
);
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import { PortfolioPosition } from '@ghostfolio/common/interfaces';
|
|
||||||
import { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
|
import { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
|
||||||
|
|
||||||
import { Rule } from '../../rule';
|
import { Rule } from '../../rule';
|
||||||
import { UserSettings } from '@ghostfolio/api/models/interfaces/user-settings.interface';
|
import { UserSettings } from '@ghostfolio/api/models/interfaces/user-settings.interface';
|
||||||
import { Currency } from '@prisma/client';
|
import { Currency } from '@prisma/client';
|
||||||
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
|
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
|
||||||
|
import { CurrentPositions } from '@ghostfolio/api/app/core/interfaces/current-positions.interface';
|
||||||
|
|
||||||
export class CurrencyClusterRiskCurrentInvestment extends Rule<Settings> {
|
export class CurrencyClusterRiskCurrentInvestment extends Rule<Settings> {
|
||||||
public constructor(
|
public constructor(
|
||||||
public exchangeRateDataService: ExchangeRateDataService,
|
public exchangeRateDataService: ExchangeRateDataService,
|
||||||
private positions: { [symbol: string]: PortfolioPosition }
|
private currentPositions: CurrentPositions
|
||||||
) {
|
) {
|
||||||
super(exchangeRateDataService, {
|
super(exchangeRateDataService, {
|
||||||
name: 'Current Investment'
|
name: 'Current Investment'
|
||||||
@ -17,8 +17,8 @@ export class CurrencyClusterRiskCurrentInvestment extends Rule<Settings> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public evaluate(ruleSettings: Settings) {
|
public evaluate(ruleSettings: Settings) {
|
||||||
const positionsGroupedByCurrency = this.groupPositionsByAttribute(
|
const positionsGroupedByCurrency = this.groupCurrentPositionsByAttribute(
|
||||||
this.positions,
|
this.currentPositions.positions,
|
||||||
'currency',
|
'currency',
|
||||||
ruleSettings.baseCurrency
|
ruleSettings.baseCurrency
|
||||||
);
|
);
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import { Currency } from '@prisma/client';
|
import { Currency } from '@prisma/client';
|
||||||
import { PortfolioPosition } from '@ghostfolio/common/interfaces';
|
|
||||||
import { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
|
import { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
|
||||||
|
|
||||||
import { Rule } from '../../rule';
|
import { Rule } from '../../rule';
|
||||||
import { UserSettings } from '@ghostfolio/api/models/interfaces/user-settings.interface';
|
import { UserSettings } from '@ghostfolio/api/models/interfaces/user-settings.interface';
|
||||||
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
|
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
|
||||||
|
import { CurrentPositions } from '@ghostfolio/api/app/core/interfaces/current-positions.interface';
|
||||||
|
|
||||||
export class CurrencyClusterRiskInitialInvestment extends Rule<Settings> {
|
export class CurrencyClusterRiskInitialInvestment extends Rule<Settings> {
|
||||||
public constructor(
|
public constructor(
|
||||||
protected exchangeRateDataService: ExchangeRateDataService,
|
protected exchangeRateDataService: ExchangeRateDataService,
|
||||||
private positions: { [symbol: string]: PortfolioPosition }
|
private currentPositions: CurrentPositions
|
||||||
) {
|
) {
|
||||||
super(exchangeRateDataService, {
|
super(exchangeRateDataService, {
|
||||||
name: 'Initial Investment'
|
name: 'Initial Investment'
|
||||||
@ -17,8 +17,8 @@ export class CurrencyClusterRiskInitialInvestment extends Rule<Settings> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public evaluate(ruleSettings: Settings) {
|
public evaluate(ruleSettings: Settings) {
|
||||||
const positionsGroupedByCurrency = this.groupPositionsByAttribute(
|
const positionsGroupedByCurrency = this.groupCurrentPositionsByAttribute(
|
||||||
this.positions,
|
this.currentPositions.positions,
|
||||||
'currency',
|
'currency',
|
||||||
ruleSettings.baseCurrency
|
ruleSettings.baseCurrency
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { Currency } from '@prisma/client';
|
import { Currency } from '@prisma/client';
|
||||||
import { PortfolioPosition } from '@ghostfolio/common/interfaces';
|
|
||||||
import { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
|
import { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
|
||||||
|
|
||||||
import { Rule } from '../../rule';
|
import { Rule } from '../../rule';
|
||||||
@ -9,7 +8,7 @@ import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.in
|
|||||||
export class FeeRatioInitialInvestment extends Rule<Settings> {
|
export class FeeRatioInitialInvestment extends Rule<Settings> {
|
||||||
public constructor(
|
public constructor(
|
||||||
protected exchangeRateDataService: ExchangeRateDataService,
|
protected exchangeRateDataService: ExchangeRateDataService,
|
||||||
private positions: { [symbol: string]: PortfolioPosition },
|
private totalInvestment: number,
|
||||||
private fees: number
|
private fees: number
|
||||||
) {
|
) {
|
||||||
super(exchangeRateDataService, {
|
super(exchangeRateDataService, {
|
||||||
@ -18,20 +17,7 @@ export class FeeRatioInitialInvestment extends Rule<Settings> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public evaluate(ruleSettings: Settings) {
|
public evaluate(ruleSettings: Settings) {
|
||||||
const positionsGroupedByCurrency = this.groupPositionsByAttribute(
|
const feeRatio = this.fees / this.totalInvestment;
|
||||||
this.positions,
|
|
||||||
'currency',
|
|
||||||
ruleSettings.baseCurrency
|
|
||||||
);
|
|
||||||
|
|
||||||
let totalInvestment = 0;
|
|
||||||
|
|
||||||
positionsGroupedByCurrency.forEach((groupItem) => {
|
|
||||||
// Calculate total investment
|
|
||||||
totalInvestment += groupItem.investment;
|
|
||||||
});
|
|
||||||
|
|
||||||
const feeRatio = this.fees / totalInvestment;
|
|
||||||
|
|
||||||
if (feeRatio > ruleSettings.threshold) {
|
if (feeRatio > ruleSettings.threshold) {
|
||||||
return {
|
return {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user