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.useCache = false; // TODO: useCache
this.useCache = useCache;
this.userId = userId;
const { endDate, startDate } = getIntervalFromDateRange(
@ -394,9 +394,11 @@ export abstract class PortfolioCalculator {
netPerformance,
netPerformancePercentage,
netPerformancePercentageWithCurrencyEffect,
netPerformancePercentageWithCurrencyEffectMap,
netPerformanceValues,
netPerformanceValuesWithCurrencyEffect,
netPerformanceWithCurrencyEffect,
netPerformanceWithCurrencyEffectMap,
timeWeightedInvestment,
timeWeightedInvestmentValues,
timeWeightedInvestmentValuesWithCurrencyEffect,
@ -468,9 +470,15 @@ export abstract class PortfolioCalculator {
netPerformancePercentageWithCurrencyEffect: !hasErrors
? (netPerformancePercentageWithCurrencyEffect ?? null)
: null,
netPerformancePercentageWithCurrencyEffectMap: !hasErrors
? (netPerformancePercentageWithCurrencyEffectMap ?? null)
: null,
netPerformanceWithCurrencyEffect: !hasErrors
? (netPerformanceWithCurrencyEffect ?? null)
: null,
netPerformanceWithCurrencyEffectMap: !hasErrors
? (netPerformanceWithCurrencyEffectMap ?? null)
: null,
quantity: item.quantity,
symbol: item.symbol,
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 { PortfolioOrderItem } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order-item.interface';
import { getFactor } from '@ghostfolio/api/helper/portfolio.helper';
import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper';
import { DATE_FORMAT } from '@ghostfolio/common/helper';
import {
AssetProfileIdentifier,
SymbolMetrics
} from '@ghostfolio/common/interfaces';
import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models';
import { DateRange } from '@ghostfolio/common/types';
import { Logger } from '@nestjs/common';
import { Big } from 'big.js';
@ -14,6 +16,7 @@ import {
addDays,
addMilliseconds,
differenceInDays,
eachDayOfInterval,
format,
isBefore
} from 'date-fns';
@ -236,9 +239,11 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
netPerformance: new Big(0),
netPerformancePercentage: new Big(0),
netPerformancePercentageWithCurrencyEffect: new Big(0),
netPerformancePercentageWithCurrencyEffectMap: {},
netPerformanceValues: {},
netPerformanceValuesWithCurrencyEffect: {},
netPerformanceWithCurrencyEffect: new Big(0),
netPerformanceWithCurrencyEffectMap: {},
timeWeightedInvestment: new Big(0),
timeWeightedInvestmentValues: {},
timeWeightedInvestmentValuesWithCurrencyEffect: {},
@ -286,6 +291,8 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
netPerformance: new Big(0),
netPerformancePercentage: new Big(0),
netPerformancePercentageWithCurrencyEffect: new Big(0),
netPerformancePercentageWithCurrencyEffectMap: {},
netPerformanceWithCurrencyEffectMap: {},
netPerformanceValues: {},
netPerformanceValuesWithCurrencyEffect: {},
netPerformanceWithCurrencyEffect: new Big(0),
@ -841,6 +848,87 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
)
: 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) {
console.log(
`
@ -893,8 +981,10 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
investmentValuesWithCurrencyEffect,
netPerformancePercentage,
netPerformancePercentageWithCurrencyEffect,
netPerformancePercentageWithCurrencyEffectMap,
netPerformanceValues,
netPerformanceValuesWithCurrencyEffect,
netPerformanceWithCurrencyEffectMap,
timeWeightedInvestmentValues,
timeWeightedInvestmentValuesWithCurrencyEffect,
totalAccountBalanceInBaseCurrency,

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

@ -346,7 +346,7 @@ export class PortfolioService {
userId,
calculationType: PerformanceCalculationType.TWR,
currency: userCurrency,
hasFilters: true, // disable cache
hasFilters: filters?.length > 0, // TODO
isExperimentalFeatures:
this.request.user?.Settings.settings.isExperimentalFeatures
});
@ -412,10 +412,8 @@ export class PortfolioService {
console.timeEnd('-- PortfolioService.getDetails - 3');
console.time('-- PortfolioService.getDetails - 4');
const [dataProviderResponses, symbolProfiles] = await Promise.all([
this.dataProviderService.getQuotes({ user, items: dataGatheringItems }),
this.symbolProfileService.getSymbolProfiles(dataGatheringItems)
]);
const symbolProfiles =
await this.symbolProfileService.getSymbolProfiles(dataGatheringItems);
const symbolProfileMap: { [symbol: string]: EnhancedSymbolProfile } = {};
for (const symbolProfile of symbolProfiles) {
@ -442,8 +440,10 @@ export class PortfolioService {
marketPrice,
netPerformance,
netPerformancePercentage,
netPerformancePercentageWithCurrencyEffect,
netPerformanceWithCurrencyEffect,
netPerformancePercentageWithCurrencyEffect, // TODO: Remove?
netPerformancePercentageWithCurrencyEffectMap,
netPerformanceWithCurrencyEffect, // TODO: Remove?
netPerformanceWithCurrencyEffectMap,
quantity,
symbol,
tags,
@ -463,7 +463,6 @@ export class PortfolioService {
}
const assetProfile = symbolProfileMap[symbol];
const dataProviderResponse = dataProviderResponses[symbol];
let markets: PortfolioPosition['markets'];
let marketsAdvanced: PortfolioPosition['marketsAdvanced'];
@ -510,14 +509,15 @@ export class PortfolioService {
}
),
investment: investment.toNumber(),
marketState: dataProviderResponse?.marketState ?? 'delayed',
name: assetProfile.name,
netPerformance: netPerformance?.toNumber() ?? 0,
netPerformancePercent: netPerformancePercentage?.toNumber() ?? 0,
netPerformancePercentWithCurrencyEffect:
netPerformancePercentageWithCurrencyEffect?.toNumber() ?? 0,
netPerformancePercentageWithCurrencyEffectMap?.[
dateRange
]?.toNumber() ?? 0,
netPerformanceWithCurrencyEffect:
netPerformanceWithCurrencyEffect?.toNumber() ?? 0,
netPerformanceWithCurrencyEffectMap?.[dateRange]?.toNumber() ?? 0,
quantity: quantity.toNumber(),
sectors: assetProfile.sectors,
url: assetProfile.url,
@ -1489,7 +1489,6 @@ export class PortfolioService {
holdings: [],
investment: balance,
marketPrice: 0,
marketState: 'open',
name: currency,
netPerformance: 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 { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service';
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 { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
@ -27,7 +26,6 @@ import {
import { UserWithSettings } from '@ghostfolio/common/types';
import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { Prisma, Role, User } from '@prisma/client';
import { differenceInDays } from 'date-fns';
import { sortBy, without } from 'lodash';
@ -40,7 +38,6 @@ export class UserService {
public constructor(
private readonly configurationService: ConfigurationService,
private readonly eventEmitter: EventEmitter2,
private readonly orderService: OrderService,
private readonly prismaService: PrismaService,
private readonly propertyService: PropertyService,
@ -444,13 +441,6 @@ export class UserService {
}
});
this.eventEmitter.emit(
PortfolioChangedEvent.getName(),
new PortfolioChangedEvent({
userId
})
);
return settings;
}

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

@ -1,5 +1,21 @@
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 {
if (value === 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 { Market, MarketAdvanced, MarketState } from '../types';
import { Country } from './country.interface';
import { Holding } from './holding.interface';
import { Sector } from './sector.interface';
@ -28,7 +29,6 @@ export interface PortfolioPosition {
marketPrice: number;
markets?: { [key in Market]: number };
marketsAdvanced?: { [key in MarketAdvanced]: number };
marketState: MarketState;
name: string;
netPerformance: 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';
export interface SymbolMetrics {
@ -27,11 +29,13 @@ export interface SymbolMetrics {
netPerformance: Big;
netPerformancePercentage: Big;
netPerformancePercentageWithCurrencyEffect: Big;
netPerformancePercentageWithCurrencyEffectMap: { [key: DateRange]: Big };
netPerformanceValues: {
[date: string]: Big;
};
netPerformanceValuesWithCurrencyEffect: { [date: string]: Big };
netPerformanceWithCurrencyEffect: Big;
netPerformanceWithCurrencyEffectMap: { [key: DateRange]: Big };
timeWeightedInvestment: Big;
timeWeightedInvestmentValues: {
[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 { Big } from 'big.js';
@ -69,10 +73,16 @@ export class TimelinePosition {
@Type(() => Big)
netPerformancePercentageWithCurrencyEffect: Big;
@Transform(transformToMapOfBig, { toClassOnly: true })
netPerformancePercentageWithCurrencyEffectMap: { [key: DateRange]: Big };
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)
netPerformanceWithCurrencyEffect: Big;
@Transform(transformToMapOfBig, { toClassOnly: true })
netPerformanceWithCurrencyEffectMap: { [key: DateRange]: Big };
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)
quantity: Big;

Loading…
Cancel
Save