Browse Source

Merge pull request #90 from dandevaud/feature/Fix-Time-weighted-calculation

Feature/fix time weighted calculation
pull/5027/head
dandevaud 1 year ago
committed by GitHub
parent
commit
8e17635663
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 299
      apps/api/src/app/portfolio/calculator/constantPortfolioReturn/portfolio-calculator.ts
  2. 18
      apps/api/src/app/portfolio/calculator/portfolio-calculator.factory.ts
  3. 2
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  4. 3
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts
  5. 3
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy-and-sell.spec.ts
  6. 3
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy.spec.ts
  7. 3
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts
  8. 3
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-fee.spec.ts
  9. 3
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-googl-buy.spec.ts
  10. 3
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-item.spec.ts
  11. 3
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-liability.spec.ts
  12. 3
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-msft-buy-with-dividend.spec.ts
  13. 3
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-no-orders.spec.ts
  14. 3
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-novn-buy-and-sell-partially.spec.ts
  15. 3
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-novn-buy-and-sell.spec.ts
  16. 3
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.spec.ts
  17. 94
      apps/api/src/app/portfolio/current-rate.service.ts
  18. 8
      apps/api/src/app/portfolio/portfolio.service.ts
  19. 17
      apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts

299
apps/api/src/app/portfolio/calculator/constantPortfolioReturn/portfolio-calculator.ts

