|
|
|
@ -22,6 +22,7 @@ import {
|
|
|
|
|
format,
|
|
|
|
|
isBefore,
|
|
|
|
|
isSameDay,
|
|
|
|
|
max,
|
|
|
|
|
subDays
|
|
|
|
|
} from 'date-fns';
|
|
|
|
|
import { cloneDeep, first, isNumber, last, sortBy, uniq } from 'lodash';
|
|
|
|
@ -449,16 +450,27 @@ export class PortfolioCalculator {
|
|
|
|
|
|
|
|
|
|
public async getCurrentPositions(
|
|
|
|
|
start: Date,
|
|
|
|
|
end = new Date(Date.now())
|
|
|
|
|
end?: Date
|
|
|
|
|
): Promise<CurrentPositions> {
|
|
|
|
|
const transactionPointsBeforeEndDate =
|
|
|
|
|
this.transactionPoints?.filter((transactionPoint) => {
|
|
|
|
|
return isBefore(parseDate(transactionPoint.date), end);
|
|
|
|
|
}) ?? [];
|
|
|
|
|
const lastTransactionPoint = last(this.transactionPoints);
|
|
|
|
|
|
|
|
|
|
let endDate = end;
|
|
|
|
|
|
|
|
|
|
if (!endDate) {
|
|
|
|
|
endDate = new Date(Date.now());
|
|
|
|
|
|
|
|
|
|
if (!transactionPointsBeforeEndDate.length) {
|
|
|
|
|
if (lastTransactionPoint) {
|
|
|
|
|
endDate = max([endDate, parseDate(lastTransactionPoint.date)]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const transactionPoints = this.transactionPoints?.filter(({ date }) => {
|
|
|
|
|
return isBefore(parseDate(date), endDate);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!transactionPoints.length) {
|
|
|
|
|
return {
|
|
|
|
|
currentValue: new Big(0),
|
|
|
|
|
currentValueInBaseCurrency: new Big(0),
|
|
|
|
|
grossPerformance: new Big(0),
|
|
|
|
|
grossPerformancePercentage: new Big(0),
|
|
|
|
|
grossPerformancePercentageWithCurrencyEffect: new Big(0),
|
|
|
|
@ -473,41 +485,40 @@ export class PortfolioCalculator {
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const lastTransactionPoint =
|
|
|
|
|
transactionPointsBeforeEndDate[transactionPointsBeforeEndDate.length - 1];
|
|
|
|
|
|
|
|
|
|
const currencies: { [symbol: string]: string } = {};
|
|
|
|
|
const dataGatheringItems: IDataGatheringItem[] = [];
|
|
|
|
|
let dates: Date[] = [];
|
|
|
|
|
let firstIndex = transactionPointsBeforeEndDate.length;
|
|
|
|
|
let firstIndex = transactionPoints.length;
|
|
|
|
|
let firstTransactionPoint: TransactionPoint = null;
|
|
|
|
|
|
|
|
|
|
dates.push(resetHours(start));
|
|
|
|
|
for (const item of transactionPointsBeforeEndDate[firstIndex - 1].items) {
|
|
|
|
|
|
|
|
|
|
for (const { currency, dataSource, symbol } of transactionPoints[
|
|
|
|
|
firstIndex - 1
|
|
|
|
|
].items) {
|
|
|
|
|
dataGatheringItems.push({
|
|
|
|
|
dataSource: item.dataSource,
|
|
|
|
|
symbol: item.symbol
|
|
|
|
|
dataSource,
|
|
|
|
|
symbol
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
currencies[item.symbol] = item.currency;
|
|
|
|
|
currencies[symbol] = currency;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < transactionPointsBeforeEndDate.length; i++) {
|
|
|
|
|
for (let i = 0; i < transactionPoints.length; i++) {
|
|
|
|
|
if (
|
|
|
|
|
!isBefore(parseDate(transactionPointsBeforeEndDate[i].date), start) &&
|
|
|
|
|
!isBefore(parseDate(transactionPoints[i].date), start) &&
|
|
|
|
|
firstTransactionPoint === null
|
|
|
|
|
) {
|
|
|
|
|
firstTransactionPoint = transactionPointsBeforeEndDate[i];
|
|
|
|
|
firstTransactionPoint = transactionPoints[i];
|
|
|
|
|
firstIndex = i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (firstTransactionPoint !== null) {
|
|
|
|
|
dates.push(
|
|
|
|
|
resetHours(parseDate(transactionPointsBeforeEndDate[i].date))
|
|
|
|
|
);
|
|
|
|
|
dates.push(resetHours(parseDate(transactionPoints[i].date)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dates.push(resetHours(end));
|
|
|
|
|
dates.push(resetHours(endDate));
|
|
|
|
|
|
|
|
|
|
// Add dates of last week for fallback
|
|
|
|
|
dates.push(subDays(resetHours(new Date()), 7));
|
|
|
|
@ -534,7 +545,7 @@ export class PortfolioCalculator {
|
|
|
|
|
let exchangeRatesByCurrency =
|
|
|
|
|
await this.exchangeRateDataService.getExchangeRatesByCurrency({
|
|
|
|
|
currencies: uniq(Object.values(currencies)),
|
|
|
|
|
endDate: endOfDay(end),
|
|
|
|
|
endDate: endOfDay(endDate),
|
|
|
|
|
startDate: parseDate(this.transactionPoints?.[0]?.date),
|
|
|
|
|
targetCurrency: this.currency
|
|
|
|
|
});
|
|
|
|
@ -570,7 +581,7 @@ export class PortfolioCalculator {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const endDateString = format(end, DATE_FORMAT);
|
|
|
|
|
const endDateString = format(endDate, DATE_FORMAT);
|
|
|
|
|
|
|
|
|
|
if (firstIndex > 0) {
|
|
|
|
|
firstIndex--;
|
|
|
|
@ -582,9 +593,9 @@ export class PortfolioCalculator {
|
|
|
|
|
const errors: ResponseError['errors'] = [];
|
|
|
|
|
|
|
|
|
|
for (const item of lastTransactionPoint.items) {
|
|
|
|
|
const marketPriceInBaseCurrency = marketSymbolMap[endDateString]?.[
|
|
|
|
|
item.symbol
|
|
|
|
|
]?.mul(
|
|
|
|
|
const marketPriceInBaseCurrency = (
|
|
|
|
|
marketSymbolMap[endDateString]?.[item.symbol] ?? item.averagePrice
|
|
|
|
|
).mul(
|
|
|
|
|
exchangeRatesByCurrency[`${item.currency}${this.currency}`]?.[
|
|
|
|
|
endDateString
|
|
|
|
|
]
|
|
|
|
@ -607,9 +618,9 @@ export class PortfolioCalculator {
|
|
|
|
|
totalInvestment,
|
|
|
|
|
totalInvestmentWithCurrencyEffect
|
|
|
|
|
} = this.getSymbolMetrics({
|
|
|
|
|
end,
|
|
|
|
|
marketSymbolMap,
|
|
|
|
|
start,
|
|
|
|
|
end: endDate,
|
|
|
|
|
exchangeRates:
|
|
|
|
|
exchangeRatesByCurrency[`${item.currency}${this.currency}`],
|
|
|
|
|
symbol: item.symbol
|
|
|
|
@ -656,7 +667,10 @@ export class PortfolioCalculator {
|
|
|
|
|
quantity: item.quantity,
|
|
|
|
|
symbol: item.symbol,
|
|
|
|
|
tags: item.tags,
|
|
|
|
|
transactionCount: item.transactionCount
|
|
|
|
|
transactionCount: item.transactionCount,
|
|
|
|
|
valueInBaseCurrency: new Big(marketPriceInBaseCurrency).mul(
|
|
|
|
|
item.quantity
|
|
|
|
|
)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
@ -725,7 +739,7 @@ export class PortfolioCalculator {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private calculateOverallPerformance(positions: TimelinePosition[]) {
|
|
|
|
|
let currentValue = new Big(0);
|
|
|
|
|
let currentValueInBaseCurrency = new Big(0);
|
|
|
|
|
let grossPerformance = new Big(0);
|
|
|
|
|
let grossPerformanceWithCurrencyEffect = new Big(0);
|
|
|
|
|
let hasErrors = false;
|
|
|
|
@ -737,14 +751,9 @@ export class PortfolioCalculator {
|
|
|
|
|
let totalTimeWeightedInvestmentWithCurrencyEffect = new Big(0);
|
|
|
|
|
|
|
|
|
|
for (const currentPosition of positions) {
|
|
|
|
|
if (
|
|
|
|
|
currentPosition.investment &&
|
|
|
|
|
currentPosition.marketPriceInBaseCurrency
|
|
|
|
|
) {
|
|
|
|
|
currentValue = currentValue.plus(
|
|
|
|
|
new Big(currentPosition.marketPriceInBaseCurrency).mul(
|
|
|
|
|
currentPosition.quantity
|
|
|
|
|
)
|
|
|
|
|
if (currentPosition.valueInBaseCurrency) {
|
|
|
|
|
currentValueInBaseCurrency = currentValueInBaseCurrency.plus(
|
|
|
|
|
currentPosition.valueInBaseCurrency
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
hasErrors = true;
|
|
|
|
@ -801,7 +810,7 @@ export class PortfolioCalculator {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
currentValue,
|
|
|
|
|
currentValueInBaseCurrency,
|
|
|
|
|
grossPerformance,
|
|
|
|
|
grossPerformanceWithCurrencyEffect,
|
|
|
|
|
hasErrors,
|
|
|
|
|