gizmodus 3 weeks ago committed by GitHub
commit 654c6811ab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -267,6 +267,7 @@ export class OrderService {
userId: string;
withExcludedAccounts?: boolean;
}): Promise<Activities> {
console.time('------ OrderService.getOrders');
let orderBy: Prisma.Enumerable<Prisma.OrderOrderByWithRelationInput> = [
{ date: 'asc' }
];
@ -426,6 +427,8 @@ export class OrderService {
};
});
console.timeEnd('------ OrderService.getOrders');
return { activities, count };
}

@ -44,7 +44,7 @@ import {
min,
subDays
} from 'date-fns';
import { first, last, uniq, uniqBy } from 'lodash';
import { first, last, sum, uniq, uniqBy } from 'lodash';
export abstract class PortfolioCalculator {
protected static readonly ENABLE_LOGGING = false;
@ -90,6 +90,8 @@ export abstract class PortfolioCalculator {
useCache: boolean;
userId: string;
}) {
console.time('--- PortfolioCalculator.constructor - 1');
this.accountBalanceItems = accountBalanceItems;
this.configurationService = configurationService;
this.currency = currency;
@ -97,6 +99,8 @@ export abstract class PortfolioCalculator {
this.dateRange = dateRange;
this.exchangeRateDataService = exchangeRateDataService;
let dateOfFirstActivity = new Date();
this.activities = activities
.map(
({
@ -108,6 +112,10 @@ export abstract class PortfolioCalculator {
type,
unitPrice
}) => {
if (isBefore(date, dateOfFirstActivity)) {
dateOfFirstActivity = date;
}
if (isAfter(date, new Date(Date.now()))) {
// Adapt date to today if activity is in future (e.g. liability)
// to include it in the interval
@ -130,17 +138,27 @@ export abstract class PortfolioCalculator {
});
this.redisCacheService = redisCacheService;
this.useCache = useCache;
this.useCache = false; // useCache;
this.userId = userId;
const { endDate, startDate } = getInterval(dateRange);
const { endDate, startDate } = getInterval(
'max',
subDays(dateOfFirstActivity, 1)
);
this.endDate = endDate;
this.startDate = startDate;
console.timeEnd('--- PortfolioCalculator.constructor - 1');
console.time('--- PortfolioCalculator.constructor - 2');
this.computeTransactionPoints();
console.timeEnd('--- PortfolioCalculator.constructor - 2');
console.time('--- PortfolioCalculator.constructor - 3');
this.snapshotPromise = this.initialize();
console.timeEnd('--- PortfolioCalculator.constructor - 3');
}
protected abstract calculateOverallPerformance(
@ -169,6 +187,7 @@ export abstract class PortfolioCalculator {
if (!transactionPoints.length) {
return {
chartData: [],
currentValueInBaseCurrency: new Big(0),
grossPerformance: new Big(0),
grossPerformancePercentage: new Big(0),
@ -264,7 +283,8 @@ export abstract class PortfolioCalculator {
} = await this.currentRateService.getValues({
dataGatheringItems,
dateQuery: {
in: dates
gte: parseDate(firstTransactionPoint?.date),
lt: end
}
});
@ -290,6 +310,26 @@ export abstract class PortfolioCalculator {
const endDateString = format(endDate, DATE_FORMAT);
const chartStartDate = this.getStartDate();
const daysInMarket = differenceInDays(endDate, chartStartDate) + 1;
const step = false /*withDataDecimation*/
? Math.round(daysInMarket / Math.min(daysInMarket, MAX_CHART_ITEMS))
: 1;
let chartDates = eachDayOfInterval(
{ start: chartStartDate, end },
{ step }
).map((date) => {
return resetHours(date);
});
const includesEndDate = isSameDay(last(chartDates), end);
if (!includesEndDate) {
chartDates.push(resetHours(end));
}
if (firstIndex > 0) {
firstIndex--;
}
@ -299,6 +339,34 @@ export abstract class PortfolioCalculator {
const errors: ResponseError['errors'] = [];
const accumulatedValuesByDate: {
[date: string]: {
investmentValueWithCurrencyEffect: Big;
totalCurrentValue: Big;
totalCurrentValueWithCurrencyEffect: Big;
totalInvestmentValue: Big;
totalInvestmentValueWithCurrencyEffect: Big;
totalNetPerformanceValue: Big;
totalNetPerformanceValueWithCurrencyEffect: Big;
totalTimeWeightedInvestmentValue: Big;
totalTimeWeightedInvestmentValueWithCurrencyEffect: Big;
};
} = {};
const valuesBySymbol: {
[symbol: string]: {
currentValues: { [date: string]: Big };
currentValuesWithCurrencyEffect: { [date: string]: Big };
investmentValuesAccumulated: { [date: string]: Big };
investmentValuesAccumulatedWithCurrencyEffect: { [date: string]: Big };
investmentValuesWithCurrencyEffect: { [date: string]: Big };
netPerformanceValues: { [date: string]: Big };
netPerformanceValuesWithCurrencyEffect: { [date: string]: Big };
timeWeightedInvestmentValues: { [date: string]: Big };
timeWeightedInvestmentValuesWithCurrencyEffect: { [date: string]: Big };
};
} = {};
for (const item of lastTransactionPoint.items) {
const marketPriceInBaseCurrency = (
marketSymbolMap[endDateString]?.[item.symbol] ?? item.averagePrice
@ -309,16 +377,25 @@ export abstract class PortfolioCalculator {
);
const {
currentValues,
currentValuesWithCurrencyEffect,
grossPerformance,
grossPerformancePercentage,
grossPerformancePercentageWithCurrencyEffect,
grossPerformanceWithCurrencyEffect,
hasErrors,
investmentValuesAccumulated,
investmentValuesAccumulatedWithCurrencyEffect,
investmentValuesWithCurrencyEffect,
netPerformance,
netPerformancePercentage,
netPerformancePercentageWithCurrencyEffect,
netPerformanceValues,
netPerformanceValuesWithCurrencyEffect,
netPerformanceWithCurrencyEffect,
timeWeightedInvestment,
timeWeightedInvestmentValues,
timeWeightedInvestmentValuesWithCurrencyEffect,
timeWeightedInvestmentWithCurrencyEffect,
totalDividend,
totalDividendInBaseCurrency,
@ -330,15 +407,29 @@ export abstract class PortfolioCalculator {
} = this.getSymbolMetrics({
marketSymbolMap,
start,
step,
dataSource: item.dataSource,
end: endDate,
exchangeRates:
exchangeRatesByCurrency[`${item.currency}${this.currency}`],
isChartMode: true,
symbol: item.symbol
});
hasAnySymbolMetricsErrors = hasAnySymbolMetricsErrors || hasErrors;
valuesBySymbol[item.symbol] = {
currentValues,
currentValuesWithCurrencyEffect,
investmentValuesAccumulated,
investmentValuesAccumulatedWithCurrencyEffect,
investmentValuesWithCurrencyEffect,
netPerformanceValues,
netPerformanceValuesWithCurrencyEffect,
timeWeightedInvestmentValues,
timeWeightedInvestmentValuesWithCurrencyEffect
};
positions.push({
dividend: totalDividend,
dividendInBaseCurrency: totalDividendInBaseCurrency,
@ -406,10 +497,139 @@ export abstract class PortfolioCalculator {
}
}
for (const currentDate of chartDates) {
const dateString = format(currentDate, DATE_FORMAT);
for (const symbol of Object.keys(valuesBySymbol)) {
const symbolValues = valuesBySymbol[symbol];
const currentValue =
symbolValues.currentValues?.[dateString] ?? new Big(0);
const currentValueWithCurrencyEffect =
symbolValues.currentValuesWithCurrencyEffect?.[dateString] ??
new Big(0);
const investmentValueAccumulated =
symbolValues.investmentValuesAccumulated?.[dateString] ?? new Big(0);
const investmentValueAccumulatedWithCurrencyEffect =
symbolValues.investmentValuesAccumulatedWithCurrencyEffect?.[
dateString
] ?? new Big(0);
const investmentValueWithCurrencyEffect =
symbolValues.investmentValuesWithCurrencyEffect?.[dateString] ??
new Big(0);
const netPerformanceValue =
symbolValues.netPerformanceValues?.[dateString] ?? new Big(0);
const netPerformanceValueWithCurrencyEffect =
symbolValues.netPerformanceValuesWithCurrencyEffect?.[dateString] ??
new Big(0);
const timeWeightedInvestmentValue =
symbolValues.timeWeightedInvestmentValues?.[dateString] ?? new Big(0);
const timeWeightedInvestmentValueWithCurrencyEffect =
symbolValues.timeWeightedInvestmentValuesWithCurrencyEffect?.[
dateString
] ?? new Big(0);
accumulatedValuesByDate[dateString] = {
investmentValueWithCurrencyEffect: (
accumulatedValuesByDate[dateString]
?.investmentValueWithCurrencyEffect ?? new Big(0)
).add(investmentValueWithCurrencyEffect),
totalCurrentValue: (
accumulatedValuesByDate[dateString]?.totalCurrentValue ?? new Big(0)
).add(currentValue),
totalCurrentValueWithCurrencyEffect: (
accumulatedValuesByDate[dateString]
?.totalCurrentValueWithCurrencyEffect ?? new Big(0)
).add(currentValueWithCurrencyEffect),
totalInvestmentValue: (
accumulatedValuesByDate[dateString]?.totalInvestmentValue ??
new Big(0)
).add(investmentValueAccumulated),
totalInvestmentValueWithCurrencyEffect: (
accumulatedValuesByDate[dateString]
?.totalInvestmentValueWithCurrencyEffect ?? new Big(0)
).add(investmentValueAccumulatedWithCurrencyEffect),
totalNetPerformanceValue: (
accumulatedValuesByDate[dateString]?.totalNetPerformanceValue ??
new Big(0)
).add(netPerformanceValue),
totalNetPerformanceValueWithCurrencyEffect: (
accumulatedValuesByDate[dateString]
?.totalNetPerformanceValueWithCurrencyEffect ?? new Big(0)
).add(netPerformanceValueWithCurrencyEffect),
totalTimeWeightedInvestmentValue: (
accumulatedValuesByDate[dateString]
?.totalTimeWeightedInvestmentValue ?? new Big(0)
).add(timeWeightedInvestmentValue),
totalTimeWeightedInvestmentValueWithCurrencyEffect: (
accumulatedValuesByDate[dateString]
?.totalTimeWeightedInvestmentValueWithCurrencyEffect ?? new Big(0)
).add(timeWeightedInvestmentValueWithCurrencyEffect)
};
}
}
const chartData: HistoricalDataItem[] = Object.entries(
accumulatedValuesByDate
).map(([date, values]) => {
const {
investmentValueWithCurrencyEffect,
totalCurrentValue,
totalCurrentValueWithCurrencyEffect,
totalInvestmentValue,
totalInvestmentValueWithCurrencyEffect,
totalNetPerformanceValue,
totalNetPerformanceValueWithCurrencyEffect,
totalTimeWeightedInvestmentValue,
totalTimeWeightedInvestmentValueWithCurrencyEffect
} = values;
const netPerformanceInPercentage = totalTimeWeightedInvestmentValue.eq(0)
? 0
: totalNetPerformanceValue
.div(totalTimeWeightedInvestmentValue)
.mul(100)
.toNumber();
const netPerformanceInPercentageWithCurrencyEffect =
totalTimeWeightedInvestmentValueWithCurrencyEffect.eq(0)
? 0
: totalNetPerformanceValueWithCurrencyEffect
.div(totalTimeWeightedInvestmentValueWithCurrencyEffect)
.mul(100)
.toNumber();
return {
date,
netPerformanceInPercentage,
netPerformanceInPercentageWithCurrencyEffect,
investmentValueWithCurrencyEffect:
investmentValueWithCurrencyEffect.toNumber(),
netPerformance: totalNetPerformanceValue.toNumber(),
netPerformanceWithCurrencyEffect:
totalNetPerformanceValueWithCurrencyEffect.toNumber(),
netWorth: 0, // TODO
totalInvestment: totalInvestmentValue.toNumber(),
totalInvestmentValueWithCurrencyEffect:
totalInvestmentValueWithCurrencyEffect.toNumber(),
value: totalCurrentValue.toNumber(),
valueWithCurrencyEffect: totalCurrentValueWithCurrencyEffect.toNumber()
};
});
const overall = this.calculateOverallPerformance(positions);
return {
...overall,
chartData,
errors,
positions,
totalInterestWithCurrencyEffect,
@ -426,6 +646,12 @@ export abstract class PortfolioCalculator {
dateRange?: DateRange;
withDataDecimation?: boolean;
}): Promise<HistoricalDataItem[]> {
console.time('-------- PortfolioCalculator.getChart');
if (this.getTransactionPoints().length === 0) {
return [];
}
const { endDate, startDate } = getInterval(dateRange, this.getStartDate());
const daysInMarket = differenceInDays(endDate, startDate) + 1;
@ -433,11 +659,15 @@ export abstract class PortfolioCalculator {
? Math.round(daysInMarket / Math.min(daysInMarket, MAX_CHART_ITEMS))
: 1;
return this.getChartData({
const chartData = await this.getChartData({
step,
end: endDate,
start: startDate
});
console.timeEnd('-------- PortfolioCalculator.getChart');
return chartData;
}
public async getChartData({
@ -489,7 +719,8 @@ export abstract class PortfolioCalculator {
await this.currentRateService.getValues({
dataGatheringItems,
dateQuery: {
in: dates
gte: start,
lt: end
}
});
@ -737,6 +968,11 @@ export abstract class PortfolioCalculator {
totalTimeWeightedInvestmentValueWithCurrencyEffect
} = values;
console.log(
'Chart: totalTimeWeightedInvestmentValue',
totalTimeWeightedInvestmentValue.toFixed()
);
const netPerformanceInPercentage = totalTimeWeightedInvestmentValue.eq(0)
? 0
: totalNetPerformanceValue
@ -848,11 +1084,83 @@ export abstract class PortfolioCalculator {
}
public async getSnapshot() {
console.time('getSnapshot');
await this.snapshotPromise;
console.timeEnd('getSnapshot');
return this.snapshot;
}
public async getPerformance({ end, start }) {
await this.snapshotPromise;
const { chartData } = this.snapshot;
const newChartData: HistoricalDataItem[] = [];
let netPerformanceAtStartDate: number;
let netPerformanceWithCurrencyEffectAtStartDate: number;
let netPerformanceInPercentageWithCurrencyEffectAtStartDate: number;
let totalInvestmentValuesWithCurrencyEffect: number[] = [];
for (let historicalDataItem of chartData) {
if (
!isBefore(parseDate(historicalDataItem.date), subDays(start, 1)) &&
!isAfter(parseDate(historicalDataItem.date), end)
) {
if (!netPerformanceAtStartDate) {
netPerformanceAtStartDate = historicalDataItem.netPerformance;
netPerformanceWithCurrencyEffectAtStartDate =
historicalDataItem.netPerformanceWithCurrencyEffect;
netPerformanceInPercentageWithCurrencyEffectAtStartDate =
historicalDataItem.netPerformanceInPercentageWithCurrencyEffect;
}
const netPerformanceWithCurrencyEffectSinceStartDate =
historicalDataItem.netPerformanceWithCurrencyEffect -
netPerformanceWithCurrencyEffectAtStartDate;
if (historicalDataItem.totalInvestmentValueWithCurrencyEffect > 0) {
totalInvestmentValuesWithCurrencyEffect.push(
historicalDataItem.totalInvestmentValueWithCurrencyEffect
);
}
const timeWeightedInvestmentValue =
totalInvestmentValuesWithCurrencyEffect.length > 0
? sum(totalInvestmentValuesWithCurrencyEffect) /
totalInvestmentValuesWithCurrencyEffect.length
: 0;
// TODO: Not sure if this is correct
console.log(historicalDataItem.totalInvestmentValueWithCurrencyEffect);
// TODO: Normalize remaining metrics
newChartData.push({
...historicalDataItem,
netPerformance:
historicalDataItem.netPerformance - netPerformanceAtStartDate,
netPerformanceWithCurrencyEffect:
netPerformanceWithCurrencyEffectSinceStartDate,
netPerformanceInPercentageWithCurrencyEffect:
(netPerformanceWithCurrencyEffectSinceStartDate /
timeWeightedInvestmentValue) *
100,
// TODO: Add net worth with valuables
// netWorth: totalCurrentValueWithCurrencyEffect
// .plus(totalAccountBalanceWithCurrencyEffect)
// .toNumber()
netWorth: 0
});
}
}
return { chart: newChartData };
}
public getStartDate() {
let firstAccountBalanceDate: Date;
let firstActivityDate: Date;

@ -98,6 +98,11 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
}
}
console.log(
'Overall: totalTimeWeightedInvestmentValue',
totalTimeWeightedInvestment.toFixed()
);
return {
currentValueInBaseCurrency,
grossPerformance,
@ -110,6 +115,7 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
totalInterestWithCurrencyEffect,
totalInvestment,
totalInvestmentWithCurrencyEffect,
chartData: [],
netPerformancePercentage: totalTimeWeightedInvestment.eq(0)
? new Big(0)
: netPerformance.div(totalTimeWeightedInvestment),
@ -331,18 +337,21 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
let lastUnitPrice: Big;
if (isChartMode) {
const datesWithOrders = {};
const ordersByDate: { [date: string]: PortfolioOrderItem[] } = {};
for (const { date, type } of orders) {
if (['BUY', 'SELL'].includes(type)) {
datesWithOrders[date] = true;
}
for (const order of orders) {
ordersByDate[order.date] = ordersByDate[order.date] ?? [];
ordersByDate[order.date].push(order);
}
while (isBefore(day, end)) {
const hasDate = datesWithOrders[format(day, DATE_FORMAT)];
if (!hasDate) {
if (ordersByDate[format(day, DATE_FORMAT)]?.length > 0) {
for (let order of ordersByDate[format(day, DATE_FORMAT)]) {
order.unitPriceFromMarketData =
marketSymbolMap[format(day, DATE_FORMAT)]?.[symbol] ??
lastUnitPrice;
}
} else {
orders.push({
date: format(day, DATE_FORMAT),
fee: new Big(0),
@ -354,12 +363,18 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
},
type: 'BUY',
unitPrice:
marketSymbolMap[format(day, DATE_FORMAT)]?.[symbol] ??
lastUnitPrice,
unitPriceFromMarketData:
marketSymbolMap[format(day, DATE_FORMAT)]?.[symbol] ??
lastUnitPrice
});
}
lastUnitPrice = last(orders).unitPrice;
const lastOrder = last(orders);
lastUnitPrice =
lastOrder.unitPriceFromMarketData ?? lastOrder.unitPrice;
day = addDays(day, step);
}
@ -453,12 +468,14 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
);
}
if (order.unitPrice) {
order.unitPriceInBaseCurrency = order.unitPrice.mul(
currentExchangeRate ?? 1
);
const unitPrice = ['BUY', 'SELL'].includes(order.type)
? order.unitPrice
: order.unitPriceFromMarketData;
order.unitPriceInBaseCurrencyWithCurrencyEffect = order.unitPrice.mul(
if (unitPrice) {
order.unitPriceInBaseCurrency = unitPrice.mul(currentExchangeRate ?? 1);
order.unitPriceInBaseCurrencyWithCurrencyEffect = unitPrice.mul(
exchangeRateAtOrderDate ?? 1
);
}
@ -642,10 +659,13 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
grossPerformanceWithCurrencyEffect;
}
if (i > indexOfStartOrder && ['BUY', 'SELL'].includes(order.type)) {
if (i > indexOfStartOrder) {
// Only consider periods with an investment for the calculation of
// the time weighted investment
if (valueOfInvestmentBeforeTransaction.gt(0)) {
if (
valueOfInvestmentBeforeTransaction.gt(0) &&
['BUY', 'SELL'].includes(order.type)
) {
// Calculate the number of days since the previous order
const orderDate = new Date(order.date);
const previousOrderDate = new Date(orders[i - 1].date);

@ -6,6 +6,7 @@ export interface PortfolioOrderItem extends PortfolioOrder {
feeInBaseCurrency?: Big;
feeInBaseCurrencyWithCurrencyEffect?: Big;
itemType?: 'end' | 'start';
unitPriceFromMarketData?: Big;
unitPriceInBaseCurrency?: Big;
unitPriceInBaseCurrencyWithCurrencyEffect?: Big;
}

@ -80,6 +80,8 @@ export class PortfolioController {
@Query('tags') filterByTags?: string,
@Query('withMarkets') withMarketsParam = 'false'
): Promise<PortfolioDetails & { hasError: boolean }> {
console.time('TOTAL');
const withMarkets = withMarketsParam === 'true';
let hasDetails = true;
@ -100,6 +102,8 @@ export class PortfolioController {
filterByTags
});
console.time('- PortfolioController.getDetails - 1');
const { accounts, hasErrors, holdings, platforms, summary } =
await this.portfolioService.getDetails({
dateRange,
@ -110,6 +114,10 @@ export class PortfolioController {
withSummary: true
});
console.timeEnd('- PortfolioController.getDetails - 1');
console.time('- PortfolioController.getDetails - 2');
if (hasErrors || hasNotDefinedValuesInObject(holdings)) {
hasError = true;
}
@ -207,6 +215,10 @@ export class PortfolioController {
};
}
console.timeEnd('- PortfolioController.getDetails - 2');
console.timeEnd('TOTAL');
return {
accounts,
hasError,

@ -70,7 +70,7 @@ import {
parseISO,
set
} from 'date-fns';
import { isEmpty, isNumber, last, uniq, uniqBy } from 'lodash';
import { isEmpty, isNumber, uniq, uniqBy } from 'lodash';
import { PortfolioCalculator } from './calculator/portfolio-calculator';
import {
@ -339,6 +339,8 @@ export class PortfolioService {
withMarkets?: boolean;
withSummary?: boolean;
}): Promise<PortfolioDetails & { hasErrors: boolean }> {
console.time('-- PortfolioService.getDetails - 1');
userId = await this.getUserId(impersonationId, userId);
const user = await this.userService.user({ id: userId });
const userCurrency = this.getUserCurrency(user);
@ -365,9 +367,16 @@ export class PortfolioService {
this.request.user?.Settings.settings.isExperimentalFeatures
});
console.timeEnd('-- PortfolioService.getDetails - 1');
console.time('-- PortfolioService.getDetails - 2');
const { currentValueInBaseCurrency, hasErrors, positions } =
await portfolioCalculator.getSnapshot();
console.timeEnd('-- PortfolioService.getDetails - 2');
console.time('-- PortfolioService.getDetails - 3');
const cashDetails = await this.accountService.getCashDetails({
filters,
userId,
@ -416,6 +425,9 @@ 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)
@ -431,6 +443,9 @@ export class PortfolioService {
portfolioItemsNow[position.symbol] = position;
}
console.timeEnd('-- PortfolioService.getDetails - 4');
console.time('-- PortfolioService.getDetails - 5');
for (const {
currency,
dividend,
@ -571,6 +586,10 @@ export class PortfolioService {
};
}
console.timeEnd('-- PortfolioService.getDetails - 5');
console.time('-- PortfolioService.getDetails - 6');
let summary: PortfolioSummary;
if (withSummary) {
@ -589,6 +608,8 @@ export class PortfolioService {
});
}
console.timeEnd('-- PortfolioService.getDetails - 6');
return {
accounts,
hasErrors,
@ -1051,15 +1072,20 @@ export class PortfolioService {
dateRange = 'max',
filters,
impersonationId,
portfolioCalculator,
userId,
withExcludedAccounts = false
}: {
dateRange?: DateRange;
filters?: Filter[];
impersonationId: string;
portfolioCalculator?: PortfolioCalculator;
userId: string;
withExcludedAccounts?: boolean;
}): Promise<PortfolioPerformanceResponse> {
// OPTIMIZE (1.34s)
console.time('------ PortfolioService.getPerformance');
userId = await this.getUserId(impersonationId, userId);
const user = await this.userService.user({ id: userId });
const userCurrency = this.getUserCurrency(user);
@ -1094,7 +1120,9 @@ export class PortfolioService {
)
);
const { endDate } = getInterval(dateRange);
const { endDate, startDate } = getInterval(dateRange);
console.time('------- PortfolioService.getPerformance - 2');
const { activities } = await this.orderService.getOrders({
endDate,
@ -1104,6 +1132,9 @@ export class PortfolioService {
withExcludedAccounts
});
console.timeEnd('------- PortfolioService.getPerformance - 2');
console.time('------- PortfolioService.getPerformance - 3');
if (accountBalanceItems?.length <= 0 && activities?.length <= 0) {
return {
chart: [],
@ -1125,19 +1156,22 @@ export class PortfolioService {
};
}
const portfolioCalculator = this.calculatorFactory.createCalculator({
accountBalanceItems,
activities,
dateRange,
userId,
calculationType: PerformanceCalculationType.TWR,
currency: userCurrency,
hasFilters: filters?.length > 0,
isExperimentalFeatures:
this.request.user.Settings.settings.isExperimentalFeatures
});
portfolioCalculator =
portfolioCalculator ??
this.calculatorFactory.createCalculator({
accountBalanceItems,
activities,
dateRange,
userId,
calculationType: PerformanceCalculationType.TWR,
currency: userCurrency,
hasFilters: filters?.length > 0,
isExperimentalFeatures:
this.request.user.Settings.settings.isExperimentalFeatures
});
const {
chartData,
currentValueInBaseCurrency,
errors,
grossPerformance,
@ -1152,6 +1186,9 @@ export class PortfolioService {
totalInvestment
} = await portfolioCalculator.getSnapshot();
console.timeEnd('------- PortfolioService.getPerformance - 3');
console.time('------- PortfolioService.getPerformance - 4');
let currentNetPerformance = netPerformance;
let currentNetPerformancePercentage = netPerformancePercentage;
@ -1164,11 +1201,10 @@ export class PortfolioService {
let currentNetWorth = 0;
const items = await portfolioCalculator.getChart({
dateRange
});
console.timeEnd('------- PortfolioService.getPerformance - 4');
console.time('------- PortfolioService.getPerformance - 5');
const itemOfToday = items.find(({ date }) => {
const itemOfToday = chartData.find(({ date }) => {
return date === format(new Date(), DATE_FORMAT);
});
@ -1190,11 +1226,20 @@ export class PortfolioService {
currentNetWorth = itemOfToday.netWorth;
}
console.timeEnd('------- PortfolioService.getPerformance - 5');
console.timeEnd('------ PortfolioService.getPerformance');
const { chart } = await portfolioCalculator.getPerformance({
end: endDate,
start: startDate
});
return {
chart,
errors,
hasErrors,
chart: items,
firstOrderDate: parseDate(items[0]?.date),
firstOrderDate: parseDate(chartData[0]?.date),
performance: {
currentNetWorth,
currentValueInBaseCurrency: currentValueInBaseCurrency.toNumber(),
@ -1603,15 +1648,23 @@ export class PortfolioService {
userCurrency: string;
userId: string;
}): Promise<PortfolioSummary> {
// OPTIMIZE (1.1 s)
console.time('---- PortfolioService.getSummary');
userId = await this.getUserId(impersonationId, userId);
const user = await this.userService.user({ id: userId });
console.time('----- PortfolioService.getSummary - 1');
const { activities } = await this.orderService.getOrders({
userCurrency,
userId,
withExcludedAccounts: true
});
console.timeEnd('----- PortfolioService.getSummary - 1');
console.time('----- PortfolioService.getSummary - 2');
const excludedActivities: Activity[] = [];
const nonExcludedActivities: Activity[] = [];
@ -1652,6 +1705,9 @@ export class PortfolioService {
const interest = await portfolioCalculator.getInterestInBaseCurrency();
console.timeEnd('----- PortfolioService.getSummary - 2');
console.time('----- PortfolioService.getSummary - 3');
const liabilities =
await portfolioCalculator.getLiabilitiesInBaseCurrency();
@ -1688,6 +1744,9 @@ export class PortfolioService {
})
);
console.timeEnd('----- PortfolioService.getSummary - 3');
console.time('----- PortfolioService.getSummary - 4');
const cashDetailsWithExcludedAccounts =
await this.accountService.getCashDetails({
userId,
@ -1725,6 +1784,10 @@ export class PortfolioService {
)
})?.toNumber();
console.timeEnd('----- PortfolioService.getSummary - 4');
console.timeEnd('---- PortfolioService.getSummary');
return {
annualizedPerformancePercent,
annualizedPerformancePercentWithCurrencyEffect,

@ -1,14 +1,17 @@
import { transformToBig } from '@ghostfolio/common/class-transformer';
import { UniqueAsset } from '@ghostfolio/common/interfaces';
import { HistoricalDataItem, UniqueAsset } from '@ghostfolio/common/interfaces';
import { TimelinePosition } from '@ghostfolio/common/models';
import { Big } from 'big.js';
import { Transform, Type } from 'class-transformer';
export class PortfolioSnapshot {
chartData: HistoricalDataItem[];
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)
currentValueInBaseCurrency: Big;
errors?: UniqueAsset[];
@Transform(transformToBig, { toClassOnly: true })

Loading…
Cancel
Save