@ -1,27 +1,34 @@
import { LogPerformance } from '@ghostfolio/api/aop/logging.interceptor';
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { OrderService } from '@ghostfolio/api/app/order/order.service';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import {
getFactor,
getInterval
} from '@ghostfolio/api/helper/portfolio.helper';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
import { MAX_CHART_ITEMS } from '@ghostfolio/common/config';
import { DATE_FORMAT, parseDate, resetHours } from '@ghostfolio/common/helper';
import { HistoricalDataItem } from '@ghostfolio/common/interfaces';
import { DateRange } from '@ghostfolio/common/types';
import { Logger } from '@nestjs/common';
import { Inject, Logger } from '@nestjs/common';
import { Big } from 'big.js';
import {
addDays,
differenceInDays,
eachDayOfInterval,
endOfDay,
format,
isAfter,
isBefore,
isEqual,
subDays
} from 'date-fns';
import { CurrentRateService } from '../../current-rate.service';
import { DateQuery } from '../../interfaces/date-query.interface';
import { PortfolioOrder } from '../../interfaces/portfolio-order.interface';
import { TWRPortfolioCalculator } from '../twr/portfolio-calculator';
@ -29,6 +36,47 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator {
private holdings: { [date: string]: { [symbol: string]: Big } } = {};
private holdingCurrencies: { [symbol: string]: string } = {};
constructor(
{
accountBalanceItems,
activities,
configurationService,
currency,
currentRateService,
dateRange,
exchangeRateDataService,
redisCacheService,
useCache,
userId
}: {
accountBalanceItems: HistoricalDataItem[];
activities: Activity[];
configurationService: ConfigurationService;
currency: string;
currentRateService: CurrentRateService;
dateRange: DateRange;
exchangeRateDataService: ExchangeRateDataService;
redisCacheService: RedisCacheService;
useCache: boolean;
userId: string;
},
@Inject()
private orderService: OrderService
) {
super({
accountBalanceItems,
activities,
configurationService,
currency,
currentRateService,
dateRange,
exchangeRateDataService,
redisCacheService,
useCache,
userId
});
}
@LogPerformance
public async getChart({
dateRange = 'max',
@ -55,14 +103,13 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator {
if (!withTimeWeightedReturn) {
return item;
} else {
let itemResult = await item;
let dates = itemResult.map((item) => parseDate(item.date));
let timeWeighted = await this.getTimeWeightedChartData({
step,
end: endDate,
start: startDate
dates
});
return item.then((data) => {
return data.map((item) => {
return itemResult.map((item) => {
let timeWeightedItem = timeWeighted.find(
(timeWeightedItem) => timeWeightedItem.date === item.date
);
@ -75,32 +122,99 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator {
return item;
});
}
}
@LogPerformance
public async getUnfilteredNetWorth(currency: string): Promise<Big> {
const activities = await this.orderService.getOrders({
userId: this.userId,
userCurrency: currency,
types: ['BUY', 'SELL', 'STAKE'],
withExcludedAccounts: true
});
const orders = this.activitiesToPortfolioOrder(activities.activities);
const start = orders.reduce(
(date, order) =>
parseDate(date.date).getTime() < parseDate(order.date).getTime()
? date
: order,
{ date: orders[0].date }
).date;
const end = new Date(Date.now());
const holdings = await this.getHoldings(orders, parseDate(start), end);
const marketMap = await this.currentRateService.getToday(
this.mapToDataGatheringItems(orders)
);
const endString = format(end, DATE_FORMAT);
let exchangeRates = await Promise.all(
Object.keys(holdings[endString]).map(async (holding) => {
let symbol = marketMap.find((m) => m.symbol === holding);
let symbolCurrency = this.getCurrencyFromActivities(orders, holding);
let exchangeRate = await this.exchangeRateDataService.toCurrencyAtDate(
1,
symbolCurrency,
this.currency,
end
);
return { symbolCurrency, exchangeRate };
})
);
let currencyRates = exchangeRates.reduce<{ [currency: string]: number }>(
(all, currency): { [currency: string]: number } => {
all[currency.symbolCurrency] ??= currency.exchangeRate;
return all;
},
{}
);
let totalInvestment = await Object.keys(holdings[endString]).reduce(
(sum, holding) => {
if (!holdings[endString][holding].toNumber()) {
return sum;
}
let symbol = marketMap.find((m) => m.symbol === holding);
if (symbol?.marketPrice === undefined) {
Logger.warn(
`Missing historical market data for ${holding} (${end})`,
'PortfolioCalculator'
);
return sum;
} else {
let symbolCurrency = this.getCurrency(holding);
let price = new Big(currencyRates[symbolCurrency]).mul(
symbol.marketPrice
);
return sum.plus(new Big(price).mul(holdings[endString][holding]));
}
},
new Big(0)
);
return totalInvestment;
}
@LogPerformance
private async getTimeWeightedChartData({
end = new Date(Date.now()),
start,
step = 1
dates
}: {
end?: Date;
start: Date;
step?: number;
dates?: Date[];
}): Promise<HistoricalDataItem[]> {
let marketMapTask = this.computeMarketMap({ gte: start, lte: end });
const timelineHoldings = await this.getHoldings(start, end);
const calculationDates = Object.keys(timelineHoldings)
.filter((date) => {
let parsed = parseDate(date);
return (
isAfter(parsed, subDays(start, 1)) &&
isBefore(parsed, addDays(end, 1))
dates = dates.sort((a, b) => a.getTime() - b.getTime());
const start = dates[0];
const end = dates[dates.length - 1];
let marketMapTask = this.computeMarketMap({
gte: start,
lt: addDays(end, 1)
});
const timelineHoldings = await this.getHoldings(
this.activities,
start,
end
);
})
.sort((a, b) => parseDate(a).getTime() - parseDate(b).getTime());
let data: HistoricalDataItem[] = [];
const startString = format(start, DATE_FORMAT);
@ -119,7 +233,7 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator {
valueWithCurrencyEffect: 0
});
await marketMapTask;
this.marketMap = await marketMapTask;
let totalInvestment = Object.keys(timelineHoldings[startString]).reduce(
(sum, holding) => {
@ -135,9 +249,9 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator {
let previousNetPerformanceInPercentage = new Big(0);
let previousNetPerformanceInPercentageWithCurrencyEffect = new Big(0);
for (let i = 1; i < calculationDates.length; i++) {
const date = calculationDates[i];
const previousDate = calculationDates[i - 1];
for (let i = 1; i < dates.length; i++) {
const date = format(dates[i], DATE_FORMAT);
const previousDate = format(dates[i - 1], DATE_FORMAT);
const holdings = timelineHoldings[previousDate];
let newTotalInvestment = new Big(0);
let netPerformanceInPercentage = new Big(0);
@ -158,24 +272,28 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator {
netPerformanceInPercentageWithCurrencyEffect,
newTotalInvestment
));
totalInvestment = newTotalInvestment;
}
totalInvestment = newTotalInvestment;
previousNetPerformanceInPercentage =
previousNetPerformanceInPercentage.mul(
netPerformanceInPercentage.plus(1)
);
previousNetPerformanceInPercentage = previousNetPerformanceInPercentage
.plus(1)
.mul(netPerformanceInPercentage.plus(1))
.minus(1);
previousNetPerformanceInPercentageWithCurrencyEffect =
previousNetPerformanceInPercentageWithCurrencyEffect.mul(
netPerformanceInPercentageWithCurrencyEffect.plus(1)
);
previousNetPerformanceInPercentageWithCurrencyEffect
.plus(1)
.mul(netPerformanceInPercentageWithCurrencyEffect.plus(1))
.minus(1);
data.push({
date,
netPerformanceInPercentage:
previousNetPerformanceInPercentage.toNumber(),
netPerformanceInPercentage: previousNetPerformanceInPercentage
.mul(100)
.toNumber(),
netPerformanceInPercentageWithCurrencyEffect:
previousNetPerformanceInPercentageWithCurrencyEffect.toNumber()
previousNetPerformanceInPercentageWithCurrencyEffect
.mul(100)
.toNumber()
});
}
@ -210,8 +328,9 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator {
if (previousHolding.eq(0)) {
return {
netPerformanceInPercentage: new Big(0),
netPerformanceInPercentageWithCurrencyEffect: new Big(0),
netPerformanceInPercentage: netPerformanceInPercentage,
netPerformanceInPercentageWithCurrencyEffect:
netPerformanceInPercentageWithCurrencyEffect,
newTotalInvestment: newTotalInvestment.plus(
timelineHoldings[date][holding].mul(priceInBaseCurrency)
)
@ -223,8 +342,9 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator {
'PortfolioCalculator'
);
return {
netPerformanceInPercentage: new Big(0),
netPerformanceInPercentageWithCurrencyEffect: new Big(0),
netPerformanceInPercentage: netPerformanceInPercentage,
netPerformanceInPercentageWithCurrencyEffect:
netPerformanceInPercentageWithCurrencyEffect,
newTotalInvestment: newTotalInvestment.plus(
timelineHoldings[date][holding].mul(priceInBaseCurrency)
)
@ -242,7 +362,7 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator {
: new Big(0);
const portfolioWeight = totalInvestment.toNumber()
? previousHolding.mul(previousPriceInBaseCurrency).div(totalInvestment)
: 0;
: new Big(0);
netPerformanceInPercentage = netPerformanceInPercentage.plus(
currentPrice.div(previousPrice).minus(1).mul(portfolioWeight)
@ -268,8 +388,16 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator {
@LogPerformance
private getCurrency(symbol: string) {
return this.getCurrencyFromActivities(this.activities, symbol);
}
@LogPerformance
private getCurrencyFromActivities(
activities: PortfolioOrder[],
symbol: string
) {
if (!this.holdingCurrencies[symbol]) {
this.holdingCurrencies[symbol] = this.activities.find(
this.holdingCurrencies[symbol] = activities.find(
(a) => a.SymbolProfile.symbol === symbol
).SymbolProfile.currency;
}
@ -278,7 +406,11 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator {
}
@LogPerformance
private async getHoldings(start: Date, end: Date) {
private async getHoldings(
activities: PortfolioOrder[],
start: Date,
end: Date
) {
if (
this.holdings &&
Object.keys(this.holdings).some((h) =>
@ -291,13 +423,25 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator {
return this.holdings;
}
this.computeHoldings(start, end);
this.computeHoldings(activities, start, end);
return this.holdings;
}
@LogPerformance
private async computeHoldings(start: Date, end: Date) {
const investmentByDate = this.getInvestmentByDate();
private async computeHoldings(
activities: PortfolioOrder[],
start: Date,
end: Date
) {
const investmentByDate = this.getInvestmentByDate(activities);
this.calculateHoldings(investmentByDate, start, end);
}
private calculateHoldings(
investmentByDate: { [date: string]: PortfolioOrder[] },
start: Date,
end: Date
) {
const transactionDates = Object.keys(investmentByDate).sort();
let dates = eachDayOfInterval({ start, end }, { step: 1 })
.map((date) => {
@ -360,8 +504,10 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator {
}
@LogPerformance
private getInvestmentByDate(): { [date: string]: PortfolioOrder[] } {
return this.activities.reduce((groupedByDate, order) => {
private getInvestmentByDate(activities: PortfolioOrder[]): {
[date: string]: PortfolioOrder[];
} {
return activities.reduce((groupedByDate, order) => {
if (!groupedByDate[order.date]) {
groupedByDate[order.date] = [];
}
@ -373,8 +519,10 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator {
}
@LogPerformance
private async computeMarketMap(dateQuery: { gte: Date; lte: Date }) {
const dataGatheringItems: IDataGatheringItem[] = this.activities
private mapToDataGatheringItems(
orders: PortfolioOrder[]
): IDataGatheringItem[] {
return orders
.map((activity) => {
return {
symbol: activity.SymbolProfile.symbol,
@ -385,6 +533,14 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator {
(gathering, i, arr) =>
arr.findIndex((t) => t.symbol === gathering.symbol) === i
);
}
@LogPerformance
private async computeMarketMap(dateQuery: DateQuery): Promise<{
[date: string]: { [symbol: string]: Big };
}> {
const dataGatheringItems: IDataGatheringItem[] =
this.mapToDataGatheringItems(this.activities);
const { values: marketSymbols } = await this.currentRateService.getValues({
dataGatheringItems,
dateQuery
@ -408,6 +564,41 @@ export class CPRPortfolioCalculator extends TWRPortfolioCalculator {
}
}
this.marketMap = marketSymbolMap;
return marketSymbolMap;
}
@LogPerformance
private activitiesToPortfolioOrder(activities: Activity[]): PortfolioOrder[] {
return activities
.map(
({
date,
fee,
quantity,
SymbolProfile,
tags = [],
type,
unitPrice
}) => {
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
date = endOfDay(new Date(Date.now()));
}
return {
SymbolProfile,
tags,
type,
date: format(date, DATE_FORMAT),
fee: new Big(fee),
quantity: new Big(quantity),
unitPrice: new Big(unitPrice)
};
}
)
.sort((a, b) => {
return a.date?.localeCompare(b.date);
});
}
}

18
apps/api/src/app/portfolio/calculator/portfolio-calculator.factory.ts

@ -8,6 +8,7 @@ import { DateRange, UserWithSettings } from '@ghostfolio/common/types';
import { Injectable } from '@nestjs/common';
import { OrderService } from '../../order/order.service';
import { CPRPortfolioCalculator } from './constantPortfolioReturn/portfolio-calculator';
import { MWRPortfolioCalculator } from './mwr/portfolio-calculator';
import { PortfolioCalculator } from './portfolio-calculator';
@ -25,7 +26,8 @@ export class PortfolioCalculatorFactory {
private readonly configurationService: ConfigurationService,
private readonly currentRateService: CurrentRateService,
private readonly exchangeRateDataService: ExchangeRateDataService,
private readonly redisCacheService: RedisCacheService
private readonly redisCacheService: RedisCacheService,
private readonly orderservice: OrderService
) {}
public createCalculator({
@ -64,7 +66,8 @@ export class PortfolioCalculatorFactory {
redisCacheService: this.redisCacheService
});
case PerformanceCalculationType.TWR:
return new CPRPortfolioCalculator({
return new CPRPortfolioCalculator(
{
accountBalanceItems,
activities,
currency,
@ -75,9 +78,12 @@ export class PortfolioCalculatorFactory {
configurationService: this.configurationService,
exchangeRateDataService: this.exchangeRateDataService,
redisCacheService: this.redisCacheService
});
},
this.orderservice
);
case PerformanceCalculationType.CPR:
return new CPRPortfolioCalculator({
return new CPRPortfolioCalculator(
{
accountBalanceItems,
activities,
currency,
@ -88,7 +94,9 @@ export class PortfolioCalculatorFactory {
configurationService: this.configurationService,
exchangeRateDataService: this.exchangeRateDataService,
redisCacheService: this.redisCacheService
});
},
this.orderservice
);
default:
throw new Error('Invalid calculation type');
}

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

@ -65,7 +65,7 @@ export abstract class PortfolioCalculator {
private startDate: Date;
private transactionPoints: TransactionPoint[];
private useCache: boolean;
private userId: string;
protected userId: string;
protected marketMap: { [date: string]: { [symbol: string]: Big } } = {};
public constructor({

3
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts

@ -61,7 +61,8 @@ describe('PortfolioCalculator', () => {
configurationService,
currentRateService,
exchangeRateDataService,
redisCacheService
redisCacheService,
null
);
});

3
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy-and-sell.spec.ts

@ -61,7 +61,8 @@ describe('PortfolioCalculator', () => {
configurationService,
currentRateService,
exchangeRateDataService,
redisCacheService
redisCacheService,
null
);
});

3
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy.spec.ts

@ -61,7 +61,8 @@ describe('PortfolioCalculator', () => {
configurationService,
currentRateService,
exchangeRateDataService,
redisCacheService
redisCacheService,
null
);
});

3
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts

@ -74,7 +74,8 @@ describe('PortfolioCalculator', () => {
configurationService,
currentRateService,
exchangeRateDataService,
redisCacheService
redisCacheService,
null
);
});

3
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-fee.spec.ts

@ -61,7 +61,8 @@ describe('PortfolioCalculator', () => {
configurationService,
currentRateService,
exchangeRateDataService,
redisCacheService
redisCacheService,
null
);
});

3
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-googl-buy.spec.ts

@ -74,7 +74,8 @@ describe('PortfolioCalculator', () => {
configurationService,
currentRateService,
exchangeRateDataService,
redisCacheService
redisCacheService,
null
);
});

3
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-item.spec.ts

@ -61,7 +61,8 @@ describe('PortfolioCalculator', () => {
configurationService,
currentRateService,
exchangeRateDataService,
redisCacheService
redisCacheService,
null
);
});

3
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-liability.spec.ts

@ -61,7 +61,8 @@ describe('PortfolioCalculator', () => {
configurationService,
currentRateService,
exchangeRateDataService,
redisCacheService
redisCacheService,
null
);
});

3
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-msft-buy-with-dividend.spec.ts

@ -74,7 +74,8 @@ describe('PortfolioCalculator', () => {
configurationService,
currentRateService,
exchangeRateDataService,
redisCacheService
redisCacheService,
null
);
});

3
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-no-orders.spec.ts

@ -57,7 +57,8 @@ describe('PortfolioCalculator', () => {
configurationService,
currentRateService,
exchangeRateDataService,
redisCacheService
redisCacheService,
null
);
});

3
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-novn-buy-and-sell-partially.spec.ts

@ -61,7 +61,8 @@ describe('PortfolioCalculator', () => {
configurationService,
currentRateService,
exchangeRateDataService,
redisCacheService
redisCacheService,
null
);
});

3
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-novn-buy-and-sell.spec.ts

@ -61,7 +61,8 @@ describe('PortfolioCalculator', () => {
configurationService,
currentRateService,
exchangeRateDataService,
redisCacheService
redisCacheService,
null
);
});

3
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.spec.ts

@ -29,7 +29,8 @@ describe('PortfolioCalculator', () => {
configurationService,
currentRateService,
exchangeRateDataService,
redisCacheService
redisCacheService,
null
);
});

94
apps/api/src/app/portfolio/current-rate.service.ts

@ -1,6 +1,7 @@
import { OrderService } from '@ghostfolio/api/app/order/order.service';
import { DateQueryHelper } from '@ghostfolio/api/helper/dateQueryHelper';
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
import { resetHours } from '@ghostfolio/common/helper';
import {
@ -48,38 +49,12 @@ export class CurrentRateService {
if (includesToday) {
promises.push(
this.dataProviderService
.getQuotes({ items: dataGatheringItems, user: this.request?.user })
.then((dataResultProvider) => {
const result: GetValueObject[] = [];
for (const dataGatheringItem of dataGatheringItems) {
if (
dataResultProvider?.[dataGatheringItem.symbol]?.dataProviderInfo
) {
dataProviderInfos.push(
dataResultProvider[dataGatheringItem.symbol].dataProviderInfo
);
}
if (dataResultProvider?.[dataGatheringItem.symbol]?.marketPrice) {
result.push({
dataSource: dataGatheringItem.dataSource,
date: today,
marketPrice:
dataResultProvider?.[dataGatheringItem.symbol]?.marketPrice,
symbol: dataGatheringItem.symbol
});
} else {
quoteErrors.push({
dataSource: dataGatheringItem.dataSource,
symbol: dataGatheringItem.symbol
});
}
}
return result;
})
this.getTodayPrivate(
dataGatheringItems,
dataProviderInfos,
today,
quoteErrors
)
);
}
@ -169,6 +144,61 @@ export class CurrentRateService {
return response;
}
public async getToday(
dataGatheringItems: IDataGatheringItem[]
): Promise<GetValueObject[]> {
const dataProviderInfos: DataProviderInfo[] = [];
const quoteErrors: UniqueAsset[] = [];
const today = resetHours(new Date());
return this.getTodayPrivate(
dataGatheringItems,
dataProviderInfos,
today,
quoteErrors
);
}
private async getTodayPrivate(
dataGatheringItems: IDataGatheringItem[],
dataProviderInfos: DataProviderInfo[],
today: Date,
quoteErrors: UniqueAsset[]
): Promise<GetValueObject[]> {
return this.dataProviderService
.getQuotes({ items: dataGatheringItems, user: this.request?.user })
.then((dataResultProvider) => {
const result: GetValueObject[] = [];
for (const dataGatheringItem of dataGatheringItems) {
if (
dataResultProvider?.[dataGatheringItem.symbol]?.dataProviderInfo
) {
dataProviderInfos.push(
dataResultProvider[dataGatheringItem.symbol].dataProviderInfo
);
}
if (dataResultProvider?.[dataGatheringItem.symbol]?.marketPrice) {
result.push({
dataSource: dataGatheringItem.dataSource,
date: today,
marketPrice:
dataResultProvider?.[dataGatheringItem.symbol]?.marketPrice,
symbol: dataGatheringItem.symbol
});
} else {
quoteErrors.push({
dataSource: dataGatheringItem.dataSource,
symbol: dataGatheringItem.symbol
});
}
}
return result;
});
}
private containsToday(dates: Date[]): boolean {
for (const date of dates) {
if (isToday(date)) {

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

@ -74,6 +74,7 @@ import {
} from 'date-fns';
import { isEmpty, isNumber, last, uniq, uniqBy } from 'lodash';
import { CPRPortfolioCalculator } from './calculator/constantPortfolioReturn/portfolio-calculator';
import { PortfolioCalculator } from './calculator/portfolio-calculator';
import {
PerformanceCalculationType,
@ -1824,7 +1825,12 @@ export class PortfolioService {
.plus(totalOfExcludedActivities)
.toNumber();
const netWorth = new Big(balanceInBaseCurrency)
const netWorth =
portfolioCalculator instanceof CPRPortfolioCalculator
? await (portfolioCalculator as CPRPortfolioCalculator)
.getUnfilteredNetWorth(this.getUserCurrency())
.then((value) => value.toNumber())
: new Big(balanceInBaseCurrency)
.plus(currentValueInBaseCurrency)
.plus(valuables)
.plus(excludedAccountsAndActivities)

17
apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts

@ -337,24 +337,9 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
value: netPerformanceInPercentageWithCurrencyEffect
});
if ((this.timeWeightedPerformance ?? 'N') !== 'N') {
let lastPerformance = 0;
if (index > 0) {
lastPerformance = new Big(
chart[index - 1].timeWeightedPerformance
)
.div(100)
.plus(1)
.mul(
new Big(chart[index].timeWeightedPerformance).div(100).plus(1)
)
.minus(1)
.mul(100)
.toNumber();
}
chart[index].timeWeightedPerformance = lastPerformance;
this.performanceDataItemsTimeWeightedInPercentage.push({
date,
value: lastPerformance
value: chart[index].timeWeightedPerformance
});
}
}

Loading…
Cancel
Save