Browse Source

Merge pull request #69 from dandevaud/feature/Performance-Enhancements

Feature/performance enhancements
pull/5027/head
dandevaud 1 year ago
committed by GitHub
parent
commit
aa3a3ef42c
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 55
      apps/api/src/aop/logging.interceptor.ts
  2. 2
      apps/api/src/app/account-balance/account-balance.service.ts
  3. 128
      apps/api/src/app/portfolio/portfolio-calculator.ts
  4. 8
      apps/api/src/app/portfolio/portfolio.controller.ts
  5. 127
      apps/api/src/app/portfolio/portfolio.service.ts
  6. 32
      apps/api/src/main.ts
  7. 1
      apps/api/src/services/configuration/configuration.service.ts
  8. 2
      apps/api/src/services/data-provider/data-provider.service.ts
  9. 2
      apps/api/src/services/symbol-profile/symbol-profile.service.ts
  10. 5
      apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
  11. 16
      apps/client/src/app/services/data.service.ts

55
apps/api/src/aop/logging.interceptor.ts

@ -0,0 +1,55 @@
import { Logger } from '@nestjs/common';
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
const dict = {};
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const methodName =
context.getClass().name + ':' + context.getHandler().name;
Logger.debug(`Before ${methodName}...`);
const now = Date.now();
return next
.handle()
.pipe(
tap(() => Logger.debug(`After ${methodName}... ${Date.now() - now}ms`))
);
}
}
export function LogPerformance(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
if (Object.keys(dict).includes(propertyKey)) {
dict[propertyKey] += 1;
} else {
dict[propertyKey] = 1;
}
descriptor.value = function (...args: any[]) {
const time = Date.now();
const result = originalMethod.apply(this, args);
const now = Date.now();
if (now - time > 100) {
Logger.debug(`${propertyKey} returned within: ${now - time} ms`);
} else if (dict[propertyKey] > 100) {
Logger.debug(`${propertyKey} was called the 100th time`);
dict[propertyKey] = 0;
}
return result;
};
return descriptor;
}

2
apps/api/src/app/account-balance/account-balance.service.ts

