Bugfix/calculation of portfolio summary caused by future liabilities (#3342)
* Adapt date of future activities * Update changelog
This commit is contained in:
parent
d735e4db75
commit
4efd5cefd8
@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed an issue in the calculation of the portfolio summary caused by future liabilities
|
||||||
|
|
||||||
## 2.77.1 - 2024-04-27
|
## 2.77.1 - 2024-04-27
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -88,21 +88,26 @@ export class OrderController {
|
|||||||
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId,
|
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId,
|
||||||
@Query('accounts') filterByAccounts?: string,
|
@Query('accounts') filterByAccounts?: string,
|
||||||
@Query('assetClasses') filterByAssetClasses?: string,
|
@Query('assetClasses') filterByAssetClasses?: string,
|
||||||
@Query('range') dateRange: DateRange = 'max',
|
@Query('range') dateRange?: DateRange,
|
||||||
@Query('skip') skip?: number,
|
@Query('skip') skip?: number,
|
||||||
@Query('sortColumn') sortColumn?: string,
|
@Query('sortColumn') sortColumn?: string,
|
||||||
@Query('sortDirection') sortDirection?: Prisma.SortOrder,
|
@Query('sortDirection') sortDirection?: Prisma.SortOrder,
|
||||||
@Query('tags') filterByTags?: string,
|
@Query('tags') filterByTags?: string,
|
||||||
@Query('take') take?: number
|
@Query('take') take?: number
|
||||||
): Promise<Activities> {
|
): Promise<Activities> {
|
||||||
|
let endDate: Date;
|
||||||
|
let startDate: Date;
|
||||||
|
|
||||||
|
if (dateRange) {
|
||||||
|
({ endDate, startDate } = getInterval(dateRange));
|
||||||
|
}
|
||||||
|
|
||||||
const filters = this.apiService.buildFiltersFromQueryParams({
|
const filters = this.apiService.buildFiltersFromQueryParams({
|
||||||
filterByAccounts,
|
filterByAccounts,
|
||||||
filterByAssetClasses,
|
filterByAssetClasses,
|
||||||
filterByTags
|
filterByTags
|
||||||
});
|
});
|
||||||
|
|
||||||
const { endDate, startDate } = getInterval(dateRange);
|
|
||||||
|
|
||||||
const impersonationUserId =
|
const impersonationUserId =
|
||||||
await this.impersonationService.validateImpersonationId(impersonationId);
|
await this.impersonationService.validateImpersonationId(impersonationId);
|
||||||
const userCurrency = this.request.user.Settings.settings.baseCurrency;
|
const userCurrency = this.request.user.Settings.settings.baseCurrency;
|
||||||
|
@ -37,6 +37,7 @@ import {
|
|||||||
eachDayOfInterval,
|
eachDayOfInterval,
|
||||||
endOfDay,
|
endOfDay,
|
||||||
format,
|
format,
|
||||||
|
isAfter,
|
||||||
isBefore,
|
isBefore,
|
||||||
isSameDay,
|
isSameDay,
|
||||||
max,
|
max,
|
||||||
@ -44,13 +45,12 @@ import {
|
|||||||
subDays
|
subDays
|
||||||
} from 'date-fns';
|
} from 'date-fns';
|
||||||
import { first, last, uniq, uniqBy } from 'lodash';
|
import { first, last, uniq, uniqBy } from 'lodash';
|
||||||
import ms from 'ms';
|
|
||||||
|
|
||||||
export abstract class PortfolioCalculator {
|
export abstract class PortfolioCalculator {
|
||||||
protected static readonly ENABLE_LOGGING = false;
|
protected static readonly ENABLE_LOGGING = false;
|
||||||
|
|
||||||
protected accountBalanceItems: HistoricalDataItem[];
|
protected accountBalanceItems: HistoricalDataItem[];
|
||||||
protected orders: PortfolioOrder[];
|
protected activities: PortfolioOrder[];
|
||||||
|
|
||||||
private configurationService: ConfigurationService;
|
private configurationService: ConfigurationService;
|
||||||
private currency: string;
|
private currency: string;
|
||||||
@ -96,7 +96,7 @@ export abstract class PortfolioCalculator {
|
|||||||
this.exchangeRateDataService = exchangeRateDataService;
|
this.exchangeRateDataService = exchangeRateDataService;
|
||||||
this.isExperimentalFeatures = isExperimentalFeatures;
|
this.isExperimentalFeatures = isExperimentalFeatures;
|
||||||
|
|
||||||
this.orders = activities
|
this.activities = activities
|
||||||
.map(
|
.map(
|
||||||
({
|
({
|
||||||
date,
|
date,
|
||||||
@ -107,6 +107,12 @@ export abstract class PortfolioCalculator {
|
|||||||
type,
|
type,
|
||||||
unitPrice
|
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 {
|
return {
|
||||||
SymbolProfile,
|
SymbolProfile,
|
||||||
tags,
|
tags,
|
||||||
@ -917,7 +923,7 @@ export abstract class PortfolioCalculator {
|
|||||||
tags,
|
tags,
|
||||||
type,
|
type,
|
||||||
unitPrice
|
unitPrice
|
||||||
} of this.orders) {
|
} of this.activities) {
|
||||||
let currentTransactionPointItem: TransactionPointSymbol;
|
let currentTransactionPointItem: TransactionPointSymbol;
|
||||||
const oldAccumulatedSymbol = symbols[SymbolProfile.symbol];
|
const oldAccumulatedSymbol = symbols[SymbolProfile.symbol];
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ describe('PortfolioCalculator', () => {
|
|||||||
const activities: Activity[] = [
|
const activities: Activity[] = [
|
||||||
{
|
{
|
||||||
...activityDummyData,
|
...activityDummyData,
|
||||||
date: new Date('2022-01-01'),
|
date: new Date('2023-01-01'), // Date in future
|
||||||
fee: 0,
|
fee: 0,
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
SymbolProfile: {
|
SymbolProfile: {
|
||||||
@ -96,61 +96,12 @@ describe('PortfolioCalculator', () => {
|
|||||||
userId: userDummyData.id
|
userId: userDummyData.id
|
||||||
});
|
});
|
||||||
|
|
||||||
const portfolioSnapshot = await portfolioCalculator.computeSnapshot(
|
|
||||||
parseDate('2022-01-01')
|
|
||||||
);
|
|
||||||
|
|
||||||
spy.mockRestore();
|
spy.mockRestore();
|
||||||
|
|
||||||
expect(portfolioSnapshot).toEqual({
|
const liabilitiesInBaseCurrency =
|
||||||
currentValueInBaseCurrency: new Big('0'),
|
await portfolioCalculator.getLiabilitiesInBaseCurrency();
|
||||||
errors: [],
|
|
||||||
grossPerformance: new Big('0'),
|
expect(liabilitiesInBaseCurrency).toEqual(new Big(3000));
|
||||||
grossPerformancePercentage: new Big('0'),
|
|
||||||
grossPerformancePercentageWithCurrencyEffect: new Big('0'),
|
|
||||||
grossPerformanceWithCurrencyEffect: new Big('0'),
|
|
||||||
hasErrors: true,
|
|
||||||
netPerformance: new Big('0'),
|
|
||||||
netPerformancePercentage: new Big('0'),
|
|
||||||
netPerformancePercentageWithCurrencyEffect: new Big('0'),
|
|
||||||
netPerformanceWithCurrencyEffect: new Big('0'),
|
|
||||||
positions: [
|
|
||||||
{
|
|
||||||
averagePrice: new Big('3000'),
|
|
||||||
currency: 'USD',
|
|
||||||
dataSource: 'MANUAL',
|
|
||||||
dividend: new Big('0'),
|
|
||||||
dividendInBaseCurrency: new Big('0'),
|
|
||||||
fee: new Big('0'),
|
|
||||||
firstBuyDate: '2022-01-01',
|
|
||||||
grossPerformance: null,
|
|
||||||
grossPerformancePercentage: null,
|
|
||||||
grossPerformancePercentageWithCurrencyEffect: null,
|
|
||||||
grossPerformanceWithCurrencyEffect: null,
|
|
||||||
investment: new Big('0'),
|
|
||||||
investmentWithCurrencyEffect: new Big('0'),
|
|
||||||
marketPrice: null,
|
|
||||||
marketPriceInBaseCurrency: 3000,
|
|
||||||
netPerformance: null,
|
|
||||||
netPerformancePercentage: null,
|
|
||||||
netPerformancePercentageWithCurrencyEffect: null,
|
|
||||||
netPerformanceWithCurrencyEffect: null,
|
|
||||||
quantity: new Big('0'),
|
|
||||||
symbol: '55196015-1365-4560-aa60-8751ae6d18f8',
|
|
||||||
tags: [],
|
|
||||||
timeWeightedInvestment: new Big('0'),
|
|
||||||
timeWeightedInvestmentWithCurrencyEffect: new Big('0'),
|
|
||||||
transactionCount: 1,
|
|
||||||
valueInBaseCurrency: new Big('0')
|
|
||||||
}
|
|
||||||
],
|
|
||||||
totalFeesWithCurrencyEffect: new Big('0'),
|
|
||||||
totalInterestWithCurrencyEffect: new Big('0'),
|
|
||||||
totalInvestment: new Big('0'),
|
|
||||||
totalInvestmentWithCurrencyEffect: new Big('0'),
|
|
||||||
totalLiabilitiesWithCurrencyEffect: new Big('0'),
|
|
||||||
totalValuablesWithCurrencyEffect: new Big('0')
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -203,7 +203,7 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
|
|||||||
let valueAtStartDateWithCurrencyEffect: Big;
|
let valueAtStartDateWithCurrencyEffect: Big;
|
||||||
|
|
||||||
// Clone orders to keep the original values in this.orders
|
// Clone orders to keep the original values in this.orders
|
||||||
let orders: PortfolioOrderItem[] = cloneDeep(this.orders).filter(
|
let orders: PortfolioOrderItem[] = cloneDeep(this.activities).filter(
|
||||||
({ SymbolProfile }) => {
|
({ SymbolProfile }) => {
|
||||||
return SymbolProfile.symbol === symbol;
|
return SymbolProfile.symbol === symbol;
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,13 @@ import { GetValuesParams } from './interfaces/get-values-params.interface';
|
|||||||
|
|
||||||
function mockGetValue(symbol: string, date: Date) {
|
function mockGetValue(symbol: string, date: Date) {
|
||||||
switch (symbol) {
|
switch (symbol) {
|
||||||
|
case '55196015-1365-4560-aa60-8751ae6d18f8':
|
||||||
|
if (isSameDay(parseDate('2022-01-31'), date)) {
|
||||||
|
return { marketPrice: 3000 };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { marketPrice: 0 };
|
||||||
|
|
||||||
case 'BALN.SW':
|
case 'BALN.SW':
|
||||||
if (isSameDay(parseDate('2021-11-12'), date)) {
|
if (isSameDay(parseDate('2021-11-12'), date)) {
|
||||||
return { marketPrice: 146 };
|
return { marketPrice: 146 };
|
||||||
|
Loading…
x
Reference in New Issue
Block a user