Improve handling of future liabilities (#3118)
* Improve handling of future liabilities * Refactor currentValue to currentValueInBaseCurrency * Update changelog
This commit is contained in:
parent
1f2f9f22f2
commit
bc8d8309d4
@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Integrated dividend into the transaction point concept in the portfolio service
|
- Integrated dividend into the transaction point concept in the portfolio service
|
||||||
- Removed the environment variable `WEB_AUTH_RP_ID`
|
- Removed the environment variable `WEB_AUTH_RP_ID`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed an issue in the calculation of the portfolio summary caused by future liabilities
|
||||||
|
|
||||||
## 2.61.1 - 2024-03-06
|
## 2.61.1 - 2024-03-06
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
@ -46,6 +46,8 @@ function mockGetValue(symbol: string, date: Date) {
|
|||||||
case 'MSFT':
|
case 'MSFT':
|
||||||
if (isSameDay(parseDate('2021-09-16'), date)) {
|
if (isSameDay(parseDate('2021-09-16'), date)) {
|
||||||
return { marketPrice: 89.12 };
|
return { marketPrice: 89.12 };
|
||||||
|
} else if (isSameDay(parseDate('2021-11-16'), date)) {
|
||||||
|
return { marketPrice: 339.51 };
|
||||||
} else if (isSameDay(parseDate('2023-07-10'), date)) {
|
} else if (isSameDay(parseDate('2023-07-10'), date)) {
|
||||||
return { marketPrice: 331.83 };
|
return { marketPrice: 331.83 };
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import { ResponseError, TimelinePosition } from '@ghostfolio/common/interfaces';
|
|||||||
import Big from 'big.js';
|
import Big from 'big.js';
|
||||||
|
|
||||||
export interface CurrentPositions extends ResponseError {
|
export interface CurrentPositions extends ResponseError {
|
||||||
positions: TimelinePosition[];
|
currentValueInBaseCurrency: Big;
|
||||||
grossPerformance: Big;
|
grossPerformance: Big;
|
||||||
grossPerformanceWithCurrencyEffect: Big;
|
grossPerformanceWithCurrencyEffect: Big;
|
||||||
grossPerformancePercentage: Big;
|
grossPerformancePercentage: Big;
|
||||||
@ -14,6 +14,6 @@ export interface CurrentPositions extends ResponseError {
|
|||||||
netPerformanceWithCurrencyEffect: Big;
|
netPerformanceWithCurrencyEffect: Big;
|
||||||
netPerformancePercentage: Big;
|
netPerformancePercentage: Big;
|
||||||
netPerformancePercentageWithCurrencyEffect: Big;
|
netPerformancePercentageWithCurrencyEffect: Big;
|
||||||
currentValue: Big;
|
positions: TimelinePosition[];
|
||||||
totalInvestment: Big;
|
totalInvestment: Big;
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ describe('PortfolioCalculator', () => {
|
|||||||
spy.mockRestore();
|
spy.mockRestore();
|
||||||
|
|
||||||
expect(currentPositions).toEqual({
|
expect(currentPositions).toEqual({
|
||||||
currentValue: new Big('0'),
|
currentValueInBaseCurrency: new Big('0'),
|
||||||
errors: [],
|
errors: [],
|
||||||
grossPerformance: new Big('-12.6'),
|
grossPerformance: new Big('-12.6'),
|
||||||
grossPerformancePercentage: new Big('-0.0440867739678096571'),
|
grossPerformancePercentage: new Big('-0.0440867739678096571'),
|
||||||
@ -131,7 +131,8 @@ describe('PortfolioCalculator', () => {
|
|||||||
symbol: 'BALN.SW',
|
symbol: 'BALN.SW',
|
||||||
timeWeightedInvestment: new Big('285.8'),
|
timeWeightedInvestment: new Big('285.8'),
|
||||||
timeWeightedInvestmentWithCurrencyEffect: new Big('285.8'),
|
timeWeightedInvestmentWithCurrencyEffect: new Big('285.8'),
|
||||||
transactionCount: 2
|
transactionCount: 2,
|
||||||
|
valueInBaseCurrency: new Big('0')
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
totalInvestment: new Big('0'),
|
totalInvestment: new Big('0'),
|
||||||
|
@ -76,7 +76,7 @@ describe('PortfolioCalculator', () => {
|
|||||||
spy.mockRestore();
|
spy.mockRestore();
|
||||||
|
|
||||||
expect(currentPositions).toEqual({
|
expect(currentPositions).toEqual({
|
||||||
currentValue: new Big('297.8'),
|
currentValueInBaseCurrency: new Big('297.8'),
|
||||||
errors: [],
|
errors: [],
|
||||||
grossPerformance: new Big('24.6'),
|
grossPerformance: new Big('24.6'),
|
||||||
grossPerformancePercentage: new Big('0.09004392386530014641'),
|
grossPerformancePercentage: new Big('0.09004392386530014641'),
|
||||||
@ -120,7 +120,8 @@ describe('PortfolioCalculator', () => {
|
|||||||
symbol: 'BALN.SW',
|
symbol: 'BALN.SW',
|
||||||
timeWeightedInvestment: new Big('273.2'),
|
timeWeightedInvestment: new Big('273.2'),
|
||||||
timeWeightedInvestmentWithCurrencyEffect: new Big('273.2'),
|
timeWeightedInvestmentWithCurrencyEffect: new Big('273.2'),
|
||||||
transactionCount: 1
|
transactionCount: 1,
|
||||||
|
valueInBaseCurrency: new Big('297.8')
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
totalInvestment: new Big('273.2'),
|
totalInvestment: new Big('273.2'),
|
||||||
|
@ -100,7 +100,7 @@ describe('PortfolioCalculator', () => {
|
|||||||
spy.mockRestore();
|
spy.mockRestore();
|
||||||
|
|
||||||
expect(currentPositions).toEqual({
|
expect(currentPositions).toEqual({
|
||||||
currentValue: new Big('13298.425356'),
|
currentValueInBaseCurrency: new Big('13298.425356'),
|
||||||
errors: [],
|
errors: [],
|
||||||
grossPerformance: new Big('27172.74'),
|
grossPerformance: new Big('27172.74'),
|
||||||
grossPerformancePercentage: new Big('42.41978276196153750666'),
|
grossPerformancePercentage: new Big('42.41978276196153750666'),
|
||||||
@ -151,7 +151,8 @@ describe('PortfolioCalculator', () => {
|
|||||||
timeWeightedInvestmentWithCurrencyEffect: new Big(
|
timeWeightedInvestmentWithCurrencyEffect: new Big(
|
||||||
'636.79469348020066587024'
|
'636.79469348020066587024'
|
||||||
),
|
),
|
||||||
transactionCount: 2
|
transactionCount: 2,
|
||||||
|
valueInBaseCurrency: new Big('13298.425356')
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
totalInvestment: new Big('320.43'),
|
totalInvestment: new Big('320.43'),
|
||||||
|
@ -89,7 +89,7 @@ describe('PortfolioCalculator', () => {
|
|||||||
spy.mockRestore();
|
spy.mockRestore();
|
||||||
|
|
||||||
expect(currentPositions).toEqual({
|
expect(currentPositions).toEqual({
|
||||||
currentValue: new Big('103.10483'),
|
currentValueInBaseCurrency: new Big('103.10483'),
|
||||||
errors: [],
|
errors: [],
|
||||||
grossPerformance: new Big('27.33'),
|
grossPerformance: new Big('27.33'),
|
||||||
grossPerformancePercentage: new Big('0.3066651705565529623'),
|
grossPerformancePercentage: new Big('0.3066651705565529623'),
|
||||||
@ -134,7 +134,8 @@ describe('PortfolioCalculator', () => {
|
|||||||
tags: undefined,
|
tags: undefined,
|
||||||
timeWeightedInvestment: new Big('89.12'),
|
timeWeightedInvestment: new Big('89.12'),
|
||||||
timeWeightedInvestmentWithCurrencyEffect: new Big('82.329056'),
|
timeWeightedInvestmentWithCurrencyEffect: new Big('82.329056'),
|
||||||
transactionCount: 1
|
transactionCount: 1,
|
||||||
|
valueInBaseCurrency: new Big('103.10483')
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
totalInvestment: new Big('89.12'),
|
totalInvestment: new Big('89.12'),
|
||||||
|
@ -64,7 +64,7 @@ describe('PortfolioCalculator', () => {
|
|||||||
spy.mockRestore();
|
spy.mockRestore();
|
||||||
|
|
||||||
expect(currentPositions).toEqual({
|
expect(currentPositions).toEqual({
|
||||||
currentValue: new Big(0),
|
currentValueInBaseCurrency: new Big(0),
|
||||||
grossPerformance: new Big(0),
|
grossPerformance: new Big(0),
|
||||||
grossPerformancePercentage: new Big(0),
|
grossPerformancePercentage: new Big(0),
|
||||||
grossPerformancePercentageWithCurrencyEffect: new Big(0),
|
grossPerformancePercentageWithCurrencyEffect: new Big(0),
|
||||||
|
@ -87,7 +87,7 @@ describe('PortfolioCalculator', () => {
|
|||||||
spy.mockRestore();
|
spy.mockRestore();
|
||||||
|
|
||||||
expect(currentPositions).toEqual({
|
expect(currentPositions).toEqual({
|
||||||
currentValue: new Big('87.8'),
|
currentValueInBaseCurrency: new Big('87.8'),
|
||||||
errors: [],
|
errors: [],
|
||||||
grossPerformance: new Big('21.93'),
|
grossPerformance: new Big('21.93'),
|
||||||
grossPerformancePercentage: new Big('0.15113417083448194384'),
|
grossPerformancePercentage: new Big('0.15113417083448194384'),
|
||||||
@ -133,7 +133,8 @@ describe('PortfolioCalculator', () => {
|
|||||||
timeWeightedInvestmentWithCurrencyEffect: new Big(
|
timeWeightedInvestmentWithCurrencyEffect: new Big(
|
||||||
'145.10285714285714285714'
|
'145.10285714285714285714'
|
||||||
),
|
),
|
||||||
transactionCount: 2
|
transactionCount: 2,
|
||||||
|
valueInBaseCurrency: new Big('87.8')
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
totalInvestment: new Big('75.80'),
|
totalInvestment: new Big('75.80'),
|
||||||
|
@ -113,7 +113,7 @@ describe('PortfolioCalculator', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(currentPositions).toEqual({
|
expect(currentPositions).toEqual({
|
||||||
currentValue: new Big('0'),
|
currentValueInBaseCurrency: new Big('0'),
|
||||||
errors: [],
|
errors: [],
|
||||||
grossPerformance: new Big('19.86'),
|
grossPerformance: new Big('19.86'),
|
||||||
grossPerformancePercentage: new Big('0.13100263852242744063'),
|
grossPerformancePercentage: new Big('0.13100263852242744063'),
|
||||||
@ -157,7 +157,8 @@ describe('PortfolioCalculator', () => {
|
|||||||
symbol: 'NOVN.SW',
|
symbol: 'NOVN.SW',
|
||||||
timeWeightedInvestment: new Big('151.6'),
|
timeWeightedInvestment: new Big('151.6'),
|
||||||
timeWeightedInvestmentWithCurrencyEffect: new Big('151.6'),
|
timeWeightedInvestmentWithCurrencyEffect: new Big('151.6'),
|
||||||
transactionCount: 2
|
transactionCount: 2,
|
||||||
|
valueInBaseCurrency: new Big('0')
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
totalInvestment: new Big('0'),
|
totalInvestment: new Big('0'),
|
||||||
|
@ -22,6 +22,7 @@ import {
|
|||||||
format,
|
format,
|
||||||
isBefore,
|
isBefore,
|
||||||
isSameDay,
|
isSameDay,
|
||||||
|
max,
|
||||||
subDays
|
subDays
|
||||||
} from 'date-fns';
|
} from 'date-fns';
|
||||||
import { cloneDeep, first, isNumber, last, sortBy, uniq } from 'lodash';
|
import { cloneDeep, first, isNumber, last, sortBy, uniq } from 'lodash';
|
||||||
@ -449,16 +450,27 @@ export class PortfolioCalculator {
|
|||||||
|
|
||||||
public async getCurrentPositions(
|
public async getCurrentPositions(
|
||||||
start: Date,
|
start: Date,
|
||||||
end = new Date(Date.now())
|
end?: Date
|
||||||
): Promise<CurrentPositions> {
|
): Promise<CurrentPositions> {
|
||||||
const transactionPointsBeforeEndDate =
|
const lastTransactionPoint = last(this.transactionPoints);
|
||||||
this.transactionPoints?.filter((transactionPoint) => {
|
|
||||||
return isBefore(parseDate(transactionPoint.date), end);
|
|
||||||
}) ?? [];
|
|
||||||
|
|
||||||
if (!transactionPointsBeforeEndDate.length) {
|
let endDate = end;
|
||||||
|
|
||||||
|
if (!endDate) {
|
||||||
|
endDate = new Date(Date.now());
|
||||||
|
|
||||||
|
if (lastTransactionPoint) {
|
||||||
|
endDate = max([endDate, parseDate(lastTransactionPoint.date)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const transactionPoints = this.transactionPoints?.filter(({ date }) => {
|
||||||
|
return isBefore(parseDate(date), endDate);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!transactionPoints.length) {
|
||||||
return {
|
return {
|
||||||
currentValue: new Big(0),
|
currentValueInBaseCurrency: new Big(0),
|
||||||
grossPerformance: new Big(0),
|
grossPerformance: new Big(0),
|
||||||
grossPerformancePercentage: new Big(0),
|
grossPerformancePercentage: new Big(0),
|
||||||
grossPerformancePercentageWithCurrencyEffect: 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 currencies: { [symbol: string]: string } = {};
|
||||||
const dataGatheringItems: IDataGatheringItem[] = [];
|
const dataGatheringItems: IDataGatheringItem[] = [];
|
||||||
let dates: Date[] = [];
|
let dates: Date[] = [];
|
||||||
let firstIndex = transactionPointsBeforeEndDate.length;
|
let firstIndex = transactionPoints.length;
|
||||||
let firstTransactionPoint: TransactionPoint = null;
|
let firstTransactionPoint: TransactionPoint = null;
|
||||||
|
|
||||||
dates.push(resetHours(start));
|
dates.push(resetHours(start));
|
||||||
for (const item of transactionPointsBeforeEndDate[firstIndex - 1].items) {
|
|
||||||
|
for (const { currency, dataSource, symbol } of transactionPoints[
|
||||||
|
firstIndex - 1
|
||||||
|
].items) {
|
||||||
dataGatheringItems.push({
|
dataGatheringItems.push({
|
||||||
dataSource: item.dataSource,
|
dataSource,
|
||||||
symbol: item.symbol
|
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 (
|
if (
|
||||||
!isBefore(parseDate(transactionPointsBeforeEndDate[i].date), start) &&
|
!isBefore(parseDate(transactionPoints[i].date), start) &&
|
||||||
firstTransactionPoint === null
|
firstTransactionPoint === null
|
||||||
) {
|
) {
|
||||||
firstTransactionPoint = transactionPointsBeforeEndDate[i];
|
firstTransactionPoint = transactionPoints[i];
|
||||||
firstIndex = i;
|
firstIndex = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (firstTransactionPoint !== null) {
|
if (firstTransactionPoint !== null) {
|
||||||
dates.push(
|
dates.push(resetHours(parseDate(transactionPoints[i].date)));
|
||||||
resetHours(parseDate(transactionPointsBeforeEndDate[i].date))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dates.push(resetHours(end));
|
dates.push(resetHours(endDate));
|
||||||
|
|
||||||
// Add dates of last week for fallback
|
// Add dates of last week for fallback
|
||||||
dates.push(subDays(resetHours(new Date()), 7));
|
dates.push(subDays(resetHours(new Date()), 7));
|
||||||
@ -534,7 +545,7 @@ export class PortfolioCalculator {
|
|||||||
let exchangeRatesByCurrency =
|
let exchangeRatesByCurrency =
|
||||||
await this.exchangeRateDataService.getExchangeRatesByCurrency({
|
await this.exchangeRateDataService.getExchangeRatesByCurrency({
|
||||||
currencies: uniq(Object.values(currencies)),
|
currencies: uniq(Object.values(currencies)),
|
||||||
endDate: endOfDay(end),
|
endDate: endOfDay(endDate),
|
||||||
startDate: parseDate(this.transactionPoints?.[0]?.date),
|
startDate: parseDate(this.transactionPoints?.[0]?.date),
|
||||||
targetCurrency: this.currency
|
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) {
|
if (firstIndex > 0) {
|
||||||
firstIndex--;
|
firstIndex--;
|
||||||
@ -582,9 +593,9 @@ export class PortfolioCalculator {
|
|||||||
const errors: ResponseError['errors'] = [];
|
const errors: ResponseError['errors'] = [];
|
||||||
|
|
||||||
for (const item of lastTransactionPoint.items) {
|
for (const item of lastTransactionPoint.items) {
|
||||||
const marketPriceInBaseCurrency = marketSymbolMap[endDateString]?.[
|
const marketPriceInBaseCurrency = (
|
||||||
item.symbol
|
marketSymbolMap[endDateString]?.[item.symbol] ?? item.averagePrice
|
||||||
]?.mul(
|
).mul(
|
||||||
exchangeRatesByCurrency[`${item.currency}${this.currency}`]?.[
|
exchangeRatesByCurrency[`${item.currency}${this.currency}`]?.[
|
||||||
endDateString
|
endDateString
|
||||||
]
|
]
|
||||||
@ -607,9 +618,9 @@ export class PortfolioCalculator {
|
|||||||
totalInvestment,
|
totalInvestment,
|
||||||
totalInvestmentWithCurrencyEffect
|
totalInvestmentWithCurrencyEffect
|
||||||
} = this.getSymbolMetrics({
|
} = this.getSymbolMetrics({
|
||||||
end,
|
|
||||||
marketSymbolMap,
|
marketSymbolMap,
|
||||||
start,
|
start,
|
||||||
|
end: endDate,
|
||||||
exchangeRates:
|
exchangeRates:
|
||||||
exchangeRatesByCurrency[`${item.currency}${this.currency}`],
|
exchangeRatesByCurrency[`${item.currency}${this.currency}`],
|
||||||
symbol: item.symbol
|
symbol: item.symbol
|
||||||
@ -656,7 +667,10 @@ export class PortfolioCalculator {
|
|||||||
quantity: item.quantity,
|
quantity: item.quantity,
|
||||||
symbol: item.symbol,
|
symbol: item.symbol,
|
||||||
tags: item.tags,
|
tags: item.tags,
|
||||||
transactionCount: item.transactionCount
|
transactionCount: item.transactionCount,
|
||||||
|
valueInBaseCurrency: new Big(marketPriceInBaseCurrency).mul(
|
||||||
|
item.quantity
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -725,7 +739,7 @@ export class PortfolioCalculator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private calculateOverallPerformance(positions: TimelinePosition[]) {
|
private calculateOverallPerformance(positions: TimelinePosition[]) {
|
||||||
let currentValue = new Big(0);
|
let currentValueInBaseCurrency = new Big(0);
|
||||||
let grossPerformance = new Big(0);
|
let grossPerformance = new Big(0);
|
||||||
let grossPerformanceWithCurrencyEffect = new Big(0);
|
let grossPerformanceWithCurrencyEffect = new Big(0);
|
||||||
let hasErrors = false;
|
let hasErrors = false;
|
||||||
@ -737,14 +751,9 @@ export class PortfolioCalculator {
|
|||||||
let totalTimeWeightedInvestmentWithCurrencyEffect = new Big(0);
|
let totalTimeWeightedInvestmentWithCurrencyEffect = new Big(0);
|
||||||
|
|
||||||
for (const currentPosition of positions) {
|
for (const currentPosition of positions) {
|
||||||
if (
|
if (currentPosition.valueInBaseCurrency) {
|
||||||
currentPosition.investment &&
|
currentValueInBaseCurrency = currentValueInBaseCurrency.plus(
|
||||||
currentPosition.marketPriceInBaseCurrency
|
currentPosition.valueInBaseCurrency
|
||||||
) {
|
|
||||||
currentValue = currentValue.plus(
|
|
||||||
new Big(currentPosition.marketPriceInBaseCurrency).mul(
|
|
||||||
currentPosition.quantity
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
hasErrors = true;
|
hasErrors = true;
|
||||||
@ -801,7 +810,7 @@ export class PortfolioCalculator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentValue,
|
currentValueInBaseCurrency,
|
||||||
grossPerformance,
|
grossPerformance,
|
||||||
grossPerformanceWithCurrencyEffect,
|
grossPerformanceWithCurrencyEffect,
|
||||||
hasErrors,
|
hasErrors,
|
||||||
|
@ -378,9 +378,10 @@ export class PortfolioService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const holdings: PortfolioDetails['holdings'] = {};
|
const holdings: PortfolioDetails['holdings'] = {};
|
||||||
const totalValueInBaseCurrency = currentPositions.currentValue.plus(
|
const totalValueInBaseCurrency =
|
||||||
cashDetails.balanceInBaseCurrency
|
currentPositions.currentValueInBaseCurrency.plus(
|
||||||
);
|
cashDetails.balanceInBaseCurrency
|
||||||
|
);
|
||||||
|
|
||||||
const isFilteredByAccount =
|
const isFilteredByAccount =
|
||||||
filters?.some((filter) => {
|
filters?.some((filter) => {
|
||||||
@ -389,7 +390,7 @@ export class PortfolioService {
|
|||||||
|
|
||||||
let filteredValueInBaseCurrency = isFilteredByAccount
|
let filteredValueInBaseCurrency = isFilteredByAccount
|
||||||
? totalValueInBaseCurrency
|
? totalValueInBaseCurrency
|
||||||
: currentPositions.currentValue;
|
: currentPositions.currentValueInBaseCurrency;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
filters?.length === 0 ||
|
filters?.length === 0 ||
|
||||||
@ -444,14 +445,14 @@ export class PortfolioService {
|
|||||||
quantity,
|
quantity,
|
||||||
symbol,
|
symbol,
|
||||||
tags,
|
tags,
|
||||||
transactionCount
|
transactionCount,
|
||||||
|
valueInBaseCurrency
|
||||||
} of currentPositions.positions) {
|
} of currentPositions.positions) {
|
||||||
if (quantity.eq(0)) {
|
if (quantity.eq(0)) {
|
||||||
// Ignore positions without any quantity
|
// Ignore positions without any quantity
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = quantity.mul(marketPriceInBaseCurrency ?? 0);
|
|
||||||
const symbolProfile = symbolProfileMap[symbol];
|
const symbolProfile = symbolProfileMap[symbol];
|
||||||
const dataProviderResponse = dataProviderResponses[symbol];
|
const dataProviderResponse = dataProviderResponses[symbol];
|
||||||
|
|
||||||
@ -517,11 +518,11 @@ export class PortfolioService {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
markets[UNKNOWN_KEY] = new Big(markets[UNKNOWN_KEY])
|
markets[UNKNOWN_KEY] = new Big(markets[UNKNOWN_KEY])
|
||||||
.plus(value)
|
.plus(valueInBaseCurrency)
|
||||||
.toNumber();
|
.toNumber();
|
||||||
|
|
||||||
marketsAdvanced[UNKNOWN_KEY] = new Big(marketsAdvanced[UNKNOWN_KEY])
|
marketsAdvanced[UNKNOWN_KEY] = new Big(marketsAdvanced[UNKNOWN_KEY])
|
||||||
.plus(value)
|
.plus(valueInBaseCurrency)
|
||||||
.toNumber();
|
.toNumber();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -535,7 +536,7 @@ export class PortfolioService {
|
|||||||
transactionCount,
|
transactionCount,
|
||||||
allocationInPercentage: filteredValueInBaseCurrency.eq(0)
|
allocationInPercentage: filteredValueInBaseCurrency.eq(0)
|
||||||
? 0
|
? 0
|
||||||
: value.div(filteredValueInBaseCurrency).toNumber(),
|
: valueInBaseCurrency.div(filteredValueInBaseCurrency).toNumber(),
|
||||||
assetClass: symbolProfile.assetClass,
|
assetClass: symbolProfile.assetClass,
|
||||||
assetSubClass: symbolProfile.assetSubClass,
|
assetSubClass: symbolProfile.assetSubClass,
|
||||||
countries: symbolProfile.countries,
|
countries: symbolProfile.countries,
|
||||||
@ -560,7 +561,7 @@ export class PortfolioService {
|
|||||||
quantity: quantity.toNumber(),
|
quantity: quantity.toNumber(),
|
||||||
sectors: symbolProfile.sectors,
|
sectors: symbolProfile.sectors,
|
||||||
url: symbolProfile.url,
|
url: symbolProfile.url,
|
||||||
valueInBaseCurrency: value.toNumber()
|
valueInBaseCurrency: valueInBaseCurrency.toNumber()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1175,7 +1176,7 @@ export class PortfolioService {
|
|||||||
|
|
||||||
const startDate = this.getStartDate(dateRange, portfolioStart);
|
const startDate = this.getStartDate(dateRange, portfolioStart);
|
||||||
const {
|
const {
|
||||||
currentValue,
|
currentValueInBaseCurrency,
|
||||||
errors,
|
errors,
|
||||||
grossPerformance,
|
grossPerformance,
|
||||||
grossPerformancePercentage,
|
grossPerformancePercentage,
|
||||||
@ -1270,7 +1271,7 @@ export class PortfolioService {
|
|||||||
currentNetPerformancePercentWithCurrencyEffect.toNumber(),
|
currentNetPerformancePercentWithCurrencyEffect.toNumber(),
|
||||||
currentNetPerformanceWithCurrencyEffect:
|
currentNetPerformanceWithCurrencyEffect:
|
||||||
currentNetPerformanceWithCurrencyEffect.toNumber(),
|
currentNetPerformanceWithCurrencyEffect.toNumber(),
|
||||||
currentValue: currentValue.toNumber(),
|
currentValue: currentValueInBaseCurrency.toNumber(),
|
||||||
totalInvestment: totalInvestment.toNumber()
|
totalInvestment: totalInvestment.toNumber()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -26,6 +26,7 @@ export const ExchangeRateDataServiceMock = {
|
|||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
USDUSD: {
|
USDUSD: {
|
||||||
'2018-01-01': 1,
|
'2018-01-01': 1,
|
||||||
|
'2021-11-16': 1,
|
||||||
'2023-07-10': 1
|
'2023-07-10': 1
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -73,7 +73,17 @@ export class ExchangeRateDataService {
|
|||||||
currencyTo: targetCurrency
|
currencyTo: targetCurrency
|
||||||
});
|
});
|
||||||
|
|
||||||
let previousExchangeRate = 1;
|
const dateStrings = Object.keys(
|
||||||
|
exchangeRatesByCurrency[`${currency}${targetCurrency}`]
|
||||||
|
);
|
||||||
|
const lastDateString = dateStrings.reduce((a, b) => {
|
||||||
|
return a > b ? a : b;
|
||||||
|
});
|
||||||
|
|
||||||
|
let previousExchangeRate =
|
||||||
|
exchangeRatesByCurrency[`${currency}${targetCurrency}`]?.[
|
||||||
|
lastDateString
|
||||||
|
] ?? 1;
|
||||||
|
|
||||||
// Start from the most recent date and fill in missing exchange rates
|
// Start from the most recent date and fill in missing exchange rates
|
||||||
// using the latest available rate
|
// using the latest available rate
|
||||||
@ -94,7 +104,7 @@ export class ExchangeRateDataService {
|
|||||||
exchangeRatesByCurrency[`${currency}${targetCurrency}`][dateString] =
|
exchangeRatesByCurrency[`${currency}${targetCurrency}`][dateString] =
|
||||||
previousExchangeRate;
|
previousExchangeRate;
|
||||||
|
|
||||||
if (currency === DEFAULT_CURRENCY) {
|
if (currency === DEFAULT_CURRENCY && isBefore(date, new Date())) {
|
||||||
Logger.error(
|
Logger.error(
|
||||||
`No exchange rate has been found for ${currency}${targetCurrency} at ${dateString}`,
|
`No exchange rate has been found for ${currency}${targetCurrency} at ${dateString}`,
|
||||||
'ExchangeRateDataService'
|
'ExchangeRateDataService'
|
||||||
@ -433,13 +443,17 @@ export class ExchangeRateDataService {
|
|||||||
]) *
|
]) *
|
||||||
marketPriceBaseCurrencyToCurrency[format(date, DATE_FORMAT)];
|
marketPriceBaseCurrencyToCurrency[format(date, DATE_FORMAT)];
|
||||||
|
|
||||||
factors[format(date, DATE_FORMAT)] = factor;
|
if (isNaN(factor)) {
|
||||||
|
throw new Error('Exchange rate is not a number');
|
||||||
|
} else {
|
||||||
|
factors[format(date, DATE_FORMAT)] = factor;
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
Logger.error(
|
Logger.error(
|
||||||
`No exchange rate has been found for ${currencyFrom}${currencyTo} at ${format(
|
`No exchange rate has been found for ${currencyFrom}${currencyTo} at ${format(
|
||||||
date,
|
date,
|
||||||
DATE_FORMAT
|
DATE_FORMAT
|
||||||
)}`,
|
)}. Please complement market data for ${DEFAULT_CURRENCY}${currencyFrom} and ${DEFAULT_CURRENCY}${currencyTo}.`,
|
||||||
'ExchangeRateDataService'
|
'ExchangeRateDataService'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -27,4 +27,5 @@ export interface TimelinePosition {
|
|||||||
timeWeightedInvestment: Big;
|
timeWeightedInvestment: Big;
|
||||||
timeWeightedInvestmentWithCurrencyEffect: Big;
|
timeWeightedInvestmentWithCurrencyEffect: Big;
|
||||||
transactionCount: number;
|
transactionCount: number;
|
||||||
|
valueInBaseCurrency: Big;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user