@ -1,3 +1,4 @@
import { LogPerformance } from '@ghostfolio/api/aop/logging.interceptor';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { AccountBalancesResponse, Filter } from '@ghostfolio/common/interfaces';
@ -40,6 +41,7 @@ export class AccountBalanceService {
});
}
@LogPerformance
public async getAccountBalances({
filters,
user,

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

@ -1,3 +1,4 @@
import { LogPerformance } from '@ghostfolio/api/aop/logging.interceptor';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
import { DATE_FORMAT, parseDate, resetHours } from '@ghostfolio/common/helper';
@ -80,6 +81,7 @@ export class PortfolioCalculator {
});
}
@LogPerformance
public computeTransactionPoints() {
this.transactionPoints = [];
const symbols: { [symbol: string]: TransactionPointSymbol } = {};
@ -123,6 +125,7 @@ export class PortfolioCalculator {
}
}
@LogPerformance
private getCurrentTransactionPointItem(
oldAccumulatedSymbol: TransactionPointSymbol,
order: PortfolioOrder,
@ -154,6 +157,7 @@ export class PortfolioCalculator {
return currentTransactionPointItem;
}
@LogPerformance
private handleSubsequentTransactions(
order: PortfolioOrder,
factor: number,
@ -196,6 +200,7 @@ export class PortfolioCalculator {
return currentTransactionPointItem;
}
@LogPerformance
public getAnnualizedPerformancePercent({
daysInMarket,
netPerformancePercent
@ -221,6 +226,7 @@ export class PortfolioCalculator {
this.transactionPoints = transactionPoints;
}
@LogPerformance
public async getChartData({
start,
end = new Date(Date.now()),
@ -345,6 +351,7 @@ export class PortfolioCalculator {
});
}
@LogPerformance
private calculatePerformance(
date: Date,
previousDate: Date,
@ -479,6 +486,7 @@ export class PortfolioCalculator {
};
}
@LogPerformance
private accumulatedValuesByDate(
valuesBySymbol: {
[symbol: string]: {
@ -587,6 +595,7 @@ export class PortfolioCalculator {
return accumulatedValuesByDate;
}
@LogPerformance
private populateSymbolMetrics(
symbols: { [symbol: string]: boolean },
end: Date,
@ -648,6 +657,7 @@ export class PortfolioCalculator {
}
}
@LogPerformance
private populateMarketSymbolMap(
marketSymbols: GetValueObject[],
marketSymbolMap: { [date: string]: { [symbol: string]: Big } }
@ -665,6 +675,7 @@ export class PortfolioCalculator {
}
}
@LogPerformance
private async getInformationFromCurrentRateService(
currencies: { [symbol: string]: string },
dataGatheringItems: IDataGatheringItem[],
@ -681,6 +692,7 @@ export class PortfolioCalculator {
});
}
@LogPerformance
private pushDataGatheringsSymbols(
transactionPointsBeforeEndDate: TransactionPoint[],
firstIndex: number,
@ -700,6 +712,7 @@ export class PortfolioCalculator {
}
}
@LogPerformance
private getRelevantStartAndEndDates(
start: Date,
end: Date,
@ -718,9 +731,11 @@ export class PortfolioCalculator {
}
}
@LogPerformance
public async getCurrentPositions(
start: Date,
end = new Date(Date.now())
end = new Date(Date.now()),
calculatePerformance = true
): Promise<CurrentPositions> {
const transactionPointsBeforeEndDate =
this.transactionPoints?.filter((transactionPoint) => {
@ -883,7 +898,8 @@ export class PortfolioCalculator {
start,
exchangeRates:
exchangeRatesByCurrency[`${item.currency}${this.currency}`],
symbol: item.symbol
symbol: item.symbol,
calculatePerformance
});
hasAnySymbolMetricsErrors = hasAnySymbolMetricsErrors || hasErrors;
@ -956,6 +972,7 @@ export class PortfolioCalculator {
return this.dataProviderInfos;
}
@LogPerformance
public getInvestments(): { date: string; investment: Big }[] {
if (this.transactionPoints.length === 0) {
return [];
@ -973,6 +990,7 @@ export class PortfolioCalculator {
});
}
@LogPerformance
public getInvestmentsByGroup({
data,
groupBy
@ -996,6 +1014,7 @@ export class PortfolioCalculator {
}));
}
@LogPerformance
private calculateOverallPerformance(positions: TimelinePosition[]) {
let currentValue = new Big(0);
let grossPerformance = new Big(0);
@ -1121,6 +1140,7 @@ export class PortfolioCalculator {
return factor;
}
@LogPerformance
private getSymbolMetrics({
end,
exchangeRates,
@ -1128,7 +1148,8 @@ export class PortfolioCalculator {
marketSymbolMap,
start,
step = 1,
symbol
symbol,
calculatePerformance = true
}: {
end: Date;
exchangeRates: { [dateString: string]: number };
@ -1139,6 +1160,7 @@ export class PortfolioCalculator {
start: Date;
step?: number;
symbol: string;
calculatePerformance?: boolean;
}): SymbolMetrics {
const currentExchangeRate = exchangeRates[format(new Date(), DATE_FORMAT)];
const currentValues: WithCurrencyEffect<{ [date: string]: Big }> = {
@ -1359,7 +1381,8 @@ export class PortfolioCalculator {
unitPriceAtEndDate,
symbol,
exchangeRates,
currentExchangeRate
currentExchangeRate,
calculatePerformance
);
return {
@ -1403,6 +1426,7 @@ export class PortfolioCalculator {
};
}
@LogPerformance
private calculatePerformanceOfSymbol(
orders: PortfolioOrderItem[],
indexOfStartOrder: number,
@ -1435,7 +1459,8 @@ export class PortfolioCalculator {
unitPriceAtEndDate: Big,
symbol: string,
exchangeRates: { [dateString: string]: number },
currentExchangeRate: number
currentExchangeRate: number,
calculatePerformance: boolean
) {
let totalInvestmentDays = 0;
let sumOfTimeWeightedInvestments = {
@ -1490,9 +1515,51 @@ export class PortfolioCalculator {
sumOfTimeWeightedInvestments,
timeWeightedInvestmentValues,
exchangeRates,
currentExchangeRate
currentExchangeRate,
calculatePerformance
));
if (!calculatePerformance) {
return {
currentValues,
grossPerformancePercentage: {
Value: new Big(0),
WithCurrencyEffect: new Big(0)
},
initialValue,
investmentValues,
maxInvestmentValues,
netPerformancePercentage: {
Value: new Big(0),
WithCurrencyEffect: new Big(0)
},
netPerformanceValues,
grossPerformance: { Value: new Big(0), WithCurrencyEffect: new Big(0) },
hasErrors: totalUnits.gt(0) && (!initialValue || !unitPriceAtEndDate),
netPerformance: { Value: new Big(0), WithCurrencyEffect: new Big(0) },
averagePriceAtStartDate,
totalUnits,
totalInvestment,
investmentAtStartDate,
valueAtStartDate,
maxTotalInvestment,
averagePriceAtEndDate,
fees,
lastAveragePrice,
grossPerformanceFromSells,
totalInvestmentWithGrossPerformanceFromSell,
feesAtStartDate,
grossPerformanceAtStartDate,
netPerformanceValuesPercentage,
investmentValuesAccumulated,
timeWeightedInvestmentValues,
timeWeightedAverageInvestmentBetweenStartAndEndDate: {
Value: new Big(0),
WithCurrencyEffect: new Big(0)
}
};
}
const totalGrossPerformance = {
Value: grossPerformance.Value.minus(grossPerformanceAtStartDate.Value),
WithCurrencyEffect: grossPerformance.WithCurrencyEffect.minus(
@ -1601,6 +1668,7 @@ export class PortfolioCalculator {
};
}
@LogPerformance
private handleOrders(
orders: PortfolioOrderItem[],
indexOfStartOrder: number,
@ -1632,11 +1700,13 @@ export class PortfolioCalculator {
sumOfTimeWeightedInvestments: WithCurrencyEffect<Big>,
timeWeightedInvestmentValues: WithCurrencyEffect<{ [date: string]: Big }>,
exchangeRates: { [dateString: string]: number },
currentExchangeRate: number
currentExchangeRate: number,
calculatePerformance: boolean
) {
for (let i = 0; i < orders.length; i += 1) {
const order = orders[i];
const previousOrderDateString = i > 0 ? orders[i - 1].date : '';
if (calculatePerformance) {
this.calculateNetPerformancePercentageForDateAndSymbol(
i,
orders,
@ -1644,6 +1714,7 @@ export class PortfolioCalculator {
netPerformanceValuesPercentage,
marketSymbolMap
);
}
if (PortfolioCalculator.ENABLE_LOGGING) {
console.log();
@ -1704,6 +1775,7 @@ export class PortfolioCalculator {
fees
));
if (calculatePerformance) {
({
grossPerformanceFromSells,
totalInvestmentWithGrossPerformanceFromSell
@ -1714,6 +1786,7 @@ export class PortfolioCalculator {
totalInvestmentWithGrossPerformanceFromSell,
transactionInvestment
));
}
lastAveragePrice.Value = totalUnits.eq(0)
? new Big(0)
@ -1744,6 +1817,26 @@ export class PortfolioCalculator {
);
}
if (!calculatePerformance) {
return {
lastAveragePrice,
grossPerformance,
feesAtStartDate,
grossPerformanceAtStartDate,
averagePriceAtStartDate,
totalUnits,
totalInvestment,
investmentAtStartDate,
valueAtStartDate,
maxTotalInvestment,
averagePriceAtEndDate,
initialValue,
fees,
netPerformanceValuesPercentage,
totalInvestmentDays
};
}
const newGrossPerformance = valueOfInvestment.Value.minus(
totalInvestment.Value
).plus(grossPerformanceFromSells.Value);
@ -1829,6 +1922,7 @@ export class PortfolioCalculator {
};
}
@LogPerformance
private handleFeeAndUnitPriceOfOrder(
order: PortfolioOrderItem,
currentExchangeRate: number,
@ -1852,6 +1946,7 @@ export class PortfolioCalculator {
}
}
@LogPerformance
private calculateNetPerformancePercentageForDateAndSymbol(
i: number,
orders: PortfolioOrderItem[],
@ -1895,6 +1990,7 @@ export class PortfolioCalculator {
}
}
@LogPerformance
private handleIfPreviousOrderIsStake(
netPerformanceValuesPercentage: { [date: string]: Big },
order: PortfolioOrderItem,
@ -1906,6 +2002,7 @@ export class PortfolioCalculator {
.minus(1);
}
@LogPerformance
private stakeHandling(
previousOrder: PortfolioOrderItem,
marketSymbolMap: { [date: string]: { [symbol: string]: Big } },
@ -1925,6 +2022,7 @@ export class PortfolioCalculator {
: new Big(0);
}
@LogPerformance
private ispreviousOrderStakeAndHasInformation(
previousOrder: PortfolioOrderItem,
marketSymbolMap: { [date: string]: { [symbol: string]: Big } }
@ -1936,6 +2034,7 @@ export class PortfolioCalculator {
);
}
@LogPerformance
private needsStakeHandling(
order: PortfolioOrderItem,
marketSymbolMap: { [date: string]: { [symbol: string]: Big } },
@ -1952,6 +2051,7 @@ export class PortfolioCalculator {
);
}
@LogPerformance
private handleLoggingOfInvestmentMetrics(
totalInvestment: WithCurrencyEffect<Big>,
order: PortfolioOrderItem,
@ -1986,6 +2086,7 @@ export class PortfolioCalculator {
}
}
@LogPerformance
private calculateNetPerformancePercentage(
timeWeightedAverageInvestmentBetweenStartAndEndDate: WithCurrencyEffect<Big>,
totalNetPerformance: WithCurrencyEffect<Big>
@ -2007,6 +2108,7 @@ export class PortfolioCalculator {
};
}
@LogPerformance
private calculateInvestmentSpecificMetrics(
averagePriceAtStartDate: Big,
i: number,
@ -2132,6 +2234,7 @@ export class PortfolioCalculator {
};
}
@LogPerformance
private calculatePerformancesForDateAndReturnTotalInvestmentDays(
isChartMode: boolean,
i: number,
@ -2234,6 +2337,7 @@ export class PortfolioCalculator {
return totalInvestmentDays;
}
@LogPerformance
private calculateSellOrders(
order: PortfolioOrderItem,
lastAveragePrice: WithCurrencyEffect<Big>,
@ -2280,6 +2384,7 @@ export class PortfolioCalculator {
};
}
@LogPerformance
private calculateInitialValue(
i: number,
indexOfStartOrder: number,
@ -2315,6 +2420,7 @@ export class PortfolioCalculator {
};
}
@LogPerformance
private calculateAveragePriceAtEnd(
i: number,
indexOfEndOrder: number,
@ -2328,6 +2434,7 @@ export class PortfolioCalculator {
return averagePriceAtEndDate;
}
@LogPerformance
private getTransactionInvestment(
order: PortfolioOrderItem,
totalUnits: Big,
@ -2350,6 +2457,7 @@ export class PortfolioCalculator {
: new Big(0);
}
@LogPerformance
private calculateAveragePrice(
averagePriceAtStartDate: Big,
i: number,
@ -2367,6 +2475,7 @@ export class PortfolioCalculator {
return averagePriceAtStartDate;
}
@LogPerformance
private handleStartOrder(
order: PortfolioOrderItem,
indexOfStartOrder: number,
@ -2384,6 +2493,7 @@ export class PortfolioCalculator {
}
}
@LogPerformance
private handleLogging(
symbol: string,
orders: PortfolioOrderItem[],
@ -2429,6 +2539,7 @@ export class PortfolioCalculator {
}
}
@LogPerformance
private sortOrdersByTime(orders: PortfolioOrderItem[]) {
return sortBy(orders, (order) => {
let sortIndex = new Date(order.date);
@ -2445,6 +2556,7 @@ export class PortfolioCalculator {
});
}
@LogPerformance
private handleChartMode(
isChartMode: boolean,
orders: PortfolioOrderItem[],
@ -2480,6 +2592,7 @@ export class PortfolioCalculator {
return { day, lastUnitPrice };
}
@LogPerformance
private handleDay(
datesWithOrders: {},
day: Date,
@ -2519,6 +2632,7 @@ export class PortfolioCalculator {
}
}
@LogPerformance
private addSyntheticStartAndEndOrders(
orders: PortfolioOrderItem[],
symbol: string,

8
apps/api/src/app/portfolio/portfolio.controller.ts

@ -1,3 +1,4 @@
import { LogPerformance } from '@ghostfolio/api/aop/logging.interceptor';
import { AccessService } from '@ghostfolio/api/app/access/access.service';
import { UserService } from '@ghostfolio/api/app/user/user.service';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
@ -71,7 +72,8 @@ export class PortfolioController {
@Query('accounts') filterByAccounts?: string,
@Query('assetClasses') filterByAssetClasses?: string,
@Query('range') dateRange: DateRange = 'max',
@Query('tags') filterByTags?: string
@Query('tags') filterByTags?: string,
@Query('isAllocation') isAllocation: boolean = false
): Promise<PortfolioDetails & { hasError: boolean }> {
let hasDetails = true;
let hasError = false;
@ -104,7 +106,8 @@ export class PortfolioController {
dateRange,
filters,
impersonationId,
userId: this.request.user.id
userId: this.request.user.id,
isAllocation
});
if (hasErrors || hasNotDefinedValuesInObject(holdings)) {
@ -375,6 +378,7 @@ export class PortfolioController {
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@UseInterceptors(TransformDataSourceInResponseInterceptor)
@Version('2')
@LogPerformance
public async getPerformanceV2(
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string,
@Query('accounts') filterByAccounts?: string,

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

@ -1,3 +1,4 @@
import { LogPerformance } from '@ghostfolio/api/aop/logging.interceptor';
import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service';
import { AccountService } from '@ghostfolio/api/app/account/account.service';
import { CashDetails } from '@ghostfolio/api/app/account/interfaces/cash-details.interface';
@ -113,6 +114,7 @@ export class PortfolioService {
private readonly userService: UserService
) {}
@LogPerformance
public async getAccounts({
filters,
userId,
@ -182,6 +184,7 @@ export class PortfolioService {
});
}
@LogPerformance
public async getAccountsWithAggregations({
filters,
userId,
@ -218,6 +221,7 @@ export class PortfolioService {
};
}
@LogPerformance
public async getDividends({
dateRange,
filters,
@ -259,6 +263,7 @@ export class PortfolioService {
});
}
@LogPerformance
public async getInvestments({
dateRange,
filters,
@ -332,18 +337,21 @@ export class PortfolioService {
};
}
@LogPerformance
public async getDetails({
dateRange = 'max',
filters,
impersonationId,
userId,
withExcludedAccounts = false
withExcludedAccounts = false,
isAllocation = false
}: {
dateRange?: DateRange;
filters?: Filter[];
impersonationId: string;
userId: string;
withExcludedAccounts?: boolean;
isAllocation?: boolean;
}): Promise<PortfolioDetails & { hasErrors: boolean }> {
userId = await this.getUserId(impersonationId, userId);
const user = await this.userService.user({ id: userId });
@ -373,8 +381,11 @@ export class PortfolioService {
transactionPoints[0]?.date ?? format(new Date(), DATE_FORMAT)
);
const startDate = this.getStartDate(dateRange, portfolioStart);
const currentPositions =
await portfolioCalculator.getCurrentPositions(startDate);
const currentPositions = await portfolioCalculator.getCurrentPositions(
startDate,
new Date(Date.now()),
!isAllocation
);
const cashDetails = await this.accountService.getCashDetails({
filters,
@ -465,8 +476,9 @@ export class PortfolioService {
accounts,
holdings
);
const summary = await this.getSummary({
let summary;
if (!isAllocation) {
summary = await this.getSummary({
impersonationId,
userCurrency,
userId,
@ -476,6 +488,11 @@ export class PortfolioService {
holdings
})
});
}
var netWorth =
summary?.netWorth ??
(await this.getNetWorth(impersonationId, userId, userCurrency));
return {
accounts,
@ -483,14 +500,15 @@ export class PortfolioService {
platforms,
summary,
filteredValueInBaseCurrency: filteredValueInBaseCurrency.toNumber(),
filteredValueInPercentage: summary.netWorth
? filteredValueInBaseCurrency.div(summary.netWorth).toNumber()
filteredValueInPercentage: netWorth
? filteredValueInBaseCurrency.div(netWorth).toNumber()
: 0,
hasErrors: currentPositions.hasErrors,
totalValueInBaseCurrency: summary.netWorth
totalValueInBaseCurrency: netWorth
};
}
@LogPerformance
private handlePositions(
currentPositions: CurrentPositions,
portfolioItemsNow: { [symbol: string]: TimelinePosition },
@ -567,6 +585,7 @@ export class PortfolioService {
}
}
@LogPerformance
private async handleCashPosition(
filters: Filter[],
isFilteredByAccount: boolean,
@ -592,6 +611,7 @@ export class PortfolioService {
}
}
@LogPerformance
private async handleEmergencyFunds(
filters: Filter[],
cashDetails: CashDetails,
@ -647,6 +667,7 @@ export class PortfolioService {
return filteredValueInBaseCurrency;
}
@LogPerformance
private calculateMarketsAllocation(
symbolProfile: EnhancedSymbolProfile,
markets: {
@ -719,6 +740,7 @@ export class PortfolioService {
}
}
@LogPerformance
public async getPosition(
aDataSource: DataSource,
aImpersonationId: string,
@ -1049,6 +1071,7 @@ export class PortfolioService {
}
}
@LogPerformance
public async getPositions({
dateRange = 'max',
filters,
@ -1192,6 +1215,7 @@ export class PortfolioService {
};
}
@LogPerformance
public async getPerformance({
dateRange = 'max',
filters,
@ -1386,6 +1410,7 @@ export class PortfolioService {
};
}
@LogPerformance
public async getReport(impersonationId: string): Promise<PortfolioReport> {
const userId = await this.getUserId(impersonationId, this.request.user.id);
const user = await this.userService.user({ id: userId });
@ -1485,6 +1510,7 @@ export class PortfolioService {
};
}
@LogPerformance
private async getCashPositions({
cashDetails,
userCurrency,
@ -1535,6 +1561,7 @@ export class PortfolioService {
return cashPositions;
}
@LogPerformance
private async getChart({
dateRange = 'max',
impersonationId,
@ -1599,6 +1626,7 @@ export class PortfolioService {
};
}
@LogPerformance
private getDividendsByGroup({
dividends,
groupBy
@ -1661,6 +1689,7 @@ export class PortfolioService {
return dividendsByGroup;
}
@LogPerformance
private getEmergencyFundPositionsValueInBaseCurrency({
holdings
}: {
@ -1684,6 +1713,7 @@ export class PortfolioService {
return valueInBaseCurrencyOfEmergencyFundPositions.toNumber();
}
@LogPerformance
private getFees({
activities,
date = new Date(0),
@ -1711,6 +1741,7 @@ export class PortfolioService {
);
}
@LogPerformance
private getInitialCashPosition({
balance,
currency
@ -1743,6 +1774,7 @@ export class PortfolioService {
};
}
@LogPerformance
private getStartDate(aDateRange: DateRange, portfolioStart: Date) {
switch (aDateRange) {
case '1d':
@ -1811,6 +1843,7 @@ export class PortfolioService {
return portfolioStart;
}
@LogPerformance
private getStreaks({
investments,
savingsRate
@ -1833,6 +1866,43 @@ export class PortfolioService {
return { currentStreak, longestStreak };
}
@LogPerformance
private async getNetWorth(
impersonationId: string,
userId: string,
userCurrency: string
) {
userId = await this.getUserId(impersonationId, userId);
const { orders, portfolioOrders, transactionPoints } =
await this.getTransactionPoints({
userId,
withExcludedAccounts: true
});
const portfolioCalculator = new PortfolioCalculator({
currency: userCurrency,
currentRateService: this.currentRateService,
exchangeRateDataService: this.exchangeRateDataService,
orders: portfolioOrders
});
const portfolioStart = parseDate(
transactionPoints[0]?.date ?? format(new Date(), DATE_FORMAT)
);
portfolioCalculator.setTransactionPoints(transactionPoints);
const { currentValue } = await portfolioCalculator.getCurrentPositions(
portfolioStart,
new Date(Date.now()),
false
);
return currentValue;
}
@LogPerformance
private async getSummary({
balanceInBaseCurrency,
emergencyFundPositionsValueInBaseCurrency,
@ -1848,11 +1918,26 @@ export class PortfolioService {
}): Promise<PortfolioSummary> {
userId = await this.getUserId(impersonationId, userId);
const user = await this.userService.user({ id: userId });
const performanceInformation = await this.getPerformance({
impersonationId,
userId
});
let performanceInformation: PortfolioPerformanceResponse = {
chart: [],
firstOrderDate: undefined,
performance: {
annualizedPerformancePercent: 0,
currentGrossPerformance: 0,
currentGrossPerformancePercent: 0,
currentGrossPerformancePercentWithCurrencyEffect: 0,
currentGrossPerformanceWithCurrencyEffect: 0,
currentNetPerformance: 0,
currentNetPerformancePercent: 0,
currentNetPerformancePercentWithCurrencyEffect: 0,
currentNetPerformanceWithCurrencyEffect: 0,
currentNetWorth: 0,
currentValue: 0,
totalInvestment: 0
},
errors: [],
hasErrors: false
};
const { activities } = await this.orderService.getOrders({
userCurrency,
@ -1871,6 +1956,13 @@ export class PortfolioService {
let totalSell = 0;
let activitiesUsed: Activity[] = [];
let ordersCount = 0;
let excludedAccountsAndActivities = 0;
const firstOrderDate = activities[0]?.date;
performanceInformation = await this.getPerformance({
impersonationId,
userId
});
for (let order of activities) {
if (order.Account?.isExcluded ?? false) {
excludedActivities.push(order);
@ -1917,8 +2009,6 @@ export class PortfolioService {
)
);
const firstOrderDate = activitiesUsed[0]?.date;
const cash = new Big(balanceInBaseCurrency)
.minus(emergencyFund)
.plus(emergencyFundPositionsValueInBaseCurrency)
@ -1942,12 +2032,11 @@ export class PortfolioService {
currency: userCurrency,
withExcludedAccounts: true
});
const excludedBalanceInBaseCurrency = new Big(
cashDetailsWithExcludedAccounts.balanceInBaseCurrency
).minus(balanceInBaseCurrency);
const excludedAccountsAndActivities = excludedBalanceInBaseCurrency
excludedAccountsAndActivities = excludedBalanceInBaseCurrency
.plus(totalOfExcludedActivities)
.toNumber();
@ -2003,6 +2092,7 @@ export class PortfolioService {
};
}
@LogPerformance
private getSumOfActivityType({
activities,
activityType,
@ -2036,6 +2126,7 @@ export class PortfolioService {
);
}
@LogPerformance
private async getTransactionPoints({
filters,
includeDrafts = false,
@ -2111,6 +2202,7 @@ export class PortfolioService {
return impersonationUserId || aUserId;
}
@LogPerformance
private async getValueOfAccountsAndPlatforms({
filters = [],
orders,
@ -2261,6 +2353,7 @@ export class PortfolioService {
return { accounts, platforms };
}
@LogPerformance
private mergeHistoricalDataItems(
accountBalanceItems: HistoricalDataItem[],
performanceChartItems: HistoricalDataItem[]

32
apps/api/src/main.ts

@ -5,6 +5,7 @@ import type { NestExpressApplication } from '@nestjs/platform-express';
import * as bodyParser from 'body-parser';
import helmet from 'helmet';
import { LoggingInterceptor } from './aop/logging.interceptor';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { HtmlTemplateMiddleware } from './middlewares/html-template.middleware';
@ -13,10 +14,34 @@ async function bootstrap() {
const configApp = await NestFactory.create(AppModule);
const configService = configApp.get<ConfigService>(ConfigService);
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
logger: environment.production
let logLevelArray = [];
let logLevel = configService.get<string>('LOG_LEVEL');
switch (logLevel) {
case 'verbose':
logLevelArray.push(['debug', 'error', 'log', 'verbose', 'warn']);
break;
case 'debug':
logLevelArray.push(['debug', 'error', 'log', 'warn']);
break;
case 'log':
logLevelArray.push([, 'error', 'log', 'warn']);
break;
case 'warn':
logLevelArray.push(['error', 'warn']);
break;
case 'error':
logLevelArray.push(['error']);
break;
default:
logLevelArray = environment.production
? ['error', 'log', 'warn']
: ['debug', 'error', 'log', 'verbose', 'warn']
: ['debug', 'error', 'log', 'verbose', 'warn'];
break;
}
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
logger: logLevelArray
});
app.enableCors();
@ -25,6 +50,7 @@ async function bootstrap() {
type: VersioningType.URI
});
app.setGlobalPrefix('api', { exclude: ['sitemap.xml'] });
app.useGlobalInterceptors(new LoggingInterceptor());
app.useGlobalPipes(
new ValidationPipe({
forbidNonWhitelisted: true,

1
apps/api/src/services/configuration/configuration.service.ts

@ -22,6 +22,7 @@ export class ConfigurationService {
API_KEY_RAPID_API: str({ default: '' }),
CACHE_QUOTES_TTL: num({ default: 1 }),
CACHE_TTL: num({ default: 1 }),
LOG_LEVEL: str({ default: '' }),
DATA_SOURCE_EXCHANGE_RATES: str({ default: DataSource.YAHOO }),
DATA_SOURCE_IMPORT: str({ default: DataSource.YAHOO }),
DATA_SOURCES: json({

2
apps/api/src/services/data-provider/data-provider.service.ts

@ -1,3 +1,4 @@
import { LogPerformance } from '@ghostfolio/api/aop/logging.interceptor';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
@ -332,6 +333,7 @@ export class DataProviderService {
return result;
}
@LogPerformance
public async getQuotes({
items,
requestTimeout,

2
apps/api/src/services/symbol-profile/symbol-profile.service.ts

@ -1,3 +1,4 @@
import { LogPerformance } from '@ghostfolio/api/aop/logging.interceptor';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
import {
@ -39,6 +40,7 @@ export class SymbolProfileService {
});
}
@LogPerformance
public async getSymbolProfiles(
aUniqueAssets: UniqueAsset[]
): Promise<EnhancedSymbolProfile[]> {

5
apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts

@ -205,7 +205,10 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
private fetchPortfolioDetails() {
return this.dataService.fetchPortfolioDetails({
filters: this.userService.getFilters()
filters: this.userService.getFilters(),
parameters: {
isAllocation: true
}
});
}

16
apps/client/src/app/services/data.service.ts

@ -389,13 +389,25 @@ export class DataService {
}
public fetchPortfolioDetails({
filters
filters,
parameters
}: {
filters?: Filter[];
parameters?: {
[param: string]:
| string
| number
| boolean
| readonly (string | number | boolean)[];
};
} = {}): Observable<PortfolioDetails> {
let params = this.buildFiltersAsQueryParams({ filters }).appendAll(
parameters
);
return this.http
.get<any>('/api/v1/portfolio/details', {
params: this.buildFiltersAsQueryParams({ filters })
params
})
.pipe(
map((response) => {

Loading…
Cancel
Save