Browse Source

Add netPerformancePercentageWithCurrencyEffectMap and netPerformanceWithCurrencyEffectMap

pull/3393/head
Thomas Kaul 1 year ago
parent
commit
30e9cc036b
  1. 10
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  2. 90
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts
  3. 23
      apps/api/src/app/portfolio/portfolio.service.ts
  4. 10
      apps/api/src/app/user/user.service.ts
  5. 16
      libs/common/src/lib/class-transformer.ts
  6. 4
      libs/common/src/lib/interfaces/portfolio-position.interface.ts
  7. 4
      libs/common/src/lib/interfaces/symbol-metrics.interface.ts
  8. 12
      libs/common/src/lib/models/timeline-position.ts

10
apps/api/src/app/portfolio/calculator/portfolio-calculator.ts

@ -136,7 +136,7 @@ export abstract class PortfolioCalculator {
}); });
this.redisCacheService = redisCacheService; this.redisCacheService = redisCacheService;
this.useCache = false; // TODO: useCache this.useCache = useCache;
this.userId = userId; this.userId = userId;
const { endDate, startDate } = getIntervalFromDateRange( const { endDate, startDate } = getIntervalFromDateRange(
@ -394,9 +394,11 @@ export abstract class PortfolioCalculator {
netPerformance, netPerformance,
netPerformancePercentage, netPerformancePercentage,
netPerformancePercentageWithCurrencyEffect, netPerformancePercentageWithCurrencyEffect,
netPerformancePercentageWithCurrencyEffectMap,
netPerformanceValues, netPerformanceValues,
netPerformanceValuesWithCurrencyEffect, netPerformanceValuesWithCurrencyEffect,
netPerformanceWithCurrencyEffect, netPerformanceWithCurrencyEffect,
netPerformanceWithCurrencyEffectMap,
timeWeightedInvestment, timeWeightedInvestment,
timeWeightedInvestmentValues, timeWeightedInvestmentValues,
timeWeightedInvestmentValuesWithCurrencyEffect, timeWeightedInvestmentValuesWithCurrencyEffect,
@ -468,9 +470,15 @@ export abstract class PortfolioCalculator {
netPerformancePercentageWithCurrencyEffect: !hasErrors netPerformancePercentageWithCurrencyEffect: !hasErrors
? (netPerformancePercentageWithCurrencyEffect ?? null) ? (netPerformancePercentageWithCurrencyEffect ?? null)
: null, : null,
netPerformancePercentageWithCurrencyEffectMap: !hasErrors
? (netPerformancePercentageWithCurrencyEffectMap ?? null)
: null,
netPerformanceWithCurrencyEffect: !hasErrors netPerformanceWithCurrencyEffect: !hasErrors
? (netPerformanceWithCurrencyEffect ?? null) ? (netPerformanceWithCurrencyEffect ?? null)
: null, : null,
netPerformanceWithCurrencyEffectMap: !hasErrors
? (netPerformanceWithCurrencyEffectMap ?? null)
: null,
quantity: item.quantity, quantity: item.quantity,
symbol: item.symbol, symbol: item.symbol,
tags: item.tags, tags: item.tags,

90
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts

@ -1,12 +1,14 @@
import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator'; import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator';
import { PortfolioOrderItem } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order-item.interface'; import { PortfolioOrderItem } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order-item.interface';
import { getFactor } from '@ghostfolio/api/helper/portfolio.helper'; import { getFactor } from '@ghostfolio/api/helper/portfolio.helper';
import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper';
import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { DATE_FORMAT } from '@ghostfolio/common/helper';
import { import {
AssetProfileIdentifier, AssetProfileIdentifier,
SymbolMetrics SymbolMetrics
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models'; import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models';
import { DateRange } from '@ghostfolio/common/types';
import { Logger } from '@nestjs/common'; import { Logger } from '@nestjs/common';
import { Big } from 'big.js'; import { Big } from 'big.js';
@ -14,6 +16,7 @@ import {
addDays, addDays,
addMilliseconds, addMilliseconds,
differenceInDays, differenceInDays,
eachDayOfInterval,
format, format,
isBefore isBefore
} from 'date-fns'; } from 'date-fns';
@ -236,9 +239,11 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
netPerformance: new Big(0), netPerformance: new Big(0),
netPerformancePercentage: new Big(0), netPerformancePercentage: new Big(0),
netPerformancePercentageWithCurrencyEffect: new Big(0), netPerformancePercentageWithCurrencyEffect: new Big(0),
netPerformancePercentageWithCurrencyEffectMap: {},
netPerformanceValues: {}, netPerformanceValues: {},
netPerformanceValuesWithCurrencyEffect: {}, netPerformanceValuesWithCurrencyEffect: {},
netPerformanceWithCurrencyEffect: new Big(0), netPerformanceWithCurrencyEffect: new Big(0),
netPerformanceWithCurrencyEffectMap: {},
timeWeightedInvestment: new Big(0), timeWeightedInvestment: new Big(0),
timeWeightedInvestmentValues: {}, timeWeightedInvestmentValues: {},
timeWeightedInvestmentValuesWithCurrencyEffect: {}, timeWeightedInvestmentValuesWithCurrencyEffect: {},
@ -286,6 +291,8 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
netPerformance: new Big(0), netPerformance: new Big(0),
netPerformancePercentage: new Big(0), netPerformancePercentage: new Big(0),
netPerformancePercentageWithCurrencyEffect: new Big(0), netPerformancePercentageWithCurrencyEffect: new Big(0),
netPerformancePercentageWithCurrencyEffectMap: {},
netPerformanceWithCurrencyEffectMap: {},
netPerformanceValues: {}, netPerformanceValues: {},
netPerformanceValuesWithCurrencyEffect: {}, netPerformanceValuesWithCurrencyEffect: {},
netPerformanceWithCurrencyEffect: new Big(0), netPerformanceWithCurrencyEffect: new Big(0),
@ -841,6 +848,87 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
) )
: new Big(0); : new Big(0);
const netPerformancePercentageWithCurrencyEffectMap: {
[key: DateRange]: Big;
} = {};
const netPerformanceWithCurrencyEffectMap: {
[key: DateRange]: Big;
} = {};
for (const dateRange of <DateRange[]>[
'1d',
'1y',
'5y',
'max',
'mtd',
'wtd',
'ytd'
// TODO: '2024', '2023', '2022', etc.
]) {
// TODO: getIntervalFromDateRange(dateRange, start)
let { endDate, startDate } = getIntervalFromDateRange(dateRange);
if (isBefore(startDate, start)) {
startDate = addDays(start, 1);
}
let average = new Big(0);
const currentValuesAtStartDateWithCurrencyEffect =
currentValuesWithCurrencyEffect[format(startDate, DATE_FORMAT)];
const investmentValuesAccumulatedAtStartDateWithCurrencyEffect =
investmentValuesAccumulatedWithCurrencyEffect[
format(startDate, DATE_FORMAT)
];
// TODO: Rename?
const grossPerformanceAtStartDateWithCurrencyEffect2 =
currentValuesAtStartDateWithCurrencyEffect.minus(
investmentValuesAccumulatedAtStartDateWithCurrencyEffect
);
const dates = eachDayOfInterval({
end: endDate,
start: startDate
}).map((date) => {
return format(date, DATE_FORMAT);
});
let dayCount = 0;
for (const date of dates) {
if (
investmentValuesAccumulatedWithCurrencyEffect[date] instanceof Big
) {
average = average.add(
investmentValuesAccumulatedWithCurrencyEffect[date].add(
grossPerformanceAtStartDateWithCurrencyEffect2
)
);
dayCount++;
}
}
average = average.div(dayCount);
netPerformanceWithCurrencyEffectMap[dateRange] = average.gt(0)
? netPerformanceValuesWithCurrencyEffect[
format(endDate, DATE_FORMAT)
].minus(
netPerformanceValuesWithCurrencyEffect[
format(startDate, DATE_FORMAT)
]
)
: new Big(0);
netPerformancePercentageWithCurrencyEffectMap[dateRange] = average.gt(0)
? netPerformanceWithCurrencyEffectMap[dateRange].div(average)
: new Big(0);
}
if (PortfolioCalculator.ENABLE_LOGGING) { if (PortfolioCalculator.ENABLE_LOGGING) {
console.log( console.log(
` `
@ -893,8 +981,10 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
investmentValuesWithCurrencyEffect, investmentValuesWithCurrencyEffect,
netPerformancePercentage, netPerformancePercentage,
netPerformancePercentageWithCurrencyEffect, netPerformancePercentageWithCurrencyEffect,
netPerformancePercentageWithCurrencyEffectMap,
netPerformanceValues, netPerformanceValues,
netPerformanceValuesWithCurrencyEffect, netPerformanceValuesWithCurrencyEffect,
netPerformanceWithCurrencyEffectMap,
timeWeightedInvestmentValues, timeWeightedInvestmentValues,
timeWeightedInvestmentValuesWithCurrencyEffect, timeWeightedInvestmentValuesWithCurrencyEffect,
totalAccountBalanceInBaseCurrency, totalAccountBalanceInBaseCurrency,

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

@ -346,7 +346,7 @@ export class PortfolioService {
userId, userId,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: userCurrency, currency: userCurrency,
hasFilters: true, // disable cache hasFilters: filters?.length > 0, // TODO
isExperimentalFeatures: isExperimentalFeatures:
this.request.user?.Settings.settings.isExperimentalFeatures this.request.user?.Settings.settings.isExperimentalFeatures
}); });
@ -412,10 +412,8 @@ export class PortfolioService {
console.timeEnd('-- PortfolioService.getDetails - 3'); console.timeEnd('-- PortfolioService.getDetails - 3');
console.time('-- PortfolioService.getDetails - 4'); console.time('-- PortfolioService.getDetails - 4');
const [dataProviderResponses, symbolProfiles] = await Promise.all([ const symbolProfiles =
this.dataProviderService.getQuotes({ user, items: dataGatheringItems }), await this.symbolProfileService.getSymbolProfiles(dataGatheringItems);
this.symbolProfileService.getSymbolProfiles(dataGatheringItems)
]);
const symbolProfileMap: { [symbol: string]: EnhancedSymbolProfile } = {}; const symbolProfileMap: { [symbol: string]: EnhancedSymbolProfile } = {};
for (const symbolProfile of symbolProfiles) { for (const symbolProfile of symbolProfiles) {
@ -442,8 +440,10 @@ export class PortfolioService {
marketPrice, marketPrice,
netPerformance, netPerformance,
netPerformancePercentage, netPerformancePercentage,
netPerformancePercentageWithCurrencyEffect, netPerformancePercentageWithCurrencyEffect, // TODO: Remove?
netPerformanceWithCurrencyEffect, netPerformancePercentageWithCurrencyEffectMap,
netPerformanceWithCurrencyEffect, // TODO: Remove?
netPerformanceWithCurrencyEffectMap,
quantity, quantity,
symbol, symbol,
tags, tags,
@ -463,7 +463,6 @@ export class PortfolioService {
} }
const assetProfile = symbolProfileMap[symbol]; const assetProfile = symbolProfileMap[symbol];
const dataProviderResponse = dataProviderResponses[symbol];
let markets: PortfolioPosition['markets']; let markets: PortfolioPosition['markets'];
let marketsAdvanced: PortfolioPosition['marketsAdvanced']; let marketsAdvanced: PortfolioPosition['marketsAdvanced'];
@ -510,14 +509,15 @@ export class PortfolioService {
} }
), ),
investment: investment.toNumber(), investment: investment.toNumber(),
marketState: dataProviderResponse?.marketState ?? 'delayed',
name: assetProfile.name, name: assetProfile.name,
netPerformance: netPerformance?.toNumber() ?? 0, netPerformance: netPerformance?.toNumber() ?? 0,
netPerformancePercent: netPerformancePercentage?.toNumber() ?? 0, netPerformancePercent: netPerformancePercentage?.toNumber() ?? 0,
netPerformancePercentWithCurrencyEffect: netPerformancePercentWithCurrencyEffect:
netPerformancePercentageWithCurrencyEffect?.toNumber() ?? 0, netPerformancePercentageWithCurrencyEffectMap?.[
dateRange
]?.toNumber() ?? 0,
netPerformanceWithCurrencyEffect: netPerformanceWithCurrencyEffect:
netPerformanceWithCurrencyEffect?.toNumber() ?? 0, netPerformanceWithCurrencyEffectMap?.[dateRange]?.toNumber() ?? 0,
quantity: quantity.toNumber(), quantity: quantity.toNumber(),
sectors: assetProfile.sectors, sectors: assetProfile.sectors,
url: assetProfile.url, url: assetProfile.url,
@ -1489,7 +1489,6 @@ export class PortfolioService {
holdings: [], holdings: [],
investment: balance, investment: balance,
marketPrice: 0, marketPrice: 0,
marketState: 'open',
name: currency, name: currency,
netPerformance: 0, netPerformance: 0,
netPerformancePercent: 0, netPerformancePercent: 0,

10
apps/api/src/app/user/user.service.ts

@ -1,7 +1,6 @@
import { OrderService } from '@ghostfolio/api/app/order/order.service'; import { OrderService } from '@ghostfolio/api/app/order/order.service';
import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service'; import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service';
import { environment } from '@ghostfolio/api/environments/environment'; import { environment } from '@ghostfolio/api/environments/environment';
import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.event';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service'; import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
@ -27,7 +26,6 @@ import {
import { UserWithSettings } from '@ghostfolio/common/types'; import { UserWithSettings } from '@ghostfolio/common/types';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { Prisma, Role, User } from '@prisma/client'; import { Prisma, Role, User } from '@prisma/client';
import { differenceInDays } from 'date-fns'; import { differenceInDays } from 'date-fns';
import { sortBy, without } from 'lodash'; import { sortBy, without } from 'lodash';
@ -40,7 +38,6 @@ export class UserService {
public constructor( public constructor(
private readonly configurationService: ConfigurationService, private readonly configurationService: ConfigurationService,
private readonly eventEmitter: EventEmitter2,
private readonly orderService: OrderService, private readonly orderService: OrderService,
private readonly prismaService: PrismaService, private readonly prismaService: PrismaService,
private readonly propertyService: PropertyService, private readonly propertyService: PropertyService,
@ -444,13 +441,6 @@ export class UserService {
} }
}); });
this.eventEmitter.emit(
PortfolioChangedEvent.getName(),
new PortfolioChangedEvent({
userId
})
);
return settings; return settings;
} }

16
libs/common/src/lib/class-transformer.ts

@ -1,5 +1,21 @@
import { Big } from 'big.js'; import { Big } from 'big.js';
export function transformToMapOfBig({
value
}: {
value: { [key: string]: string };
}): {
[key: string]: Big;
} {
const mapOfBig: { [key: string]: Big } = {};
for (const key in value) {
mapOfBig[key] = new Big(value[key]);
}
return mapOfBig;
}
export function transformToBig({ value }: { value: string }): Big { export function transformToBig({ value }: { value: string }): Big {
if (value === null) { if (value === null) {
return null; return null;

4
libs/common/src/lib/interfaces/portfolio-position.interface.ts

@ -1,6 +1,7 @@
import { Market, MarketAdvanced } from '@ghostfolio/common/types';
import { AssetClass, AssetSubClass, DataSource, Tag } from '@prisma/client'; import { AssetClass, AssetSubClass, DataSource, Tag } from '@prisma/client';
import { Market, MarketAdvanced, MarketState } from '../types';
import { Country } from './country.interface'; import { Country } from './country.interface';
import { Holding } from './holding.interface'; import { Holding } from './holding.interface';
import { Sector } from './sector.interface'; import { Sector } from './sector.interface';
@ -28,7 +29,6 @@ export interface PortfolioPosition {
marketPrice: number; marketPrice: number;
markets?: { [key in Market]: number }; markets?: { [key in Market]: number };
marketsAdvanced?: { [key in MarketAdvanced]: number }; marketsAdvanced?: { [key in MarketAdvanced]: number };
marketState: MarketState;
name: string; name: string;
netPerformance: number; netPerformance: number;
netPerformancePercent: number; netPerformancePercent: number;

4
libs/common/src/lib/interfaces/symbol-metrics.interface.ts

@ -1,3 +1,5 @@
import { DateRange } from '@ghostfolio/common/types';
import { Big } from 'big.js'; import { Big } from 'big.js';
export interface SymbolMetrics { export interface SymbolMetrics {
@ -27,11 +29,13 @@ export interface SymbolMetrics {
netPerformance: Big; netPerformance: Big;
netPerformancePercentage: Big; netPerformancePercentage: Big;
netPerformancePercentageWithCurrencyEffect: Big; netPerformancePercentageWithCurrencyEffect: Big;
netPerformancePercentageWithCurrencyEffectMap: { [key: DateRange]: Big };
netPerformanceValues: { netPerformanceValues: {
[date: string]: Big; [date: string]: Big;
}; };
netPerformanceValuesWithCurrencyEffect: { [date: string]: Big }; netPerformanceValuesWithCurrencyEffect: { [date: string]: Big };
netPerformanceWithCurrencyEffect: Big; netPerformanceWithCurrencyEffect: Big;
netPerformanceWithCurrencyEffectMap: { [key: DateRange]: Big };
timeWeightedInvestment: Big; timeWeightedInvestment: Big;
timeWeightedInvestmentValues: { timeWeightedInvestmentValues: {
[date: string]: Big; [date: string]: Big;

12
libs/common/src/lib/models/timeline-position.ts

@ -1,4 +1,8 @@
import { transformToBig } from '@ghostfolio/common/class-transformer'; import {
transformToBig,
transformToMapOfBig
} from '@ghostfolio/common/class-transformer';
import { DateRange } from '@ghostfolio/common/types';
import { DataSource, Tag } from '@prisma/client'; import { DataSource, Tag } from '@prisma/client';
import { Big } from 'big.js'; import { Big } from 'big.js';
@ -69,10 +73,16 @@ export class TimelinePosition {
@Type(() => Big) @Type(() => Big)
netPerformancePercentageWithCurrencyEffect: Big; netPerformancePercentageWithCurrencyEffect: Big;
@Transform(transformToMapOfBig, { toClassOnly: true })
netPerformancePercentageWithCurrencyEffectMap: { [key: DateRange]: Big };
@Transform(transformToBig, { toClassOnly: true }) @Transform(transformToBig, { toClassOnly: true })
@Type(() => Big) @Type(() => Big)
netPerformanceWithCurrencyEffect: Big; netPerformanceWithCurrencyEffect: Big;
@Transform(transformToMapOfBig, { toClassOnly: true })
netPerformanceWithCurrencyEffectMap: { [key: DateRange]: Big };
@Transform(transformToBig, { toClassOnly: true }) @Transform(transformToBig, { toClassOnly: true })
@Type(() => Big) @Type(() => Big)
quantity: Big; quantity: Big;

Loading…
Cancel
Save